giveaway_eligibility.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. from __future__ import annotations
  2. from typing import Any
  3. from pyrogram.enums import ChatMemberStatus
  4. from wbb import BOT_ID, app
  5. class EligibilityUnavailable(RuntimeError):
  6. """Telegram cannot give a trustworthy membership decision."""
  7. def is_member(member: Any) -> bool:
  8. if member.status in {ChatMemberStatus.LEFT, ChatMemberStatus.BANNED}:
  9. return False
  10. if member.status == ChatMemberStatus.RESTRICTED:
  11. return bool(getattr(member, "is_member", False))
  12. return member.status in {
  13. ChatMemberStatus.OWNER,
  14. ChatMemberStatus.ADMINISTRATOR,
  15. ChatMemberStatus.MEMBER,
  16. }
  17. async def validate_targets(targets: list[dict[str, Any]]) -> list[dict[str, Any]]:
  18. if len(targets) > 10:
  19. raise ValueError("最多设置 10 个资格频道或群。")
  20. normalized: list[dict[str, Any]] = []
  21. seen: set[int] = set()
  22. for target in targets:
  23. try:
  24. chat_id = int(target["chat_id"])
  25. except (KeyError, TypeError, ValueError) as exc:
  26. raise ValueError("资格目标必须填写有效的 Telegram ID。") from exc
  27. if chat_id in seen:
  28. continue
  29. seen.add(chat_id)
  30. try:
  31. chat = await app.get_chat(chat_id)
  32. bot = await app.get_chat_member(chat_id, BOT_ID)
  33. except Exception as exc:
  34. raise ValueError(f"机器人无法访问资格目标 {chat_id}。") from exc
  35. kind = str(getattr(chat.type, "value", chat.type)).lower()
  36. if kind not in {"channel", "group", "supergroup"}:
  37. raise ValueError("资格目标只能是频道或群。")
  38. if bot.status not in {ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR}:
  39. raise ValueError(f"机器人必须是资格目标 {chat_id} 的管理员。")
  40. normalized.append(
  41. {
  42. "chat_id": chat_id, "type": kind,
  43. "title": str(chat.title or chat_id)[:200],
  44. "username": str(getattr(chat, "username", None) or "")[:100],
  45. }
  46. )
  47. return normalized
  48. async def check_eligibility(
  49. giveaway: dict[str, Any], user_id: int
  50. ) -> tuple[bool, list[int]]:
  51. targets = list(giveaway.get("eligibility_targets") or [])
  52. chat_ids = [int(giveaway["chat_id"]), *[int(item["chat_id"]) for item in targets]]
  53. outcomes: list[bool] = []
  54. failed: list[int] = []
  55. unknown: list[int] = []
  56. for chat_id in dict.fromkeys(chat_ids):
  57. try:
  58. if chat_id != int(giveaway["chat_id"]):
  59. bot = await app.get_chat_member(chat_id, BOT_ID)
  60. if bot.status not in {ChatMemberStatus.OWNER, ChatMemberStatus.ADMINISTRATOR}:
  61. raise EligibilityUnavailable(f"机器人不再是 {chat_id} 的管理员")
  62. member = await app.get_chat_member(chat_id, user_id)
  63. except EligibilityUnavailable:
  64. unknown.append(chat_id)
  65. continue
  66. except Exception:
  67. unknown.append(chat_id)
  68. continue
  69. present = is_member(member)
  70. if chat_id == int(giveaway["chat_id"]):
  71. if not present:
  72. failed.append(chat_id)
  73. else:
  74. outcomes.append(present)
  75. if not present:
  76. failed.append(chat_id)
  77. mode = giveaway.get("eligibility_mode", "all")
  78. if int(giveaway["chat_id"]) in failed:
  79. return False, failed
  80. if int(giveaway["chat_id"]) in unknown:
  81. raise EligibilityUnavailable("无法核验报名群的成员状态")
  82. if any(int(item["chat_id"]) == int(giveaway["chat_id"]) for item in targets):
  83. outcomes.append(True)
  84. if mode == "all" and failed:
  85. return False, failed
  86. if mode == "any" and any(outcomes):
  87. return True, []
  88. if unknown:
  89. raise EligibilityUnavailable(
  90. "无法核验以下频道或群的成员状态:" + ", ".join(map(str, unknown))
  91. )
  92. eligible = not targets or (all(outcomes) if mode == "all" else any(outcomes))
  93. return eligible, ([] if eligible else failed)