from __future__ import annotations from collections.abc import Iterable from dataclasses import asdict, dataclass @dataclass(frozen=True) class BotPermission: key: str name: str group: str description: str @dataclass(frozen=True) class BuiltinBotRole: role_id: str name: str description: str permissions: tuple[str, ...] PERMISSIONS = ( BotPermission("chat.profile", "群资料", "群组管理", "修改群标题和描述。"), BotPermission("chat.members", "成员与管理员", "群组管理", "查询、处罚及调整管理员。"), BotPermission("chat.permissions", "群权限", "群组管理", "修改普通成员默认权限。"), BotPermission("chat.announcements", "公告", "群组管理", "发送并置顶群公告。"), BotPermission("chat.invites", "邀请链接", "群组管理", "创建和撤销邀请链接。"), BotPermission("chat.rules", "群规", "群组管理", "查看和维护群规。"), BotPermission("chat.notes", "群笔记", "群组管理", "维护群内笔记功能。"), BotPermission( "automation.manage", "自动化与风控", "自动化", "管理欢迎语、自动回复、验证码、聊天机器人、内容风控和资料监控。", ), BotPermission("points.manage", "积分", "社区运营", "运行积分规则、排行和人工调整。"), BotPermission("giveaways.manage", "抽奖", "社区运营", "创建、开奖、退款和管理参与者。"), BotPermission("karma.manage", "声望", "社区运营", "处理点赞、点踩和声望排行。"), BotPermission( "teacher_directory.manage", "技师与附近", "社区运营", "提供技师申请、上下榜、在线状态、位置和附近技师目录。", ), BotPermission( "business_assistant.manage", "智能接待", "自动化", "管理 Telegram Business 连接、知识库、客户会话和人工接管。", ), BotPermission("media.upload", "媒体中转", "系统能力", "向媒体中转群上传文件。"), ) ALL_BOT_PERMISSIONS = frozenset(item.key for item in PERMISSIONS) GROUP_MANAGEMENT_PERMISSIONS = frozenset( { "chat.announcements", "chat.members", "chat.permissions", "automation.manage", "points.manage", "giveaways.manage", } ) PRIVATE_MANAGEMENT_PERMISSIONS = GROUP_MANAGEMENT_PERMISSIONS | { "teacher_directory.manage", "business_assistant.manage", } BUILTIN_ROLES = ( BuiltinBotRole( "full_access", "全部职责", "拥有当前系统的全部机器人职责。", tuple(item.key for item in PERMISSIONS), ), BuiltinBotRole( "group_manager", "群组管理员", "负责群资料、成员、权限、公告、邀请链接、群规和群笔记。", ( "chat.profile", "chat.members", "chat.permissions", "chat.announcements", "chat.invites", "chat.rules", "chat.notes", ), ), BuiltinBotRole( "automation_manager", "自动化与风控专员", "负责自动回复、欢迎语、验证码、聊天机器人、风控和资料监控。", ("automation.manage", "media.upload"), ), BuiltinBotRole( "points_manager", "积分专员", "负责积分规则、签到、排行榜和积分调整。", ("points.manage",), ), BuiltinBotRole( "giveaway_manager", "抽奖专员", "负责抽奖全流程,并可处理抽奖关联积分。", ("giveaways.manage", "points.manage"), ), BuiltinBotRole( "karma_manager", "声望专员", "负责点赞、点踩和声望排行。", ("karma.manage",), ), BuiltinBotRole( "teacher_directory_manager", "技师目录专员", "负责技师申请、位置、上下榜、在线状态和附近技师目录。", ("teacher_directory.manage",), ), BuiltinBotRole( "business_assistant_manager", "智能接待专员", "负责 Telegram Business 智能接待、知识库和客户会话。", ("business_assistant.manage",), ), ) MODULE_PERMISSIONS: dict[str, frozenset[str]] = { "admin": frozenset({"chat.members", "chat.announcements", "chat.invites"}), "admin_misc": frozenset({"chat.profile", "chat.members"}), "admin_panel": GROUP_MANAGEMENT_PERMISSIONS, "antiservice": frozenset({"automation.manage"}), "blacklist": frozenset({"automation.manage"}), "blacklist_chat": frozenset({"automation.manage"}), "business_assistant": frozenset({"business_assistant.manage"}), "chatbot": frozenset({"automation.manage"}), "filters": frozenset({"automation.manage"}), "flood": frozenset({"automation.manage"}), "greetings": frozenset({"automation.manage"}), "giveaway": frozenset({"giveaways.manage"}), "karma": frozenset({"karma.manage"}), "locks": frozenset({"chat.permissions"}), "notes": frozenset({"chat.notes"}), "points": frozenset({"points.manage"}), "rules": frozenset({"chat.rules"}), "teacher_directory": frozenset({"teacher_directory.manage"}), "technician_services": frozenset({"teacher_directory.manage"}), } TELEGRAM_COMMAND_PERMISSIONS = { "purge": "chat.members", "kick": "chat.members", "dkick": "chat.members", "ban": "chat.members", "dban": "chat.members", "tban": "chat.members", "unban": "chat.members", "listban": "chat.members", "listunban": "chat.members", "del": "chat.members", "promote": "chat.members", "fullpromote": "chat.members", "demote": "chat.members", "mute": "chat.members", "tmute": "chat.members", "unmute": "chat.members", "ban_ghosts": "chat.members", "warn": "chat.members", "dwarn": "chat.members", "rmwarns": "chat.members", "warns": "chat.members", "report": "chat.members", "admins": "chat.members", "admin": "chat.members", "set_user_title": "chat.members", "pin": "chat.announcements", "unpin": "chat.announcements", "invite": "chat.invites", "set_chat_title": "chat.profile", "set_chat_photo": "chat.profile", } def permission_catalog() -> list[dict[str, str]]: return [asdict(item) for item in PERMISSIONS] def builtin_roles() -> list[dict[str, object]]: return [ { "role_id": item.role_id, "name": item.name, "description": item.description, "permissions": list(item.permissions), "builtin": True, } for item in BUILTIN_ROLES ] def normalize_permissions(values: object) -> list[str]: if not isinstance(values, (list, tuple, set, frozenset)): values = str(values or "").replace(",", " ").split() normalized = list(dict.fromkeys(str(item).strip() for item in values if str(item).strip())) unknown = sorted(set(normalized) - ALL_BOT_PERMISSIONS) if unknown: raise ValueError(f"未知机器人权限:{', '.join(unknown)}") return normalized def has_permission(permission: str, permissions: Iterable[str] | None) -> bool: current = set(permissions or ()) return "*" in current or permission in current def has_any_permission( required: Iterable[str], permissions: Iterable[str] | None, ) -> bool: current = set(permissions or ()) return "*" in current or bool(set(required) & current) def module_allowed(module: str, permissions: Iterable[str] | None) -> bool: required = MODULE_PERMISSIONS.get(module) return not required or has_any_permission(required, permissions) def api_permission(method: str, path: str) -> str | None: if path.startswith("/api/admin/v1/business-assistant"): return "business_assistant.manage" if path.startswith("/api/admin/v1/directory"): return "teacher_directory.manage" if path.startswith("/api/admin/v1/media"): return "media.upload" if path.startswith("/api/admin/v1/points"): return "points.manage" if path.startswith("/api/admin/v1/giveaways"): return "giveaways.manage" if "/giveaway-bans" in path: return "giveaways.manage" if not path.startswith("/api/admin/v1/chats/"): return None if path.endswith("/profile"): return "chat.profile" if path.endswith("/permissions"): return "chat.permissions" if path.endswith("/announcements"): return "chat.announcements" if "/members" in path or path.endswith("/admins"): return "chat.members" if path.endswith("/invites"): return "chat.invites" if path.endswith("/rules"): return "chat.rules" if path.endswith("/automation"): return "automation.manage" if "/points/" in path: return "points.manage" return None