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
24 changes: 21 additions & 3 deletions astrbot/core/platform/astr_message_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,27 @@ async def react(self, emoji: str) -> None:
await self.send(MessageChain([Plain(emoji)]))

async def get_group(self, group_id: str | None = None, **kwargs) -> Group | None:
"""获取一个群聊的数据, 如果不填写 group_id: 如果是私聊消息,返回 None。如果是群聊消息,返回当前群聊的数据。
"""Get group information.

适配情况:
Platform event subclasses can enrich the result through their APIs. The
default implementation returns inbound group data, or an ID-only object
when an explicit group is queried.

- aiocqhttp(OneBotv11)
Args:
group_id: Group ID to query. Defaults to the current message group.
**kwargs: Extra platform-specific query options.

Returns:
Group information, or ``None`` for a private message without an
explicit group ID.
"""
resolved_group_id = group_id or self.get_group_id()
if not resolved_group_id:
return None
resolved_group_id = str(resolved_group_id)
if (
self.message_obj.group
and self.message_obj.group.group_id == resolved_group_id
):
return self.message_obj.group
return Group(group_id=resolved_group_id)
3 changes: 3 additions & 0 deletions astrbot/core/platform/astrbot_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class Group:
"""群管理员 id"""
members: list[MessageMember] | None = None
"""所有群成员"""
member_count: int | None = None
"""Total members, available even when the member list is incomplete."""

def __str__(self) -> str:
# 使用 f-string 来构建返回的字符串表示形式
Expand All @@ -42,6 +44,7 @@ def __str__(self) -> str:
f"Avatar: {self.group_avatar if self.group_avatar else 'N/A'}\n"
f"Owner ID: {self.group_owner if self.group_owner else 'N/A'}\n"
f"Admin IDs: {self.group_admins if self.group_admins else 'N/A'}\n"
f"Member Count: {self.member_count if self.member_count is not None else 'N/A'}\n"
f"Members Len: {len(self.members) if self.members else 0}\n"
f"First Member: {self.members[0] if self.members else 'N/A'}\n"
)
Expand Down
115 changes: 80 additions & 35 deletions astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from aiocqhttp import CQHttp, Event

from astrbot.api import logger
from astrbot.api.event import AstrMessageEvent, MessageChain
from astrbot.api.message_components import (
At,
Expand Down Expand Up @@ -234,50 +235,94 @@ async def send_streaming(
return await super().send_streaming(generator, use_fallback)

async def get_group(self, group_id=None, **kwargs):
if isinstance(group_id, str) and group_id.isdigit():
group_id = int(group_id)
elif self.get_group_id():
group_id = int(self.get_group_id())
else:
"""Get OneBot group details while preserving inbound data on failures.

Args:
group_id: Optional OneBot group identifier.
**kwargs: Reserved compatibility arguments.

Returns:
Enriched group information, or a basic group when an API is unavailable.
"""
resolved_group_id = group_id or self.get_group_id()
if not resolved_group_id:
return None
resolved_group_id = str(resolved_group_id)
api_group_id = (
int(resolved_group_id) if resolved_group_id.isdigit() else resolved_group_id
)

current_group = self.message_obj.group
group = (
current_group
if current_group and current_group.group_id == resolved_group_id
else Group(group_id=resolved_group_id)
)

routing_params = {}
if getattr(self.message_obj, "self_id", None):
routing_params["self_id"] = self.message_obj.self_id

info: dict = await self.bot.call_action(
"get_group_info",
group_id=group_id,
**routing_params,
)
try:
info = await self.bot.call_action(
"get_group_info",
group_id=api_group_id,
**routing_params,
)
if isinstance(info, dict):
group.group_name = info.get("group_name") or group.group_name
member_count = info.get("member_count")
if member_count is not None:
try:
group.member_count = int(member_count)
except (TypeError, ValueError):
logger.warning(
"[aiocqhttp] Invalid member_count for group %s",
resolved_group_id,
)
except Exception as exc:
logger.warning(
"[aiocqhttp] Failed to get group information for %s: %s",
resolved_group_id,
exc,
)

members: list[dict] = await self.bot.call_action(
"get_group_member_list",
group_id=group_id,
**routing_params,
)
try:
members = await self.bot.call_action(
"get_group_member_list",
group_id=api_group_id,
**routing_params,
)
except Exception as exc:
logger.warning(
"[aiocqhttp] Failed to get members for group %s: %s",
resolved_group_id,
exc,
)
return group
if not isinstance(members, list):
return group

owner_id = None
admin_ids = []
admin_ids: list[str] = []
for member in members:
if member["role"] == "owner":
owner_id = member["user_id"]
if member["role"] == "admin":
admin_ids.append(member["user_id"])

group = Group(
group_id=str(group_id),
group_name=info.get("group_name"),
group_avatar="",
group_admins=admin_ids,
group_owner=str(owner_id),
members=[
MessageMember(
user_id=member["user_id"],
nickname=member.get("nickname") or member.get("card"),
)
for member in members
],
)
if not isinstance(member, dict) or member.get("user_id") is None:
continue
if member.get("role") == "owner":
owner_id = str(member["user_id"])
if member.get("role") == "admin":
admin_ids.append(str(member["user_id"]))

group.group_admins = admin_ids
group.group_owner = owner_id
group.members = [
MessageMember(
user_id=str(member["user_id"]),
nickname=member.get("nickname") or member.get("card"),
)
for member in members
if isinstance(member, dict) and member.get("user_id") is not None
]
if group.member_count is None:
group.member_count = len(group.members)
return group
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ async def _convert_handle_message_event(
abm.type = MessageType.GROUP_MESSAGE
abm.group_id = str(event.group_id)
abm.group = Group(str(event.group_id))
abm.group.group_name = event.get("group_name", "N/A")
abm.group.group_name = event.get("group_name")
elif event["message_type"] == "private":
abm.type = MessageType.FRIEND_MESSAGE
abm.session_id = (
Expand Down
2 changes: 2 additions & 0 deletions astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ async def convert_msg(
if index == 0 and id == abm.self_id:
leading_at_is_self = True
abm.group_id = message.conversation_id
if abm.group:
abm.group.group_name = message.conversation_title
abm.session_id = abm.group_id
else:
abm.session_id = abm.sender.user_id
Expand Down
34 changes: 34 additions & 0 deletions astrbot/core/platform/sources/discord/discord_platform_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ async def send_by_session(
if channel:
message_obj.type = self._get_message_type(channel)
message_obj.group_id = self._get_channel_id(channel)
group_name = self._get_group_name(channel)
if (
message_obj.type == MessageType.GROUP_MESSAGE
and message_obj.group
and group_name
):
message_obj.group.group_name = group_name
else:
logger.warning(
f"[Discord] Can't get channel info for {channel_id_str}, will guess message type.",
Expand Down Expand Up @@ -189,6 +196,27 @@ def _get_channel_id(
"""根据 channel 对象获取ID"""
return str(getattr(channel, "id", None))

@staticmethod
def _get_group_name(
channel: Messageable | GuildChannel | PrivateChannel,
) -> str | None:
"""Build the AstrBot group name for a Discord guild channel.

Args:
channel: Discord channel or thread associated with the message.

Returns:
``<guild name>-<channel name>`` when both are available, otherwise the
available name, or ``None`` when neither has a name.
"""
channel_name = getattr(channel, "name", None)
guild_name = getattr(getattr(channel, "guild", None), "name", None)
if isinstance(guild_name, str) and isinstance(channel_name, str):
return f"{guild_name}-{channel_name}"
if isinstance(channel_name, str):
return channel_name
return guild_name if isinstance(guild_name, str) else None

def _convert_message_to_abm(self, data: dict) -> AstrBotMessage:
"""将普通消息转换为 AstrBotMessage"""
message = data["message"]
Expand Down Expand Up @@ -226,6 +254,9 @@ def _convert_message_to_abm(self, data: dict) -> AstrBotMessage:
abm = AstrBotMessage()
abm.type = self._get_message_type(message.channel)
abm.group_id = self._get_channel_id(message.channel)
group_name = self._get_group_name(message.channel)
if abm.type == MessageType.GROUP_MESSAGE and abm.group and group_name:
abm.group.group_name = group_name
abm.message_str = content
abm.sender = MessageMember(
user_id=str(message.author.id),
Expand Down Expand Up @@ -512,6 +543,9 @@ async def dynamic_callback(
if channel is not None:
abm.type = self._get_message_type(channel, ctx.guild_id)
abm.group_id = self._get_channel_id(channel)
group_name = self._get_group_name(channel)
if abm.type == MessageType.GROUP_MESSAGE and abm.group and group_name:
abm.group.group_name = group_name
else:
# 防守式兜底:channel 取不到时,仍能根据 guild_id/channel_id 推断会话信息
abm.type = (
Expand Down
Loading
Loading