koishi-plugin-mai-bridge 0.1.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.
Files changed (80) hide show
  1. package/README.md +110 -0
  2. package/client/index.ts +11 -0
  3. package/client/page.vue +148 -0
  4. package/client/tsconfig.json +11 -0
  5. package/deploy/Dockerfile.maimai-koishi +35 -0
  6. package/dist/index.js +2 -0
  7. package/out/bridge/agreements.d.ts +12 -0
  8. package/out/bridge/agreements.js +47 -0
  9. package/out/bridge/convert.d.ts +25 -0
  10. package/out/bridge/convert.js +274 -0
  11. package/out/bridge/docker.d.ts +30 -0
  12. package/out/bridge/docker.js +229 -0
  13. package/out/bridge/external-logs.d.ts +29 -0
  14. package/out/bridge/external-logs.js +166 -0
  15. package/out/bridge/history.d.ts +27 -0
  16. package/out/bridge/history.js +195 -0
  17. package/out/bridge/logging.d.ts +7 -0
  18. package/out/bridge/logging.js +77 -0
  19. package/out/bridge/prepare.d.ts +42 -0
  20. package/out/bridge/prepare.js +199 -0
  21. package/out/bridge/process.d.ts +31 -0
  22. package/out/bridge/process.js +208 -0
  23. package/out/bridge/routes.d.ts +17 -0
  24. package/out/bridge/routes.js +85 -0
  25. package/out/bridge/runtime-key.d.ts +3 -0
  26. package/out/bridge/runtime-key.js +34 -0
  27. package/out/bridge/transport.d.ts +39 -0
  28. package/out/bridge/transport.js +197 -0
  29. package/out/bridge/webui-token.d.ts +9 -0
  30. package/out/bridge/webui-token.js +36 -0
  31. package/out/commands/index.d.ts +5 -0
  32. package/out/commands/index.js +64 -0
  33. package/out/config.d.ts +52 -0
  34. package/out/config.js +77 -0
  35. package/out/console.d.ts +16 -0
  36. package/out/console.js +24 -0
  37. package/out/index.d.ts +11 -0
  38. package/out/index.js +32 -0
  39. package/out/locales.d.ts +18 -0
  40. package/out/locales.js +21 -0
  41. package/out/service.d.ts +61 -0
  42. package/out/service.js +558 -0
  43. package/out/setup/postinstall.d.ts +1 -0
  44. package/out/setup/postinstall.js +98 -0
  45. package/out/types.d.ts +165 -0
  46. package/out/types.js +2 -0
  47. package/out/utils/command.d.ts +2 -0
  48. package/out/utils/command.js +20 -0
  49. package/out/utils/paths.d.ts +4 -0
  50. package/out/utils/paths.js +17 -0
  51. package/out/utils/spawn.d.ts +13 -0
  52. package/out/utils/spawn.js +53 -0
  53. package/package.json +74 -0
  54. package/patches/maimai-koishi.patch +553 -0
  55. package/scripts/postinstall.js +10 -0
  56. package/src/bridge/agreements.ts +60 -0
  57. package/src/bridge/convert.ts +308 -0
  58. package/src/bridge/docker.ts +244 -0
  59. package/src/bridge/external-logs.ts +171 -0
  60. package/src/bridge/history.ts +199 -0
  61. package/src/bridge/logging.ts +71 -0
  62. package/src/bridge/prepare.ts +226 -0
  63. package/src/bridge/process.ts +214 -0
  64. package/src/bridge/routes.ts +82 -0
  65. package/src/bridge/runtime-key.ts +36 -0
  66. package/src/bridge/transport.ts +211 -0
  67. package/src/bridge/webui-token.ts +41 -0
  68. package/src/commands/index.ts +69 -0
  69. package/src/config.ts +126 -0
  70. package/src/console.ts +39 -0
  71. package/src/index.ts +36 -0
  72. package/src/locales/en-US.yml +6 -0
  73. package/src/locales/zh-CN.yml +6 -0
  74. package/src/locales.ts +19 -0
  75. package/src/service.ts +567 -0
  76. package/src/setup/postinstall.ts +99 -0
  77. package/src/types.ts +178 -0
  78. package/src/utils/command.ts +15 -0
  79. package/src/utils/paths.ts +15 -0
  80. package/src/utils/spawn.ts +59 -0
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # mai-bridge
2
+
3
+ 将 maibot 作为 Docker 托管的消息回复后端运行,使用 mai.ko 桥接 Koishi 与 maibot 的消息收发。
4
+
5
+ mai-bridge 不重写 maibot 的对话链、记忆系统和插件机制。它负责在 Koishi 侧完成三件事:
6
+
7
+ - 自动准备 MaiBot 源码,应用 Koishi 适配补丁。
8
+ - 构建并启动 `maimai-ko` Docker 容器。
9
+ - 把 Koishi 消息转发给 maibot,再把 maibot 回复发回原会话。
10
+
11
+ ## 安装
12
+
13
+ 在 Koishi 控制台打开插件市场,搜索并安装:
14
+
15
+ ```text
16
+ mai-bridge
17
+ ```
18
+
19
+ 插件包名:
20
+
21
+ ```text
22
+ koishi-plugin-mai-bridge
23
+ ```
24
+
25
+ 同时需要启用 Koishi 的 HTTP 服务:
26
+
27
+ ```text
28
+ @koishijs/plugin-http
29
+ ```
30
+
31
+ ## 运行要求
32
+
33
+ 默认模式使用 Docker。
34
+
35
+ Koishi 所在环境需要能执行 Docker 命令,并能访问 Docker socket:
36
+
37
+ ```text
38
+ /var/run/docker.sock
39
+ ```
40
+
41
+ 如果 Koishi 本身也运行在 Docker 内,需要把 Docker socket 挂载进 Koishi 容器,并确保容器里有 `docker`、`git`、`patch` 命令。
42
+
43
+ ## 基础配置
44
+
45
+ 插件市场安装后,进入插件配置页。
46
+
47
+ 常用配置:
48
+
49
+ ```yaml
50
+ processMode: docker
51
+ autoPrepareMaibot: true
52
+ acceptMaibotAgreements: true
53
+ dockerContainerName: maimai-ko
54
+ dockerImageName: maimai-ko:latest
55
+ apiHost: maimai-ko
56
+ apiPort: 8090
57
+ webuiEnabled: true
58
+ webuiHost: 0.0.0.0
59
+ webuiPort: 8002
60
+ messageMode: coexist
61
+ ```
62
+
63
+ 说明:
64
+
65
+ - `processMode` 保持 `docker`,插件会自动拉取 MaiBot、打补丁、构建镜像并启动容器。
66
+ - `acceptMaibotAgreements` 默认关闭。确认接受 MaiBot 的 EULA 和隐私条款后再开启。
67
+ - `apiKey` 可以留空。插件会生成并复用运行期密钥。
68
+ - `dockerNetwork` 按你的 Koishi Docker 网络填写。Koishi 需要能通过 `apiHost` 访问 `maimai-ko`。
69
+ - `webuiPublicUrl` 可填写反代或宿主机映射后的 WebUI 地址,用于 Koishi 控制台显示入口。
70
+
71
+ ## 消息模式
72
+
73
+ ```yaml
74
+ messageMode: coexist
75
+ ```
76
+
77
+ 可选值:
78
+
79
+ - `coexist`:消息转发给 maibot 后,继续交给其他 Koishi 插件处理。
80
+ - `exclusive`:消息转发给 maibot 后,不再交给后续插件。
81
+ - `command`:只有命中指定前缀时才转发。
82
+
83
+ ## 常用命令
84
+
85
+ ```text
86
+ mai.ko.status
87
+ mai.ko.prepare
88
+ mai.ko.docker.start
89
+ mai.ko.docker.stop
90
+ mai.ko.docker.restart
91
+ mai.ko.reconnect
92
+ ```
93
+
94
+ ## 开发
95
+
96
+ ```bash
97
+ npm install
98
+ npm test -- --reporter dot
99
+ npm run build
100
+ ```
101
+
102
+ 发布 npm 前检查打包内容:
103
+
104
+ ```bash
105
+ npm pack --dry-run
106
+ ```
107
+
108
+ ## 许可证
109
+
110
+ GPL-3.0-or-later
@@ -0,0 +1,11 @@
1
+ import { Context } from '@koishijs/client'
2
+ import Page from './page.vue'
3
+
4
+ export default (ctx: Context) => {
5
+ ctx.page({
6
+ name: 'mai.ko',
7
+ path: '/mai-ko',
8
+ authority: 3,
9
+ component: Page,
10
+ })
11
+ }
@@ -0,0 +1,148 @@
1
+ <template>
2
+ <k-layout>
3
+ <k-card>
4
+ <template #header>
5
+ <span>mai.ko 托管状态</span>
6
+ </template>
7
+ <p>进程:{{ status?.process.state || 'unknown' }}</p>
8
+ <p>准备:{{ status?.prepare.state || 'unknown' }} / {{ status?.prepare.root || '-' }}</p>
9
+ <p>Docker:{{ status?.docker.state || 'unknown' }} / {{ status?.docker.containerName || '-' }}</p>
10
+ <p>Bridge:{{ status?.transport.state || 'unknown' }}</p>
11
+ <p v-if="status?.bridge">中转:Koishi 收 {{ status.bridge.koishiReceived }} / 发往 mai.ko {{ status.bridge.maimSent }} / 收到 mai.ko {{ status.bridge.maimReceived }} / 回发 Koishi {{ status.bridge.koishiSent }}</p>
12
+ <p v-if="status?.bridge">中转异常:路由失败 {{ status.bridge.routeMissed }} / 发送失败 {{ status.bridge.sendFailed }}</p>
13
+ <p>WebUI:{{ webuiText }}</p>
14
+ <p v-if="status?.webui.token?.value">WebUI Token:<code>{{ status.webui.token.value }}</code></p>
15
+ <p v-else-if="status?.webui.token?.lastError">WebUI Token:{{ status.webui.token.lastError }}</p>
16
+ <p>PID:{{ status?.process.pid || '-' }}</p>
17
+ <p v-if="status?.process.blockedReason">阻塞原因:{{ status.process.blockedReason }}</p>
18
+ <p v-if="status?.prepare.blockedReason">准备阻塞:{{ status.prepare.blockedReason }}</p>
19
+ <p v-if="status?.prepare.lastError">准备错误:{{ status.prepare.lastError }}</p>
20
+ <p v-if="status?.docker.lastError">Docker 错误:{{ status.docker.lastError }}</p>
21
+ <p v-if="status?.transport.lastError">Bridge 错误:{{ status.transport.lastError }}</p>
22
+ <p v-if="status?.bridge?.lastError">中转错误:{{ status.bridge.lastError }}</p>
23
+ <k-button @click="refresh">刷新</k-button>
24
+ <k-button @click="start">启动</k-button>
25
+ <k-button @click="stop">停止</k-button>
26
+ <k-button @click="restart">重启</k-button>
27
+ <k-button @click="reconnect">重连</k-button>
28
+ <k-button @click="prepare">准备</k-button>
29
+ <k-button @click="dockerStart">Docker 启动</k-button>
30
+ <k-button @click="dockerStop">Docker 停止</k-button>
31
+ <k-button @click="dockerRestart">Docker 重启</k-button>
32
+ <k-button v-if="status?.webui.enabled && status?.webui.url" @click="openWebui">打开 WebUI</k-button>
33
+ <p v-if="status?.logsHint">{{ status.logsHint }}</p>
34
+ <pre v-if="status?.logs.length">{{ status.logs.slice(-20).join('\n') }}</pre>
35
+ </k-card>
36
+ </k-layout>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { computed, onMounted, ref } from 'vue'
41
+ import { send } from '@koishijs/client'
42
+
43
+ interface RuntimeStatus {
44
+ prepare: {
45
+ state: string
46
+ root: string
47
+ gitUrl: string
48
+ gitRef: string
49
+ patchApplied?: boolean
50
+ patchChecksum?: string
51
+ commit?: string
52
+ blockedReason?: string
53
+ lastError?: string
54
+ updatedAt?: number
55
+ }
56
+ docker: {
57
+ state: string
58
+ containerName: string
59
+ imageName: string
60
+ lastError?: string
61
+ updatedAt?: number
62
+ }
63
+ process: {
64
+ state: string
65
+ pid?: number
66
+ blockedReason?: string
67
+ }
68
+ transport: {
69
+ state: string
70
+ lastError?: string
71
+ }
72
+ bridge: {
73
+ koishiReceived: number
74
+ maimSent: number
75
+ maimReceived: number
76
+ koishiSent: number
77
+ routeMissed: number
78
+ sendFailed: number
79
+ lastError?: string
80
+ }
81
+ webui: {
82
+ enabled: boolean
83
+ host: string
84
+ port: number
85
+ url?: string
86
+ publicUrl?: string
87
+ token?: {
88
+ value?: string
89
+ source?: string
90
+ path?: string
91
+ lastError?: string
92
+ loggedAt?: number
93
+ }
94
+ }
95
+ logs: string[]
96
+ logsHint?: string
97
+ }
98
+
99
+ const status = ref<RuntimeStatus>()
100
+ const webuiText = computed(() => {
101
+ if (!status.value) return 'unknown'
102
+ if (!status.value.webui.enabled) return 'disabled'
103
+ return status.value.webui.url || `${status.value.webui.host}:${status.value.webui.port}`
104
+ })
105
+
106
+ async function refresh() {
107
+ status.value = await send('mai-ko/status')
108
+ }
109
+
110
+ async function start() {
111
+ status.value = await send('mai-ko/start')
112
+ }
113
+
114
+ async function stop() {
115
+ status.value = await send('mai-ko/stop')
116
+ }
117
+
118
+ async function restart() {
119
+ status.value = await send('mai-ko/restart')
120
+ }
121
+
122
+ async function reconnect() {
123
+ status.value = await send('mai-ko/reconnect')
124
+ }
125
+
126
+ async function prepare() {
127
+ status.value = await send('mai-ko/prepare')
128
+ }
129
+
130
+ async function dockerStart() {
131
+ status.value = await send('mai-ko/docker-start')
132
+ }
133
+
134
+ async function dockerStop() {
135
+ status.value = await send('mai-ko/docker-stop')
136
+ }
137
+
138
+ async function dockerRestart() {
139
+ status.value = await send('mai-ko/docker-restart')
140
+ }
141
+
142
+ function openWebui() {
143
+ if (!status.value?.webui.url) return
144
+ window.open(status.value.webui.url, '_blank', 'noopener,noreferrer')
145
+ }
146
+
147
+ onMounted(refresh)
148
+ </script>
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": ".",
4
+ "module": "esnext",
5
+ "moduleResolution": "node",
6
+ "types": [
7
+ "@koishijs/client/global"
8
+ ]
9
+ },
10
+ "include": ["."]
11
+ }
@@ -0,0 +1,35 @@
1
+ FROM python:3.13-slim
2
+
3
+ WORKDIR /MaiMBot
4
+
5
+ ENV MAIBOT_LEGACY_0X_UPGRADE_CONFIRMED=1
6
+ ENV PATH="/MaiMBot/.venv/bin:${PATH}"
7
+ ENV UV_INDEX_URL="https://pypi.tuna.tsinghua.edu.cn/simple"
8
+ ENV UV_LINK_MODE=copy
9
+ ENV PIP_INDEX_URL="https://pypi.tuna.tsinghua.edu.cn/simple"
10
+
11
+ COPY pyproject.toml uv.lock ./
12
+
13
+ RUN sed -i \
14
+ -e 's|http://deb.debian.org/debian|http://mirrors.tuna.tsinghua.edu.cn/debian|g' \
15
+ -e 's|http://security.debian.org/debian-security|http://mirrors.tuna.tsinghua.edu.cn/debian-security|g' \
16
+ /etc/apt/sources.list.d/debian.sources
17
+
18
+ RUN apt-get update \
19
+ && apt-get install -y --no-install-recommends git curl ca-certificates \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ RUN pip install --no-cache-dir uv
23
+
24
+ RUN uv sync --frozen --no-dev --no-install-project
25
+
26
+ RUN python -m playwright install-deps chromium \
27
+ && rm -rf /var/lib/apt/lists/*
28
+
29
+ COPY . .
30
+
31
+ RUN chmod +x docker-entrypoint.sh
32
+
33
+ EXPOSE 8000 8001 8090
34
+
35
+ ENTRYPOINT [ "./docker-entrypoint.sh" ]
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{defineComponent as Z,ref as ee,computed as te,onMounted as oe,resolveComponent as p,openBlock as a,createBlock as F,withCtx as r,createVNode as s,createElementVNode as d,toDisplayString as o,createElementBlock as n,createCommentVNode as u,createTextVNode as l}from"vue";import{send as k}from"@koishijs/client";const ae={key:0},re={key:1},ne={key:2},ue={key:3},le={key:4},ie={key:5},se={key:6},ke={key:7},de={key:8},ve={key:9},pe={key:11},ce={key:12},fe=Z({__name:"page",setup(c){const e=ee(),P=te(()=>e.value?e.value.webui.enabled?e.value.webui.url||`${e.value.webui.host}:${e.value.webui.port}`:"disabled":"unknown");async function f(){e.value=await k("mai-ko/status")}async function h(){e.value=await k("mai-ko/start")}async function q(){e.value=await k("mai-ko/stop")}async function z(){e.value=await k("mai-ko/restart")}async function A(){e.value=await k("mai-ko/reconnect")}async function G(){e.value=await k("mai-ko/prepare")}async function J(){e.value=await k("mai-ko/docker-start")}async function L(){e.value=await k("mai-ko/docker-stop")}async function O(){e.value=await k("mai-ko/docker-restart")}function Q(){var v;(v=e.value)!=null&&v.webui.url&&window.open(e.value.webui.url,"_blank","noopener,noreferrer")}return oe(f),(v,t)=>{const i=p("k-button"),X=p("k-card"),Y=p("k-layout");return a(),F(Y,null,{default:r(()=>[s(X,null,{header:r(()=>[...t[0]||(t[0]=[d("span",null,"mai.ko 托管状态",-1)])]),default:r(()=>{var b,m,w,_,y,g,C,E,D,R,x,B,I,N,S,W,T,U,V,H,K,M,$,j;return[d("p",null,"进程:"+o(((b=e.value)==null?void 0:b.process.state)||"unknown"),1),d("p",null,"准备:"+o(((m=e.value)==null?void 0:m.prepare.state)||"unknown")+" / "+o(((w=e.value)==null?void 0:w.prepare.root)||"-"),1),d("p",null,"Docker:"+o(((_=e.value)==null?void 0:_.docker.state)||"unknown")+" / "+o(((y=e.value)==null?void 0:y.docker.containerName)||"-"),1),d("p",null,"Bridge:"+o(((g=e.value)==null?void 0:g.transport.state)||"unknown"),1),(C=e.value)!=null&&C.bridge?(a(),n("p",ae,"中转:Koishi 收 "+o(e.value.bridge.koishiReceived)+" / 发往 mai.ko "+o(e.value.bridge.maimSent)+" / 收到 mai.ko "+o(e.value.bridge.maimReceived)+" / 回发 Koishi "+o(e.value.bridge.koishiSent),1)):u("v-if",true),(E=e.value)!=null&&E.bridge?(a(),n("p",re,"中转异常:路由失败 "+o(e.value.bridge.routeMissed)+" / 发送失败 "+o(e.value.bridge.sendFailed),1)):u("v-if",true),d("p",null,"WebUI:"+o(P.value),1),(R=(D=e.value)==null?void 0:D.webui.token)!=null&&R.value?(a(),n("p",ne,[t[1]||(t[1]=l("WebUI Token:",-1)),d("code",null,o(e.value.webui.token.value),1)])):(B=(x=e.value)==null?void 0:x.webui.token)!=null&&B.lastError?(a(),n("p",ue,"WebUI Token:"+o(e.value.webui.token.lastError),1)):u("v-if",true),d("p",null,"PID:"+o(((I=e.value)==null?void 0:I.process.pid)||"-"),1),(N=e.value)!=null&&N.process.blockedReason?(a(),n("p",le,"阻塞原因:"+o(e.value.process.blockedReason),1)):u("v-if",true),(S=e.value)!=null&&S.prepare.blockedReason?(a(),n("p",ie,"准备阻塞:"+o(e.value.prepare.blockedReason),1)):u("v-if",true),(W=e.value)!=null&&W.prepare.lastError?(a(),n("p",se,"准备错误:"+o(e.value.prepare.lastError),1)):u("v-if",true),(T=e.value)!=null&&T.docker.lastError?(a(),n("p",ke,"Docker 错误:"+o(e.value.docker.lastError),1)):u("v-if",true),(U=e.value)!=null&&U.transport.lastError?(a(),n("p",de,"Bridge 错误:"+o(e.value.transport.lastError),1)):u("v-if",true),(H=(V=e.value)==null?void 0:V.bridge)!=null&&H.lastError?(a(),n("p",ve,"中转错误:"+o(e.value.bridge.lastError),1)):u("v-if",true),s(i,{onClick:f},{default:r(()=>[...t[2]||(t[2]=[l("刷新",-1)])]),_:1}),s(i,{onClick:h},{default:r(()=>[...t[3]||(t[3]=[l("启动",-1)])]),_:1}),s(i,{onClick:q},{default:r(()=>[...t[4]||(t[4]=[l("停止",-1)])]),_:1}),s(i,{onClick:z},{default:r(()=>[...t[5]||(t[5]=[l("重启",-1)])]),_:1}),s(i,{onClick:A},{default:r(()=>[...t[6]||(t[6]=[l("重连",-1)])]),_:1}),s(i,{onClick:G},{default:r(()=>[...t[7]||(t[7]=[l("准备",-1)])]),_:1}),s(i,{onClick:J},{default:r(()=>[...t[8]||(t[8]=[l("Docker 启动",-1)])]),_:1}),s(i,{onClick:L},{default:r(()=>[...t[9]||(t[9]=[l("Docker 停止",-1)])]),_:1}),s(i,{onClick:O},{default:r(()=>[...t[10]||(t[10]=[l("Docker 重启",-1)])]),_:1}),(K=e.value)!=null&&K.webui.enabled&&((M=e.value)!=null&&M.webui.url)?(a(),F(i,{key:10,onClick:Q},{default:r(()=>[...t[11]||(t[11]=[l("打开 WebUI",-1)])]),_:1})):u("v-if",true),($=e.value)!=null&&$.logsHint?(a(),n("p",pe,o(e.value.logsHint),1)):u("v-if",true),(j=e.value)!=null&&j.logs.length?(a(),n("pre",ce,o(e.value.logs.slice(-20).join(`
2
+ `)),1)):u("v-if",true)]}),_:1})]),_:1})}}}),we=c=>{c.page({name:"mai.ko",path:"/mai-ko",authority:3,component:fe})};export{we as default};
@@ -0,0 +1,12 @@
1
+ export interface AgreementOptions {
2
+ accept: boolean;
3
+ env?: NodeJS.ProcessEnv;
4
+ }
5
+ export type AgreementResult = {
6
+ ok: true;
7
+ env: Record<string, string>;
8
+ } | {
9
+ ok: false;
10
+ reason: string;
11
+ };
12
+ export declare function prepareAgreementEnv(root: string, options: AgreementOptions): AgreementResult;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareAgreementEnv = prepareAgreementEnv;
4
+ const crypto_1 = require("crypto");
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ function prepareAgreementEnv(root, options) {
8
+ const eula = (0, path_1.join)(root, 'EULA.md');
9
+ const privacy = (0, path_1.join)(root, 'PRIVACY.md');
10
+ if (!(0, fs_1.existsSync)(eula) || !(0, fs_1.existsSync)(privacy)) {
11
+ return {
12
+ ok: false,
13
+ reason: 'mai.ko EULA/Privacy files are missing. Check maibotRoot or finish MaiBot preparation first.',
14
+ };
15
+ }
16
+ const eulaHash = md5(eula);
17
+ const privacyHash = md5(privacy);
18
+ const currentEnv = options.env || process.env;
19
+ const eulaConfirmed = isAgreementConfirmed(root, 'eula.confirmed', 'EULA_AGREE', eulaHash, currentEnv);
20
+ const privacyConfirmed = isAgreementConfirmed(root, 'privacy.confirmed', 'PRIVACY_AGREE', privacyHash, currentEnv);
21
+ if (eulaConfirmed && privacyConfirmed)
22
+ return { ok: true, env: {} };
23
+ if (!options.accept) {
24
+ return {
25
+ ok: false,
26
+ reason: 'mai.ko EULA/Privacy is not confirmed. Run mai.ko once manually or set acceptMaibotAgreements=true.',
27
+ };
28
+ }
29
+ return {
30
+ ok: true,
31
+ env: {
32
+ EULA_AGREE: eulaHash,
33
+ PRIVACY_AGREE: privacyHash,
34
+ },
35
+ };
36
+ }
37
+ function isAgreementConfirmed(root, fileName, envName, expected, env) {
38
+ if (env[envName] === expected)
39
+ return true;
40
+ const path = (0, path_1.join)(root, fileName);
41
+ if (!(0, fs_1.existsSync)(path))
42
+ return false;
43
+ return (0, fs_1.readFileSync)(path, 'utf8').trim() === expected;
44
+ }
45
+ function md5(path) {
46
+ return (0, crypto_1.createHash)('md5').update((0, fs_1.readFileSync)(path)).digest('hex');
47
+ }
@@ -0,0 +1,25 @@
1
+ import { type Awaitable, type Fragment, type Session } from 'koishi';
2
+ import type { Config } from '../config';
3
+ import type { KoishiRoute, MaimApiMessage } from '../types';
4
+ export interface SessionToMaimOptions {
5
+ resolveImage?: (source: string) => Awaitable<string | undefined>;
6
+ replyContext?: ReplyContext;
7
+ }
8
+ export interface ReplyContext {
9
+ targetMessageId: string;
10
+ targetMessageContent?: string;
11
+ targetMessageSenderId?: string;
12
+ targetMessageSenderNickname?: string;
13
+ targetMessageSenderCardname?: string;
14
+ contextCount?: number;
15
+ }
16
+ export declare function sessionToMaimMessage(session: Session, route: KoishiRoute, apiKey: string, options?: SessionToMaimOptions): Promise<MaimApiMessage>;
17
+ export declare function maimMessageToFragment(message: MaimApiMessage): Fragment;
18
+ export declare function getRouteIdFromMaim(message: MaimApiMessage): string | undefined;
19
+ export declare function getFallbackRouteHints(message: MaimApiMessage): {
20
+ channelId: string | undefined;
21
+ userId: string | undefined;
22
+ selfId: string | undefined;
23
+ isDirect: boolean;
24
+ };
25
+ export declare function shouldForwardSession(session: Session, config: Config): boolean;