Compare commits

..

20 Commits

Author SHA1 Message Date
手瓜一十雪
6c46cdd947 fix: error 2024-11-17 11:33:01 +08:00
手瓜一十雪
372452fbee fix: 消息上报 2024-11-17 11:29:27 +08:00
手瓜一十雪
417ef5d335 Revert "fix"
This reverts commit 9c534f8afd.
2024-11-17 11:21:48 +08:00
手瓜一十雪
9c534f8afd fix 2024-11-17 11:12:14 +08:00
pk5ls20
ecd426bb80 refactor: webui network 2024-11-17 08:17:09 +08:00
pk5ls20
f74ef273de fix: workflow
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 3m25s
Build Action / Build-Shell (push) Failing after 2m52s
2024-11-17 06:24:58 +08:00
pk5ls20
f913e0b027 chore: workflow 2024-11-17 06:23:33 +08:00
pk5ls20
f7268c30ca chore: revert todo 2024-11-17 05:28:46 +08:00
pk5ls20
0f5ef03d63 chore: try todo x2 2024-11-17 05:21:35 +08:00
pk5ls20
745276d0f0 chore: try todo 2024-11-17 05:16:18 +08:00
pk5ls20
2e108a4bd6 feat: error stack 2024-11-17 04:43:29 +08:00
pk5ls20
666da80ef5 feat: version display 2024-11-17 03:43:09 +08:00
pk5ls20
cc73104d62 chore: eslint 2024-11-17 03:35:20 +08:00
手瓜一十雪
3c10b82bab Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 11s
Build Action / Build-Shell (push) Failing after 14s
2024-11-16 20:35:31 +08:00
手瓜一十雪
9a65dae6a2 fix: #531 2024-11-16 20:32:52 +08:00
Mlikiowa
f26cd8cdc9 release: v4.1.3 2024-11-16 12:22:06 +00:00
手瓜一十雪
eeec905df0 fix: 反向ws 2024-11-16 20:21:38 +08:00
手瓜一十雪
0c6aac7f66 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-16 20:20:07 +08:00
手瓜一十雪
86d22db141 feat: remove hasBeenClosed 2024-11-16 20:15:02 +08:00
Mlikiowa
48a5d0eef3 release: v4.1.2 2024-11-16 12:14:28 +00:00
38 changed files with 323 additions and 598 deletions

View File

