metame-cli 1.5.13 → 1.5.15
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 +38 -2
- package/index.js +70 -76
- package/package.json +4 -1
- package/scripts/daemon-admin-commands.js +149 -0
- package/scripts/daemon-bridges.js +15 -1
- package/scripts/daemon-default.yaml +10 -0
- package/scripts/daemon-utils.js +1 -0
- package/scripts/daemon-weixin-api.js +266 -0
- package/scripts/daemon-weixin-auth.js +188 -0
- package/scripts/daemon-weixin-bridge.js +308 -0
- package/scripts/daemon.js +12 -4
- package/scripts/docs/hook-config.md +4 -2
- package/scripts/docs/maintenance-manual.md +4 -1
- package/scripts/docs/pointer-map.md +4 -1
- package/scripts/hooks/intent-weixin-bridge.js +40 -0
- package/scripts/intent-registry.js +2 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
|
|
6
|
+
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35000;
|
|
7
|
+
const DEFAULT_API_TIMEOUT_MS = 15000;
|
|
8
|
+
const DEFAULT_QR_POLL_TIMEOUT_MS = 35000;
|
|
9
|
+
|
|
10
|
+
function ensureTrailingSlash(url) {
|
|
11
|
+
const text = String(url || '').trim();
|
|
12
|
+
return text.endsWith('/') ? text : `${text}/`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function randomWechatUin() {
|
|
16
|
+
const uint32 = crypto.randomBytes(4).readUInt32BE(0);
|
|
17
|
+
return Buffer.from(String(uint32), 'utf8').toString('base64');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function generateClientId() {
|
|
21
|
+
return `metame-weixin-${Date.now().toString(36)}-${crypto.randomBytes(6).toString('hex')}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildHeaders(body, opts = {}) {
|
|
25
|
+
const headers = {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'Content-Length': String(Buffer.byteLength(body || '', 'utf8')),
|
|
28
|
+
'X-WECHAT-UIN': randomWechatUin(),
|
|
29
|
+
};
|
|
30
|
+
if (opts.token) {
|
|
31
|
+
headers.AuthorizationType = 'ilink_bot_token';
|
|
32
|
+
headers.Authorization = `Bearer ${String(opts.token).trim()}`;
|
|
33
|
+
}
|
|
34
|
+
if (opts.routeTag !== undefined && opts.routeTag !== null && String(opts.routeTag).trim()) {
|
|
35
|
+
headers.SKRouteTag = String(opts.routeTag).trim();
|
|
36
|
+
}
|
|
37
|
+
if (opts.extraHeaders && typeof opts.extraHeaders === 'object') {
|
|
38
|
+
Object.assign(headers, opts.extraHeaders);
|
|
39
|
+
}
|
|
40
|
+
return headers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createAbortSignal(timeoutMs) {
|
|
44
|
+
if (!(timeoutMs > 0)) return { signal: undefined, cancel: () => {} };
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
47
|
+
return {
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
cancel: () => clearTimeout(timer),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createWeixinApiClient(deps = {}) {
|
|
54
|
+
const fetchImpl = deps.fetchImpl || global.fetch;
|
|
55
|
+
const log = typeof deps.log === 'function' ? deps.log : () => {};
|
|
56
|
+
const baseInfo = deps.baseInfo || { channel_version: 'metame-weixin-bridge' };
|
|
57
|
+
|
|
58
|
+
if (typeof fetchImpl !== 'function') {
|
|
59
|
+
throw new Error('fetch implementation is required');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function requestJson(params) {
|
|
63
|
+
const baseUrl = String(params.baseUrl || DEFAULT_BASE_URL).trim();
|
|
64
|
+
const endpoint = String(params.endpoint || '').replace(/^\/+/, '');
|
|
65
|
+
const body = JSON.stringify(params.body || {});
|
|
66
|
+
const url = new URL(endpoint, ensureTrailingSlash(baseUrl)).toString();
|
|
67
|
+
const aborter = createAbortSignal(params.timeoutMs);
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetchImpl(url, {
|
|
70
|
+
method: params.method || 'POST',
|
|
71
|
+
headers: buildHeaders(body, {
|
|
72
|
+
token: params.token,
|
|
73
|
+
routeTag: params.routeTag,
|
|
74
|
+
extraHeaders: params.extraHeaders,
|
|
75
|
+
}),
|
|
76
|
+
body,
|
|
77
|
+
signal: aborter.signal,
|
|
78
|
+
});
|
|
79
|
+
const rawText = await response.text();
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = rawText ? JSON.parse(rawText) : {};
|
|
83
|
+
} catch {
|
|
84
|
+
parsed = { rawText };
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const err = new Error(`${params.label || endpoint} failed: ${response.status} ${response.statusText}`);
|
|
88
|
+
err.status = response.status;
|
|
89
|
+
err.response = parsed;
|
|
90
|
+
err.rawText = rawText;
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
return { response, data: parsed, rawText };
|
|
94
|
+
} finally {
|
|
95
|
+
aborter.cancel();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function getBotQrCode(params = {}) {
|
|
100
|
+
const baseUrl = String(params.baseUrl || DEFAULT_BASE_URL).trim();
|
|
101
|
+
const botType = String(params.botType || '3').trim();
|
|
102
|
+
const url = new URL(`ilink/bot/get_bot_qrcode?bot_type=${encodeURIComponent(botType)}`, ensureTrailingSlash(baseUrl)).toString();
|
|
103
|
+
const aborter = createAbortSignal(params.timeoutMs || DEFAULT_API_TIMEOUT_MS);
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetchImpl(url, {
|
|
106
|
+
headers: params.routeTag ? { SKRouteTag: String(params.routeTag).trim() } : {},
|
|
107
|
+
signal: aborter.signal,
|
|
108
|
+
});
|
|
109
|
+
const rawText = await response.text();
|
|
110
|
+
const data = rawText ? JSON.parse(rawText) : {};
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const err = new Error(`get_bot_qrcode failed: ${response.status} ${response.statusText}`);
|
|
113
|
+
err.status = response.status;
|
|
114
|
+
err.response = data;
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
} finally {
|
|
119
|
+
aborter.cancel();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function getQrCodeStatus(params = {}) {
|
|
124
|
+
const baseUrl = String(params.baseUrl || DEFAULT_BASE_URL).trim();
|
|
125
|
+
const qrcode = String(params.qrcode || '').trim();
|
|
126
|
+
const url = new URL(`ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, ensureTrailingSlash(baseUrl)).toString();
|
|
127
|
+
const aborter = createAbortSignal(params.timeoutMs || DEFAULT_QR_POLL_TIMEOUT_MS);
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetchImpl(url, {
|
|
130
|
+
headers: {
|
|
131
|
+
'iLink-App-ClientVersion': '1',
|
|
132
|
+
...(params.routeTag ? { SKRouteTag: String(params.routeTag).trim() } : {}),
|
|
133
|
+
},
|
|
134
|
+
signal: aborter.signal,
|
|
135
|
+
});
|
|
136
|
+
const rawText = await response.text();
|
|
137
|
+
const data = rawText ? JSON.parse(rawText) : {};
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
const err = new Error(`get_qrcode_status failed: ${response.status} ${response.statusText}`);
|
|
140
|
+
err.status = response.status;
|
|
141
|
+
err.response = data;
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
return data;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err && err.name === 'AbortError') return { status: 'wait' };
|
|
147
|
+
throw err;
|
|
148
|
+
} finally {
|
|
149
|
+
aborter.cancel();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function getUpdates(params = {}) {
|
|
154
|
+
try {
|
|
155
|
+
const result = await requestJson({
|
|
156
|
+
label: 'getUpdates',
|
|
157
|
+
baseUrl: params.baseUrl,
|
|
158
|
+
endpoint: 'ilink/bot/getupdates',
|
|
159
|
+
token: params.token,
|
|
160
|
+
routeTag: params.routeTag,
|
|
161
|
+
timeoutMs: params.timeoutMs || DEFAULT_LONG_POLL_TIMEOUT_MS,
|
|
162
|
+
body: {
|
|
163
|
+
get_updates_buf: params.getUpdatesBuf || '',
|
|
164
|
+
base_info: baseInfo,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
return result.data;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if (err && err.name === 'AbortError') {
|
|
170
|
+
return { ret: 0, msgs: [], get_updates_buf: params.getUpdatesBuf || '' };
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function sendTextMessage(params = {}) {
|
|
177
|
+
if (!String(params.contextToken || '').trim()) {
|
|
178
|
+
throw new Error('contextToken is required');
|
|
179
|
+
}
|
|
180
|
+
const text = String(params.text || '');
|
|
181
|
+
const clientId = String(params.clientId || generateClientId()).trim();
|
|
182
|
+
const result = await requestJson({
|
|
183
|
+
label: 'sendMessage',
|
|
184
|
+
baseUrl: params.baseUrl,
|
|
185
|
+
endpoint: 'ilink/bot/sendmessage',
|
|
186
|
+
token: params.token,
|
|
187
|
+
routeTag: params.routeTag,
|
|
188
|
+
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
189
|
+
body: {
|
|
190
|
+
msg: {
|
|
191
|
+
from_user_id: '',
|
|
192
|
+
to_user_id: String(params.toUserId || '').trim(),
|
|
193
|
+
client_id: clientId,
|
|
194
|
+
message_type: 2,
|
|
195
|
+
message_state: 2,
|
|
196
|
+
context_token: String(params.contextToken).trim(),
|
|
197
|
+
item_list: [
|
|
198
|
+
{
|
|
199
|
+
type: 1,
|
|
200
|
+
text_item: { text },
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
base_info: baseInfo,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
return result.data;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function getConfig(params = {}) {
|
|
211
|
+
const result = await requestJson({
|
|
212
|
+
label: 'getConfig',
|
|
213
|
+
baseUrl: params.baseUrl,
|
|
214
|
+
endpoint: 'ilink/bot/getconfig',
|
|
215
|
+
token: params.token,
|
|
216
|
+
routeTag: params.routeTag,
|
|
217
|
+
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
218
|
+
body: {
|
|
219
|
+
ilink_user_id: String(params.userId || '').trim(),
|
|
220
|
+
context_token: params.contextToken ? String(params.contextToken).trim() : undefined,
|
|
221
|
+
base_info: baseInfo,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
return result.data;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function sendTyping(params = {}) {
|
|
228
|
+
const result = await requestJson({
|
|
229
|
+
label: 'sendTyping',
|
|
230
|
+
baseUrl: params.baseUrl,
|
|
231
|
+
endpoint: 'ilink/bot/sendtyping',
|
|
232
|
+
token: params.token,
|
|
233
|
+
routeTag: params.routeTag,
|
|
234
|
+
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
235
|
+
body: {
|
|
236
|
+
ilink_user_id: String(params.userId || '').trim(),
|
|
237
|
+
typing_ticket: String(params.typingTicket || '').trim(),
|
|
238
|
+
status: params.status,
|
|
239
|
+
base_info: baseInfo,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
return result.data;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
DEFAULT_BASE_URL,
|
|
247
|
+
getBotQrCode,
|
|
248
|
+
getQrCodeStatus,
|
|
249
|
+
getUpdates,
|
|
250
|
+
sendTextMessage,
|
|
251
|
+
getConfig,
|
|
252
|
+
sendTyping,
|
|
253
|
+
_requestJson: requestJson,
|
|
254
|
+
_log: log,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = {
|
|
259
|
+
DEFAULT_BASE_URL,
|
|
260
|
+
DEFAULT_LONG_POLL_TIMEOUT_MS,
|
|
261
|
+
DEFAULT_API_TIMEOUT_MS,
|
|
262
|
+
DEFAULT_QR_POLL_TIMEOUT_MS,
|
|
263
|
+
createWeixinApiClient,
|
|
264
|
+
ensureTrailingSlash,
|
|
265
|
+
generateClientId,
|
|
266
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const { DEFAULT_BASE_URL } = require('./daemon-weixin-api');
|
|
8
|
+
|
|
9
|
+
function createWeixinAuthStore(deps = {}) {
|
|
10
|
+
const HOME = deps.HOME || os.homedir();
|
|
11
|
+
const fsMod = deps.fs || fs;
|
|
12
|
+
const pathMod = deps.path || path;
|
|
13
|
+
const apiClient = deps.apiClient;
|
|
14
|
+
const log = typeof deps.log === 'function' ? deps.log : () => {};
|
|
15
|
+
const now = typeof deps.now === 'function' ? deps.now : () => new Date();
|
|
16
|
+
const sleep = typeof deps.sleep === 'function' ? deps.sleep : (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
17
|
+
|
|
18
|
+
if (!apiClient) throw new Error('apiClient is required');
|
|
19
|
+
|
|
20
|
+
const baseDir = pathMod.join(HOME, '.metame', 'weixin');
|
|
21
|
+
const accountsDir = pathMod.join(baseDir, 'accounts');
|
|
22
|
+
const sessionsDir = pathMod.join(baseDir, 'sessions');
|
|
23
|
+
const indexFile = pathMod.join(baseDir, 'accounts.json');
|
|
24
|
+
|
|
25
|
+
function ensureDirs() {
|
|
26
|
+
fsMod.mkdirSync(accountsDir, { recursive: true });
|
|
27
|
+
fsMod.mkdirSync(sessionsDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeAccountId(raw) {
|
|
31
|
+
return String(raw || '').trim().replace(/[^a-zA-Z0-9._@-]/g, '-');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeSessionKey(raw) {
|
|
35
|
+
const value = String(raw || '').trim();
|
|
36
|
+
if (!value) throw new Error('sessionKey is required');
|
|
37
|
+
if (!/^[a-zA-Z0-9._-]{1,80}$/.test(value)) {
|
|
38
|
+
throw new Error('invalid sessionKey: use 1-80 chars from [a-zA-Z0-9._-]');
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function sessionPath(sessionKey) {
|
|
44
|
+
return pathMod.join(sessionsDir, `${normalizeSessionKey(sessionKey)}.json`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function accountPath(accountId) {
|
|
48
|
+
return pathMod.join(accountsDir, `${normalizeAccountId(accountId)}.json`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readJson(filePath, fallback = null) {
|
|
52
|
+
try {
|
|
53
|
+
if (!fsMod.existsSync(filePath)) return fallback;
|
|
54
|
+
return JSON.parse(fsMod.readFileSync(filePath, 'utf8'));
|
|
55
|
+
} catch {
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeJson(filePath, value) {
|
|
61
|
+
ensureDirs();
|
|
62
|
+
fsMod.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
|
|
63
|
+
try {
|
|
64
|
+
fsMod.chmodSync(filePath, 0o600);
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listAccounts() {
|
|
69
|
+
const ids = readJson(indexFile, []);
|
|
70
|
+
return Array.isArray(ids) ? ids.filter(Boolean) : [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function registerAccountId(accountId) {
|
|
74
|
+
const normalized = normalizeAccountId(accountId);
|
|
75
|
+
const next = listAccounts();
|
|
76
|
+
if (!next.includes(normalized)) next.push(normalized);
|
|
77
|
+
writeJson(indexFile, next);
|
|
78
|
+
return normalized;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function loadAccount(accountId) {
|
|
82
|
+
if (!accountId) return null;
|
|
83
|
+
return readJson(accountPath(accountId), null);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function saveAccount(accountId, payload) {
|
|
87
|
+
const normalized = registerAccountId(accountId);
|
|
88
|
+
const next = {
|
|
89
|
+
...(loadAccount(normalized) || {}),
|
|
90
|
+
...payload,
|
|
91
|
+
accountId: normalized,
|
|
92
|
+
savedAt: now().toISOString(),
|
|
93
|
+
};
|
|
94
|
+
writeJson(accountPath(normalized), next);
|
|
95
|
+
try {
|
|
96
|
+
fsMod.chmodSync(accountPath(normalized), 0o600);
|
|
97
|
+
} catch {}
|
|
98
|
+
return next;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function loadSession(sessionKey) {
|
|
102
|
+
return readJson(sessionPath(sessionKey), null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function saveSession(sessionKey, payload) {
|
|
106
|
+
const normalized = normalizeSessionKey(sessionKey);
|
|
107
|
+
const next = {
|
|
108
|
+
...(loadSession(normalized) || {}),
|
|
109
|
+
...payload,
|
|
110
|
+
sessionKey: normalized,
|
|
111
|
+
savedAt: now().toISOString(),
|
|
112
|
+
};
|
|
113
|
+
writeJson(sessionPath(normalized), next);
|
|
114
|
+
return next;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function startQrLogin(params = {}) {
|
|
118
|
+
const sessionKey = normalizeSessionKey(params.sessionKey || crypto.randomUUID());
|
|
119
|
+
const baseUrl = String(params.baseUrl || DEFAULT_BASE_URL).trim();
|
|
120
|
+
const botType = String(params.botType || '3').trim();
|
|
121
|
+
const routeTag = params.routeTag === undefined || params.routeTag === null ? null : String(params.routeTag).trim();
|
|
122
|
+
const qr = await apiClient.getBotQrCode({ baseUrl, botType, routeTag });
|
|
123
|
+
const session = saveSession(sessionKey, {
|
|
124
|
+
baseUrl,
|
|
125
|
+
botType,
|
|
126
|
+
routeTag,
|
|
127
|
+
qrcode: qr.qrcode,
|
|
128
|
+
qrcodeUrl: qr.qrcode_img_content,
|
|
129
|
+
startedAt: now().toISOString(),
|
|
130
|
+
rawStartResponse: qr,
|
|
131
|
+
});
|
|
132
|
+
log('INFO', `[WEIXIN] QR login started session=${sessionKey} bot_type=${botType}`);
|
|
133
|
+
return session;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function waitForQrLogin(params = {}) {
|
|
137
|
+
const sessionKey = normalizeSessionKey(params.sessionKey || '');
|
|
138
|
+
const session = loadSession(sessionKey);
|
|
139
|
+
if (!session || !session.qrcode) throw new Error(`weixin session not found: ${sessionKey}`);
|
|
140
|
+
const timeoutMs = Math.max(Number(params.timeoutMs || 480000), 1000);
|
|
141
|
+
const deadline = Date.now() + timeoutMs;
|
|
142
|
+
while (Date.now() < deadline) {
|
|
143
|
+
const status = await apiClient.getQrCodeStatus({
|
|
144
|
+
baseUrl: session.baseUrl,
|
|
145
|
+
qrcode: session.qrcode,
|
|
146
|
+
routeTag: session.routeTag,
|
|
147
|
+
});
|
|
148
|
+
saveSession(sessionKey, { lastStatus: status.status || null, status });
|
|
149
|
+
if (status.status === 'confirmed') {
|
|
150
|
+
const accountId = normalizeAccountId(status.ilink_bot_id || '');
|
|
151
|
+
const account = saveAccount(accountId, {
|
|
152
|
+
token: status.bot_token,
|
|
153
|
+
baseUrl: status.baseurl || session.baseUrl,
|
|
154
|
+
userId: status.ilink_user_id || '',
|
|
155
|
+
botType: session.botType,
|
|
156
|
+
routeTag: session.routeTag,
|
|
157
|
+
linkedAt: now().toISOString(),
|
|
158
|
+
});
|
|
159
|
+
saveSession(sessionKey, { confirmedAt: now().toISOString(), accountId, status });
|
|
160
|
+
return { connected: true, account, status };
|
|
161
|
+
}
|
|
162
|
+
if (status.status === 'expired') {
|
|
163
|
+
return { connected: false, expired: true, status };
|
|
164
|
+
}
|
|
165
|
+
await sleep(params.pollIntervalMs || 1000);
|
|
166
|
+
}
|
|
167
|
+
return { connected: false, timeout: true };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
baseDir,
|
|
172
|
+
accountsDir,
|
|
173
|
+
sessionsDir,
|
|
174
|
+
listAccounts,
|
|
175
|
+
loadAccount,
|
|
176
|
+
saveAccount,
|
|
177
|
+
loadSession,
|
|
178
|
+
saveSession,
|
|
179
|
+
startQrLogin,
|
|
180
|
+
waitForQrLogin,
|
|
181
|
+
normalizeAccountId,
|
|
182
|
+
normalizeSessionKey,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
createWeixinAuthStore,
|
|
188
|
+
};
|