feat: Migrate Arbiter and Modpack Version Checker to monorepo
WHAT WAS DONE: - Migrated Arbiter (discord-oauth-arbiter) code to services/arbiter/ - Migrated Modpack Version Checker code to services/modpack-version-checker/ - Created .env.example for Arbiter with all required environment variables - Moved systemd service file to services/arbiter/deploy/ - Organized directory structure per Gemini monorepo recommendations WHY: - Consolidate all service code in one repository - Prepare for Gemini code review (Panel v1.12 compatibility check) - Enable service-prefixed Git tagging (arbiter-v2.1.0, modpack-v1.0.0) - Support npm workspaces for shared dependencies SERVICES MIGRATED: 1. Arbiter (Discord OAuth bot) - Originally written by Gemini + Claude - Full source code from ops-manual docs/implementation/ - Created comprehensive .env.example - Ready for Panel v1.12 compatibility verification 2. Modpack Version Checker (Python CLI tool) - Full source code from ops-manual docs/tasks/ - Written for Panel v1.11, needs Gemini review for v1.12 - Never had code review before STILL TODO: - Whitelist Manager - Pull from Billing VPS (38.68.14.188) - Currently deployed and running - Needs Panel v1.12 API compatibility fix (Task #86) - Requires SSH access to pull code NEXT STEPS: - Gemini code review for Panel v1.12 API compatibility - Create package.json for each service - Test npm workspaces integration - Deploy after verification FILES: - services/arbiter/ (25 new files, full application) - services/modpack-version-checker/ (21 new files, full application) Signed-off-by: The Golden Chronicler <claude@firefrostgaming.com>
This commit is contained in:
122
services/modpack-version-checker/src/modpack_checker/notifier.py
Normal file
122
services/modpack-version-checker/src/modpack_checker/notifier.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Notification delivery — Discord webhooks and generic HTTP webhooks."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class NotificationError(Exception):
|
||||
"""Raised when a notification cannot be delivered."""
|
||||
|
||||
|
||||
class DiscordNotifier:
|
||||
"""Send modpack update alerts to a Discord channel via webhook.
|
||||
|
||||
Embeds are used so the messages look polished without additional
|
||||
configuration on the user's side.
|
||||
"""
|
||||
|
||||
_EMBED_COLOR_UPDATE = 0xF5A623 # amber — update available
|
||||
_EMBED_COLOR_TEST = 0x43B581 # green — test / OK
|
||||
|
||||
def __init__(self, webhook_url: str, timeout: int = 10) -> None:
|
||||
self.webhook_url = webhook_url
|
||||
self.timeout = timeout
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Public helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def send_update(
|
||||
self,
|
||||
modpack_name: str,
|
||||
curseforge_id: int,
|
||||
old_version: Optional[str],
|
||||
new_version: str,
|
||||
) -> None:
|
||||
"""Post an update-available embed to Discord.
|
||||
|
||||
Raises:
|
||||
NotificationError: if the webhook request fails.
|
||||
"""
|
||||
embed = {
|
||||
"title": "Modpack Update Available",
|
||||
"color": self._EMBED_COLOR_UPDATE,
|
||||
"url": f"https://www.curseforge.com/minecraft/modpacks/{curseforge_id}",
|
||||
"fields": [
|
||||
{
|
||||
"name": "Modpack",
|
||||
"value": modpack_name,
|
||||
"inline": True,
|
||||
},
|
||||
{
|
||||
"name": "CurseForge ID",
|
||||
"value": str(curseforge_id),
|
||||
"inline": True,
|
||||
},
|
||||
{
|
||||
"name": "Previous Version",
|
||||
"value": old_version or "Unknown",
|
||||
"inline": False,
|
||||
},
|
||||
{
|
||||
"name": "New Version",
|
||||
"value": new_version,
|
||||
"inline": False,
|
||||
},
|
||||
],
|
||||
"footer": {
|
||||
"text": "Modpack Version Checker \u2022 Firefrost Gaming",
|
||||
},
|
||||
}
|
||||
self._post({"embeds": [embed]})
|
||||
|
||||
def test(self) -> None:
|
||||
"""Send a simple test message to verify the webhook URL works.
|
||||
|
||||
Raises:
|
||||
NotificationError: if the request fails.
|
||||
"""
|
||||
self._post(
|
||||
{
|
||||
"embeds": [
|
||||
{
|
||||
"title": "Webhook Connected",
|
||||
"description": (
|
||||
"Modpack Version Checker notifications are working correctly."
|
||||
),
|
||||
"color": self._EMBED_COLOR_TEST,
|
||||
"footer": {"text": "Modpack Version Checker \u2022 Firefrost Gaming"},
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Internal
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _post(self, payload: dict) -> None:
|
||||
try:
|
||||
response = requests.post(
|
||||
self.webhook_url,
|
||||
json=payload,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
# Discord returns 204 No Content on success
|
||||
if response.status_code not in (200, 204):
|
||||
raise NotificationError(
|
||||
f"Discord returned HTTP {response.status_code}: {response.text[:200]}"
|
||||
)
|
||||
except requests.ConnectionError as exc:
|
||||
raise NotificationError(
|
||||
"Could not reach Discord. Check your internet connection."
|
||||
) from exc
|
||||
except requests.Timeout:
|
||||
raise NotificationError(
|
||||
f"Discord webhook timed out after {self.timeout}s."
|
||||
)
|
||||
except requests.RequestException as exc:
|
||||
raise NotificationError(f"Webhook request failed: {exc}") from exc
|
||||
Reference in New Issue
Block a user