bot_permissions.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. from __future__ import annotations
  2. from collections.abc import Iterable
  3. from dataclasses import asdict, dataclass
  4. @dataclass(frozen=True)
  5. class BotPermission:
  6. key: str
  7. name: str
  8. group: str
  9. description: str
  10. @dataclass(frozen=True)
  11. class BuiltinBotRole:
  12. role_id: str
  13. name: str
  14. description: str
  15. permissions: tuple[str, ...]
  16. PERMISSIONS = (
  17. BotPermission("chat.profile", "群资料", "群组管理", "修改群标题和描述。"),
  18. BotPermission("chat.members", "成员与管理员", "群组管理", "查询、处罚及调整管理员。"),
  19. BotPermission("chat.permissions", "群权限", "群组管理", "修改普通成员默认权限。"),
  20. BotPermission("chat.announcements", "公告", "群组管理", "发送并置顶群公告。"),
  21. BotPermission("chat.invites", "邀请链接", "群组管理", "创建和撤销邀请链接。"),
  22. BotPermission("chat.rules", "群规", "群组管理", "查看和维护群规。"),
  23. BotPermission("chat.notes", "群笔记", "群组管理", "维护群内笔记功能。"),
  24. BotPermission(
  25. "automation.manage",
  26. "自动化与风控",
  27. "自动化",
  28. "管理欢迎语、自动回复、验证码、聊天机器人、内容风控和资料监控。",
  29. ),
  30. BotPermission("points.manage", "积分", "社区运营", "运行积分规则、排行和人工调整。"),
  31. BotPermission("giveaways.manage", "抽奖", "社区运营", "创建、开奖、退款和管理参与者。"),
  32. BotPermission("karma.manage", "声望", "社区运营", "处理点赞、点踩和声望排行。"),
  33. BotPermission(
  34. "teacher_directory.manage",
  35. "技师与附近",
  36. "社区运营",
  37. "提供技师申请、上下榜、在线状态、位置和附近技师目录。",
  38. ),
  39. BotPermission("media.upload", "媒体中转", "系统能力", "向媒体中转群上传文件。"),
  40. )
  41. ALL_BOT_PERMISSIONS = frozenset(item.key for item in PERMISSIONS)
  42. GROUP_MANAGEMENT_PERMISSIONS = frozenset(
  43. {
  44. "chat.announcements",
  45. "chat.members",
  46. "chat.permissions",
  47. "automation.manage",
  48. "points.manage",
  49. "giveaways.manage",
  50. }
  51. )
  52. PRIVATE_MANAGEMENT_PERMISSIONS = GROUP_MANAGEMENT_PERMISSIONS | {
  53. "teacher_directory.manage"
  54. }
  55. BUILTIN_ROLES = (
  56. BuiltinBotRole(
  57. "full_access",
  58. "全部职责",
  59. "拥有当前系统的全部机器人职责。",
  60. tuple(item.key for item in PERMISSIONS),
  61. ),
  62. BuiltinBotRole(
  63. "group_manager",
  64. "群组管理员",
  65. "负责群资料、成员、权限、公告、邀请链接、群规和群笔记。",
  66. (
  67. "chat.profile",
  68. "chat.members",
  69. "chat.permissions",
  70. "chat.announcements",
  71. "chat.invites",
  72. "chat.rules",
  73. "chat.notes",
  74. ),
  75. ),
  76. BuiltinBotRole(
  77. "automation_manager",
  78. "自动化与风控专员",
  79. "负责自动回复、欢迎语、验证码、聊天机器人、风控和资料监控。",
  80. ("automation.manage", "media.upload"),
  81. ),
  82. BuiltinBotRole(
  83. "points_manager",
  84. "积分专员",
  85. "负责积分规则、签到、排行榜和积分调整。",
  86. ("points.manage",),
  87. ),
  88. BuiltinBotRole(
  89. "giveaway_manager",
  90. "抽奖专员",
  91. "负责抽奖全流程,并可处理抽奖关联积分。",
  92. ("giveaways.manage", "points.manage"),
  93. ),
  94. BuiltinBotRole(
  95. "karma_manager",
  96. "声望专员",
  97. "负责点赞、点踩和声望排行。",
  98. ("karma.manage",),
  99. ),
  100. BuiltinBotRole(
  101. "teacher_directory_manager",
  102. "技师目录专员",
  103. "负责技师申请、位置、上下榜、在线状态和附近技师目录。",
  104. ("teacher_directory.manage",),
  105. ),
  106. )
  107. MODULE_PERMISSIONS: dict[str, frozenset[str]] = {
  108. "admin": frozenset({"chat.members", "chat.announcements", "chat.invites"}),
  109. "admin_misc": frozenset({"chat.profile", "chat.members"}),
  110. "admin_panel": GROUP_MANAGEMENT_PERMISSIONS,
  111. "antiservice": frozenset({"automation.manage"}),
  112. "blacklist": frozenset({"automation.manage"}),
  113. "blacklist_chat": frozenset({"automation.manage"}),
  114. "chatbot": frozenset({"automation.manage"}),
  115. "filters": frozenset({"automation.manage"}),
  116. "flood": frozenset({"automation.manage"}),
  117. "greetings": frozenset({"automation.manage"}),
  118. "giveaway": frozenset({"giveaways.manage"}),
  119. "karma": frozenset({"karma.manage"}),
  120. "locks": frozenset({"chat.permissions"}),
  121. "notes": frozenset({"chat.notes"}),
  122. "points": frozenset({"points.manage"}),
  123. "rules": frozenset({"chat.rules"}),
  124. "teacher_directory": frozenset({"teacher_directory.manage"}),
  125. "technician_services": frozenset({"teacher_directory.manage"}),
  126. }
  127. TELEGRAM_COMMAND_PERMISSIONS = {
  128. "purge": "chat.members",
  129. "kick": "chat.members",
  130. "dkick": "chat.members",
  131. "ban": "chat.members",
  132. "dban": "chat.members",
  133. "tban": "chat.members",
  134. "unban": "chat.members",
  135. "listban": "chat.members",
  136. "listunban": "chat.members",
  137. "del": "chat.members",
  138. "promote": "chat.members",
  139. "fullpromote": "chat.members",
  140. "demote": "chat.members",
  141. "mute": "chat.members",
  142. "tmute": "chat.members",
  143. "unmute": "chat.members",
  144. "ban_ghosts": "chat.members",
  145. "warn": "chat.members",
  146. "dwarn": "chat.members",
  147. "rmwarns": "chat.members",
  148. "warns": "chat.members",
  149. "report": "chat.members",
  150. "admins": "chat.members",
  151. "admin": "chat.members",
  152. "set_user_title": "chat.members",
  153. "pin": "chat.announcements",
  154. "unpin": "chat.announcements",
  155. "invite": "chat.invites",
  156. "set_chat_title": "chat.profile",
  157. "set_chat_photo": "chat.profile",
  158. }
  159. def permission_catalog() -> list[dict[str, str]]:
  160. return [asdict(item) for item in PERMISSIONS]
  161. def builtin_roles() -> list[dict[str, object]]:
  162. return [
  163. {
  164. "role_id": item.role_id,
  165. "name": item.name,
  166. "description": item.description,
  167. "permissions": list(item.permissions),
  168. "builtin": True,
  169. }
  170. for item in BUILTIN_ROLES
  171. ]
  172. def normalize_permissions(values: object) -> list[str]:
  173. if not isinstance(values, (list, tuple, set, frozenset)):
  174. values = str(values or "").replace(",", " ").split()
  175. normalized = list(dict.fromkeys(str(item).strip() for item in values if str(item).strip()))
  176. unknown = sorted(set(normalized) - ALL_BOT_PERMISSIONS)
  177. if unknown:
  178. raise ValueError(f"未知机器人权限:{', '.join(unknown)}")
  179. return normalized
  180. def has_permission(permission: str, permissions: Iterable[str] | None) -> bool:
  181. current = set(permissions or ())
  182. return "*" in current or permission in current
  183. def has_any_permission(
  184. required: Iterable[str],
  185. permissions: Iterable[str] | None,
  186. ) -> bool:
  187. current = set(permissions or ())
  188. return "*" in current or bool(set(required) & current)
  189. def module_allowed(module: str, permissions: Iterable[str] | None) -> bool:
  190. required = MODULE_PERMISSIONS.get(module)
  191. return not required or has_any_permission(required, permissions)
  192. def api_permission(method: str, path: str) -> str | None:
  193. if path.startswith("/api/admin/v1/directory"):
  194. return "teacher_directory.manage"
  195. if path.startswith("/api/admin/v1/media"):
  196. return "media.upload"
  197. if path.startswith("/api/admin/v1/points"):
  198. return "points.manage"
  199. if path.startswith("/api/admin/v1/giveaways"):
  200. return "giveaways.manage"
  201. if "/giveaway-bans" in path:
  202. return "giveaways.manage"
  203. if not path.startswith("/api/admin/v1/chats/"):
  204. return None
  205. if path.endswith("/profile"):
  206. return "chat.profile"
  207. if path.endswith("/permissions"):
  208. return "chat.permissions"
  209. if path.endswith("/announcements"):
  210. return "chat.announcements"
  211. if "/members" in path or path.endswith("/admins"):
  212. return "chat.members"
  213. if path.endswith("/invites"):
  214. return "chat.invites"
  215. if path.endswith("/rules"):
  216. return "chat.rules"
  217. if path.endswith("/automation"):
  218. return "automation.manage"
  219. if "/points/" in path:
  220. return "points.manage"
  221. return None