mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-14 08:26:07 +00:00
fix(aiocqhttp): resolve group member metadata (#2298)
* fix(aiocqhttp): resolve group member metadata * style: ruff format
This commit is contained in:
@@ -4,6 +4,7 @@ import asyncio
|
||||
import traceback
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
|
||||
import aiocqhttp
|
||||
import pydantic
|
||||
@@ -16,6 +17,14 @@ from ...utils import image
|
||||
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
|
||||
|
||||
|
||||
_GROUP_NAME_CACHE_TTL_SECONDS = 3600
|
||||
_GROUP_NAME_NEGATIVE_CACHE_TTL_SECONDS = 60
|
||||
_GROUP_NAME_LOOKUP_TIMEOUT_SECONDS = 2
|
||||
_GROUP_MEMBER_INFO_CACHE_TTL_SECONDS = 86400
|
||||
_GROUP_MEMBER_INFO_NEGATIVE_CACHE_TTL_SECONDS = 600
|
||||
_GROUP_MEMBER_INFO_LOOKUP_TIMEOUT_SECONDS = 2
|
||||
|
||||
|
||||
def _normalize_base64_payload(value: str) -> str:
|
||||
if value.startswith('base64://'):
|
||||
return value.removeprefix('base64://')
|
||||
@@ -24,6 +33,21 @@ def _normalize_base64_payload(value: str) -> str:
|
||||
return value
|
||||
|
||||
|
||||
def _get_field(data: dict, key: str, default: str = '') -> str:
|
||||
value = data.get(key)
|
||||
if value is None:
|
||||
return default
|
||||
return str(value)
|
||||
|
||||
|
||||
def _get_group_member_name(sender: dict) -> str:
|
||||
return _get_field(sender, 'card') or _get_field(sender, 'nickname') or _get_field(sender, 'user_id')
|
||||
|
||||
|
||||
def _get_group_name_placeholder(group_id: typing.Union[int, str]) -> str:
|
||||
return f'Group {group_id}'
|
||||
|
||||
|
||||
class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
@staticmethod
|
||||
async def yiri2target(
|
||||
@@ -335,16 +359,96 @@ class AiocqhttpMessageConverter(abstract_platform_adapter.AbstractMessageConvert
|
||||
|
||||
|
||||
class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
def __init__(self):
|
||||
self._group_name_cache: dict[typing.Union[int, str], tuple[str, float]] = {}
|
||||
self._group_name_negative_cache: dict[typing.Union[int, str], float] = {}
|
||||
self._group_member_info_cache: dict[
|
||||
tuple[typing.Union[int, str], typing.Union[int, str]], tuple[dict, float]
|
||||
] = {}
|
||||
self._group_member_info_negative_cache: dict[tuple[typing.Union[int, str], typing.Union[int, str]], float] = {}
|
||||
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.MessageEvent, bot_account_id: int):
|
||||
return event.source_platform_object
|
||||
|
||||
@staticmethod
|
||||
async def target2yiri(event: aiocqhttp.Event, bot=None):
|
||||
async def _get_group_name(self, group_id: typing.Union[int, str], bot=None) -> str:
|
||||
now = time.monotonic()
|
||||
if group_id in self._group_name_cache:
|
||||
group_name, expires_at = self._group_name_cache[group_id]
|
||||
if expires_at > now:
|
||||
return group_name
|
||||
del self._group_name_cache[group_id]
|
||||
if group_id in self._group_name_negative_cache:
|
||||
expires_at = self._group_name_negative_cache[group_id]
|
||||
if expires_at > now:
|
||||
return ''
|
||||
del self._group_name_negative_cache[group_id]
|
||||
if bot is None:
|
||||
return ''
|
||||
try:
|
||||
group_info = await asyncio.wait_for(
|
||||
bot.get_group_info(group_id=group_id),
|
||||
timeout=_GROUP_NAME_LOOKUP_TIMEOUT_SECONDS,
|
||||
)
|
||||
except Exception:
|
||||
self._group_name_negative_cache[group_id] = now + _GROUP_NAME_NEGATIVE_CACHE_TTL_SECONDS
|
||||
return ''
|
||||
group_name = _get_field(group_info, 'group_name') if isinstance(group_info, dict) else ''
|
||||
if group_name:
|
||||
self._group_name_cache[group_id] = (group_name, now + _GROUP_NAME_CACHE_TTL_SECONDS)
|
||||
self._group_name_negative_cache.pop(group_id, None)
|
||||
else:
|
||||
self._group_name_negative_cache[group_id] = now + _GROUP_NAME_NEGATIVE_CACHE_TTL_SECONDS
|
||||
return group_name
|
||||
|
||||
async def _get_group_member_info(
|
||||
self,
|
||||
group_id: typing.Union[int, str],
|
||||
user_id: typing.Union[int, str],
|
||||
bot=None,
|
||||
) -> dict:
|
||||
now = time.monotonic()
|
||||
cache_key = (group_id, user_id)
|
||||
if cache_key in self._group_member_info_cache:
|
||||
member_info, expires_at = self._group_member_info_cache[cache_key]
|
||||
if expires_at > now:
|
||||
return member_info
|
||||
del self._group_member_info_cache[cache_key]
|
||||
if cache_key in self._group_member_info_negative_cache:
|
||||
expires_at = self._group_member_info_negative_cache[cache_key]
|
||||
if expires_at > now:
|
||||
return {}
|
||||
del self._group_member_info_negative_cache[cache_key]
|
||||
if bot is None:
|
||||
return {}
|
||||
try:
|
||||
member_info = await asyncio.wait_for(
|
||||
bot.get_group_member_info(group_id=group_id, user_id=user_id),
|
||||
timeout=_GROUP_MEMBER_INFO_LOOKUP_TIMEOUT_SECONDS,
|
||||
)
|
||||
except Exception:
|
||||
self._group_member_info_negative_cache[cache_key] = now + _GROUP_MEMBER_INFO_NEGATIVE_CACHE_TTL_SECONDS
|
||||
return {}
|
||||
if isinstance(member_info, dict) and member_info:
|
||||
self._group_member_info_cache[cache_key] = (
|
||||
member_info,
|
||||
now + _GROUP_MEMBER_INFO_CACHE_TTL_SECONDS,
|
||||
)
|
||||
self._group_member_info_negative_cache.pop(cache_key, None)
|
||||
return member_info
|
||||
self._group_member_info_negative_cache[cache_key] = now + _GROUP_MEMBER_INFO_NEGATIVE_CACHE_TTL_SECONDS
|
||||
return {}
|
||||
|
||||
async def target2yiri(self, event: aiocqhttp.Event, bot=None):
|
||||
yiri_chain = await AiocqhttpMessageConverter.target2yiri(event.message, event.message_id, bot)
|
||||
|
||||
if event.message_type == 'group':
|
||||
permission = 'MEMBER'
|
||||
group_name = await self._get_group_name(event.group_id, bot) or _get_group_name_placeholder(event.group_id)
|
||||
special_title = _get_field(event.sender, 'title')
|
||||
if not special_title:
|
||||
member_info = await self._get_group_member_info(event.group_id, event.sender['user_id'], bot)
|
||||
special_title = _get_field(member_info, 'title')
|
||||
|
||||
if 'role' in event.sender:
|
||||
if event.sender['role'] == 'admin':
|
||||
@@ -354,14 +458,14 @@ class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
converted_event = platform_events.GroupMessage(
|
||||
sender=platform_entities.GroupMember(
|
||||
id=event.sender['user_id'], # message_seq 放哪?
|
||||
member_name=event.sender['nickname'],
|
||||
member_name=_get_group_member_name(event.sender),
|
||||
permission=permission,
|
||||
group=platform_entities.Group(
|
||||
id=event.group_id,
|
||||
name=event.sender['nickname'],
|
||||
name=group_name,
|
||||
permission=platform_entities.Permission.Member,
|
||||
),
|
||||
special_title=event.sender['title'] if 'title' in event.sender else '',
|
||||
special_title=special_title,
|
||||
),
|
||||
message_chain=yiri_chain,
|
||||
time=event.time,
|
||||
@@ -385,7 +489,7 @@ class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter)
|
||||
bot: aiocqhttp.CQHttp = pydantic.Field(exclude=True, default_factory=aiocqhttp.CQHttp)
|
||||
|
||||
message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter()
|
||||
event_converter: AiocqhttpEventConverter = AiocqhttpEventConverter()
|
||||
event_converter: AiocqhttpEventConverter = pydantic.Field(default_factory=AiocqhttpEventConverter)
|
||||
|
||||
on_websocket_connection_event_cache: typing.List[typing.Callable[[aiocqhttp.Event], None]] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user