Files
clawdbot/extensions/qq/src/send.ts
empty 79b946584e
Some checks are pending
CI / install-check (push) Waiting to run
CI / checks (bunx tsc -p tsconfig.json, bun, build) (push) Waiting to run
CI / checks (bunx vitest run, bun, test) (push) Waiting to run
CI / checks (pnpm build, node, build) (push) Waiting to run
CI / checks (pnpm format, node, format) (push) Waiting to run
CI / checks (pnpm lint, node, lint) (push) Waiting to run
CI / checks (pnpm protocol:check, node, protocol) (push) Waiting to run
CI / checks (pnpm test, node, test) (push) Waiting to run
CI / secrets (push) Waiting to run
CI / checks-windows (pnpm build, node, build) (push) Waiting to run
CI / checks-windows (pnpm lint, node, lint) (push) Waiting to run
CI / checks-windows (pnpm protocol:check, node, protocol) (push) Waiting to run
CI / checks-windows (pnpm test, node, test) (push) Waiting to run
CI / checks-macos (pnpm test, test) (push) Waiting to run
CI / macos-app (set -euo pipefail for attempt in 1 2 3; do if swift build --package-path apps/macos --configuration release; then exit 0 fi echo "swift build failed (attempt $attempt/3). Retrying…" sleep $((attempt * 20)) done exit 1 , build) (push) Waiting to run
CI / macos-app (set -euo pipefail for attempt in 1 2 3; do if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then exit 0 fi echo "swift test failed (attempt $attempt/3). Retrying…" sleep $((attempt *… (push) Waiting to run
CI / macos-app (swiftlint --config .swiftlint.yml swiftformat --lint apps/macos/Sources --config .swiftformat , lint) (push) Waiting to run
CI / ios (push) Waiting to run
CI / android (./gradlew --no-daemon :app:assembleDebug, build) (push) Waiting to run
CI / android (./gradlew --no-daemon :app:testDebugUnitTest, test) (push) Waiting to run
Docker Release / build-amd64 (push) Waiting to run
Docker Release / build-arm64 (push) Waiting to run
Docker Release / create-manifest (push) Blocked by required conditions
Install Smoke / install-smoke (push) Waiting to run
Workflow Sanity / no-tabs (push) Waiting to run
feat(qq): add QQ Bot channel plugin (Official Bot API)
- Implement QQ Bot API client with token caching
- Add WebSocket monitor for event handling
- Support C2C (single chat) and group messages
- Include pairing mechanism for DM authorization

Also fix memory-core peerDependencies to use workspace:*
2026-01-28 00:05:33 +08:00

118 lines
3.2 KiB
TypeScript

/**
* QQ Bot Message Sending
*/
import type { MoltbotConfig } from "clawdbot/plugin-sdk";
import {
getAccessToken,
sendC2CMessage,
sendGroupMessage,
sendChannelMessage,
sendDmsMessage,
type SendMessageResult,
} from "./api.js";
import { resolveQQAccount } from "./accounts.js";
const QQ_TEXT_LIMIT = 2000;
export interface SendQQMessageOptions {
accountId?: string;
cfg?: MoltbotConfig;
appId?: string;
appSecret?: string;
msgId?: string;
msgSeq?: number;
mediaUrl?: string;
}
export type ChatType = "c2c" | "group" | "channel" | "dms";
/**
* Send a message via QQ Bot API
*/
export async function sendMessageQQ(
chatType: ChatType,
targetId: string,
text: string | undefined,
options: SendQQMessageOptions,
): Promise<SendMessageResult> {
const { accountId, cfg, msgId, msgSeq } = options;
let appId = options.appId;
let appSecret = options.appSecret;
// Resolve from config if not provided directly
if ((!appId || !appSecret) && cfg) {
const account = resolveQQAccount({ cfg, accountId });
appId = account.appId;
appSecret = account.appSecret;
}
if (!appId || !appSecret) {
return { ok: false, error: "QQ appId or appSecret not configured" };
}
try {
const token = await getAccessToken(appId, appSecret);
const request = {
content: text,
msg_type: 0, // text message
msg_id: msgId,
msg_seq: msgSeq,
};
switch (chatType) {
case "c2c":
return sendC2CMessage(token, targetId, request);
case "group":
return sendGroupMessage(token, targetId, request);
case "channel":
return sendChannelMessage(token, targetId, request);
case "dms":
return sendDmsMessage(token, targetId, request);
default:
return { ok: false, error: `Unknown chat type: ${chatType}` };
}
} catch (err) {
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
};
}
}
/**
* Chunk text for QQ message limit
*/
export function chunkQQText(text: string, limit = QQ_TEXT_LIMIT): string[] {
if (!text) return [];
if (text.length <= limit) return [text];
const chunks: string[] = [];
let remaining = text;
while (remaining.length > limit) {
const window = remaining.slice(0, limit);
const lastNewline = window.lastIndexOf("\n");
const lastSpace = window.lastIndexOf(" ");
let breakIdx = lastNewline > 0 ? lastNewline : lastSpace;
if (breakIdx <= 0) breakIdx = limit;
const rawChunk = remaining.slice(0, breakIdx);
const chunk = rawChunk.trimEnd();
if (chunk.length > 0) chunks.push(chunk);
const brokeOnSeparator =
breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
const nextStart = Math.min(
remaining.length,
breakIdx + (brokeOnSeparator ? 1 : 0),
);
remaining = remaining.slice(nextStart).trimStart();
}
if (remaining.length) chunks.push(remaining);
return chunks;
}