member_identity.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. from __future__ import annotations
  2. import asyncio
  3. from collections import defaultdict
  4. from typing import Any
  5. from wbb import app, log
  6. from wbb.utils.dbadmin import (
  7. get_managed_chat_settings,
  8. get_recent_chat_member,
  9. record_member_identity_change,
  10. upsert_recent_chat_member,
  11. )
  12. from wbb.utils.dbpoints import update_point_account_identity
  13. _identity_locks: defaultdict[tuple[int, int], asyncio.Lock] = defaultdict(asyncio.Lock)
  14. def display_name(first_name: str | None, last_name: str | None) -> str:
  15. return " ".join(value.strip() for value in (first_name, last_name) if value and value.strip())
  16. def user_display_name(user: Any) -> str:
  17. return (
  18. display_name(getattr(user, "first_name", None), getattr(user, "last_name", None))
  19. or (
  20. f"@{user.username}"
  21. if getattr(user, "username", None)
  22. else str(user.id)
  23. )
  24. )
  25. def _snapshot(*, username: str | None, first_name: str | None, last_name: str | None) -> dict[str, Any]:
  26. return {
  27. "username": username,
  28. "first_name": first_name,
  29. "last_name": last_name,
  30. "display_name": display_name(first_name, last_name),
  31. }
  32. def _username_label(value: str | None) -> str:
  33. return f"@{value}" if value else "未设置"
  34. async def observe_member_identity(*, chat_id: int, user: Any) -> dict[str, Any] | None:
  35. user_id = int(user.id)
  36. username = getattr(user, "username", None)
  37. first_name = getattr(user, "first_name", None)
  38. last_name = getattr(user, "last_name", None)
  39. is_bot = bool(getattr(user, "is_bot", False))
  40. async with _identity_locks[(int(chat_id), user_id)]:
  41. previous = await get_recent_chat_member(chat_id, user_id)
  42. await upsert_recent_chat_member(
  43. chat_id=chat_id,
  44. user_id=user_id,
  45. username=username,
  46. first_name=first_name,
  47. last_name=last_name,
  48. is_bot=is_bot,
  49. )
  50. await update_point_account_identity(
  51. chat_id=chat_id,
  52. user_id=user_id,
  53. username=username,
  54. first_name=first_name,
  55. last_name=last_name,
  56. display_name=display_name(first_name, last_name),
  57. )
  58. if not previous or is_bot:
  59. return None
  60. settings = await get_managed_chat_settings(chat_id)
  61. monitor = settings.get("identity_monitor") or {}
  62. if not monitor.get("enabled"):
  63. return None
  64. before = _snapshot(
  65. username=previous.get("username"),
  66. first_name=previous.get("first_name"),
  67. last_name=previous.get("last_name"),
  68. )
  69. after = _snapshot(
  70. username=username,
  71. first_name=first_name,
  72. last_name=last_name,
  73. )
  74. changed_fields = [
  75. field
  76. for field in ("username", "display_name")
  77. if before[field] != after[field]
  78. ]
  79. if not changed_fields:
  80. return None
  81. event = await record_member_identity_change(
  82. chat_id=chat_id,
  83. user_id=user_id,
  84. before=before,
  85. after=after,
  86. changed_fields=changed_fields,
  87. )
  88. if monitor.get("notify_in_chat"):
  89. details = []
  90. if "display_name" in changed_fields:
  91. details.append(
  92. f"昵称:{before['display_name'] or '未设置'} -> "
  93. f"{after['display_name'] or '未设置'}"
  94. )
  95. if "username" in changed_fields:
  96. details.append(
  97. f"用户名:{_username_label(before['username'])} -> "
  98. f"{_username_label(after['username'])}"
  99. )
  100. try:
  101. await app.send_message(
  102. chat_id,
  103. "成员资料发生变化\n"
  104. f"用户 ID:{user_id}\n"
  105. + "\n".join(details),
  106. )
  107. except Exception as exc:
  108. log.error(f"群 {chat_id} 成员 {user_id} 资料变更提醒发送失败:{exc}")
  109. return event
  110. async def observe_message_member_identities(
  111. *, chat_id: int, message: Any
  112. ) -> list[dict[str, Any]]:
  113. users: dict[int, Any] = {}
  114. sender = getattr(message, "from_user", None)
  115. if sender:
  116. users[int(sender.id)] = sender
  117. for member in getattr(message, "new_chat_members", None) or []:
  118. users[int(member.id)] = member
  119. changes: list[dict[str, Any]] = []
  120. for user in users.values():
  121. event = await observe_member_identity(chat_id=chat_id, user=user)
  122. if event:
  123. changes.append(event)
  124. return changes