visionclaw 0.1.217 → 0.1.218-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/engines/pi/tools.d.ts.map +1 -1
- package/dist/agent/engines/pi/tools.js +2 -1
- package/dist/agent/engines/pi/tools.js.map +1 -1
- package/dist/agent/fast-response-eligibility.d.ts.map +1 -1
- package/dist/agent/fast-response-eligibility.js +3 -0
- package/dist/agent/fast-response-eligibility.js.map +1 -1
- package/dist/agent/fund-league-hold.d.ts +1 -1
- package/dist/agent/fund-league-hold.d.ts.map +1 -1
- package/dist/agent/fund-league-hold.js +11 -1
- package/dist/agent/fund-league-hold.js.map +1 -1
- package/dist/agent/heartbeat-manager.d.ts +4 -0
- package/dist/agent/heartbeat-manager.d.ts.map +1 -1
- package/dist/agent/heartbeat-manager.js +56 -0
- package/dist/agent/heartbeat-manager.js.map +1 -1
- package/dist/agent/interrupt-handler.d.ts +0 -2
- package/dist/agent/interrupt-handler.d.ts.map +1 -1
- package/dist/agent/interrupt-handler.js +0 -4
- package/dist/agent/interrupt-handler.js.map +1 -1
- package/dist/agent/lifecycle-trace.d.ts +9 -0
- package/dist/agent/lifecycle-trace.d.ts.map +1 -1
- package/dist/agent/lifecycle-trace.js +16 -1
- package/dist/agent/lifecycle-trace.js.map +1 -1
- package/dist/agent/lifecycle-transitions.d.ts +21 -0
- package/dist/agent/lifecycle-transitions.d.ts.map +1 -0
- package/dist/agent/lifecycle-transitions.js +33 -0
- package/dist/agent/lifecycle-transitions.js.map +1 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +4 -0
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/onboarding-gate.d.ts.map +1 -1
- package/dist/agent/onboarding-gate.js +5 -0
- package/dist/agent/onboarding-gate.js.map +1 -1
- package/dist/agent/reply-target.d.ts.map +1 -1
- package/dist/agent/reply-target.js +2 -0
- package/dist/agent/reply-target.js.map +1 -1
- package/dist/agent/status.d.ts.map +1 -1
- package/dist/agent/status.js +2 -0
- package/dist/agent/status.js.map +1 -1
- package/dist/agent/wake-cycle-runner.d.ts.map +1 -1
- package/dist/agent/wake-cycle-runner.js +10 -16
- package/dist/agent/wake-cycle-runner.js.map +1 -1
- package/dist/channels/qq-access.d.ts +294 -0
- package/dist/channels/qq-access.d.ts.map +1 -0
- package/dist/channels/qq-access.js +426 -0
- package/dist/channels/qq-access.js.map +1 -0
- package/dist/channels/qq-bind-link.d.ts +65 -0
- package/dist/channels/qq-bind-link.d.ts.map +1 -0
- package/dist/channels/qq-bind-link.js +108 -0
- package/dist/channels/qq-bind-link.js.map +1 -0
- package/dist/channels/qq-commands.d.ts.map +1 -1
- package/dist/channels/qq-commands.js +3 -0
- package/dist/channels/qq-commands.js.map +1 -1
- package/dist/channels/qq-group-names.d.ts +27 -0
- package/dist/channels/qq-group-names.d.ts.map +1 -0
- package/dist/channels/qq-group-names.js +64 -0
- package/dist/channels/qq-group-names.js.map +1 -0
- package/dist/channels/qq-group-welcome.d.ts +75 -0
- package/dist/channels/qq-group-welcome.d.ts.map +1 -0
- package/dist/channels/qq-group-welcome.js +165 -0
- package/dist/channels/qq-group-welcome.js.map +1 -0
- package/dist/channels/qq-owner-commands.d.ts +178 -0
- package/dist/channels/qq-owner-commands.d.ts.map +1 -0
- package/dist/channels/qq-owner-commands.js +425 -0
- package/dist/channels/qq-owner-commands.js.map +1 -0
- package/dist/channels/qq-owner-name.d.ts +41 -0
- package/dist/channels/qq-owner-name.d.ts.map +1 -0
- package/dist/channels/qq-owner-name.js +71 -0
- package/dist/channels/qq-owner-name.js.map +1 -0
- package/dist/channels/qq-status.d.ts +2 -0
- package/dist/channels/qq-status.d.ts.map +1 -1
- package/dist/channels/qq-status.js +4 -0
- package/dist/channels/qq-status.js.map +1 -1
- package/dist/channels/qq.d.ts +126 -3
- package/dist/channels/qq.d.ts.map +1 -1
- package/dist/channels/qq.js +688 -25
- package/dist/channels/qq.js.map +1 -1
- package/dist/config/qq-access.d.ts +102 -0
- package/dist/config/qq-access.d.ts.map +1 -0
- package/dist/config/qq-access.js +99 -0
- package/dist/config/qq-access.js.map +1 -0
- package/dist/config/types.d.ts +138 -3
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +11 -0
- package/dist/config/types.js.map +1 -1
- package/dist/i18n/messages.d.ts +136 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/messages.js +108 -0
- package/dist/i18n/messages.js.map +1 -1
- package/dist/onboarding/onboarding-service-client.d.ts +58 -0
- package/dist/onboarding/onboarding-service-client.d.ts.map +1 -1
- package/dist/onboarding/onboarding-service-client.js +27 -0
- package/dist/onboarding/onboarding-service-client.js.map +1 -1
- package/dist/onboarding/remote-set-owner.d.ts.map +1 -1
- package/dist/onboarding/remote-set-owner.js +26 -0
- package/dist/onboarding/remote-set-owner.js.map +1 -1
- package/dist/onboarding/send-invite.d.ts +6 -2
- package/dist/onboarding/send-invite.d.ts.map +1 -1
- package/dist/onboarding/send-invite.js +43 -3
- package/dist/onboarding/send-invite.js.map +1 -1
- package/dist/threads/task-sidecar.d.ts +1 -1
- package/dist/threads/task-sidecar.d.ts.map +1 -1
- package/dist/threads/task-sidecar.js +5 -4
- package/dist/threads/task-sidecar.js.map +1 -1
- package/dist/tools/email.d.ts +1 -1
- package/dist/tools/event-trigger.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp-manager.d.ts +1 -1
- package/dist/tools/qq-access.d.ts +6 -0
- package/dist/tools/qq-access.d.ts.map +1 -0
- package/dist/tools/qq-access.js +36 -0
- package/dist/tools/qq-access.js.map +1 -0
- package/dist/tools/skill.d.ts +1 -1
- package/dist/tools/totp.d.ts +1 -1
- package/dist-agent/bundle.cjs +2 -2
- package/package.json +1 -1
- package/dist/builtin-skills/catalog/phone-adb-automation/SKILL.md +0 -412
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_input.sh +0 -132
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_launch.sh +0 -166
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_screenshot.sh +0 -87
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_security_kbd.py +0 -174
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_setup.sh +0 -274
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_swipe.sh +0 -111
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_tap.sh +0 -87
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_ui_parse.py +0 -176
- package/dist/builtin-skills/catalog/phone-adb-automation/phone_wake_unlock.sh +0 -67
- package/dist/builtin-skills/macos-automation/SKILL.md +0 -132
- package/dist/builtin-skills/transcribe-audio/SKILL.md +0 -122
- package/dist/builtin-skills/visionclaw-manual/SKILL.md +0 -337
- package/dist/obs/rdp/icons/icons/app_windows.svg +0 -4
- package/dist/obs/rdp/icons/icons/clip_get.svg +0 -4
- package/dist/obs/rdp/icons/icons/clip_send.svg +0 -4
- package/dist/obs/rdp/icons/icons/clip_shared.svg +0 -4
- package/dist/obs/rdp/icons/icons/clipboard.svg +0 -4
- package/dist/obs/rdp/icons/icons/clipboard_shared.svg +0 -4
- package/dist/obs/rdp/icons/icons/control.svg +0 -4
- package/dist/obs/rdp/icons/icons/desktop.svg +0 -4
- package/dist/obs/rdp/icons/icons/display.svg +0 -4
- package/dist/obs/rdp/icons/icons/launchpad.svg +0 -4
- package/dist/obs/rdp/icons/icons/mission_control.svg +0 -4
- package/dist/obs/rdp/icons/icons/screenshot.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_actual.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_fit.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_in.svg +0 -4
- package/dist/obs/rdp/icons/icons/zoom_out.svg +0 -4
- package/dist/realtime/assets/index.html +0 -1058
- package/dist/realtime/assets/samples/alloy.mp3 +0 -0
- package/dist/realtime/assets/samples/ash.mp3 +0 -0
- package/dist/realtime/assets/samples/ballad.mp3 +0 -0
- package/dist/realtime/assets/samples/cedar.mp3 +0 -0
- package/dist/realtime/assets/samples/coral.mp3 +0 -0
- package/dist/realtime/assets/samples/echo.mp3 +0 -0
- package/dist/realtime/assets/samples/marin.mp3 +0 -0
- package/dist/realtime/assets/samples/sage.mp3 +0 -0
- package/dist/realtime/assets/samples/shimmer.mp3 +0 -0
- package/dist/realtime/assets/samples/verse.mp3 +0 -0
- package/dist-agent/assets/index.html +0 -1058
- package/dist-agent/assets/samples/alloy.mp3 +0 -0
- package/dist-agent/assets/samples/ash.mp3 +0 -0
- package/dist-agent/assets/samples/ballad.mp3 +0 -0
- package/dist-agent/assets/samples/cedar.mp3 +0 -0
- package/dist-agent/assets/samples/coral.mp3 +0 -0
- package/dist-agent/assets/samples/echo.mp3 +0 -0
- package/dist-agent/assets/samples/marin.mp3 +0 -0
- package/dist-agent/assets/samples/sage.mp3 +0 -0
- package/dist-agent/assets/samples/shimmer.mp3 +0 -0
- package/dist-agent/assets/samples/verse.mp3 +0 -0
- package/dist-agent/playwriter-relay.cjs +0 -95
- package/dist-agent/realtime/assets/index.html +0 -1058
- package/dist-agent/realtime/assets/samples/alloy.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/ash.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/ballad.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/cedar.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/coral.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/echo.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/marin.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/sage.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/shimmer.mp3 +0 -0
- package/dist-agent/realtime/assets/samples/verse.mp3 +0 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { normalizeQqOpenId, } from "../config/qq-access.js";
|
|
3
|
+
export function isQqGroupMessageEvent(eventType) {
|
|
4
|
+
return eventType === "GROUP_AT_MESSAGE_CREATE" || eventType === "GROUP_MESSAGE_CREATE";
|
|
5
|
+
}
|
|
6
|
+
/** A list written before sender policies existed: empty meant every member. */
|
|
7
|
+
const policyOfList = (list) => (list && list.length > 0 ? "list" : "everyone");
|
|
8
|
+
/**
|
|
9
|
+
* The settings that apply to one group: its own override where it has one,
|
|
10
|
+
* otherwise the global default, separately for the mode and the sender list.
|
|
11
|
+
* Null when the group is not allowed at all: not in `allow` and `allowAllGroups`
|
|
12
|
+
* is off. An override for a group that is not allowed grants nothing.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveGroupPolicy(access, groupOpenId) {
|
|
15
|
+
const id = normalizeQqOpenId(groupOpenId);
|
|
16
|
+
if (!access)
|
|
17
|
+
return null;
|
|
18
|
+
// Typed as always present (schema default), but a config written by an older
|
|
19
|
+
// release or built by hand may lack it.
|
|
20
|
+
const { allowAllGroups } = access;
|
|
21
|
+
if (allowAllGroups !== true && !access.allow.includes(id))
|
|
22
|
+
return null;
|
|
23
|
+
const { groupSettings: settings } = access;
|
|
24
|
+
const override = settings?.find((entry) => entry.group === id);
|
|
25
|
+
const globalPolicy = access.senderPolicy ?? policyOfList(access.memberAllow);
|
|
26
|
+
const senderPolicy = override?.senderPolicy ?? (override?.memberAllow ? policyOfList(override.memberAllow) : globalPolicy);
|
|
27
|
+
return {
|
|
28
|
+
mode: override?.mode ?? access.mode,
|
|
29
|
+
senderPolicy,
|
|
30
|
+
memberAllow: override?.memberAllow ?? access.memberAllow,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The owner's member_openid in a group: from the server's confirmed list, or a binding this process just verified. */
|
|
34
|
+
export function ownerMemberIn(access, groupOpenId, local = [], options = {}) {
|
|
35
|
+
const group = normalizeQqOpenId(groupOpenId);
|
|
36
|
+
const confirmed = access?.ownerMembers ?? [];
|
|
37
|
+
return [
|
|
38
|
+
// A name match is a guess (a nickname can be copied): where it matters (the "only the owner" gate) only proof counts.
|
|
39
|
+
...confirmed.filter((entry) => entry.group === group && (!options.provenOnly || entry.via !== "name")).map((entry) => entry.member),
|
|
40
|
+
...local.filter((entry) => entry.groupOpenid === group).map((entry) => entry.memberOpenid),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
/** True when this member is the group's owner only because their nickname matched the owner's name (not proven). */
|
|
44
|
+
export function isNameRecognisedOwner(access, groupOpenId, memberOpenId) {
|
|
45
|
+
const group = normalizeQqOpenId(groupOpenId);
|
|
46
|
+
const member = normalizeQqOpenId(memberOpenId);
|
|
47
|
+
const entries = access?.ownerMembers ?? [];
|
|
48
|
+
const mine = entries.filter((entry) => entry.group === group && entry.member === member);
|
|
49
|
+
return mine.length > 0 && mine.every((entry) => entry.via === "name");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Decide whether a group message may reach the agent, using the group's own
|
|
53
|
+
* settings when it has an override and the global ones otherwise.
|
|
54
|
+
*
|
|
55
|
+
* A group author carries only `member_openid`, which differs per group and is
|
|
56
|
+
* a different identifier from the `user_openid` bound as `homeChannelOpenId`,
|
|
57
|
+
* so nothing here treats a group sender as the owner by default. The "owner"
|
|
58
|
+
* sender policy accepts only the member_openid the owner proved with a binding
|
|
59
|
+
* code in that very group.
|
|
60
|
+
*/
|
|
61
|
+
export function decideGroupMessage(input) {
|
|
62
|
+
const { eventType, access, groupOpenId, memberOpenId } = input;
|
|
63
|
+
if (!memberOpenId)
|
|
64
|
+
return { accept: false, reason: "missing_member" };
|
|
65
|
+
const policy = resolveGroupPolicy(access, groupOpenId);
|
|
66
|
+
if (!policy)
|
|
67
|
+
return { accept: false, reason: "group_not_allowed" };
|
|
68
|
+
if (eventType === "GROUP_MESSAGE_CREATE" && policy.mode !== "all") {
|
|
69
|
+
return { accept: false, reason: "mode_at_only" };
|
|
70
|
+
}
|
|
71
|
+
const member = normalizeQqOpenId(memberOpenId);
|
|
72
|
+
if (policy.senderPolicy === "owner") {
|
|
73
|
+
// Nobody counts as the owner until they proved it with a binding code in this group.
|
|
74
|
+
const owners = ownerMemberIn(access, groupOpenId, input.localOwners, { provenOnly: true });
|
|
75
|
+
if (owners.length === 0)
|
|
76
|
+
return { accept: false, reason: "owner_not_bound" };
|
|
77
|
+
if (!owners.includes(member))
|
|
78
|
+
return { accept: false, reason: "member_not_allowed" };
|
|
79
|
+
}
|
|
80
|
+
else if (policy.senderPolicy === "list" && !policy.memberAllow.includes(member)) {
|
|
81
|
+
return { accept: false, reason: "member_not_allowed" };
|
|
82
|
+
}
|
|
83
|
+
return { accept: true };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The single mode the agent reports on and the page explains: "all" when any
|
|
87
|
+
* allowed group answers every message (with allowAllGroups: the global mode is
|
|
88
|
+
* all, or any override is), otherwise "at". With no allowed group
|
|
89
|
+
* there is nothing to override, so it is the global mode. GRU computes the
|
|
90
|
+
* same value from the saved policy; keep the two in step.
|
|
91
|
+
*/
|
|
92
|
+
export function overallDesiredMode(access) {
|
|
93
|
+
const { allowAllGroups } = access;
|
|
94
|
+
// With every group allowed, any group nobody overrode follows the global mode.
|
|
95
|
+
if (allowAllGroups === true) {
|
|
96
|
+
const overrides = access.groupSettings ?? [];
|
|
97
|
+
return access.mode === "all" || overrides.some((entry) => entry.mode === "all") ? "all" : "at";
|
|
98
|
+
}
|
|
99
|
+
if (access.allow.length === 0)
|
|
100
|
+
return access.mode;
|
|
101
|
+
return access.allow.some((group) => resolveGroupPolicy(access, group)?.mode === "all") ? "all" : "at";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The bot always subscribes to every group message: GROUP_MESSAGE_CREATE rides on the same intent bit
|
|
105
|
+
* as @ messages (GROUP_AND_C2C_EVENT, 1 << 25), so there is nothing to opt into and an IDENTIFY is
|
|
106
|
+
* never rejected for it. Whether the platform really delivers them depends on a permission of the
|
|
107
|
+
* bot ("receive all group messages"); without it the gateway simply never sends the event and only
|
|
108
|
+
* @ messages arrive. Reading and answering are separate: the settings only decide whether a message
|
|
109
|
+
* makes the assistant answer.
|
|
110
|
+
*
|
|
111
|
+
* `degradedReason` is present while no GROUP_MESSAGE_CREATE has been seen on this connection (the
|
|
112
|
+
* platform has not enabled full group messages, or nobody has posted without @ yet). Then the
|
|
113
|
+
* effective response mode is "at" even if "every message" is chosen, honestly: it cannot answer what
|
|
114
|
+
* it never receives.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveModeStatus(desiredMode, groupMessageEventObserved) {
|
|
117
|
+
if (groupMessageEventObserved)
|
|
118
|
+
return { desiredMode, effectiveMode: desiredMode };
|
|
119
|
+
return { desiredMode, effectiveMode: "at", degradedReason: "GROUP_MESSAGE_EVENT_NOT_OBSERVED" };
|
|
120
|
+
}
|
|
121
|
+
/** Longest message excerpt an agent reports; the server enforces the same limit. */
|
|
122
|
+
export const QQ_MESSAGE_PREVIEW_MAX_LENGTH = 30;
|
|
123
|
+
/**
|
|
124
|
+
* One-line excerpt of a group message for the owner to recognise a group or
|
|
125
|
+
* sender on the GRU bot page: whitespace and control characters collapse to
|
|
126
|
+
* single spaces, at most 30 characters. Returns undefined when nothing is left.
|
|
127
|
+
* The excerpt goes only into the heartbeat report; it is never logged.
|
|
128
|
+
*/
|
|
129
|
+
export function summarizeQqMessage(text) {
|
|
130
|
+
if (!text)
|
|
131
|
+
return undefined;
|
|
132
|
+
// eslint-disable-next-line no-control-regex
|
|
133
|
+
const flat = text.slice(0, 400).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\s]+/g, " ").trim();
|
|
134
|
+
if (!flat)
|
|
135
|
+
return undefined;
|
|
136
|
+
return Array.from(flat).slice(0, QQ_MESSAGE_PREVIEW_MAX_LENGTH).join("");
|
|
137
|
+
}
|
|
138
|
+
/** A group or speaker name for display: control characters out, one line, at most 40 characters. */
|
|
139
|
+
export function cleanQqName(value) {
|
|
140
|
+
if (typeof value !== "string")
|
|
141
|
+
return undefined;
|
|
142
|
+
// eslint-disable-next-line no-control-regex
|
|
143
|
+
const flat = value.slice(0, 200).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\s]+/g, " ").trim();
|
|
144
|
+
return flat ? Array.from(flat).slice(0, 40).join("") : undefined;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Risk of recognising the owner by name, kept here so the code that does it points to one place:
|
|
148
|
+
* a QQ nickname can be copied, so a member who picks the owner's nickname before the owner speaks would be
|
|
149
|
+
* recognised as the owner. Mitigations: (1) only when the group has no owner binding at all, (2) a binding
|
|
150
|
+
* proven by a code or a Gru confirmation is never replaced, (3) a name binding is never treated as proof by the
|
|
151
|
+
* "only me" gate, (4) it is dropped as soon as a second member with the same name shows up, (5) the Gru page
|
|
152
|
+
* shows it as "recognised by name" and the owner can unbind it or confirm properly with the Bind button.
|
|
153
|
+
*/
|
|
154
|
+
export const QQ_NAME_MATCH_RISK = "owner recognised by nickname is unverified";
|
|
155
|
+
export const QQ_IDENTITY_PROBE_MAX = 20;
|
|
156
|
+
const PROBE_VALUE_MAX = 128;
|
|
157
|
+
const PROBE_USERNAME_MAX = 64;
|
|
158
|
+
const PROBE_KEYS_MAX = 20;
|
|
159
|
+
const probeText = (value, max) => {
|
|
160
|
+
const text = typeof value === "string" ? value : typeof value === "number" && Number.isFinite(value) ? String(value) : "";
|
|
161
|
+
// eslint-disable-next-line no-control-regex
|
|
162
|
+
const flat = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").trim();
|
|
163
|
+
return flat ? Array.from(flat).slice(0, max).join("") : undefined;
|
|
164
|
+
};
|
|
165
|
+
/** Shorten a group_openid for display (first 6…last 4). */
|
|
166
|
+
export const shortQqId = (id) => (id.length <= 12 ? id : `${id.slice(0, 6)}…${id.slice(-4)}`);
|
|
167
|
+
/** Build a sample from an event's author. Returns null when there is no author object at all. */
|
|
168
|
+
export function buildQqIdentityProbe(input) {
|
|
169
|
+
const author = input.author;
|
|
170
|
+
if (!author || typeof author !== "object" || Array.isArray(author))
|
|
171
|
+
return null;
|
|
172
|
+
const fields = author;
|
|
173
|
+
const keys = Object.keys(fields).filter((key) => /^[A-Za-z0-9_]{1,32}$/.test(key)).slice(0, PROBE_KEYS_MAX);
|
|
174
|
+
const group = input.group ? shortQqId(normalizeQqOpenId(input.group)) : undefined;
|
|
175
|
+
const put = (key, value) => (value === undefined ? {} : { [key]: value });
|
|
176
|
+
return {
|
|
177
|
+
id: input.id ?? randomBytes(6).toString("hex"),
|
|
178
|
+
at: input.at ?? new Date().toISOString(),
|
|
179
|
+
event: input.event,
|
|
180
|
+
...(group ? { group } : {}),
|
|
181
|
+
keys,
|
|
182
|
+
...put("userOpenid", probeText(fields.user_openid, PROBE_VALUE_MAX)),
|
|
183
|
+
...put("memberOpenid", probeText(fields.member_openid, PROBE_VALUE_MAX)),
|
|
184
|
+
...put("unionOpenid", probeText(fields.union_openid, PROBE_VALUE_MAX)),
|
|
185
|
+
...put("unionUserAccount", probeText(fields.union_user_account, PROBE_VALUE_MAX)),
|
|
186
|
+
...put("username", probeText(fields.username, PROBE_USERNAME_MAX)),
|
|
187
|
+
...put("memberRole", probeText(fields.member_role, 32)),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** The latest samples not yet delivered; bounded, re-queued when a heartbeat fails. */
|
|
191
|
+
export class QqIdentityProbeTracker {
|
|
192
|
+
pending = [];
|
|
193
|
+
note(probe) {
|
|
194
|
+
if (!probe)
|
|
195
|
+
return;
|
|
196
|
+
this.pending.push(probe);
|
|
197
|
+
if (this.pending.length > QQ_IDENTITY_PROBE_MAX)
|
|
198
|
+
this.pending.splice(0, this.pending.length - QQ_IDENTITY_PROBE_MAX);
|
|
199
|
+
}
|
|
200
|
+
drain() {
|
|
201
|
+
const out = this.pending;
|
|
202
|
+
this.pending = [];
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
requeue(probes) {
|
|
206
|
+
const known = new Set(this.pending.map((probe) => probe.id));
|
|
207
|
+
this.pending = [...probes.filter((probe) => !known.has(probe.id)), ...this.pending].slice(-QQ_IDENTITY_PROBE_MAX);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export const QQ_REPORT_MAX_GROUPS = 100;
|
|
211
|
+
export const QQ_REPORT_MAX_MEMBERS = 200;
|
|
212
|
+
/** Ceiling on what one process remembers between heartbeats. */
|
|
213
|
+
const TRACKED_GROUPS_LIMIT = 500;
|
|
214
|
+
const TRACKED_MEMBERS_LIMIT = 2000;
|
|
215
|
+
/**
|
|
216
|
+
* Buffers groups and speakers seen since the last successful heartbeat.
|
|
217
|
+
* Drained per heartbeat and re-queued if the heartbeat fails, so a network
|
|
218
|
+
* error never loses a first sighting. Unknown groups are recorded before the
|
|
219
|
+
* allowlist check on purpose: that is how the owner discovers them.
|
|
220
|
+
*/
|
|
221
|
+
export class QqAccessTracker {
|
|
222
|
+
groups = new Map();
|
|
223
|
+
members = new Map();
|
|
224
|
+
noteGroup(groupOpenId, options = {}) {
|
|
225
|
+
const id = normalizeQqOpenId(groupOpenId);
|
|
226
|
+
if (!id)
|
|
227
|
+
return;
|
|
228
|
+
const previous = this.groups.get(id);
|
|
229
|
+
if (!previous && this.groups.size >= TRACKED_GROUPS_LIMIT)
|
|
230
|
+
return;
|
|
231
|
+
// Only the latest excerpt is kept; an event without one (a robot add/remove)
|
|
232
|
+
// must not erase the excerpt waiting for the next heartbeat.
|
|
233
|
+
const lastMessage = options.lastMessage ?? previous?.lastMessage;
|
|
234
|
+
const name = options.name ?? previous?.name;
|
|
235
|
+
// A name-only note stays name-only until a real sighting of the group comes in.
|
|
236
|
+
const nameOnly = options.nameOnly === true && (previous ? previous.nameOnly === true : true);
|
|
237
|
+
this.groups.set(id, {
|
|
238
|
+
lastSeen: options.at ?? (options.nameOnly ? previous?.lastSeen : undefined) ?? new Date().toISOString(),
|
|
239
|
+
removed: options.removed === true,
|
|
240
|
+
...(lastMessage ? { lastMessage } : {}),
|
|
241
|
+
...(name ? { name } : {}),
|
|
242
|
+
...(nameOnly ? { nameOnly: true } : {}),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
noteMember(groupOpenId, memberOpenId, at = new Date().toISOString(), lastMessage, name) {
|
|
246
|
+
const groupOpenid = normalizeQqOpenId(groupOpenId);
|
|
247
|
+
const memberOpenid = normalizeQqOpenId(memberOpenId);
|
|
248
|
+
if (!groupOpenid || !memberOpenid)
|
|
249
|
+
return;
|
|
250
|
+
const key = `${groupOpenid}\u0000${memberOpenid}`;
|
|
251
|
+
const previous = this.members.get(key);
|
|
252
|
+
if (!previous && this.members.size >= TRACKED_MEMBERS_LIMIT)
|
|
253
|
+
return;
|
|
254
|
+
const excerpt = lastMessage ?? previous?.lastMessage;
|
|
255
|
+
const nick = name ?? previous?.name;
|
|
256
|
+
this.members.set(key, { groupOpenid, memberOpenid, lastSeen: at, ...(excerpt ? { lastMessage: excerpt } : {}), ...(nick ? { name: nick } : {}) });
|
|
257
|
+
}
|
|
258
|
+
/** Remove and return up to one heartbeat's worth of reports; null when idle. */
|
|
259
|
+
drain() {
|
|
260
|
+
const groups = [];
|
|
261
|
+
for (const [groupOpenid, entry] of this.groups) {
|
|
262
|
+
if (groups.length >= QQ_REPORT_MAX_GROUPS)
|
|
263
|
+
break;
|
|
264
|
+
groups.push({
|
|
265
|
+
groupOpenid,
|
|
266
|
+
lastSeen: entry.lastSeen,
|
|
267
|
+
...(entry.removed ? { removed: true } : {}),
|
|
268
|
+
...(entry.lastMessage ? { lastMessage: entry.lastMessage } : {}),
|
|
269
|
+
...(entry.name ? { name: entry.name } : {}),
|
|
270
|
+
...(entry.nameOnly ? { nameOnly: true } : {}),
|
|
271
|
+
});
|
|
272
|
+
this.groups.delete(groupOpenid);
|
|
273
|
+
}
|
|
274
|
+
const members = [];
|
|
275
|
+
for (const [key, entry] of this.members) {
|
|
276
|
+
if (members.length >= QQ_REPORT_MAX_MEMBERS)
|
|
277
|
+
break;
|
|
278
|
+
members.push(entry);
|
|
279
|
+
this.members.delete(key);
|
|
280
|
+
}
|
|
281
|
+
return groups.length === 0 && members.length === 0 ? null : { groups, members };
|
|
282
|
+
}
|
|
283
|
+
/** Put an undelivered report back, without clobbering anything newer. */
|
|
284
|
+
requeue(report) {
|
|
285
|
+
for (const group of report.groups) {
|
|
286
|
+
if (this.groups.has(group.groupOpenid))
|
|
287
|
+
continue;
|
|
288
|
+
this.groups.set(group.groupOpenid, {
|
|
289
|
+
lastSeen: group.lastSeen,
|
|
290
|
+
removed: group.removed === true,
|
|
291
|
+
...(group.lastMessage ? { lastMessage: group.lastMessage } : {}),
|
|
292
|
+
...(group.name ? { name: group.name } : {}),
|
|
293
|
+
...(group.nameOnly ? { nameOnly: true } : {}),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
for (const member of report.members) {
|
|
297
|
+
const key = `${member.groupOpenid}\u0000${member.memberOpenid}`;
|
|
298
|
+
if (!this.members.has(key))
|
|
299
|
+
this.members.set(key, member);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export function isQqAccessReporter(value) {
|
|
304
|
+
return typeof value === "object" && value !== null
|
|
305
|
+
&& typeof value.drainQqAccessReport === "function"
|
|
306
|
+
&& typeof value.requeueQqAccessReport === "function";
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The `/bind-owner <code>` command (and `/bind`, which handed out the code) is hidden for now: the only way to
|
|
310
|
+
* become the owner in a group is join -> private question -> "Bind me" button -> Gru confirmation link.
|
|
311
|
+
* While false the agent neither recognises nor answers the command (in a group or in private; it is plain text
|
|
312
|
+
* then), and the verification code below stays but is unreachable. Flip to true to bring the command back
|
|
313
|
+
* (also SHOW_BIND_OWNER_COMMAND in the onboarding service and the code box on the GRU page).
|
|
314
|
+
*/
|
|
315
|
+
export const SHOW_BIND_OWNER_COMMAND = false;
|
|
316
|
+
/** Prefix of the binding command an owner types into a group. */
|
|
317
|
+
export const QQ_OWNER_BIND_COMMAND = "/bind-owner";
|
|
318
|
+
const OWNER_BIND_COMMAND_PATTERN = /^\/bind-owner(?:\s+(\S*))?\s*$/i;
|
|
319
|
+
/**
|
|
320
|
+
* Parse a group message as the owner binding command. Returns null when it is
|
|
321
|
+
* not that command at all, otherwise the normalized code ("" when missing).
|
|
322
|
+
* Such a message is only ever used for binding: never AI input, never logged.
|
|
323
|
+
*/
|
|
324
|
+
export function parseOwnerBindCommand(text, enabled = SHOW_BIND_OWNER_COMMAND) {
|
|
325
|
+
if (!enabled)
|
|
326
|
+
return null;
|
|
327
|
+
const match = OWNER_BIND_COMMAND_PATTERN.exec(text.trim());
|
|
328
|
+
if (!match)
|
|
329
|
+
return null;
|
|
330
|
+
return { code: (match[1] ?? "").toUpperCase().slice(0, 64) };
|
|
331
|
+
}
|
|
332
|
+
export function hashOwnerBindCode(salt, code) {
|
|
333
|
+
return createHash("sha256").update(`${salt}:${code}`).digest("hex");
|
|
334
|
+
}
|
|
335
|
+
const BIND_WINDOW_MS = 10 * 60_000;
|
|
336
|
+
/** Wrong tries per sender in one group, and per group (all senders): a group that is being guessed at locks only itself. */
|
|
337
|
+
const BIND_MAX_FAILURES_PER_MEMBER = 5;
|
|
338
|
+
const BIND_MAX_FAILURES_PER_GROUP = 20;
|
|
339
|
+
/** Memory bound on remembered failures (oldest dropped); far above what the limits above let through. */
|
|
340
|
+
const BIND_MAX_TRACKED_FAILURES = 5000;
|
|
341
|
+
/** Owner notices about a locked group: once per group and window, at most this many per window overall. */
|
|
342
|
+
const BIND_MAX_LIMIT_NOTICES = 5;
|
|
343
|
+
/**
|
|
344
|
+
* Verifies owner binding codes. Failures are limited per sender and per group in a
|
|
345
|
+
* sliding window (one group being guessed at does not lock the others), so a code cannot be guessed by trying; a used code (or an
|
|
346
|
+
* expired one) never works again, and nothing but the code's hash is held.
|
|
347
|
+
*/
|
|
348
|
+
export class QqOwnerBinder {
|
|
349
|
+
now;
|
|
350
|
+
failures = [];
|
|
351
|
+
/** Groups the owner was already told are locked, with when. */
|
|
352
|
+
limitNotified = new Map();
|
|
353
|
+
used = new Set();
|
|
354
|
+
/** Verified here, waiting to be reported (and until the server echoes them). */
|
|
355
|
+
bound = new Map();
|
|
356
|
+
unreported = [];
|
|
357
|
+
constructor(now = Date.now) {
|
|
358
|
+
this.now = now;
|
|
359
|
+
}
|
|
360
|
+
attempt(access, groupOpenId, memberOpenId, code) {
|
|
361
|
+
const at = this.now();
|
|
362
|
+
this.failures = this.failures.filter((entry) => at - entry.at < BIND_WINDOW_MS);
|
|
363
|
+
const group = normalizeQqOpenId(groupOpenId);
|
|
364
|
+
const key = `${group}\u0000${normalizeQqOpenId(memberOpenId)}`;
|
|
365
|
+
if (this.failures.filter((entry) => entry.group === group).length >= BIND_MAX_FAILURES_PER_GROUP ||
|
|
366
|
+
this.failures.filter((entry) => entry.key === key).length >= BIND_MAX_FAILURES_PER_MEMBER) {
|
|
367
|
+
return "rate_limited";
|
|
368
|
+
}
|
|
369
|
+
const pending = access?.ownerBind;
|
|
370
|
+
const expires = pending ? Date.parse(pending.expiresAt) : NaN;
|
|
371
|
+
if (pending === undefined ||
|
|
372
|
+
code.length === 0 ||
|
|
373
|
+
this.used.has(pending.id) ||
|
|
374
|
+
!Number.isFinite(expires) ||
|
|
375
|
+
expires <= at ||
|
|
376
|
+
!constantTimeEqual(hashOwnerBindCode(pending.salt, code), pending.hash)) {
|
|
377
|
+
this.failures.push({ group, key, at });
|
|
378
|
+
if (this.failures.length > BIND_MAX_TRACKED_FAILURES)
|
|
379
|
+
this.failures.splice(0, this.failures.length - BIND_MAX_TRACKED_FAILURES);
|
|
380
|
+
return "invalid";
|
|
381
|
+
}
|
|
382
|
+
this.used.add(pending.id);
|
|
383
|
+
const binding = { groupOpenid: normalizeQqOpenId(groupOpenId), memberOpenid: normalizeQqOpenId(memberOpenId) };
|
|
384
|
+
this.bound.set(binding.groupOpenid, binding);
|
|
385
|
+
this.unreported.push({ ...binding, bindId: pending.id });
|
|
386
|
+
return "ok";
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* True once per group and window after it was locked: the owner should be told (privately, never in
|
|
390
|
+
* the group, where an attacker would see it). Bounded overall so it cannot become a message storm.
|
|
391
|
+
*/
|
|
392
|
+
takeLimitNotice(groupOpenId) {
|
|
393
|
+
const at = this.now();
|
|
394
|
+
for (const [group, when] of this.limitNotified)
|
|
395
|
+
if (at - when >= BIND_WINDOW_MS)
|
|
396
|
+
this.limitNotified.delete(group);
|
|
397
|
+
const group = normalizeQqOpenId(groupOpenId);
|
|
398
|
+
if (this.limitNotified.has(group) || this.limitNotified.size >= BIND_MAX_LIMIT_NOTICES)
|
|
399
|
+
return false;
|
|
400
|
+
this.limitNotified.set(group, at);
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
/** Bindings in force here before the server has echoed them. */
|
|
404
|
+
local() {
|
|
405
|
+
return [...this.bound.values()];
|
|
406
|
+
}
|
|
407
|
+
/** The server confirmed these groups: stop carrying them locally. */
|
|
408
|
+
confirm(access) {
|
|
409
|
+
for (const [group, binding] of [...this.bound]) {
|
|
410
|
+
if (ownerMemberIn(access, group).includes(binding.memberOpenid))
|
|
411
|
+
this.bound.delete(group);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
drain() {
|
|
415
|
+
return this.unreported.splice(0);
|
|
416
|
+
}
|
|
417
|
+
requeue(bindings) {
|
|
418
|
+
this.unreported.unshift(...bindings);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function constantTimeEqual(a, b) {
|
|
422
|
+
const left = Buffer.from(a);
|
|
423
|
+
const right = Buffer.from(b);
|
|
424
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
425
|
+
}
|
|
426
|
+
//# sourceMappingURL=qq-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-access.js","sourceRoot":"","sources":["../../src/channels/qq-access.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Bind me" in a QQ group -> "Confirm in Gru".
|
|
3
|
+
*
|
|
4
|
+
* The group tap (INTERACTION_CREATE, scene=group) tells us which group and which member pressed. We ack the
|
|
5
|
+
* click first (QQ wants that within 5 seconds), ask the onboarding service for a one-time link bound to
|
|
6
|
+
* (this agent, that group, that member), and answer in the group as a passive reply to the click's event id.
|
|
7
|
+
* Whoever taps gets a link; it only binds if a verified GRU owner of this agent opens it and confirms, so a
|
|
8
|
+
* stranger's tap changes nothing. Nothing here logs a full id, the link or its token.
|
|
9
|
+
*/
|
|
10
|
+
export declare const BIND_BUTTON_DATA = "qqbl:1";
|
|
11
|
+
/** Taps by the same member in the same group within this window are answered once (no second link). */
|
|
12
|
+
export declare const BIND_TAP_WINDOW_MS = 30000;
|
|
13
|
+
/** At most this many links per group in BIND_GROUP_WINDOW_MS; a crowd tapping cannot flood the service. */
|
|
14
|
+
export declare const BIND_GROUP_MAX = 10;
|
|
15
|
+
export declare const BIND_GROUP_WINDOW_MS: number;
|
|
16
|
+
/** The Bind me prompt (re-offered by the hidden bare /bind-owner) is answered at most once per group in this window. */
|
|
17
|
+
export declare const BIND_PROMPT_COOLDOWN_MS = 60000;
|
|
18
|
+
export type BindLinkReply = {
|
|
19
|
+
token: string;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
gruUrl: string | null;
|
|
22
|
+
} | {
|
|
23
|
+
error: string;
|
|
24
|
+
} | null;
|
|
25
|
+
export interface BindLinkClick {
|
|
26
|
+
/** `d.id` of the interaction: only for the PUT /interactions/{id} ack. */
|
|
27
|
+
interactionId: string;
|
|
28
|
+
/** The gateway payload's top-level `id`: the only id QQ accepts as `event_id` for a passive reply (40034025 otherwise). */
|
|
29
|
+
eventId?: string;
|
|
30
|
+
groupOpenId: string;
|
|
31
|
+
memberOpenId: string;
|
|
32
|
+
}
|
|
33
|
+
export interface BindLinkDeps {
|
|
34
|
+
now: () => number;
|
|
35
|
+
/** PUT /interactions/{id}; resolves once QQ has the answer. */
|
|
36
|
+
ack: (interactionId: string, code: number) => Promise<void>;
|
|
37
|
+
request: (input: {
|
|
38
|
+
groupOpenid: string;
|
|
39
|
+
memberOpenid: string;
|
|
40
|
+
interactionId: string;
|
|
41
|
+
}) => Promise<BindLinkReply>;
|
|
42
|
+
/** Passive reply to the click in the group (event id). Rejects with the platform error. */
|
|
43
|
+
replyInGroup: (click: BindLinkClick, text: string) => Promise<void>;
|
|
44
|
+
/** The GRU address when the service did not name one. */
|
|
45
|
+
fallbackBase: () => string;
|
|
46
|
+
text: (key: "reply" | "unavailable" | "url_blocked", link?: string) => string;
|
|
47
|
+
log: (message: string) => void;
|
|
48
|
+
}
|
|
49
|
+
/** True when the platform refused a message because it carries a link that is not on the bot's allowed list. */
|
|
50
|
+
export declare function isUrlNotAllowed(error: unknown): boolean;
|
|
51
|
+
/** QQ's error with every 32-hex id cut to 8 characters, safe for the log (never carries the link or token). */
|
|
52
|
+
export declare function describePostError(error: unknown): string;
|
|
53
|
+
export declare function buildBindUrl(base: string, token: string): string;
|
|
54
|
+
export declare class QqBindLinkFlow {
|
|
55
|
+
private readonly deps;
|
|
56
|
+
private readonly taps;
|
|
57
|
+
private readonly perGroup;
|
|
58
|
+
private readonly prompts;
|
|
59
|
+
constructor(deps: BindLinkDeps);
|
|
60
|
+
/** The bare /bind-owner may post the prompt: once per group per cooldown. */
|
|
61
|
+
takePrompt(groupOpenId: string): boolean;
|
|
62
|
+
handleClick(click: BindLinkClick): Promise<void>;
|
|
63
|
+
private say;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=qq-bind-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-bind-link.d.ts","sourceRoot":"","sources":["../../src/channels/qq-bind-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,gBAAgB,WAAW,CAAC;AACzC,uGAAuG;AACvG,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,2GAA2G;AAC3G,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,QAAc,CAAC;AAChD,wHAAwH;AACxH,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAEnH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;IACtB,2HAA2H;IAC3H,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,+DAA+D;IAC/D,GAAG,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACjH,2FAA2F;IAC3F,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,yDAAyD;IACzD,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,GAAG,aAAa,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9E,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAID,gHAAgH;AAChH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGvD;AAED,+GAA+G;AAC/G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGxD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,qBAAa,cAAc;IAKb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+B;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;gBAExB,IAAI,EAAE,YAAY;IAE/C,6EAA6E;IAC7E,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAQlC,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;YA6CxC,GAAG;CAOlB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Bind me" in a QQ group -> "Confirm in Gru".
|
|
3
|
+
*
|
|
4
|
+
* The group tap (INTERACTION_CREATE, scene=group) tells us which group and which member pressed. We ack the
|
|
5
|
+
* click first (QQ wants that within 5 seconds), ask the onboarding service for a one-time link bound to
|
|
6
|
+
* (this agent, that group, that member), and answer in the group as a passive reply to the click's event id.
|
|
7
|
+
* Whoever taps gets a link; it only binds if a verified GRU owner of this agent opens it and confirms, so a
|
|
8
|
+
* stranger's tap changes nothing. Nothing here logs a full id, the link or its token.
|
|
9
|
+
*/
|
|
10
|
+
export const BIND_BUTTON_DATA = "qqbl:1";
|
|
11
|
+
/** Taps by the same member in the same group within this window are answered once (no second link). */
|
|
12
|
+
export const BIND_TAP_WINDOW_MS = 30_000;
|
|
13
|
+
/** At most this many links per group in BIND_GROUP_WINDOW_MS; a crowd tapping cannot flood the service. */
|
|
14
|
+
export const BIND_GROUP_MAX = 10;
|
|
15
|
+
export const BIND_GROUP_WINDOW_MS = 10 * 60_000;
|
|
16
|
+
/** The Bind me prompt (re-offered by the hidden bare /bind-owner) is answered at most once per group in this window. */
|
|
17
|
+
export const BIND_PROMPT_COOLDOWN_MS = 60_000;
|
|
18
|
+
const shortId = (id) => id.slice(0, 8);
|
|
19
|
+
/** True when the platform refused a message because it carries a link that is not on the bot's allowed list. */
|
|
20
|
+
export function isUrlNotAllowed(error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
return /40054010|304003|不允许发送\s*url|url\s*未报备/i.test(message);
|
|
23
|
+
}
|
|
24
|
+
/** QQ's error with every 32-hex id cut to 8 characters, safe for the log (never carries the link or token). */
|
|
25
|
+
export function describePostError(error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
+
return message.replace(/(?:UID_)?([0-9A-Fa-f]{8})[0-9A-Fa-f]{24}/g, "$1…").slice(0, 300);
|
|
28
|
+
}
|
|
29
|
+
export function buildBindUrl(base, token) {
|
|
30
|
+
return `${base.trim().replace(/\/+$/, "")}/bind/qq#t=${token}`;
|
|
31
|
+
}
|
|
32
|
+
export class QqBindLinkFlow {
|
|
33
|
+
deps;
|
|
34
|
+
taps = new Map();
|
|
35
|
+
perGroup = new Map();
|
|
36
|
+
prompts = new Map();
|
|
37
|
+
constructor(deps) {
|
|
38
|
+
this.deps = deps;
|
|
39
|
+
}
|
|
40
|
+
/** The bare /bind-owner may post the prompt: once per group per cooldown. */
|
|
41
|
+
takePrompt(groupOpenId) {
|
|
42
|
+
const at = this.deps.now();
|
|
43
|
+
for (const [group, when] of this.prompts)
|
|
44
|
+
if (at - when >= BIND_PROMPT_COOLDOWN_MS)
|
|
45
|
+
this.prompts.delete(group);
|
|
46
|
+
if (this.prompts.has(groupOpenId))
|
|
47
|
+
return false;
|
|
48
|
+
this.prompts.set(groupOpenId, at);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
async handleClick(click) {
|
|
52
|
+
const { deps } = this;
|
|
53
|
+
// Ack first and unconditionally: a missing ack leaves the tapper's client spinning.
|
|
54
|
+
await deps.ack(click.interactionId, 0);
|
|
55
|
+
const at = deps.now();
|
|
56
|
+
for (const [key, when] of this.taps)
|
|
57
|
+
if (at - when >= BIND_TAP_WINDOW_MS)
|
|
58
|
+
this.taps.delete(key);
|
|
59
|
+
const tapKey = `${click.groupOpenId}\u0000${click.memberOpenId}`;
|
|
60
|
+
if (this.taps.has(tapKey)) {
|
|
61
|
+
deps.log(`bind link: repeated tap ignored (group ${shortId(click.groupOpenId)}, member ${shortId(click.memberOpenId)})`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const recent = (this.perGroup.get(click.groupOpenId) ?? []).filter((when) => at - when < BIND_GROUP_WINDOW_MS);
|
|
65
|
+
if (recent.length >= BIND_GROUP_MAX) {
|
|
66
|
+
this.perGroup.set(click.groupOpenId, recent);
|
|
67
|
+
deps.log(`bind link: group ${shortId(click.groupOpenId)} is rate limited`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.taps.set(tapKey, at);
|
|
71
|
+
this.perGroup.set(click.groupOpenId, [...recent, at]);
|
|
72
|
+
if (this.perGroup.size > 500) {
|
|
73
|
+
const oldest = this.perGroup.keys().next();
|
|
74
|
+
if (!oldest.done)
|
|
75
|
+
this.perGroup.delete(oldest.value);
|
|
76
|
+
}
|
|
77
|
+
const reply = await deps.request({ groupOpenid: click.groupOpenId, memberOpenid: click.memberOpenId, interactionId: click.interactionId });
|
|
78
|
+
if (reply && "error" in reply && reply.error === "duplicate")
|
|
79
|
+
return; // the same click again: already answered
|
|
80
|
+
if (!reply || "error" in reply) {
|
|
81
|
+
deps.log(`bind link: not created for group ${shortId(click.groupOpenId)} (${reply ? reply.error : "service unreachable"})`);
|
|
82
|
+
this.taps.delete(tapKey);
|
|
83
|
+
await this.say(click, deps.text("unavailable"));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const url = buildBindUrl(reply.gruUrl ?? deps.fallbackBase(), reply.token);
|
|
87
|
+
try {
|
|
88
|
+
await deps.replyInGroup(click, deps.text("reply", url));
|
|
89
|
+
deps.log(`bind link sent (group ${shortId(click.groupOpenId)}, member ${shortId(click.memberOpenId)})`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// Never silent: say why (QQ's code and message, ids shortened), then tell the group something without a link.
|
|
93
|
+
// The link never reached the group, so a second tap must be allowed to try again (not "repeated tap").
|
|
94
|
+
this.taps.delete(tapKey);
|
|
95
|
+
deps.log(`bind link: could not post in group ${shortId(click.groupOpenId)}${isUrlNotAllowed(error) ? " (link not allowed by the platform)" : ""}: ${describePostError(error)}`);
|
|
96
|
+
await this.say(click, deps.text(isUrlNotAllowed(error) ? "url_blocked" : "unavailable"));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async say(click, text) {
|
|
100
|
+
try {
|
|
101
|
+
await this.deps.replyInGroup(click, text);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.deps.log(`bind link: the notice without a link could not be posted either: ${describePostError(error)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=qq-bind-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-bind-link.js","sourceRoot":"","sources":["../../src/channels/qq-bind-link.ts"],"names":[],"mappings":"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"}
|