-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathconfig.py
More file actions
115 lines (100 loc) · 3.97 KB
/
config.py
File metadata and controls
115 lines (100 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""Configuration management using Pydantic settings."""
from __future__ import annotations
import os
from typing import Any, Optional
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Config(BaseSettings):
"""Bot configuration settings."""
discord_token: str = Field(default='demo_token')
# Backwards compatible single guild id
guild_id: Optional[int] = Field(default=None)
# Preferred: comma-separated list (or JSON list) of guild ids for fast per-guild slash-command sync
guild_ids: list[int] = Field(default_factory=list)
log_level: str = Field(default='INFO')
owner_id: Optional[int] = Field(default=None)
topgg_token: Optional[str] = Field(default=None)
topgg_webhook_secret: Optional[str] = Field(default=None)
redis_url: Optional[str] = Field(default=None)
# CodeBuddy settings
question_channel_id: Optional[int] = Field(default=None)
model_config = SettingsConfigDict(
env_file='.env',
case_sensitive=False,
extra='ignore' # Ignore extra fields from .env
)
@field_validator('guild_id', mode='before')
@classmethod
def _parse_guild_id(cls, v: Any) -> Optional[int]:
"""Parse legacy single guild id.
Some deployments mistakenly set `GUILD_ID` as a CSV / JSON list.
To stay backwards compatible and avoid startup failures, we accept:
- int
- numeric string
- CSV: "1,2,3" (uses the first value)
- JSON list: "[1,2]" (uses the first value)
"""
if v is None:
return None
if isinstance(v, int):
return v
if isinstance(v, str):
s = v.strip()
if not s:
return None
if s.startswith('[') and s.endswith(']'):
try:
import json
parsed = json.loads(s)
if isinstance(parsed, list) and parsed:
return int(parsed[0])
except Exception:
# Fall back to CSV/int parsing
pass
if ',' in s:
first = s.split(',', 1)[0].strip()
return int(first) if first else None
return int(s)
return int(v)
@field_validator('guild_ids', mode='before')
@classmethod
def _parse_guild_ids(cls, v: Any) -> list[int]:
"""Parse guild ids from env.
Supports:
- unset / empty -> []
- JSON list: "[1,2]"
- CSV: "1,2,3"
"""
if v is None:
# If GUILD_IDS isn't set, allow legacy GUILD_ID to behave like a list.
legacy = os.getenv('GUILD_ID')
if legacy and str(legacy).strip():
v = legacy
else:
return []
if isinstance(v, list):
return [int(x) for x in v if str(x).strip()]
if isinstance(v, (int, str)):
s = str(v).strip()
if not s:
return []
# JSON list
if s.startswith('[') and s.endswith(']'):
try:
import json
parsed = json.loads(s)
if isinstance(parsed, list):
return [int(x) for x in parsed]
except Exception:
# Fall back to CSV parsing
pass
# CSV
parts = [p.strip() for p in s.split(',')]
return [int(p) for p in parts if p]
raise TypeError('guild_ids must be a list, int, or string')
@model_validator(mode='after')
def _coerce_single_guild_to_list(self) -> 'Config':
# If only legacy GUILD_ID is set, treat it as a single-item list.
if not self.guild_ids and self.guild_id:
self.guild_ids = [int(self.guild_id)]
return self