feat: integrate config.yaml values into environment for enhanced flexibility
- Added functionality to load values from config.yaml into the environment, allowing os.getenv() to access them. - Ensured that existing environment variables take precedence over config values. - Updated DiscordAdapter to resolve usernames in DISCORD_ALLOWED_USERS to numeric IDs, improving user authorization checks. - Enhanced event handling to provide clearer logging and ensure proper synchronization of slash commands.
This commit is contained in:
parent
e0ed44388f
commit
92447141d9
2 changed files with 92 additions and 7 deletions
|
|
@ -80,11 +80,12 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Set up intents
|
# Set up intents -- members intent needed for username-to-ID resolution
|
||||||
intents = Intents.default()
|
intents = Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
intents.dm_messages = True
|
intents.dm_messages = True
|
||||||
intents.guild_messages = True
|
intents.guild_messages = True
|
||||||
|
intents.members = True
|
||||||
|
|
||||||
# Create bot
|
# Create bot
|
||||||
self._client = commands.Bot(
|
self._client = commands.Bot(
|
||||||
|
|
@ -92,24 +93,30 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
intents=intents,
|
intents=intents,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse allowed user IDs for button authorization
|
# Parse allowed user entries (may contain usernames or IDs)
|
||||||
allowed_env = os.getenv("DISCORD_ALLOWED_USERS", "")
|
allowed_env = os.getenv("DISCORD_ALLOWED_USERS", "")
|
||||||
if allowed_env:
|
if allowed_env:
|
||||||
self._allowed_user_ids = {
|
self._allowed_user_ids = {
|
||||||
uid.strip() for uid in allowed_env.split(",") if uid.strip()
|
uid.strip() for uid in allowed_env.split(",") if uid.strip()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adapter_self = self # capture for closure
|
||||||
|
|
||||||
# Register event handlers
|
# Register event handlers
|
||||||
@self._client.event
|
@self._client.event
|
||||||
async def on_ready():
|
async def on_ready():
|
||||||
print(f"[{self.name}] Connected as {self._client.user}")
|
print(f"[{adapter_self.name}] Connected as {adapter_self._client.user}")
|
||||||
|
|
||||||
|
# Resolve any usernames in the allowed list to numeric IDs
|
||||||
|
await adapter_self._resolve_allowed_usernames()
|
||||||
|
|
||||||
# Sync slash commands with Discord
|
# Sync slash commands with Discord
|
||||||
try:
|
try:
|
||||||
synced = await self._client.tree.sync()
|
synced = await adapter_self._client.tree.sync()
|
||||||
print(f"[{self.name}] Synced {len(synced)} slash command(s)")
|
print(f"[{adapter_self.name}] Synced {len(synced)} slash command(s)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[{self.name}] Slash command sync failed: {e}")
|
print(f"[{adapter_self.name}] Slash command sync failed: {e}")
|
||||||
self._ready_event.set()
|
adapter_self._ready_event.set()
|
||||||
|
|
||||||
@self._client.event
|
@self._client.event
|
||||||
async def on_message(message: DiscordMessage):
|
async def on_message(message: DiscordMessage):
|
||||||
|
|
@ -341,6 +348,70 @@ class DiscordAdapter(BasePlatformAdapter):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"name": str(chat_id), "type": "dm", "error": str(e)}
|
return {"name": str(chat_id), "type": "dm", "error": str(e)}
|
||||||
|
|
||||||
|
async def _resolve_allowed_usernames(self) -> None:
|
||||||
|
"""
|
||||||
|
Resolve non-numeric entries in DISCORD_ALLOWED_USERS to Discord user IDs.
|
||||||
|
|
||||||
|
Users can specify usernames (e.g. "teknium") or display names instead of
|
||||||
|
raw numeric IDs. After resolution, the env var and internal set are updated
|
||||||
|
so authorization checks work with IDs only.
|
||||||
|
"""
|
||||||
|
if not self._allowed_user_ids or not self._client:
|
||||||
|
return
|
||||||
|
|
||||||
|
numeric_ids = set()
|
||||||
|
to_resolve = set()
|
||||||
|
|
||||||
|
for entry in self._allowed_user_ids:
|
||||||
|
if entry.isdigit():
|
||||||
|
numeric_ids.add(entry)
|
||||||
|
else:
|
||||||
|
to_resolve.add(entry.lower())
|
||||||
|
|
||||||
|
if not to_resolve:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"[{self.name}] Resolving {len(to_resolve)} username(s): {', '.join(to_resolve)}")
|
||||||
|
resolved_count = 0
|
||||||
|
|
||||||
|
for guild in self._client.guilds:
|
||||||
|
# Fetch full member list (requires members intent)
|
||||||
|
try:
|
||||||
|
members = guild.members
|
||||||
|
if len(members) < guild.member_count:
|
||||||
|
members = [m async for m in guild.fetch_members(limit=None)]
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to fetch members for guild %s: %s", guild.name, e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for member in members:
|
||||||
|
name_lower = member.name.lower()
|
||||||
|
display_lower = member.display_name.lower()
|
||||||
|
global_lower = (member.global_name or "").lower()
|
||||||
|
|
||||||
|
matched = name_lower in to_resolve or display_lower in to_resolve or global_lower in to_resolve
|
||||||
|
if matched:
|
||||||
|
uid = str(member.id)
|
||||||
|
numeric_ids.add(uid)
|
||||||
|
resolved_count += 1
|
||||||
|
matched_name = name_lower if name_lower in to_resolve else (
|
||||||
|
display_lower if display_lower in to_resolve else global_lower
|
||||||
|
)
|
||||||
|
to_resolve.discard(matched_name)
|
||||||
|
print(f"[{self.name}] Resolved '{matched_name}' -> {uid} ({member.name}#{member.discriminator})")
|
||||||
|
|
||||||
|
if not to_resolve:
|
||||||
|
break
|
||||||
|
|
||||||
|
if to_resolve:
|
||||||
|
print(f"[{self.name}] Could not resolve usernames: {', '.join(to_resolve)}")
|
||||||
|
|
||||||
|
# Update internal set and env var so gateway auth checks use IDs
|
||||||
|
self._allowed_user_ids = numeric_ids
|
||||||
|
os.environ["DISCORD_ALLOWED_USERS"] = ",".join(sorted(numeric_ids))
|
||||||
|
if resolved_count:
|
||||||
|
print(f"[{self.name}] Updated DISCORD_ALLOWED_USERS with {resolved_count} resolved ID(s)")
|
||||||
|
|
||||||
def format_message(self, content: str) -> str:
|
def format_message(self, content: str) -> str:
|
||||||
"""
|
"""
|
||||||
Format message for Discord.
|
Format message for Discord.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,20 @@ if _env_path.exists():
|
||||||
# Also try project .env as fallback
|
# Also try project .env as fallback
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
# Bridge config.yaml values into the environment so os.getenv() picks them up.
|
||||||
|
# Values already set in the environment (from .env or shell) take precedence.
|
||||||
|
_config_path = Path.home() / '.hermes' / 'config.yaml'
|
||||||
|
if _config_path.exists():
|
||||||
|
try:
|
||||||
|
import yaml as _yaml
|
||||||
|
with open(_config_path) as _f:
|
||||||
|
_cfg = _yaml.safe_load(_f) or {}
|
||||||
|
for _key, _val in _cfg.items():
|
||||||
|
if isinstance(_val, (str, int, float, bool)) and _key not in os.environ:
|
||||||
|
os.environ[_key] = str(_val)
|
||||||
|
except Exception:
|
||||||
|
pass # Non-fatal; gateway can still run with .env values
|
||||||
|
|
||||||
# Gateway runs in quiet mode - suppress debug output and use cwd directly (no temp dirs)
|
# Gateway runs in quiet mode - suppress debug output and use cwd directly (no temp dirs)
|
||||||
os.environ["HERMES_QUIET"] = "1"
|
os.environ["HERMES_QUIET"] = "1"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue