mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9204b9b286 | ||
![]() |
da94faa9bb | ||
![]() |
4b53e9a895 | ||
![]() |
f5db96187b | ||
![]() |
857b191b03 | ||
![]() |
09014d1ab5 | ||
![]() |
7557b71869 | ||
![]() |
d07187bd5d | ||
![]() |
2c6a6ba440 | ||
![]() |
4592bf7817 | ||
![]() |
afd6d450a0 | ||
![]() |
b134849dcf | ||
![]() |
e7d0f6d6da | ||
![]() |
16a29b0127 | ||
![]() |
1f5596ef16 | ||
![]() |
bef05432d0 | ||
![]() |
67533d7743 | ||
![]() |
0cc86c6348 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,14 +1,12 @@
|
|||||||
# Develop
|
# Develop
|
||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
pnpm-lock.yaml
|
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
/src/core.lib/common/
|
/src/core.lib/common/
|
||||||
/localdebug/
|
/localdebug/
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
||||||
|
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"explorer.fileNesting.enabled": true,
|
||||||
|
"explorer.fileNesting.expand": false,
|
||||||
|
"explorer.fileNesting.patterns": {
|
||||||
|
".env.universal": ".env.*",
|
||||||
|
"tsconfig.json": "tsconfig.*.json, env.d.ts, vite.config.ts",
|
||||||
|
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
|
||||||
|
}
|
||||||
|
}
|
@@ -32,7 +32,7 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
|
|
||||||
[Server.Other](https://docs.napcat.cyou/)
|
[Server.Other](https://docs.napcat.cyou/)
|
||||||
|
|
||||||
[Qbot.News](https://neko.qbot.news)
|
[NapCat.Wiki](https://www.napcat.wiki)
|
||||||
|
|
||||||
## 回家旅途
|
## 回家旅途
|
||||||
[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
|
[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
|
||||||
@@ -46,7 +46,7 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
## 性能设计/协议标准
|
## 性能设计/协议标准
|
||||||
NapCat 已实现90%+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
|
NapCat 已实现90%+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
|
||||||
|
|
||||||
由此设计带来一系列好处,在开发中,获取群员列表通常小于50Ms,单条文本消息发送在320Ms以内,在1k+的群聊流畅运行,同时带来一些副作用,上报数据中大量使用Magic生成字段,消息Id无法持久,无法上报撤回消息原始内容。
|
由此设计带来一系列好处,在开发中,获取群员列表通常小于50Ms,单条文本消息发送在320Ms以内,在1k+的群聊流畅运行,同时带来一些副作用,消息Id无法持久,无法上报撤回消息原始内容。
|
||||||
|
|
||||||
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
|
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.3.4",
|
"version": "4.3.9",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -11,32 +11,28 @@
|
|||||||
<t-divider align="right">
|
<t-divider align="right">
|
||||||
<t-button @click="addConfig()">
|
<t-button @click="addConfig()">
|
||||||
<template #icon><add-icon /></template>
|
<template #icon><add-icon /></template>
|
||||||
添加配置</t-button
|
添加配置</t-button>
|
||||||
>
|
|
||||||
</t-divider>
|
</t-divider>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="loadPage" ref="setting" class="setting">
|
<div v-if="loadPage" ref="setting" class="setting">
|
||||||
<t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
|
<t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
|
||||||
<t-tab-panel value="all" label="全部"></t-tab-panel>
|
<t-tab-panel value="all" label="全部"></t-tab-panel>
|
||||||
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
|
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
|
||||||
|
<t-tab-panel value="httpSseServers" label="HTTP SSE 服务器"></t-tab-panel>
|
||||||
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
|
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
|
||||||
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
|
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
|
||||||
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
|
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
|
||||||
|
|
||||||
</t-tabs>
|
</t-tabs>
|
||||||
</div>
|
</div>
|
||||||
<t-loading attach="#alice" :loading="!loadPage" :showOverlay="false">
|
<t-loading attach="#alice" :loading="!loadPage" :showOverlay="false">
|
||||||
<div id="alice" v-if="!loadPage" style="height: 80vh;position: relative" ></div>
|
<div id="alice" v-if="!loadPage" style="height: 80vh;position: relative"></div>
|
||||||
</t-loading>
|
</t-loading>
|
||||||
<div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
|
<div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
|
||||||
<div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
|
<div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
|
||||||
<div v-for="(item, index) in cardConfig" :key="index">
|
<div v-for="(item, index) in cardConfig" :key="index">
|
||||||
<t-card
|
<t-card :title="item.name" :description="item.type" :style="{ width: cardWidth + 'px' }"
|
||||||
:title="item.name"
|
:header-bordered="true" class="setting-card">
|
||||||
:description="item.type"
|
|
||||||
:style="{ width: cardWidth + 'px' }"
|
|
||||||
:header-bordered="true"
|
|
||||||
class="setting-card"
|
|
||||||
>
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<t-space>
|
<t-space>
|
||||||
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
|
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
|
||||||
@@ -46,50 +42,35 @@
|
|||||||
</t-space>
|
</t-space>
|
||||||
</template>
|
</template>
|
||||||
<div class="setting-content">
|
<div class="setting-content">
|
||||||
<t-card
|
<t-card class="card-address" :style="{
|
||||||
class="card-address"
|
|
||||||
:style="{
|
|
||||||
borderLeft:
|
borderLeft:
|
||||||
'7px solid ' + (item.enable ? 'var(--td-success-color)' : 'var(--td-error-color)'),
|
'7px solid ' + (item.enable ? 'var(--td-success-color)' : 'var(--td-error-color)'),
|
||||||
}"
|
}">
|
||||||
>
|
|
||||||
<div class="local-box" v-if="item.host && item.port">
|
<div class="local-box" v-if="item.host && item.port">
|
||||||
<server-filled-icon class="local-icon" size="20px" @click="toggleProperty(item, 'enable')"></server-filled-icon>
|
<server-filled-icon class="local-icon" size="20px"
|
||||||
|
@click="toggleProperty(item, 'enable')"></server-filled-icon>
|
||||||
<strong class="local">{{ item.host }}:{{ item.port }}</strong>
|
<strong class="local">{{ item.host }}:{{ item.port }}</strong>
|
||||||
<copy-icon
|
<copy-icon class="copy-icon" size="20px"
|
||||||
class="copy-icon"
|
@click="copyText(item.host + ':' + item.port)"></copy-icon>
|
||||||
size="20px"
|
|
||||||
@click="copyText(item.host + ':' + item.port)"
|
|
||||||
></copy-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="local-box" v-if="item.url">
|
<div class="local-box" v-if="item.url">
|
||||||
<server-filled-icon class="local-icon" size="20px" @click="toggleProperty(item, 'enable')"></server-filled-icon>
|
<server-filled-icon class="local-icon" size="20px"
|
||||||
|
@click="toggleProperty(item, 'enable')"></server-filled-icon>
|
||||||
<strong class="local">{{ item.url }}</strong>
|
<strong class="local">{{ item.url }}</strong>
|
||||||
<copy-icon class="copy-icon" size="20px" @click="copyText(item.url)"></copy-icon>
|
<copy-icon class="copy-icon" size="20px" @click="copyText(item.url)"></copy-icon>
|
||||||
</div>
|
</div>
|
||||||
</t-card>
|
</t-card>
|
||||||
<t-collapse :default-value="[0]" expand-mutex style="margin-top: 10px" class="info-coll">
|
<t-collapse :default-value="[0]" expand-mutex style="margin-top: 10px" class="info-coll">
|
||||||
<t-collapse-panel header="基础信息">
|
<t-collapse-panel header="基础信息">
|
||||||
<t-descriptions
|
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
|
||||||
size="small"
|
class="setting-base-info">
|
||||||
:layout="infoOneCol ? 'vertical' : 'horizontal'"
|
|
||||||
class="setting-base-info"
|
|
||||||
>
|
|
||||||
<t-descriptions-item v-if="item.token" label="连接密钥">
|
<t-descriptions-item v-if="item.token" label="连接密钥">
|
||||||
<div v-if="mediumScreen.matches || largeScreen.matches" class="token-view">
|
<div v-if="mediumScreen.matches || largeScreen.matches" class="token-view">
|
||||||
<span>{{ showToken ? item.token : '******' }}</span>
|
<span>{{ showToken ? item.token : '******' }}</span>
|
||||||
<browse-icon
|
<browse-icon class="browse-icon" v-if="showToken" size="18px"
|
||||||
class="browse-icon"
|
@click="showToken = false"></browse-icon>
|
||||||
v-if="showToken"
|
<browse-off-icon class="browse-icon" v-else size="18px"
|
||||||
size="18px"
|
@click="showToken = true"></browse-off-icon>
|
||||||
@click="showToken = false"
|
|
||||||
></browse-icon>
|
|
||||||
<browse-off-icon
|
|
||||||
class="browse-icon"
|
|
||||||
v-else
|
|
||||||
size="18px"
|
|
||||||
@click="showToken = true"
|
|
||||||
></browse-off-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<t-popup :showArrow="true" trigger="click">
|
<t-popup :showArrow="true" trigger="click">
|
||||||
@@ -106,60 +87,36 @@
|
|||||||
</t-descriptions>
|
</t-descriptions>
|
||||||
</t-collapse-panel>
|
</t-collapse-panel>
|
||||||
<t-collapse-panel header="状态信息">
|
<t-collapse-panel header="状态信息">
|
||||||
<t-descriptions
|
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
|
||||||
size="small"
|
class="setting-base-info">
|
||||||
:layout="infoOneCol ? 'vertical' : 'horizontal'"
|
|
||||||
class="setting-base-info"
|
|
||||||
>
|
|
||||||
<t-descriptions-item v-if="item.hasOwnProperty('debug')" label="调试日志">
|
<t-descriptions-item v-if="item.hasOwnProperty('debug')" label="调试日志">
|
||||||
<t-tag
|
<t-tag :class="item.debug ? 'tag-item-on' : 'tag-item-off'"
|
||||||
:class="item.debug ? 'tag-item-on' : 'tag-item-off'"
|
@click="toggleProperty(item, 'debug')">
|
||||||
@click="toggleProperty(item, 'debug')"
|
{{ item.debug ? '开启' : '关闭' }}</t-tag>
|
||||||
>
|
|
||||||
{{ item.debug ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableWebsocket')"
|
||||||
v-if="item.hasOwnProperty('enableWebsocket')"
|
label="Websocket 功能">
|
||||||
label="Websocket 功能"
|
<t-tag :class="item.enableWebsocket ? 'tag-item-on' : 'tag-item-off'"
|
||||||
>
|
@click="toggleProperty(item, 'enableWebsocket')">
|
||||||
<t-tag
|
{{ item.enableWebsocket ? '启用' : '禁用' }}</t-tag>
|
||||||
:class="item.enableWebsocket ? 'tag-item-on' : 'tag-item-off'"
|
|
||||||
@click="toggleProperty(item, 'enableWebsocket')"
|
|
||||||
>
|
|
||||||
{{ item.enableWebsocket ? '启用' : '禁用' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableCors')" label="跨域放行">
|
||||||
v-if="item.hasOwnProperty('enableCors')"
|
<t-tag :class="item.enableCors ? 'tag-item-on' : 'tag-item-off'"
|
||||||
label="跨域放行"
|
@click="toggleProperty(item, 'enableCors')">
|
||||||
>
|
{{ item.enableCors ? '开启' : '关闭' }}</t-tag>
|
||||||
<t-tag :class="item.enableCors ? 'tag-item-on' : 'tag-item-off'" @click="toggleProperty(item, 'enableCors')">
|
|
||||||
{{ item.enableCors ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
|
||||||
v-if="item.hasOwnProperty('enableForcePushEvent')"
|
label="上报自身消息">
|
||||||
label="上报自身消息"
|
<t-tag :class="item.reportSelfMessage ? 'tag-item-on' : 'tag-item-off'"
|
||||||
>
|
@click="toggleProperty(item, 'reportSelfMessage')">
|
||||||
<t-tag
|
{{ item.reportSelfMessage ? '开启' : '关闭' }}</t-tag>
|
||||||
:class="item.reportSelfMessage ? 'tag-item-on' : 'tag-item-off'"
|
|
||||||
@click="toggleProperty(item, 'reportSelfMessage')"
|
|
||||||
>
|
|
||||||
{{ item.reportSelfMessage ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
|
||||||
v-if="item.hasOwnProperty('enableForcePushEvent')"
|
label="强制推送事件">
|
||||||
label="强制推送事件"
|
<t-tag class="tag-item"
|
||||||
>
|
|
||||||
<t-tag
|
|
||||||
class="tag-item"
|
|
||||||
:class="item.enableForcePushEvent ? 'tag-item-on' : 'tag-item-off'"
|
:class="item.enableForcePushEvent ? 'tag-item-on' : 'tag-item-off'"
|
||||||
@click="toggleProperty(item, 'enableForcePushEvent')"
|
@click="toggleProperty(item, 'enableForcePushEvent')">
|
||||||
>
|
{{ item.enableForcePushEvent ? '开启' : '关闭' }}</t-tag>
|
||||||
{{ item.enableForcePushEvent ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
</t-descriptions>
|
</t-descriptions>
|
||||||
</t-collapse-panel>
|
</t-collapse-panel>
|
||||||
@@ -173,42 +130,27 @@
|
|||||||
<t-empty class="card-none" title="暂无网络配置"> </t-empty>
|
<t-empty class="card-none" title="暂无网络配置"> </t-empty>
|
||||||
</t-card>
|
</t-card>
|
||||||
</div>
|
</div>
|
||||||
<t-dialog
|
<t-dialog v-model:visible="visibleBody" :header="dialogTitle" :destroy-on-close="true"
|
||||||
v-model:visible="visibleBody"
|
:show-in-attached-element="true" :on-confirm="saveConfig" class=".t-dialog__ctx .t-dialog__position">
|
||||||
:header="dialogTitle"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:show-in-attached-element="true"
|
|
||||||
:on-confirm="saveConfig"
|
|
||||||
class=".t-dialog__ctx .t-dialog__position"
|
|
||||||
>
|
|
||||||
<div slot="body" class="dialog-body">
|
<div slot="body" class="dialog-body">
|
||||||
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
|
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
|
||||||
<t-form-item
|
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
|
||||||
style="text-align: left"
|
label="名称" name="name">
|
||||||
:rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
|
|
||||||
label="名称"
|
|
||||||
name="name"
|
|
||||||
>
|
|
||||||
<t-input v-model="newTab.name" />
|
<t-input v-model="newTab.name" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item
|
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
|
||||||
style="text-align: left"
|
label="类型" name="type">
|
||||||
:rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
|
|
||||||
label="类型"
|
|
||||||
name="type"
|
|
||||||
>
|
|
||||||
<t-select v-model="newTab.type" @change="onloadDefault">
|
<t-select v-model="newTab.type" @change="onloadDefault">
|
||||||
<t-option value="httpServers">HTTP 服务器</t-option>
|
<t-option value="httpServers">HTTP 服务器</t-option>
|
||||||
|
<t-option value="httpSseServers">HTTP SSE 服务器</t-option>
|
||||||
<t-option value="httpClients">HTTP 客户端</t-option>
|
<t-option value="httpClients">HTTP 客户端</t-option>
|
||||||
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
||||||
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
||||||
</t-select>
|
</t-select>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<div>
|
<div>
|
||||||
<component
|
<component :is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
|
||||||
:is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
|
:config="newTab.data" />
|
||||||
:config="newTab.data"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</t-form>
|
</t-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,12 +168,10 @@ import {
|
|||||||
BrowseIcon,
|
BrowseIcon,
|
||||||
Wifi1Icon,
|
Wifi1Icon,
|
||||||
} from 'tdesign-icons-vue-next';
|
} from 'tdesign-icons-vue-next';
|
||||||
import { onMounted, onUnmounted, ref, resolveDynamicComponent, watch } from 'vue';
|
|
||||||
import emitter from '@/ts/event-bus';
|
|
||||||
import {
|
import {
|
||||||
mergeNetworkDefaultConfig,
|
loadConfig as loadConfigOnebot,
|
||||||
mergeOneBotConfigs,
|
NetworkAdapterConfig,
|
||||||
NetworkConfig,
|
NetworkConfigKey,
|
||||||
OneBotConfig,
|
OneBotConfig,
|
||||||
} from '../../../src/onebot/config/config';
|
} from '../../../src/onebot/config/config';
|
||||||
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
|
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
|
||||||
@@ -240,6 +180,9 @@ import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.v
|
|||||||
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
|
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
import { QQLoginManager } from '@/backend/shell';
|
import { QQLoginManager } from '@/backend/shell';
|
||||||
|
import { onMounted, onUnmounted, ref, watch, resolveDynamicComponent } from 'vue';
|
||||||
|
import emitter from '@/ts/event-bus';
|
||||||
|
import HttpSseServerComponent from './network/HttpSseServerComponent.vue';
|
||||||
|
|
||||||
const showToken = ref<boolean>(false);
|
const showToken = ref<boolean>(false);
|
||||||
const infoOneCol = ref<boolean>(true);
|
const infoOneCol = ref<boolean>(true);
|
||||||
@@ -256,16 +199,17 @@ const visibleBody = ref<boolean>(false);
|
|||||||
const newTab = ref<{ name: string; data: any; type: string }>({ name: '', data: {}, type: '' });
|
const newTab = ref<{ name: string; data: any; type: string }>({ name: '', data: {}, type: '' });
|
||||||
const dialogTitle = ref<string>('');
|
const dialogTitle = ref<string>('');
|
||||||
|
|
||||||
type ComponentKey = keyof typeof mergeNetworkDefaultConfig;
|
type ComponentKey = Exclude<NetworkConfigKey, 'plugins'>
|
||||||
|
|
||||||
const componentMap: Record<
|
const componentMap: Record<
|
||||||
ComponentKey,
|
ComponentKey,
|
||||||
| typeof HttpServerComponent
|
| typeof HttpServerComponent
|
||||||
| typeof HttpClientComponent
|
| typeof HttpClientComponent
|
||||||
| typeof WebsocketServerComponent
|
| typeof WebsocketServerComponent
|
||||||
| typeof WebsocketClientComponent
|
| typeof WebsocketClientComponent
|
||||||
|
| typeof HttpSseServerComponent
|
||||||
> = {
|
> = {
|
||||||
httpServers: HttpServerComponent,
|
httpServers: HttpServerComponent,
|
||||||
|
httpSseServers: HttpSseServerComponent,
|
||||||
httpClients: HttpClientComponent,
|
httpClients: HttpClientComponent,
|
||||||
websocketServers: WebsocketServerComponent,
|
websocketServers: WebsocketServerComponent,
|
||||||
websocketClients: WebsocketClientComponent,
|
websocketClients: WebsocketClientComponent,
|
||||||
@@ -276,9 +220,10 @@ const operateType = ref<string>('');
|
|||||||
//配置项索引
|
//配置项索引
|
||||||
const configIndex = ref<number>(0);
|
const configIndex = ref<number>(0);
|
||||||
//保存时所用数据
|
//保存时所用数据
|
||||||
const networkConfig: NetworkConfig & { [key: string]: any } = {
|
const networkConfig: { [key: string]: any } = {
|
||||||
websocketClients: [],
|
websocketClients: [],
|
||||||
websocketServers: [],
|
websocketServers: [],
|
||||||
|
httpSseServers: [],
|
||||||
httpClients: [],
|
httpClients: [],
|
||||||
httpServers: [],
|
httpServers: [],
|
||||||
};
|
};
|
||||||
@@ -289,6 +234,7 @@ const WebConfg = ref(
|
|||||||
['all', []],
|
['all', []],
|
||||||
['httpServers', []],
|
['httpServers', []],
|
||||||
['httpClients', []],
|
['httpClients', []],
|
||||||
|
['httpSseServers', []],
|
||||||
['websocketServers', []],
|
['websocketServers', []],
|
||||||
['websocketClients', []],
|
['websocketClients', []],
|
||||||
])
|
])
|
||||||
@@ -296,6 +242,7 @@ const WebConfg = ref(
|
|||||||
const typeCh: Record<ComponentKey, string> = {
|
const typeCh: Record<ComponentKey, string> = {
|
||||||
httpServers: 'HTTP 服务器',
|
httpServers: 'HTTP 服务器',
|
||||||
httpClients: 'HTTP 客户端',
|
httpClients: 'HTTP 客户端',
|
||||||
|
httpSseServers: 'HTTP SSE 服务器',
|
||||||
websocketServers: 'WebSocket 服务器',
|
websocketServers: 'WebSocket 服务器',
|
||||||
websocketClients: 'WebSocket 客户端',
|
websocketClients: 'WebSocket 客户端',
|
||||||
};
|
};
|
||||||
@@ -315,15 +262,12 @@ const addConfig = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const editConfig = (item: any) => {
|
const editConfig = (item: any) => {
|
||||||
dialogTitle.value = '修改配置';
|
dialogTitle.value = '编辑配置';
|
||||||
const type = getKeyByValue(typeCh, item.type);
|
newTab.value = { name: item.name, data: { ...item }, type: getKeyByValue(typeCh, item.type) || '' };
|
||||||
if (type) {
|
|
||||||
newTab.value = { name: item.name, data: item, type: type };
|
|
||||||
}
|
|
||||||
operateType.value = 'edit';
|
operateType.value = 'edit';
|
||||||
configIndex.value = networkConfig[newTab.value.type].findIndex((obj: any) => obj.name === item.name);
|
|
||||||
visibleBody.value = true;
|
visibleBody.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleProperty = async (item: any, tagData: string) => {
|
const toggleProperty = async (item: any, tagData: string) => {
|
||||||
const type = getKeyByValue(typeCh, item.type);
|
const type = getKeyByValue(typeCh, item.type);
|
||||||
const newData = { ...item };
|
const newData = { ...item };
|
||||||
@@ -349,11 +293,12 @@ const delConfig = (item: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectType = (key: ComponentKey) => {
|
const selectType = (key: ComponentKey) => {
|
||||||
cardConfig.value = WebConfg.value.get(key);
|
console.log(WebConfg.value, key, WebConfg.value.get(key));
|
||||||
|
cardConfig.value = WebConfg.value.get(key) || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const onloadDefault = (key: ComponentKey) => {
|
const onloadDefault = (key: ComponentKey) => {
|
||||||
newTab.value.data = structuredClone(mergeNetworkDefaultConfig[key]);
|
newTab.value.data = {};
|
||||||
};
|
};
|
||||||
//检测重名
|
//检测重名
|
||||||
const checkName = (name: string) => {
|
const checkName = (name: string) => {
|
||||||
@@ -383,7 +328,7 @@ const saveConfig = async () => {
|
|||||||
}
|
}
|
||||||
const userConfig = await getOB11Config();
|
const userConfig = await getOB11Config();
|
||||||
if (!userConfig) return;
|
if (!userConfig) return;
|
||||||
userConfig.network = networkConfig;
|
userConfig.network = networkConfig as any;
|
||||||
const success = await setOB11Config(userConfig);
|
const success = await setOB11Config(userConfig);
|
||||||
if (success) {
|
if (success) {
|
||||||
operateType.value = '';
|
operateType.value = '';
|
||||||
@@ -416,12 +361,12 @@ const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//获取卡片数据
|
//获取卡片数据
|
||||||
const getAllData = (data: NetworkConfig) => {
|
const getAllData = (data: { [key: string]: Array<NetworkAdapterConfig> }) => {
|
||||||
cardConfig.value = [];
|
cardConfig.value = [];
|
||||||
WebConfg.value.set('all', []);
|
WebConfg.value.set('all', []);
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
const configs = data[key as keyof NetworkConfig];
|
const configs = data[key as keyof NetworkAdapterConfig];
|
||||||
if (key in mergeNetworkDefaultConfig) {
|
if (key in networkConfig) {
|
||||||
networkConfig[key] = [...configs];
|
networkConfig[key] = [...configs];
|
||||||
const newConfigsArray = configs.map((config: any) => ({
|
const newConfigsArray = configs.map((config: any) => ({
|
||||||
...config,
|
...config,
|
||||||
@@ -442,13 +387,12 @@ const loadConfig = async () => {
|
|||||||
try {
|
try {
|
||||||
const userConfig = await getOB11Config();
|
const userConfig = await getOB11Config();
|
||||||
if (!userConfig) return;
|
if (!userConfig) return;
|
||||||
const mergedConfig = mergeOneBotConfigs(userConfig);
|
const mergedConfig = loadConfigOnebot(userConfig);
|
||||||
getAllData(mergedConfig.network);
|
getAllData(mergedConfig.network);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading config:', error);
|
console.error('Error loading config:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyText = async (text: string) => {
|
const copyText = async (text: string) => {
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
@@ -474,9 +418,9 @@ const handleResize = () => {
|
|||||||
cardWidth.value = tabsWidth.value;
|
cardWidth.value = tabsWidth.value;
|
||||||
}
|
}
|
||||||
loadPage.value = true;
|
loadPage.value = true;
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
cardHeight.value = window.innerHeight - (headerBox.value?.offsetHeight ?? 0) - (setting.value?.offsetHeight ?? 0) - 21;
|
cardHeight.value = window.innerHeight - (headerBox.value?.offsetHeight ?? 0) - (setting.value?.offsetHeight ?? 0) - 21;
|
||||||
},300)
|
}, 300)
|
||||||
};
|
};
|
||||||
emitter.on('sendWidth', (width) => {
|
emitter.on('sendWidth', (width) => {
|
||||||
if (typeof width === 'string') {
|
if (typeof width === 'string') {
|
||||||
@@ -486,30 +430,30 @@ emitter.on('sendWidth', (width) => {
|
|||||||
});
|
});
|
||||||
watch(menuWidth, (newValue, oldValue) => {
|
watch(menuWidth, (newValue, oldValue) => {
|
||||||
loadPage.value = false;
|
loadPage.value = false;
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
handleResize();
|
handleResize();
|
||||||
},300)
|
}, 300)
|
||||||
});
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
const cachedWidth = localStorage.getItem('menuWidth');
|
const cachedWidth = localStorage.getItem('menuWidth');
|
||||||
if (cachedWidth) {
|
if (cachedWidth) {
|
||||||
menuWidth.value = parseInt(cachedWidth);
|
menuWidth.value = parseInt(cachedWidth);
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
handleResize();
|
handleResize();
|
||||||
},300)
|
}, 300)
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', ()=>{
|
window.addEventListener('resize', () => {
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
handleResize();
|
handleResize();
|
||||||
},300)
|
}, 300)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', ()=>{
|
window.removeEventListener('resize', () => {
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
handleResize();
|
handleResize();
|
||||||
},300)
|
}, 300)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -550,9 +494,11 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.local-icon {
|
.local-icon {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.local {
|
.local {
|
||||||
flex: 6;
|
flex: 6;
|
||||||
margin: 0 10px 0 10px;
|
margin: 0 10px 0 10px;
|
||||||
@@ -579,22 +525,26 @@ onUnmounted(() => {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-item-on{
|
.tag-item-on {
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-image: linear-gradient(to top, #0ba360 0%, #3cba92 100%) !important;
|
background-image: linear-gradient(to top, #0ba360 0%, #3cba92 100%) !important;
|
||||||
}
|
}
|
||||||
.tag-item-off{
|
|
||||||
|
.tag-item-off {
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-image: linear-gradient(to top, rgba(255, 8, 68, 0.93) 0%, #D54941 100%) !important;
|
background-image: linear-gradient(to top, rgba(255, 8, 68, 0.93) 0%, #D54941 100%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browse-icon {
|
.browse-icon {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.t-dialog__ctx .t-dialog__position) {
|
:global(.t-dialog__ctx .t-dialog__position) {
|
||||||
padding: 48px 10px;
|
padding: 48px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.setting-box {
|
.setting-box {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@@ -644,7 +594,7 @@ onUnmounted(() => {
|
|||||||
padding: 0 var(--td-comp-paddingLR-l) !important;
|
padding: 0 var(--td-comp-paddingLR-l) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-base-info tr > td:last-child {
|
.setting-base-info tr>td:last-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,20 +27,32 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { HttpClientConfig } from '../../../../src/onebot/config/config';
|
import { HttpClientConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const defaultConfig: HttpClientConfig = {
|
||||||
|
name: 'http-client',
|
||||||
|
enable: false,
|
||||||
|
url: 'http://localhost:8080',
|
||||||
|
messagePostFormat: 'array',
|
||||||
|
reportSelfMessage: false,
|
||||||
|
token: '',
|
||||||
|
debug: false
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
config: HttpClientConfig;
|
config: HttpClientConfig;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const config = ref(Object.assign({}, defaultConfig, props.config));
|
||||||
|
|
||||||
const messageFormatOptions = ref([
|
const messageFormatOptions = ref([
|
||||||
{ label: 'Array', value: 'array' },
|
{ label: 'Array', value: 'array' },
|
||||||
{ label: 'String', value: 'string' },
|
{ label: 'String', value: 'string' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.config.messagePostFormat,
|
() => config.value.messagePostFormat,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue !== 'array' && newValue !== 'string') {
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
props.config.messagePostFormat = 'array';
|
config.value.messagePostFormat = 'array';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -33,20 +33,34 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { HttpServerConfig } from '../../../../src/onebot/config/config';
|
import { HttpServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const defaultConfig: HttpServerConfig = {
|
||||||
|
name: 'http-server',
|
||||||
|
enable: false,
|
||||||
|
port: 3000,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
enableCors: true,
|
||||||
|
enableWebsocket: true,
|
||||||
|
messagePostFormat: 'array',
|
||||||
|
token: '',
|
||||||
|
debug: false
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
config: HttpServerConfig;
|
config: HttpServerConfig;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const config = ref(Object.assign({}, defaultConfig, props.config));
|
||||||
|
|
||||||
const messageFormatOptions = ref([
|
const messageFormatOptions = ref([
|
||||||
{ label: 'Array', value: 'array' },
|
{ label: 'Array', value: 'array' },
|
||||||
{ label: 'String', value: 'string' },
|
{ label: 'String', value: 'string' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.config.messagePostFormat,
|
() => config.value.messagePostFormat,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue !== 'array' && newValue !== 'string') {
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
props.config.messagePostFormat = 'array';
|
config.value.messagePostFormat = 'array';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
73
napcat.webui/src/pages/network/HttpSseServerComponent.vue
Normal file
73
napcat.webui/src/pages/network/HttpSseServerComponent.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<t-form labelAlign="left">
|
||||||
|
<t-form-item label="启用">
|
||||||
|
<t-switch v-model="config.enable" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="端口">
|
||||||
|
<t-input v-model.number="config.port" type="number" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="主机">
|
||||||
|
<t-input v-model="config.host" type="text" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="报告自身消息">
|
||||||
|
<t-switch v-model="config.reportSelfMessage" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="启用 CORS">
|
||||||
|
<t-switch v-model="config.enableCors" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="启用 WS">
|
||||||
|
<t-switch v-model="config.enableWebsocket" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="消息格式">
|
||||||
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="Token">
|
||||||
|
<t-input v-model="config.token" type="text" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="调试模式">
|
||||||
|
<t-switch v-model="config.debug" />
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { HttpSseServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const defaultConfig: HttpSseServerConfig = {
|
||||||
|
name: 'http-sse-server',
|
||||||
|
enable: false,
|
||||||
|
port: 3000,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
enableCors: true,
|
||||||
|
enableWebsocket: true,
|
||||||
|
messagePostFormat: 'array',
|
||||||
|
token: '',
|
||||||
|
debug: false,
|
||||||
|
reportSelfMessage: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: HttpSseServerConfig;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const config = ref(Object.assign({}, defaultConfig, props.config));
|
||||||
|
|
||||||
|
const messageFormatOptions = ref([
|
||||||
|
{ label: 'Array', value: 'array' },
|
||||||
|
{ label: 'String', value: 'string' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => config.value.messagePostFormat,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
|
config.value.messagePostFormat = 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@@ -30,20 +30,34 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { WebsocketClientConfig } from '../../../../src/onebot/config/config';
|
import { WebsocketClientConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const defaultConfig: WebsocketClientConfig = {
|
||||||
|
name: 'websocket-client',
|
||||||
|
enable: false,
|
||||||
|
url: 'ws://localhost:8082',
|
||||||
|
messagePostFormat: 'array',
|
||||||
|
reportSelfMessage: false,
|
||||||
|
reconnectInterval: 5000,
|
||||||
|
token: '',
|
||||||
|
debug: false,
|
||||||
|
heartInterval: 30000
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
config: WebsocketClientConfig;
|
config: WebsocketClientConfig;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const config = ref(Object.assign({}, defaultConfig, props.config));
|
||||||
|
|
||||||
const messageFormatOptions = ref([
|
const messageFormatOptions = ref([
|
||||||
{ label: 'Array', value: 'array' },
|
{ label: 'Array', value: 'array' },
|
||||||
{ label: 'String', value: 'string' },
|
{ label: 'String', value: 'string' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.config.messagePostFormat,
|
() => config.value.messagePostFormat,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue !== 'array' && newValue !== 'string') {
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
props.config.messagePostFormat = 'array';
|
config.value.messagePostFormat = 'array';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -36,20 +36,35 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { WebsocketServerConfig } from '../../../../src/onebot/config/config';
|
import { WebsocketServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
|
const defaultConfig: WebsocketServerConfig = {
|
||||||
|
name: 'websocket-server',
|
||||||
|
enable: false,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 3001,
|
||||||
|
messagePostFormat: 'array',
|
||||||
|
reportSelfMessage: false,
|
||||||
|
token: '',
|
||||||
|
enableForcePushEvent: true,
|
||||||
|
debug: false,
|
||||||
|
heartInterval: 30000
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
config: WebsocketServerConfig;
|
config: WebsocketServerConfig;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const config = ref(Object.assign({}, defaultConfig, props.config));
|
||||||
|
|
||||||
const messageFormatOptions = ref([
|
const messageFormatOptions = ref([
|
||||||
{ label: 'Array', value: 'array' },
|
{ label: 'Array', value: 'array' },
|
||||||
{ label: 'String', value: 'string' },
|
{ label: 'String', value: 'string' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.config.messagePostFormat,
|
() => config.value.messagePostFormat,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue !== 'array' && newValue !== 'string') {
|
if (newValue !== 'array' && newValue !== 'string') {
|
||||||
props.config.messagePostFormat = 'array';
|
config.value.messagePostFormat = 'array';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.3.4",
|
"version": "4.3.9",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
|
@@ -1,6 +1,72 @@
|
|||||||
import { Peer } from '@/core';
|
import { Peer } from '@/core';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
class TimeBasedCache<K, V> {
|
||||||
|
private cache = new Map<K, { value: V, timestamp: number, frequency: number }>();
|
||||||
|
private keyList = new Set<K>();
|
||||||
|
private operationCount = 0;
|
||||||
|
|
||||||
|
constructor(private maxCapacity: number, private ttl: number = 30 * 1000 * 60, private cleanupCount: number = 10) {}
|
||||||
|
|
||||||
|
public put(key: K, value: V): void {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const cacheEntry = { value, timestamp, frequency: 1 };
|
||||||
|
this.cache.set(key, cacheEntry);
|
||||||
|
this.keyList.add(key);
|
||||||
|
this.operationCount++;
|
||||||
|
if (this.keyList.size > this.maxCapacity) this.evict();
|
||||||
|
if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(key: K): V | undefined {
|
||||||
|
const entry = this.cache.get(key);
|
||||||
|
if (entry && Date.now() - entry.timestamp < this.ttl) {
|
||||||
|
entry.timestamp = Date.now();
|
||||||
|
entry.frequency++;
|
||||||
|
this.operationCount++;
|
||||||
|
if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount);
|
||||||
|
return entry.value;
|
||||||
|
} else {
|
||||||
|
this.deleteKey(key);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanup(count: number): void {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
let cleaned = 0;
|
||||||
|
for (const key of this.keyList) {
|
||||||
|
if (cleaned >= count) break;
|
||||||
|
const entry = this.cache.get(key);
|
||||||
|
if (entry && currentTime - entry.timestamp >= this.ttl) {
|
||||||
|
this.deleteKey(key);
|
||||||
|
cleaned++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.operationCount = 0; // 重置操作计数器
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteKey(key: K): void {
|
||||||
|
this.cache.delete(key);
|
||||||
|
this.keyList.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private evict(): void {
|
||||||
|
while (this.keyList.size > this.maxCapacity) {
|
||||||
|
let oldestKey: K | undefined;
|
||||||
|
let minFrequency = Infinity;
|
||||||
|
for (const key of this.keyList) {
|
||||||
|
const entry = this.cache.get(key);
|
||||||
|
if (entry && entry.frequency < minFrequency) {
|
||||||
|
minFrequency = entry.frequency;
|
||||||
|
oldestKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldestKey !== undefined) this.deleteKey(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface FileUUIDData {
|
interface FileUUIDData {
|
||||||
peer: Peer;
|
peer: Peer;
|
||||||
modelId?: string;
|
modelId?: string;
|
||||||
@@ -10,49 +76,11 @@ interface FileUUIDData {
|
|||||||
fileUUID?: string;
|
fileUUID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeBasedCache<K, V> {
|
|
||||||
private cache: Map<K, { value: V, timestamp: number }>;
|
|
||||||
private ttl: number;
|
|
||||||
|
|
||||||
constructor(ttl: number) {
|
|
||||||
this.cache = new Map();
|
|
||||||
this.ttl = ttl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public put(key: K, value: V): void {
|
|
||||||
const timestamp = Date.now();
|
|
||||||
this.cache.set(key, { value, timestamp });
|
|
||||||
this.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(key: K): V | undefined {
|
|
||||||
const entry = this.cache.get(key);
|
|
||||||
if (entry) {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
if (currentTime - entry.timestamp < this.ttl) {
|
|
||||||
return entry.value;
|
|
||||||
} else {
|
|
||||||
this.cache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private cleanup(): void {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
for (const [key, entry] of this.cache.entries()) {
|
|
||||||
if (currentTime - entry.timestamp >= this.ttl) {
|
|
||||||
this.cache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileUUIDManager {
|
class FileUUIDManager {
|
||||||
private cache: TimeBasedCache<string, FileUUIDData>;
|
private cache: TimeBasedCache<string, FileUUIDData>;
|
||||||
|
|
||||||
constructor(ttl: number) {
|
constructor(ttl: number) {
|
||||||
this.cache = new TimeBasedCache<string, FileUUIDData>(ttl);
|
this.cache = new TimeBasedCache<string, FileUUIDData>(5000, ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public encode(data: FileUUIDData, endString: string = "", customUUID?: string): string {
|
public encode(data: FileUUIDData, endString: string = "", customUUID?: string): string {
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.3.4';
|
export const napCatVersion = '4.3.9';
|
||||||
|
@@ -462,7 +462,7 @@ export class NTQQFileApi {
|
|||||||
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
||||||
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError('获取rkey失败 Fallback Old Mode', e);
|
this.context.logger.logDebug('获取rkey失败 Fallback Old Mode', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,6 +15,10 @@ export class RkeyManager {
|
|||||||
private_rkey: '',
|
private_rkey: '',
|
||||||
expired_time: 0,
|
expired_time: 0,
|
||||||
};
|
};
|
||||||
|
private failureCount: number = 0;
|
||||||
|
private lastFailureTimestamp: number = 0;
|
||||||
|
private readonly FAILURE_LIMIT: number = 8;
|
||||||
|
private readonly ONE_DAY: number = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
constructor(serverUrl: string[], logger: LogWrapper) {
|
constructor(serverUrl: string[], logger: LogWrapper) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -22,11 +26,21 @@ export class RkeyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getRkey() {
|
async getRkey() {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (now - this.lastFailureTimestamp > this.ONE_DAY) {
|
||||||
|
this.failureCount = 0; // 重置失败计数器
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.failureCount >= this.FAILURE_LIMIT) {
|
||||||
|
this.logger.logError(`[Rkey] 服务存在异常, 图片使用FallBack机制`);
|
||||||
|
throw new Error('获取rkey失败次数过多,请稍后再试');
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isExpired()) {
|
if (this.isExpired()) {
|
||||||
try {
|
try {
|
||||||
await this.refreshRkey();
|
await this.refreshRkey();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
throw new Error(`${e}`);//外抛
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.rkeyData;
|
return this.rkeyData;
|
||||||
@@ -34,7 +48,6 @@ export class RkeyManager {
|
|||||||
|
|
||||||
isExpired(): boolean {
|
isExpired(): boolean {
|
||||||
const now = new Date().getTime() / 1000;
|
const now = new Date().getTime() / 1000;
|
||||||
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`);
|
|
||||||
return now > this.rkeyData.expired_time;
|
return now > this.rkeyData.expired_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,14 +61,17 @@ export class RkeyManager {
|
|||||||
private_rkey: temp.private_rkey.slice(6),
|
private_rkey: temp.private_rkey.slice(6),
|
||||||
expired_time: temp.expired_time
|
expired_time: temp.expired_time
|
||||||
};
|
};
|
||||||
|
this.failureCount = 0;
|
||||||
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.logError(`[Rkey] Get Rkey ${url} Error `, e);
|
this.logger.logError(`[Rkey] 异常服务 ${url} 异常 / `, e);
|
||||||
|
this.failureCount++;
|
||||||
|
this.lastFailureTimestamp = new Date().getTime();
|
||||||
//是否为最后一个url
|
//是否为最后一个url
|
||||||
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
||||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -18,7 +18,7 @@ export interface BuddyCategoryType {
|
|||||||
export interface CoreInfo {
|
export interface CoreInfo {
|
||||||
uid: string;
|
uid: string;
|
||||||
uin: string;
|
uin: string;
|
||||||
nick: string;
|
nick?: string;
|
||||||
remark: string;
|
remark: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
import '@/universal/napcat';
|
@@ -2,6 +2,7 @@ import { ActionName, BaseCheckResult } from './router';
|
|||||||
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
|
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { NapCatOneBot11Adapter, OB11Return } from '@/onebot';
|
import { NapCatOneBot11Adapter, OB11Return } from '@/onebot';
|
||||||
|
import { NetworkAdapterConfig } from '../config/config';
|
||||||
|
|
||||||
export class OB11Response {
|
export class OB11Response {
|
||||||
private static createResponse<T>(data: T, status: string, retcode: number, message: string = '', echo: any = null): OB11Return<T> {
|
private static createResponse<T>(data: T, status: string, retcode: number, message: string = '', echo: any = null): OB11Return<T> {
|
||||||
@@ -55,13 +56,13 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
return { valid: true };
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(payload: PayloadType, adaptername: string): Promise<OB11Return<ReturnDataType | null>> {
|
public async handle(payload: PayloadType, adaptername: string, config: NetworkAdapterConfig): Promise<OB11Return<ReturnDataType | null>> {
|
||||||
const result = await this.check(payload);
|
const result = await this.check(payload);
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
return OB11Response.error(result.message, 400);
|
return OB11Response.error(result.message, 400);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resData = await this._handle(payload, adaptername);
|
const resData = await this._handle(payload, adaptername, config);
|
||||||
return OB11Response.ok(resData);
|
return OB11Response.ok(resData);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.core.context.logger.logError('发生错误', e);
|
this.core.context.logger.logError('发生错误', e);
|
||||||
@@ -69,13 +70,13 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async websocketHandle(payload: PayloadType, echo: any, adaptername: string): Promise<OB11Return<ReturnDataType | null>> {
|
public async websocketHandle(payload: PayloadType, echo: any, adaptername: string, config: NetworkAdapterConfig): Promise<OB11Return<ReturnDataType | null>> {
|
||||||
const result = await this.check(payload);
|
const result = await this.check(payload);
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
return OB11Response.error(result.message, 1400, echo);
|
return OB11Response.error(result.message, 1400, echo);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resData = await this._handle(payload, adaptername);
|
const resData = await this._handle(payload, adaptername, config);
|
||||||
return OB11Response.ok(resData, echo);
|
return OB11Response.ok(resData, echo);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.core.context.logger.logError('发生错误', e);
|
this.core.context.logger.logError('发生错误', e);
|
||||||
@@ -83,5 +84,5 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract _handle(payload: PayloadType, adaptername: string): Promise<ReturnDataType>;
|
abstract _handle(payload: PayloadType, adaptername: string, config: NetworkAdapterConfig): Promise<ReturnDataType>;
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,9 @@ import { OB11Message } from '@/onebot';
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { ChatType } from '@/core/types';
|
import { ChatType } from '@/core/types';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { AdapterConfigWrap } from '@/onebot/config/config';
|
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
import { NetworkAdapterConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
messages: OB11Message[];
|
messages: OB11Message[];
|
||||||
@@ -23,7 +24,7 @@ export default class GetFriendMsgHistory extends OneBotAction<Payload, Response>
|
|||||||
actionName = ActionName.GetFriendMsgHistory;
|
actionName = ActionName.GetFriendMsgHistory;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload, adapter: string): Promise<Response> {
|
async _handle(payload: Payload, adapter: string, config: NetworkAdapterConfig): Promise<Response> {
|
||||||
//处理参数
|
//处理参数
|
||||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
|
|
||||||
@@ -42,10 +43,9 @@ export default class GetFriendMsgHistory extends OneBotAction<Payload, Response>
|
|||||||
await Promise.all(msgList.map(async msg => {
|
await Promise.all(msgList.map(async msg => {
|
||||||
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
||||||
}));
|
}));
|
||||||
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
|
|
||||||
//烘焙消息
|
//烘焙消息
|
||||||
const ob11MsgList = (await Promise.all(
|
const ob11MsgList = (await Promise.all(
|
||||||
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array')))
|
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat)))
|
||||||
).filter(msg => msg !== undefined);
|
).filter(msg => msg !== undefined);
|
||||||
return { 'messages': ob11MsgList };
|
return { 'messages': ob11MsgList };
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,8 @@ import { OB11Message } from '@/onebot';
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { ChatType, Peer } from '@/core/types';
|
import { ChatType, Peer } from '@/core/types';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { AdapterConfigWrap } from '@/onebot/config/config';
|
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
import { NetworkAdapterConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
messages: OB11Message[];
|
messages: OB11Message[];
|
||||||
@@ -25,7 +25,7 @@ export default class GoCQHTTPGetGroupMsgHistory extends OneBotAction<Payload, Re
|
|||||||
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory;
|
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload, adapter: string): Promise<Response> {
|
async _handle(payload: Payload, adapter: string, config: NetworkAdapterConfig): Promise<Response> {
|
||||||
//处理参数
|
//处理参数
|
||||||
const isReverseOrder = typeof payload.reverseOrder === 'string' ? payload.reverseOrder === 'true' : !!payload.reverseOrder;
|
const isReverseOrder = typeof payload.reverseOrder === 'string' ? payload.reverseOrder === 'true' : !!payload.reverseOrder;
|
||||||
const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
|
const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
|
||||||
@@ -41,11 +41,9 @@ export default class GoCQHTTPGetGroupMsgHistory extends OneBotAction<Payload, Re
|
|||||||
await Promise.all(msgList.map(async msg => {
|
await Promise.all(msgList.map(async msg => {
|
||||||
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
||||||
}));
|
}));
|
||||||
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
|
|
||||||
//烘焙消息
|
//烘焙消息
|
||||||
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
|
|
||||||
const ob11MsgList = (await Promise.all(
|
const ob11MsgList = (await Promise.all(
|
||||||
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, msgFormat)))
|
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat)))
|
||||||
).filter(msg => msg !== undefined);
|
).filter(msg => msg !== undefined);
|
||||||
return { 'messages': ob11MsgList };
|
return { 'messages': ob11MsgList };
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,8 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { AdapterConfigWrap } from '@/onebot/config/config';
|
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
import { NetworkAdapterConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const SchemaData = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||||
@@ -27,9 +27,7 @@ export class GetGroupEssence extends OneBotAction<Payload, any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handle(payload: Payload, adapter: string) {
|
async _handle(payload: Payload, adapter: string, config: NetworkAdapterConfig) {
|
||||||
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
|
|
||||||
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
|
|
||||||
const msglist = (await this.core.apis.WebApi.getGroupEssenceMsgAll(payload.group_id.toString())).flatMap((e) => e.data.msg_list);
|
const msglist = (await this.core.apis.WebApi.getGroupEssenceMsgAll(payload.group_id.toString())).flatMap((e) => e.data.msg_list);
|
||||||
if (!msglist) {
|
if (!msglist) {
|
||||||
throw new Error('获取失败');
|
throw new Error('获取失败');
|
||||||
@@ -50,7 +48,7 @@ export class GetGroupEssence extends OneBotAction<Payload, any> {
|
|||||||
operator_nick: msg.add_digest_nick,
|
operator_nick: msg.add_digest_nick,
|
||||||
message_id: message_id,
|
message_id: message_id,
|
||||||
operator_time: msg.add_digest_time,
|
operator_time: msg.add_digest_time,
|
||||||
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, msgFormat))?.message
|
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, config.messagePostFormat))?.message
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const msgTempData = JSON.stringify({
|
const msgTempData = JSON.stringify({
|
||||||
|
@@ -3,8 +3,8 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { RawMessage } from '@/core';
|
import { RawMessage } from '@/core';
|
||||||
import { AdapterConfigWrap } from '@/onebot/config/config';
|
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
import { NetworkAdapterConfig } from '@/onebot/config/config';
|
||||||
|
|
||||||
export type ReturnDataType = OB11Message
|
export type ReturnDataType = OB11Message
|
||||||
|
|
||||||
@@ -18,10 +18,8 @@ class GetMsg extends OneBotAction<Payload, OB11Message> {
|
|||||||
actionName = ActionName.GetMsg;
|
actionName = ActionName.GetMsg;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload, adapter: string) {
|
async _handle(payload: Payload, adapter: string, config: NetworkAdapterConfig) {
|
||||||
// log("history msg ids", Object.keys(msgHistory));
|
// log("history msg ids", Object.keys(msgHistory));
|
||||||
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
|
|
||||||
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
|
|
||||||
if (!payload.message_id) {
|
if (!payload.message_id) {
|
||||||
throw Error('参数message_id不能为空');
|
throw Error('参数message_id不能为空');
|
||||||
}
|
}
|
||||||
@@ -38,7 +36,7 @@ class GetMsg extends OneBotAction<Payload, OB11Message> {
|
|||||||
} else {
|
} else {
|
||||||
msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0];
|
msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0];
|
||||||
}
|
}
|
||||||
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg, msgFormat);
|
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat);
|
||||||
if (!retMsg) throw Error('消息为空');
|
if (!retMsg) throw Error('消息为空');
|
||||||
try {
|
try {
|
||||||
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!;
|
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { AdapterConfigWrap } from '@/onebot/config/config';
|
import { NetworkAdapterConfig } from '@/onebot/config/config';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const SchemaData = Type.Object({
|
||||||
@@ -14,16 +14,14 @@ export default class GetRecentContact extends OneBotAction<Payload, any> {
|
|||||||
actionName = ActionName.GetRecentContact;
|
actionName = ActionName.GetRecentContact;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload, adapter: string) {
|
async _handle(payload: Payload, adapter: string, config: NetworkAdapterConfig) {
|
||||||
const ret = await this.core.apis.UserApi.getRecentContactListSnapShot(+payload.count);
|
const ret = await this.core.apis.UserApi.getRecentContactListSnapShot(+payload.count);
|
||||||
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
|
|
||||||
//烘焙消息
|
//烘焙消息
|
||||||
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
|
|
||||||
return await Promise.all(ret.info.changedList.map(async (t) => {
|
return await Promise.all(ret.info.changedList.map(async (t) => {
|
||||||
const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
|
const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
|
||||||
if (FastMsg.msgList.length > 0) {
|
if (FastMsg.msgList.length > 0) {
|
||||||
//扩展ret.info.changedList
|
//扩展ret.info.changedList
|
||||||
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], msgFormat);
|
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], config.messagePostFormat);
|
||||||
return {
|
return {
|
||||||
lastestMsg: lastestMsg,
|
lastestMsg: lastestMsg,
|
||||||
peerUin: t.peerUin,
|
peerUin: t.peerUin,
|
||||||
|
@@ -20,7 +20,7 @@ import {
|
|||||||
GroupNotify,
|
GroupNotify,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import faceConfig from '@/core/external/face_config.json';
|
import faceConfig from '@/core/external/face_config.json';
|
||||||
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageFileBase, OB11MessageForward, } from '@/onebot';
|
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageFileBase, OB11MessageForward, OB11MessageImage, OB11MessageVideo, } from '@/onebot';
|
||||||
import { OB11Construct } from '@/onebot/helper/data';
|
import { OB11Construct } from '@/onebot/helper/data';
|
||||||
import { EventType } from '@/onebot/event/OneBotEvent';
|
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||||
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
||||||
@@ -956,30 +956,56 @@ export class OneBotMsgApi {
|
|||||||
|
|
||||||
private async handleOb11FileLikeMessage(
|
private async handleOb11FileLikeMessage(
|
||||||
{ data: inputdata }: OB11MessageFileBase,
|
{ data: inputdata }: OB11MessageFileBase,
|
||||||
{ deleteAfterSentFiles }: SendMessageContext,
|
{ deleteAfterSentFiles }: SendMessageContext
|
||||||
) {
|
) {
|
||||||
const realUri = inputdata.url ?? inputdata.file ?? inputdata.path ?? '';
|
let realUri = [inputdata.url, inputdata.file, inputdata.path].find(uri => uri && uri.trim()) ?? '';
|
||||||
if (realUri.length === 0) {
|
if (!realUri) {
|
||||||
this.core.context.logger.logError('文件消息缺少参数', inputdata);
|
this.core.context.logger.logError('文件消息缺少参数', inputdata);
|
||||||
throw Error('文件消息缺少参数');
|
throw new Error('文件消息缺少参数');
|
||||||
}
|
}
|
||||||
const {
|
|
||||||
path,
|
|
||||||
fileName,
|
|
||||||
errMsg,
|
|
||||||
success,
|
|
||||||
} = (await uriToLocalFile(this.core.NapCatTempPath, realUri));
|
|
||||||
|
|
||||||
|
const downloadFile = async (uri: string) => {
|
||||||
|
const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, uri);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
this.core.context.logger.logError('文件下载失败', errMsg);
|
this.core.context.logger.logError('文件下载失败', errMsg);
|
||||||
throw Error('文件下载失败' + errMsg);
|
throw new Error('文件下载失败: ' + errMsg);
|
||||||
}
|
}
|
||||||
|
return { path, fileName };
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const { path, fileName } = await downloadFile(realUri);
|
||||||
|
deleteAfterSentFiles.push(path);
|
||||||
|
return { path, fileName: inputdata.name ?? fileName };
|
||||||
|
} catch {
|
||||||
|
realUri = await this.handleObfuckName(realUri);
|
||||||
|
const { path, fileName } = await downloadFile(realUri);
|
||||||
deleteAfterSentFiles.push(path);
|
deleteAfterSentFiles.push(path);
|
||||||
|
|
||||||
return { path, fileName: inputdata.name ?? fileName };
|
return { path, fileName: inputdata.name ?? fileName };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
async handleObfuckName(name: string) {
|
||||||
|
const contextMsgFile = FileNapCatOneBotUUID.decode(name);
|
||||||
|
if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
|
||||||
|
const { peer, msgId, elementId } = contextMsgFile;
|
||||||
|
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList.find(msg => msg.msgId === msgId);
|
||||||
|
const mixElement = rawMessage?.elements.find(e => e.elementId === elementId);
|
||||||
|
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
|
||||||
|
if (!mixElementInner) throw new Error('element not found');
|
||||||
|
let url = '';
|
||||||
|
if (mixElement?.picElement && rawMessage) {
|
||||||
|
const tempData =
|
||||||
|
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageImage | undefined;
|
||||||
|
url = tempData?.data.url ?? '';
|
||||||
|
}
|
||||||
|
if (mixElement?.videoElement && rawMessage) {
|
||||||
|
const tempData =
|
||||||
|
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false }) as OB11MessageVideo | undefined;
|
||||||
|
url = tempData?.data.url ?? '';
|
||||||
|
}
|
||||||
|
return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
|
||||||
|
}
|
||||||
|
throw new Error('文件名解析失败');
|
||||||
|
}
|
||||||
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 130:
|
case 130:
|
||||||
|
@@ -1,254 +1,108 @@
|
|||||||
interface v1Config {
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
http: {
|
import Ajv from 'ajv';
|
||||||
enable: boolean;
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
secret: string;
|
|
||||||
enableHeart: boolean;
|
|
||||||
enablePost: boolean;
|
|
||||||
postUrls: string[];
|
|
||||||
};
|
|
||||||
ws: {
|
|
||||||
enable: boolean;
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
};
|
|
||||||
reverseWs: {
|
|
||||||
enable: boolean;
|
|
||||||
urls: string[];
|
|
||||||
};
|
|
||||||
debug: boolean;
|
|
||||||
heartInterval: number;
|
|
||||||
messagePostFormat: string;
|
|
||||||
enableLocalFile2Url: boolean;
|
|
||||||
musicSignUrl: string;
|
|
||||||
reportSelfMessage: boolean;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
export interface AdapterConfigInner {
|
|
||||||
name: string;
|
|
||||||
enable: boolean;
|
|
||||||
|
|
||||||
}
|
const HttpServerConfigSchema = Type.Object({
|
||||||
export type AdapterConfigWrap = AdapterConfigInner & Partial<NetworkConfigAdapter>;
|
name: Type.String({ default: 'http-server' }),
|
||||||
|
enable: Type.Boolean({ default: false }),
|
||||||
export interface AdapterConfig extends AdapterConfigInner {
|
port: Type.Number({ default: 3000 }),
|
||||||
[key: string]: any;
|
host: Type.String({ default: '0.0.0.0' }),
|
||||||
}
|
enableCors: Type.Boolean({ default: true }),
|
||||||
|
enableWebsocket: Type.Boolean({ default: true }),
|
||||||
const createDefaultAdapterConfig = <T extends AdapterConfig>(config: T): T => config;
|
messagePostFormat: Type.String({ default: 'array' }),
|
||||||
|
token: Type.String({ default: '' }),
|
||||||
export interface PluginConfig extends AdapterConfig {
|
debug: Type.Boolean({ default: false })
|
||||||
name: string;
|
|
||||||
enable: boolean;
|
|
||||||
messagePostFormat: string;
|
|
||||||
reportSelfMessage: boolean;
|
|
||||||
debug: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const httpServerDefaultConfigs = createDefaultAdapterConfig({
|
|
||||||
name: 'http-server',
|
|
||||||
enable: false as boolean,
|
|
||||||
port: 3000,
|
|
||||||
host: '0.0.0.0',
|
|
||||||
enableCors: true,
|
|
||||||
enableWebsocket: true,
|
|
||||||
messagePostFormat: 'array',
|
|
||||||
token: '',
|
|
||||||
debug: false,
|
|
||||||
});
|
|
||||||
export type HttpServerConfig = typeof httpServerDefaultConfigs;
|
|
||||||
|
|
||||||
export const httpSseServerDefaultConfigs = createDefaultAdapterConfig({
|
|
||||||
...httpServerDefaultConfigs,
|
|
||||||
name: 'http-sse-server',
|
|
||||||
reportSelfMessage: false,
|
|
||||||
});
|
|
||||||
export type HttpSseServerConfig = typeof httpSseServerDefaultConfigs;
|
|
||||||
|
|
||||||
export const httpClientDefaultConfigs = createDefaultAdapterConfig({
|
|
||||||
name: 'http-client',
|
|
||||||
enable: false as boolean,
|
|
||||||
url: 'http://localhost:8080',
|
|
||||||
messagePostFormat: 'array',
|
|
||||||
reportSelfMessage: false,
|
|
||||||
token: '',
|
|
||||||
debug: false,
|
|
||||||
});
|
|
||||||
export type HttpClientConfig = typeof httpClientDefaultConfigs;
|
|
||||||
|
|
||||||
export const websocketServerDefaultConfigs = createDefaultAdapterConfig({
|
|
||||||
name: 'websocket-server',
|
|
||||||
enable: false as boolean,
|
|
||||||
host: '0.0.0.0',
|
|
||||||
port: 3001,
|
|
||||||
messagePostFormat: 'array',
|
|
||||||
reportSelfMessage: false,
|
|
||||||
token: '',
|
|
||||||
enableForcePushEvent: true,
|
|
||||||
debug: false,
|
|
||||||
heartInterval: 30000,
|
|
||||||
});
|
|
||||||
export type WebsocketServerConfig = typeof websocketServerDefaultConfigs;
|
|
||||||
|
|
||||||
export const websocketClientDefaultConfigs = createDefaultAdapterConfig({
|
|
||||||
name: 'websocket-client',
|
|
||||||
enable: false as boolean,
|
|
||||||
url: 'ws://localhost:8082',
|
|
||||||
messagePostFormat: 'array',
|
|
||||||
reportSelfMessage: false,
|
|
||||||
reconnectInterval: 5000,
|
|
||||||
token: '',
|
|
||||||
debug: false,
|
|
||||||
heartInterval: 30000,
|
|
||||||
});
|
|
||||||
export type WebsocketClientConfig = typeof websocketClientDefaultConfigs;
|
|
||||||
|
|
||||||
export interface NetworkConfig {
|
|
||||||
httpServers: Array<HttpServerConfig>;
|
|
||||||
httpSseServers: Array<HttpSseServerConfig>;
|
|
||||||
httpClients: Array<HttpClientConfig>;
|
|
||||||
websocketServers: Array<WebsocketServerConfig>;
|
|
||||||
websocketClients: Array<WebsocketClientConfig>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mergeConfigs<T extends AdapterConfig>(defaultConfig: T, userConfig: Partial<T>): T {
|
|
||||||
return { ...defaultConfig, ...userConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OneBotConfig {
|
|
||||||
network: NetworkConfig; // 网络配置
|
|
||||||
musicSignUrl: string; // 音乐签名地址
|
|
||||||
enableLocalFile2Url: boolean;
|
|
||||||
parseMultMsg: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createDefaultConfig = <T>(config: T): T => config;
|
|
||||||
|
|
||||||
export const defaultOneBotConfigs = createDefaultConfig<OneBotConfig>({
|
|
||||||
network: {
|
|
||||||
httpServers: [],
|
|
||||||
httpSseServers: [],
|
|
||||||
httpClients: [],
|
|
||||||
websocketServers: [],
|
|
||||||
websocketClients: [],
|
|
||||||
},
|
|
||||||
musicSignUrl: '',
|
|
||||||
enableLocalFile2Url: false,
|
|
||||||
parseMultMsg: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mergeNetworkDefaultConfig = {
|
const HttpSseServerConfigSchema = Type.Object({
|
||||||
httpServers: httpServerDefaultConfigs,
|
name: Type.String({ default: 'http-sse-server' }),
|
||||||
httpClients: httpClientDefaultConfigs,
|
enable: Type.Boolean({ default: false }),
|
||||||
websocketServers: websocketServerDefaultConfigs,
|
port: Type.Number({ default: 3000 }),
|
||||||
websocketClients: websocketClientDefaultConfigs,
|
host: Type.String({ default: '0.0.0.0' }),
|
||||||
} as const;
|
enableCors: Type.Boolean({ default: true }),
|
||||||
|
enableWebsocket: Type.Boolean({ default: true }),
|
||||||
|
messagePostFormat: Type.String({ default: 'array' }),
|
||||||
|
token: Type.String({ default: '' }),
|
||||||
|
debug: Type.Boolean({ default: false }),
|
||||||
|
reportSelfMessage: Type.Boolean({ default: false })
|
||||||
|
});
|
||||||
|
|
||||||
export type NetworkConfigAdapter = HttpServerConfig | HttpClientConfig | WebsocketServerConfig | WebsocketClientConfig | PluginConfig;
|
const HttpClientConfigSchema = Type.Object({
|
||||||
type NetworkConfigKeys = keyof typeof mergeNetworkDefaultConfig;
|
name: Type.String({ default: 'http-client' }),
|
||||||
|
enable: Type.Boolean({ default: false }),
|
||||||
|
url: Type.String({ default: 'http://localhost:8080' }),
|
||||||
|
messagePostFormat: Type.String({ default: 'array' }),
|
||||||
|
reportSelfMessage: Type.Boolean({ default: false }),
|
||||||
|
token: Type.String({ default: '' }),
|
||||||
|
debug: Type.Boolean({ default: false })
|
||||||
|
});
|
||||||
|
|
||||||
export function mergeOneBotConfigs(
|
const WebsocketServerConfigSchema = Type.Object({
|
||||||
userConfig: Partial<OneBotConfig>,
|
name: Type.String({ default: 'websocket-server' }),
|
||||||
defaultConfig: OneBotConfig = defaultOneBotConfigs
|
enable: Type.Boolean({ default: false }),
|
||||||
): OneBotConfig {
|
host: Type.String({ default: '0.0.0.0' }),
|
||||||
const mergedConfig = { ...defaultConfig };
|
port: Type.Number({ default: 3001 }),
|
||||||
|
messagePostFormat: Type.String({ default: 'array' }),
|
||||||
|
reportSelfMessage: Type.Boolean({ default: false }),
|
||||||
|
token: Type.String({ default: '' }),
|
||||||
|
enableForcePushEvent: Type.Boolean({ default: true }),
|
||||||
|
debug: Type.Boolean({ default: false }),
|
||||||
|
heartInterval: Type.Number({ default: 30000 })
|
||||||
|
});
|
||||||
|
|
||||||
if (userConfig.network) {
|
const WebsocketClientConfigSchema = Type.Object({
|
||||||
mergedConfig.network = { ...defaultConfig.network };
|
name: Type.String({ default: 'websocket-client' }),
|
||||||
for (const key in userConfig.network) {
|
enable: Type.Boolean({ default: false }),
|
||||||
const userNetworkConfig = userConfig.network[key as keyof NetworkConfig];
|
url: Type.String({ default: 'ws://localhost:8082' }),
|
||||||
const defaultNetworkConfig = mergeNetworkDefaultConfig[key as NetworkConfigKeys];
|
messagePostFormat: Type.String({ default: 'array' }),
|
||||||
if (Array.isArray(userNetworkConfig)) {
|
reportSelfMessage: Type.Boolean({ default: false }),
|
||||||
mergedConfig.network[key as keyof NetworkConfig] = userNetworkConfig.map<any>((e) =>
|
reconnectInterval: Type.Number({ default: 5000 }),
|
||||||
mergeConfigs(defaultNetworkConfig, e)
|
token: Type.String({ default: '' }),
|
||||||
);
|
debug: Type.Boolean({ default: false }),
|
||||||
}
|
heartInterval: Type.Number({ default: 30000 })
|
||||||
}
|
});
|
||||||
}
|
|
||||||
if (userConfig.musicSignUrl !== undefined) {
|
|
||||||
mergedConfig.musicSignUrl = userConfig.musicSignUrl;
|
|
||||||
}
|
|
||||||
if (userConfig.enableLocalFile2Url !== undefined) {
|
|
||||||
mergedConfig.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
|
||||||
}
|
|
||||||
if (userConfig.parseMultMsg !== undefined) {
|
|
||||||
mergedConfig.parseMultMsg = userConfig.parseMultMsg;
|
|
||||||
}
|
|
||||||
return mergedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkIsOneBotConfigV1(v1Config: Partial<v1Config>): boolean {
|
const PluginConfigSchema = Type.Object({
|
||||||
return v1Config.http !== undefined || v1Config.ws !== undefined || v1Config.reverseWs !== undefined;
|
name: Type.String({ default: 'plugin' }),
|
||||||
}
|
enable: Type.Boolean({ default: false }),
|
||||||
|
messagePostFormat: Type.String({ default: 'array' }),
|
||||||
|
reportSelfMessage: Type.Boolean({ default: false }),
|
||||||
|
debug: Type.Boolean({ default: false }),
|
||||||
|
});
|
||||||
|
|
||||||
export function migrateOneBotConfigsV1(config: Partial<v1Config>): OneBotConfig {
|
const NetworkConfigSchema = Type.Object({
|
||||||
if (!checkIsOneBotConfigV1(config)) {
|
httpServers: Type.Array(HttpServerConfigSchema, { default: [] }),
|
||||||
|
httpSseServers: Type.Array(HttpSseServerConfigSchema, { default: [] }),
|
||||||
|
httpClients: Type.Array(HttpClientConfigSchema, { default: [] }),
|
||||||
|
websocketServers: Type.Array(WebsocketServerConfigSchema, { default: [] }),
|
||||||
|
websocketClients: Type.Array(WebsocketClientConfigSchema, { default: [] }),
|
||||||
|
plugins: Type.Array(PluginConfigSchema, { default: [] })
|
||||||
|
}, { default: {} });
|
||||||
|
|
||||||
|
const OneBotConfigSchema = Type.Object({
|
||||||
|
network: NetworkConfigSchema,
|
||||||
|
musicSignUrl: Type.String({ default: '' }),
|
||||||
|
enableLocalFile2Url: Type.Boolean({ default: false }),
|
||||||
|
parseMultMsg: Type.Boolean({ default: true })
|
||||||
|
});
|
||||||
|
|
||||||
|
export type OneBotConfig = Static<typeof OneBotConfigSchema>;
|
||||||
|
export type HttpServerConfig = Static<typeof HttpServerConfigSchema>;
|
||||||
|
export type HttpSseServerConfig = Static<typeof HttpSseServerConfigSchema>;
|
||||||
|
export type HttpClientConfig = Static<typeof HttpClientConfigSchema>;
|
||||||
|
export type WebsocketServerConfig = Static<typeof WebsocketServerConfigSchema>;
|
||||||
|
export type WebsocketClientConfig = Static<typeof WebsocketClientConfigSchema>;
|
||||||
|
export type PluginConfig = Static<typeof PluginConfigSchema>;
|
||||||
|
|
||||||
|
export type NetworkAdapterConfig = HttpServerConfig | HttpSseServerConfig | HttpClientConfig | WebsocketServerConfig | WebsocketClientConfig | PluginConfig;
|
||||||
|
export type NetworkConfigKey = keyof OneBotConfig['network'];
|
||||||
|
|
||||||
|
|
||||||
|
export function loadConfig(config: Partial<OneBotConfig>): OneBotConfig {
|
||||||
|
const ajv = new Ajv({ useDefaults: true });
|
||||||
|
const validate = ajv.compile(OneBotConfigSchema);
|
||||||
|
const valid = validate(config);
|
||||||
|
if (!valid) {
|
||||||
|
throw new Error(ajv.errorsText(validate.errors));
|
||||||
|
}
|
||||||
return config as OneBotConfig;
|
return config as OneBotConfig;
|
||||||
}
|
|
||||||
const mergedConfig = { ...defaultOneBotConfigs };
|
|
||||||
if (config.http) {
|
|
||||||
mergedConfig.network.httpServers = [
|
|
||||||
mergeConfigs(httpServerDefaultConfigs, {
|
|
||||||
name: 'http-server',
|
|
||||||
enable: config.http.enable,
|
|
||||||
port: config.http.port,
|
|
||||||
host: config.http.host,
|
|
||||||
token: config.http.secret,
|
|
||||||
debug: config.debug,
|
|
||||||
messagePostFormat: config.messagePostFormat,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (config.ws) {
|
|
||||||
mergedConfig.network.websocketServers = [
|
|
||||||
mergeConfigs(websocketServerDefaultConfigs, {
|
|
||||||
name: 'websocket-server',
|
|
||||||
enable: config.ws.enable,
|
|
||||||
port: config.ws.port,
|
|
||||||
host: config.ws.host,
|
|
||||||
token: config.token,
|
|
||||||
debug: config.debug,
|
|
||||||
messagePostFormat: config.messagePostFormat,
|
|
||||||
reportSelfMessage: config.reportSelfMessage,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (config.reverseWs) {
|
|
||||||
mergedConfig.network.websocketClients = config.reverseWs.urls.map((url) =>
|
|
||||||
mergeConfigs(websocketClientDefaultConfigs, {
|
|
||||||
name: 'websocket-client-' + config.reverseWs?.urls.indexOf(url).toString(),
|
|
||||||
enable: config.reverseWs?.enable,
|
|
||||||
url: url,
|
|
||||||
token: config.token,
|
|
||||||
debug: config.debug,
|
|
||||||
messagePostFormat: config.messagePostFormat,
|
|
||||||
reportSelfMessage: config.reportSelfMessage,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (config.heartInterval) {
|
|
||||||
mergedConfig.network.websocketServers[0].heartInterval = config.heartInterval;
|
|
||||||
}
|
|
||||||
if (config.musicSignUrl) {
|
|
||||||
mergedConfig.musicSignUrl = config.musicSignUrl;
|
|
||||||
}
|
|
||||||
if (config.enableLocalFile2Url) {
|
|
||||||
mergedConfig.enableLocalFile2Url = config.enableLocalFile2Url;
|
|
||||||
}
|
|
||||||
return mergedConfig;
|
|
||||||
}
|
|
||||||
export function getConfigBoolKey(
|
|
||||||
configs: Array<NetworkConfigAdapter>,
|
|
||||||
prediction: (config: NetworkConfigAdapter) => boolean
|
|
||||||
): { positive: Array<string>, negative: Array<string> } {
|
|
||||||
const result: { positive: string[], negative: string[] } = { positive: [], negative: [] };
|
|
||||||
configs.forEach(config => {
|
|
||||||
if (prediction(config)) {
|
|
||||||
result.positive.push(config.name);
|
|
||||||
} else {
|
|
||||||
result.negative.push(config.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
@@ -23,7 +23,7 @@ export class OB11Construct {
|
|||||||
...rawFriend.baseInfo,
|
...rawFriend.baseInfo,
|
||||||
...rawFriend.coreInfo,
|
...rawFriend.coreInfo,
|
||||||
user_id: parseInt(rawFriend.coreInfo.uin),
|
user_id: parseInt(rawFriend.coreInfo.uin),
|
||||||
nickname: rawFriend.coreInfo.nick,
|
nickname: rawFriend.coreInfo.nick ?? "",
|
||||||
remark: rawFriend.coreInfo.remark ?? rawFriend.coreInfo.nick,
|
remark: rawFriend.coreInfo.remark ?? rawFriend.coreInfo.nick,
|
||||||
sex: this.sex(rawFriend.baseInfo.sex),
|
sex: this.sex(rawFriend.baseInfo.sex),
|
||||||
level: 0,
|
level: 0,
|
||||||
|
@@ -44,10 +44,8 @@ import { LRUCache } from '@/common/lru-cache';
|
|||||||
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
||||||
import { BotOfflineEvent } from './event/notice/BotOfflineEvent';
|
import { BotOfflineEvent } from './event/notice/BotOfflineEvent';
|
||||||
import {
|
import {
|
||||||
AdapterConfigWrap,
|
NetworkAdapterConfig,
|
||||||
mergeOneBotConfigs,
|
loadConfig,
|
||||||
migrateOneBotConfigsV1,
|
|
||||||
NetworkConfigAdapter,
|
|
||||||
OneBotConfig,
|
OneBotConfig,
|
||||||
} from './config/config';
|
} from './config/config';
|
||||||
import { OB11Message } from './types';
|
import { OB11Message } from './types';
|
||||||
@@ -71,8 +69,8 @@ export class NapCatOneBot11Adapter {
|
|||||||
this.core = core;
|
this.core = core;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.configLoader = new OB11ConfigLoader(core, pathWrapper.configPath);
|
this.configLoader = new OB11ConfigLoader(core, pathWrapper.configPath);
|
||||||
this.configLoader.save(migrateOneBotConfigsV1(this.configLoader.configData));
|
this.configLoader.save(this.configLoader.configData);
|
||||||
this.configLoader.save(mergeOneBotConfigs(this.configLoader.configData));
|
this.configLoader.save(loadConfig(this.configLoader.configData));
|
||||||
this.apis = {
|
this.apis = {
|
||||||
GroupApi: new OneBotGroupApi(this, core),
|
GroupApi: new OneBotGroupApi(this, core),
|
||||||
UserApi: new OneBotUserApi(this, core),
|
UserApi: new OneBotUserApi(this, core),
|
||||||
@@ -129,8 +127,8 @@ export class NapCatOneBot11Adapter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(const key of ob11Config.network.httpSseServers){
|
for (const key of ob11Config.network.httpSseServers) {
|
||||||
if(key.enable) {
|
if (key.enable) {
|
||||||
this.networkManager.registerAdapter(
|
this.networkManager.registerAdapter(
|
||||||
new OB11ActiveHttpSSEAdapter(key.name, key, this.core, this, this.actions)
|
new OB11ActiveHttpSSEAdapter(key.name, key, this.core, this, this.actions)
|
||||||
);
|
);
|
||||||
@@ -181,7 +179,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => {
|
WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => {
|
||||||
const prev = this.configLoader.configData;
|
const prev = this.configLoader.configData;
|
||||||
// 保证默认配置
|
// 保证默认配置
|
||||||
newConfig = mergeOneBotConfigs(newConfig);
|
newConfig = loadConfig(newConfig);
|
||||||
|
|
||||||
this.configLoader.save(newConfig);
|
this.configLoader.save(newConfig);
|
||||||
//this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`);
|
//this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`);
|
||||||
@@ -210,13 +208,14 @@ export class NapCatOneBot11Adapter {
|
|||||||
|
|
||||||
await this.handleConfigChange(prev.network.httpServers, now.network.httpServers, OB11PassiveHttpAdapter);
|
await this.handleConfigChange(prev.network.httpServers, now.network.httpServers, OB11PassiveHttpAdapter);
|
||||||
await this.handleConfigChange(prev.network.httpClients, now.network.httpClients, OB11ActiveHttpAdapter);
|
await this.handleConfigChange(prev.network.httpClients, now.network.httpClients, OB11ActiveHttpAdapter);
|
||||||
|
await this.handleConfigChange(prev.network.httpSseServers, now.network.httpSseServers, OB11ActiveHttpSSEAdapter);
|
||||||
await this.handleConfigChange(prev.network.websocketServers, now.network.websocketServers, OB11PassiveWebSocketAdapter);
|
await this.handleConfigChange(prev.network.websocketServers, now.network.websocketServers, OB11PassiveWebSocketAdapter);
|
||||||
await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11ActiveWebSocketAdapter);
|
await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11ActiveWebSocketAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleConfigChange<CT extends NetworkConfigAdapter>(
|
private async handleConfigChange<CT extends NetworkAdapterConfig>(
|
||||||
prevConfig: NetworkConfigAdapter[],
|
prevConfig: NetworkAdapterConfig[],
|
||||||
nowConfig: NetworkConfigAdapter[],
|
nowConfig: NetworkAdapterConfig[],
|
||||||
adapterClass: new (
|
adapterClass: new (
|
||||||
...args: ConstructorParameters<typeof IOB11NetworkAdapter<CT>>
|
...args: ConstructorParameters<typeof IOB11NetworkAdapter<CT>>
|
||||||
) => IOB11NetworkAdapter<CT>
|
) => IOB11NetworkAdapter<CT>
|
||||||
@@ -478,7 +477,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleMsg(message: RawMessage, network: Array<AdapterConfigWrap>) {
|
private async handleMsg(message: RawMessage, network: Array<NetworkAdapterConfig>) {
|
||||||
// 过滤无效消息
|
// 过滤无效消息
|
||||||
if (message.msgType === NTMsgType.KMSGTYPENULL) {
|
if (message.msgType === NTMsgType.KMSGTYPENULL) {
|
||||||
return;
|
return;
|
||||||
@@ -507,7 +506,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
ob11Msg.arrayMsg.user_id.toString() == this.core.selfInfo.uin;
|
ob11Msg.arrayMsg.user_id.toString() == this.core.selfInfo.uin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createMsgMap(network: Array<AdapterConfigWrap>, ob11Msg: any, isSelfMsg: boolean, message: RawMessage): Map<string, OB11Message> {
|
private createMsgMap(network: Array<NetworkAdapterConfig>, ob11Msg: any, isSelfMsg: boolean, message: RawMessage): Map<string, OB11Message> {
|
||||||
const msgMap: Map<string, OB11Message> = new Map();
|
const msgMap: Map<string, OB11Message> = new Map();
|
||||||
network.filter(e => e.enable).forEach(e => {
|
network.filter(e => e.enable).forEach(e => {
|
||||||
if (isSelfMsg || message.chatType !== ChatType.KCHATTYPEGROUP) {
|
if (isSelfMsg || message.chatType !== ChatType.KCHATTYPEGROUP) {
|
||||||
@@ -524,7 +523,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
return msgMap;
|
return msgMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDebugNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, message: RawMessage) {
|
private handleDebugNetwork(network: Array<NetworkAdapterConfig>, msgMap: Map<string, OB11Message>, message: RawMessage) {
|
||||||
const debugNetwork = network.filter(e => e.enable && e.debug);
|
const debugNetwork = network.filter(e => e.enable && e.debug);
|
||||||
if (debugNetwork.length > 0) {
|
if (debugNetwork.length > 0) {
|
||||||
debugNetwork.forEach(adapter => {
|
debugNetwork.forEach(adapter => {
|
||||||
@@ -538,7 +537,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleNotReportSelfNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, isSelfMsg: boolean) {
|
private handleNotReportSelfNetwork(network: Array<NetworkAdapterConfig>, msgMap: Map<string, OB11Message>, isSelfMsg: boolean) {
|
||||||
if (isSelfMsg) {
|
if (isSelfMsg) {
|
||||||
const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e)));
|
const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e)));
|
||||||
notReportSelfNetwork.forEach(adapter => {
|
notReportSelfNetwork.forEach(adapter => {
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { NetworkConfigAdapter } from "@/onebot/config/config";
|
import { NetworkAdapterConfig } from "@/onebot/config/config";
|
||||||
import { LogWrapper } from "@/common/log";
|
import { LogWrapper } from "@/common/log";
|
||||||
import { NapCatCore } from "@/core";
|
import { NapCatCore } from "@/core";
|
||||||
import { NapCatOneBot11Adapter } from "@/onebot";
|
import { NapCatOneBot11Adapter } from "@/onebot";
|
||||||
import { ActionMap } from "@/onebot/action";
|
import { ActionMap } from "@/onebot/action";
|
||||||
import { OB11EmitEventContent, OB11NetworkReloadType } from "@/onebot/network/index";
|
import { OB11EmitEventContent, OB11NetworkReloadType } from "@/onebot/network/index";
|
||||||
|
|
||||||
export abstract class IOB11NetworkAdapter<CT extends NetworkConfigAdapter> {
|
export abstract class IOB11NetworkAdapter<CT extends NetworkAdapterConfig> {
|
||||||
name: string;
|
name: string;
|
||||||
isEnable: boolean = false;
|
isEnable: boolean = false;
|
||||||
config: CT;
|
config: CT;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { OneBotEvent } from '@/onebot/event/OneBotEvent';
|
import { OneBotEvent } from '@/onebot/event/OneBotEvent';
|
||||||
import { OB11Message } from '@/onebot';
|
import { OB11Message } from '@/onebot';
|
||||||
import { NetworkConfigAdapter } from '@/onebot/config/config';
|
import { NetworkAdapterConfig } from '@/onebot/config/config';
|
||||||
import { IOB11NetworkAdapter } from "@/onebot/network/adapter";
|
import { IOB11NetworkAdapter } from "@/onebot/network/adapter";
|
||||||
|
|
||||||
export type OB11EmitEventContent = OneBotEvent | OB11Message;
|
export type OB11EmitEventContent = OneBotEvent | OB11Message;
|
||||||
@@ -13,7 +13,7 @@ export enum OB11NetworkReloadType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class OB11NetworkManager {
|
export class OB11NetworkManager {
|
||||||
adapters: Map<string, IOB11NetworkAdapter<NetworkConfigAdapter>> = new Map();
|
adapters: Map<string, IOB11NetworkAdapter<NetworkAdapterConfig>> = new Map();
|
||||||
|
|
||||||
async openAllAdapters() {
|
async openAllAdapters() {
|
||||||
return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.open()));
|
return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.open()));
|
||||||
@@ -49,22 +49,22 @@ export class OB11NetworkManager {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAdapter<CT extends NetworkConfigAdapter>(adapter: IOB11NetworkAdapter<CT>) {
|
registerAdapter<CT extends NetworkAdapterConfig>(adapter: IOB11NetworkAdapter<CT>) {
|
||||||
this.adapters.set(adapter.name, adapter);
|
this.adapters.set(adapter.name, adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerAdapterAndOpen<CT extends NetworkConfigAdapter>(adapter: IOB11NetworkAdapter<CT>) {
|
async registerAdapterAndOpen<CT extends NetworkAdapterConfig>(adapter: IOB11NetworkAdapter<CT>) {
|
||||||
this.registerAdapter(adapter);
|
this.registerAdapter(adapter);
|
||||||
await adapter.open();
|
await adapter.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeSomeAdapters<CT extends NetworkConfigAdapter>(adaptersToClose: IOB11NetworkAdapter<CT>[]) {
|
async closeSomeAdapters<CT extends NetworkAdapterConfig>(adaptersToClose: IOB11NetworkAdapter<CT>[]) {
|
||||||
for (const adapter of adaptersToClose) {
|
for (const adapter of adaptersToClose) {
|
||||||
this.adapters.delete(adapter.name);
|
this.adapters.delete(adapter.name);
|
||||||
await adapter.close();
|
await adapter.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async closeSomeAdaterWhenOpen<CT extends NetworkConfigAdapter>(adaptersToClose: IOB11NetworkAdapter<CT>[]) {
|
async closeSomeAdaterWhenOpen<CT extends NetworkAdapterConfig>(adaptersToClose: IOB11NetworkAdapter<CT>[]) {
|
||||||
for (const adapter of adaptersToClose) {
|
for (const adapter of adaptersToClose) {
|
||||||
this.adapters.delete(adapter.name);
|
this.adapters.delete(adapter.name);
|
||||||
if (adapter.isEnable) {
|
if (adapter.isEnable) {
|
||||||
@@ -77,7 +77,7 @@ export class OB11NetworkManager {
|
|||||||
return this.adapters.get(name);
|
return this.adapters.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeAdapterByPredicate(closeFilter: (adapter: IOB11NetworkAdapter<NetworkConfigAdapter>) => boolean) {
|
async closeAdapterByPredicate(closeFilter: (adapter: IOB11NetworkAdapter<NetworkAdapterConfig>) => boolean) {
|
||||||
const adaptersToClose = Array.from(this.adapters.values()).filter(closeFilter);
|
const adaptersToClose = Array.from(this.adapters.values()).filter(closeFilter);
|
||||||
await this.closeSomeAdapters(adaptersToClose);
|
await this.closeSomeAdapters(adaptersToClose);
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ export class OB11PluginAdapter extends IOB11NetworkAdapter<PluginConfig> {
|
|||||||
|
|
||||||
onEvent<T extends OB11EmitEventContent>(event: T) {
|
onEvent<T extends OB11EmitEventContent>(event: T) {
|
||||||
if (event.post_type === 'message') {
|
if (event.post_type === 'message') {
|
||||||
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11Message,this.actions).then().catch();
|
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11Message, this.actions, this).then().catch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { NapCatOneBot11Adapter, OB11Message } from "@/onebot";
|
import { NapCatOneBot11Adapter, OB11Message } from "@/onebot";
|
||||||
import { NapCatCore } from "@/core";
|
import { NapCatCore } from "@/core";
|
||||||
import { ActionMap } from "@/onebot/action";
|
import { ActionMap } from "@/onebot/action";
|
||||||
|
import { OB11PluginAdapter } from "@/onebot/network/plugin";
|
||||||
|
|
||||||
export const plugin_onmessage = async (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap) => {
|
export const plugin_onmessage = async (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap, instance: OB11PluginAdapter) => {
|
||||||
if (message.raw_message === 'ping') {
|
if (message.raw_message === 'ping') {
|
||||||
const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter);
|
const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config);
|
||||||
console.log(ret);
|
console.log(ret);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
6
src/universal/LiteLoader.d.ts
vendored
Normal file
6
src/universal/LiteLoader.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
declare global {
|
||||||
|
namespace globalThis {
|
||||||
|
var LiteLoader: symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {};
|
@@ -1,7 +1,6 @@
|
|||||||
import { NCoreInitShell } from "@/shell/base";
|
import { NCoreInitShell } from "@/shell/base";
|
||||||
|
|
||||||
export * from "@/framework/napcat";
|
export * from "@/framework/napcat";
|
||||||
export * from "@/shell/base";
|
export * from "@/shell/base";
|
||||||
if ((global as any).LiteLoader == undefined) {
|
if (global.LiteLoader == undefined) {
|
||||||
NCoreInitShell();
|
NCoreInitShell();
|
||||||
}
|
}
|
Reference in New Issue
Block a user