member_identity.py 4.8 KB

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