@@ -1,8 +1,7 @@
name: "Build Action"
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
permissions: write-all
@@ -11,60 +10,38 @@ jobs:
Build-LiteLoader:
runs-on: ubuntu-latest
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
with:
repository: 'NapNeko/NapCatQQ'
submodules: true
ref: main
token: ${{ secrets.NAPCAT_BUILD }}
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NuCat Framework
run: |
npm i
cd napcat.webui
npm i
cd ..
npm run build:framework
cd dist
npm i --omit=dev
- name: Clone Main Repository
uses: actions/checkout@v4
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NapCat.Framework
run: |
npm i && cd napcat.webui && npm i && cd ..
npm run build:framework && npm run depend
rm package-lock.json
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.Framework
path: dist
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.Framework
path: dist
Build-Shell:
runs-on: ubuntu-latest
steps:
- name: Clone Main Repository
uses: actions/checkout@v4
with:
repository: 'NapNeko/NapCatQQ'
submodules: true
ref: main
token: ${{ secrets.NAPCAT_BUILD }}
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NuCat LiteLoader
run: |
npm i
cd napcat.webui
npm i
cd ..
npm run build:shell
cd dist
npm i --omit=dev
rm package-lock.json
cd ..
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.Shell
path: dist
- name: Clone Main Repository
uses: actions/checkout@v4
- name: Use Node.js 20.X
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Build NapCat.Shell
run: |
npm i && cd napcat.webui && npm i && cd ..
npm run build:shell && npm run depend
rm package-lock.json
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: NapCat.Shell
path: dist

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.1.1",
"version": "4.1.3",
"icon": "./logo.png",
"authors": [
{

View File

@@ -1,10 +1,10 @@
{
"name": "napcat.webui",
"private": true,
"version": "0.0.0",
"version": "1.0.0",
"type": "module",
"scripts": {
"webui:lint": "eslint . --fix",
"webui:lint": "eslint --fix src/**/*.{js,ts,vue}",
"webui:dev": "vite",
"webui:build": "vue-tsc -b && vite build",
"webui:preview": "vite preview"

View File

@@ -13,9 +13,11 @@
<t-list-item class="list-item">
<span class="item-label">版本信息:</span>
<span class="item-content">
<t-tag class="tag-item" theme="success"> WebUi: 1.0.0 </t-tag>
<t-tag class="tag-item" theme="success"> NapCat: 4.?.? </t-tag>
<t-tag class="tag-item" theme="success"> Tdesign: 1.10.3 </t-tag>
<t-tag class="tag-item" theme="success"> WebUi: {{ pkg.version }} </t-tag>
<t-tag class="tag-item" theme="success"> NapCat: {{ napCatVersion }} </t-tag>
<t-tag class="tag-item" theme="success">
TDesign: {{ pkg.dependencies['tdesign-vue-next'] }}
</t-tag>
</span>
</t-list-item>
</t-list>
@@ -24,7 +26,8 @@
</template>
<script setup lang="ts">
import pkg from '../../package.json';
import { napCatVersion } from '../../../src/common/version';
</script>
<style scoped>

View File

@@ -1,52 +1,59 @@
<template>
<t-space class="full-space">
<template v-if="clientPanelData.length > 0">
<t-tabs v-model="activeTab" :addable="true" theme="card" @add="showAddTabDialog" @remove="removeTab" class="full-tabs">
<t-tab-panel
v-for="(config, idx) in clientPanelData"
:key="idx"
:label="config.name"
:removable="true"
:value="idx"
class="full-tab-panel"
>
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
<div class="button-container">
<t-button @click="saveConfig" style="width: 100px; height: 40px;">保存</t-button>
</div>
</t-tab-panel>
</t-tabs>
</template>
<template v-else>
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" />
</template>
<t-dialog
v-model:visible="isDialogVisible"
header="添加网络配置"
@close="isDialogVisible = false"
@confirm="addTab"
>
<t-form ref="form" :model="newTab">
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
<t-input v-model="newTab.name" />
</t-form-item>
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
<t-select v-model="newTab.type">
<t-option value="httpServers">HTTP 服务器</t-option>
<t-option value="httpClients">HTTP 客户端</t-option>
<t-option value="websocketServers">WebSocket 服务器</t-option>
<t-option value="websocketClients">WebSocket 客户端</t-option>
</t-select>
</t-form-item>
</t-form>
</t-dialog>
<template v-if="clientPanelData.length > 0">
<t-tabs
v-model="activeTab"
:addable="true"
theme="card"
@add="showAddTabDialog"
@remove="removeTab"
class="full-tabs"
>
<t-tab-panel
v-for="(config, idx) in clientPanelData"
:key="idx"
:label="config.name"
:removable="true"
:value="idx"
class="full-tab-panel"
>
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
<div class="button-container">
<t-button @click="saveConfig" style="width: 100px; height: 40px">保存</t-button>
</div>
</t-tab-panel>
</t-tabs>
</template>
<template v-else>
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" />
</template>
<t-dialog
v-model:visible="isDialogVisible"
header="添加网络配置"
@close="isDialogVisible = false"
@confirm="addTab"
>
<t-form ref="form" :model="newTab">
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
<t-input v-model="newTab.name" />
</t-form-item>
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
<t-select v-model="newTab.type">
<t-option value="httpServers">HTTP 服务器</t-option>
<t-option value="httpClients">HTTP 客户端</t-option>
<t-option value="websocketServers">WebSocket 服务器</t-option>
<t-option value="websocketClients">WebSocket 客户端</t-option>
</t-select>
</t-form-item>
</t-form>
</t-dialog>
</t-space>
</template>
<script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted, reactive, Reactive } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import {
</template>
<script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import {
httpServerDefaultConfigs,
httpClientDefaultConfigs,
websocketServerDefaultConfigs,
@@ -58,187 +65,185 @@
NetworkConfig,
OneBotConfig,
mergeOneBotConfigs,
} from '../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
const componentMap: Record<
ConfigKey,
} from '../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
type ComponentUnion =
| typeof HttpServerComponent
| typeof HttpClientComponent
| typeof WebsocketServerComponent
| typeof WebsocketClientComponent
> = {
| typeof WebsocketClientComponent;
const componentMap: Record<ConfigKey, ComponentUnion> = {
httpServers: HttpServerComponent,
httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent,
};
interface ClientPanel {
};
const defaultConfigMap: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
interface ConfigMap {
httpServers: HttpServerConfig;
httpClients: HttpClientConfig;
websocketServers: WebsocketServerConfig;
websocketClients: WebsocketClientConfig;
}
interface ClientPanel<K extends ConfigKey = ConfigKey> {
name: string;
key: ConfigKey;
data: Ref<ConfigUnion>;
}
type ComponentKey = keyof typeof componentMap;
// TODO: store these state in global store (aka pinia)
const activeTab = ref<number>(0);
const isDialogVisible = ref(false);
const newTab = ref<{ name: string; type: ComponentKey }>({ name: '', type: 'httpServers' });
const clientPanelData: Reactive<Array<ClientPanel>> = reactive([]);
const getComponent = (type: ComponentKey) => {
key: K;
data: ConfigMap[K];
}
const activeTab = ref<number>(0);
const isDialogVisible = ref(false);
const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' });
const clientPanelData: Ref<ClientPanel[]> = ref([]);
const getComponent = (type: ConfigKey) => {
return componentMap[type];
};
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
};
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
console.error('No stored credential found');
return;
console.error('No stored credential found');
return;
}
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.GetOB11Config();
};
const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
};
const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
console.error('No stored credential found');
return false;
console.error('No stored credential found');
return false;
}
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.SetOB11Config(config);
};
const addToPanel = <T extends ConfigUnion>(configs: T[], key: ConfigKey) => {
configs.forEach((config) => clientPanelData.push({ name: config.name, data: config, key: key }));
};
const addConfigDataToPanel = (data: NetworkConfig) => {
Object.entries(data).forEach(([key, configs]) => {
if (key in defaultConfigs) {
addToPanel(configs as ConfigUnion[], key as ConfigKey);
}
};
const addToPanel = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => {
configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key }));
};
const addConfigDataToPanel = (data: NetworkConfig) => {
(Object.keys(data) as ConfigKey[]).forEach((key) => {
addToPanel(data[key], key);
});
};
const parsePanelData = (): NetworkConfig => {
return {
websocketClients: clientPanelData
.filter((panel) => panel.key === 'websocketClients')
.map((panel) => panel.data as WebsocketClientConfig),
websocketServers: clientPanelData
.filter((panel) => panel.key === 'websocketServers')
.map((panel) => panel.data as WebsocketServerConfig),
httpClients: clientPanelData
.filter((panel) => panel.key === 'httpClients')
.map((panel) => panel.data as HttpClientConfig),
httpServers: clientPanelData
.filter((panel) => panel.key === 'httpServers')
.map((panel) => panel.data as HttpServerConfig),
};
const parsePanelData = (): NetworkConfig => {
const result: NetworkConfig = {
httpServers: [],
httpClients: [],
websocketServers: [],
websocketClients: [],
};
};
const loadConfig = async () => {
clientPanelData.value.forEach((panel) => {
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
});
return result;
};
const loadConfig = async () => {
try {
const userConfig = await getOB11Config();
if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network);
const userConfig = await getOB11Config();
if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network);
} catch (error) {
console.error('Error loading config:', error);
console.error('Error loading config:', error);
}
};
// It's better to "saveConfig" instead of using deep watch
const saveConfig = async () => {
};
const saveConfig = async () => {
const config = parsePanelData();
const userConfig = await getOB11Config();
if (!userConfig) return;
if (!userConfig) {
await MessagePlugin.error('无法获取配置!');
return;
}
userConfig.network = config;
const success = await setOB11Config(userConfig);
if (success) {
MessagePlugin.success('配置保存成功');
await MessagePlugin.success('配置保存成功');
} else {
MessagePlugin.error('配置保存失败');
await MessagePlugin.error('配置保存失败');
}
};
const showAddTabDialog = () => {
};
const showAddTabDialog = () => {
newTab.value = { name: '', type: 'httpServers' };
isDialogVisible.value = true;
};
const addTab = async () => {
};
const addTab = async () => {
const { name, type } = newTab.value;
if (clientPanelData.some(panel => panel.name === name)) {
MessagePlugin.error('选项卡名称已存在');
return;
if (clientPanelData.value.some((panel) => panel.name === name)) {
await MessagePlugin.error('选项卡名称已存在');
return;
}
const defaultConfig = structuredClone(defaultConfigs[type]);
const defaultConfig = structuredClone(defaultConfigMap[type]);
defaultConfig.name = name;
clientPanelData.push({ name, data: defaultConfig, key: type });
clientPanelData.value.push({ name, data: defaultConfig, key: type });
isDialogVisible.value = false;
await nextTick();
activeTab.value = clientPanelData.length - 1;
MessagePlugin.success('选项卡添加成功');
};
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
clientPanelData.splice(payload.index, 1);
activeTab.value = clientPanelData.value.length - 1;
await MessagePlugin.success('选项卡添加成功');
};
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
clientPanelData.value.splice(payload.index, 1);
activeTab.value = Math.max(0, activeTab.value - 1);
await saveConfig();
};
onMounted(() => {
};
onMounted(() => {
loadConfig();
});
</script>
<style scoped>
.full-space {
});
</script>
<style scoped>
.full-space {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.full-tabs {
}
.full-tabs {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.full-tab-panel {
}
.full-tab-panel {
flex: 1;
display: flex;
flex-direction: column;
}
.button-container {
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>
}
</style>

View File

@@ -131,4 +131,4 @@ onMounted(() => {
margin-left: 20px;
}
}
</style>
</style>

View File

@@ -19,4 +19,4 @@ defineProps<{ showAddTabDialog: () => void }>();
height: 100%;
text-align: center;
}
</style>
</style>

View File

@@ -36,14 +36,17 @@ const props = defineProps<{
const messageFormatOptions = ref([
{ label: 'Array', value: 'array' },
{ label: 'String', value: 'string' }
{ label: 'String', value: 'string' },
]);
watch(() => props.config.messagePostFormat, (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
watch(
() => props.config.messagePostFormat,
(newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
}
});
);
</script>
<style scoped>
@@ -62,4 +65,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px;
border-radius: 8px;
}
</style>
</style>

View File

@@ -42,14 +42,17 @@ const props = defineProps<{
const messageFormatOptions = ref([
{ label: 'Array', value: 'array' },
{ label: 'String', value: 'string' }
{ label: 'String', value: 'string' },
]);
watch(() => props.config.messagePostFormat, (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
watch(
() => props.config.messagePostFormat,
(newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
}
});
);
</script>
<style scoped>
@@ -68,4 +71,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px;
border-radius: 8px;
}
</style>
</style>

View File

@@ -39,14 +39,17 @@ const props = defineProps<{
const messageFormatOptions = ref([
{ label: 'Array', value: 'array' },
{ label: 'String', value: 'string' }
{ label: 'String', value: 'string' },
]);
watch(() => props.config.messagePostFormat, (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
watch(
() => props.config.messagePostFormat,
(newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
}
});
);
</script>
<style scoped>
@@ -65,4 +68,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px;
border-radius: 8px;
}
</style>
</style>

View File

@@ -45,14 +45,17 @@ const props = defineProps<{
const messageFormatOptions = ref([
{ label: 'Array', value: 'array' },
{ label: 'String', value: 'string' }
{ label: 'String', value: 'string' },
]);
watch(() => props.config.messagePostFormat, (newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
watch(
() => props.config.messagePostFormat,
(newValue) => {
if (newValue !== 'array' && newValue !== 'string') {
props.config.messagePostFormat = 'array';
}
}
});
);
</script>
<style scoped>
@@ -71,4 +74,4 @@ watch(() => props.config.messagePostFormat, (newValue) => {
padding: 20px;
border-radius: 8px;
}
</style>
</style>

View File

@@ -1,267 +0,0 @@
<template>
<t-space class="full-space">
<t-tabs v-model="activeTab" :addable="true" theme="card" @add="showAddTabDialog" @remove="removeTab" class="full-tabs">
<t-tab-panel
v-for="(config, idx) in clientPanelData"
:key="idx"
:label="config.name"
:removable="true"
:value="idx"
class="full-tab-panel"
>
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
<div class="button-container">
<t-button @click="saveConfig" style="width: 100px; height: 40px;">保存</t-button>
</div>
</t-tab-panel>
</t-tabs>
<t-dialog
v-model:visible="isDialogVisible"
header="添加新选项卡"
@close="isDialogVisible = false"
@confirm="addTab"
>
<t-form ref="form" :model="newTab">
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
<t-input v-model="newTab.name" />
</t-form-item>
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
<t-select v-model="newTab.type">
<t-option value="httpServers">HTTP 服务器</t-option>
<t-option value="httpClients">HTTP 客户端</t-option>
<t-option value="websocketServers">WebSocket 服务器</t-option>
<t-option value="websocketClients">WebSocket 客户端</t-option>
</t-select>
</t-form-item>
</t-form>
</t-dialog>
</t-space>
</template>
<script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted, reactive, Reactive } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import {
httpServerDefaultConfigs,
httpClientDefaultConfigs,
websocketServerDefaultConfigs,
websocketClientDefaultConfigs,
HttpClientConfig,
HttpServerConfig,
WebsocketClientConfig,
WebsocketServerConfig,
NetworkConfig,
OneBotConfig,
mergeOneBotConfigs,
} from '../../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
const componentMap: Record<
ConfigKey,
| typeof HttpServerComponent
| typeof HttpClientComponent
| typeof WebsocketServerComponent
| typeof WebsocketClientComponent
> = {
httpServers: HttpServerComponent,
httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent,
};
interface ClientPanel {
name: string;
key: ConfigKey;
data: Ref<ConfigUnion>;
}
type ComponentKey = keyof typeof componentMap;
// TODO: store these state in global store (aka pinia)
const activeTab = ref<number>(0);
const isDialogVisible = ref(false);
const newTab = ref<{ name: string; type: ComponentKey }>({ name: '', type: 'httpServers' });
const clientPanelData: Reactive<Array<ClientPanel>> = reactive([]);
const getComponent = (type: ComponentKey) => {
return componentMap[type];
};
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
console.error('No stored credential found');
return;
}
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.GetOB11Config();
};
const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
console.error('No stored credential found');
return false;
}
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.SetOB11Config(config);
};
const addToPanel = <T extends ConfigUnion>(configs: T[], key: ConfigKey) => {
configs.forEach((config) => clientPanelData.push({ name: config.name, data: config, key: key }));
};
const addConfigDataToPanel = (data: NetworkConfig) => {
Object.entries(data).forEach(([key, configs]) => {
if (key in defaultConfigs) {
addToPanel(configs as ConfigUnion[], key as ConfigKey);
}
});
};
const parsePanelData = (): NetworkConfig => {
return {
websocketClients: clientPanelData
.filter((panel) => panel.key === 'websocketClients')
.map((panel) => panel.data as WebsocketClientConfig),
websocketServers: clientPanelData
.filter((panel) => panel.key === 'websocketServers')
.map((panel) => panel.data as WebsocketServerConfig),
httpClients: clientPanelData
.filter((panel) => panel.key === 'httpClients')
.map((panel) => panel.data as HttpClientConfig),
httpServers: clientPanelData
.filter((panel) => panel.key === 'httpServers')
.map((panel) => panel.data as HttpServerConfig),
};
};
const loadConfig = async () => {
try {
const userConfig = await getOB11Config();
if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network);
} catch (error) {
console.error('Error loading config:', error);
}
};
// It's better to "saveConfig" instead of using deep watch
const saveConfig = async () => {
const config = parsePanelData();
const userConfig = await getOB11Config();
if (!userConfig) return;
userConfig.network = config;
const success = await setOB11Config(userConfig);
if (success) {
MessagePlugin.success('配置保存成功');
} else {
MessagePlugin.error('配置保存失败');
}
};
const showAddTabDialog = () => {
newTab.value = { name: '', type: 'httpServers' };
isDialogVisible.value = true;
};
const addTab = async () => {
const { name, type } = newTab.value;
if (clientPanelData.some(panel => panel.name === name)) {
MessagePlugin.error('选项卡名称已存在');
return;
}
const defaultConfig = structuredClone(defaultConfigs[type]);
defaultConfig.name = name;
clientPanelData.push({ name, data: defaultConfig, key: type });
isDialogVisible.value = false;
await nextTick();
activeTab.value = clientPanelData.length - 1;
MessagePlugin.success('选项卡添加成功');
};
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
clientPanelData.splice(payload.index, 1);
activeTab.value = Math.max(0, activeTab.value - 1);
await saveConfig();
};
onMounted(() => {
loadConfig();
});
</script>
<style scoped>
.full-space {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.full-tabs {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.full-tab-panel {
flex: 1;
display: flex;
flex-direction: column;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>
<style scoped>
.full-space {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.full-tabs {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.full-tab-panel {
flex: 1;
display: flex;
flex-direction: column;
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.1.1",
"version": "4.1.3",
"scripts": {
"build:framework": "npm run build:webui && vite build --mode framework",
"build:shell": "npm run build:webui && vite build --mode shell",

View File

@@ -242,7 +242,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
//解析Http和Https协议
if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
}
//解析File协议和本地文件
if (UriType == FileUriType.Local) {
@@ -289,5 +289,5 @@ export async function uri2local(dir: string, uri: string, filename: string | und
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
}

View File

@@ -36,7 +36,7 @@ export class LogWrapper {
this.logger = winston.createLogger({
level: 'debug',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.timestamp({ format: 'MM-DD HH:mm:ss' }),
format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`;
@@ -61,7 +61,7 @@ export class LogWrapper {
]
});
this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
this.setLogSelfInfo({ nick: '', uid: '' });
this.cleanOldLogs(logDir);
}
@@ -111,8 +111,8 @@ export class LogWrapper {
});
}
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
setLogSelfInfo(selfInfo: { nick: string, uid: string }) {
const userInfo = `${selfInfo.nick}`;
this.logger.defaultMeta = { userInfo };
}

View File

@@ -1 +1 @@
export const napCatVersion = '4.1.1';
export const napCatVersion = '4.1.3';

View File

@@ -23,7 +23,7 @@ export interface ChatCacheList {
export interface ChatCacheListItem {
chatType: ChatType;
basicChatCacheInfo: ChatCacheListItemBasic;
guildChatCacheInfo: unknown[]; // work: 没用过频道所以不知道这里边的详细内容
guildChatCacheInfo: unknown[]; // TODO: 没用过频道所以不知道这里边的详细内容
}
export interface ChatCacheListItemBasic {

View File

@@ -1,4 +1,4 @@
// work:further refactor in NapCat.Packet v2
// TODO: further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const LikeDetail = {

View File

@@ -1,4 +1,4 @@
// work:further refactor in NapCat.Packet v2
// TODO: further refactor in NapCat.Packet v2
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
const BodyInner = {

View File

@@ -120,7 +120,7 @@ export class NapCatCore {
if (!fs.existsSync(this.NapCatTempPath)) {
fs.mkdirSync(this.NapCatTempPath, { recursive: true });
}
//遍历this.apis[i].initApi 如果存在该函数进行async 调用
//遍历this.apis[i].initApi 如果存在该函数进行async 调用
for (const apiKey in this.apis) {
const api = this.apis[apiKey as keyof StableNTApiWrapper];
if ('initApi' in api && typeof api.initApi === 'function') {
@@ -210,7 +210,7 @@ export class NapCatCore {
});
};
groupListener.onMemberListChange = (arg) => {
// work:应该加一个内部自己维护的成员变动callback用于判断成员变化通知
// TODO: 应该加一个内部自己维护的成员变动callback用于判断成员变化通知
const groupCode = arg.sceneId.split('_')[0];
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;

View File

@@ -24,7 +24,7 @@ export class PacketClientSession {
return this.context.operation;
}
// work: global message element adapter (?
// TODO: global message element adapter (?
get msgConverter() {
return this.context.msgConverter;
}

View File

@@ -1,7 +1,7 @@
import { LogLevel, LogWrapper } from "@/common/log";
import { PacketContext } from "@/core/packet/context/packetContext";
// work: check bind?
// TODO: check bind?
export class PacketLogger {
private readonly napLogger: LogWrapper;

View File

@@ -76,7 +76,7 @@ export type rawMsgWithSendMsg = {
msg: PacketSendMsgElement[]
}
// work:make it become adapter?
// TODO: make it become adapter?
export class PacketMsgConverter {
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
return SupportedElementTypes.includes(type);
@@ -116,7 +116,7 @@ export class PacketMsgConverter {
[ElementType.MARKDOWN]: (element) => {
return new PacketMsgMarkDownElement(element as SendMarkdownElement);
},
// work:check this logic, move it in arkElement?
// TODO: check this logic, move it in arkElement?
[ElementType.STRUCTLONGMSG]: (element) => {
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
}

View File

@@ -32,7 +32,7 @@ import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
// raw <-> packet
// work:SendStructLongMsgElement
// TODO: SendStructLongMsgElement
export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
protected constructor(rawElement: T) {
}
@@ -118,7 +118,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
this.targetUin = +(element.replyElement.senderUin ?? 0);
this.targetUid = element.replyElement.senderUidStr ?? '';
this.time = +(element.replyElement.replyMsgTime ?? 0);
this.elems = []; // work:in replyElement.sourceMsgTextElems
this.elems = []; // TODO: in replyElement.sourceMsgTextElems
}
get isGroupReply(): boolean {
@@ -131,7 +131,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
origSeqs: [this.isGroupReply ? this.messageClientSeq : this.messageSeq],
senderUin: BigInt(this.targetUin),
time: this.time,
elems: [], // work:in replyElement.sourceMsgTextElems
elems: [], // TODO: in replyElement.sourceMsgTextElems
pbReserve: {
messageId: this.messageId,
},
@@ -346,9 +346,9 @@ export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
constructor(element: SendPttElement) {
super(element);
this.filePath = element.pttElement.filePath;
this.fileSize = +element.pttElement.fileSize; // work:cc
this.fileSize = +element.pttElement.fileSize; // TODO: cc
this.fileMd5 = element.pttElement.md5HexStr;
this.fileDuration = Math.round(element.pttElement.duration); // work:cc
this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc
}
get valid(): boolean {

View File

@@ -25,7 +25,7 @@ class DownloadOfflineFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0
return OidbBase.build(0xE37, 800, body, false, false);
}
// work:check
// TODO:check
parse(data: Buffer) {
const oidbBody = OidbBase.parse(data).body;
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37Response).decode(oidbBody);

View File

@@ -16,7 +16,7 @@ class FetchSessionKey extends PacketTransformer<typeof proto.HttpConn0x6ff_501Re
field4: 1,
field6: 3,
serviceTypes: [1, 5, 10, 21],
// tgt: "", // work:do we really need tgt? seems not
// tgt: "", // TODO: do we really need tgt? seems not
field9: 2,
field10: 9,
field11: 8,

View File

@@ -16,7 +16,7 @@ class UploadGroupFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0x6D6
appId: 4,
busId: 102,
entrance: 6,
targetDirectory: '/', // work:
targetDirectory: '/', // TODO:
fileName: file.fileName,
localDirectory: `/${file.fileName}`,
fileSize: BigInt(file.fileSize),

View File

@@ -40,7 +40,7 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //work:extend NapCat imgType /cc @MliKiowa
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
@@ -59,7 +59,7 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // work:
textSummary: "Nya~", // TODO:
},
video: {
bytesPbReserve: Buffer.alloc(0),

View File

@@ -40,7 +40,7 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
fileName: img.name,
type: {
type: 1,
picFormat: img.picType, //work:extend NapCat imgType /cc @MliKiowa
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
videoFormat: 0,
voiceFormat: 0,
},
@@ -59,7 +59,7 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
extBizInfo: {
pic: {
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
textSummary: "Nya~", // work:
textSummary: "Nya~", // TODO:
},
video: {
bytesPbReserve: Buffer.alloc(0),

View File

@@ -47,7 +47,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
return OB11Response.ok(resData);
} catch (e: any) {
this.core.context.logger.logError.bind(this.core.context.logger)('发生错误', e);
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
return OB11Response.error(e?.stack?.toString() || e?.toString() || '未知错误,可能操作超时', 200);
}
}

View File

@@ -15,7 +15,7 @@ type Payload = FromSchema<typeof SchemaData>;
export class GetGroupFileSystemInfo extends BaseAction<Payload, {
file_count: number,
limit_count: number, // unimplemented
used_space: number, // work:unimplemented, but can be implemented later
used_space: number, // TODO:unimplemented, but can be implemented later
total_space: number, // unimplemented, 10 GB by default
}> {
actionName = ActionName.GoCQHTTP_GetGroupFileSystemInfo;

View File

@@ -122,8 +122,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
returnMsgAndResId = packetMode
? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt)
: await this.handleForwardedNodes(peer, messages as OB11MessageNode[]);
} catch (e) {
throw Error(packetMode ? `发送伪造合并转发消息失败: ${e}` : `发送合并转发消息失败: ${e}`);
} catch (e: any) {
throw Error(packetMode ? `发送伪造合并转发消息失败: ${e?.stack}` : `发送合并转发消息失败: ${e?.stack}`);
}
if (!returnMsgAndResId) {
throw Error('发送合并转发消息失败returnMsgAndResId 为空!');
@@ -308,8 +308,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
MessageUnique.createUniqueMsgId(selfPeer, result.value.msgId);
}
});
} catch (e) {
logger.logDebug('生成转发消息节点失败', e);
} catch (e: any) {
logger.logDebug('生成转发消息节点失败', e?.stack);
}
}
}
@@ -350,8 +350,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
return {
message: await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds)
};
} catch (e) {
logger.logError.bind(this.core.context.logger)('forward failed', e);
} catch (e: any) {
logger.logError.bind(this.core.context.logger)('forward failed', e?.stack);
return {
message: null
};
@@ -376,8 +376,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
try {
return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements, true);
} catch (e) {
logger.logError.bind(this.core.context.logger)(e, '克隆转发消息失败,将忽略本条消息', msg);
} catch (e: any) {
logger.logError.bind(this.core.context.logger)(e?.stack, '克隆转发消息失败,将忽略本条消息', msg);
}
}
}

View File

@@ -7,7 +7,6 @@ export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT>
protected async check(payload: PT): Promise<BaseCheckResult>{
if (!this.core.apis.PacketApi.available) {
// work:add error stack?
return {
valid: false,
message: "packetBackend不可用请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置" +

View File

@@ -5,7 +5,7 @@ export type GroupDecreaseSubType = 'leave' | 'kick' | 'kick_me';
export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent {
notice_type = 'group_decrease';
sub_type: GroupDecreaseSubType = 'leave'; // work:实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
sub_type: GroupDecreaseSubType = 'leave'; // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
operator_id: number;
constructor(core: NapCatCore, groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') {

View File

@@ -1,7 +1,7 @@
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
import { NapCatCore } from '@/core';
//work: 输入状态事件 初步完成 Mlikiowa 需要做一些过滤
//TODO: 输入状态事件 初步完成 Mlikiowa 需要做一些过滤
export class OB11InputStatusEvent extends OB11BaseNoticeEvent {
notice_type = 'notify';
sub_type = 'input_status';

View File

@@ -207,7 +207,7 @@ export class NapCatOneBot11Adapter {
for (const adapterConfig of adapters) {
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
if (existingAdapter) {
let networkChange = await existingAdapter.reload(adapterConfig);
const networkChange = await existingAdapter.reload(adapterConfig);
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
this.networkManager.closeSomeAdapters([existingAdapter]);
@@ -543,28 +543,29 @@ export class NapCatOneBot11Adapter {
}
private async emitMsg(message: RawMessage) {
const network = Object.values(this.configLoader.configData.network) as Array<AdapterConfigWrap>;
const network = Object.values(this.configLoader.configData.network).flat() as Array<AdapterConfigWrap>;
this.context.logger.logDebug('收到新消息 RawMessage', message);
await this.handleMsg(message, network);
await this.handleGroupEvent(message);
await this.handlePrivateMsgEvent(message);
}
private async handleMsg(message: RawMessage, network: Array<AdapterConfigWrap>) {
try {
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message);
if (!ob11Msg) return;
if (ob11Msg) {
const isSelfMsg = this.isSelfMessage(ob11Msg);
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message);
this.handleDebugNetwork(network, msgMap, message);
this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg);
this.networkManager.emitEventByNames(msgMap);
}
const isSelfMsg = this.isSelfMessage(ob11Msg);
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message);
this.handleDebugNetwork(network, msgMap, message);
this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg);
this.networkManager.emitEventByNames(msgMap);
} catch (e) {
this.context.logger.logError('constructMessage error: ', e);
}
this.handleGroupEvent(message);
this.handlePrivateMsgEvent(message);
}
private isSelfMessage(ob11Msg: {
stringMsg: OB11Message;
arrayMsg: OB11Message;
@@ -575,7 +576,7 @@ export class NapCatOneBot11Adapter {
private createMsgMap(network: Array<AdapterConfigWrap>, ob11Msg: any, isSelfMsg: boolean, message: RawMessage): Map<string, OB11Message> {
const msgMap: Map<string, OB11Message> = new Map();
network.flat().filter(e => e.enable).forEach(e => {
network.filter(e => e.enable).forEach(e => {
if (e.messagePostFormat == 'string') {
msgMap.set(e.name, structuredClone(ob11Msg.stringMsg));
} else {
@@ -590,7 +591,7 @@ export class NapCatOneBot11Adapter {
}
private handleDebugNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, message: RawMessage) {
const debugNetwork = network.flat().filter(e => e.enable && e.debug);
const debugNetwork = network.filter(e => e.enable && e.debug);
if (debugNetwork.length > 0) {
debugNetwork.forEach(adapter => {
const msg = msgMap.get(adapter.name);
@@ -605,7 +606,7 @@ export class NapCatOneBot11Adapter {
private handleNotReportSelfNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, isSelfMsg: boolean) {
if (isSelfMsg) {
const notReportSelfNetwork = network.flat().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 => {
msgMap.delete(adapter.name);
});
@@ -638,7 +639,7 @@ export class NapCatOneBot11Adapter {
// log("message update", message.sendStatus, message.msgId, message.msgSeq)
const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' };
if (message.recallTime != '0' && !cache.get(message.msgId)) {
//work:这个判断方法不太好,应该使用灰色消息元素来判断?
//TODO: 这个判断方法不太好,应该使用灰色消息元素来判断?
cache.put(message.msgId, true);
// 撤回消息上报
let oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId);

View File

@@ -43,7 +43,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
}
}, this.config.heartInterval);
}
this.isEnable = true;
await this.tryConnect();
}
@@ -70,7 +70,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
}
private async tryConnect() {
if (!this.connection && !this.isEnable) {
if (!this.connection && this.isEnable) {
let isClosedByError = false;
this.connection = new WebSocket(this.config.url, {
@@ -106,7 +106,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
if (!isClosedByError) {
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接意外关闭`);
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`);
if (!this.isEnable) {
if (this.isEnable) {
this.connection = null;
setTimeout(() => this.tryConnect(), this.config.reconnectInterval);
}
@@ -116,7 +116,7 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
isClosedByError = true;
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接错误`, err);
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`);
if (!this.isEnable) {
if (this.isEnable) {
this.connection = null;
setTimeout(() => this.tryConnect(), this.config.reconnectInterval);
}

View File

@@ -17,7 +17,6 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
wsClients: WebSocket[] = [];
wsClientsMutex = new Mutex();
isEnable: boolean = false;
hasBeenClosed: boolean = false;
heartbeatInterval: number = 0;
logger: LogWrapper;
public config: WebsocketServerConfig;
@@ -107,10 +106,6 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Cannot open a opened WebSocket server');
return;
}
if (this.hasBeenClosed) {
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Cannot open a WebSocket server that has been closed');
return;
}
const addressInfo = this.wsServer.address();
this.logger.log('[OneBot] [WebSocket Server] Server Started', typeof (addressInfo) === 'string' ? addressInfo : addressInfo?.address + ':' + addressInfo?.port);