luckytri 1.0.0 → 1.0.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/.github/SECURITY.md +4 -4
- package/README.en.md +252 -250
- package/README.md +252 -250
- package/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +8 -0
- package/docs/en/connect.md +18 -18
- package/docs/en/guide.md +202 -216
- package/docs/en/npm-release.md +25 -25
- package/docs/zh/CHANGELOG.md +8 -0
- package/docs/zh/connect.md +17 -17
- package/docs/zh/guide.md +202 -216
- package/docs/zh/npm-release.md +33 -33
- package/package.json +110 -108
- package/plugins/sdk/index.d.ts +155 -155
- package/plugins/sdk/testing.js +58 -58
- package/plugins/weather/index.js +82 -82
- package/plugins/weather/luckytri-plugin.json +29 -29
- package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
- package/public/app/assets/Empty-DP6WMGHi.js +2 -0
- package/public/app/assets/Field-CtbdqaiB.js +1 -0
- package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
- package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
- package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
- package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
- package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
- package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
- package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
- package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
- package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
- package/public/app/assets/TimePage-7-awH5XL.js +3 -0
- package/public/app/assets/index-4rItIkD-.js +4 -0
- package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
- package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
- package/public/app/assets/media-SMfTT-yc.js +1 -0
- package/public/app/index.html +3 -3
- package/public/guide.en.html +72 -99
- package/public/guide.html +44 -86
- package/public/tutorial.en.md +25 -39
- package/public/tutorial.md +25 -39
- package/scripts/backup.js +311 -311
- package/scripts/browser.js +41 -41
- package/scripts/import-data.js +128 -0
- package/scripts/launch.js +101 -94
- package/scripts/plugin.js +150 -150
- package/scripts/reset-password.js +22 -0
- package/scripts/runtime.js +25 -25
- package/scripts/service.js +56 -47
- package/scripts/setup.js +50 -50
- package/scripts/start-server.js +41 -41
- package/scripts/stop.js +36 -36
- package/server/app.js +112 -96
- package/server/auth.js +217 -0
- package/server/channels/contract.js +57 -57
- package/server/channels/hub.js +165 -165
- package/server/channels/index.js +28 -25
- package/server/channels/onebot/index.js +335 -336
- package/server/channels/onebot/token.js +22 -3
- package/server/core/capabilities.js +208 -208
- package/server/core/events.js +64 -60
- package/server/core/image-preprocessor.js +208 -208
- package/server/core/model-manager.js +1213 -1213
- package/server/core/net-guard.js +49 -49
- package/server/core/orchestrator.js +569 -569
- package/server/core/perceivers.js +91 -91
- package/server/core/persona-manager.js +243 -243
- package/server/core/repository.js +293 -293
- package/server/core/response-validator.js +508 -508
- package/server/core/turn-processor.js +630 -630
- package/server/core/turn.js +236 -236
- package/server/core/vision-manager.js +633 -633
- package/server/i18n/errors.js +548 -497
- package/server/i18n/labels.js +649 -653
- package/server/index.js +151 -102
- package/server/knowledge/manager.js +386 -386
- package/server/knowledge/schema.js +12 -8
- package/server/local-access.js +32 -0
- package/server/mind/anticipations.js +507 -507
- package/server/mind/api.js +703 -703
- package/server/mind/budget.js +152 -152
- package/server/mind/context-selection.js +61 -61
- package/server/mind/day-planner.js +324 -324
- package/server/mind/evidence.js +148 -148
- package/server/mind/index.js +445 -445
- package/server/mind/life/diary.js +786 -786
- package/server/mind/life/index.js +1049 -1049
- package/server/mind/observations.js +84 -84
- package/server/mind/own-day.js +324 -324
- package/server/mind/own-voice.js +318 -318
- package/server/mind/senses.js +53 -53
- package/server/mind/time/activity-clock.js +196 -196
- package/server/mind/time/availability.js +69 -69
- package/server/mind/time/commitment-review.js +198 -198
- package/server/mind/time/executor.js +512 -512
- package/server/mind/time/experiences.js +128 -128
- package/server/mind/time/games.js +562 -562
- package/server/mind/time/index.js +739 -739
- package/server/mind/time/intent.js +166 -166
- package/server/mind/time/kinds.js +171 -171
- package/server/mind/time/plugin-activity.js +207 -207
- package/server/mind/time/tasks.js +587 -587
- package/server/mind/util.js +110 -110
- package/server/mind/view.js +273 -273
- package/server/paths.js +102 -102
- package/server/plugins/api.js +204 -204
- package/server/plugins/host.js +967 -967
- package/server/plugins/install.js +295 -295
- package/server/plugins/manifest.js +201 -201
- package/server/plugins/permissions.js +42 -42
- package/server/plugins/runner/guard.mjs +36 -36
- package/server/plugins/runner/worker.mjs +250 -250
- package/server/plugins/schema.js +91 -91
- package/server/plugins/zip.js +112 -112
- package/server/startup-lock.js +118 -118
- package/server/storage/archive.js +242 -241
- package/server/storage/backup-cleanup-worker.js +8 -8
- package/server/storage/backup-cleanup.js +415 -414
- package/server/storage/backup-lock.js +76 -76
- package/server/storage/backup-scheduler.js +131 -131
- package/server/storage/data-package.js +298 -0
- package/server/storage/data-transfer-api.js +50 -0
- package/server/storage/data-transfer-worker.js +18 -0
- package/server/storage/data-transfer.js +415 -0
- package/server/storage/store.js +202 -199
- package/server/studio/readiness.js +181 -190
- package/server/studio/settings.js +236 -236
- package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
- package/public/app/assets/Empty-CpP5kQHH.js +0 -2
- package/public/app/assets/Field-D4gY-INU.js +0 -1
- package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
- package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
- package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
- package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
- package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
- package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
- package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
- package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
- package/public/app/assets/TimePage-Cw44inM4.js +0 -3
- package/public/app/assets/index-BheRW-1t.js +0 -4
- package/public/app/assets/media-BzPDBm7e.js +0 -1
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
// A channel is the only part of LuckyTri that knows how a platform connects.
|
|
2
|
-
// Everything above it (sessions, attention, memory, time, her inner life) sees
|
|
3
|
-
// the same neutral message and talks back through the same few calls, so a
|
|
4
|
-
// new platform is one folder that fulfils this contract and one line in the
|
|
5
|
-
// registry (see channels/index.js).
|
|
6
|
-
//
|
|
7
|
-
// type stable id, also the first part of every session key
|
|
8
|
-
// capabilities what the platform can express: mention, quote,
|
|
9
|
-
// image, thread, sticker
|
|
10
|
-
// attach(server, chat) one-time wiring to the HTTP server and the ChatSystem
|
|
11
|
-
// start() begin accepting or opening the connection for the
|
|
12
|
-
// current settings; safe to call again after a change
|
|
13
|
-
// stop() drop the connection and stop timers; safe to repeat
|
|
14
|
-
// send(message, text) deliver one bubble; resolves { message_id }
|
|
15
|
-
// fetchQuoted(message) the message that `message.replyId` points to, or null
|
|
16
|
-
// fetchImage(file) a fresh URL or bytes for an expired image, or null
|
|
17
|
-
// fetchMedia(file, type) optional: a voice or file the same way, or null
|
|
18
|
-
// refreshDirectory() learn real group and friend names; resolves a count
|
|
19
|
-
// canReach(sessionId) whether she may start a conversation there now
|
|
20
|
-
// status() { online, ... } for the connection panel
|
|
21
|
-
// close() final shutdown
|
|
22
|
-
//
|
|
23
|
-
// The message every channel hands to `ChatSystem.receive` is neutral. Besides
|
|
24
|
-
// the fields the core already uses, an adapter fills platformId, accountId,
|
|
25
|
-
// time, mentions, replyId and attachments itself, so nothing above the
|
|
26
|
-
// channel ever has to parse a platform's wire format.
|
|
27
|
-
|
|
28
|
-
const REQUIRED = [
|
|
29
|
-
"start",
|
|
30
|
-
"stop",
|
|
31
|
-
"attach",
|
|
32
|
-
"send",
|
|
33
|
-
"fetchQuoted",
|
|
34
|
-
"fetchImage",
|
|
35
|
-
"refreshDirectory",
|
|
36
|
-
"canReach",
|
|
37
|
-
"status",
|
|
38
|
-
"close",
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
export function defineChannel(channel) {
|
|
42
|
-
if (!channel || typeof channel.type !== "string" || !channel.type)
|
|
43
|
-
throw new TypeError("channel needs a type");
|
|
44
|
-
for (const name of REQUIRED)
|
|
45
|
-
if (typeof channel[name] !== "function")
|
|
46
|
-
throw new TypeError(`channel ${channel.type} is missing ${name}()`);
|
|
47
|
-
return Object.freeze({
|
|
48
|
-
capabilities: {
|
|
49
|
-
mention: false,
|
|
50
|
-
quote: false,
|
|
51
|
-
image: false,
|
|
52
|
-
thread: false,
|
|
53
|
-
sticker: false,
|
|
54
|
-
},
|
|
55
|
-
...channel,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
1
|
+
// A channel is the only part of LuckyTri that knows how a platform connects.
|
|
2
|
+
// Everything above it (sessions, attention, memory, time, her inner life) sees
|
|
3
|
+
// the same neutral message and talks back through the same few calls, so a
|
|
4
|
+
// new platform is one folder that fulfils this contract and one line in the
|
|
5
|
+
// registry (see channels/index.js).
|
|
6
|
+
//
|
|
7
|
+
// type stable id, also the first part of every session key
|
|
8
|
+
// capabilities what the platform can express: mention, quote,
|
|
9
|
+
// image, thread, sticker
|
|
10
|
+
// attach(server, chat) one-time wiring to the HTTP server and the ChatSystem
|
|
11
|
+
// start() begin accepting or opening the connection for the
|
|
12
|
+
// current settings; safe to call again after a change
|
|
13
|
+
// stop() drop the connection and stop timers; safe to repeat
|
|
14
|
+
// send(message, text) deliver one bubble; resolves { message_id }
|
|
15
|
+
// fetchQuoted(message) the message that `message.replyId` points to, or null
|
|
16
|
+
// fetchImage(file) a fresh URL or bytes for an expired image, or null
|
|
17
|
+
// fetchMedia(file, type) optional: a voice or file the same way, or null
|
|
18
|
+
// refreshDirectory() learn real group and friend names; resolves a count
|
|
19
|
+
// canReach(sessionId) whether she may start a conversation there now
|
|
20
|
+
// status() { online, ... } for the connection panel
|
|
21
|
+
// close() final shutdown
|
|
22
|
+
//
|
|
23
|
+
// The message every channel hands to `ChatSystem.receive` is neutral. Besides
|
|
24
|
+
// the fields the core already uses, an adapter fills platformId, accountId,
|
|
25
|
+
// time, mentions, replyId and attachments itself, so nothing above the
|
|
26
|
+
// channel ever has to parse a platform's wire format.
|
|
27
|
+
|
|
28
|
+
const REQUIRED = [
|
|
29
|
+
"start",
|
|
30
|
+
"stop",
|
|
31
|
+
"attach",
|
|
32
|
+
"send",
|
|
33
|
+
"fetchQuoted",
|
|
34
|
+
"fetchImage",
|
|
35
|
+
"refreshDirectory",
|
|
36
|
+
"canReach",
|
|
37
|
+
"status",
|
|
38
|
+
"close",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export function defineChannel(channel) {
|
|
42
|
+
if (!channel || typeof channel.type !== "string" || !channel.type)
|
|
43
|
+
throw new TypeError("channel needs a type");
|
|
44
|
+
for (const name of REQUIRED)
|
|
45
|
+
if (typeof channel[name] !== "function")
|
|
46
|
+
throw new TypeError(`channel ${channel.type} is missing ${name}()`);
|
|
47
|
+
return Object.freeze({
|
|
48
|
+
capabilities: {
|
|
49
|
+
mention: false,
|
|
50
|
+
quote: false,
|
|
51
|
+
image: false,
|
|
52
|
+
thread: false,
|
|
53
|
+
sticker: false,
|
|
54
|
+
},
|
|
55
|
+
...channel,
|
|
56
|
+
});
|
|
57
|
+
}
|
package/server/channels/hub.js
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
import { parseSessionKey } from "./session-key.js";
|
|
2
|
-
|
|
3
|
-
export const CHANNEL_TYPES = ["onebot", "qqbot"];
|
|
4
|
-
|
|
5
|
-
// Which channel is wanted: the environment wins over the saved setting, the
|
|
6
|
-
// same way LLM_API_KEY wins over a key saved in the studio.
|
|
7
|
-
export function selectedChannel(store, env = process.env) {
|
|
8
|
-
const forced = String(env.LUCKYTRI_CHANNEL || "")
|
|
9
|
-
.trim()
|
|
10
|
-
.toLowerCase();
|
|
11
|
-
if (CHANNEL_TYPES.includes(forced)) return { type: forced, locked: true };
|
|
12
|
-
const saved = String(store.settings().channel || "");
|
|
13
|
-
return {
|
|
14
|
-
type: CHANNEL_TYPES.includes(saved) ? saved : "onebot",
|
|
15
|
-
locked: false,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Exactly one channel is connected at a time. Everything above the hub talks
|
|
20
|
-
// to it as if it were the channel itself, so the core, her mind and her life
|
|
21
|
-
// are the same whichever platform she is on; switching is a setting, not a
|
|
22
|
-
// restart.
|
|
23
|
-
const QQ = new Set(CHANNEL_TYPES);
|
|
24
|
-
|
|
25
|
-
export function createChannelHub(store, channels, { env = process.env } = {}) {
|
|
26
|
-
const registry = new Map(channels.map((channel) => [channel.type, channel]));
|
|
27
|
-
if (!registry.size) throw new Error("channel hub needs at least one channel");
|
|
28
|
-
let active = null;
|
|
29
|
-
let locked = false;
|
|
30
|
-
let attached = null;
|
|
31
|
-
// Plugin channels stay connected beside the one QQ channel in use.
|
|
32
|
-
const started = new Set();
|
|
33
|
-
|
|
34
|
-
const current = () => registry.get(active) || registry.values().next().value;
|
|
35
|
-
|
|
36
|
-
function resolve(sessionId) {
|
|
37
|
-
let type = active;
|
|
38
|
-
if (sessionId) {
|
|
39
|
-
try {
|
|
40
|
-
type = parseSessionKey(sessionId).channel;
|
|
41
|
-
} catch {
|
|
42
|
-
/* A session without a channel key is spoken to over the active one. */
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (QQ.has(type) && type !== active)
|
|
46
|
-
throw new Error("这个会话属于另一种 QQ 接入方式,当前不能向它发送");
|
|
47
|
-
const channel = registry.get(type);
|
|
48
|
-
if (!channel || (!QQ.has(type) && !started.has(type)))
|
|
49
|
-
throw new Error("这个会话的通道没有接上");
|
|
50
|
-
return channel;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Bring the connected channel in line with the settings. Cheap when nothing
|
|
54
|
-
// changed, so it is also what runs after the credentials are edited.
|
|
55
|
-
function sync() {
|
|
56
|
-
const wanted = selectedChannel(store, env);
|
|
57
|
-
const type = registry.has(wanted.type)
|
|
58
|
-
? wanted.type
|
|
59
|
-
: registry.keys().next().value;
|
|
60
|
-
locked = wanted.locked;
|
|
61
|
-
if (active && active !== type) registry.get(active).stop();
|
|
62
|
-
const switched = active !== type;
|
|
63
|
-
active = type;
|
|
64
|
-
registry.get(type).start();
|
|
65
|
-
if (switched)
|
|
66
|
-
registry
|
|
67
|
-
.get(type)
|
|
68
|
-
.refreshDirectory()
|
|
69
|
-
.catch(() => {});
|
|
70
|
-
return type;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function attach(server, chat) {
|
|
74
|
-
attached = { server, chat };
|
|
75
|
-
for (const channel of registry.values()) channel.attach(server, chat);
|
|
76
|
-
for (const type of started) registry.get(type)?.start();
|
|
77
|
-
sync();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function register(channel) {
|
|
81
|
-
if (!channel?.type || QQ.has(channel.type) || registry.has(channel.type))
|
|
82
|
-
throw new Error("这个通道不能接上");
|
|
83
|
-
registry.set(channel.type, channel);
|
|
84
|
-
started.add(channel.type);
|
|
85
|
-
if (attached) {
|
|
86
|
-
channel.attach(attached.server, attached.chat);
|
|
87
|
-
channel.start();
|
|
88
|
-
}
|
|
89
|
-
return channel.type;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function unregister(type) {
|
|
93
|
-
if (QQ.has(type) || !registry.has(type)) return false;
|
|
94
|
-
started.delete(type);
|
|
95
|
-
const channel = registry.get(type);
|
|
96
|
-
try {
|
|
97
|
-
channel.stop();
|
|
98
|
-
channel.close();
|
|
99
|
-
} catch {
|
|
100
|
-
/* A plugin channel that already died should still leave the hub. */
|
|
101
|
-
}
|
|
102
|
-
registry.delete(type);
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const send = async (message, text) => {
|
|
107
|
-
if (message?.simulated) return { message_id: "sim" };
|
|
108
|
-
return resolve(message.sessionId).send(message, text);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
function canReach(sessionId) {
|
|
112
|
-
try {
|
|
113
|
-
return resolve(sessionId).canReach(sessionId);
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function status() {
|
|
120
|
-
const channel = current();
|
|
121
|
-
const own = channel.status();
|
|
122
|
-
const all = {};
|
|
123
|
-
for (const item of registry.values()) all[item.type] = item.status();
|
|
124
|
-
return {
|
|
125
|
-
channel: channel.type,
|
|
126
|
-
locked,
|
|
127
|
-
online: !!own.online,
|
|
128
|
-
connectedAt: own.connectedAt ?? null,
|
|
129
|
-
lastEventAt: own.lastEventAt ?? null,
|
|
130
|
-
lastDisconnectAt: own.lastDisconnectAt ?? null,
|
|
131
|
-
adminProtected: !!env.ADMIN_TOKEN,
|
|
132
|
-
port: Number(env.PORT || 3210),
|
|
133
|
-
capabilities: channel.capabilities,
|
|
134
|
-
...all,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
attach,
|
|
140
|
-
sync,
|
|
141
|
-
register,
|
|
142
|
-
unregister,
|
|
143
|
-
send,
|
|
144
|
-
canReach,
|
|
145
|
-
status,
|
|
146
|
-
type: () => current().type,
|
|
147
|
-
online: () =>
|
|
148
|
-
!!current().status().online ||
|
|
149
|
-
[...started].some((type) => registry.get(type)?.status().online),
|
|
150
|
-
fetchQuoted: (message) =>
|
|
151
|
-
(message?.sessionId ? resolve(message.sessionId) : current()).fetchQuoted(
|
|
152
|
-
message,
|
|
153
|
-
),
|
|
154
|
-
fetchImage: (file, sessionId) =>
|
|
155
|
-
(sessionId ? resolve(sessionId) : current()).fetchImage(file),
|
|
156
|
-
fetchMedia: (file, sessionId, type) => {
|
|
157
|
-
const channel = sessionId ? resolve(sessionId) : current();
|
|
158
|
-
return channel.fetchMedia ? channel.fetchMedia(file, type) : null;
|
|
159
|
-
},
|
|
160
|
-
refreshDirectory: () => current().refreshDirectory(),
|
|
161
|
-
close() {
|
|
162
|
-
for (const channel of registry.values()) channel.close();
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
1
|
+
import { parseSessionKey } from "./session-key.js";
|
|
2
|
+
|
|
3
|
+
export const CHANNEL_TYPES = ["onebot", "qqbot"];
|
|
4
|
+
|
|
5
|
+
// Which channel is wanted: the environment wins over the saved setting, the
|
|
6
|
+
// same way LLM_API_KEY wins over a key saved in the studio.
|
|
7
|
+
export function selectedChannel(store, env = process.env) {
|
|
8
|
+
const forced = String(env.LUCKYTRI_CHANNEL || "")
|
|
9
|
+
.trim()
|
|
10
|
+
.toLowerCase();
|
|
11
|
+
if (CHANNEL_TYPES.includes(forced)) return { type: forced, locked: true };
|
|
12
|
+
const saved = String(store.settings().channel || "");
|
|
13
|
+
return {
|
|
14
|
+
type: CHANNEL_TYPES.includes(saved) ? saved : "onebot",
|
|
15
|
+
locked: false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Exactly one channel is connected at a time. Everything above the hub talks
|
|
20
|
+
// to it as if it were the channel itself, so the core, her mind and her life
|
|
21
|
+
// are the same whichever platform she is on; switching is a setting, not a
|
|
22
|
+
// restart.
|
|
23
|
+
const QQ = new Set(CHANNEL_TYPES);
|
|
24
|
+
|
|
25
|
+
export function createChannelHub(store, channels, { env = process.env } = {}) {
|
|
26
|
+
const registry = new Map(channels.map((channel) => [channel.type, channel]));
|
|
27
|
+
if (!registry.size) throw new Error("channel hub needs at least one channel");
|
|
28
|
+
let active = null;
|
|
29
|
+
let locked = false;
|
|
30
|
+
let attached = null;
|
|
31
|
+
// Plugin channels stay connected beside the one QQ channel in use.
|
|
32
|
+
const started = new Set();
|
|
33
|
+
|
|
34
|
+
const current = () => registry.get(active) || registry.values().next().value;
|
|
35
|
+
|
|
36
|
+
function resolve(sessionId) {
|
|
37
|
+
let type = active;
|
|
38
|
+
if (sessionId) {
|
|
39
|
+
try {
|
|
40
|
+
type = parseSessionKey(sessionId).channel;
|
|
41
|
+
} catch {
|
|
42
|
+
/* A session without a channel key is spoken to over the active one. */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (QQ.has(type) && type !== active)
|
|
46
|
+
throw new Error("这个会话属于另一种 QQ 接入方式,当前不能向它发送");
|
|
47
|
+
const channel = registry.get(type);
|
|
48
|
+
if (!channel || (!QQ.has(type) && !started.has(type)))
|
|
49
|
+
throw new Error("这个会话的通道没有接上");
|
|
50
|
+
return channel;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Bring the connected channel in line with the settings. Cheap when nothing
|
|
54
|
+
// changed, so it is also what runs after the credentials are edited.
|
|
55
|
+
function sync() {
|
|
56
|
+
const wanted = selectedChannel(store, env);
|
|
57
|
+
const type = registry.has(wanted.type)
|
|
58
|
+
? wanted.type
|
|
59
|
+
: registry.keys().next().value;
|
|
60
|
+
locked = wanted.locked;
|
|
61
|
+
if (active && active !== type) registry.get(active).stop();
|
|
62
|
+
const switched = active !== type;
|
|
63
|
+
active = type;
|
|
64
|
+
registry.get(type).start();
|
|
65
|
+
if (switched)
|
|
66
|
+
registry
|
|
67
|
+
.get(type)
|
|
68
|
+
.refreshDirectory()
|
|
69
|
+
.catch(() => {});
|
|
70
|
+
return type;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function attach(server, chat) {
|
|
74
|
+
attached = { server, chat };
|
|
75
|
+
for (const channel of registry.values()) channel.attach(server, chat);
|
|
76
|
+
for (const type of started) registry.get(type)?.start();
|
|
77
|
+
sync();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function register(channel) {
|
|
81
|
+
if (!channel?.type || QQ.has(channel.type) || registry.has(channel.type))
|
|
82
|
+
throw new Error("这个通道不能接上");
|
|
83
|
+
registry.set(channel.type, channel);
|
|
84
|
+
started.add(channel.type);
|
|
85
|
+
if (attached) {
|
|
86
|
+
channel.attach(attached.server, attached.chat);
|
|
87
|
+
channel.start();
|
|
88
|
+
}
|
|
89
|
+
return channel.type;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function unregister(type) {
|
|
93
|
+
if (QQ.has(type) || !registry.has(type)) return false;
|
|
94
|
+
started.delete(type);
|
|
95
|
+
const channel = registry.get(type);
|
|
96
|
+
try {
|
|
97
|
+
channel.stop();
|
|
98
|
+
channel.close();
|
|
99
|
+
} catch {
|
|
100
|
+
/* A plugin channel that already died should still leave the hub. */
|
|
101
|
+
}
|
|
102
|
+
registry.delete(type);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const send = async (message, text) => {
|
|
107
|
+
if (message?.simulated) return { message_id: "sim" };
|
|
108
|
+
return resolve(message.sessionId).send(message, text);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function canReach(sessionId) {
|
|
112
|
+
try {
|
|
113
|
+
return resolve(sessionId).canReach(sessionId);
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function status() {
|
|
120
|
+
const channel = current();
|
|
121
|
+
const own = channel.status();
|
|
122
|
+
const all = {};
|
|
123
|
+
for (const item of registry.values()) all[item.type] = item.status();
|
|
124
|
+
return {
|
|
125
|
+
channel: channel.type,
|
|
126
|
+
locked,
|
|
127
|
+
online: !!own.online,
|
|
128
|
+
connectedAt: own.connectedAt ?? null,
|
|
129
|
+
lastEventAt: own.lastEventAt ?? null,
|
|
130
|
+
lastDisconnectAt: own.lastDisconnectAt ?? null,
|
|
131
|
+
adminProtected: !!env.ADMIN_TOKEN,
|
|
132
|
+
port: Number(env.PORT || 3210),
|
|
133
|
+
capabilities: channel.capabilities,
|
|
134
|
+
...all,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
attach,
|
|
140
|
+
sync,
|
|
141
|
+
register,
|
|
142
|
+
unregister,
|
|
143
|
+
send,
|
|
144
|
+
canReach,
|
|
145
|
+
status,
|
|
146
|
+
type: () => current().type,
|
|
147
|
+
online: () =>
|
|
148
|
+
!!current().status().online ||
|
|
149
|
+
[...started].some((type) => registry.get(type)?.status().online),
|
|
150
|
+
fetchQuoted: (message) =>
|
|
151
|
+
(message?.sessionId ? resolve(message.sessionId) : current()).fetchQuoted(
|
|
152
|
+
message,
|
|
153
|
+
),
|
|
154
|
+
fetchImage: (file, sessionId) =>
|
|
155
|
+
(sessionId ? resolve(sessionId) : current()).fetchImage(file),
|
|
156
|
+
fetchMedia: (file, sessionId, type) => {
|
|
157
|
+
const channel = sessionId ? resolve(sessionId) : current();
|
|
158
|
+
return channel.fetchMedia ? channel.fetchMedia(file, type) : null;
|
|
159
|
+
},
|
|
160
|
+
refreshDirectory: () => current().refreshDirectory(),
|
|
161
|
+
close() {
|
|
162
|
+
for (const channel of registry.values()) channel.close();
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
package/server/channels/index.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import { createOneBotChannel } from "./onebot/index.js";
|
|
2
|
-
import { createQqBotChannel } from "./qqbot/index.js";
|
|
3
|
-
|
|
4
|
-
// Adding a platform: write a folder that fulfils channels/contract.js, give its
|
|
5
|
-
// adapter an entry in adapters.js (so the core can ask what its media links
|
|
6
|
-
// look like) and its factory a line in createChannels. Nothing above the hub
|
|
7
|
-
// changes.
|
|
8
|
-
export function createChannels(store, options = {}) {
|
|
9
|
-
return [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { createOneBotChannel } from "./onebot/index.js";
|
|
2
|
+
import { createQqBotChannel } from "./qqbot/index.js";
|
|
3
|
+
|
|
4
|
+
// Adding a platform: write a folder that fulfils channels/contract.js, give its
|
|
5
|
+
// adapter an entry in adapters.js (so the core can ask what its media links
|
|
6
|
+
// look like) and its factory a line in createChannels. Nothing above the hub
|
|
7
|
+
// changes.
|
|
8
|
+
export function createChannels(store, options = {}) {
|
|
9
|
+
return [
|
|
10
|
+
createOneBotChannel(store, options.onebot),
|
|
11
|
+
createQqBotChannel(store, options.qqbot),
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { createChannelHub, selectedChannel, CHANNEL_TYPES } from "./hub.js";
|
|
16
|
+
export { defineChannel } from "./contract.js";
|
|
17
|
+
export { adapterFor, registerChannel, onebot, qqbot } from "./adapters.js";
|
|
18
|
+
export {
|
|
19
|
+
bindSessionId,
|
|
20
|
+
formatSessionKey,
|
|
21
|
+
isGroupSession,
|
|
22
|
+
parseSessionKey,
|
|
23
|
+
publicSession,
|
|
24
|
+
sessionAliases,
|
|
25
|
+
sessionKind,
|
|
26
|
+
sessionNativeId,
|
|
27
|
+
} from "./session-key.js";
|
|
28
|
+
export { normalize } from "./onebot/adapter.js";
|