@zhin.js/adapter-qq 4.0.2 → 5.0.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/CHANGELOG.md +12 -0
- package/README.md +15 -0
- package/dist/index.js +2 -2
- package/lib/adapter.d.ts +4 -0
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +13 -0
- package/lib/adapter.js.map +1 -1
- package/lib/endpoint.d.ts +1 -1
- package/lib/endpoint.d.ts.map +1 -1
- package/lib/endpoint.js +1 -1
- package/lib/endpoint.js.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +8 -1
- package/lib/index.js.map +1 -1
- package/lib/qq-bind-api.d.ts +27 -0
- package/lib/qq-bind-api.d.ts.map +1 -0
- package/lib/qq-bind-api.js +86 -0
- package/lib/qq-bind-api.js.map +1 -0
- package/lib/qq-bind-flow.d.ts +19 -0
- package/lib/qq-bind-flow.d.ts.map +1 -0
- package/lib/qq-bind-flow.js +77 -0
- package/lib/qq-bind-flow.js.map +1 -0
- package/lib/qq-endpoint-manager.d.ts +16 -0
- package/lib/qq-endpoint-manager.d.ts.map +1 -0
- package/lib/qq-endpoint-manager.js +124 -0
- package/lib/qq-endpoint-manager.js.map +1 -0
- package/package.json +11 -11
- package/src/adapter.ts +16 -0
- package/src/endpoint.ts +1 -2
- package/src/index.ts +9 -1
- package/src/qq-bind-api.ts +126 -0
- package/src/qq-bind-flow.ts +117 -0
- package/src/qq-endpoint-manager.ts +146 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EndpointConfigRecord,
|
|
3
|
+
EndpointManager,
|
|
4
|
+
ProvisionContext,
|
|
5
|
+
} from 'zhin.js';
|
|
6
|
+
import type { QQAdapter } from './adapter.js';
|
|
7
|
+
import type { QQEndpointConfig, ReceiverMode } from './types.js';
|
|
8
|
+
import { ReceiverMode as QQReceiverMode } from './types.js';
|
|
9
|
+
import { startQqBindFlow } from './qq-bind-flow.js';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_INTENTS = [
|
|
12
|
+
'GUILDS',
|
|
13
|
+
'GUILD_MEMBERS',
|
|
14
|
+
'GUILD_MESSAGE_REACTIONS',
|
|
15
|
+
'DIRECT_MESSAGE',
|
|
16
|
+
'GROUP_AND_C2C_EVENT',
|
|
17
|
+
'INTERACTION',
|
|
18
|
+
'MESSAGE_AUDIT',
|
|
19
|
+
'AUDIO_ACTION',
|
|
20
|
+
'PUBLIC_GUILD_MESSAGES',
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
function resolveQqEndpointTemplate(root: ProvisionContext['root']): Partial<QQEndpointConfig<ReceiverMode>> {
|
|
24
|
+
const configService = root.inject('config') as { getPrimary?: () => { endpoints?: Array<Record<string, unknown>> } } | undefined;
|
|
25
|
+
const doc = configService?.getPrimary?.();
|
|
26
|
+
const existing = doc?.endpoints?.find((e) => e.context === 'qq');
|
|
27
|
+
if (!existing) {
|
|
28
|
+
return {
|
|
29
|
+
mode: QQReceiverMode.WEBSOCKET,
|
|
30
|
+
sandbox: false,
|
|
31
|
+
timeout: 10_000,
|
|
32
|
+
data_dir: './data',
|
|
33
|
+
intents: [...DEFAULT_INTENTS],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const { name: _n, appid: _a, secret: _s, context: _c, ...rest } = existing;
|
|
37
|
+
return rest as Partial<QQEndpointConfig<ReceiverMode>>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readQqConfigs(root: ProvisionContext['root']): EndpointConfigRecord[] {
|
|
41
|
+
const configService = root.inject('config') as { getRaw?: (f: string) => { endpoints?: EndpointConfigRecord[] } ; primaryFile?: string } | undefined;
|
|
42
|
+
if (!configService?.primaryFile || !configService.getRaw) return [];
|
|
43
|
+
const raw = configService.getRaw(configService.primaryFile);
|
|
44
|
+
return (raw?.endpoints ?? []).filter((e) => e.context === 'qq');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 全局单例:同一时间只允许一个 QQ 扫码绑定 */
|
|
48
|
+
let activeStopFlow: (() => void) | null = null;
|
|
49
|
+
|
|
50
|
+
export class QqEndpointManager implements EndpointManager {
|
|
51
|
+
constructor(private readonly adapter: QQAdapter) {}
|
|
52
|
+
|
|
53
|
+
supportsProvision(): boolean {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
listEndpoints(): EndpointConfigRecord[] {
|
|
58
|
+
return readQqConfigs(this.adapter.plugin.root);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
cancelProvision(): boolean {
|
|
62
|
+
if (!activeStopFlow) return false;
|
|
63
|
+
activeStopFlow();
|
|
64
|
+
activeStopFlow = null;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async addEndpoint(ctx: ProvisionContext): Promise<EndpointConfigRecord> {
|
|
69
|
+
if (activeStopFlow) {
|
|
70
|
+
throw new Error('已有进行中的 QQ 机器人绑定,请先 /endpoint cancel');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let endpointName: string | undefined;
|
|
74
|
+
const nameMatch = ctx.message.$raw?.match(/\/endpoint\s+add\s+qq\s+(\S+)/);
|
|
75
|
+
if (nameMatch?.[1]) {
|
|
76
|
+
endpointName = nameMatch[1];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return new Promise<EndpointConfigRecord>((resolve, reject) => {
|
|
80
|
+
const stopFlow = startQqBindFlow(
|
|
81
|
+
{
|
|
82
|
+
onQrDisplayed: async (url) => {
|
|
83
|
+
await ctx.onStatusUpdate('请用手机 QQ 扫描下方二维码完成机器人绑定(source=zhin)。', {
|
|
84
|
+
qrcode: url,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
onQrExpired: async () => {
|
|
88
|
+
await ctx.onStatusUpdate('二维码已过期,正在刷新,请扫描新二维码…');
|
|
89
|
+
},
|
|
90
|
+
onSuccess: async (credentials) => {
|
|
91
|
+
activeStopFlow = null;
|
|
92
|
+
const [{ appId, appSecret }] = credentials;
|
|
93
|
+
const name = endpointName?.trim() || appId;
|
|
94
|
+
const template = resolveQqEndpointTemplate(ctx.root);
|
|
95
|
+
resolve({
|
|
96
|
+
context: 'qq',
|
|
97
|
+
name,
|
|
98
|
+
appid: appId,
|
|
99
|
+
secret: appSecret,
|
|
100
|
+
...template,
|
|
101
|
+
} as EndpointConfigRecord);
|
|
102
|
+
},
|
|
103
|
+
onFailure: async (error) => {
|
|
104
|
+
activeStopFlow = null;
|
|
105
|
+
reject(error);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{ source: 'zhin' },
|
|
109
|
+
);
|
|
110
|
+
activeStopFlow = stopFlow;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async editEndpoint(name: string, ctx: ProvisionContext): Promise<EndpointConfigRecord> {
|
|
115
|
+
const existing = this.listEndpoints().find((e) => e.name === name);
|
|
116
|
+
if (!existing) {
|
|
117
|
+
throw new Error(`配置中不存在 qq/${name}`);
|
|
118
|
+
}
|
|
119
|
+
await ctx.onStatusUpdate(
|
|
120
|
+
`QQ 官方机器人暂不支持在线改凭据,请 /endpoint remove qq ${name} 后重新 /endpoint add qq。`,
|
|
121
|
+
);
|
|
122
|
+
return existing;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async removeEndpoint(name: string): Promise<boolean> {
|
|
126
|
+
return this.listEndpoints().some((e) => e.name === name);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async startEndpoint(name: string, ctx: ProvisionContext): Promise<void> {
|
|
130
|
+
if (!this.listEndpoints().some((e) => e.name === name)) {
|
|
131
|
+
throw new Error(`配置中不存在 qq/${name}`);
|
|
132
|
+
}
|
|
133
|
+
await ctx.onStatusUpdate(`正在连接 qq/${name}…`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async stopEndpoint(name: string): Promise<boolean> {
|
|
137
|
+
return this.adapter.endpoints.has(name);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function disposeQqEndpointProvision(): void {
|
|
142
|
+
if (activeStopFlow) {
|
|
143
|
+
activeStopFlow();
|
|
144
|
+
activeStopFlow = null;
|
|
145
|
+
}
|
|
146
|
+
}
|