1
0
forked from sass/tipibot

Added Fienta integration

This commit is contained in:
AlacrisDevs
2026-04-29 22:38:47 +03:00
parent a4a447867f
commit 3c2b4342a2
12 changed files with 1336 additions and 29 deletions

93
bot.py
View File

@@ -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."