mirror of
https://github.com/artemium428/428th-exchange-bot.git
synced 2026-09-15 16:56:20 +00:00
156 lines
5.2 KiB
Python
156 lines
5.2 KiB
Python
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import logging
|
||
import sys
|
||
|
||
from aiogram import Bot, Dispatcher
|
||
from aiogram.client.default import DefaultBotProperties
|
||
from aiogram.enums import ChatMemberStatus, ChatType, ParseMode
|
||
from aiogram.exceptions import TelegramAPIError
|
||
from aiogram.types import ChatMemberUpdated
|
||
|
||
from bot.config import Config, apply_text_overrides, load_config
|
||
from bot.db.repository import ClientRepository
|
||
from bot.handlers.client import router as client_router
|
||
from bot.handlers.manager import build_manager_router
|
||
from bot.services.cooldown import CooldownService
|
||
from bot.services.relay import RelayService
|
||
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
||
stream=sys.stdout,
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def on_bot_added(event: ChatMemberUpdated, config: Config, relay: RelayService) -> None:
|
||
"""Log chat id when the bot is added to a group — helps set MANAGER_GROUP_ID."""
|
||
new = event.new_chat_member
|
||
if not relay.is_self(new.user.id):
|
||
return
|
||
if new.status in {ChatMemberStatus.ADMINISTRATOR, ChatMemberStatus.MEMBER}:
|
||
logger.info(
|
||
"Bot added to chat %s (%s). Use this as MANAGER_GROUP_ID if it is the forum.",
|
||
event.chat.id,
|
||
event.chat.title,
|
||
)
|
||
|
||
|
||
async def validate_manager_group(bot: Bot, manager_group_id: int) -> None:
|
||
"""Fail fast if MANAGER_GROUP_ID is wrong or the bot lacks required rights."""
|
||
try:
|
||
chat = await bot.get_chat(manager_group_id)
|
||
except TelegramAPIError as exc:
|
||
raise RuntimeError(
|
||
f"Не удалось получить чат MANAGER_GROUP_ID={manager_group_id}: {exc}. "
|
||
"Проверьте id и что бот добавлен в группу."
|
||
) from exc
|
||
|
||
if chat.type != ChatType.SUPERGROUP:
|
||
raise RuntimeError(
|
||
f"MANAGER_GROUP_ID должен быть супергруппой, сейчас type={chat.type!r} "
|
||
f"(chat_id={chat.id}, title={chat.title!r})."
|
||
)
|
||
|
||
if not bool(getattr(chat, "is_forum", False)):
|
||
raise RuntimeError(
|
||
f"В чате {chat.id} ({chat.title!r}) не включены Topics. "
|
||
"Включите темы в настройках группы."
|
||
)
|
||
|
||
me = await bot.get_me()
|
||
try:
|
||
member = await bot.get_chat_member(manager_group_id, me.id)
|
||
except TelegramAPIError as exc:
|
||
raise RuntimeError(
|
||
f"Не удалось проверить права бота в группе {chat.id}: {exc}."
|
||
) from exc
|
||
|
||
if member.status == ChatMemberStatus.CREATOR:
|
||
logger.info("Manager group OK: %s (%s), bot is creator", chat.id, chat.title)
|
||
return
|
||
|
||
if member.status != ChatMemberStatus.ADMINISTRATOR:
|
||
raise RuntimeError(
|
||
f"Бот должен быть администратором группы {chat.title!r} (id={chat.id}). "
|
||
f"Текущий статус: {member.status!r}."
|
||
)
|
||
|
||
missing: list[str] = []
|
||
if not bool(getattr(member, "can_manage_topics", True)):
|
||
missing.append("Manage topics")
|
||
if not bool(getattr(member, "can_delete_messages", True)):
|
||
missing.append("Delete messages")
|
||
can_post = getattr(member, "can_post_messages", None)
|
||
# In groups can_post_messages may be None; treat explicit False as missing.
|
||
if can_post is False:
|
||
missing.append("Post messages")
|
||
|
||
if missing:
|
||
raise RuntimeError(
|
||
f"Бот — админ группы {chat.title!r}, но не хватает прав: {', '.join(missing)}. "
|
||
"Выдайте их в настройках администраторов."
|
||
)
|
||
|
||
logger.info("Manager group OK: %s (%s), bot is admin", chat.id, chat.title)
|
||
|
||
|
||
async def run() -> None:
|
||
config = load_config()
|
||
apply_text_overrides(config)
|
||
repo = ClientRepository(config.database_path)
|
||
await repo.connect()
|
||
|
||
relay = RelayService(repo, config.manager_group_id)
|
||
cooldown = CooldownService(config.client_cooldown_seconds)
|
||
bot = Bot(
|
||
token=config.bot_token,
|
||
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
|
||
)
|
||
|
||
me = await bot.get_me()
|
||
relay.set_bot_id(me.id)
|
||
|
||
try:
|
||
await validate_manager_group(bot, config.manager_group_id)
|
||
except RuntimeError as exc:
|
||
logger.error("%s", exc)
|
||
print(f"ERROR: {exc}", file=sys.stderr)
|
||
await repo.close()
|
||
await bot.session.close()
|
||
raise SystemExit(1) from exc
|
||
|
||
dp = Dispatcher()
|
||
dp["config"] = config
|
||
dp["repo"] = repo
|
||
dp["relay"] = relay
|
||
dp["cooldown"] = cooldown
|
||
|
||
dp.include_router(client_router)
|
||
dp.include_router(build_manager_router(config.manager_group_id))
|
||
dp.my_chat_member.register(on_bot_added)
|
||
|
||
logger.info(
|
||
"Starting bot @%s (manager group %s, client cooldown %.1fs)",
|
||
me.username,
|
||
config.manager_group_id,
|
||
config.client_cooldown_seconds,
|
||
)
|
||
try:
|
||
await dp.start_polling(
|
||
bot,
|
||
allowed_updates=dp.resolve_used_update_types(),
|
||
)
|
||
finally:
|
||
await repo.close()
|
||
await bot.session.close()
|
||
|
||
|
||
def main() -> None:
|
||
asyncio.run(run())
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|