Merge pull request #1 from sliverp/feature/group

11
This commit is contained in:
Bijin
2026-01-28 18:21:31 +08:00
committed by GitHub
3 changed files with 59 additions and 11 deletions

View File

@@ -114,3 +114,19 @@ export async function sendChannelMessage(
...(msgId ? { msg_id: msgId } : {}), ...(msgId ? { msg_id: msgId } : {}),
}); });
} }
/**
* 发送群聊消息
*/
export async function sendGroupMessage(
accessToken: string,
groupOpenid: string,
content: string,
msgId?: string
): Promise<{ id: string; timestamp: string }> {
return apiRequest(accessToken, "POST", `/v2/groups/${groupOpenid}/messages`, {
content,
msg_type: 0,
...(msgId ? { msg_id: msgId } : {}),
});
}

View File

@@ -1,12 +1,13 @@
import WebSocket from "ws"; import WebSocket from "ws";
import type { ResolvedQQBotAccount, WSPayload, C2CMessageEvent, GuildMessageEvent } from "./types.js"; import type { ResolvedQQBotAccount, WSPayload, C2CMessageEvent, GuildMessageEvent, GroupMessageEvent } from "./types.js";
import { getAccessToken, getGatewayUrl, sendC2CMessage, sendChannelMessage } from "./api.js"; import { getAccessToken, getGatewayUrl, sendC2CMessage, sendChannelMessage, sendGroupMessage } from "./api.js";
import { getQQBotRuntime } from "./runtime.js"; import { getQQBotRuntime } from "./runtime.js";
// QQ Bot intents // QQ Bot intents
const INTENTS = { const INTENTS = {
PUBLIC_GUILD_MESSAGES: 1 << 30, PUBLIC_GUILD_MESSAGES: 1 << 30, // 频道公开消息
DIRECT_MESSAGE: 1 << 25, DIRECT_MESSAGE: 1 << 12, // 频道私信
GROUP_AND_C2C: 1 << 25, // 群聊和 C2C 私聊
}; };
export interface GatewayContext { export interface GatewayContext {
@@ -56,7 +57,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
// 处理收到的消息 // 处理收到的消息
const handleMessage = async (event: { const handleMessage = async (event: {
type: "c2c" | "guild" | "dm"; type: "c2c" | "guild" | "dm" | "group";
senderId: string; senderId: string;
senderName?: string; senderName?: string;
content: string; content: string;
@@ -64,6 +65,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
timestamp: string; timestamp: string;
channelId?: string; channelId?: string;
guildId?: string; guildId?: string;
groupOpenid?: string;
}) => { }) => {
log?.info(`[qqbot:${account.accountId}] Processing message from ${event.senderId}: ${event.content}`); log?.info(`[qqbot:${account.accountId}] Processing message from ${event.senderId}: ${event.content}`);
@@ -73,8 +75,10 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
direction: "inbound", direction: "inbound",
}); });
const isGroup = event.type === "guild"; const isGroup = event.type === "guild" || event.type === "group";
const peerId = isGroup ? `channel:${event.channelId}` : event.senderId; const peerId = event.type === "guild" ? `channel:${event.channelId}`
: event.type === "group" ? `group:${event.groupOpenid}`
: event.senderId;
const route = pluginRuntime.channel.routing.resolveAgentRoute({ const route = pluginRuntime.channel.routing.resolveAgentRoute({
cfg, cfg,
@@ -101,9 +105,9 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
envelope: envelopeOptions, envelope: envelopeOptions,
}); });
const fromAddress = isGroup const fromAddress = event.type === "guild" ? `qqbot:channel:${event.channelId}`
? `qqbot:channel:${event.channelId}` : event.type === "group" ? `qqbot:group:${event.groupOpenid}`
: `qqbot:${event.senderId}`; : `qqbot:${event.senderId}`;
const toAddress = fromAddress; const toAddress = fromAddress;
const ctxPayload = pluginRuntime.channel.reply.finalizeInboundContext({ const ctxPayload = pluginRuntime.channel.reply.finalizeInboundContext({
@@ -126,6 +130,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
// QQBot 特有字段 // QQBot 特有字段
QQChannelId: event.channelId, QQChannelId: event.channelId,
QQGuildId: event.guildId, QQGuildId: event.guildId,
QQGroupOpenid: event.groupOpenid,
}); });
// 分发到 AI 系统 // 分发到 AI 系统
@@ -144,6 +149,8 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
try { try {
if (event.type === "c2c") { if (event.type === "c2c") {
await sendC2CMessage(accessToken, event.senderId, replyText, event.messageId); await sendC2CMessage(accessToken, event.senderId, replyText, event.messageId);
} else if (event.type === "group" && event.groupOpenid) {
await sendGroupMessage(accessToken, event.groupOpenid, replyText, event.messageId);
} else if (event.channelId) { } else if (event.channelId) {
await sendChannelMessage(accessToken, event.channelId, replyText, event.messageId); await sendChannelMessage(accessToken, event.channelId, replyText, event.messageId);
} }
@@ -191,7 +198,7 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
op: 2, op: 2,
d: { d: {
token: `QQBot ${accessToken}`, token: `QQBot ${accessToken}`,
intents: INTENTS.PUBLIC_GUILD_MESSAGES | INTENTS.DIRECT_MESSAGE, intents: INTENTS.PUBLIC_GUILD_MESSAGES | INTENTS.DIRECT_MESSAGE | INTENTS.GROUP_AND_C2C,
shard: [0, 1], shard: [0, 1],
}, },
}) })
@@ -239,6 +246,16 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
timestamp: event.timestamp, timestamp: event.timestamp,
guildId: event.guild_id, guildId: event.guild_id,
}); });
} else if (t === "GROUP_AT_MESSAGE_CREATE") {
const event = d as GroupMessageEvent;
await handleMessage({
type: "group",
senderId: event.author.member_openid,
content: event.content,
messageId: event.id,
timestamp: event.timestamp,
groupOpenid: event.group_openid,
});
} }
break; break;

View File

@@ -70,6 +70,21 @@ export interface GuildMessageEvent {
}; };
} }
/**
* 群聊 AT 消息事件
*/
export interface GroupMessageEvent {
author: {
id: string;
member_openid: string;
};
content: string;
id: string;
timestamp: string;
group_id: string;
group_openid: string;
}
/** /**
* WebSocket 事件负载 * WebSocket 事件负载
*/ */