Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions onebot_adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ if ctx:
| `send_private_msg` | `user_id`、`message` | `data.message_id` |
| `delete_msg` | `message_id` | 当前实现依赖消息 ID,失败会返回 `retcode=1` |
| `get_login_info` | 无 | `user_id`、`nickname` |
| `get_group_info` | `group_id` | 实时群名称、当前成员数和成员上限 |
| `get_group_list` | 无 | 已缓存群列表 |
| `get_friend_list` | 无 | 已缓存用户列表 |
| `get_stranger_info` | `user_id` | 最小用户信息 |
Expand Down
2 changes: 2 additions & 0 deletions onebot_adapter/action_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
CanSendRecordAction,
DeleteMessageAction,
GetFriendListAction,
GetGroupInfoAction,
GetGroupListAction,
GetGroupMemberInfoAction,
GetGroupMemberListAction,
Expand Down Expand Up @@ -71,6 +72,7 @@ def create_default(cls, ctx: ActionContext) -> ActionRegistry:
'send_private_msg': SendMessageAction(ctx, force_type='private'),
'delete_msg': DeleteMessageAction(ctx),
'get_login_info': GetLoginInfoAction(ctx),
'get_group_info': GetGroupInfoAction(ctx),
'get_group_list': GetGroupListAction(ctx),
'get_friend_list': GetFriendListAction(ctx),
'get_stranger_info': GetStrangerInfoAction(ctx),
Expand Down
3 changes: 3 additions & 0 deletions onebot_adapter/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- SendMessageAction: send_msg / send_group_msg / send_private_msg
- DeleteMessageAction: delete_msg
- GetLoginInfoAction: get_login_info
- GetGroupInfoAction: get_group_info
- GetGroupListAction: get_group_list
- GetFriendListAction: get_friend_list
- GetStrangerInfoAction: get_stranger_info
Expand All @@ -23,6 +24,7 @@
from modules.onebot_adapter.actions.can_send_record import CanSendRecordAction
from modules.onebot_adapter.actions.delete_msg import DeleteMessageAction
from modules.onebot_adapter.actions.get_friend_list import GetFriendListAction
from modules.onebot_adapter.actions.get_group_info import GetGroupInfoAction
from modules.onebot_adapter.actions.get_group_list import GetGroupListAction
from modules.onebot_adapter.actions.get_group_member_info import (
GetGroupMemberInfoAction,
Expand All @@ -43,6 +45,7 @@
'CanSendRecordAction',
'DeleteMessageAction',
'GetFriendListAction',
'GetGroupInfoAction',
'GetGroupListAction',
'GetGroupMemberInfoAction',
'GetGroupMemberListAction',
Expand Down
78 changes: 78 additions & 0 deletions onebot_adapter/actions/get_group_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""get_group_info - query and return real group information."""

from __future__ import annotations

from typing import Any

from modules.onebot_adapter.base_action import BaseAction


class GetGroupInfoAction(BaseAction):
"""Convert QQ group information to the OneBot 11 response shape."""

async def execute(self, params: dict[str, Any], echo: str | None = None) -> dict[str, Any]:
group_id = params.get('group_id', 0)
self._ctx.log.info(f'get_group_info 请求: group_id={group_id}')

group_openid = await self._resolve_group_openid(group_id)
if not group_openid:
self._ctx.log.warning(f'get_group_info 参数错误: 无效 group_id={group_id}')
return self._fail('缺少有效的 group_id', echo=echo)

sender = self._ctx.get_sender()
if not sender:
self._ctx.log.warning(
f'get_group_info 查询失败: group_id={group_openid}, '
'reason=无可用的消息发送器'
)
return self._fail('无可用的消息发送器', echo=echo)

group, error = await sender.get_group_info(group_openid, return_error=True)
if group is None:
self._ctx.log.warning(
f'get_group_info 查询失败: group_id={group_openid}, response={error}'
)
return self._platform_fail(error, echo)

member_count = self._to_non_negative_int(
group.get('group_member_num', group.get('member_count', 0))
)
max_member_count = self._to_non_negative_int(
group.get('max_group_member_num', group.get('max_member_count', 0))
)
result = {
'group_id': group_id,
'group_name': str(group.get('group_name') or ''),
'member_count': member_count,
'max_member_count': max_member_count,
}
self._ctx.log.info(
f'get_group_info 成功: group_id={group_openid}, member_count={member_count}'
)
return self._ok(result, echo=echo)

async def _resolve_group_openid(self, raw_id: Any) -> str | None:
value = str(raw_id or '').strip()
if not value:
return None
if not value.isdigit():
return value
if not self._ctx.id_mapper:
return None
return await self._ctx.id_mapper.to_openid_by_type(int(value), 'group')

def _platform_fail(self, response: Any, echo: str | None) -> dict[str, Any]:
data = response if isinstance(response, dict) else {}
message = str(data.get('message') or data.get('msg') or response or '查询群信息失败')
try:
retcode = int(data.get('code') or data.get('err_code') or 1)
except (TypeError, ValueError):
retcode = 1
return self._fail(message, echo=echo, retcode=retcode)

@staticmethod
def _to_non_negative_int(value: Any) -> int:
try:
return max(0, int(value or 0))
except (TypeError, ValueError):
return 0
58 changes: 57 additions & 1 deletion onebot_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

from __future__ import annotations

import asyncio
import time
from typing import TYPE_CHECKING, Any

from modules.onebot_adapter.action_context import ActionContext
Expand Down Expand Up @@ -58,6 +60,9 @@ class OneBotAdapter:
- 事件格式转换 → lib/event_converter.py
"""

_GROUP_NAME_CACHE_TTL = 300
_EMPTY_GROUP_NAME_CACHE_TTL = 3600

# --- instance variables (declared for type checker) ---
_mctx: ModuleContext
log: Any
Expand Down Expand Up @@ -87,6 +92,8 @@ def __init__(self, module_ctx: ModuleContext) -> None:

# 运行时状态
self._bm: BotManager | None = None # BotManager 引用
self._group_name_cache: dict[tuple[str, str], tuple[float, str]] = {}
self._group_name_locks: dict[tuple[str, str], asyncio.Lock] = {}

# ==================== 生命周期 ====================

Expand Down Expand Up @@ -348,6 +355,49 @@ async def _stop_network(self) -> None:

# ==================== 事件处理 (Observer) ====================

async def _get_cached_group_name(self, event: Event, bot: Any) -> str:
"""优先读取已保存的群名,未命中时查询一次群资料。"""
group_id = str(event.group_id or '')
if not event.is_group or not group_id:
return ''

cache_key = (str(event.appid or ''), group_id)
now = time.monotonic()
cached = self._group_name_cache.get(cache_key)
if cached and cached[0] > now:
return cached[1]

lock = self._group_name_locks.setdefault(cache_key, asyncio.Lock())
async with lock:
now = time.monotonic()
cached = self._group_name_cache.get(cache_key)
if cached and cached[0] > now:
return cached[1]

group_name = ''
sender = getattr(bot, 'sender', None)
get_group_record = getattr(sender, 'get_group_record', None)
if get_group_record is not None:
try:
record = await get_group_record(group_id)
if isinstance(record, dict):
group_name = str(record.get('group_name') or '')
except Exception as e:
self.log.debug(f'读取 OneBot 上报群名失败: group_id={group_id}, error={e}')

get_group_info = getattr(sender, 'get_group_info', None)
if not group_name and get_group_info is not None:
try:
group_info = await get_group_info(group_id)
if isinstance(group_info, dict):
group_name = str(group_info.get('group_name') or '')
except Exception as e:
self.log.debug(f'刷新 OneBot 上报群名失败: group_id={group_id}, error={e}')

ttl = self._GROUP_NAME_CACHE_TTL if group_name else self._EMPTY_GROUP_NAME_CACHE_TTL
self._group_name_cache[cache_key] = (now + ttl, group_name)
return group_name

async def _on_raw_event(self, event: Event, bot: Any) -> None:
"""on_raw_event 回调 — 将事件转为 OneBot 格式推送到 WS 客户端

Expand Down Expand Up @@ -391,7 +441,13 @@ async def _on_raw_event(self, event: Event, bot: Any) -> None:
if event.is_lifecycle:
ob_event = await convert_lifecycle_event(event, self.id_mapper, self_qq)
else:
ob_event = await convert_message_event(event, self.id_mapper, self_qq)
group_name = await self._get_cached_group_name(event, bot)
ob_event = await convert_message_event(
event,
self.id_mapper,
self_qq,
group_name=group_name,
)

if ob_event:
if has_ws:
Expand Down
9 changes: 8 additions & 1 deletion onebot_adapter/lib/event_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ def _segments_to_raw(segments: list[dict]) -> str:
return ''.join(parts)


async def convert_message_event(event, id_mapper, self_qq: int) -> dict | None:
async def convert_message_event(
event,
id_mapper,
self_qq: int,
*,
group_name: str = '',
) -> dict | None:
"""将 Elaina Event 转换为 OneBot 11 message 事件"""
et = event.event_type
if et not in (
Expand Down Expand Up @@ -157,6 +163,7 @@ async def convert_message_event(event, id_mapper, self_qq: int) -> dict | None:

if is_group:
ob_event['group_id'] = qq_group
ob_event['group_name'] = str(group_name or '')
ob_event['sender']['card'] = ''
ob_event['sender']['role'] = event.member_role
ob_event['anonymous'] = None
Expand Down
2 changes: 1 addition & 1 deletion onebot_adapter/payload/message_sender_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

from core.message._http import MessageType
from core.message.sender import MessageSender
from modules.onebot_adapter.payload.segment_parser import ParsedMessage
from modules.onebot_adapter.payload.payload_converter import PayloadConverter
from modules.onebot_adapter.payload.segment_parser import ParsedMessage

Expand Down Expand Up @@ -140,6 +139,7 @@ async def _send_media(
media_data,
media_type,
ctn,
file_name=parsed.file_name,
target_group_id=group_id,
target_user_id=user_id,
**kwargs,
Expand Down