feat(qq): add QQ Bot channel plugin (Official Bot API)
Some checks failed
CI / install-check (push) Has been cancelled
CI / checks (bunx tsc -p tsconfig.json, bun, build) (push) Has been cancelled
CI / checks (bunx vitest run, bun, test) (push) Has been cancelled
CI / checks (pnpm build, node, build) (push) Has been cancelled
CI / checks (pnpm format, node, format) (push) Has been cancelled
CI / checks (pnpm lint, node, lint) (push) Has been cancelled
CI / checks (pnpm protocol:check, node, protocol) (push) Has been cancelled
CI / checks (pnpm test, node, test) (push) Has been cancelled
CI / secrets (push) Has been cancelled
CI / checks-windows (pnpm build, node, build) (push) Has been cancelled
CI / checks-windows (pnpm lint, node, lint) (push) Has been cancelled
CI / checks-windows (pnpm protocol:check, node, protocol) (push) Has been cancelled
CI / checks-windows (pnpm test, node, test) (push) Has been cancelled
CI / checks-macos (pnpm test, test) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / macos-app (swiftlint --config .swiftlint.yml
swiftformat --lint apps/macos/Sources --config .swiftformat
, lint) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / android (./gradlew --no-daemon :app:assembleDebug, build) (push) Has been cancelled
CI / android (./gradlew --no-daemon :app:testDebugUnitTest, test) (push) Has been cancelled
Docker Release / build-amd64 (push) Has been cancelled
Docker Release / build-arm64 (push) Has been cancelled
Install Smoke / install-smoke (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Docker Release / create-manifest (push) Has been cancelled
Some checks failed
CI / install-check (push) Has been cancelled
CI / checks (bunx tsc -p tsconfig.json, bun, build) (push) Has been cancelled
CI / checks (bunx vitest run, bun, test) (push) Has been cancelled
CI / checks (pnpm build, node, build) (push) Has been cancelled
CI / checks (pnpm format, node, format) (push) Has been cancelled
CI / checks (pnpm lint, node, lint) (push) Has been cancelled
CI / checks (pnpm protocol:check, node, protocol) (push) Has been cancelled
CI / checks (pnpm test, node, test) (push) Has been cancelled
CI / secrets (push) Has been cancelled
CI / checks-windows (pnpm build, node, build) (push) Has been cancelled
CI / checks-windows (pnpm lint, node, lint) (push) Has been cancelled
CI / checks-windows (pnpm protocol:check, node, protocol) (push) Has been cancelled
CI / checks-windows (pnpm test, node, test) (push) Has been cancelled
CI / checks-macos (pnpm test, test) (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
CI / macos-app (swiftlint --config .swiftlint.yml
swiftformat --lint apps/macos/Sources --config .swiftformat
, lint) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / android (./gradlew --no-daemon :app:assembleDebug, build) (push) Has been cancelled
CI / android (./gradlew --no-daemon :app:testDebugUnitTest, test) (push) Has been cancelled
Docker Release / build-amd64 (push) Has been cancelled
Docker Release / build-arm64 (push) Has been cancelled
Install Smoke / install-smoke (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Docker Release / create-manifest (push) Has been cancelled
- 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:*
This commit is contained in:
117
extensions/qq/src/send.ts
Normal file
117
extensions/qq/src/send.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user