@zhin.js/scaffold-wizard 0.1.8 → 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/README.md +4 -0
- package/lib/adapter-configurers.d.ts +10 -0
- package/lib/adapter-configurers.d.ts.map +1 -1
- package/lib/adapter-configurers.js +436 -71
- package/lib/adapter-configurers.js.map +1 -1
- package/lib/adapter.d.ts +55 -9
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +206 -110
- package/lib/adapter.js.map +1 -1
- package/lib/apply.d.ts +22 -1
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +156 -23
- package/lib/apply.js.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -3
- package/lib/index.js.map +1 -1
- package/lib/project-config-plan.d.ts +2 -0
- package/lib/project-config-plan.d.ts.map +1 -1
- package/lib/project-config-plan.js +54 -15
- package/lib/project-config-plan.js.map +1 -1
- package/lib/weixin-ilink-login.d.ts +33 -0
- package/lib/weixin-ilink-login.d.ts.map +1 -0
- package/lib/weixin-ilink-login.js +90 -0
- package/lib/weixin-ilink-login.js.map +1 -0
- package/lib/zhin-stack-deps.d.ts +9 -1
- package/lib/zhin-stack-deps.d.ts.map +1 -1
- package/lib/zhin-stack-deps.js +26 -10
- package/lib/zhin-stack-deps.js.map +1 -1
- package/package.json +3 -1
- package/src/adapter-configurers.ts +464 -70
- package/src/adapter.ts +224 -112
- package/src/apply.ts +170 -21
- package/src/index.ts +10 -4
- package/src/project-config-plan.ts +55 -15
- package/src/weixin-ilink-login.ts +151 -0
- package/src/zhin-stack-deps.ts +26 -10
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import { IlinkQrLoginError, loginWithIlinkQr } from './weixin-ilink-login.js';
|
|
3
4
|
/** 平台适配器文档索引 */
|
|
4
5
|
export const ADAPTERS_DOCS_URL = 'https://zhin.js.org/adapters/';
|
|
5
6
|
/** @deprecated 使用 ADAPTERS_DOCS_URL */
|
|
@@ -46,18 +47,16 @@ export async function configureTelegramEndpoint(ctx) {
|
|
|
46
47
|
default: 'polling',
|
|
47
48
|
},
|
|
48
49
|
]);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
name: endpointName.trim(),
|
|
52
|
-
token: '${TELEGRAM_TOKEN}',
|
|
50
|
+
// schema:polling / webhook 为顶层共享字段;name / token 为 endpoint 级
|
|
51
|
+
const config = {
|
|
53
52
|
polling: transport === 'polling',
|
|
53
|
+
endpoints: [{ name: endpointName.trim(), token: '${TELEGRAM_TOKEN}' }],
|
|
54
54
|
};
|
|
55
55
|
if (transport === 'webhook') {
|
|
56
56
|
console.log('');
|
|
57
57
|
console.log(chalk.yellow(' Webhook 前置条件:'));
|
|
58
58
|
console.log(chalk.gray(' • Endpoint 进程需绑定可被 Telegram 访问的 HTTPS 域名(有效 TLS 证书)'));
|
|
59
|
-
console.log(chalk.gray(' •
|
|
60
|
-
console.log(chalk.gray(' • 防火墙/反向代理需放行 webhook.port(默认由 Telegraf 监听)'));
|
|
59
|
+
console.log(chalk.gray(' • Webhook 由内置 HTTP Host(zhin runtime start)承接,防火墙/反向代理需放行 http 端口'));
|
|
61
60
|
const webhook = await inquirer.prompt([
|
|
62
61
|
{
|
|
63
62
|
type: 'input',
|
|
@@ -75,29 +74,16 @@ export async function configureTelegramEndpoint(ctx) {
|
|
|
75
74
|
type: 'input',
|
|
76
75
|
name: 'path',
|
|
77
76
|
message: ' Webhook 路径:',
|
|
78
|
-
default: '/telegram
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
type: 'input',
|
|
82
|
-
name: 'port',
|
|
83
|
-
message: ' 本地监听端口(Telegraf webhook server):',
|
|
84
|
-
default: '8443',
|
|
85
|
-
validate: (v) => {
|
|
86
|
-
const p = parseInt(v, 10);
|
|
87
|
-
if (Number.isNaN(p) || p < 1 || p > 65535)
|
|
88
|
-
return '请输入 1–65535 端口';
|
|
89
|
-
return true;
|
|
90
|
-
},
|
|
77
|
+
default: '/telegram/webhook',
|
|
91
78
|
},
|
|
92
79
|
]);
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
config.polling = false;
|
|
81
|
+
config.webhook = {
|
|
95
82
|
domain: webhook.domain.trim(),
|
|
96
83
|
path: webhook.path.trim(),
|
|
97
|
-
port: parseInt(webhook.port, 10),
|
|
98
84
|
};
|
|
99
85
|
}
|
|
100
|
-
return
|
|
86
|
+
return config;
|
|
101
87
|
}
|
|
102
88
|
export async function configureGitHubEndpoint(ctx) {
|
|
103
89
|
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('github')));
|
|
@@ -129,10 +115,11 @@ export async function configureGitHubEndpoint(ctx) {
|
|
|
129
115
|
default: 'github_app',
|
|
130
116
|
},
|
|
131
117
|
]);
|
|
118
|
+
// schema:app_id / private_key / webhook_secret 为 endpoint 级;webhook_path / poll_interval 为顶层共享字段
|
|
132
119
|
const endpointConfig = {
|
|
133
|
-
context: 'github',
|
|
134
120
|
name: endpointName.trim(),
|
|
135
121
|
};
|
|
122
|
+
const sharedConfig = {};
|
|
136
123
|
if (authMode === 'github_app') {
|
|
137
124
|
console.log(chalk.gray(' 在 github.com/settings/apps 创建 App,记录 App ID 并下载 .pem 私钥'));
|
|
138
125
|
const appConfig = await inquirer.prompt([
|
|
@@ -164,7 +151,7 @@ export async function configureGitHubEndpoint(ctx) {
|
|
|
164
151
|
message: ' 事件接收方式:',
|
|
165
152
|
choices: [
|
|
166
153
|
{
|
|
167
|
-
name: 'Webhook
|
|
154
|
+
name: 'Webhook 实时推送(由内置 HTTP Host 承接,需公网 URL)',
|
|
168
155
|
value: 'webhook',
|
|
169
156
|
},
|
|
170
157
|
{
|
|
@@ -178,9 +165,8 @@ export async function configureGitHubEndpoint(ctx) {
|
|
|
178
165
|
if (eventsMode === 'webhook') {
|
|
179
166
|
console.log('');
|
|
180
167
|
console.log(chalk.yellow(' Webhook 前置条件:'));
|
|
181
|
-
console.log(chalk.gray(' •
|
|
182
|
-
console.log(chalk.gray(' • GitHub App Webhook URL 填 https://<你的域名>/
|
|
183
|
-
console.log(chalk.gray(' • /pub/* 路径绕过 Bearer 认证,供 GitHub 回调'));
|
|
168
|
+
console.log(chalk.gray(' • Webhook 由内置 HTTP Host(zhin runtime start 的 http 配置)承接'));
|
|
169
|
+
console.log(chalk.gray(' • GitHub App Webhook URL 填 https://<你的域名>/github/webhook'));
|
|
184
170
|
const webhook = await inquirer.prompt([
|
|
185
171
|
{
|
|
186
172
|
type: 'password',
|
|
@@ -192,12 +178,12 @@ export async function configureGitHubEndpoint(ctx) {
|
|
|
192
178
|
type: 'input',
|
|
193
179
|
name: 'webhook_path',
|
|
194
180
|
message: ' Webhook 路径:',
|
|
195
|
-
default: '/
|
|
181
|
+
default: '/github/webhook',
|
|
196
182
|
},
|
|
197
183
|
]);
|
|
198
184
|
ctx.envVars.GITHUB_WEBHOOK_SECRET = webhook.webhook_secret;
|
|
199
185
|
endpointConfig.webhook_secret = '${GITHUB_WEBHOOK_SECRET}';
|
|
200
|
-
|
|
186
|
+
sharedConfig.webhook_path = webhook.webhook_path.trim();
|
|
201
187
|
}
|
|
202
188
|
else {
|
|
203
189
|
const { poll_interval } = await inquirer.prompt([
|
|
@@ -214,7 +200,7 @@ export async function configureGitHubEndpoint(ctx) {
|
|
|
214
200
|
},
|
|
215
201
|
},
|
|
216
202
|
]);
|
|
217
|
-
|
|
203
|
+
sharedConfig.poll_interval = parseInt(poll_interval, 10);
|
|
218
204
|
}
|
|
219
205
|
const { enableMcp } = await inquirer.prompt([
|
|
220
206
|
{
|
|
@@ -235,7 +221,7 @@ export async function configureGitHubEndpoint(ctx) {
|
|
|
235
221
|
]);
|
|
236
222
|
ctx.envVars.GITHUB_PERSONAL_ACCESS_TOKEN = pat;
|
|
237
223
|
}
|
|
238
|
-
return endpointConfig;
|
|
224
|
+
return { ...sharedConfig, endpoints: [endpointConfig] };
|
|
239
225
|
}
|
|
240
226
|
export async function configureOneBot11Bot(ctx) {
|
|
241
227
|
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('onebot11')));
|
|
@@ -246,7 +232,7 @@ export async function configureOneBot11Bot(ctx) {
|
|
|
246
232
|
message: ' 连接方式:',
|
|
247
233
|
choices: [
|
|
248
234
|
{ name: '正向 WebSocket (ws) — 本 Endpoint 连接 OneBot 实现', value: 'ws' },
|
|
249
|
-
{ name: '反向 WebSocket (wss) — OneBot 实现连本 Bot
|
|
235
|
+
{ name: '反向 WebSocket (wss) — OneBot 实现连本 Bot(由内置 HTTP Host 承接)', value: 'wss' },
|
|
250
236
|
],
|
|
251
237
|
default: 'ws',
|
|
252
238
|
},
|
|
@@ -260,9 +246,8 @@ export async function configureOneBot11Bot(ctx) {
|
|
|
260
246
|
validate: (v) => (v.trim() ? true : '名称不能为空'),
|
|
261
247
|
},
|
|
262
248
|
]);
|
|
249
|
+
// schema:connection 为顶层共享字段;name / url / path / access_token 为 endpoint 级
|
|
263
250
|
const endpointConfig = {
|
|
264
|
-
context: 'onebot11',
|
|
265
|
-
connection,
|
|
266
251
|
name: endpointName.trim(),
|
|
267
252
|
};
|
|
268
253
|
if (connection === 'ws') {
|
|
@@ -278,7 +263,7 @@ export async function configureOneBot11Bot(ctx) {
|
|
|
278
263
|
endpointConfig.url = url.trim();
|
|
279
264
|
}
|
|
280
265
|
else {
|
|
281
|
-
console.log(chalk.gray(' 反向 WS
|
|
266
|
+
console.log(chalk.gray(' 反向 WS 由内置 HTTP Host 承接,OneBot 实现配置连接到此 Bot'));
|
|
282
267
|
const { path: wsPath } = await inquirer.prompt([
|
|
283
268
|
{
|
|
284
269
|
type: 'input',
|
|
@@ -302,7 +287,7 @@ export async function configureOneBot11Bot(ctx) {
|
|
|
302
287
|
ctx.envVars.ONEBOT11_ACCESS_TOKEN = accessToken.trim();
|
|
303
288
|
endpointConfig.access_token = '${ONEBOT11_ACCESS_TOKEN}';
|
|
304
289
|
}
|
|
305
|
-
return endpointConfig;
|
|
290
|
+
return { connection, endpoints: [endpointConfig] };
|
|
306
291
|
}
|
|
307
292
|
export async function configureQQBot(ctx) {
|
|
308
293
|
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('qq')));
|
|
@@ -320,12 +305,6 @@ export async function configureQQBot(ctx) {
|
|
|
320
305
|
message: ' App ID(QQ 开放平台):',
|
|
321
306
|
validate: (v) => (v.trim() ? true : 'App ID 不能为空'),
|
|
322
307
|
},
|
|
323
|
-
{
|
|
324
|
-
type: 'password',
|
|
325
|
-
name: 'token',
|
|
326
|
-
message: ' Token:',
|
|
327
|
-
validate: (v) => (v.trim() ? true : 'Token 不能为空'),
|
|
328
|
-
},
|
|
329
308
|
{
|
|
330
309
|
type: 'password',
|
|
331
310
|
name: 'secret',
|
|
@@ -334,7 +313,6 @@ export async function configureQQBot(ctx) {
|
|
|
334
313
|
},
|
|
335
314
|
]);
|
|
336
315
|
ctx.envVars.QQ_APP_ID = credentials.appID.trim();
|
|
337
|
-
ctx.envVars.QQ_TOKEN = credentials.token;
|
|
338
316
|
ctx.envVars.QQ_SECRET = credentials.secret;
|
|
339
317
|
const { mode, sandbox } = await inquirer.prompt([
|
|
340
318
|
{
|
|
@@ -342,10 +320,10 @@ export async function configureQQBot(ctx) {
|
|
|
342
320
|
name: 'mode',
|
|
343
321
|
message: ' 消息接收模式 (mode):',
|
|
344
322
|
choices: [
|
|
345
|
-
{ name: '
|
|
346
|
-
{ name: '
|
|
323
|
+
{ name: 'websocket — WebSocket 网关(默认,无需公网)', value: 'websocket' },
|
|
324
|
+
{ name: 'webhook — HTTP 回调(需公网可达的内置 HTTP Host)', value: 'webhook' },
|
|
347
325
|
],
|
|
348
|
-
default: '
|
|
326
|
+
default: 'websocket',
|
|
349
327
|
},
|
|
350
328
|
{
|
|
351
329
|
type: 'confirm',
|
|
@@ -354,15 +332,15 @@ export async function configureQQBot(ctx) {
|
|
|
354
332
|
default: false,
|
|
355
333
|
},
|
|
356
334
|
]);
|
|
335
|
+
// schema:mode / sandbox 为顶层共享字段;name / appid / secret 为 endpoint 级
|
|
357
336
|
return {
|
|
358
|
-
context: 'qq',
|
|
359
|
-
name: credentials.name.trim(),
|
|
360
|
-
appID: '${QQ_APP_ID}',
|
|
361
|
-
token: '${QQ_TOKEN}',
|
|
362
|
-
secret: '${QQ_SECRET}',
|
|
363
337
|
mode,
|
|
364
|
-
platform: 'qq',
|
|
365
338
|
sandbox,
|
|
339
|
+
endpoints: [{
|
|
340
|
+
name: credentials.name.trim(),
|
|
341
|
+
appid: '${QQ_APP_ID}',
|
|
342
|
+
secret: '${QQ_SECRET}',
|
|
343
|
+
}],
|
|
366
344
|
};
|
|
367
345
|
}
|
|
368
346
|
export async function configureDiscordEndpoint(ctx) {
|
|
@@ -395,14 +373,13 @@ export async function configureDiscordEndpoint(ctx) {
|
|
|
395
373
|
},
|
|
396
374
|
]);
|
|
397
375
|
ctx.envVars.DISCORD_TOKEN = token;
|
|
398
|
-
|
|
399
|
-
|
|
376
|
+
// schema:connection / applicationId / publicKey / interactionsPath 为顶层共享字段;name / token 为 endpoint 级
|
|
377
|
+
const config = {
|
|
400
378
|
connection,
|
|
401
|
-
name: endpointName.trim(),
|
|
402
|
-
token: '${DISCORD_TOKEN}',
|
|
379
|
+
endpoints: [{ name: endpointName.trim(), token: '${DISCORD_TOKEN}' }],
|
|
403
380
|
};
|
|
404
381
|
if (connection === 'interactions') {
|
|
405
|
-
console.log(chalk.gray(' Interactions
|
|
382
|
+
console.log(chalk.gray(' Interactions 模式由内置 HTTP Host 承接,需 Developer Portal 中的公钥'));
|
|
406
383
|
const interactions = await inquirer.prompt([
|
|
407
384
|
{
|
|
408
385
|
type: 'input',
|
|
@@ -423,11 +400,11 @@ export async function configureDiscordEndpoint(ctx) {
|
|
|
423
400
|
default: '/discord/interactions',
|
|
424
401
|
},
|
|
425
402
|
]);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
403
|
+
config.applicationId = interactions.applicationId.trim();
|
|
404
|
+
config.publicKey = interactions.publicKey.trim();
|
|
405
|
+
config.interactionsPath = interactions.interactionsPath.trim();
|
|
429
406
|
}
|
|
430
|
-
return
|
|
407
|
+
return config;
|
|
431
408
|
}
|
|
432
409
|
export async function configureSlackEndpoint(ctx) {
|
|
433
410
|
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('slack')));
|
|
@@ -466,12 +443,11 @@ export async function configureSlackEndpoint(ctx) {
|
|
|
466
443
|
]);
|
|
467
444
|
ctx.envVars.SLACK_BOT_TOKEN = token;
|
|
468
445
|
ctx.envVars.SLACK_SIGNING_SECRET = signingSecret;
|
|
446
|
+
// schema:socketMode 为顶层共享字段;name / token / signingSecret / appToken 为 endpoint 级
|
|
469
447
|
const endpointConfig = {
|
|
470
|
-
context: 'slack',
|
|
471
448
|
name: endpointName.trim(),
|
|
472
449
|
token: '${SLACK_BOT_TOKEN}',
|
|
473
450
|
signingSecret: '${SLACK_SIGNING_SECRET}',
|
|
474
|
-
socketMode: socketMode === 'true',
|
|
475
451
|
};
|
|
476
452
|
if (socketMode === 'true') {
|
|
477
453
|
const { appToken } = await inquirer.prompt([
|
|
@@ -486,17 +462,406 @@ export async function configureSlackEndpoint(ctx) {
|
|
|
486
462
|
endpointConfig.appToken = '${SLACK_APP_TOKEN}';
|
|
487
463
|
}
|
|
488
464
|
else {
|
|
489
|
-
console.log(chalk.yellow(' HTTP
|
|
490
|
-
|
|
465
|
+
console.log(chalk.yellow(' HTTP 模式由内置 HTTP Host 承接,需公网可达并配置 Slack Event Subscriptions URL'));
|
|
466
|
+
}
|
|
467
|
+
return { socketMode: socketMode === 'true', endpoints: [endpointConfig] };
|
|
468
|
+
}
|
|
469
|
+
export async function configureNapcatBot(ctx) {
|
|
470
|
+
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('napcat')));
|
|
471
|
+
const { connection } = await inquirer.prompt([
|
|
472
|
+
{
|
|
473
|
+
type: 'select',
|
|
474
|
+
name: 'connection',
|
|
475
|
+
message: ' 连接方式:',
|
|
476
|
+
choices: [
|
|
477
|
+
{ name: '正向 WebSocket (ws) — 本 Bot 连接 NapCat', value: 'ws' },
|
|
478
|
+
{ name: '反向 WebSocket (wss) — NapCat 连本 Bot(由内置 HTTP Host 承接)', value: 'wss' },
|
|
479
|
+
{ name: 'HTTP (http) — POST 上报入站 + HTTP API 出站(由内置 HTTP Host 承接)', value: 'http' },
|
|
480
|
+
],
|
|
481
|
+
default: 'ws',
|
|
482
|
+
},
|
|
483
|
+
]);
|
|
484
|
+
const { endpointName } = await inquirer.prompt([
|
|
485
|
+
{
|
|
486
|
+
type: 'input',
|
|
487
|
+
name: 'endpointName',
|
|
488
|
+
message: ' Endpoint 标识名称:',
|
|
489
|
+
default: 'napcat-bot',
|
|
490
|
+
validate: (v) => (v.trim() ? true : '名称不能为空'),
|
|
491
|
+
},
|
|
492
|
+
]);
|
|
493
|
+
// schema:connection 为顶层共享字段;name / url / path / http_url / post_path / access_token 为 endpoint 级
|
|
494
|
+
const endpointConfig = {
|
|
495
|
+
name: endpointName.trim(),
|
|
496
|
+
};
|
|
497
|
+
if (connection === 'ws') {
|
|
498
|
+
const { url } = await inquirer.prompt([
|
|
499
|
+
{
|
|
500
|
+
type: 'input',
|
|
501
|
+
name: 'url',
|
|
502
|
+
message: ' NapCat WS 地址:',
|
|
503
|
+
default: 'ws://127.0.0.1:3001',
|
|
504
|
+
validate: (v) => (v.trim() ? true : '地址不能为空'),
|
|
505
|
+
},
|
|
506
|
+
]);
|
|
507
|
+
endpointConfig.url = url.trim();
|
|
508
|
+
}
|
|
509
|
+
else if (connection === 'wss') {
|
|
510
|
+
console.log(chalk.gray(' 反向 WS 由内置 HTTP Host 承接,NapCat 配置连接到此 Bot'));
|
|
511
|
+
const { path: wsPath } = await inquirer.prompt([
|
|
512
|
+
{
|
|
513
|
+
type: 'input',
|
|
514
|
+
name: 'path',
|
|
515
|
+
message: ' 反向 WS 路径:',
|
|
516
|
+
default: '/napcat/ws',
|
|
517
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
518
|
+
},
|
|
519
|
+
]);
|
|
520
|
+
endpointConfig.path = wsPath.trim();
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
const http = await inquirer.prompt([
|
|
524
|
+
{
|
|
525
|
+
type: 'input',
|
|
526
|
+
name: 'http_url',
|
|
527
|
+
message: ' NapCat HTTP API 地址(出站):',
|
|
528
|
+
default: 'http://127.0.0.1:3000',
|
|
529
|
+
validate: (v) => (v.trim() ? true : '地址不能为空'),
|
|
530
|
+
},
|
|
491
531
|
{
|
|
492
532
|
type: 'input',
|
|
493
|
-
name: '
|
|
494
|
-
message: '
|
|
495
|
-
default: '
|
|
533
|
+
name: 'post_path',
|
|
534
|
+
message: ' HTTP POST 上报路径(入站):',
|
|
535
|
+
default: '/napcat/post',
|
|
536
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
537
|
+
},
|
|
538
|
+
]);
|
|
539
|
+
endpointConfig.http_url = http.http_url.trim();
|
|
540
|
+
endpointConfig.post_path = http.post_path.trim();
|
|
541
|
+
}
|
|
542
|
+
const { accessToken } = await inquirer.prompt([
|
|
543
|
+
{
|
|
544
|
+
type: 'password',
|
|
545
|
+
name: 'accessToken',
|
|
546
|
+
message: ' access_token(无则留空):',
|
|
547
|
+
default: '',
|
|
548
|
+
},
|
|
549
|
+
]);
|
|
550
|
+
if (accessToken?.trim()) {
|
|
551
|
+
ctx.envVars.NAPCAT_TOKEN = accessToken.trim();
|
|
552
|
+
endpointConfig.access_token = '${NAPCAT_TOKEN}';
|
|
553
|
+
}
|
|
554
|
+
return { connection, endpoints: [endpointConfig] };
|
|
555
|
+
}
|
|
556
|
+
export async function configureOneBot12Bot(ctx) {
|
|
557
|
+
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('onebot12')));
|
|
558
|
+
const { connection } = await inquirer.prompt([
|
|
559
|
+
{
|
|
560
|
+
type: 'select',
|
|
561
|
+
name: 'connection',
|
|
562
|
+
message: ' 连接方式:',
|
|
563
|
+
choices: [
|
|
564
|
+
{ name: '正向 WebSocket (ws) — 本 Bot 连接 OneBot 实现', value: 'ws' },
|
|
565
|
+
{ name: 'HTTP Webhook (webhook) — POST 入站 + api_url HTTP 出站(由内置 HTTP Host 承接)', value: 'webhook' },
|
|
566
|
+
{ name: '反向 WebSocket (wss) — OneBot 实现连本 Bot(由内置 HTTP Host 承接)', value: 'wss' },
|
|
567
|
+
],
|
|
568
|
+
default: 'ws',
|
|
569
|
+
},
|
|
570
|
+
]);
|
|
571
|
+
const { endpointName } = await inquirer.prompt([
|
|
572
|
+
{
|
|
573
|
+
type: 'input',
|
|
574
|
+
name: 'endpointName',
|
|
575
|
+
message: ' Endpoint 标识名称:',
|
|
576
|
+
default: 'onebot12-bot',
|
|
577
|
+
validate: (v) => (v.trim() ? true : '名称不能为空'),
|
|
578
|
+
},
|
|
579
|
+
]);
|
|
580
|
+
// schema:connection 为顶层共享字段;name / url / path / api_url / access_token 为 endpoint 级
|
|
581
|
+
const endpointConfig = {
|
|
582
|
+
name: endpointName.trim(),
|
|
583
|
+
};
|
|
584
|
+
if (connection === 'ws') {
|
|
585
|
+
const { url } = await inquirer.prompt([
|
|
586
|
+
{
|
|
587
|
+
type: 'input',
|
|
588
|
+
name: 'url',
|
|
589
|
+
message: ' OneBot WS 地址:',
|
|
590
|
+
default: 'ws://127.0.0.1:6700',
|
|
591
|
+
validate: (v) => (v.trim() ? true : '地址不能为空'),
|
|
592
|
+
},
|
|
593
|
+
]);
|
|
594
|
+
endpointConfig.url = url.trim();
|
|
595
|
+
}
|
|
596
|
+
else if (connection === 'webhook') {
|
|
597
|
+
const webhook = await inquirer.prompt([
|
|
598
|
+
{
|
|
599
|
+
type: 'input',
|
|
600
|
+
name: 'path',
|
|
601
|
+
message: ' Webhook POST 路径(入站):',
|
|
602
|
+
default: '/onebot12/webhook',
|
|
603
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
type: 'input',
|
|
607
|
+
name: 'api_url',
|
|
608
|
+
message: ' HTTP action 地址(出站):',
|
|
609
|
+
default: 'http://127.0.0.1:6700',
|
|
610
|
+
validate: (v) => (v.trim() ? true : '地址不能为空'),
|
|
496
611
|
},
|
|
497
612
|
]);
|
|
498
|
-
endpointConfig.
|
|
613
|
+
endpointConfig.path = webhook.path.trim();
|
|
614
|
+
endpointConfig.api_url = webhook.api_url.trim();
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
console.log(chalk.gray(' 反向 WS 由内置 HTTP Host 承接,OneBot 实现配置连接到此 Bot'));
|
|
618
|
+
const { path: wsPath } = await inquirer.prompt([
|
|
619
|
+
{
|
|
620
|
+
type: 'input',
|
|
621
|
+
name: 'path',
|
|
622
|
+
message: ' 反向 WS 路径:',
|
|
623
|
+
default: '/onebot12/ws',
|
|
624
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
625
|
+
},
|
|
626
|
+
]);
|
|
627
|
+
endpointConfig.path = wsPath.trim();
|
|
628
|
+
}
|
|
629
|
+
const { accessToken } = await inquirer.prompt([
|
|
630
|
+
{
|
|
631
|
+
type: 'password',
|
|
632
|
+
name: 'accessToken',
|
|
633
|
+
message: ' access_token(无则留空):',
|
|
634
|
+
default: '',
|
|
635
|
+
},
|
|
636
|
+
]);
|
|
637
|
+
if (accessToken?.trim()) {
|
|
638
|
+
ctx.envVars.ONEBOT12_ACCESS_TOKEN = accessToken.trim();
|
|
639
|
+
endpointConfig.access_token = '${ONEBOT12_ACCESS_TOKEN}';
|
|
640
|
+
}
|
|
641
|
+
return { connection, endpoints: [endpointConfig] };
|
|
642
|
+
}
|
|
643
|
+
export async function configureMilkyBot(ctx) {
|
|
644
|
+
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('milky')));
|
|
645
|
+
const { connection } = await inquirer.prompt([
|
|
646
|
+
{
|
|
647
|
+
type: 'select',
|
|
648
|
+
name: 'connection',
|
|
649
|
+
message: ' 连接方式:',
|
|
650
|
+
choices: [
|
|
651
|
+
{ name: '正向 WebSocket (ws) — 本 Bot 连协议端 ws(s)://baseUrl/event', value: 'ws' },
|
|
652
|
+
{ name: 'SSE (sse) — HTTP GET baseUrl/event(text/event-stream)', value: 'sse' },
|
|
653
|
+
{ name: 'Webhook (webhook) — POST 入站 + baseUrl HTTP API 出站(由内置 HTTP Host 承接)', value: 'webhook' },
|
|
654
|
+
{ name: '反向 WebSocket (wss) — 由内置 HTTP Host 承接', value: 'wss' },
|
|
655
|
+
],
|
|
656
|
+
default: 'ws',
|
|
657
|
+
},
|
|
658
|
+
]);
|
|
659
|
+
const { endpointName, baseUrl } = await inquirer.prompt([
|
|
660
|
+
{
|
|
661
|
+
type: 'input',
|
|
662
|
+
name: 'endpointName',
|
|
663
|
+
message: ' Endpoint 标识名称:',
|
|
664
|
+
default: 'milky-bot',
|
|
665
|
+
validate: (v) => (v.trim() ? true : '名称不能为空'),
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
type: 'input',
|
|
669
|
+
name: 'baseUrl',
|
|
670
|
+
message: ' Milky HTTP API baseUrl:',
|
|
671
|
+
default: 'http://127.0.0.1:8080',
|
|
672
|
+
validate: (v) => (v.trim() ? true : 'baseUrl 不能为空'),
|
|
673
|
+
},
|
|
674
|
+
]);
|
|
675
|
+
// schema:connection 为顶层共享字段;name / baseUrl / path / access_token 为 endpoint 级
|
|
676
|
+
const endpointConfig = {
|
|
677
|
+
name: endpointName.trim(),
|
|
678
|
+
baseUrl: baseUrl.trim(),
|
|
679
|
+
};
|
|
680
|
+
if (connection === 'webhook' || connection === 'wss') {
|
|
681
|
+
const { path: hookPath } = await inquirer.prompt([
|
|
682
|
+
{
|
|
683
|
+
type: 'input',
|
|
684
|
+
name: 'path',
|
|
685
|
+
message: ` ${connection === 'webhook' ? 'Webhook POST' : '反向 WS'} 路径:`,
|
|
686
|
+
default: connection === 'webhook' ? '/milky/webhook' : '/milky/ws',
|
|
687
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
688
|
+
},
|
|
689
|
+
]);
|
|
690
|
+
endpointConfig.path = hookPath.trim();
|
|
691
|
+
}
|
|
692
|
+
const { accessToken } = await inquirer.prompt([
|
|
693
|
+
{
|
|
694
|
+
type: 'password',
|
|
695
|
+
name: 'accessToken',
|
|
696
|
+
message: ' access_token(无则留空):',
|
|
697
|
+
default: '',
|
|
698
|
+
},
|
|
699
|
+
]);
|
|
700
|
+
if (accessToken?.trim()) {
|
|
701
|
+
ctx.envVars.MILKY_ACCESS_TOKEN = accessToken.trim();
|
|
702
|
+
endpointConfig.access_token = '${MILKY_ACCESS_TOKEN}';
|
|
703
|
+
}
|
|
704
|
+
return { connection, endpoints: [endpointConfig] };
|
|
705
|
+
}
|
|
706
|
+
export async function configureSatoriBot(ctx) {
|
|
707
|
+
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('satori')));
|
|
708
|
+
const { connection } = await inquirer.prompt([
|
|
709
|
+
{
|
|
710
|
+
type: 'select',
|
|
711
|
+
name: 'connection',
|
|
712
|
+
message: ' 连接方式:',
|
|
713
|
+
choices: [
|
|
714
|
+
{ name: '正向 WebSocket (ws) — 本 Bot 连 Satori SDK', value: 'ws' },
|
|
715
|
+
{ name: 'Webhook (webhook) — SDK POST 事件到 path(由内置 HTTP Host 承接)', value: 'webhook' },
|
|
716
|
+
],
|
|
717
|
+
default: 'ws',
|
|
718
|
+
},
|
|
719
|
+
]);
|
|
720
|
+
const { endpointName, baseUrl } = await inquirer.prompt([
|
|
721
|
+
{
|
|
722
|
+
type: 'input',
|
|
723
|
+
name: 'endpointName',
|
|
724
|
+
message: ' Endpoint 标识名称:',
|
|
725
|
+
default: 'satori-bot',
|
|
726
|
+
validate: (v) => (v.trim() ? true : '名称不能为空'),
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
type: 'input',
|
|
730
|
+
name: 'baseUrl',
|
|
731
|
+
message: ' Satori SDK baseUrl:',
|
|
732
|
+
default: 'http://127.0.0.1:5140',
|
|
733
|
+
validate: (v) => (v.trim() ? true : 'baseUrl 不能为空'),
|
|
734
|
+
},
|
|
735
|
+
]);
|
|
736
|
+
// schema:connection 为顶层共享字段;name / baseUrl / token / path 为 endpoint 级
|
|
737
|
+
const endpointConfig = {
|
|
738
|
+
name: endpointName.trim(),
|
|
739
|
+
baseUrl: baseUrl.trim(),
|
|
740
|
+
};
|
|
741
|
+
if (connection === 'webhook') {
|
|
742
|
+
const { path: hookPath } = await inquirer.prompt([
|
|
743
|
+
{
|
|
744
|
+
type: 'input',
|
|
745
|
+
name: 'path',
|
|
746
|
+
message: ' Webhook POST 路径:',
|
|
747
|
+
default: '/satori/webhook',
|
|
748
|
+
validate: (v) => (v.trim() ? true : '路径不能为空'),
|
|
749
|
+
},
|
|
750
|
+
]);
|
|
751
|
+
endpointConfig.path = hookPath.trim();
|
|
752
|
+
}
|
|
753
|
+
const { token } = await inquirer.prompt([
|
|
754
|
+
{
|
|
755
|
+
type: 'password',
|
|
756
|
+
name: 'token',
|
|
757
|
+
message: ' Bearer Token(无则留空):',
|
|
758
|
+
default: '',
|
|
759
|
+
},
|
|
760
|
+
]);
|
|
761
|
+
if (token?.trim()) {
|
|
762
|
+
ctx.envVars.SATORI_TOKEN = token.trim();
|
|
763
|
+
endpointConfig.token = '${SATORI_TOKEN}';
|
|
764
|
+
}
|
|
765
|
+
return { connection, endpoints: [endpointConfig] };
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* 微信 iLink(个人微信 / ClawBot)配置:默认走扫码绑定 —— 终端展示二维码,
|
|
769
|
+
* 微信扫码确认后 bot_token 写入 .env(`WEIXIN_ILINK_TOKEN`),
|
|
770
|
+
* zhin.config.yml 只留 `${WEIXIN_ILINK_TOKEN}` 引用;扫码失败可降级手动输入 token。
|
|
771
|
+
*/
|
|
772
|
+
export async function configureWeixinIlinkBot(ctx) {
|
|
773
|
+
console.log(chalk.gray(' 文档: ' + adapterDocsUrl('weixin-ilink')));
|
|
774
|
+
console.log(chalk.gray(' 需最新版微信 + ClawBot 灰度资格(仅私聊)'));
|
|
775
|
+
const { endpointName } = await inquirer.prompt([
|
|
776
|
+
{
|
|
777
|
+
type: 'input',
|
|
778
|
+
name: 'endpointName',
|
|
779
|
+
message: ' Endpoint 标识名称:',
|
|
780
|
+
default: 'weixin-bot',
|
|
781
|
+
validate: (v) => (v.trim() ? true : '名称不能为空'),
|
|
782
|
+
},
|
|
783
|
+
]);
|
|
784
|
+
const { method } = await inquirer.prompt([
|
|
785
|
+
{
|
|
786
|
+
type: 'select',
|
|
787
|
+
name: 'method',
|
|
788
|
+
message: ' 登录方式:',
|
|
789
|
+
choices: [
|
|
790
|
+
{ name: '扫码绑定(微信 ClawBot 入口扫码,自动获取 bot_token)', value: 'qr' },
|
|
791
|
+
{ name: '手动输入 bot_token', value: 'manual' },
|
|
792
|
+
],
|
|
793
|
+
default: 'qr',
|
|
794
|
+
},
|
|
795
|
+
]);
|
|
796
|
+
if (method === 'qr') {
|
|
797
|
+
const scanned = await tryQrLogin(ctx);
|
|
798
|
+
if (scanned) {
|
|
799
|
+
return {
|
|
800
|
+
endpoints: [{ name: endpointName.trim(), botToken: '${WEIXIN_ILINK_TOKEN}' }],
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
console.log(chalk.yellow(' 扫码未完成,降级为手动输入 bot_token'));
|
|
804
|
+
}
|
|
805
|
+
const { token } = await inquirer.prompt([
|
|
806
|
+
{
|
|
807
|
+
type: 'password',
|
|
808
|
+
name: 'token',
|
|
809
|
+
message: ' iLink Bot Token:',
|
|
810
|
+
validate: (v) => (v.trim() ? true : 'Token 不能为空'),
|
|
811
|
+
},
|
|
812
|
+
]);
|
|
813
|
+
ctx.envVars.WEIXIN_ILINK_TOKEN = token.trim();
|
|
814
|
+
return {
|
|
815
|
+
endpoints: [{ name: endpointName.trim(), botToken: '${WEIXIN_ILINK_TOKEN}' }],
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/** 扫码尝试:成功写入 envVars 并返回 true;用户放弃/过期/超时返回 false。 */
|
|
819
|
+
async function tryQrLogin(ctx) {
|
|
820
|
+
// 动态加载 qrcode:扫码路径才需要,失败则降级为纯链接展示
|
|
821
|
+
let renderQr;
|
|
822
|
+
try {
|
|
823
|
+
const qrcode = await import('qrcode');
|
|
824
|
+
renderQr = (text) => qrcode.toString(text, { type: 'terminal', small: true });
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
renderQr = undefined;
|
|
828
|
+
}
|
|
829
|
+
for (;;) {
|
|
830
|
+
try {
|
|
831
|
+
const creds = await loginWithIlinkQr({
|
|
832
|
+
onQrCode: async (url) => {
|
|
833
|
+
console.log('');
|
|
834
|
+
if (renderQr) {
|
|
835
|
+
console.log(await renderQr(url));
|
|
836
|
+
}
|
|
837
|
+
console.log(chalk.cyan(' 请用微信扫描上方二维码(或打开链接完成绑定):'));
|
|
838
|
+
console.log(chalk.cyan(` ${url}`));
|
|
839
|
+
console.log('');
|
|
840
|
+
},
|
|
841
|
+
onStatus: (status) => {
|
|
842
|
+
if (status === 'scaned') {
|
|
843
|
+
console.log(chalk.green(' 已扫码,请在微信中确认…'));
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
ctx.envVars.WEIXIN_ILINK_TOKEN = creds.botToken;
|
|
848
|
+
console.log(chalk.green(' 绑定成功,bot_token 已写入 .env(WEIXIN_ILINK_TOKEN)'));
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
catch (error) {
|
|
852
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
853
|
+
console.log(chalk.yellow(` 扫码登录失败:${message}`));
|
|
854
|
+
const { retry } = await inquirer.prompt([
|
|
855
|
+
{
|
|
856
|
+
type: 'confirm',
|
|
857
|
+
name: 'retry',
|
|
858
|
+
message: ' 重新获取二维码再试一次?(选否则手动输入 token)',
|
|
859
|
+
default: error instanceof IlinkQrLoginError && error.reason === 'expired',
|
|
860
|
+
},
|
|
861
|
+
]);
|
|
862
|
+
if (!retry)
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
499
865
|
}
|
|
500
|
-
return endpointConfig;
|
|
501
866
|
}
|
|
502
867
|
//# sourceMappingURL=adapter-configurers.js.map
|