Add group manage service

This commit is contained in:
omg-xtao 2024-01-16 23:04:41 +08:00 committed by GitHub
parent d520a0980a
commit ce7ab18eda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 348 additions and 27 deletions

View File

@ -0,0 +1,57 @@
"""groups
Revision ID: 27aaa52f9d4a
Revises: c6282bc5bf67
Create Date: 2024-01-16 13:54:37.980830
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision = "27aaa52f9d4a"
down_revision = "c6282bc5bf67"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"groups",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("chat_id", sa.BigInteger(), nullable=True),
sa.Column(
"type",
sa.Enum(
"SENDER",
"PRIVATE",
"GROUP",
"SUPERGROUP",
"CHANNEL",
name="chattypeenum",
),
nullable=True,
),
sa.Column("description", sa.TEXT(), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("is_left", sa.Integer(), nullable=True),
sa.Column("is_banned", sa.Integer(), nullable=True),
sa.Column("title", sqlmodel.AutoString(), nullable=False),
sa.Column("username", sqlmodel.AutoString(), nullable=True),
sa.Column("big_photo_id", sqlmodel.AutoString(), nullable=True),
sa.Column("small_photo_id", sqlmodel.AutoString(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("chat_id"),
mysql_charset="utf8mb4",
mysql_collate="utf8mb4_general_ci",
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("groups")
# ### end Alembic commands ###

View File

@ -0,0 +1,4 @@
from gram_core.handler.grouphandler import GroupHandler
__all__ = ("GroupHandler",)

View File

@ -0,0 +1,3 @@
from .services import GroupService
__all__ = ("GroupService",)

View File

@ -0,0 +1,6 @@
from gram_core.services.groups.cache import GroupBanCache, GroupUpdateCache
__all__ = (
"GroupBanCache",
"GroupUpdateCache",
)

View File

@ -0,0 +1,7 @@
from gram_core.services.groups.models import Group, GroupDataBase, ChatTypeEnum
__all__ = (
"Group",
"GroupDataBase",
"ChatTypeEnum",
)

View File

@ -0,0 +1,3 @@
from gram_core.services.groups.repositories import GroupRepository
__all__ = ("GroupRepository",)

View File

@ -0,0 +1,3 @@
from gram_core.services.groups.services import GroupService
__all__ = ("GroupService",)

View File

@ -0,0 +1,8 @@
from .services import UserService, UserAdminService, UserBanService
__all__ = (
"UserService",
"UserAdminService",
"UserBanService",
)

View File

@ -1,3 +1,6 @@
from gram_core.services.users.cache import UserAdminCache from gram_core.services.users.cache import UserAdminCache, UserBanCache
__all__ = ("UserAdminCache",) __all__ = (
"UserAdminCache",
"UserBanCache",
)

View File

@ -1,3 +1,7 @@
from gram_core.services.users.services import UserService, UserAdminService from gram_core.services.users.services import UserService, UserAdminService, UserBanService
__all__ = ("UserService", "UserAdminService") __all__ = (
"UserService",
"UserAdminService",
"UserBanService",
)

@ -1 +1 @@
Subproject commit 6d1e8bcc497ed255e581b5e47de88e01cdef98f7 Subproject commit af709ec5da23040949cbcbdb8e6833039c904894

View File

@ -1,29 +1,40 @@
import contextlib
import html import html
from typing import Tuple from typing import Tuple, Optional, TYPE_CHECKING
from telegram import Chat, ChatMember, ChatMemberAdministrator, ChatMemberOwner, Update from telegram import ChatMemberAdministrator, ChatMemberOwner, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.error import BadRequest, Forbidden from telegram.error import BadRequest, Forbidden
from telegram.ext import CallbackContext, CommandHandler
from core.basemodel import RegionEnum from core.basemodel import RegionEnum
from core.plugin import Plugin, handler from core.plugin import Plugin, handler
from core.services.cookies import CookiesService from core.services.cookies import CookiesService
from core.services.players import PlayersService from core.services.players import PlayersService
from core.services.groups.services import GroupService
from core.services.users.services import UserBanService
from utils.log import logger from utils.log import logger
if TYPE_CHECKING:
from telegram import Chat, ChatMember, Update
from telegram.ext import ContextTypes
class GetChat(Plugin): class GetChat(Plugin):
def __init__( def __init__(
self, self,
players_service: PlayersService, players_service: PlayersService,
cookies_service: CookiesService, cookies_service: CookiesService,
group_service: GroupService,
user_ban_service: UserBanService,
): ):
self.cookies_service = cookies_service self.cookies_service = cookies_service
self.players_service = players_service self.players_service = players_service
self.group_service = group_service
self.user_ban_service = user_ban_service
@staticmethod @staticmethod
async def parse_group_chat(chat: Chat, admins: Tuple[ChatMember]) -> str: async def parse_group_chat(chat: "Chat", admins: Tuple["ChatMember", ...], is_banned: bool) -> str:
text = f"群 ID<code>{chat.id}</code>\n群名称:<code>{chat.title}</code>\n" text = f"群 ID<code>{chat.id}</code>\n群名称:<code>{chat.title}</code>\n"
text += f"黑名单:<code>{'' if is_banned else ''}</code>\n"
if chat.username: if chat.username:
text += f"群用户名:@{chat.username}\n" text += f"群用户名:@{chat.username}\n"
if chat.description: if chat.description:
@ -46,12 +57,13 @@ class GetChat(Plugin):
text += "\n" text += "\n"
return text return text
async def parse_private_chat(self, chat: Chat) -> str: async def parse_private_chat(self, chat: "Chat", is_banned: bool) -> str:
text = ( text = (
f'<a href="tg://user?id={chat.id}">MENTION</a>\n' f'<a href="tg://user?id={chat.id}">MENTION</a>\n'
f"用户 ID<code>{chat.id}</code>\n" f"用户 ID<code>{chat.id}</code>\n"
f"用户名称:<code>{chat.full_name}</code>\n" f"用户名称:<code>{chat.full_name}</code>\n"
) )
text += f"黑名单:<code>{'' if is_banned else ''}</code>\n"
if chat.username: if chat.username:
text += f"用户名:@{chat.username}\n" text += f"用户名:@{chat.username}\n"
player_info = await self.players_service.get_player(chat.id) player_info = await self.players_service.get_player(chat.id)
@ -68,27 +80,70 @@ class GetChat(Plugin):
text += f"<code>{temp}</code>\n游戏 ID<code>{player_info.player_id}</code>" text += f"<code>{temp}</code>\n游戏 ID<code>{player_info.player_id}</code>"
return text return text
@handler(CommandHandler, command="get_chat", block=False, admin=True) def get_chat_id(self, context: "ContextTypes.DEFAULT_TYPE") -> Optional[int]:
async def get_chat_command(self, update: Update, context: CallbackContext):
user = update.effective_user
logger.info("用户 %s[%s] get_chat 命令请求", user.full_name, user.id)
message = update.effective_message
args = self.get_args(context) args = self.get_args(context)
if not args: if args and len(args) > 1 and args[0].isnumeric():
return int(args[0])
@staticmethod
def gen_button(chat_id: int) -> "InlineKeyboardMarkup":
return InlineKeyboardMarkup(
[
[
InlineKeyboardButton(
"拉黑",
callback_data=f"block|add|{chat_id}",
),
InlineKeyboardButton(
"取消拉黑",
callback_data=f"block|del|{chat_id}",
),
],
]
)
@handler.command(command="get_chat", block=False, admin=True)
async def get_chat_command(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"):
user = update.effective_user
message = update.effective_message
chat_id = self.get_chat_id(context)
logger.info("用户 %s[%s] get_chat 命令请求 chat_id[%s]", user.full_name, user.id, chat_id)
if not chat_id:
await message.reply_text("参数错误,请指定群 id ") await message.reply_text("参数错误,请指定群 id ")
return return
if chat_id < 0:
is_banned = await self.group_service.is_banned(chat_id)
else:
is_banned = await self.user_ban_service.is_banned(chat_id)
try: try:
chat_id = int(args[0]) chat = await self.get_chat(chat_id)
except ValueError:
await message.reply_text("参数错误,请指定群 id ")
return
try:
chat = await self.get_chat(args[0])
if chat_id < 0: if chat_id < 0:
admins = await chat.get_administrators() if chat_id < 0 else None admins = await chat.get_administrators()
text = await self.parse_group_chat(chat, admins) text = await self.parse_group_chat(chat, admins, is_banned)
else: else:
text = await self.parse_private_chat(chat) text = await self.parse_private_chat(chat, is_banned)
await message.reply_text(text, parse_mode="HTML") await message.reply_text(text, parse_mode="HTML", reply_markup=self.gen_button(chat_id))
except (BadRequest, Forbidden) as exc: except (BadRequest, Forbidden) as exc:
await message.reply_text(f"通过 id 获取会话信息失败API 返回:{exc.message}") logger.warning("通过 id 获取会话信息失败API 返回:%s", str(exc))
text = f"会话 ID<code>{chat_id}</code>\n"
text += f"黑名单:<code>{'' if is_banned else ''}</code>\n"
await message.reply_text(text, parse_mode="HTML", reply_markup=self.gen_button(chat_id))
@handler.command(command="leave_chat", block=False, admin=True)
async def leave_chat(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"):
user = update.effective_user
message = update.effective_message
chat_id = self.get_chat_id(context)
logger.info("用户 %s[%s] leave_chat 命令请求 chat_id[%s]", user.full_name, user.id, chat_id)
if not chat_id:
await message.reply_text("参数错误,请指定群 id ")
return
try:
with contextlib.suppress(BadRequest, Forbidden):
chat = await context.bot.get_chat(chat_id)
await message.reply_text(f"正在尝试退出群 {chat.title}[{chat.id}]")
await context.bot.leave_chat(chat_id)
except (BadRequest, Forbidden) as exc:
await message.reply_text(f"退出 chat_id[{chat_id}] 发生错误! 错误信息为 {str(exc)}")
return
await message.reply_text(f"退出 chat_id[{chat_id}] 成功!")

View File

@ -0,0 +1,159 @@
from typing import TYPE_CHECKING, Optional
from telegram.constants import ChatType
from telegram.error import BadRequest, Forbidden
from telegram.ext import ChatMemberHandler
from core.handler.grouphandler import GroupHandler
from core.plugin import Plugin, handler
from core.services.groups import GroupService
from core.services.groups.models import GroupDataBase as Group
from core.services.users import UserBanService
from utils.chatmember import extract_status_change
from utils.log import logger
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import ContextTypes
class GroupManage(Plugin):
def __init__(
self,
group_service: GroupService,
user_ban_service: UserBanService,
):
self.type_handler = None
self.group_service = group_service
self.user_ban_service = user_ban_service
async def initialize(self) -> None:
self.type_handler = GroupHandler(self.application)
self.application.telegram.add_handler(self.type_handler, group=-2)
async def shutdown(self) -> None:
self.application.telegram.remove_handler(self.type_handler, group=-2)
@handler.chat_member(chat_member_types=ChatMemberHandler.MY_CHAT_MEMBER, block=False)
async def check_group(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
result = extract_status_change(update.my_chat_member)
if result is None:
return
was_member, is_member = result
chat = update.effective_chat
if chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP, ChatType.CHANNEL]:
return
if not was_member and is_member:
if await self.group_service.is_banned(chat.id):
logger.info("会话 %s[%s] 在黑名单中,尝试退出", chat.title, chat.id)
await GroupHandler.leave_chat(context.bot, chat.id)
return
if await self.group_service.is_need_update(chat.id):
await GroupHandler.update_group(context.bot, self.group_service, chat)
else:
await self.group_service.join(chat.id)
if was_member and not is_member:
await self.group_service.leave(chat.id)
def get_chat_id(self, context: "ContextTypes.DEFAULT_TYPE") -> Optional[int]:
args = self.get_args(context)
if args and len(args) > 1 and args[0].isnumeric():
return int(args[0])
async def add_block_group(self, chat_id: int):
group = await self.group_service.get_group_by_id(chat_id)
if group:
group.is_banned = True
await self.group_service.update_group(group)
else:
chat = None
try:
chat = await self.get_chat(chat_id)
except (BadRequest, Forbidden) as exc:
logger.warning("通过 id 获取会话信息失败API 返回:%s", str(exc))
if chat:
group = Group.from_chat(chat)
else:
group = Group.from_id(chat_id)
group.is_banned = True
await self.group_service.update_group(group)
@handler.command(command="add_block", block=False, admin=True)
@handler.callback_query(pattern=r"^block\|add\|", block=False, admin=True)
async def add_block(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"):
callback_query = update.callback_query
user = update.effective_user
message = update.effective_message
chat_id = self.get_chat_id(context) if not callback_query else int(callback_query.data.split("|")[2])
logger.info(
"用户 %s[%s] add_block 命令请求 chat_id[%s] callback[%s]", user.full_name, user.id, chat_id, bool(callback_query)
)
if not chat_id:
await message.reply_text("参数错误,请指定群 id ")
return
async def reply(text: str):
if callback_query:
await callback_query.answer(text, show_alert=True)
else:
await message.reply_text(text)
if chat_id < 0:
if await self.group_service.is_banned(chat_id):
await reply("该群已在黑名单中!")
return
await self.add_block_group(chat_id)
await reply("已将该群加入黑名单!")
else:
if await self.user_ban_service.is_banned(chat_id):
await reply("该用户已在黑名单中!")
return
try:
await self.user_ban_service.add_ban(chat_id)
except PermissionError:
await reply("无法操作管理员!")
return
await reply("已将该用户加入黑名单!")
@handler.command(command="del_block", block=False, admin=True)
@handler.callback_query(pattern=r"^block\|del\|", block=False, admin=True)
async def del_block(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"):
callback_query = update.callback_query
user = update.effective_user
message = update.effective_message
chat_id = self.get_chat_id(context) if not callback_query else int(callback_query.data.split("|")[2])
logger.info(
"用户 %s[%s] del_block 命令请求 chat_id[%s] callback[%s]", user.full_name, user.id, chat_id, bool(callback_query)
)
if not chat_id:
await message.reply_text("参数错误,请指定群 id ")
return
async def reply(text: str):
if callback_query:
await callback_query.answer(text, show_alert=True)
else:
await message.reply_text(text)
if chat_id < 0:
if not await self.group_service.is_banned(chat_id):
await reply("该群不在黑名单中!")
return
success = await self.group_service.del_ban(chat_id)
if not success:
await reply("该群不在黑名单中!")
return
await reply("已将该群移出黑名单!")
else:
if not await self.user_ban_service.is_banned(chat_id):
await reply("该用户不在黑名单中!")
return
try:
success = await self.user_ban_service.del_ban(chat_id)
if not success:
await reply("该用户不在黑名单中!")
return
except PermissionError:
await reply("无法操作管理员!")
return
await reply("已将该用户移出黑名单!")

View File

@ -85,6 +85,10 @@ class SetCommandPlugin(Plugin):
BotCommand("update", "更新"), BotCommand("update", "更新"),
BotCommand("set_command", "重设命令"), BotCommand("set_command", "重设命令"),
BotCommand("status", "当前Bot运行状态"), BotCommand("status", "当前Bot运行状态"),
BotCommand("leave_chat", "退出群组"),
BotCommand("get_chat", "获取会话信息"),
BotCommand("add_block", "添加黑名单"),
BotCommand("del_block", "移除黑名单"),
] ]
await context.bot.set_my_commands(commands=group_command) # 留空default 为 botCommandScopeDefault, 所有聊天可见 await context.bot.set_my_commands(commands=group_command) # 留空default 为 botCommandScopeDefault, 所有聊天可见
await context.bot.set_my_commands(commands=user_command + group_command, scope=BotCommandScopeAllPrivateChats()) await context.bot.set_my_commands(commands=user_command + group_command, scope=BotCommandScopeAllPrivateChats())

View File

@ -7,6 +7,7 @@ from core.plugin import Plugin, handler
from core.services.cookies import CookiesService from core.services.cookies import CookiesService
from core.services.players import PlayersService from core.services.players import PlayersService
from core.services.users.services import UserAdminService from core.services.users.services import UserAdminService
from core.services.groups.services import GroupService
from utils.chatmember import extract_status_change from utils.chatmember import extract_status_change
from utils.log import logger from utils.log import logger
@ -17,10 +18,12 @@ class ChatMember(Plugin):
user_admin_service: UserAdminService = None, user_admin_service: UserAdminService = None,
players_service: PlayersService = None, players_service: PlayersService = None,
cookies_service: CookiesService = None, cookies_service: CookiesService = None,
group_service: GroupService = None,
): ):
self.cookies_service = cookies_service self.cookies_service = cookies_service
self.players_service = players_service self.players_service = players_service
self.user_admin_service = user_admin_service self.user_admin_service = user_admin_service
self.group_service = group_service
@handler.chat_member(chat_member_types=ChatMemberHandler.MY_CHAT_MEMBER, block=False) @handler.chat_member(chat_member_types=ChatMemberHandler.MY_CHAT_MEMBER, block=False)
async def track_chats(self, update: Update, context: CallbackContext) -> None: async def track_chats(self, update: Update, context: CallbackContext) -> None:
@ -48,6 +51,8 @@ class ChatMember(Plugin):
logger.info("用户 %s[%s] 从 %s[%s] 频道移除Bot", user.full_name, user.id, chat.title, chat.id) logger.info("用户 %s[%s] 从 %s[%s] 频道移除Bot", user.full_name, user.id, chat.title, chat.id)
async def greet(self, user: User, chat: Chat, context: CallbackContext) -> None: async def greet(self, user: User, chat: Chat, context: CallbackContext) -> None:
if await self.group_service.is_banned(chat.id):
return
quit_status = True quit_status = True
if config.join_groups == JoinGroups.NO_ALLOW: if config.join_groups == JoinGroups.NO_ALLOW:
try: try: