feat(onebot): add group_name to group message events (#212)
Group message events (message + message_sent) now carry group_name — a non-standard but common OneBot extension. The name comes from the message's own wire field (ResponseGrp field 7, accurate even right after a rename), falling back to the identity group cache, else '' when neither is available. It's resolved in the group-message decoder, where both the wire head and the identity cache are already at hand, so the converter just passes it through (no ConverterContext change). Verified live (tempserver): real inbound group messages from a non-self account carry group_name set to the actual group name.
This commit is contained in:
@@ -73,6 +73,7 @@ export async function convertGroupMessage(ctx: ConverterContext, event: GroupMes
|
||||
message_id: messageId,
|
||||
message_seq: event.msgSeq,
|
||||
group_id: event.groupId,
|
||||
group_name: event.groupName,
|
||||
user_id: event.senderUin,
|
||||
message: segments,
|
||||
raw_message: segmentsToRawMessage(segments),
|
||||
|
||||
@@ -54,6 +54,7 @@ function makeGroupMessage(senderUin: number): GroupMessage {
|
||||
time: 1700000000,
|
||||
selfUin: SELF_ID,
|
||||
groupId: 99999,
|
||||
groupName: 'Test Group 测试群',
|
||||
senderUin,
|
||||
senderNick: senderUin === SELF_ID ? 'me' : 'peer',
|
||||
senderCard: '',
|
||||
@@ -100,6 +101,7 @@ describe('convertEvent — message kinds', () => {
|
||||
expect(out!.message_type).toBe('group');
|
||||
expect(out!.sub_type).toBe('normal');
|
||||
expect(out!.group_id).toBe(GROUP_ID);
|
||||
expect(out!.group_name).toBe('Test Group 测试群');
|
||||
});
|
||||
|
||||
it('group_message self → post_type "message_sent"', async () => {
|
||||
|
||||
@@ -133,6 +133,10 @@ export interface FriendMessage extends QQEvent {
|
||||
export interface GroupMessage extends QQEvent {
|
||||
kind: 'group_message';
|
||||
groupId: number;
|
||||
/** Group display name. From the message's own wire field (ResponseGrp
|
||||
* field 7 — accurate even right after a rename), falling back to the
|
||||
* identity group cache, else '' when neither is available. */
|
||||
groupName: string;
|
||||
senderUin: number;
|
||||
senderNick: string;
|
||||
senderCard: string;
|
||||
|
||||
@@ -12,6 +12,7 @@ export const decodeGroupMessage: MsgPushDecoder = (ctx) => {
|
||||
msgId: ctx.head.msgId & 0x7FFFFFFF,
|
||||
elements: decodeRichBody(ctx.body, true),
|
||||
groupId: 0,
|
||||
groupName: '',
|
||||
senderNick: '',
|
||||
senderCard: '',
|
||||
senderRole: '',
|
||||
@@ -43,5 +44,9 @@ export const decodeGroupMessage: MsgPushDecoder = (ctx) => {
|
||||
} else if (!ev.senderNick && grp?.memberCard) {
|
||||
ev.senderNick = grp.memberCard;
|
||||
}
|
||||
// Group name: prefer the message's own wire field (ResponseGrp field 7 —
|
||||
// fresh even right after a rename), fall back to the cached group list, else
|
||||
// leave it empty. `ev.groupId` is set above from grp.groupUin.
|
||||
ev.groupName = grp?.groupName || ctx.identity.findGroup(ev.groupId)?.groupName || '';
|
||||
return [ev];
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { SsoGetGroupMsg, SsoGetGroupMsgResponse } from '@snowluma/proto-def
|
||||
import type { IdentityService } from '../../src/identity-service';
|
||||
import { SSO_GET_GROUP_MSG_CMD, fetchGroupMessageRange } from '../../src/msg-push';
|
||||
|
||||
const identity = { findGroupMember: () => undefined } as unknown as IdentityService;
|
||||
const identity = { findGroupMember: () => undefined, findGroup: () => null } as unknown as IdentityService;
|
||||
|
||||
function okResult(data: Uint8Array): SendPacketResult {
|
||||
return { success: true, gotResponse: true, responseData: data } as SendPacketResult;
|
||||
@@ -69,7 +69,7 @@ describe('fetchGroupMessageRange / SsoGetGroupMsg', () => {
|
||||
|
||||
it('[#1] prefers the fresher group card in grp.memberCard (field 4) over a stale cache card', async () => {
|
||||
const stale = { uin: 222, uid: '', nickname: 'BaseNick', card: 'StaleCard', role: 'member', level: 0, title: '', joinTime: 0, lastSentTime: 0, shutUpTime: 0 };
|
||||
const idWithMember = { findGroupMember: (_g: number, u: number) => (u === 222 ? stale : undefined) } as unknown as IdentityService;
|
||||
const idWithMember = { findGroupMember: (_g: number, u: number) => (u === 222 ? stale : undefined), findGroup: () => null } as unknown as IdentityService;
|
||||
const resp = protobuf_encode<SsoGetGroupMsgResponse>({
|
||||
body: { groupUin: 9999, messages: [
|
||||
{ responseHead: { fromUin: 222, grp: { groupUin: 9999, memberCard: 'FreshCard' } }, // field2 empty, field4 = fresh card
|
||||
@@ -84,7 +84,7 @@ describe('fetchGroupMessageRange / SsoGetGroupMsg', () => {
|
||||
|
||||
it('[#1] keeps the cache card when field 4 equals the base nickname (no active group card)', async () => {
|
||||
const m = { uin: 222, uid: '', nickname: 'BaseNick', card: 'CachedCard', role: 'member', level: 0, title: '', joinTime: 0, lastSentTime: 0, shutUpTime: 0 };
|
||||
const id = { findGroupMember: () => m } as unknown as IdentityService;
|
||||
const id = { findGroupMember: () => m, findGroup: () => null } as unknown as IdentityService;
|
||||
const resp = protobuf_encode<SsoGetGroupMsgResponse>({
|
||||
body: { groupUin: 9999, messages: [
|
||||
{ responseHead: { fromUin: 222, grp: { groupUin: 9999, memberCard: 'BaseNick' } }, // field4 == base nick → not a card
|
||||
|
||||
Reference in New Issue
Block a user