@dsh-overdrive/gateway 0.1.0 → 0.1.2
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/adapter.d.ts +33 -0
- package/dist/adapter.js +2 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/cli.d.ts +16 -0
- package/dist/adapters/cli.js +34 -0
- package/dist/adapters/cli.js.map +1 -0
- package/dist/adapters/dingtalk.d.ts +40 -0
- package/dist/adapters/dingtalk.js +93 -0
- package/dist/adapters/dingtalk.js.map +1 -0
- package/dist/adapters/discord.d.ts +38 -0
- package/dist/adapters/discord.js +88 -0
- package/dist/adapters/discord.js.map +1 -0
- package/dist/adapters/feishu.d.ts +43 -0
- package/dist/adapters/feishu.js +107 -0
- package/dist/adapters/feishu.js.map +1 -0
- package/dist/adapters/slack.d.ts +35 -0
- package/dist/adapters/slack.js +83 -0
- package/dist/adapters/slack.js.map +1 -0
- package/dist/adapters/telegram.d.ts +57 -0
- package/dist/adapters/telegram.js +94 -0
- package/dist/adapters/telegram.js.map +1 -0
- package/dist/adapters/wecom.d.ts +42 -0
- package/dist/adapters/wecom.js +177 -0
- package/dist/adapters/wecom.js.map +1 -0
- package/dist/adapters/whatsapp.d.ts +81 -0
- package/dist/adapters/whatsapp.js +193 -0
- package/dist/adapters/whatsapp.js.map +1 -0
- package/dist/commands.d.ts +18 -0
- package/dist/commands.js +29 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/dist/session.d.ts +10 -0
- package/dist/session.js +15 -0
- package/dist/session.js.map +1 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +265 -0
- package/dist/setup.js.map +1 -0
- package/dist/status.d.ts +22 -0
- package/dist/status.js +47 -0
- package/dist/status.js.map +1 -0
- package/dist/trajectory.d.ts +10 -0
- package/dist/trajectory.js +43 -0
- package/dist/trajectory.js.map +1 -0
- package/package.json +7 -3
- package/src/setup.ts +252 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/adapters/slack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Adapter, NormalizedMessage, OutboundButton, OutboundPayload } from '../adapter.js';
|
|
2
|
+
export interface RawTelegramMessage {
|
|
3
|
+
chat?: {
|
|
4
|
+
id?: number | string;
|
|
5
|
+
};
|
|
6
|
+
from?: {
|
|
7
|
+
id?: number | string;
|
|
8
|
+
};
|
|
9
|
+
message?: {
|
|
10
|
+
text?: string;
|
|
11
|
+
caption?: string;
|
|
12
|
+
photo?: Array<{
|
|
13
|
+
file_id?: string;
|
|
14
|
+
}>;
|
|
15
|
+
voice?: {
|
|
16
|
+
file_id?: string;
|
|
17
|
+
};
|
|
18
|
+
audio?: {
|
|
19
|
+
file_id?: string;
|
|
20
|
+
};
|
|
21
|
+
video?: {
|
|
22
|
+
file_id?: string;
|
|
23
|
+
};
|
|
24
|
+
document?: {
|
|
25
|
+
file_id?: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** 纯函数:取 photo 数组最后一张(最大尺寸)的 file_id;无图返回 undefined。 */
|
|
30
|
+
export declare function telegramPhotoFileId(photo: Array<{
|
|
31
|
+
file_id?: string;
|
|
32
|
+
}>): string | undefined;
|
|
33
|
+
/** 纯函数:Telegram 文件下载 URL 模板。file_path 需 getFile(file_id) 换取(真实调用在 adapter 薄层)。 */
|
|
34
|
+
export declare function telegramImageUrl(token: string, filePath: string): string;
|
|
35
|
+
export declare function normalizeTelegramMessage(raw: RawTelegramMessage): NormalizedMessage | null;
|
|
36
|
+
export declare function buttonRows(buttons: OutboundButton[]): Array<[string, string]>;
|
|
37
|
+
export interface TelegramAdapterOptions {
|
|
38
|
+
token: string;
|
|
39
|
+
}
|
|
40
|
+
export declare class TelegramAdapter implements Adapter {
|
|
41
|
+
readonly id = "telegram";
|
|
42
|
+
private readonly bot;
|
|
43
|
+
private readonly token;
|
|
44
|
+
private connected;
|
|
45
|
+
private messageCb?;
|
|
46
|
+
private replyCb?;
|
|
47
|
+
constructor(opts: TelegramAdapterOptions);
|
|
48
|
+
connect(): Promise<void>;
|
|
49
|
+
/** 薄层:photo → getFile(file_id) 换 file_path → 下载 URL 填充 msg.media.url(纯函数只做模板)。 */
|
|
50
|
+
private handleMessage;
|
|
51
|
+
send(chatId: string, payload: OutboundPayload): Promise<void>;
|
|
52
|
+
onMessage(cb: (msg: NormalizedMessage) => void): void;
|
|
53
|
+
onReply(cb: (buttonId: string) => void): void;
|
|
54
|
+
status(): {
|
|
55
|
+
connected: boolean;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Bot, InlineKeyboard } from 'grammy';
|
|
2
|
+
/** 纯函数:取 photo 数组最后一张(最大尺寸)的 file_id;无图返回 undefined。 */
|
|
3
|
+
export function telegramPhotoFileId(photo) {
|
|
4
|
+
return photo[photo.length - 1]?.file_id;
|
|
5
|
+
}
|
|
6
|
+
/** 纯函数:Telegram 文件下载 URL 模板。file_path 需 getFile(file_id) 换取(真实调用在 adapter 薄层)。 */
|
|
7
|
+
export function telegramImageUrl(token, filePath) {
|
|
8
|
+
return `https://api.telegram.org/file/bot${token}/${filePath}`;
|
|
9
|
+
}
|
|
10
|
+
export function normalizeTelegramMessage(raw) {
|
|
11
|
+
if (raw.chat?.id === undefined || raw.from?.id === undefined)
|
|
12
|
+
return null;
|
|
13
|
+
const msg = raw.message;
|
|
14
|
+
if (!msg)
|
|
15
|
+
return null;
|
|
16
|
+
const text = msg.text ?? msg.caption ?? '';
|
|
17
|
+
let media;
|
|
18
|
+
if (msg.photo?.length)
|
|
19
|
+
media = { kind: 'image' }; // url 由 adapter getFile 薄层填充
|
|
20
|
+
else if (msg.voice || msg.audio)
|
|
21
|
+
media = { kind: 'voice' };
|
|
22
|
+
else if (msg.video)
|
|
23
|
+
media = { kind: 'video' };
|
|
24
|
+
else if (msg.document)
|
|
25
|
+
media = { kind: 'file' };
|
|
26
|
+
if (!text && !media)
|
|
27
|
+
return null;
|
|
28
|
+
const out = { chatId: String(raw.chat.id), userId: String(raw.from.id), text };
|
|
29
|
+
if (media)
|
|
30
|
+
out.media = media;
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
export function buttonRows(buttons) {
|
|
34
|
+
return buttons.map((b) => [b.label, b.id]);
|
|
35
|
+
}
|
|
36
|
+
export class TelegramAdapter {
|
|
37
|
+
id = 'telegram';
|
|
38
|
+
bot;
|
|
39
|
+
token;
|
|
40
|
+
connected = false;
|
|
41
|
+
messageCb;
|
|
42
|
+
replyCb;
|
|
43
|
+
constructor(opts) {
|
|
44
|
+
this.token = opts.token;
|
|
45
|
+
this.bot = new Bot(opts.token);
|
|
46
|
+
}
|
|
47
|
+
async connect() {
|
|
48
|
+
await this.bot.api.getMe(); // 校验 token
|
|
49
|
+
this.connected = true;
|
|
50
|
+
this.bot.on('message', (ctx) => {
|
|
51
|
+
console.log(`[telegram] 收到更新: chat=${ctx.chat?.id} from=${ctx.from?.id} text="${ctx.message?.text?.slice(0, 40) ?? ''}"`);
|
|
52
|
+
void this.handleMessage(ctx);
|
|
53
|
+
});
|
|
54
|
+
this.bot.on('callback_query:data', async (ctx) => {
|
|
55
|
+
const data = ctx.callbackQuery.data;
|
|
56
|
+
await ctx.answerCallbackQuery().catch(() => undefined);
|
|
57
|
+
this.replyCb?.(data);
|
|
58
|
+
});
|
|
59
|
+
this.bot.catch((err) => console.error('[telegram]', err));
|
|
60
|
+
void this.bot.start(); // 长轮询(自托管无需 webhook)
|
|
61
|
+
}
|
|
62
|
+
/** 薄层:photo → getFile(file_id) 换 file_path → 下载 URL 填充 msg.media.url(纯函数只做模板)。 */
|
|
63
|
+
async handleMessage(raw) {
|
|
64
|
+
const msg = normalizeTelegramMessage(raw);
|
|
65
|
+
if (!msg)
|
|
66
|
+
return;
|
|
67
|
+
const fileId = msg.media?.kind === 'image' ? telegramPhotoFileId(raw.message?.photo ?? []) : undefined;
|
|
68
|
+
if (fileId) {
|
|
69
|
+
try {
|
|
70
|
+
const file = await this.bot.api.getFile(fileId);
|
|
71
|
+
if (file.file_path)
|
|
72
|
+
msg.media.url = telegramImageUrl(this.token, file.file_path);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error('[telegram] getFile 失败:', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
this.messageCb?.(msg);
|
|
79
|
+
}
|
|
80
|
+
async send(chatId, payload) {
|
|
81
|
+
if (payload.buttons?.length) {
|
|
82
|
+
const kb = new InlineKeyboard();
|
|
83
|
+
for (const [label, id] of buttonRows(payload.buttons))
|
|
84
|
+
kb.text(label, id);
|
|
85
|
+
await this.bot.api.sendMessage(chatId, payload.text, { reply_markup: kb });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await this.bot.api.sendMessage(chatId, payload.text);
|
|
89
|
+
}
|
|
90
|
+
onMessage(cb) { this.messageCb = cb; }
|
|
91
|
+
onReply(cb) { this.replyCb = cb; }
|
|
92
|
+
status() { return { connected: this.connected }; }
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=telegram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.js","sourceRoot":"","sources":["../../src/adapters/telegram.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Adapter, NormalizedMessage, OutboundButton, OutboundPayload } from '../adapter.js';
|
|
2
|
+
export declare function deriveAesKey(encodingAESKey: string): Buffer;
|
|
3
|
+
export declare function pkcs7Unpad(buf: Buffer): Buffer;
|
|
4
|
+
export declare function pkcs7Pad(buf: Buffer): Buffer;
|
|
5
|
+
export declare function decryptWeComPayload(encodingAESKey: string, encrypted: string): {
|
|
6
|
+
message: string;
|
|
7
|
+
receiveId: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function encryptWeComPayload(encodingAESKey: string, message: string, receiveId: string): {
|
|
10
|
+
encrypted: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function weComSignature(token: string, timestamp: string, nonce: string, encrypt: string): string;
|
|
13
|
+
export declare function parseWeComXmlMessage(xml: string): NormalizedMessage | null;
|
|
14
|
+
export declare function buildNumberedText(text: string, buttons: OutboundButton[]): string;
|
|
15
|
+
export declare function matchNumberedButton(text: string, buttons: OutboundButton[]): OutboundButton | undefined;
|
|
16
|
+
export interface WeComAdapterOptions {
|
|
17
|
+
corpId: string;
|
|
18
|
+
secret: string;
|
|
19
|
+
agentId: string;
|
|
20
|
+
token: string;
|
|
21
|
+
encodingAESKey: string;
|
|
22
|
+
callbackPort: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class WeComAdapter implements Adapter {
|
|
25
|
+
private readonly opts;
|
|
26
|
+
readonly id = "wecom";
|
|
27
|
+
private server?;
|
|
28
|
+
private connected;
|
|
29
|
+
private messageCb?;
|
|
30
|
+
private replyCb?;
|
|
31
|
+
private readonly pendingButtons;
|
|
32
|
+
constructor(opts: WeComAdapterOptions);
|
|
33
|
+
connect(): Promise<void>;
|
|
34
|
+
private route;
|
|
35
|
+
send(chatId: string, payload: OutboundPayload): Promise<void>;
|
|
36
|
+
private fetchAccessToken;
|
|
37
|
+
onMessage(cb: (msg: NormalizedMessage) => void): void;
|
|
38
|
+
onReply(cb: (buttonId: string) => void): void;
|
|
39
|
+
status(): {
|
|
40
|
+
connected: boolean;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createHash, createDecipheriv, createCipheriv, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
// ── 纯函数:AES-256-CBC 加解密(企业微信协议)──────────────────
|
|
4
|
+
export function deriveAesKey(encodingAESKey) {
|
|
5
|
+
return Buffer.from(encodingAESKey + '=', 'base64'); // 43 位 + '=' → 32 字节
|
|
6
|
+
}
|
|
7
|
+
export function pkcs7Unpad(buf) {
|
|
8
|
+
const pad = buf[buf.length - 1];
|
|
9
|
+
if (pad < 1 || pad > 32)
|
|
10
|
+
throw new Error('invalid pkcs7 padding');
|
|
11
|
+
return buf.subarray(0, buf.length - pad);
|
|
12
|
+
}
|
|
13
|
+
export function pkcs7Pad(buf) {
|
|
14
|
+
const pad = 32 - (buf.length % 32);
|
|
15
|
+
return Buffer.concat([buf, Buffer.alloc(pad, pad)]);
|
|
16
|
+
}
|
|
17
|
+
export function decryptWeComPayload(encodingAESKey, encrypted) {
|
|
18
|
+
const key = deriveAesKey(encodingAESKey);
|
|
19
|
+
const iv = key.subarray(0, 16);
|
|
20
|
+
const decipher = createDecipheriv('aes-256-cbc', key, iv);
|
|
21
|
+
decipher.setAutoPadding(false);
|
|
22
|
+
const plain = Buffer.concat([decipher.update(Buffer.from(encrypted, 'base64')), decipher.final()]);
|
|
23
|
+
const unpadded = pkcs7Unpad(plain);
|
|
24
|
+
// 结构:random(16) + msgLen(4, big-endian) + msg + receiveId
|
|
25
|
+
const msgLen = unpadded.readUInt32BE(16);
|
|
26
|
+
const message = unpadded.subarray(20, 20 + msgLen).toString('utf8');
|
|
27
|
+
const receiveId = unpadded.subarray(20 + msgLen).toString('utf8');
|
|
28
|
+
return { message, receiveId };
|
|
29
|
+
}
|
|
30
|
+
export function encryptWeComPayload(encodingAESKey, message, receiveId) {
|
|
31
|
+
const key = deriveAesKey(encodingAESKey);
|
|
32
|
+
const iv = key.subarray(0, 16);
|
|
33
|
+
const msgBuf = Buffer.from(message, 'utf8');
|
|
34
|
+
const head = Buffer.alloc(20);
|
|
35
|
+
randomBytes(16).copy(head, 0);
|
|
36
|
+
head.writeUInt32BE(msgBuf.length, 16);
|
|
37
|
+
const plain = pkcs7Pad(Buffer.concat([head, msgBuf, Buffer.from(receiveId, 'utf8')]));
|
|
38
|
+
const cipher = createCipheriv('aes-256-cbc', key, iv);
|
|
39
|
+
cipher.setAutoPadding(false);
|
|
40
|
+
const encrypted = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
41
|
+
return { encrypted: encrypted.toString('base64') };
|
|
42
|
+
}
|
|
43
|
+
export function weComSignature(token, timestamp, nonce, encrypt) {
|
|
44
|
+
const arr = [token, timestamp, nonce, encrypt].sort();
|
|
45
|
+
return createHash('sha1').update(arr.join('')).digest('hex');
|
|
46
|
+
}
|
|
47
|
+
// ── 纯函数:XML 消息解析 ─────────────────────────────────────
|
|
48
|
+
export function parseWeComXmlMessage(xml) {
|
|
49
|
+
const get = (tag) => {
|
|
50
|
+
const m = xml.match(new RegExp(`<${tag}><!\\[CDATA\\[([\\s\\S]*?)\\]\\]></${tag}>`));
|
|
51
|
+
return m ? m[1] : '';
|
|
52
|
+
};
|
|
53
|
+
const msgType = get('MsgType');
|
|
54
|
+
if (msgType !== 'text')
|
|
55
|
+
return null;
|
|
56
|
+
const from = get('FromUserName');
|
|
57
|
+
const content = get('Content');
|
|
58
|
+
if (!from || !content)
|
|
59
|
+
return null;
|
|
60
|
+
return { chatId: from, userId: from, text: content };
|
|
61
|
+
}
|
|
62
|
+
// ── 纯函数:审批编号回复 ─────────────────────────────────────
|
|
63
|
+
export function buildNumberedText(text, buttons) {
|
|
64
|
+
if (buttons.length === 0)
|
|
65
|
+
return text;
|
|
66
|
+
const options = buttons.map((b, i) => `${i + 1}) ${b.label}`).join('\n');
|
|
67
|
+
return `${text}\n\n${options}\n\n回复数字选择。`;
|
|
68
|
+
}
|
|
69
|
+
export function matchNumberedButton(text, buttons) {
|
|
70
|
+
const n = Number(text.trim());
|
|
71
|
+
if (!Number.isInteger(n) || n < 1 || n > buttons.length)
|
|
72
|
+
return undefined;
|
|
73
|
+
return buttons[n - 1];
|
|
74
|
+
}
|
|
75
|
+
export class WeComAdapter {
|
|
76
|
+
opts;
|
|
77
|
+
id = 'wecom';
|
|
78
|
+
server;
|
|
79
|
+
connected = false;
|
|
80
|
+
messageCb;
|
|
81
|
+
replyCb;
|
|
82
|
+
pendingButtons = new Map();
|
|
83
|
+
constructor(opts) {
|
|
84
|
+
this.opts = opts;
|
|
85
|
+
}
|
|
86
|
+
async connect() {
|
|
87
|
+
this.server = createServer((req, res) => void this.route(req, res));
|
|
88
|
+
await new Promise((resolve) => this.server.listen(this.opts.callbackPort, '0.0.0.0', () => resolve()));
|
|
89
|
+
this.connected = true;
|
|
90
|
+
console.log(`[wecom] 回调服务器已启动 http://0.0.0.0:${this.opts.callbackPort}(需公网可达并配置为企业微信回调 URL)`);
|
|
91
|
+
}
|
|
92
|
+
async route(req, res) {
|
|
93
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
94
|
+
const params = url.searchParams;
|
|
95
|
+
if (req.method === 'GET') {
|
|
96
|
+
// URL 验证:回显解密后的 echostr
|
|
97
|
+
const msgSignature = params.get('msg_signature') ?? '';
|
|
98
|
+
const timestamp = params.get('timestamp') ?? '';
|
|
99
|
+
const nonce = params.get('nonce') ?? '';
|
|
100
|
+
const echostr = params.get('echostr') ?? '';
|
|
101
|
+
const sign = weComSignature(this.opts.token, timestamp, nonce, echostr);
|
|
102
|
+
if (sign !== msgSignature) {
|
|
103
|
+
res.writeHead(403);
|
|
104
|
+
res.end('invalid signature');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const { message } = decryptWeComPayload(this.opts.encodingAESKey, echostr);
|
|
108
|
+
res.writeHead(200, { 'content-type': 'text/plain' });
|
|
109
|
+
res.end(message);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (req.method === 'POST') {
|
|
113
|
+
const raw = await readBody(req);
|
|
114
|
+
const msgSignature = params.get('msg_signature') ?? '';
|
|
115
|
+
const timestamp = params.get('timestamp') ?? '';
|
|
116
|
+
const nonce = params.get('nonce') ?? '';
|
|
117
|
+
const encrypt = (raw.match(/<Encrypt><!\[CDATA\[([\s\S]*?)\]\]><\/Encrypt>/) ?? [])[1] ?? '';
|
|
118
|
+
const sign = weComSignature(this.opts.token, timestamp, nonce, encrypt);
|
|
119
|
+
if (sign !== msgSignature) {
|
|
120
|
+
res.writeHead(403);
|
|
121
|
+
res.end('invalid signature');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const { message } = decryptWeComPayload(this.opts.encodingAESKey, encrypt);
|
|
125
|
+
const normalized = parseWeComXmlMessage(message);
|
|
126
|
+
res.writeHead(200, { 'content-type': 'text/plain' });
|
|
127
|
+
res.end('success'); // 先应答,避免企业微信重试
|
|
128
|
+
if (!normalized)
|
|
129
|
+
return;
|
|
130
|
+
const pending = this.pendingButtons.get(normalized.chatId);
|
|
131
|
+
if (pending) {
|
|
132
|
+
const button = matchNumberedButton(normalized.text, pending);
|
|
133
|
+
if (button) {
|
|
134
|
+
this.pendingButtons.delete(normalized.chatId);
|
|
135
|
+
this.replyCb?.(button.id);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
this.messageCb?.(normalized);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async send(chatId, payload) {
|
|
143
|
+
if (payload.buttons?.length)
|
|
144
|
+
this.pendingButtons.set(chatId, payload.buttons);
|
|
145
|
+
const text = buildNumberedText(payload.text, payload.buttons ?? []);
|
|
146
|
+
const token = await this.fetchAccessToken();
|
|
147
|
+
const res = await fetch(`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${encodeURIComponent(token)}`, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
touser: chatId,
|
|
151
|
+
msgtype: 'text',
|
|
152
|
+
agentid: this.opts.agentId,
|
|
153
|
+
text: { content: text },
|
|
154
|
+
}),
|
|
155
|
+
headers: { 'content-type': 'application/json' },
|
|
156
|
+
}).then((r) => r.json());
|
|
157
|
+
if (res.errcode !== 0)
|
|
158
|
+
throw new Error(`企业微信发送失败: ${res.errcode} ${res.errmsg}`);
|
|
159
|
+
}
|
|
160
|
+
async fetchAccessToken() {
|
|
161
|
+
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${encodeURIComponent(this.opts.corpId)}&corpsecret=${encodeURIComponent(this.opts.secret)}`;
|
|
162
|
+
const data = (await fetch(url).then((r) => r.json()));
|
|
163
|
+
if (!data.access_token)
|
|
164
|
+
throw new Error(`企业微信 token 获取失败: ${data.errcode}`);
|
|
165
|
+
return data.access_token;
|
|
166
|
+
}
|
|
167
|
+
onMessage(cb) { this.messageCb = cb; }
|
|
168
|
+
onReply(cb) { this.replyCb = cb; }
|
|
169
|
+
status() { return { connected: this.connected }; }
|
|
170
|
+
}
|
|
171
|
+
async function readBody(req) {
|
|
172
|
+
const chunks = [];
|
|
173
|
+
for await (const chunk of req)
|
|
174
|
+
chunks.push(chunk);
|
|
175
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=wecom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wecom.js","sourceRoot":"","sources":["../../src/adapters/wecom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Adapter, NormalizedMessage, OutboundButton, OutboundPayload } from '../adapter.js';
|
|
2
|
+
export interface RawWhatsAppMessage {
|
|
3
|
+
key?: {
|
|
4
|
+
remoteJid?: string;
|
|
5
|
+
participant?: string;
|
|
6
|
+
fromMe?: boolean;
|
|
7
|
+
};
|
|
8
|
+
message?: {
|
|
9
|
+
conversation?: string;
|
|
10
|
+
extendedTextMessage?: {
|
|
11
|
+
text?: string;
|
|
12
|
+
};
|
|
13
|
+
imageMessage?: {
|
|
14
|
+
url?: string;
|
|
15
|
+
directPath?: string;
|
|
16
|
+
caption?: string;
|
|
17
|
+
mimetype?: string;
|
|
18
|
+
};
|
|
19
|
+
audioMessage?: {
|
|
20
|
+
url?: string;
|
|
21
|
+
directPath?: string;
|
|
22
|
+
mimetype?: string;
|
|
23
|
+
};
|
|
24
|
+
videoMessage?: {
|
|
25
|
+
url?: string;
|
|
26
|
+
directPath?: string;
|
|
27
|
+
mimetype?: string;
|
|
28
|
+
caption?: string;
|
|
29
|
+
};
|
|
30
|
+
documentMessage?: {
|
|
31
|
+
url?: string;
|
|
32
|
+
directPath?: string;
|
|
33
|
+
mimetype?: string;
|
|
34
|
+
fileName?: string;
|
|
35
|
+
};
|
|
36
|
+
interactiveResponseMessage?: {
|
|
37
|
+
nativeFlowResponseMessage?: {
|
|
38
|
+
paramsJson?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
} & Record<string, unknown>;
|
|
42
|
+
messageType?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function extractWhatsAppText(raw: RawWhatsAppMessage): string | null;
|
|
45
|
+
/** 纯函数:imageMessage 的下载 URL(Baileys 已解析的 url 优先,退 directPath);无图返回 undefined。 */
|
|
46
|
+
export declare function whatsappImageUrl(raw: RawWhatsAppMessage): string | undefined;
|
|
47
|
+
export declare function normalizeWhatsAppMessage(raw: RawWhatsAppMessage): {
|
|
48
|
+
kind: 'message';
|
|
49
|
+
msg: NormalizedMessage;
|
|
50
|
+
} | null;
|
|
51
|
+
export declare function buildNumberedReply(text: string, buttons: OutboundButton[]): string;
|
|
52
|
+
export declare function matchNumberedReply(text: string, buttons: OutboundButton[]): OutboundButton | undefined;
|
|
53
|
+
export interface NativeFlowButton {
|
|
54
|
+
name: string;
|
|
55
|
+
buttonParamsJson: string;
|
|
56
|
+
}
|
|
57
|
+
/** WhatsApp 原生交互按钮:nativeFlowMessage.buttons 数组({ name: 'quick_reply', buttonParamsJson: JSON({id, display_text}) })。 */
|
|
58
|
+
export declare function buildNativeFlowButtons(buttons: OutboundButton[]): NativeFlowButton[];
|
|
59
|
+
/** 解析原生按钮响应:interactiveResponseMessage.nativeFlowResponseMessage.paramsJson 中的 id;非交互/非法 JSON/缺 id 返回 null。 */
|
|
60
|
+
export declare function parseNativeButtonResponse(raw: RawWhatsAppMessage): string | null;
|
|
61
|
+
export interface WhatsAppAdapterOptions {
|
|
62
|
+
authDir: string;
|
|
63
|
+
}
|
|
64
|
+
export declare class WhatsAppAdapter implements Adapter {
|
|
65
|
+
private readonly opts;
|
|
66
|
+
readonly id = "whatsapp";
|
|
67
|
+
private sock?;
|
|
68
|
+
private connected;
|
|
69
|
+
private messageCb?;
|
|
70
|
+
private replyCb?;
|
|
71
|
+
/** chatId → 当前 pending 按钮(编号回复 → 按钮 id) */
|
|
72
|
+
private readonly pendingButtons;
|
|
73
|
+
constructor(opts: WhatsAppAdapterOptions);
|
|
74
|
+
connect(): Promise<void>;
|
|
75
|
+
send(chatId: string, payload: OutboundPayload): Promise<void>;
|
|
76
|
+
onMessage(cb: (msg: NormalizedMessage) => void): void;
|
|
77
|
+
onReply(cb: (buttonId: string) => void): void;
|
|
78
|
+
status(): {
|
|
79
|
+
connected: boolean;
|
|
80
|
+
};
|
|
81
|
+
}
|