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
- 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:*
118 lines
3.2 KiB
TypeScript
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;
|
|
}
|