Skip to content
31 changes: 24 additions & 7 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from datetime import datetime

import pytz
from py_tools.connections.db.mysql import DBManager, BaseOrmTable, SQLAlchemyManager
from py_tools.connections.db.mysql import DBManager, BaseOrmTable, \
SQLAlchemyManager
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine

from bot.command import CommandHandler
from bot.bot_client import BotClient
from bot.commands import CommandHandler
from config import config
from core.emby_api import EmbyApi, EmbyRouterAPI
from services import UserService
Expand All @@ -20,7 +21,8 @@
async def create_database_if_not_exists() -> None:
"""创建数据库。"""
engine_without_db = create_async_engine(
f"mysql+asyncmy://{config.db_user}:{config.db_pass}@{config.db_host}:{config.db_port}/",
f"mysql+asyncmy://{config.db_user}:{config.db_pass}@"
f"{config.db_host}:{config.db_port}/",
echo=True,
)
async with engine_without_db.begin() as conn:
Expand Down Expand Up @@ -69,6 +71,20 @@ def _init_logger() -> None:
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# 创建 logger 并设置级别
logger_i = logging.getLogger()
logger_i.setLevel(config.log_level)

# 文件处理器,记录到 default.log
file_handler = logging.FileHandler("default.log")
file_handler.setFormatter(formatter)
logger_i.addHandler(file_handler)

# 控制台处理器,打印到终端
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger_i.addHandler(console_handler)


def _init_tz() -> None:
"""初始化时区设置。"""
Expand Down Expand Up @@ -97,7 +113,8 @@ async def setup_bot() -> BotClient:

async def fetch_group_members(bot_client: BotClient) -> None:
"""获取群组成员并更新配置。"""
members_in_group = await bot_client.get_group_members(config.telegram_group_ids)
members_in_group = await bot_client.get_group_members(
config.telegram_group_ids)
for group_members in members_in_group.values():
for telegram_id in group_members:
config.group_members[telegram_id] = group_members[telegram_id]
Expand All @@ -121,9 +138,10 @@ async def main() -> None:
# 初始化 Emby API 和命令处理器
emby_api = EmbyApi(config.emby_url, config.emby_api)
emby_router_api = EmbyRouterAPI(config.api_url, config.api_key)
command_handler = CommandHandler(
CommandHandler(
bot_client=bot_client,
user_service=UserService(emby_api=emby_api, emby_router_api=emby_router_api),
user_service=UserService(emby_api=emby_api,
emby_router_api=emby_router_api),
)
logger.info("Emby API 和命令处理器初始化完成。")

Expand All @@ -133,7 +151,6 @@ async def main() -> None:
logger.info("群组成员信息已更新。")

# 设置命令并进入空闲状态
command_handler.setup_commands()
logger.info("命令处理器设置完成,Bot 进入运行状态。")
await bot_client.idle()

Expand Down
8 changes: 4 additions & 4 deletions bot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import logging

from bot.utils.filters import user_in_group_on_filter, admin_user_on_filter, \
emby_user_on_filter
from .bot_client import BotClient
from .commands import CommandHandler
from .filters import user_in_group_on_filter, admin_user_on_filter, emby_user_on_filter
from .message_helper import get_user_telegram_id
from .command import CommandHandler
from .utils import parse_iso8601_to_normal_date, parse_timestamp_to_normal_date
from .utils.message_helper import get_user_telegram_id

logger = logging.getLogger(__name__)
logger.info("Bot module initialized")

__all__ = [
"BotClient",
"CommandHandler",
"user_in_group_on_filter",
"admin_user_on_filter",
"emby_user_on_filter",
Expand Down
10 changes: 5 additions & 5 deletions bot/bot_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

class BotClient:
def __init__(
self,
api_id: str,
api_hash: str,
bot_token: str,
name="emby_bot",
self,
api_id: str,
api_hash: str,
bot_token: str,
name="emby_bot",
):
self.client = Client(
name=name, api_id=api_id, api_hash=api_hash, bot_token=bot_token
Expand Down
25 changes: 25 additions & 0 deletions bot/command/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import logging

from bot import BotClient
from bot.command.admin_command import AdminCommandHandler
from bot.command.event_command import EventHandler
from bot.command.user_command import UserCommandHandler
from bot.command_router import setup_command_routes
from services import UserService

logger = logging.getLogger(__name__)


class CommandHandler:
def __init__(self, bot_client: BotClient, user_service: UserService):
self.bot_client = bot_client
self.user_service = user_service
self.code_to_message_id = {}
self.user_command_handler = UserCommandHandler(bot_client,
user_service)
self.admin_command_handler = AdminCommandHandler(bot_client,
user_service)
self.event_handler = EventHandler(bot_client, user_service)
setup_command_routes(bot_client, self.user_command_handler,
self.admin_command_handler, self.event_handler)
logger.info("CommandHandler initialized")
180 changes: 180 additions & 0 deletions bot/command/admin_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import logging
from datetime import datetime

from pyrogram.enums import ParseMode
from pyrogram.types import Message

from bot import BotClient
from bot.utils import with_parsed_args, reply_html, send_error, \
with_ensure_args
from bot.utils.message_helper import get_user_telegram_id
from services import UserService

logger = logging.getLogger(__name__)


class AdminCommandHandler:
def __init__(self, bot_client: BotClient, user_service: UserService):
self.bot_client = bot_client
self.user_service = user_service
self.code_to_message_id = {}
logger.info("AdminCommandHandler initialized")

@with_parsed_args
async def new_code(self, message: Message, args: list[str]):
"""
/new_code [数量]
"""
num = 1
if args:
try:
num = int(args[0])
except ValueError:
return await reply_html(message,
"❌ 请输入有效数量 /new_code [整数]")

num = min(num, 20)
try:
code_list = await (
self.user_service
.create_invite_code(message.from_user.id, num)
)
await self.send_code(code_list, message)
except Exception as e:
await send_error(message, e, prefix="创建邀请码失败")

@with_parsed_args
async def new_whitelist_code(self, message: Message, args: list[str]):
"""
/new_whitelist_code [数量]
"""
num = 1
if args:
try:
num = int(args[0])
except ValueError:
return await reply_html(
message,
"❌ 请输入有效数量 /new_whitelist_code [整数]")

num = min(num, 20)
try:
code_list = await self.user_service.create_whitelist_code(
message.from_user.id, num)
await self.send_code(code_list, message, whitelist=True)
except Exception as e:
await send_error(message, e, prefix="创建白名单邀请码失败")

async def send_code(self, code_list, message, whitelist: bool = False):
if whitelist:
base_text = "📌 白名单邀请码:\n点击复制👉"
else:
base_text = "📌 邀请码:\n点击复制👉"
for code_obj in code_list:
# 每次用 base_text 重置消息文本哦~
message_text = f"{base_text}<code>{code_obj.code}</code>"
if message.reply_to_message is not None:
await self.bot_client.client.send_message(
chat_id=message.from_user.id,
text=message_text,
parse_mode=ParseMode.HTML,
)
await self.bot_client.client.send_message(
chat_id=message.reply_to_message.from_user.id,
text=message_text,
parse_mode=ParseMode.HTML,
)
await reply_html(message, "✅ 已发送邀请码")
else:
msg = await reply_html(
message,
message_text
)
self.code_to_message_id[code_obj.code] = (
message.chat.id, msg.id
)

@with_parsed_args
async def ban_emby(self, message: Message, args: list[str]):
"""
/ban_emby [原因] (群里需回复某人或手动指定)
"""
reason = args[0] if args else "管理员禁用"

operator_id = message.from_user.id
telegram_id = await get_user_telegram_id(self.bot_client.client,
message)
try:
if await self.user_service.emby_ban(telegram_id, reason,
operator_id):
await reply_html(
message,
f"✅ 已禁用用户 <code>{telegram_id}</code> 的Emby账号"
)
else:
await reply_html(message, "❌ 禁用失败,请稍后重试。")
except Exception as e:
await send_error(message, e, prefix="禁用失败")

async def unban_emby(self, message: Message):
"""
/unban_emby (群里需回复某人或手动指定)
"""
operator_id = message.from_user.id
telegram_id = await get_user_telegram_id(self.bot_client.client,
message)
try:
if await self.user_service.emby_unban(telegram_id, operator_id):
await reply_html(
message,
f"✅ 已解禁用户 <code>{telegram_id}</code> 的Emby账号"
)
else:
await reply_html(message, "❌ 解禁失败,请稍后重试。")
except Exception as e:
await send_error(message, e, prefix="解禁失败")

@with_parsed_args
@with_ensure_args(2, "/register_until 2023-10-01 12:00:00")
async def register_until(self, message: Message, args: list[str]):
"""
/register_until <时间: YYYY-MM-DD HH:MM:SS>
限时开放注册
"""
time_str = " ".join(args)
try:
time = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
now = datetime.now()
if time < now:
return await reply_html(message, "❌ 时间必须晚于当前时间")

await self.user_service.set_emby_config(
message.from_user.id,
register_public_time=int(time.timestamp())
)
await reply_html(
message,
f"✅ 已开放注册,截止时间:<code>{time_str}</code>"
)
except Exception as e:
await send_error(message, e, prefix="开放注册失败")

@with_parsed_args
@with_ensure_args(1, "/register_amount <人数>")
async def register_amount(self, message: Message, args: list[str]):
"""
/register_amount <人数>
开放指定数量的注册名额
"""
try:
amount = int(args[0])
await self.user_service.set_emby_config(
message.from_user.id,
register_public_user=amount
)
await reply_html(
message,
f"✅ 已开放注册,名额:<code>{amount}</code>"
)
except Exception as e:
await send_error(message, e, prefix="开放注册失败")
66 changes: 66 additions & 0 deletions bot/command/event_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import logging

from pyrogram.types import Message, CallbackQuery

from bot import BotClient
from config import config
from services import UserService

logger = logging.getLogger(__name__)


class EventHandler:
def __init__(self, bot_client: BotClient, user_service: UserService):
self.bot_client = bot_client
self.user_service = user_service
self.code_to_message_id = {}
logger.info("EventHandler initialized")

async def handle_callback_query(self, _,
callback_query: CallbackQuery):
"""
回调按钮事件统一处理,如切换线路。
"""
data = callback_query.data.split('_')
if data[0] == 'SELECTROUTE':
index = data[1]
try:
if not config.router_list:
await callback_query.answer("尚未加载线路列表,请稍后重试")
return

selected_router = next(
(r for r in config.router_list if r['index'] == index),
None)
if not selected_router:
await callback_query.answer("线路不存在")
return

await self.user_service.update_user_router(
callback_query.from_user.id, index)
await callback_query.answer("线路已更新")
await callback_query.message.edit(
f"已选择 <b>{selected_router['name']}</b>\n"
"生效可能会有 30 秒延迟,请耐心等候。"
)
except Exception as e:
await callback_query.answer(f"操作失败:{str(e)}",
show_alert=True)
logger.error(f"Callback query failed: {e}", exc_info=True)

async def group_member_change_handler(self, _, message: Message):
"""
群组成员变动处理器。
"""
if message.left_chat_member:
left_member_id = message.left_chat_member.id
left_member = await self.user_service.must_get_user(left_member_id)
if (left_member.has_emby_account()
and not left_member.is_emby_baned()
and not left_member.is_whitelist):
await self.user_service.emby_ban(message.left_chat_member.id,
"用户已退出群组")
config.group_members.pop(message.left_chat_member.id, None)
if message.new_chat_members:
for new_member in message.new_chat_members:
config.group_members[new_member.id] = new_member
Loading
Loading