koishi-plugin-mai-bridge 0.1.0 → 0.1.1
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/out/bridge/convert.js +1 -0
- package/out/bridge/docker.d.ts +3 -0
- package/out/bridge/docker.js +74 -6
- package/out/config.js +1 -1
- package/out/console.js +1 -1
- package/out/service.js +4 -1
- package/package.json +1 -1
- package/patches/maimai-koishi.patch +45 -0
- package/src/bridge/convert.ts +1 -0
- package/src/bridge/docker.ts +88 -7
- package/src/config.ts +1 -1
- package/src/console.ts +1 -1
- package/src/service.ts +4 -1
package/out/bridge/convert.js
CHANGED
|
@@ -193,6 +193,7 @@ async function sessionToMaimMessage(session, route, apiKey, options = {}) {
|
|
|
193
193
|
additional_config: {
|
|
194
194
|
koishi_route_id: route.routeId,
|
|
195
195
|
koishi_self_id: session.selfId,
|
|
196
|
+
platform_io_account_id: session.selfId,
|
|
196
197
|
koishi_platform: session.platform,
|
|
197
198
|
koishi_channel_id: session.channelId || '',
|
|
198
199
|
koishi_guild_id: session.guildId,
|
package/out/bridge/docker.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export declare class MaibotDockerManager {
|
|
|
20
20
|
private buildEnv;
|
|
21
21
|
private volumeMap;
|
|
22
22
|
private ensureVolumeDirs;
|
|
23
|
+
private resolveDockerVolumes;
|
|
24
|
+
private resolveDockerHostPath;
|
|
25
|
+
private resolveDockerNetwork;
|
|
23
26
|
private containerExists;
|
|
24
27
|
private removeContainer;
|
|
25
28
|
private docker;
|
package/out/bridge/docker.js
CHANGED
|
@@ -106,12 +106,14 @@ class MaibotDockerManager {
|
|
|
106
106
|
throw new Error(`blocked: ${agreements.reason}`);
|
|
107
107
|
}
|
|
108
108
|
this.ensureVolumeDirs();
|
|
109
|
-
const
|
|
109
|
+
const dockerNetwork = await this.resolveDockerNetwork();
|
|
110
|
+
const dockerVolumes = await this.resolveDockerVolumes();
|
|
111
|
+
const args = this.createRunArgs(agreements.env, dockerNetwork, dockerVolumes);
|
|
110
112
|
this.pushLog(`creating Docker container: ${this.config.dockerContainerName}`);
|
|
111
113
|
const result = await this.docker(args);
|
|
112
114
|
(0, spawn_1.assertSuccessful)(result, 'docker create');
|
|
113
115
|
}
|
|
114
|
-
createRunArgs(agreementEnv) {
|
|
116
|
+
createRunArgs(agreementEnv, dockerNetwork = this.config.dockerNetwork, dockerVolumes = this.volumeMap()) {
|
|
115
117
|
const args = [
|
|
116
118
|
'create',
|
|
117
119
|
'--name',
|
|
@@ -119,8 +121,8 @@ class MaibotDockerManager {
|
|
|
119
121
|
'--restart',
|
|
120
122
|
'unless-stopped',
|
|
121
123
|
];
|
|
122
|
-
if (
|
|
123
|
-
args.push('--network',
|
|
124
|
+
if (dockerNetwork) {
|
|
125
|
+
args.push('--network', dockerNetwork);
|
|
124
126
|
}
|
|
125
127
|
if (this.config.dockerPublishedWebuiPort > 0) {
|
|
126
128
|
args.push('-p', `${this.config.dockerPublishedWebuiPort}:${this.config.webuiPort}`);
|
|
@@ -129,7 +131,7 @@ class MaibotDockerManager {
|
|
|
129
131
|
for (const [key, value] of Object.entries(env)) {
|
|
130
132
|
args.push('-e', `${key}=${value}`);
|
|
131
133
|
}
|
|
132
|
-
for (const [source, target] of
|
|
134
|
+
for (const [source, target] of dockerVolumes) {
|
|
133
135
|
args.push('-v', `${source}:${target}`);
|
|
134
136
|
}
|
|
135
137
|
args.push(this.config.dockerImageName);
|
|
@@ -167,8 +169,74 @@ class MaibotDockerManager {
|
|
|
167
169
|
(0, fs_1.mkdirSync)(source, { recursive: true });
|
|
168
170
|
}
|
|
169
171
|
}
|
|
172
|
+
async resolveDockerVolumes() {
|
|
173
|
+
const volumes = [];
|
|
174
|
+
for (const [source, target] of this.volumeMap()) {
|
|
175
|
+
volumes.push([await this.resolveDockerHostPath(source), target]);
|
|
176
|
+
}
|
|
177
|
+
return volumes;
|
|
178
|
+
}
|
|
179
|
+
async resolveDockerHostPath(containerPath) {
|
|
180
|
+
const hostname = process.env.HOSTNAME;
|
|
181
|
+
if (!hostname)
|
|
182
|
+
return containerPath;
|
|
183
|
+
const result = await this.run(this.config.dockerCommand, [
|
|
184
|
+
'container',
|
|
185
|
+
'inspect',
|
|
186
|
+
hostname,
|
|
187
|
+
'--format',
|
|
188
|
+
'{{json .Mounts}}',
|
|
189
|
+
], false);
|
|
190
|
+
if (result.code !== 0)
|
|
191
|
+
return containerPath;
|
|
192
|
+
try {
|
|
193
|
+
const mounts = JSON.parse(result.stdout);
|
|
194
|
+
const normalizedPath = (0, path_1.resolve)(containerPath);
|
|
195
|
+
const candidates = mounts
|
|
196
|
+
.filter((mount) => mount.Source && mount.Destination)
|
|
197
|
+
.map((mount) => ({
|
|
198
|
+
source: mount.Source,
|
|
199
|
+
destination: (0, path_1.resolve)(mount.Destination),
|
|
200
|
+
}))
|
|
201
|
+
.sort((a, b) => b.destination.length - a.destination.length);
|
|
202
|
+
for (const mount of candidates) {
|
|
203
|
+
const rest = (0, path_1.relative)(mount.destination, normalizedPath);
|
|
204
|
+
if (rest === '' || (rest && !rest.startsWith('..') && !(0, path_1.isAbsolute)(rest))) {
|
|
205
|
+
return (0, path_1.join)(mount.source, rest);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
this.logger.debug(error);
|
|
211
|
+
}
|
|
212
|
+
return containerPath;
|
|
213
|
+
}
|
|
214
|
+
async resolveDockerNetwork() {
|
|
215
|
+
if (this.config.dockerNetwork)
|
|
216
|
+
return this.config.dockerNetwork;
|
|
217
|
+
const hostname = process.env.HOSTNAME;
|
|
218
|
+
if (!hostname)
|
|
219
|
+
return '';
|
|
220
|
+
const result = await this.run(this.config.dockerCommand, [
|
|
221
|
+
'container',
|
|
222
|
+
'inspect',
|
|
223
|
+
hostname,
|
|
224
|
+
'--format',
|
|
225
|
+
'{{range $name, $_ := .NetworkSettings.Networks}}{{println $name}}{{end}}',
|
|
226
|
+
], false);
|
|
227
|
+
if (result.code !== 0)
|
|
228
|
+
return '';
|
|
229
|
+
const network = result.stdout
|
|
230
|
+
.split(/\r?\n/)
|
|
231
|
+
.map((line) => line.trim())
|
|
232
|
+
.find((line) => line && !['bridge', 'host', 'none'].includes(line));
|
|
233
|
+
if (!network)
|
|
234
|
+
return '';
|
|
235
|
+
this.pushLog(`using Docker network from Koishi container: ${network}`);
|
|
236
|
+
return network;
|
|
237
|
+
}
|
|
170
238
|
async containerExists() {
|
|
171
|
-
const result = await this.run(this.config.dockerCommand, ['inspect', this.config.dockerContainerName], false);
|
|
239
|
+
const result = await this.run(this.config.dockerCommand, ['container', 'inspect', this.config.dockerContainerName], false);
|
|
172
240
|
return result.code === 0;
|
|
173
241
|
}
|
|
174
242
|
async removeContainer(force = false) {
|
package/out/config.js
CHANGED
|
@@ -19,7 +19,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
19
19
|
pythonCommand: koishi_1.Schema.string().default('python3').description('用于启动 mai.ko 的 Python 命令。'),
|
|
20
20
|
entryScript: koishi_1.Schema.string().default('bot.py').description('相对于 mai.ko 根目录的启动脚本。'),
|
|
21
21
|
autoStart: koishi_1.Schema.boolean().default(true).description('Koishi ready 后自动启动 mai.ko。'),
|
|
22
|
-
acceptMaibotAgreements: koishi_1.Schema.boolean().default(false).description('
|
|
22
|
+
acceptMaibotAgreements: koishi_1.Schema.boolean().default(false).description('确认同意 MaiBot 的 EULA/Privacy。'),
|
|
23
23
|
}).description('mai.ko 进程'),
|
|
24
24
|
koishi_1.Schema.object({
|
|
25
25
|
apiHost: koishi_1.Schema.string().default('maimai-ko').description('Koishi 连接 maim_message 新版 API Server 的地址。Docker 模式默认使用容器名。'),
|
package/out/console.js
CHANGED
|
@@ -5,7 +5,7 @@ const path_1 = require("path");
|
|
|
5
5
|
function registerConsole(ctx, config) {
|
|
6
6
|
if (!config.enableConsole)
|
|
7
7
|
return;
|
|
8
|
-
ctx.inject(['console'], (ctx) => {
|
|
8
|
+
ctx.inject(['console', 'maimai'], (ctx) => {
|
|
9
9
|
const options = { authority: config.commandAuthority };
|
|
10
10
|
ctx.console.addListener('mai-ko/status', () => ctx.maimai.getStatus(), options);
|
|
11
11
|
ctx.console.addListener('mai-ko/start', () => ctx.maimai.launch(), options);
|
package/out/service.js
CHANGED
|
@@ -503,7 +503,10 @@ class MaibotService extends koishi_1.Service {
|
|
|
503
503
|
if (host === '::')
|
|
504
504
|
host = '::1';
|
|
505
505
|
const normalizedHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
|
|
506
|
-
|
|
506
|
+
const port = this.pluginConfig.processMode === 'docker' && this.pluginConfig.dockerPublishedWebuiPort > 0
|
|
507
|
+
? this.pluginConfig.dockerPublishedWebuiPort
|
|
508
|
+
: this.pluginConfig.webuiPort;
|
|
509
|
+
return `http://${normalizedHost}:${port}`;
|
|
507
510
|
}
|
|
508
511
|
async handleMaimMessage(message) {
|
|
509
512
|
this.bridge.maimReceived += 1;
|
package/package.json
CHANGED
|
@@ -551,3 +551,48 @@ index 23af394..41860ae 100644
|
|
|
551
551
|
@staticmethod
|
|
552
552
|
def _first_non_empty_string(*values: Any) -> str:
|
|
553
553
|
"""返回第一个非空字符串表示。"""
|
|
554
|
+
diff --git a/src/services/send_service.py b/src/services/send_service.py
|
|
555
|
+
index 6e8aea1..f63933a 100644
|
|
556
|
+
--- a/src/services/send_service.py
|
|
557
|
+
+++ b/src/services/send_service.py
|
|
558
|
+
@@ -288,6 +288,31 @@ def _inherit_platform_io_route_metadata(target_stream: BotChatSession) -> Dict[s
|
|
559
|
+
return inherited_metadata
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
+def _get_target_stream_bot_account(target_stream: BotChatSession) -> str:
|
|
563
|
+
+ """获取目标会话对应的机器人账号。"""
|
|
564
|
+
+
|
|
565
|
+
+ bot_account = get_bot_account(target_stream.platform)
|
|
566
|
+
+ if bot_account:
|
|
567
|
+
+ return bot_account
|
|
568
|
+
+
|
|
569
|
+
+ context_message = target_stream.context.message if target_stream.context else None
|
|
570
|
+
+ if context_message is None:
|
|
571
|
+
+ return ""
|
|
572
|
+
+
|
|
573
|
+
+ additional_config = context_message.message_info.additional_config
|
|
574
|
+
+ if not isinstance(additional_config, dict):
|
|
575
|
+
+ return ""
|
|
576
|
+
+
|
|
577
|
+
+ for key in (*RouteKeyFactory.ACCOUNT_ID_KEYS, "koishi_self_id"):
|
|
578
|
+
+ value = additional_config.get(key)
|
|
579
|
+
+ if value is None:
|
|
580
|
+
+ continue
|
|
581
|
+
+ normalized_value = str(value).strip()
|
|
582
|
+
+ if normalized_value:
|
|
583
|
+
+ return normalized_value
|
|
584
|
+
+ return ""
|
|
585
|
+
+
|
|
586
|
+
+
|
|
587
|
+
def _build_binary_component_from_base64(component_type: str, raw_data: str) -> StandardMessageComponents:
|
|
588
|
+
"""根据 Base64 数据构造二进制消息组件。
|
|
589
|
+
|
|
590
|
+
@@ -538,7 +563,7 @@ def _build_outbound_session_message(
|
|
591
|
+
logger.error(f"[SendService] 未找到聊天流: {stream_id}")
|
|
592
|
+
return None
|
|
593
|
+
|
|
594
|
+
- bot_user_id = get_bot_account(target_stream.platform)
|
|
595
|
+
+ bot_user_id = _get_target_stream_bot_account(target_stream)
|
|
596
|
+
if not bot_user_id:
|
|
597
|
+
logger.error(f"[SendService] 平台 {target_stream.platform} 未配置机器人账号,无法发送消息")
|
|
598
|
+
return None
|
package/src/bridge/convert.ts
CHANGED
|
@@ -216,6 +216,7 @@ export async function sessionToMaimMessage(
|
|
|
216
216
|
additional_config: {
|
|
217
217
|
koishi_route_id: route.routeId,
|
|
218
218
|
koishi_self_id: session.selfId,
|
|
219
|
+
platform_io_account_id: session.selfId,
|
|
219
220
|
koishi_platform: session.platform,
|
|
220
221
|
koishi_channel_id: session.channelId || '',
|
|
221
222
|
koishi_guild_id: session.guildId,
|
package/src/bridge/docker.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from 'fs'
|
|
2
|
-
import { join } from 'path'
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from 'path'
|
|
3
3
|
import { Logger } from 'koishi'
|
|
4
4
|
import type { Config } from '../config'
|
|
5
5
|
import type { DockerState, RuntimeStatus } from '../types'
|
|
@@ -8,6 +8,13 @@ import { assertSuccessful, runCommand, type CommandResult } from '../utils/spawn
|
|
|
8
8
|
import { redactSecret } from '../utils/command'
|
|
9
9
|
import { prepareAgreementEnv } from './agreements'
|
|
10
10
|
|
|
11
|
+
interface DockerMount {
|
|
12
|
+
Source?: string
|
|
13
|
+
Destination?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type DockerVolume = readonly [string, string]
|
|
17
|
+
|
|
11
18
|
export class MaibotDockerManager {
|
|
12
19
|
private logger = new Logger('mai.ko/docker')
|
|
13
20
|
private state: DockerState = 'idle'
|
|
@@ -110,13 +117,19 @@ export class MaibotDockerManager {
|
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
this.ensureVolumeDirs()
|
|
113
|
-
const
|
|
120
|
+
const dockerNetwork = await this.resolveDockerNetwork()
|
|
121
|
+
const dockerVolumes = await this.resolveDockerVolumes()
|
|
122
|
+
const args = this.createRunArgs(agreements.env, dockerNetwork, dockerVolumes)
|
|
114
123
|
this.pushLog(`creating Docker container: ${this.config.dockerContainerName}`)
|
|
115
124
|
const result = await this.docker(args)
|
|
116
125
|
assertSuccessful(result, 'docker create')
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
private createRunArgs(
|
|
128
|
+
private createRunArgs(
|
|
129
|
+
agreementEnv: Record<string, string>,
|
|
130
|
+
dockerNetwork = this.config.dockerNetwork,
|
|
131
|
+
dockerVolumes: readonly DockerVolume[] = this.volumeMap(),
|
|
132
|
+
) {
|
|
120
133
|
const args = [
|
|
121
134
|
'create',
|
|
122
135
|
'--name',
|
|
@@ -125,8 +138,8 @@ export class MaibotDockerManager {
|
|
|
125
138
|
'unless-stopped',
|
|
126
139
|
]
|
|
127
140
|
|
|
128
|
-
if (
|
|
129
|
-
args.push('--network',
|
|
141
|
+
if (dockerNetwork) {
|
|
142
|
+
args.push('--network', dockerNetwork)
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
if (this.config.dockerPublishedWebuiPort > 0) {
|
|
@@ -138,7 +151,7 @@ export class MaibotDockerManager {
|
|
|
138
151
|
args.push('-e', `${key}=${value}`)
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
for (const [source, target] of
|
|
154
|
+
for (const [source, target] of dockerVolumes) {
|
|
142
155
|
args.push('-v', `${source}:${target}`)
|
|
143
156
|
}
|
|
144
157
|
|
|
@@ -181,8 +194,76 @@ export class MaibotDockerManager {
|
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
196
|
|
|
197
|
+
private async resolveDockerVolumes() {
|
|
198
|
+
const volumes: DockerVolume[] = []
|
|
199
|
+
for (const [source, target] of this.volumeMap()) {
|
|
200
|
+
volumes.push([await this.resolveDockerHostPath(source), target])
|
|
201
|
+
}
|
|
202
|
+
return volumes
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async resolveDockerHostPath(containerPath: string) {
|
|
206
|
+
const hostname = process.env.HOSTNAME
|
|
207
|
+
if (!hostname) return containerPath
|
|
208
|
+
|
|
209
|
+
const result = await this.run(this.config.dockerCommand, [
|
|
210
|
+
'container',
|
|
211
|
+
'inspect',
|
|
212
|
+
hostname,
|
|
213
|
+
'--format',
|
|
214
|
+
'{{json .Mounts}}',
|
|
215
|
+
], false)
|
|
216
|
+
if (result.code !== 0) return containerPath
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const mounts = JSON.parse(result.stdout) as DockerMount[]
|
|
220
|
+
const normalizedPath = resolve(containerPath)
|
|
221
|
+
const candidates = mounts
|
|
222
|
+
.filter((mount) => mount.Source && mount.Destination)
|
|
223
|
+
.map((mount) => ({
|
|
224
|
+
source: mount.Source!,
|
|
225
|
+
destination: resolve(mount.Destination!),
|
|
226
|
+
}))
|
|
227
|
+
.sort((a, b) => b.destination.length - a.destination.length)
|
|
228
|
+
|
|
229
|
+
for (const mount of candidates) {
|
|
230
|
+
const rest = relative(mount.destination, normalizedPath)
|
|
231
|
+
if (rest === '' || (rest && !rest.startsWith('..') && !isAbsolute(rest))) {
|
|
232
|
+
return join(mount.source, rest)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.logger.debug(error)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return containerPath
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async resolveDockerNetwork() {
|
|
243
|
+
if (this.config.dockerNetwork) return this.config.dockerNetwork
|
|
244
|
+
const hostname = process.env.HOSTNAME
|
|
245
|
+
if (!hostname) return ''
|
|
246
|
+
|
|
247
|
+
const result = await this.run(this.config.dockerCommand, [
|
|
248
|
+
'container',
|
|
249
|
+
'inspect',
|
|
250
|
+
hostname,
|
|
251
|
+
'--format',
|
|
252
|
+
'{{range $name, $_ := .NetworkSettings.Networks}}{{println $name}}{{end}}',
|
|
253
|
+
], false)
|
|
254
|
+
if (result.code !== 0) return ''
|
|
255
|
+
|
|
256
|
+
const network = result.stdout
|
|
257
|
+
.split(/\r?\n/)
|
|
258
|
+
.map((line) => line.trim())
|
|
259
|
+
.find((line) => line && !['bridge', 'host', 'none'].includes(line))
|
|
260
|
+
if (!network) return ''
|
|
261
|
+
this.pushLog(`using Docker network from Koishi container: ${network}`)
|
|
262
|
+
return network
|
|
263
|
+
}
|
|
264
|
+
|
|
184
265
|
private async containerExists() {
|
|
185
|
-
const result = await this.run(this.config.dockerCommand, ['inspect', this.config.dockerContainerName], false)
|
|
266
|
+
const result = await this.run(this.config.dockerCommand, ['container', 'inspect', this.config.dockerContainerName], false)
|
|
186
267
|
return result.code === 0
|
|
187
268
|
}
|
|
188
269
|
|
package/src/config.ts
CHANGED
|
@@ -68,7 +68,7 @@ export const Config: Schema<Config> = Schema.intersect([
|
|
|
68
68
|
pythonCommand: Schema.string().default('python3').description('用于启动 mai.ko 的 Python 命令。'),
|
|
69
69
|
entryScript: Schema.string().default('bot.py').description('相对于 mai.ko 根目录的启动脚本。'),
|
|
70
70
|
autoStart: Schema.boolean().default(true).description('Koishi ready 后自动启动 mai.ko。'),
|
|
71
|
-
acceptMaibotAgreements: Schema.boolean().default(false).description('
|
|
71
|
+
acceptMaibotAgreements: Schema.boolean().default(false).description('确认同意 MaiBot 的 EULA/Privacy。'),
|
|
72
72
|
}).description('mai.ko 进程'),
|
|
73
73
|
Schema.object({
|
|
74
74
|
apiHost: Schema.string().default('maimai-ko').description('Koishi 连接 maim_message 新版 API Server 的地址。Docker 模式默认使用容器名。'),
|
package/src/console.ts
CHANGED
|
@@ -20,7 +20,7 @@ declare module '@koishijs/plugin-console' {
|
|
|
20
20
|
|
|
21
21
|
export function registerConsole(ctx: Context, config: Config) {
|
|
22
22
|
if (!config.enableConsole) return
|
|
23
|
-
ctx.inject(['console'], (ctx) => {
|
|
23
|
+
ctx.inject(['console', 'maimai'], (ctx) => {
|
|
24
24
|
const options = { authority: config.commandAuthority }
|
|
25
25
|
ctx.console.addListener('mai-ko/status', () => ctx.maimai.getStatus(), options)
|
|
26
26
|
ctx.console.addListener('mai-ko/start', () => ctx.maimai.launch(), options)
|
package/src/service.ts
CHANGED
|
@@ -511,7 +511,10 @@ export class MaibotService extends Service {
|
|
|
511
511
|
if (host === '0.0.0.0') host = '127.0.0.1'
|
|
512
512
|
if (host === '::') host = '::1'
|
|
513
513
|
const normalizedHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host
|
|
514
|
-
|
|
514
|
+
const port = this.pluginConfig.processMode === 'docker' && this.pluginConfig.dockerPublishedWebuiPort > 0
|
|
515
|
+
? this.pluginConfig.dockerPublishedWebuiPort
|
|
516
|
+
: this.pluginConfig.webuiPort
|
|
517
|
+
return `http://${normalizedHost}:${port}`
|
|
515
518
|
}
|
|
516
519
|
|
|
517
520
|
private async handleMaimMessage(message: MaimApiMessage) {
|