forked from sass/tipibot
Added Fienta integration
This commit is contained in:
93
bot.py
93
bot.py
@@ -18,10 +18,11 @@ from discord.ext import tasks
|
||||
|
||||
import colorlog
|
||||
import psutil
|
||||
from aiohttp import web
|
||||
|
||||
import config
|
||||
import strings as S
|
||||
from core import economy, pb_client, sheets
|
||||
from core import economy, lan_fienta, pb_client, sheets
|
||||
from core.member_sync import SyncResult
|
||||
from commands.dev_member_commands import register_dev_member_commands
|
||||
from commands.dev_member_runtime import handle_member_join, run_birthday_daily
|
||||
@@ -33,6 +34,7 @@ from commands.economy_income_commands import register_economy_income_commands
|
||||
from commands.economy_prestige_commands import register_prestige_commands
|
||||
from commands.economy_profile_commands import register_economy_profile_commands
|
||||
from commands.economy_support_commands import register_economy_support_commands
|
||||
from commands.lan_fienta_commands import register_lan_fienta_commands
|
||||
from commands.ops_channel_commands import register_ops_channel_commands
|
||||
from commands.ops_admin_commands import register_ops_admin_commands
|
||||
|
||||
@@ -94,6 +96,7 @@ tree = app_commands.CommandTree(bot)
|
||||
|
||||
GUILD_OBJ = discord.Object(id=config.GUILD_ID)
|
||||
IS_DEV_PROFILE = config.BOT_PROFILE == "dev"
|
||||
IS_LAN_PROFILE = config.BOT_PROFILE == "lan"
|
||||
TALLINN_TZ = ZoneInfo("Europe/Tallinn")
|
||||
_start_time = datetime.datetime.now()
|
||||
_process = psutil.Process()
|
||||
@@ -112,6 +115,8 @@ _RESTART_FILE = _DATA_DIR / "restart_channel.json"
|
||||
_BOT_CONFIG = _DATA_DIR / "bot_config.json"
|
||||
_PAUSED = False # maintenance mode: blocks non-admin commands when True
|
||||
_DEV_ONLY_COMMANDS: tuple[str, ...] = ("birthdays", "check", "member")
|
||||
_LAN_ONLY_COMMANDS: tuple[str, ...] = ("fientasync",)
|
||||
_FIENTA_RUNNER: web.AppRunner | None = None
|
||||
|
||||
|
||||
def _apply_profile_command_filters() -> None:
|
||||
@@ -161,6 +166,64 @@ def _member_cache_size() -> int:
|
||||
return len(sheets.get_cache())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fienta webhook server (LAN profile)
|
||||
# ---------------------------------------------------------------------------
|
||||
async def _fienta_health(_: web.Request) -> web.Response:
|
||||
return web.json_response({"ok": True, "profile": config.BOT_PROFILE})
|
||||
|
||||
|
||||
async def _process_fienta_payload(payload: dict, source: str) -> None:
|
||||
try:
|
||||
summary = await lan_fienta.process_payload(bot, payload)
|
||||
log.info("Fienta %s webhook processed: %s", source, summary.short())
|
||||
except Exception as exc:
|
||||
log.exception("Fienta %s webhook processing failed: %s", source, exc)
|
||||
|
||||
|
||||
async def _accept_fienta_payload(request: web.Request, source: str) -> web.Response:
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception:
|
||||
return web.json_response({"ok": False, "error": "invalid JSON"}, status=400)
|
||||
asyncio.create_task(_process_fienta_payload(payload, source))
|
||||
return web.json_response({"ok": True, "accepted": True, "source": source})
|
||||
|
||||
|
||||
async def _handle_fienta_secret_webhook(request: web.Request) -> web.Response:
|
||||
if not config.FIENTA_WEBHOOK_SECRET:
|
||||
return web.json_response({"ok": False, "error": "webhook secret not configured"}, status=503)
|
||||
if request.match_info.get("secret") != config.FIENTA_WEBHOOK_SECRET:
|
||||
return web.json_response({"ok": False, "error": "not found"}, status=404)
|
||||
return await _accept_fienta_payload(request, "secret")
|
||||
|
||||
|
||||
async def _handle_fienta_purchase(request: web.Request) -> web.Response:
|
||||
return await _accept_fienta_payload(request, "purchase")
|
||||
|
||||
|
||||
async def _handle_fienta_registration(request: web.Request) -> web.Response:
|
||||
return await _accept_fienta_payload(request, "registration")
|
||||
|
||||
|
||||
async def _start_fienta_webhook() -> None:
|
||||
global _FIENTA_RUNNER
|
||||
if not IS_LAN_PROFILE or _FIENTA_RUNNER is not None:
|
||||
return
|
||||
app = web.Application(client_max_size=5 * 1024 * 1024)
|
||||
app.router.add_get("/health", _fienta_health)
|
||||
app.router.add_post("/fienta/purchase", _handle_fienta_purchase)
|
||||
app.router.add_post("/fienta/registration", _handle_fienta_registration)
|
||||
app.router.add_post("/fienta/webhook/{secret}", _handle_fienta_secret_webhook)
|
||||
|
||||
runner = web.AppRunner(app)
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, "0.0.0.0", config.FIENTA_WEBHOOK_PORT)
|
||||
await site.start()
|
||||
_FIENTA_RUNNER = runner
|
||||
log.info("LAN Fienta webhook listening on 0.0.0.0:%s", config.FIENTA_WEBHOOK_PORT)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EXP / Level role helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -389,6 +452,13 @@ async def on_ready():
|
||||
log.info("Loaded %d member rows from Google Sheets", len(data))
|
||||
except Exception as e:
|
||||
log.error("Failed to load sheet on startup: %s", e)
|
||||
elif IS_LAN_PROFILE:
|
||||
try:
|
||||
created = await lan_fienta.ensure_storage()
|
||||
if created:
|
||||
log.info("Created LAN Fienta PocketBase collection '%s'", config.PB_FIENTA_COLLECTION_LAN)
|
||||
except Exception as e:
|
||||
log.error("Failed to prepare LAN Fienta storage: %s", e)
|
||||
|
||||
# Sync slash commands to the guild only; wipe any leftover global registrations
|
||||
tree.copy_global_to(guild=GUILD_OBJ)
|
||||
@@ -407,6 +477,10 @@ async def on_ready():
|
||||
_rotate_presence.start()
|
||||
log.info("Rich presence rotation started")
|
||||
|
||||
# Start Fienta webhook for LAN registration sync
|
||||
if IS_LAN_PROFILE:
|
||||
await _start_fienta_webhook()
|
||||
|
||||
# Re-schedule any reminder tasks lost on restart
|
||||
await _restore_reminders()
|
||||
|
||||
@@ -436,6 +510,11 @@ async def on_resumed():
|
||||
@bot.event
|
||||
async def on_member_join(member: discord.Member):
|
||||
"""When someone joins, look them up in the sheet and sync."""
|
||||
if IS_LAN_PROFILE:
|
||||
summary = await lan_fienta.sync_member_join(bot, member)
|
||||
if summary.roles_synced or summary.alerts:
|
||||
log.info("LAN join Fienta sync for %s: %s", member, summary.short())
|
||||
return
|
||||
if not IS_DEV_PROFILE:
|
||||
return
|
||||
await handle_member_join(
|
||||
@@ -460,6 +539,9 @@ if IS_DEV_PROFILE:
|
||||
mark_announced_today=_mark_announced_today,
|
||||
)
|
||||
|
||||
if IS_LAN_PROFILE:
|
||||
register_lan_fienta_commands(tree, bot, log)
|
||||
|
||||
register_ops_admin_commands(
|
||||
tree,
|
||||
bot,
|
||||
@@ -494,6 +576,7 @@ async def cmd_ping(interaction: discord.Interaction):
|
||||
# ---------------------------------------------------------------------------
|
||||
_HELP_PAGE_SIZE = 10
|
||||
_DEV_ONLY_HELP_TOKENS: tuple[str, ...] = tuple(f"/{name}" for name in _DEV_ONLY_COMMANDS)
|
||||
_LAN_ONLY_HELP_TOKENS: tuple[str, ...] = tuple(f"/{name}" for name in _LAN_ONLY_COMMANDS)
|
||||
|
||||
|
||||
def _visible_help_fields(category_key: str) -> list[tuple[str, str]]:
|
||||
@@ -506,6 +589,8 @@ def _visible_help_fields(category_key: str) -> list[tuple[str, str]]:
|
||||
blob = f"{name}\n{value}".lower()
|
||||
if any(tok in blob for tok in _DEV_ONLY_HELP_TOKENS):
|
||||
continue
|
||||
if not IS_LAN_PROFILE and any(tok in blob for tok in _LAN_ONLY_HELP_TOKENS):
|
||||
continue
|
||||
visible.append((name, value))
|
||||
return visible
|
||||
|
||||
@@ -881,7 +966,11 @@ def _asyncio_exception_handler(loop: asyncio.AbstractEventLoop, context: dict) -
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not config.DISCORD_TOKEN:
|
||||
profile_key = "DISCORD_TOKEN_ECONOMY" if config.BOT_PROFILE == "economy" else "DISCORD_TOKEN_DEV"
|
||||
profile_key = {
|
||||
"dev": "DISCORD_TOKEN_DEV",
|
||||
"economy": "DISCORD_TOKEN_ECONOMY",
|
||||
"lan": "DISCORD_BOT_LAN",
|
||||
}[config.BOT_PROFILE]
|
||||
raise SystemExit(
|
||||
f"{profile_key} pole seadistatud profiilile '{config.BOT_PROFILE}'. "
|
||||
"Kopeeri .env.example failiks .env ja täida see."
|
||||
|
||||
Reference in New Issue
Block a user