101 lines
3.6 KiB
Python
101 lines
3.6 KiB
Python
import asyncio
|
|
import logging
|
|
|
|
from pymax import MaxClient, Message
|
|
from dotenv import load_dotenv
|
|
|
|
from bridge import MaxToTelegramBridge
|
|
from config import load_settings
|
|
from health import HealthState
|
|
from health_web import start_health_server
|
|
from reverse_bridge import TelegramToMaxBridge
|
|
from storage import BridgeStorage
|
|
from telegram_api import TelegramClient
|
|
|
|
|
|
def _setup_logging() -> None:
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
)
|
|
|
|
|
|
def build_client() -> tuple[MaxClient, MaxToTelegramBridge]:
|
|
settings = load_settings()
|
|
max_client = MaxClient(
|
|
phone=settings.max_phone,
|
|
work_dir=settings.max_work_dir,
|
|
)
|
|
telegram_client = TelegramClient(
|
|
bot_token=settings.telegram_bot_token,
|
|
fallback_user_id=settings.telegram_fallback_user_id,
|
|
)
|
|
storage = BridgeStorage(settings.sqlite_path)
|
|
bridge = MaxToTelegramBridge(max_client=max_client, telegram=telegram_client, storage=storage)
|
|
return max_client, bridge
|
|
|
|
|
|
def main() -> None:
|
|
load_dotenv()
|
|
_setup_logging()
|
|
settings = load_settings()
|
|
max_client = MaxClient(phone=settings.max_phone, work_dir=settings.max_work_dir)
|
|
telegram_client = TelegramClient(
|
|
bot_token=settings.telegram_bot_token,
|
|
fallback_user_id=settings.telegram_fallback_user_id,
|
|
)
|
|
storage = BridgeStorage(settings.sqlite_path)
|
|
bridge = MaxToTelegramBridge(max_client=max_client, telegram=telegram_client, storage=storage)
|
|
health = HealthState(unhealthy_after_sec=15 * 60)
|
|
start_health_server(host="0.0.0.0", port=5000, health=health)
|
|
|
|
reverse_bridge = TelegramToMaxBridge(max_client=max_client, telegram=telegram_client, storage=storage, health=health)
|
|
logger = logging.getLogger("max2telegram")
|
|
reverse_bridge_task: asyncio.Task[None] | None = None
|
|
max_probe_task: asyncio.Task[None] | None = None
|
|
|
|
@max_client.on_start
|
|
async def on_start() -> None:
|
|
nonlocal reverse_bridge_task, max_probe_task
|
|
logger.info("Max client started as %s", max_client.me.id)
|
|
health.mark_max_ok()
|
|
if reverse_bridge_task is None or reverse_bridge_task.done():
|
|
reverse_bridge_task = asyncio.create_task(reverse_bridge.start(), name="reverse-bridge")
|
|
if max_probe_task is None or max_probe_task.done():
|
|
max_probe_task = asyncio.create_task(
|
|
_max_probe_loop(max_client=max_client, health=health),
|
|
name="max-probe-loop",
|
|
)
|
|
|
|
@max_client.on_message()
|
|
async def on_message(message: Message) -> None:
|
|
health.mark_max_event()
|
|
try:
|
|
await bridge.forward_message(message)
|
|
except Exception:
|
|
logger.exception("Failed to forward Max message")
|
|
|
|
asyncio.run(max_client.start())
|
|
|
|
|
|
async def _max_probe_loop(*, max_client: MaxClient, health: HealthState) -> None:
|
|
# Best-effort контроль соединения: периодически дергаем API.
|
|
# Если PyMax разорвет соединение/сломается сессия, это обычно проявится как исключение.
|
|
await asyncio.sleep(2)
|
|
while True:
|
|
try:
|
|
me = getattr(max_client, "me", None)
|
|
my_id = getattr(me, "id", None)
|
|
if my_id is not None:
|
|
await max_client.get_user(user_id=my_id)
|
|
health.mark_max_ok()
|
|
except Exception:
|
|
health.mark_max_error()
|
|
logger = logging.getLogger("max2telegram")
|
|
logger.exception("Max probe failed")
|
|
await asyncio.sleep(60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|