@krischoichoi/channel-weixin 0.6.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/README.md +101 -0
- package/lib/adapter.d.ts +90 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +247 -0
- package/lib/adapter.js.map +1 -0
- package/lib/auth/account-store.d.ts +60 -0
- package/lib/auth/account-store.d.ts.map +1 -0
- package/lib/auth/account-store.js +84 -0
- package/lib/auth/account-store.js.map +1 -0
- package/lib/auth/login.d.ts +83 -0
- package/lib/auth/login.d.ts.map +1 -0
- package/lib/auth/login.js +202 -0
- package/lib/auth/login.js.map +1 -0
- package/lib/config.d.ts +52 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +36 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +59 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +146 -0
- package/lib/definition.js.map +1 -0
- package/lib/ilink/base-info.d.ts +22 -0
- package/lib/ilink/base-info.d.ts.map +1 -0
- package/lib/ilink/base-info.js +11 -0
- package/lib/ilink/base-info.js.map +1 -0
- package/lib/ilink/client.d.ts +110 -0
- package/lib/ilink/client.d.ts.map +1 -0
- package/lib/ilink/client.js +308 -0
- package/lib/ilink/client.js.map +1 -0
- package/lib/ilink/constants.d.ts +59 -0
- package/lib/ilink/constants.d.ts.map +1 -0
- package/lib/ilink/constants.js +59 -0
- package/lib/ilink/constants.js.map +1 -0
- package/lib/ilink/errors.d.ts +79 -0
- package/lib/ilink/errors.d.ts.map +1 -0
- package/lib/ilink/errors.js +121 -0
- package/lib/ilink/errors.js.map +1 -0
- package/lib/ilink/headers.d.ts +40 -0
- package/lib/ilink/headers.d.ts.map +1 -0
- package/lib/ilink/headers.js +70 -0
- package/lib/ilink/headers.js.map +1 -0
- package/lib/ilink/schema.d.ts +154 -0
- package/lib/ilink/schema.d.ts.map +1 -0
- package/lib/ilink/schema.js +140 -0
- package/lib/ilink/schema.js.map +1 -0
- package/lib/ilink/types.d.ts +235 -0
- package/lib/ilink/types.d.ts.map +1 -0
- package/lib/ilink/types.js +21 -0
- package/lib/ilink/types.js.map +1 -0
- package/lib/index.d.ts +50 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +91 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest.d.ts +54 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +32 -0
- package/lib/manifest.js.map +1 -0
- package/lib/media/decrypt.d.ts +3 -0
- package/lib/media/decrypt.d.ts.map +1 -0
- package/lib/media/decrypt.js +16 -0
- package/lib/media/decrypt.js.map +1 -0
- package/lib/media/download.d.ts +22 -0
- package/lib/media/download.d.ts.map +1 -0
- package/lib/media/download.js +62 -0
- package/lib/media/download.js.map +1 -0
- package/lib/media/encrypt.d.ts +3 -0
- package/lib/media/encrypt.d.ts.map +1 -0
- package/lib/media/encrypt.js +16 -0
- package/lib/media/encrypt.js.map +1 -0
- package/lib/media/send-media.d.ts +41 -0
- package/lib/media/send-media.d.ts.map +1 -0
- package/lib/media/send-media.js +52 -0
- package/lib/media/send-media.js.map +1 -0
- package/lib/media/silk-transcode.d.ts +38 -0
- package/lib/media/silk-transcode.d.ts.map +1 -0
- package/lib/media/silk-transcode.js +88 -0
- package/lib/media/silk-transcode.js.map +1 -0
- package/lib/media/upload.d.ts +38 -0
- package/lib/media/upload.d.ts.map +1 -0
- package/lib/media/upload.js +122 -0
- package/lib/media/upload.js.map +1 -0
- package/lib/messaging/dedup.d.ts +87 -0
- package/lib/messaging/dedup.d.ts.map +1 -0
- package/lib/messaging/dedup.js +127 -0
- package/lib/messaging/dedup.js.map +1 -0
- package/lib/messaging/mapper.d.ts +26 -0
- package/lib/messaging/mapper.d.ts.map +1 -0
- package/lib/messaging/mapper.js +107 -0
- package/lib/messaging/mapper.js.map +1 -0
- package/lib/messaging/monitor.d.ts +109 -0
- package/lib/messaging/monitor.d.ts.map +1 -0
- package/lib/messaging/monitor.js +234 -0
- package/lib/messaging/monitor.js.map +1 -0
- package/lib/messaging/quote.d.ts +9 -0
- package/lib/messaging/quote.d.ts.map +1 -0
- package/lib/messaging/quote.js +35 -0
- package/lib/messaging/quote.js.map +1 -0
- package/lib/messaging/send.d.ts +54 -0
- package/lib/messaging/send.d.ts.map +1 -0
- package/lib/messaging/send.js +173 -0
- package/lib/messaging/send.js.map +1 -0
- package/lib/messaging/typing.d.ts +34 -0
- package/lib/messaging/typing.d.ts.map +1 -0
- package/lib/messaging/typing.js +56 -0
- package/lib/messaging/typing.js.map +1 -0
- package/lib/storage/context-token.d.ts +26 -0
- package/lib/storage/context-token.d.ts.map +1 -0
- package/lib/storage/context-token.js +26 -0
- package/lib/storage/context-token.js.map +1 -0
- package/lib/storage/sync-cursor.d.ts +27 -0
- package/lib/storage/sync-cursor.d.ts.map +1 -0
- package/lib/storage/sync-cursor.js +25 -0
- package/lib/storage/sync-cursor.js.map +1 -0
- package/lib/transport.d.ts +38 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +68 -0
- package/lib/transport.js.map +1 -0
- package/lib/upstream/compat.d.ts +54 -0
- package/lib/upstream/compat.d.ts.map +1 -0
- package/lib/upstream/compat.js +54 -0
- package/lib/upstream/compat.js.map +1 -0
- package/lib/upstream/config-manager.d.ts +35 -0
- package/lib/upstream/config-manager.d.ts.map +1 -0
- package/lib/upstream/config-manager.js +91 -0
- package/lib/upstream/config-manager.js.map +1 -0
- package/lib/upstream/index.d.ts +42 -0
- package/lib/upstream/index.d.ts.map +1 -0
- package/lib/upstream/index.js +13 -0
- package/lib/upstream/index.js.map +1 -0
- package/lib/upstream/port.d.ts +153 -0
- package/lib/upstream/port.d.ts.map +1 -0
- package/lib/upstream/port.js +15 -0
- package/lib/upstream/port.js.map +1 -0
- package/lib/upstream/tencent-upstream.d.ts +116 -0
- package/lib/upstream/tencent-upstream.d.ts.map +1 -0
- package/lib/upstream/tencent-upstream.js +518 -0
- package/lib/upstream/tencent-upstream.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const dedupRecordsSchema = z.array(z.object({
|
|
3
|
+
key: z.string(),
|
|
4
|
+
committedAt: z.number().finite(),
|
|
5
|
+
}));
|
|
6
|
+
/**
|
|
7
|
+
* Stable hash (FNV-1a-ish) used as the final fallback for a missing
|
|
8
|
+
* message_id/seq. Never the primary identity.
|
|
9
|
+
*/
|
|
10
|
+
export function stableHash(input) {
|
|
11
|
+
let hash = 0x811c9dc5;
|
|
12
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
13
|
+
hash ^= input.charCodeAt(i);
|
|
14
|
+
hash = Math.imul(hash, 0x01000193);
|
|
15
|
+
}
|
|
16
|
+
return (hash >>> 0).toString(36);
|
|
17
|
+
}
|
|
18
|
+
/** Compute a dedup identity for a raw iLink message. */
|
|
19
|
+
export function dedupKey(raw) {
|
|
20
|
+
const msg = raw;
|
|
21
|
+
if (msg?.message_id !== undefined && msg.message_id !== null) {
|
|
22
|
+
return 'mid:' + String(msg.message_id);
|
|
23
|
+
}
|
|
24
|
+
if (msg?.seq !== undefined && msg.seq !== null) {
|
|
25
|
+
return 'seq:' + String(msg.seq);
|
|
26
|
+
}
|
|
27
|
+
// Final fallback — stable hash of the JSON shape.
|
|
28
|
+
return 'hash:' + stableHash(JSON.stringify(msg ?? {}));
|
|
29
|
+
}
|
|
30
|
+
/** Default cap on persistent dedup records (R1 recommends 1000..5000). */
|
|
31
|
+
const MAX_RECORDS = 5000;
|
|
32
|
+
/** In-memory two-phase dedup (tests / no persistence). */
|
|
33
|
+
export class MemoryDedupStore {
|
|
34
|
+
seen = new Map();
|
|
35
|
+
windowMs;
|
|
36
|
+
now;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.windowMs = options.windowMs;
|
|
39
|
+
this.now = options.now ?? Date.now;
|
|
40
|
+
}
|
|
41
|
+
async has(key) {
|
|
42
|
+
this.prune(this.now());
|
|
43
|
+
const last = this.seen.get(key);
|
|
44
|
+
return last !== undefined && this.now() - last < this.windowMs;
|
|
45
|
+
}
|
|
46
|
+
async commit(key) {
|
|
47
|
+
const now = this.now();
|
|
48
|
+
this.prune(now);
|
|
49
|
+
this.seen.set(key, now);
|
|
50
|
+
}
|
|
51
|
+
prune(now) {
|
|
52
|
+
for (const [key, ts] of this.seen) {
|
|
53
|
+
if (now - ts >= this.windowMs)
|
|
54
|
+
this.seen.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Durable two-phase dedup store. Committed records survive a restart so a
|
|
60
|
+
* cursor crash-replay window never re-triggers the Agent. State is a JSON
|
|
61
|
+
* array of DedupRecord under weixin:dedup:<accountId>, loaded lazily and
|
|
62
|
+
* rewritten (pruned + capped) on each commit.
|
|
63
|
+
*/
|
|
64
|
+
export class PersistentDedupStore {
|
|
65
|
+
storage;
|
|
66
|
+
key;
|
|
67
|
+
windowMs;
|
|
68
|
+
now;
|
|
69
|
+
maxRecords;
|
|
70
|
+
records;
|
|
71
|
+
constructor(options) {
|
|
72
|
+
this.storage = options.storage;
|
|
73
|
+
this.key = 'weixin:dedup:' + (options.accountId ?? 'main');
|
|
74
|
+
this.windowMs = options.windowMs;
|
|
75
|
+
this.now = options.now ?? Date.now;
|
|
76
|
+
this.maxRecords = options.maxRecords ?? MAX_RECORDS;
|
|
77
|
+
}
|
|
78
|
+
async load() {
|
|
79
|
+
if (this.records)
|
|
80
|
+
return this.records;
|
|
81
|
+
const map = new Map();
|
|
82
|
+
const raw = await this.storage.get(this.key);
|
|
83
|
+
if (raw) {
|
|
84
|
+
try {
|
|
85
|
+
const parsedJson = JSON.parse(raw);
|
|
86
|
+
const parsed = dedupRecordsSchema.safeParse(parsedJson);
|
|
87
|
+
if (parsed.success) {
|
|
88
|
+
for (const rec of parsed.data)
|
|
89
|
+
map.set(rec.key, rec.committedAt);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Corrupt dedup window -> start fresh (worst case: a replay re-emits,
|
|
94
|
+
// which is safer than dropping messages forever).
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.records = map;
|
|
98
|
+
return map;
|
|
99
|
+
}
|
|
100
|
+
async persist() {
|
|
101
|
+
const records = this.records ?? new Map();
|
|
102
|
+
const now = this.now();
|
|
103
|
+
const kept = [];
|
|
104
|
+
for (const [key, ts] of records) {
|
|
105
|
+
if (now - ts >= this.windowMs)
|
|
106
|
+
continue;
|
|
107
|
+
kept.push({ key, committedAt: ts });
|
|
108
|
+
}
|
|
109
|
+
kept.sort((a, b) => b.committedAt - a.committedAt);
|
|
110
|
+
const capped = kept.slice(0, this.maxRecords);
|
|
111
|
+
await this.storage.set(this.key, JSON.stringify(capped));
|
|
112
|
+
this.records = new Map(capped.map((r) => [r.key, r.committedAt]));
|
|
113
|
+
}
|
|
114
|
+
async has(key) {
|
|
115
|
+
const map = await this.load();
|
|
116
|
+
const last = map.get(key);
|
|
117
|
+
if (last === undefined)
|
|
118
|
+
return false;
|
|
119
|
+
return this.now() - last < this.windowMs;
|
|
120
|
+
}
|
|
121
|
+
async commit(key) {
|
|
122
|
+
const map = await this.load();
|
|
123
|
+
map.set(key, this.now());
|
|
124
|
+
await this.persist();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=dedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/messaging/dedup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classification: A — DSH pure mapping [keep].
|
|
3
|
+
*
|
|
4
|
+
* Pure iLink-message → Channel MessagePart mapping, no I/O, no platform
|
|
5
|
+
* algorithm. The official package's inbound mapper (messaging/inbound.js) is a
|
|
6
|
+
* different model surface (OpenClaw messages); DSH keeps its own neutral
|
|
7
|
+
* mapper for the Channel contract. Keep.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Pure inbound mapper — an iLink {@link ILinkMessage} -> Channel
|
|
11
|
+
* {@link MessageReceived}. No I/O.
|
|
12
|
+
*
|
|
13
|
+
* Mapping rules (doc §21-22):
|
|
14
|
+
* - `message_id` -> `message.id` (fallback stable hash only when omitted).
|
|
15
|
+
* - `from_user_id` -> `sender.id` AND `conversation.id` (DM).
|
|
16
|
+
* - `create_time_ms` -> `createdAt`.
|
|
17
|
+
* - `item_list` -> `MessagePart[]` (TEXT -> text part; IMAGE/VOICE/FILE/VIDEO ->
|
|
18
|
+
* media part when CDN fields are present, else unsupported placeholder).
|
|
19
|
+
*/
|
|
20
|
+
import type { MessagePart, MessageReceived } from '@krischoichoi/channel-core';
|
|
21
|
+
import type { ILinkMessage, ILinkMessageItem, WeixinInboundMeta } from '../ilink/types.js';
|
|
22
|
+
/** Map one item to a Channel MessagePart. */
|
|
23
|
+
export declare function mapItem(item: ILinkMessageItem): MessagePart;
|
|
24
|
+
/** Map one raw iLink message into a stable channel event. */
|
|
25
|
+
export declare function mapInbound(raw: ILinkMessage, meta: WeixinInboundMeta): MessageReceived;
|
|
26
|
+
//# sourceMappingURL=mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../../src/messaging/mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAEV,WAAW,EACX,eAAe,EAGhB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AA0B3F,6CAA6C;AAC7C,wBAAgB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,WAAW,CA4C3D;AAYD,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,iBAAiB,GAAG,eAAe,CA6BtF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { stableHash } from './dedup.js';
|
|
2
|
+
import { formatQuotedContext } from './quote.js';
|
|
3
|
+
/** Human label for common item types. */
|
|
4
|
+
function itemTypeName(type) {
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 1: return 'text';
|
|
7
|
+
case 2: return 'image';
|
|
8
|
+
case 3: return 'voice';
|
|
9
|
+
case 4: return 'file';
|
|
10
|
+
case 5: return 'video';
|
|
11
|
+
default: return 'unknown';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Whether a CDN media reference is present and downloadable. */
|
|
15
|
+
function hasMedia(item) {
|
|
16
|
+
const media = item.image_item?.media ??
|
|
17
|
+
item.voice_item?.media ??
|
|
18
|
+
item.file_item?.media ??
|
|
19
|
+
item.video_item?.media;
|
|
20
|
+
return Boolean(media && (media.encrypt_query_param || media.full_url));
|
|
21
|
+
}
|
|
22
|
+
/** Map one item to a Channel MessagePart. */
|
|
23
|
+
export function mapItem(item) {
|
|
24
|
+
switch (item.type) {
|
|
25
|
+
case 1: {
|
|
26
|
+
const text = item.text_item?.text ?? '';
|
|
27
|
+
if (!text)
|
|
28
|
+
return { type: 'unsupported', reason: 'empty text item' };
|
|
29
|
+
return { type: 'text', text };
|
|
30
|
+
}
|
|
31
|
+
case 2: {
|
|
32
|
+
const img = item.image_item;
|
|
33
|
+
if (img && hasMedia(item)) {
|
|
34
|
+
return {
|
|
35
|
+
type: 'image',
|
|
36
|
+
url: img.media?.full_url ?? img.url,
|
|
37
|
+
mimeType: 'image/jpeg',
|
|
38
|
+
alt: undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { type: 'unsupported', reason: 'image without downloadable CDN fields (WX5)' };
|
|
42
|
+
}
|
|
43
|
+
case 3: {
|
|
44
|
+
const voice = item.voice_item;
|
|
45
|
+
if (voice?.media && (voice.media.full_url || voice.media.encrypt_query_param)) {
|
|
46
|
+
return { type: 'audio', url: voice.media.full_url, durationMs: voice.playtime };
|
|
47
|
+
}
|
|
48
|
+
if (voice?.text)
|
|
49
|
+
return { type: 'text', text: voice.text };
|
|
50
|
+
return { type: 'unsupported', reason: 'voice without downloadable CDN fields (WX5)' };
|
|
51
|
+
}
|
|
52
|
+
case 4: {
|
|
53
|
+
const file = item.file_item;
|
|
54
|
+
if (file?.media && (file.media.full_url || file.media.encrypt_query_param)) {
|
|
55
|
+
return { type: 'file', url: file.media.full_url, name: file.file_name };
|
|
56
|
+
}
|
|
57
|
+
return { type: 'unsupported', reason: 'file without downloadable CDN fields (WX5)' };
|
|
58
|
+
}
|
|
59
|
+
case 5: {
|
|
60
|
+
const video = item.video_item;
|
|
61
|
+
if (video?.media && (video.media.full_url || video.media.encrypt_query_param)) {
|
|
62
|
+
return { type: 'video', url: video.media.full_url, durationMs: video.play_length };
|
|
63
|
+
}
|
|
64
|
+
return { type: 'unsupported', reason: 'video without downloadable CDN fields (WX5)' };
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
return { type: 'unsupported', reason: `unknown weixin item type '${itemTypeName(item.type)}'` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Map a wire item, preserving both quote context and voice transcription. */
|
|
71
|
+
function mapItemParts(item) {
|
|
72
|
+
const quoted = formatQuotedContext(item.ref_msg);
|
|
73
|
+
const mapped = mapItem(item);
|
|
74
|
+
if (item.type === 3 && item.voice_item?.text && mapped.type === 'audio') {
|
|
75
|
+
return [...quoted, { type: 'text', text: item.voice_item.text }, mapped];
|
|
76
|
+
}
|
|
77
|
+
return [...quoted, mapped];
|
|
78
|
+
}
|
|
79
|
+
/** Map one raw iLink message into a stable channel event. */
|
|
80
|
+
export function mapInbound(raw, meta) {
|
|
81
|
+
const from = (raw.from_user_id ?? 'unknown');
|
|
82
|
+
const conversationId = from;
|
|
83
|
+
const items = Array.isArray(raw.item_list) ? raw.item_list : [];
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
parts.push(...mapItemParts(item));
|
|
87
|
+
}
|
|
88
|
+
if (parts.length === 0)
|
|
89
|
+
parts.push({ type: 'unsupported', reason: 'empty item_list' });
|
|
90
|
+
const messageId = raw.message_id !== undefined && raw.message_id !== null
|
|
91
|
+
? `wx-${String(raw.message_id)}`
|
|
92
|
+
: `wx-${stableHash(`${from}:${raw.create_time_ms ?? 0}:${raw.seq ?? ''}`)}`;
|
|
93
|
+
return {
|
|
94
|
+
type: 'message.received',
|
|
95
|
+
channel: meta.channel,
|
|
96
|
+
accountId: meta.accountId,
|
|
97
|
+
conversation: { id: conversationId, type: 'dm' },
|
|
98
|
+
sender: { id: from },
|
|
99
|
+
message: {
|
|
100
|
+
id: messageId,
|
|
101
|
+
content: parts,
|
|
102
|
+
createdAt: raw.create_time_ms ?? Date.now(),
|
|
103
|
+
},
|
|
104
|
+
raw,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.js","sourceRoot":"","sources":["../../src/messaging/mapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classification: A — DSH glue getUpdates loop [keep].
|
|
3
|
+
*
|
|
4
|
+
* DSH's receive loop (cursor + dedup + context-token capture + dispatch). The
|
|
5
|
+
* official monitor (monitor/monitor.js) is coupled to the OpenClaw channel
|
|
6
|
+
* lifecycle (PluginRuntime) and is not reusable host-neutrally. This loop is
|
|
7
|
+
* DSH orchestration, not a duplicated wire protocol. Keep; driven by
|
|
8
|
+
* upstream/tencent-upstream.ts startMonitor().
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* WeixinMonitor — the getUpdates loop (doc §16-18).
|
|
12
|
+
*
|
|
13
|
+
* Flow per round:
|
|
14
|
+
* load cursor -> getUpdates(cursor, timeout, signal)
|
|
15
|
+
* -> for each msg: dedup.has -> capture context_token -> map
|
|
16
|
+
* -> beforeEmit/hydrate -> sanitized log -> emit -> dedup.commit
|
|
17
|
+
* -> THEN commit the new cursor
|
|
18
|
+
*
|
|
19
|
+
* R1 ordering guarantees:
|
|
20
|
+
* - a message becomes a committed dedup entry ONLY after a successful emit,
|
|
21
|
+
* so an emit failure replays it next round instead of dropping it forever;
|
|
22
|
+
* - the cursor is persisted only after the whole round succeeded, and a
|
|
23
|
+
* failed cursor write throws (CursorCommitError) so the loop retries
|
|
24
|
+
* instead of diverging the in-memory cursor from the durable one.
|
|
25
|
+
*
|
|
26
|
+
* The next long-poll timeout is dynamically updated from the server's
|
|
27
|
+
* longpolling_timeout_ms. Reconnect uses exponential backoff per the
|
|
28
|
+
* reconnect config. On AbortSignal it exits cleanly. Notify lifecycle
|
|
29
|
+
* (notifystart/notifystop) is best-effort.
|
|
30
|
+
*/
|
|
31
|
+
import type { ChannelAdapterContext, MessagePart } from '@krischoichoi/channel-core';
|
|
32
|
+
import type { ILinkClient } from '../ilink/client.js';
|
|
33
|
+
import type { WeixinInboundMeta } from '../ilink/types.js';
|
|
34
|
+
import type { SyncCursorStore } from '../storage/sync-cursor.js';
|
|
35
|
+
import type { ContextTokenStore } from '../storage/context-token.js';
|
|
36
|
+
import { type DedupStore } from './dedup.js';
|
|
37
|
+
import { mapInbound } from './mapper.js';
|
|
38
|
+
import { StaleTokenError } from '../ilink/errors.js';
|
|
39
|
+
export interface MonitorReconnectConfig {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
baseDelayMs: number;
|
|
42
|
+
maxDelayMs: number;
|
|
43
|
+
}
|
|
44
|
+
/** Raised when the durable cursor write fails; the monitor loop retries. */
|
|
45
|
+
export declare class CursorCommitError extends Error {
|
|
46
|
+
readonly cursor: string;
|
|
47
|
+
constructor(cursor: string, options?: ErrorOptions);
|
|
48
|
+
}
|
|
49
|
+
export interface WeixinMonitorOptions {
|
|
50
|
+
client: ILinkClient;
|
|
51
|
+
cursor: SyncCursorStore;
|
|
52
|
+
contextTokens: ContextTokenStore;
|
|
53
|
+
ctx: ChannelAdapterContext;
|
|
54
|
+
meta: WeixinInboundMeta;
|
|
55
|
+
/** Dispatch an inbound message event into the pipeline. Injectable for tests. */
|
|
56
|
+
emit?: (event: ReturnType<typeof mapInbound>) => Promise<void>;
|
|
57
|
+
/** Enrich mapped content before the summary is logged and the event emitted. */
|
|
58
|
+
beforeEmit?: (event: ReturnType<typeof mapInbound>) => Promise<void>;
|
|
59
|
+
reconnect: MonitorReconnectConfig;
|
|
60
|
+
/** Initial long-poll timeout. */
|
|
61
|
+
longPollTimeoutMs: number;
|
|
62
|
+
/** Dedup window in ms. */
|
|
63
|
+
dedupWindowMs: number;
|
|
64
|
+
/** Two-phase dedup store; defaults to a durable PersistentDedupStore. */
|
|
65
|
+
dedup?: DedupStore;
|
|
66
|
+
/** Injectable clock (tests). */
|
|
67
|
+
now?: () => number;
|
|
68
|
+
/** Called on health/connection transitions. */
|
|
69
|
+
onConnectionChange?: (state: 'connected' | 'reconnecting' | 'disconnected') => void;
|
|
70
|
+
/** Terminal credential invalidation; never enters the reconnect loop. */
|
|
71
|
+
onStaleToken?: (error: StaleTokenError) => void | Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export declare class WeixinMonitor {
|
|
74
|
+
private readonly client;
|
|
75
|
+
private readonly cursor;
|
|
76
|
+
private readonly contextTokens;
|
|
77
|
+
private readonly ctx;
|
|
78
|
+
private readonly meta;
|
|
79
|
+
private readonly emitMsg;
|
|
80
|
+
private readonly beforeEmit?;
|
|
81
|
+
private readonly reconnect;
|
|
82
|
+
private readonly dedup;
|
|
83
|
+
private readonly now;
|
|
84
|
+
private readonly onConnectionChange?;
|
|
85
|
+
private readonly onStaleToken?;
|
|
86
|
+
private running;
|
|
87
|
+
private loop?;
|
|
88
|
+
private nextLongPollTimeoutMs;
|
|
89
|
+
constructor(options: WeixinMonitorOptions);
|
|
90
|
+
/** Start the monitor loop. Best-effort notifystart first. */
|
|
91
|
+
start(): Promise<void>;
|
|
92
|
+
/** Abort the loop and best-effort notifystop. */
|
|
93
|
+
stop(): Promise<void>;
|
|
94
|
+
/** Wait for the current loop iteration to finish (used by tests/teardown). */
|
|
95
|
+
join(): Promise<void>;
|
|
96
|
+
private runLoop;
|
|
97
|
+
/**
|
|
98
|
+
* Persist the new cursor. The local cursor is advanced only after the
|
|
99
|
+
* durable write succeeds; on failure a CursorCommitError is thrown so the
|
|
100
|
+
* loop retries with the previous cursor instead of silently diverging.
|
|
101
|
+
*/
|
|
102
|
+
private commitCursor;
|
|
103
|
+
private handleMessage;
|
|
104
|
+
/** Current long-poll timeout (dynamic). */
|
|
105
|
+
get longPollTimeout(): number;
|
|
106
|
+
}
|
|
107
|
+
/** Compact per-part summary for inbound message logs (debug diagnostics). */
|
|
108
|
+
export declare function summarizeInboundParts(parts: readonly MessagePart[]): unknown[];
|
|
109
|
+
//# sourceMappingURL=monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../../src/messaging/monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { PersistentDedupStore, dedupKey } from './dedup.js';
|
|
2
|
+
import { mapInbound } from './mapper.js';
|
|
3
|
+
import { StaleTokenError } from '../ilink/errors.js';
|
|
4
|
+
/** Raised when the durable cursor write fails; the monitor loop retries. */
|
|
5
|
+
export class CursorCommitError extends Error {
|
|
6
|
+
cursor;
|
|
7
|
+
constructor(cursor, options) {
|
|
8
|
+
super('failed to commit sync cursor', options);
|
|
9
|
+
this.name = 'CursorCommitError';
|
|
10
|
+
this.cursor = cursor;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class WeixinMonitor {
|
|
14
|
+
client;
|
|
15
|
+
cursor;
|
|
16
|
+
contextTokens;
|
|
17
|
+
ctx;
|
|
18
|
+
meta;
|
|
19
|
+
emitMsg;
|
|
20
|
+
beforeEmit;
|
|
21
|
+
reconnect;
|
|
22
|
+
dedup;
|
|
23
|
+
now;
|
|
24
|
+
onConnectionChange;
|
|
25
|
+
onStaleToken;
|
|
26
|
+
running = false;
|
|
27
|
+
loop;
|
|
28
|
+
nextLongPollTimeoutMs;
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.client = options.client;
|
|
31
|
+
this.cursor = options.cursor;
|
|
32
|
+
this.contextTokens = options.contextTokens;
|
|
33
|
+
this.ctx = options.ctx;
|
|
34
|
+
this.meta = options.meta;
|
|
35
|
+
this.emitMsg = options.emit ?? ((event) => this.ctx.emit(event));
|
|
36
|
+
this.beforeEmit = options.beforeEmit;
|
|
37
|
+
this.reconnect = options.reconnect;
|
|
38
|
+
this.dedup = options.dedup ?? new PersistentDedupStore({
|
|
39
|
+
storage: options.ctx.storage,
|
|
40
|
+
accountId: options.meta.accountId,
|
|
41
|
+
windowMs: options.dedupWindowMs,
|
|
42
|
+
now: options.now ?? Date.now,
|
|
43
|
+
});
|
|
44
|
+
this.now = options.now ?? Date.now;
|
|
45
|
+
this.onConnectionChange = options.onConnectionChange;
|
|
46
|
+
this.onStaleToken = options.onStaleToken;
|
|
47
|
+
this.nextLongPollTimeoutMs = options.longPollTimeoutMs;
|
|
48
|
+
}
|
|
49
|
+
/** Start the monitor loop. Best-effort notifystart first. */
|
|
50
|
+
async start() {
|
|
51
|
+
if (this.running)
|
|
52
|
+
return;
|
|
53
|
+
this.running = true;
|
|
54
|
+
try {
|
|
55
|
+
await this.client.notifyStart();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// best effort
|
|
59
|
+
}
|
|
60
|
+
this.loop = this.runLoop(this.ctx.signal);
|
|
61
|
+
}
|
|
62
|
+
/** Abort the loop and best-effort notifystop. */
|
|
63
|
+
async stop() {
|
|
64
|
+
if (!this.running)
|
|
65
|
+
return;
|
|
66
|
+
this.running = false;
|
|
67
|
+
try {
|
|
68
|
+
await this.client.notifyStop();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// best effort
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Wait for the current loop iteration to finish (used by tests/teardown). */
|
|
75
|
+
async join() {
|
|
76
|
+
if (this.loop)
|
|
77
|
+
await this.loop.catch(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
async runLoop(signal) {
|
|
80
|
+
let attempt = 0;
|
|
81
|
+
let cursor = await this.cursor.load().catch(() => '');
|
|
82
|
+
this.onConnectionChange?.('connected');
|
|
83
|
+
while (this.running && !signal.aborted) {
|
|
84
|
+
try {
|
|
85
|
+
const result = await this.client.getUpdates({
|
|
86
|
+
getUpdatesBuf: cursor,
|
|
87
|
+
longPollTimeoutMs: this.nextLongPollTimeoutMs,
|
|
88
|
+
signal,
|
|
89
|
+
});
|
|
90
|
+
const t = result.nextLongPollTimeoutMs;
|
|
91
|
+
if (typeof t === 'number' && t > 0) {
|
|
92
|
+
this.nextLongPollTimeoutMs = t;
|
|
93
|
+
}
|
|
94
|
+
attempt = 0;
|
|
95
|
+
this.onConnectionChange?.('connected');
|
|
96
|
+
// Process each message; then commit the cursor AFTER the round.
|
|
97
|
+
if (result.msgs && result.msgs.length > 0) {
|
|
98
|
+
for (const msg of result.msgs) {
|
|
99
|
+
if (signal.aborted)
|
|
100
|
+
break;
|
|
101
|
+
await this.handleMessage(msg);
|
|
102
|
+
}
|
|
103
|
+
// Persist only after the inbound round is processed (§18 / R1).
|
|
104
|
+
if (result.get_updates_buf !== undefined) {
|
|
105
|
+
cursor = await this.commitCursor(result.get_updates_buf);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// A client-side long-poll timeout is normal control flow; retry.
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (signal.aborted || !this.running)
|
|
112
|
+
break;
|
|
113
|
+
if (error instanceof StaleTokenError) {
|
|
114
|
+
this.running = false;
|
|
115
|
+
await this.onStaleToken?.(error);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
this.onConnectionChange?.('reconnecting');
|
|
119
|
+
attempt += 1;
|
|
120
|
+
if (!this.reconnect.enabled)
|
|
121
|
+
break;
|
|
122
|
+
const delay = Math.min(this.reconnect.baseDelayMs * 2 ** Math.min(attempt - 1, 8), this.reconnect.maxDelayMs);
|
|
123
|
+
this.ctx.logger.warn('[channel-weixin] getUpdates error; retry in ' + delay + 'ms', error);
|
|
124
|
+
await sleep(delay, signal);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.onConnectionChange?.('disconnected');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Persist the new cursor. The local cursor is advanced only after the
|
|
131
|
+
* durable write succeeds; on failure a CursorCommitError is thrown so the
|
|
132
|
+
* loop retries with the previous cursor instead of silently diverging.
|
|
133
|
+
*/
|
|
134
|
+
async commitCursor(next) {
|
|
135
|
+
try {
|
|
136
|
+
await this.cursor.set(next);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
throw new CursorCommitError(next, { cause: error });
|
|
140
|
+
}
|
|
141
|
+
return next;
|
|
142
|
+
}
|
|
143
|
+
async handleMessage(msg) {
|
|
144
|
+
const key = dedupKey(msg);
|
|
145
|
+
if (await this.dedup.has(key)) {
|
|
146
|
+
this.ctx.logger.debug("[channel-weixin] dropped duplicate message '" + key + "'");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Capture the latest peer reply context before emit (idempotent on replay).
|
|
150
|
+
if (msg.context_token && msg.from_user_id) {
|
|
151
|
+
await this.contextTokens.set(msg.from_user_id, msg.context_token);
|
|
152
|
+
}
|
|
153
|
+
const event = mapInbound(msg, this.meta);
|
|
154
|
+
await this.beforeEmit?.(event);
|
|
155
|
+
// The summary must precede ctx.emit so diagnostics survive downstream
|
|
156
|
+
// failures. It contains only stable identifiers and sanitized part facts.
|
|
157
|
+
this.ctx.logger.info(`[channel-weixin] inbound message ${event.message.id} from ${event.sender.id} in ${event.conversation.id}`, { parts: summarizeInboundParts(event.message.content) });
|
|
158
|
+
await this.emitMsg(event);
|
|
159
|
+
// Only after a successful emit is the message a committed dedup entry.
|
|
160
|
+
await this.dedup.commit(key);
|
|
161
|
+
}
|
|
162
|
+
/** Current long-poll timeout (dynamic). */
|
|
163
|
+
get longPollTimeout() {
|
|
164
|
+
return this.nextLongPollTimeoutMs;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Compact per-part summary for inbound message logs (debug diagnostics). */
|
|
168
|
+
export function summarizeInboundParts(parts) {
|
|
169
|
+
return parts.map((part) => {
|
|
170
|
+
switch (part.type) {
|
|
171
|
+
case 'text':
|
|
172
|
+
return { type: 'text', length: part.text.length };
|
|
173
|
+
case 'image':
|
|
174
|
+
return {
|
|
175
|
+
type: 'image',
|
|
176
|
+
resourceRef: part.resourceRef,
|
|
177
|
+
mimeType: part.mimeType,
|
|
178
|
+
localDataBytes: part.localData?.byteLength,
|
|
179
|
+
ingressFailure: part.ingressFailure,
|
|
180
|
+
};
|
|
181
|
+
case 'file':
|
|
182
|
+
return {
|
|
183
|
+
type: 'file',
|
|
184
|
+
name: part.name,
|
|
185
|
+
size: part.size,
|
|
186
|
+
mimeType: part.mimeType,
|
|
187
|
+
localDataBytes: part.localData?.byteLength,
|
|
188
|
+
ingressFailure: part.ingressFailure,
|
|
189
|
+
};
|
|
190
|
+
case 'audio':
|
|
191
|
+
return {
|
|
192
|
+
type: 'audio',
|
|
193
|
+
resourceRef: part.resourceRef,
|
|
194
|
+
name: part.name,
|
|
195
|
+
mimeType: part.mimeType,
|
|
196
|
+
size: part.size,
|
|
197
|
+
durationMs: part.durationMs,
|
|
198
|
+
localDataBytes: part.localData?.byteLength,
|
|
199
|
+
ingressFailure: part.ingressFailure,
|
|
200
|
+
};
|
|
201
|
+
case 'video':
|
|
202
|
+
return {
|
|
203
|
+
type: 'video',
|
|
204
|
+
resourceRef: part.resourceRef,
|
|
205
|
+
name: part.name,
|
|
206
|
+
mimeType: part.mimeType,
|
|
207
|
+
size: part.size,
|
|
208
|
+
durationMs: part.durationMs,
|
|
209
|
+
localDataBytes: part.localData?.byteLength,
|
|
210
|
+
ingressFailure: part.ingressFailure,
|
|
211
|
+
};
|
|
212
|
+
default:
|
|
213
|
+
return { type: part.type };
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function sleep(ms, signal) {
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
if (signal.aborted) {
|
|
220
|
+
resolve();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const timer = setTimeout(() => {
|
|
224
|
+
signal.removeEventListener('abort', onAbort);
|
|
225
|
+
resolve();
|
|
226
|
+
}, ms);
|
|
227
|
+
const onAbort = () => {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
resolve();
|
|
230
|
+
};
|
|
231
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/messaging/monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MessagePart } from '@krischoichoi/channel-core';
|
|
2
|
+
import type { ILinkRefMessage } from '../ilink/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Preserve a Weixin reference as explicit model-visible context. iLink does
|
|
5
|
+
* not expose a stable referenced Channel message id, so this must not create a
|
|
6
|
+
* synthetic replyTo relationship.
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatQuotedContext(ref: ILinkRefMessage | undefined): MessagePart[];
|
|
9
|
+
//# sourceMappingURL=quote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote.d.ts","sourceRoot":"","sources":["../../src/messaging/quote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,SAAS,GAAG,WAAW,EAAE,CAQnF"}
|