@nextclaw/desktop-extension-wechat 0.2.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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +15 -0
- package/dist/main.js.map +1 -0
- package/dist/services/wechat-desktop-observation.service.d.ts +16 -0
- package/dist/services/wechat-desktop-observation.service.d.ts.map +1 -0
- package/dist/services/wechat-desktop-observation.service.js +94 -0
- package/dist/services/wechat-desktop-observation.service.js.map +1 -0
- package/dist/types/wechat-desktop-extension.types.d.ts +30 -0
- package/dist/types/wechat-desktop-extension.types.d.ts.map +1 -0
- package/dist/utils/wechat-desktop-snapshot.utils.d.ts +13 -0
- package/dist/utils/wechat-desktop-snapshot.utils.d.ts.map +1 -0
- package/dist/utils/wechat-desktop-snapshot.utils.js +67 -0
- package/dist/utils/wechat-desktop-snapshot.utils.js.map +1 -0
- package/nextclaw.extension.json +46 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NextClaw contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @nextclaw/desktop-extension-wechat
|
|
2
|
+
|
|
3
|
+
微信桌面观察 Extension。它通过 NextClaw Desktop 的辅助功能宿主读取微信窗口,并把新出现的可见消息接入 Observation。
|
|
4
|
+
|
|
5
|
+
使用前需要:
|
|
6
|
+
|
|
7
|
+
1. 在 macOS 系统设置中允许 NextClaw Desktop 使用辅助功能。
|
|
8
|
+
2. 在 NextClaw 中分别允许本 Extension 读取和观察微信。
|
|
9
|
+
3. 由 Agent 使用 `bind_context` 或 `subscribe_events`,并将 `extensionId` 设为 `nextclaw-desktop-extension-wechat`。
|
|
10
|
+
|
|
11
|
+
当前版本只读取和观察可见内容,不发送消息。微信界面未打开、目标对话不可见或授权被撤销时,Observation 会进入可诊断的阻塞状态。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { WechatDesktopObservationService } from "./services/wechat-desktop-observation.service.js";
|
|
2
|
+
import { AccessibilityNode, WechatDesktopObservationConfig, WechatDesktopSnapshot, WechatVisibleMessage } from "./types/wechat-desktop-extension.types.js";
|
|
3
|
+
import { findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot } from "./utils/wechat-desktop-snapshot.utils.js";
|
|
4
|
+
export { type AccessibilityNode, type WechatDesktopObservationConfig, WechatDesktopObservationService, type WechatDesktopSnapshot, type WechatVisibleMessage, findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot } from "./utils/wechat-desktop-snapshot.utils.js";
|
|
2
|
+
import { WechatDesktopObservationService } from "./services/wechat-desktop-observation.service.js";
|
|
3
|
+
export { WechatDesktopObservationService, findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot };
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { WechatDesktopObservationService } from "./services/wechat-desktop-observation.service.js";
|
|
2
|
+
import { NextClawExtension } from "@nextclaw/extension-sdk";
|
|
3
|
+
//#region src/main.ts
|
|
4
|
+
const extension = new NextClawExtension();
|
|
5
|
+
const observation = new WechatDesktopObservationService(extension.host.desktop);
|
|
6
|
+
extension.observations.provide(observation.handlers);
|
|
7
|
+
process.once("SIGTERM", () => {
|
|
8
|
+
observation.close();
|
|
9
|
+
extension.close();
|
|
10
|
+
});
|
|
11
|
+
await extension.ready();
|
|
12
|
+
//#endregion
|
|
13
|
+
export {};
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","names":[],"sources":["../src/main.ts"],"sourcesContent":["import { NextClawExtension } from \"@nextclaw/extension-sdk\";\nimport { WechatDesktopObservationService } from \"./services/wechat-desktop-observation.service.js\";\n\nconst extension = new NextClawExtension();\nconst observation = new WechatDesktopObservationService(extension.host.desktop);\nextension.observations.provide(observation.handlers);\n\nprocess.once(\"SIGTERM\", () => {\n observation.close();\n extension.close();\n});\n\nawait extension.ready();\n"],"mappings":";;;AAGA,MAAM,YAAY,IAAI,mBAAmB;AACzC,MAAM,cAAc,IAAI,gCAAgC,UAAU,KAAK,QAAQ;AAC/E,UAAU,aAAa,QAAQ,YAAY,SAAS;AAEpD,QAAQ,KAAK,iBAAiB;AAC5B,aAAY,OAAO;AACnB,WAAU,OAAO;EACjB;AAEF,MAAM,UAAU,OAAO"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DesktopHost, ExtensionObservationHandlers } from "@nextclaw/extension-sdk";
|
|
2
|
+
|
|
3
|
+
//#region src/services/wechat-desktop-observation.service.d.ts
|
|
4
|
+
declare class WechatDesktopObservationService {
|
|
5
|
+
private readonly desktop;
|
|
6
|
+
private readonly watches;
|
|
7
|
+
private readonly removeEventListener;
|
|
8
|
+
constructor(desktop: DesktopHost);
|
|
9
|
+
readonly handlers: ExtensionObservationHandlers;
|
|
10
|
+
close: () => void;
|
|
11
|
+
private readSnapshot;
|
|
12
|
+
private readonly onDesktopEvent;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { WechatDesktopObservationService };
|
|
16
|
+
//# sourceMappingURL=wechat-desktop-observation.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-desktop-observation.service.d.ts","names":[],"sources":["../../src/services/wechat-desktop-observation.service.ts"],"mappings":";;;cAqBa,+BAAA;EAAA,iBAIkB,OAAA;EAAA,iBAHZ,OAAA;EAAA,iBACA,mBAAA;cAEY,OAAA,EAAS,WAAA;EAAA,SAI7B,QAAA,EAAU,4BAAA;EAkCnB,KAAA;EAAA,QAKQ,YAAA;EAAA,iBAcS,cAAA;AAAA"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot } from "../utils/wechat-desktop-snapshot.utils.js";
|
|
2
|
+
//#region src/services/wechat-desktop-observation.service.ts
|
|
3
|
+
var WechatDesktopObservationService = class {
|
|
4
|
+
watches = /* @__PURE__ */ new Map();
|
|
5
|
+
removeEventListener;
|
|
6
|
+
constructor(desktop) {
|
|
7
|
+
this.desktop = desktop;
|
|
8
|
+
this.removeEventListener = desktop.onEvent(this.onDesktopEvent);
|
|
9
|
+
}
|
|
10
|
+
handlers = {
|
|
11
|
+
read: async ({ config }) => await this.readSnapshot(normalizeWechatObservationConfig(config)),
|
|
12
|
+
subscribe: async ({ config, emit, signal, subscriptionId }) => {
|
|
13
|
+
const normalized = normalizeWechatObservationConfig(config);
|
|
14
|
+
const baseline = await this.readSnapshot(normalized);
|
|
15
|
+
const observed = await this.desktop.invoke({
|
|
16
|
+
method: "host.ui.observe",
|
|
17
|
+
payload: {
|
|
18
|
+
target: { applicationId: "wechat" },
|
|
19
|
+
maxDepth: 24,
|
|
20
|
+
maxNodes: 1e4
|
|
21
|
+
},
|
|
22
|
+
caller: { subscriptionId }
|
|
23
|
+
});
|
|
24
|
+
this.watches.set(observed.watchId, {
|
|
25
|
+
config: normalized,
|
|
26
|
+
snapshot: baseline,
|
|
27
|
+
emit
|
|
28
|
+
});
|
|
29
|
+
const cleanup = async () => {
|
|
30
|
+
this.watches.delete(observed.watchId);
|
|
31
|
+
await this.desktop.invoke({
|
|
32
|
+
method: "host.ui.unobserve",
|
|
33
|
+
payload: { watchId: observed.watchId },
|
|
34
|
+
caller: { subscriptionId }
|
|
35
|
+
}).catch(() => void 0);
|
|
36
|
+
};
|
|
37
|
+
signal.addEventListener("abort", () => void cleanup(), { once: true });
|
|
38
|
+
return cleanup;
|
|
39
|
+
},
|
|
40
|
+
replay: "unsupported"
|
|
41
|
+
};
|
|
42
|
+
close = () => {
|
|
43
|
+
this.removeEventListener();
|
|
44
|
+
this.watches.clear();
|
|
45
|
+
};
|
|
46
|
+
readSnapshot = async (config) => {
|
|
47
|
+
return toWechatDesktopSnapshot({
|
|
48
|
+
root: await this.desktop.invoke({
|
|
49
|
+
method: "host.ui.snapshot",
|
|
50
|
+
payload: {
|
|
51
|
+
target: { applicationId: "wechat" },
|
|
52
|
+
maxDepth: 24,
|
|
53
|
+
maxNodes: 1e4
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
config
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
onDesktopEvent = async (event) => {
|
|
60
|
+
const state = this.watches.get(event.watchId);
|
|
61
|
+
if (!state) return;
|
|
62
|
+
const root = readEventSnapshot(event.event);
|
|
63
|
+
if (!root) return;
|
|
64
|
+
const next = toWechatDesktopSnapshot({
|
|
65
|
+
root,
|
|
66
|
+
config: state.config
|
|
67
|
+
});
|
|
68
|
+
const added = findNewWechatMessages(state.snapshot, next);
|
|
69
|
+
state.snapshot = next;
|
|
70
|
+
for (const message of added) await state.emit({
|
|
71
|
+
id: `wechat-${message.id}`,
|
|
72
|
+
type: "wechat.desktop.message.visible",
|
|
73
|
+
occurredAt: next.capturedAt,
|
|
74
|
+
cursor: message.id,
|
|
75
|
+
dedupeKey: message.id,
|
|
76
|
+
payload: {
|
|
77
|
+
applicationId: next.applicationId,
|
|
78
|
+
...next.conversation ? { conversation: next.conversation } : {},
|
|
79
|
+
text: message.text,
|
|
80
|
+
path: message.path
|
|
81
|
+
},
|
|
82
|
+
sourceRefs: [`desktop://wechat/${message.id}`]
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
function readEventSnapshot(value) {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
88
|
+
const event = value;
|
|
89
|
+
return event.type === "snapshotChanged" ? event.snapshot ?? null : null;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { WechatDesktopObservationService };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=wechat-desktop-observation.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-desktop-observation.service.js","names":[],"sources":["../../src/services/wechat-desktop-observation.service.ts"],"sourcesContent":["import type {\n DesktopHost,\n DesktopHostEvent,\n ExtensionObservationHandlers,\n} from \"@nextclaw/extension-sdk\";\nimport type {\n WechatDesktopObservationConfig,\n WechatDesktopSnapshot,\n} from \"../types/wechat-desktop-extension.types.js\";\nimport {\n findNewWechatMessages,\n normalizeWechatObservationConfig,\n toWechatDesktopSnapshot,\n} from \"../utils/wechat-desktop-snapshot.utils.js\";\n\ntype WatchState = {\n config: WechatDesktopObservationConfig;\n snapshot: WechatDesktopSnapshot;\n emit: Parameters<NonNullable<ExtensionObservationHandlers[\"subscribe\"]>>[0][\"emit\"];\n};\n\nexport class WechatDesktopObservationService {\n private readonly watches = new Map<string, WatchState>();\n private readonly removeEventListener: () => void;\n\n constructor(private readonly desktop: DesktopHost) {\n this.removeEventListener = desktop.onEvent(this.onDesktopEvent);\n }\n\n readonly handlers: ExtensionObservationHandlers = {\n read: async ({ config }) =>\n await this.readSnapshot(normalizeWechatObservationConfig(config)),\n subscribe: async ({ config, emit, signal, subscriptionId }) => {\n const normalized = normalizeWechatObservationConfig(config);\n const baseline = await this.readSnapshot(normalized);\n const observed = await this.desktop.invoke<{ watchId: string }>({\n method: \"host.ui.observe\",\n payload: {\n target: { applicationId: \"wechat\" },\n maxDepth: 24,\n maxNodes: 10_000,\n },\n caller: { subscriptionId },\n });\n this.watches.set(observed.watchId, {\n config: normalized,\n snapshot: baseline,\n emit,\n });\n const cleanup = async () => {\n this.watches.delete(observed.watchId);\n await this.desktop.invoke({\n method: \"host.ui.unobserve\",\n payload: { watchId: observed.watchId },\n caller: { subscriptionId },\n }).catch(() => undefined);\n };\n signal.addEventListener(\"abort\", () => void cleanup(), { once: true });\n return cleanup;\n },\n replay: \"unsupported\",\n };\n\n close = (): void => {\n this.removeEventListener();\n this.watches.clear();\n };\n\n private readSnapshot = async (\n config: ReturnType<typeof normalizeWechatObservationConfig>,\n ): Promise<WechatDesktopSnapshot> => {\n const root = await this.desktop.invoke({\n method: \"host.ui.snapshot\",\n payload: {\n target: { applicationId: \"wechat\" },\n maxDepth: 24,\n maxNodes: 10_000,\n },\n });\n return toWechatDesktopSnapshot({ root, config });\n };\n\n private readonly onDesktopEvent = async (\n event: DesktopHostEvent,\n ): Promise<void> => {\n const state = this.watches.get(event.watchId);\n if (!state) return;\n const root = readEventSnapshot(event.event);\n if (!root) return;\n const next = toWechatDesktopSnapshot({\n root,\n config: state.config,\n });\n const added = findNewWechatMessages(state.snapshot, next);\n state.snapshot = next;\n for (const message of added) {\n await state.emit({\n id: `wechat-${message.id}`,\n type: \"wechat.desktop.message.visible\",\n occurredAt: next.capturedAt,\n cursor: message.id,\n dedupeKey: message.id,\n payload: {\n applicationId: next.applicationId,\n ...(next.conversation ? { conversation: next.conversation } : {}),\n text: message.text,\n path: message.path,\n },\n sourceRefs: [`desktop://wechat/${message.id}`],\n });\n }\n };\n}\n\nfunction readEventSnapshot(value: unknown): unknown | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n const event = value as Record<string, unknown>;\n return event.type === \"snapshotChanged\" ? event.snapshot ?? null : null;\n}\n"],"mappings":";;AAqBA,IAAa,kCAAb,MAA6C;CAC3C,0BAA2B,IAAI,KAAyB;CACxD;CAEA,YAAY,SAAuC;AAAtB,OAAA,UAAA;AAC3B,OAAK,sBAAsB,QAAQ,QAAQ,KAAK,eAAe;;CAGjE,WAAkD;EAChD,MAAM,OAAO,EAAE,aACb,MAAM,KAAK,aAAa,iCAAiC,OAAO,CAAC;EACnE,WAAW,OAAO,EAAE,QAAQ,MAAM,QAAQ,qBAAqB;GAC7D,MAAM,aAAa,iCAAiC,OAAO;GAC3D,MAAM,WAAW,MAAM,KAAK,aAAa,WAAW;GACpD,MAAM,WAAW,MAAM,KAAK,QAAQ,OAA4B;IAC9D,QAAQ;IACR,SAAS;KACP,QAAQ,EAAE,eAAe,UAAU;KACnC,UAAU;KACV,UAAU;KACX;IACD,QAAQ,EAAE,gBAAgB;IAC3B,CAAC;AACF,QAAK,QAAQ,IAAI,SAAS,SAAS;IACjC,QAAQ;IACR,UAAU;IACV;IACD,CAAC;GACF,MAAM,UAAU,YAAY;AAC1B,SAAK,QAAQ,OAAO,SAAS,QAAQ;AACrC,UAAM,KAAK,QAAQ,OAAO;KACxB,QAAQ;KACR,SAAS,EAAE,SAAS,SAAS,SAAS;KACtC,QAAQ,EAAE,gBAAgB;KAC3B,CAAC,CAAC,YAAY,KAAA,EAAU;;AAE3B,UAAO,iBAAiB,eAAe,KAAK,SAAS,EAAE,EAAE,MAAM,MAAM,CAAC;AACtE,UAAO;;EAET,QAAQ;EACT;CAED,cAAoB;AAClB,OAAK,qBAAqB;AAC1B,OAAK,QAAQ,OAAO;;CAGtB,eAAuB,OACrB,WACmC;AASnC,SAAO,wBAAwB;GAAE,MARpB,MAAM,KAAK,QAAQ,OAAO;IACrC,QAAQ;IACR,SAAS;KACP,QAAQ,EAAE,eAAe,UAAU;KACnC,UAAU;KACV,UAAU;KACX;IACF,CAAC;GACqC;GAAQ,CAAC;;CAGlD,iBAAkC,OAChC,UACkB;EAClB,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM,QAAQ;AAC7C,MAAI,CAAC,MAAO;EACZ,MAAM,OAAO,kBAAkB,MAAM,MAAM;AAC3C,MAAI,CAAC,KAAM;EACX,MAAM,OAAO,wBAAwB;GACnC;GACA,QAAQ,MAAM;GACf,CAAC;EACF,MAAM,QAAQ,sBAAsB,MAAM,UAAU,KAAK;AACzD,QAAM,WAAW;AACjB,OAAK,MAAM,WAAW,MACpB,OAAM,MAAM,KAAK;GACf,IAAI,UAAU,QAAQ;GACtB,MAAM;GACN,YAAY,KAAK;GACjB,QAAQ,QAAQ;GAChB,WAAW,QAAQ;GACnB,SAAS;IACP,eAAe,KAAK;IACpB,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,cAAc,GAAG,EAAE;IAChE,MAAM,QAAQ;IACd,MAAM,QAAQ;IACf;GACD,YAAY,CAAC,oBAAoB,QAAQ,KAAK;GAC/C,CAAC;;;AAKR,SAAS,kBAAkB,OAAgC;AACzD,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAAE,QAAO;CACxE,MAAM,QAAQ;AACd,QAAO,MAAM,SAAS,oBAAoB,MAAM,YAAY,OAAO"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/types/wechat-desktop-extension.types.d.ts
|
|
2
|
+
type AccessibilityNode = {
|
|
3
|
+
role?: string;
|
|
4
|
+
subrole?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
value?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
identifier?: string;
|
|
9
|
+
focused?: boolean;
|
|
10
|
+
children?: AccessibilityNode[];
|
|
11
|
+
};
|
|
12
|
+
type WechatDesktopObservationConfig = {
|
|
13
|
+
conversation?: string;
|
|
14
|
+
ignorePrefixes: string[];
|
|
15
|
+
maxItems: number;
|
|
16
|
+
};
|
|
17
|
+
type WechatVisibleMessage = {
|
|
18
|
+
id: string;
|
|
19
|
+
text: string;
|
|
20
|
+
path: number[];
|
|
21
|
+
};
|
|
22
|
+
type WechatDesktopSnapshot = {
|
|
23
|
+
applicationId: "wechat";
|
|
24
|
+
conversation?: string;
|
|
25
|
+
messages: WechatVisibleMessage[];
|
|
26
|
+
capturedAt: string;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { AccessibilityNode, WechatDesktopObservationConfig, WechatDesktopSnapshot, WechatVisibleMessage };
|
|
30
|
+
//# sourceMappingURL=wechat-desktop-extension.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-desktop-extension.types.d.ts","names":[],"sources":["../../src/types/wechat-desktop-extension.types.ts"],"mappings":";KAAY,iBAAA;EACV,IAAA;EACA,OAAA;EACA,KAAA;EACA,KAAA;EACA,WAAA;EACA,UAAA;EACA,OAAA;EACA,QAAA,GAAW,iBAAA;AAAA;AAAA,KAGD,8BAAA;EACV,YAAA;EACA,cAAA;EACA,QAAA;AAAA;AAAA,KAGU,oBAAA;EACV,EAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,KAGU,qBAAA;EACV,aAAA;EACA,YAAA;EACA,QAAA,EAAU,oBAAA;EACV,UAAA;AAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { WechatDesktopObservationConfig, WechatDesktopSnapshot, WechatVisibleMessage } from "../types/wechat-desktop-extension.types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/wechat-desktop-snapshot.utils.d.ts
|
|
4
|
+
declare function normalizeWechatObservationConfig(value: unknown): WechatDesktopObservationConfig;
|
|
5
|
+
declare function toWechatDesktopSnapshot(input: {
|
|
6
|
+
root: unknown;
|
|
7
|
+
config: WechatDesktopObservationConfig;
|
|
8
|
+
capturedAt?: string;
|
|
9
|
+
}): WechatDesktopSnapshot;
|
|
10
|
+
declare function findNewWechatMessages(previous: WechatDesktopSnapshot, next: WechatDesktopSnapshot): WechatVisibleMessage[];
|
|
11
|
+
//#endregion
|
|
12
|
+
export { findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot };
|
|
13
|
+
//# sourceMappingURL=wechat-desktop-snapshot.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-desktop-snapshot.utils.d.ts","names":[],"sources":["../../src/utils/wechat-desktop-snapshot.utils.ts"],"mappings":";;;iBAWgB,gCAAA,CACd,KAAA,YACC,8BAAA;AAAA,iBAoBa,uBAAA,CAAwB,KAAA;EACtC,IAAA;EACA,MAAA,EAAQ,8BAAA;EACR,UAAA;AAAA,IACE,qBAAA;AAAA,iBAaY,qBAAA,CACd,QAAA,EAAU,qBAAA,EACV,IAAA,EAAM,qBAAA,GACL,oBAAA"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
//#region src/utils/wechat-desktop-snapshot.utils.ts
|
|
3
|
+
const DEFAULT_IGNORE_PREFIXES = ["🤖[墨爪]"];
|
|
4
|
+
const MESSAGE_ROLES = new Set([
|
|
5
|
+
"AXStaticText",
|
|
6
|
+
"AXTextArea",
|
|
7
|
+
"AXTextField"
|
|
8
|
+
]);
|
|
9
|
+
function normalizeWechatObservationConfig(value) {
|
|
10
|
+
const record = asRecord(value);
|
|
11
|
+
const maxItems = Number(record.maxItems);
|
|
12
|
+
const ignorePrefixes = Array.isArray(record.ignorePrefixes) ? record.ignorePrefixes.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean) : DEFAULT_IGNORE_PREFIXES;
|
|
13
|
+
return {
|
|
14
|
+
...readString(record.conversation) ? { conversation: readString(record.conversation) } : {},
|
|
15
|
+
ignorePrefixes,
|
|
16
|
+
maxItems: Number.isInteger(maxItems) ? Math.max(1, Math.min(100, maxItems)) : 30
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function toWechatDesktopSnapshot(input) {
|
|
20
|
+
const messages = [];
|
|
21
|
+
visitNode(input.root, [], input.config, messages);
|
|
22
|
+
return {
|
|
23
|
+
applicationId: "wechat",
|
|
24
|
+
...input.config.conversation ? { conversation: input.config.conversation } : {},
|
|
25
|
+
messages: messages.slice(-input.config.maxItems),
|
|
26
|
+
capturedAt: input.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function findNewWechatMessages(previous, next) {
|
|
30
|
+
const seen = new Set(previous.messages.map((message) => message.id));
|
|
31
|
+
return next.messages.filter((message) => !seen.has(message.id));
|
|
32
|
+
}
|
|
33
|
+
function visitNode(value, path, config, messages) {
|
|
34
|
+
const node = asNode(value);
|
|
35
|
+
if (!node) return;
|
|
36
|
+
const text = readNodeText(node);
|
|
37
|
+
if (text && MESSAGE_ROLES.has(node.role ?? "") && !config.ignorePrefixes.some((prefix) => text.startsWith(prefix))) messages.push({
|
|
38
|
+
id: createHash("sha256").update(`${path.join(".")}\u0000${text}`).digest("hex").slice(0, 24),
|
|
39
|
+
text,
|
|
40
|
+
path
|
|
41
|
+
});
|
|
42
|
+
for (const [index, child] of (node.children ?? []).entries()) visitNode(child, [...path, index], config, messages);
|
|
43
|
+
}
|
|
44
|
+
function readNodeText(node) {
|
|
45
|
+
for (const value of [
|
|
46
|
+
node.value,
|
|
47
|
+
node.title,
|
|
48
|
+
node.description
|
|
49
|
+
]) {
|
|
50
|
+
const text = readString(value);
|
|
51
|
+
if (text && text.length <= 8e3) return text;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function asNode(value) {
|
|
56
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
57
|
+
}
|
|
58
|
+
function asRecord(value) {
|
|
59
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
60
|
+
}
|
|
61
|
+
function readString(value) {
|
|
62
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { findNewWechatMessages, normalizeWechatObservationConfig, toWechatDesktopSnapshot };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=wechat-desktop-snapshot.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wechat-desktop-snapshot.utils.js","names":[],"sources":["../../src/utils/wechat-desktop-snapshot.utils.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type {\n AccessibilityNode,\n WechatDesktopObservationConfig,\n WechatDesktopSnapshot,\n WechatVisibleMessage,\n} from \"../types/wechat-desktop-extension.types.js\";\n\nconst DEFAULT_IGNORE_PREFIXES = [\"🤖[墨爪]\"];\nconst MESSAGE_ROLES = new Set([\"AXStaticText\", \"AXTextArea\", \"AXTextField\"]);\n\nexport function normalizeWechatObservationConfig(\n value: unknown,\n): WechatDesktopObservationConfig {\n const record = asRecord(value);\n const maxItems = Number(record.maxItems);\n const ignorePrefixes = Array.isArray(record.ignorePrefixes)\n ? record.ignorePrefixes\n .filter((entry): entry is string => typeof entry === \"string\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n : DEFAULT_IGNORE_PREFIXES;\n return {\n ...(readString(record.conversation)\n ? { conversation: readString(record.conversation) }\n : {}),\n ignorePrefixes,\n maxItems: Number.isInteger(maxItems)\n ? Math.max(1, Math.min(100, maxItems))\n : 30,\n };\n}\n\nexport function toWechatDesktopSnapshot(input: {\n root: unknown;\n config: WechatDesktopObservationConfig;\n capturedAt?: string;\n}): WechatDesktopSnapshot {\n const messages: WechatVisibleMessage[] = [];\n visitNode(input.root, [], input.config, messages);\n return {\n applicationId: \"wechat\",\n ...(input.config.conversation\n ? { conversation: input.config.conversation }\n : {}),\n messages: messages.slice(-input.config.maxItems),\n capturedAt: input.capturedAt ?? new Date().toISOString(),\n };\n}\n\nexport function findNewWechatMessages(\n previous: WechatDesktopSnapshot,\n next: WechatDesktopSnapshot,\n): WechatVisibleMessage[] {\n const seen = new Set(previous.messages.map((message) => message.id));\n return next.messages.filter((message) => !seen.has(message.id));\n}\n\nfunction visitNode(\n value: unknown,\n path: number[],\n config: WechatDesktopObservationConfig,\n messages: WechatVisibleMessage[],\n): void {\n const node = asNode(value);\n if (!node) return;\n const text = readNodeText(node);\n if (\n text &&\n MESSAGE_ROLES.has(node.role ?? \"\") &&\n !config.ignorePrefixes.some((prefix) => text.startsWith(prefix))\n ) {\n messages.push({\n id: createHash(\"sha256\")\n .update(`${path.join(\".\")}\\u0000${text}`)\n .digest(\"hex\")\n .slice(0, 24),\n text,\n path,\n });\n }\n for (const [index, child] of (node.children ?? []).entries()) {\n visitNode(child, [...path, index], config, messages);\n }\n}\n\nfunction readNodeText(node: AccessibilityNode): string | null {\n for (const value of [node.value, node.title, node.description]) {\n const text = readString(value);\n if (text && text.length <= 8_000) return text;\n }\n return null;\n}\n\nfunction asNode(value: unknown): AccessibilityNode | null {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as AccessibilityNode)\n : null;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n"],"mappings":";;AAQA,MAAM,0BAA0B,CAAC,SAAS;AAC1C,MAAM,gBAAgB,IAAI,IAAI;CAAC;CAAgB;CAAc;CAAc,CAAC;AAE5E,SAAgB,iCACd,OACgC;CAChC,MAAM,SAAS,SAAS,MAAM;CAC9B,MAAM,WAAW,OAAO,OAAO,SAAS;CACxC,MAAM,iBAAiB,MAAM,QAAQ,OAAO,eAAe,GACvD,OAAO,eACJ,QAAQ,UAA2B,OAAO,UAAU,SAAS,CAC7D,KAAK,UAAU,MAAM,MAAM,CAAC,CAC5B,OAAO,QAAQ,GAClB;AACJ,QAAO;EACL,GAAI,WAAW,OAAO,aAAa,GAC/B,EAAE,cAAc,WAAW,OAAO,aAAa,EAAE,GACjD,EAAE;EACN;EACA,UAAU,OAAO,UAAU,SAAS,GAChC,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,CAAC,GACpC;EACL;;AAGH,SAAgB,wBAAwB,OAId;CACxB,MAAM,WAAmC,EAAE;AAC3C,WAAU,MAAM,MAAM,EAAE,EAAE,MAAM,QAAQ,SAAS;AACjD,QAAO;EACL,eAAe;EACf,GAAI,MAAM,OAAO,eACb,EAAE,cAAc,MAAM,OAAO,cAAc,GAC3C,EAAE;EACN,UAAU,SAAS,MAAM,CAAC,MAAM,OAAO,SAAS;EAChD,YAAY,MAAM,+BAAc,IAAI,MAAM,EAAC,aAAa;EACzD;;AAGH,SAAgB,sBACd,UACA,MACwB;CACxB,MAAM,OAAO,IAAI,IAAI,SAAS,SAAS,KAAK,YAAY,QAAQ,GAAG,CAAC;AACpE,QAAO,KAAK,SAAS,QAAQ,YAAY,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC;;AAGjE,SAAS,UACP,OACA,MACA,QACA,UACM;CACN,MAAM,OAAO,OAAO,MAAM;AAC1B,KAAI,CAAC,KAAM;CACX,MAAM,OAAO,aAAa,KAAK;AAC/B,KACE,QACA,cAAc,IAAI,KAAK,QAAQ,GAAG,IAClC,CAAC,OAAO,eAAe,MAAM,WAAW,KAAK,WAAW,OAAO,CAAC,CAEhE,UAAS,KAAK;EACZ,IAAI,WAAW,SAAS,CACrB,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,QAAQ,OAAO,CACxC,OAAO,MAAM,CACb,MAAM,GAAG,GAAG;EACf;EACA;EACD,CAAC;AAEJ,MAAK,MAAM,CAAC,OAAO,WAAW,KAAK,YAAY,EAAE,EAAE,SAAS,CAC1D,WAAU,OAAO,CAAC,GAAG,MAAM,MAAM,EAAE,QAAQ,SAAS;;AAIxD,SAAS,aAAa,MAAwC;AAC5D,MAAK,MAAM,SAAS;EAAC,KAAK;EAAO,KAAK;EAAO,KAAK;EAAY,EAAE;EAC9D,MAAM,OAAO,WAAW,MAAM;AAC9B,MAAI,QAAQ,KAAK,UAAU,IAAO,QAAO;;AAE3C,QAAO;;AAGT,SAAS,OAAO,OAA0C;AACxD,QAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC7D,QACD;;AAGN,SAAS,SAAS,OAAyC;AACzD,QAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC7D,QACD,EAAE;;AAGR,SAAS,WAAW,OAAoC;AACtD,QAAO,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,MAAM,MAAM,GAAG,KAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nextclaw-desktop-extension-wechat",
|
|
3
|
+
"name": "微信桌面观察",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"server": {
|
|
6
|
+
"type": "stdio",
|
|
7
|
+
"command": "node",
|
|
8
|
+
"args": ["dist/main.js"]
|
|
9
|
+
},
|
|
10
|
+
"contributes": {
|
|
11
|
+
"hostCapabilities": {
|
|
12
|
+
"desktopAutomation": {
|
|
13
|
+
"access": ["ui.read", "ui.observe"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"observations": {
|
|
17
|
+
"read": {
|
|
18
|
+
"description": "读取当前微信窗口中可见的对话和消息。",
|
|
19
|
+
"configSchema": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"conversation": { "type": "string" },
|
|
24
|
+
"maxItems": { "type": "integer", "minimum": 1, "maximum": 100 }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"events": {
|
|
29
|
+
"description": "微信窗口出现新的可见消息时通知当前会话。",
|
|
30
|
+
"replay": "unsupported",
|
|
31
|
+
"configSchema": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"properties": {
|
|
35
|
+
"conversation": { "type": "string" },
|
|
36
|
+
"ignorePrefixes": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": { "type": "string" }
|
|
39
|
+
},
|
|
40
|
+
"maxItems": { "type": "integer", "minimum": 1, "maximum": 100 }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextclaw/desktop-extension-wechat",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "NextClaw WeChat Desktop observation extension.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"development": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"nextclaw.extension.json",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@nextclaw/extension-sdk": "0.5.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.17.6",
|
|
25
|
+
"typescript": "^5.6.3",
|
|
26
|
+
"vitest": "^4.1.2"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsdown src/index.ts src/main.ts --dts.sourcemap --clean --target es2022 --no-fixedExtension --unbundle",
|
|
30
|
+
"lint": "eslint src --max-warnings=0",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"tsc": "tsc -p tsconfig.json"
|
|
33
|
+
}
|
|
34
|
+
}
|