Skip to content

Commit 6b7754e

Browse files
committed
Unify shared modules and increase API rate limits
- Symlink shared modules (footer, logger, health, status_webhook, http, retry) - Update footer to use server icon instead of owner avatar - Increase stats API rate limits for dashboard - Update presence handler to use unified base class
1 parent 9092680 commit 6b7754e

File tree

16 files changed

+356
-1860
lines changed

16 files changed

+356
-1860
lines changed

shared

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../shared

src/bot.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,40 @@
99
"""
1010

1111
import asyncio
12+
import os
1213
import discord
1314
from discord.ext import commands
1415
from typing import Optional
1516

1617
from src.core.config import config, validate_config
18+
19+
20+
# =============================================================================
21+
# Guild Protection
22+
# =============================================================================
23+
24+
def _get_authorized_guilds() -> set:
25+
"""Get authorized guild IDs from environment."""
26+
guilds = set()
27+
syria_id = os.getenv("GUILD_ID")
28+
mods_id = os.getenv("MODS_GUILD_ID")
29+
if syria_id:
30+
guilds.add(int(syria_id))
31+
if mods_id:
32+
guilds.add(int(mods_id))
33+
return guilds
34+
35+
AUTHORIZED_GUILD_IDS = _get_authorized_guilds()
36+
1737
from src.core.logger import log
1838
from src.services.tempvoice import TempVoiceService
1939
from src.services.sync_profile import ProfileSyncService
2040
from src.services.xp import XPService
2141
from src.services.xp import card as rank_card
2242
from src.services.stats_api import SyriaAPI
43+
from src.core.health import HealthCheckServer
2344
from src.services.status_webhook import get_status_service
45+
from src.services.backup import BackupScheduler
2446
from src.services.afk import AFKService
2547
from src.services.gallery import GalleryService
2648
from src.services.presence import PresenceHandler
@@ -62,6 +84,7 @@ def __init__(self) -> None:
6284
self.profile_sync: Optional[ProfileSyncService] = None
6385
self.xp_service: Optional[XPService] = None
6486
self.stats_api: Optional[SyriaAPI] = None
87+
self.health_server: Optional[HealthCheckServer] = None
6588
self.status_webhook = None
6689
self.afk_service: Optional[AFKService] = None
6790
self.gallery_service: Optional[GalleryService] = None
@@ -73,6 +96,7 @@ def __init__(self) -> None:
7396
self.city_game_service: Optional[CityGameService] = None
7497
self.guide_service: Optional[GuideService] = None
7598
self.social_monitor: Optional[SocialMonitorService] = None
99+
self.backup_scheduler: Optional[BackupScheduler] = None
76100

77101
async def setup_hook(self) -> None:
78102
"""Called when the bot is starting up."""
@@ -177,6 +201,58 @@ async def _on_app_command_error(
177201
("ID", str(interaction.user.id)),
178202
])
179203

204+
# =========================================================================
205+
# Guild Protection
206+
# =========================================================================
207+
208+
async def on_guild_join(self, guild: discord.Guild) -> None:
209+
"""Leave immediately if guild is not authorized."""
210+
# Safety: Don't leave if authorized set is empty (misconfigured env)
211+
if not AUTHORIZED_GUILD_IDS:
212+
return
213+
if guild.id not in AUTHORIZED_GUILD_IDS:
214+
log.warning("Added To Unauthorized Guild - Leaving", [
215+
("Guild", guild.name),
216+
("ID", str(guild.id)),
217+
])
218+
try:
219+
await guild.leave()
220+
except Exception as e:
221+
log.error("Failed To Leave Unauthorized Guild", [
222+
("Guild", guild.name),
223+
("Error", str(e)),
224+
])
225+
226+
async def _leave_unauthorized_guilds(self) -> None:
227+
"""Leave any guilds not in AUTHORIZED_GUILD_IDS."""
228+
# Safety: Don't leave any guilds if authorized set is empty (misconfigured env)
229+
if not AUTHORIZED_GUILD_IDS:
230+
log.warning("Guild Protection Skipped", [
231+
("Reason", "AUTHORIZED_GUILD_IDS is empty"),
232+
("Action", "Check GUILD_ID and MODS_GUILD_ID in .env"),
233+
])
234+
return
235+
unauthorized = [g for g in self.guilds if g.id not in AUTHORIZED_GUILD_IDS]
236+
if not unauthorized:
237+
return
238+
239+
log.tree("Leaving Unauthorized Guilds", [
240+
("Count", str(len(unauthorized))),
241+
], emoji="⚠️")
242+
243+
for guild in unauthorized:
244+
try:
245+
log.warning("Leaving Unauthorized Guild", [
246+
("Guild", guild.name),
247+
("ID", str(guild.id)),
248+
])
249+
await guild.leave()
250+
except Exception as e:
251+
log.error("Failed To Leave Guild", [
252+
("Guild", guild.name),
253+
("Error", str(e)),
254+
])
255+
180256
async def _init_services(self) -> None:
181257
"""Initialize bot services."""
182258
log.tree("Services Init", [
@@ -190,6 +266,9 @@ async def _init_services(self) -> None:
190266
("Action", "Check environment variables"),
191267
], emoji="🚨")
192268

269+
# Leave unauthorized guilds before initializing services
270+
await self._leave_unauthorized_guilds()
271+
193272
# Check database health first - critical for most services
194273
if not db.is_healthy:
195274
log.tree("CRITICAL: Database Unhealthy", [
@@ -227,6 +306,47 @@ async def _init_services(self) -> None:
227306
except Exception as e:
228307
log.error_tree("XP Service Init Failed", e)
229308

309+
# Health Check Server (unified)
310+
try:
311+
self.health_server = HealthCheckServer(self)
312+
313+
# Register database health callback
314+
async def db_health() -> dict:
315+
try:
316+
connected = db.is_healthy
317+
error = db.corruption_reason if not connected else None
318+
return {"connected": connected, "error": error}
319+
except Exception as e:
320+
return {"connected": False, "error": str(e)}
321+
322+
self.health_server.register_db_health(db_health)
323+
324+
# Register system health callback
325+
import psutil
326+
_psutil_process = psutil.Process()
327+
328+
async def system_health() -> dict:
329+
cpu_percent = psutil.cpu_percent(interval=None)
330+
memory = psutil.virtual_memory()
331+
disk = psutil.disk_usage("/")
332+
bot_memory_mb = _psutil_process.memory_info().rss / (1024 * 1024)
333+
return {
334+
"cpu_percent": cpu_percent,
335+
"memory_percent": memory.percent,
336+
"disk_percent": disk.percent,
337+
"disk_total_gb": round(disk.total / (1024 ** 3), 1),
338+
"disk_used_gb": round(disk.used / (1024 ** 3), 1),
339+
"bot_memory_mb": round(bot_memory_mb, 1),
340+
"threads": _psutil_process.num_threads(),
341+
"open_files": len(_psutil_process.open_files()),
342+
}
343+
344+
self.health_server.register_system(system_health)
345+
await self.health_server.start()
346+
initialized.append("HealthServer")
347+
except Exception as e:
348+
log.error_tree("Health Server Init Failed", e)
349+
230350
# Stats API
231351
try:
232352
self.stats_api = SyriaAPI(self)
@@ -238,14 +358,22 @@ async def _init_services(self) -> None:
238358
# Status Webhook
239359
if config.STATUS_WEBHOOK_URL:
240360
try:
241-
self.status_webhook = get_status_service(config.STATUS_WEBHOOK_URL)
361+
self.status_webhook = get_status_service(config.STATUS_WEBHOOK_URL, bot_name="SyriaBot")
242362
self.status_webhook.set_bot(self)
243363
await self.status_webhook.send_startup_alert()
244364
await self.status_webhook.start_hourly_alerts()
245365
initialized.append("StatusWebhook")
246366
except Exception as e:
247367
log.error_tree("Status Webhook Init Failed", e)
248368

369+
# Backup Scheduler
370+
try:
371+
self.backup_scheduler = BackupScheduler()
372+
await self.backup_scheduler.start()
373+
initialized.append("Backup")
374+
except Exception as e:
375+
log.error_tree("Backup Scheduler Init Failed", e)
376+
249377
# AFK Service
250378
try:
251379
self.afk_service = AFKService(self)
@@ -359,6 +487,13 @@ async def close(self) -> None:
359487
except Exception as e:
360488
log.error_tree("Status Webhook Stop Error", e)
361489

490+
if self.backup_scheduler:
491+
try:
492+
await self.backup_scheduler.stop()
493+
stopped.append("Backup")
494+
except Exception as e:
495+
log.error_tree("Backup Scheduler Stop Error", e)
496+
362497
if self.stats_api:
363498
try:
364499
await self.stats_api.stop()

src/core/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class Config:
113113
MOD_ROLE_ID: int = _env_int("MOD_ROLE_ID")
114114
AUTO_ROLE_ID: int = _env_int("SYRIA_AUTO_ROLE")
115115
BOOSTER_ROLE_ID: int = _env_int("SYRIA_BOOSTER_ROLE")
116+
DEAD_CHAT_ROLE_ID: int = _env_int("SYRIA_DEAD_CHAT_ROLE")
116117

117118
# ==========================================================================
118119
# Channels
@@ -127,6 +128,7 @@ class Config:
127128
BANK_CHANNEL_ID: int = _env_int("SYRIA_BANK_CH")
128129
DEBATES_CHANNEL_ID: int = _env_int("SYRIA_DEBATES_CH")
129130
INBOX_CHANNEL_ID: int = _env_int("SYRIA_INBOX_CH")
131+
GIVEAWAY_CHANNEL_ID: int = _env_int("SYRIA_GIVEAWAY_CH")
130132

131133
# ==========================================================================
132134
# TempVoice
@@ -211,7 +213,7 @@ class Config:
211213
# ==========================================================================
212214
# Webhooks
213215
# ==========================================================================
214-
STATUS_WEBHOOK_URL: str = _env("STATUS_WEBHOOK")
216+
STATUS_WEBHOOK_URL: str = _env("STATUS_WEBHOOK_URL")
215217

216218
# ==========================================================================
217219
# URLs

src/core/health.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/Users/johnhamwi/Developer/shared/health.py

0 commit comments

Comments
 (0)