116 lines
4 KiB
Python
116 lines
4 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import signal
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .auth import print_password_hash
|
|
from .core import BotRuntime, bool_env, env, load_dotenv, normalize_discord_token
|
|
from .db import initialize_database, migrate_runtime_documents
|
|
from .dashboard import maybe_start_dashboard
|
|
from .discord_api import DiscordGatewayManager, discord_bot_identity
|
|
from .media import maybe_run_jellyfin_sync
|
|
from .status import load_services, print_preview, run_check_cycle
|
|
from .watchparty import initialize_watchparty_schema
|
|
|
|
|
|
def main() -> int:
|
|
load_dotenv()
|
|
|
|
if "--hash-password" in sys.argv:
|
|
print_password_hash()
|
|
return 0
|
|
|
|
if "--preview" in sys.argv:
|
|
config_path = Path(os.getenv("ARCHIVE_STATUS_CONFIG", "services.json"))
|
|
initialize_database()
|
|
migrate_runtime_documents([config_path])
|
|
print_preview(load_services(config_path))
|
|
return 0
|
|
|
|
token = normalize_discord_token(env("DISCORD_BOT_TOKEN"))
|
|
channel_id = os.getenv("DISCORD_CHANNEL_ID", "").strip()
|
|
config_path = Path(env("ARCHIVE_STATUS_CONFIG", "services.json"))
|
|
state_path = Path(env("ARCHIVE_STATUS_STATE", "state/status-message.json"))
|
|
media_state_path = Path(env("MEDIA_CATALOG_STATE", "state/media-catalog.json"))
|
|
media_library_path = Path(env("MEDIA_LIBRARY_STATE", "state/media-library.json"))
|
|
settings_path = Path(env("BOT_SETTINGS_STATE", "state/bot-settings.json"))
|
|
initialize_database()
|
|
initialize_watchparty_schema()
|
|
migrate_runtime_documents(
|
|
[
|
|
config_path,
|
|
state_path,
|
|
media_state_path,
|
|
media_library_path,
|
|
settings_path,
|
|
]
|
|
)
|
|
interval = int(env("CHECK_INTERVAL_SECONDS", "60"))
|
|
runtime = BotRuntime(
|
|
token,
|
|
channel_id,
|
|
config_path,
|
|
state_path,
|
|
media_state_path,
|
|
media_library_path,
|
|
settings_path,
|
|
dry_run=bool_env("DISCORD_DRY_RUN", False),
|
|
)
|
|
gateway = None
|
|
if bool_env("DISCORD_GATEWAY_ENABLED", True) and not runtime.dry_run:
|
|
gateway = DiscordGatewayManager(token, runtime)
|
|
gateway.start()
|
|
dashboard = maybe_start_dashboard(runtime)
|
|
|
|
if runtime.dry_run:
|
|
print("Discord dry run is enabled; no Discord messages will be sent or edited.", flush=True)
|
|
else:
|
|
try:
|
|
identity = discord_bot_identity(token)
|
|
username = identity.get("username", "unknown")
|
|
bot_id = identity.get("id", "unknown")
|
|
print(f"Discord token authenticated as {username} ({bot_id})", flush=True)
|
|
except Exception as exc:
|
|
print(f"Could not verify Discord bot identity: {exc}", file=sys.stderr, flush=True)
|
|
|
|
stopped = False
|
|
|
|
def stop(_signum: int, _frame: Any) -> None:
|
|
nonlocal stopped
|
|
stopped = True
|
|
|
|
signal.signal(signal.SIGINT, stop)
|
|
signal.signal(signal.SIGTERM, stop)
|
|
|
|
while not stopped:
|
|
try:
|
|
message_id, results = run_check_cycle(runtime)
|
|
online = sum(1 for result in results if result.ok)
|
|
print(f"Updated Discord status message {message_id}: {online}/{len(results)} online", flush=True)
|
|
sync_result = maybe_run_jellyfin_sync(runtime)
|
|
if sync_result is not None:
|
|
action = "published" if sync_result["published"] else "checked"
|
|
print(
|
|
f"Jellyfin sync {action}: {sync_result['movieCount']} movies, {sync_result['showCount']} shows",
|
|
flush=True,
|
|
)
|
|
except Exception as exc:
|
|
with runtime.lock:
|
|
runtime.last_error = str(exc)
|
|
print(f"Status update failed: {exc}", file=sys.stderr, flush=True)
|
|
|
|
for _ in range(interval):
|
|
if stopped:
|
|
break
|
|
time.sleep(1)
|
|
|
|
if dashboard is not None:
|
|
dashboard.shutdown()
|
|
if gateway is not None:
|
|
gateway.stop()
|
|
|
|
return 0
|