Merge pull request #565 from Ander-pixe/webui-new

修改webui
This commit is contained in:
手瓜一十雪
2024-11-27 18:35:13 +08:00
committed by GitHub
24 changed files with 1118 additions and 663 deletions

View File

@@ -1,13 +1,13 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" /> <link rel="icon" type="image/svg+xml" href="./logo_webui.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NapCat WebUI</title> <title>NapCat WebUI</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="./src/main.ts"></script> <script type="module" src="./src/main.ts"></script>
</body> </body>
</html> </html>

View File

@@ -14,7 +14,7 @@
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"tdesign-icons-vue-next": "^0.3.3", "tdesign-icons-vue-next": "^0.3.3",
"tdesign-vue-next": "^1.10.3", "tdesign-vue-next": "^1.10.3",
"vue": "^3.5.12", "vue": "^3.5.13",
"vue-router": "^4.4.5" "vue-router": "^4.4.5"
}, },
"devDependencies": { "devDependencies": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -1,7 +1,112 @@
<template> <template>
<div id="app"> <div id="app" theme-mode="dark">
<router-view /> <router-view />
</div> </div>
<div v-if="show">
<t-sticky-tool shape="round" placement="right-bottom" :offset="[-50, 10]" @click="changeTheme">
<t-sticky-item label="浅色" popup="切换浅色模式">
<template #icon><sunny-icon /></template>
</t-sticky-item>
<t-sticky-item label="深色" popup="切换深色模式">
<template #icon><mode-dark-icon /></template>
</t-sticky-item>
<t-sticky-item label="自动" popup="跟随系统">
<template #icon><control-platform-icon /></template>
</t-sticky-item>
</t-sticky-tool>
</div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
import { onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue';
import { ControlPlatformIcon, ModeDarkIcon, SunnyIcon } from 'tdesign-icons-vue-next';
const smallScreen = window.matchMedia('(max-width: 768px)');
interface Item {
label: string;
popup: string;
}
interface Context {
item: Item;
}
enum ThemeMode {
Dark = 'dark',
Light = 'light',
Auto = 'auto',
}
const themeLabelMap: Record<string, ThemeMode> = {
"浅色": ThemeMode.Light,
"深色": ThemeMode.Dark,
"自动": ThemeMode.Auto,
};
const show = ref<boolean>(true);
const createSetThemeAttributeFunction = () => {
let mediaQueryForAutoTheme: MediaQueryList | null = null;
return (mode: ThemeMode | null) => {
const element = document.documentElement;
if (mode === ThemeMode.Dark) {
element.setAttribute('theme-mode', ThemeMode.Dark);
} else if (mode === ThemeMode.Light) {
element.removeAttribute('theme-mode');
} else if (mode === ThemeMode.Auto) {
mediaQueryForAutoTheme = window.matchMedia('(prefers-color-scheme: dark)');
const handleMediaChange = (e: MediaQueryListEvent) => {
if (e.matches) {
element.setAttribute('theme-mode', ThemeMode.Dark);
} else {
element.removeAttribute('theme-mode');
}
};
mediaQueryForAutoTheme.addEventListener('change', handleMediaChange);
const event = new Event('change');
Object.defineProperty(event, 'matches', {
value: mediaQueryForAutoTheme.matches,
writable: false,
});
mediaQueryForAutoTheme.dispatchEvent(event);
onBeforeUnmount(() => {
if (mediaQueryForAutoTheme) {
mediaQueryForAutoTheme.removeEventListener('change', handleMediaChange);
}
});
}
};
};
const setThemeAttribute = createSetThemeAttributeFunction();
const getStoredTheme = (): ThemeMode | null => {
return localStorage.getItem('theme') as ThemeMode | null;
};
const initTheme = () => {
const storedTheme = getStoredTheme();
if (storedTheme === null) {
setThemeAttribute(ThemeMode.Auto);
} else {
setThemeAttribute(storedTheme);
}
};
const changeTheme = (context: Context) => {
const themeLabel = themeLabelMap[context.item.label] as ThemeMode;
console.log(themeLabel);
setThemeAttribute(themeLabel);
localStorage.setItem('theme', themeLabel);
};
const haddingFbars = () => {
show.value = !smallScreen.matches;
if (smallScreen.matches) {
localStorage.setItem('theme', 'auto');
}
};
onMounted(() => {
initTheme();
haddingFbars();
window.addEventListener('resize', haddingFbars);
});
onUnmounted(() => {
window.removeEventListener('resize', haddingFbars);
});
</script>
<style scoped></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -74,7 +74,7 @@ export class QQLoginManager {
} }
return false; return false;
} }
public async checkQQLoginStatusWithQrcode(): Promise<{ qrcodeurl: string, isLogin: string } | undefined> { public async checkQQLoginStatusWithQrcode(): Promise<{ qrcodeurl: string; isLogin: string } | undefined> {
try { try {
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/CheckLoginStatus`, { const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/CheckLoginStatus`, {
method: 'POST', method: 'POST',

View File

@@ -1,16 +1,18 @@
<template> <template>
<div class="dashboard-container"> <t-layout class="dashboard-container">
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" /> <div ref="menuRef">
<div class="content"> <SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
<router-view />
</div> </div>
</div> <t-layout>
<router-view />
</t-layout>
</t-layout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { onMounted, ref } from 'vue';
import SidebarMenu from './webui/Nav.vue'; import SidebarMenu from './webui/Nav.vue';
import emitter from '@/ts/event-bus';
interface MenuItem { interface MenuItem {
value: string; value: string;
icon: string; icon: string;
@@ -25,6 +27,14 @@ const menuItems = ref<MenuItem[]>([
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' }, { value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' }, { value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
]); ]);
const menuRef = ref<HTMLDivElement | null>(null);
emitter.on('sendMenu', (event) => {
emitter.emit('sendWidth', menuRef.value?.offsetWidth);
localStorage.setItem('menuWidth', menuRef.value?.offsetWidth?.toString() || '0');
});
onMounted(() => {
localStorage.setItem('menuWidth', menuRef.value?.offsetWidth?.toString() || '0');
});
</script> </script>
<style scoped> <style scoped>
@@ -32,6 +42,7 @@ const menuItems = ref<MenuItem[]>([
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 100vh; height: 100vh;
width: 100%;
} }
.sidebar-menu { .sidebar-menu {
@@ -39,14 +50,6 @@ const menuItems = ref<MenuItem[]>([
z-index: 2; z-index: 2;
} }
.content {
flex: 1;
/* padding: 20px; */
overflow: auto;
position: relative;
z-index: 1;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.content { .content {
padding: 10px; padding: 10px;

View File

@@ -1,22 +1,43 @@
<template> <template>
<div class="login-container"> <t-card class="layout">
<h2 class="sotheby-font">QQ Login</h2> <div class="login-container">
<div class="login-methods"> <h2 class="sotheby-font">QQ Login</h2>
<t-button id="quick-login" class="login-method" :class="{ active: loginMethod === 'quick' }" <div class="login-methods">
@click="loginMethod = 'quick'">Quick Login</t-button> <t-tooltip content="快速登录">
<t-button id="qrcode-login" class="login-method" :class="{ active: loginMethod === 'qrcode' }" <t-button
@click="loginMethod = 'qrcode'">QR Code</t-button> id="quick-login"
class="login-method"
:class="{ active: loginMethod === 'quick' }"
@click="loginMethod = 'quick'"
>Quick Login</t-button
>
</t-tooltip>
<t-tooltip content="二维码登录">
<t-button
id="qrcode-login"
class="login-method"
:class="{ active: loginMethod === 'qrcode' }"
@click="loginMethod = 'qrcode'"
>QR Code</t-button
>
</t-tooltip>
</div>
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
<t-select
id="quick-login-select"
v-model="selectedAccount"
placeholder="Select Account"
@change="selectAccount"
>
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
</t-select>
</div>
<div v-show="loginMethod === 'qrcode'" id="qrcode" class="qrcode">
<canvas ref="qrcodeCanvas"></canvas>
</div>
</div> </div>
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form"> <t-footer class="footer">Power By NapCat.WebUi</t-footer>
<t-select id="quick-login-select" v-model="selectedAccount" placeholder="Select Account" </t-card>
@change="selectAccount">
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
</t-select>
</div>
<div v-show="loginMethod === 'qrcode'" id="qrcode" class="qrcode">
<canvas ref="qrcodeCanvas"></canvas>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -88,14 +109,16 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
.layout {
height: 100vh;
}
.login-container { .login-container {
padding: 20px; padding: 20px;
border-radius: 5px; border-radius: 5px;
background-color: white;
max-width: 400px; max-width: 400px;
min-width: 300px; min-width: 300px;
position: relative; position: relative;
margin: 0 auto; margin: 50px auto;
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@@ -154,7 +177,5 @@ onMounted(() => {
bottom: 20px; bottom: 20px;
left: 0; left: 0;
right: 0; right: 0;
width: 100%;
background-color: white;
} }
</style> </style>

View File

@@ -1,20 +1,22 @@
<template> <template>
<div class="login-container"> <t-card class="layout">
<h2 class="sotheby-font">WebUi Login</h2> <div class="login-container">
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit"> <h2 class="sotheby-font">WebUi Login</h2>
<t-form-item name="password"> <t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token"> <t-form-item name="password">
<template #prefix-icon> <t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
<lock-on-icon /> <template #prefix-icon>
</template> <lock-on-icon />
</t-input> </template>
</t-form-item> </t-input>
<t-form-item> </t-form-item>
<t-button theme="primary" type="submit" block>登录</t-button> <t-form-item>
</t-form-item> <t-button theme="primary" type="submit" block>登录</t-button>
</t-form> </t-form-item>
</div> </t-form>
<div class="footer">Power By NapCat.WebUi</div> </div>
<t-footer class="footer">Power By NapCat.WebUi</t-footer>
</t-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -94,14 +96,16 @@ const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
</script> </script>
<style scoped> <style scoped>
.layout {
height: 100vh;
}
.login-container { .login-container {
padding: 20px; padding: 20px;
border-radius: 5px; border-radius: 5px;
background-color: white;
max-width: 400px; max-width: 400px;
min-width: 300px; min-width: 300px;
position: relative; position: relative;
margin: 0 auto; margin: 50px auto;
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@@ -145,7 +149,5 @@ const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
bottom: 20px; bottom: 20px;
left: 0; left: 0;
right: 0; right: 0;
width: 100%;
background-color: white;
} }
</style> </style>

View File

@@ -1,16 +1,31 @@
<template> <template>
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu"> <t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
<template #logo> </template> <template #logo>
<div class="logo">
<img class="logo-img" :width="collapsed ? 35 : 'auto'" src="@/assets/logo_webui.png" alt="logo" />
<div class="logo-textBox">
<div class="logo-text">{{ collapsed ? '' : 'NapCat' }}</div>
</div>
</div>
</template>
<router-link v-for="item in menuItems" :key="item.value" :to="item.route"> <router-link v-for="item in menuItems" :key="item.value" :to="item.route">
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item"> <t-tooltip :disabled="!collapsed" :content="item.label" placement="right">
<template #icon> <t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
<t-icon :name="item.icon" /> <template #icon>
</template> <t-icon :name="item.icon" />
{{ item.label }} </template>
</t-menu-item> {{ item.label }}
</t-menu-item>
</t-tooltip>
</router-link> </router-link>
<template #operations> <template #operations>
<t-button class="t-demo-collapse-btn" variant="text" shape="square" @click="changeCollapsed"> <t-button
:disabled="disBtn"
class="t-demo-collapse-btn"
variant="text"
shape="square"
@click="changeCollapsed"
>
<template #icon><t-icon :name="iconName" /></template> <template #icon><t-icon :name="iconName" /></template>
</t-button> </t-button>
</template> </template>
@@ -18,7 +33,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineProps } from 'vue'; import { ref, defineProps, onMounted, watch } from 'vue';
import emitter from '@/ts/event-bus';
type MenuItem = { type MenuItem = {
value: string; value: string;
@@ -31,15 +47,39 @@ type MenuItem = {
defineProps<{ defineProps<{
menuItems: MenuItem[]; menuItems: MenuItem[];
}>(); }>();
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true'); const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold'); const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
const disBtn = ref<boolean>(false);
const changeCollapsed = (): void => { const changeCollapsed = (): void => {
collapsed.value = !collapsed.value; collapsed.value = !collapsed.value;
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold'; iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
localStorage.setItem('sidebar-collapsed', collapsed.value.toString()); localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
}; };
watch(collapsed, (newValue, oldValue) => {
setTimeout(() => {
emitter.emit('sendMenu', collapsed.value);
}, 300);
});
onMounted(() => {
const mediaQuery = window.matchMedia('(max-width: 800px)');
const handleMediaChange = (e: MediaQueryListEvent) => {
disBtn.value = e.matches;
if (e.matches) {
collapsed.value = e.matches;
}
};
mediaQuery.addEventListener('change', handleMediaChange);
const event = new Event('change');
Object.defineProperty(event, 'matches', {
value: mediaQuery.matches,
writable: false,
});
mediaQuery.dispatchEvent(event);
return () => {
mediaQuery.removeEventListener('change', handleMediaChange);
};
});
</script> </script>
<style scoped> <style scoped>
@@ -57,12 +97,28 @@ const changeCollapsed = (): void => {
width: 100px; /* 移动端侧边栏宽度 */ width: 100px; /* 移动端侧边栏宽度 */
} }
} }
.logo {
display: flex;
width: auto;
height: 100%;
}
.logo-img {
object-fit: contain;
margin-top: 8px;
margin-bottom: 8px;
}
.logo-textBox {
display: flex;
align-items: center;
margin-left: 10px;
}
.logo-text { .logo-text {
display: block; display: block;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 22px;
font-family: Sotheby, Helvetica, monospace;
} }
.menu-item { .menu-item {

View File

@@ -19,6 +19,10 @@ import {
List as TList, List as TList,
Alert as TAlert, Alert as TAlert,
Tag as TTag, Tag as TTag,
Descriptions as TDescriptionsProps,
DescriptionsItem as TDescriptionsItem,
Collapse as TCollapse,
CollapsePanel as TCollapsePanel,
ListItem as TListItem, ListItem as TListItem,
Tabs as TTabs, Tabs as TTabs,
TabPanel as TTabPanel, TabPanel as TTabPanel,
@@ -27,10 +31,18 @@ import {
Popup as TPopup, Popup as TPopup,
Dialog as TDialog, Dialog as TDialog,
Switch as TSwitch, Switch as TSwitch,
Tooltip as Tooltip,
StickyTool as TStickyTool,
StickyItem as TStickyItem,
Layout as TLayout,
Content as TContent,
Footer as TFooter,
Aside as TAside,
Popconfirm as Tpopconfirm,
Empty as TEmpty,
} from 'tdesign-vue-next'; } from 'tdesign-vue-next';
import { router } from './router'; import { router } from './router';
import 'tdesign-vue-next/es/style/index.css'; import 'tdesign-vue-next/es/style/index.css';
const app = createApp(App); const app = createApp(App);
app.use(router); app.use(router);
app.use(TButton); app.use(TButton);
@@ -51,6 +63,10 @@ app.use(TLink);
app.use(TList); app.use(TList);
app.use(TAlert); app.use(TAlert);
app.use(TTag); app.use(TTag);
app.use(TDescriptionsProps);
app.use(TDescriptionsItem);
app.use(TCollapse);
app.use(TCollapsePanel);
app.use(TListItem); app.use(TListItem);
app.use(TTabs); app.use(TTabs);
app.use(TTabPanel); app.use(TTabPanel);
@@ -59,4 +75,13 @@ app.use(TCheckbox);
app.use(TPopup); app.use(TPopup);
app.use(TDialog); app.use(TDialog);
app.use(TSwitch); app.use(TSwitch);
app.use(Tooltip);
app.use(TStickyTool);
app.use(TStickyItem);
app.use(TLayout);
app.use(TContent);
app.use(TFooter);
app.use(TAside);
app.use(Tpopconfirm);
app.use(TEmpty);
app.mount('#app'); app.mount('#app');

View File

@@ -1,122 +1,300 @@
<template> <template>
<t-space class="full-space"> <div ref="headerBox" class="title">
<template v-if="clientPanelData.length > 0"> <t-divider content="网络配置" align="left" />
<t-tabs <t-divider align="right">
v-model="activeTab" <t-button @click="addConfig()">
:addable="true" <template #icon><add-icon /></template>
theme="card" 添加配置</t-button>
@add="showAddTabDialog" </t-divider>
@remove="removeTab" </div>
class="full-tabs" <div v-if="loadPage" ref="setting" class="setting">
> <t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
<t-tab-panel <t-tab-panel value="all" label="全部"></t-tab-panel>
v-for="(config, idx) in clientPanelData" <t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
:key="idx" <t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
:label="config.name" <t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
:removable="true" <t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
:value="idx" </t-tabs>
class="full-tab-panel" </div>
> <div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" /> <div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
<div class="button-container"> <div v-for="(item, index) in cardConfig" :key="index">
<t-button @click="saveConfig" style="width: 100px; height: 40px">保存</t-button> <t-card :title="item.name" :description="item.type" :style="{ width: cardWidth + 'px' }"
:header-bordered="true" class="setting-card">
<template #actions>
<t-space>
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
<t-popconfirm theme="danger" content="确认删除" @confirm="delConfig(item)">
<delete-icon size="20px"></delete-icon>
</t-popconfirm>
</t-space>
</template>
<div class="setting-content">
<t-card class="card-address" :style="{
borderLeft: '7px solid ' + (item.enable ?
'var(--td-success-color)' :
'var(--td-error-color)')
}">
<div class="local-box" v-if="item.host&&item.port">
<server-filled-icon class="local-icon" size="20px"></server-filled-icon>
<strong class="local">{{ item.host }}:{{ item.port }}</strong>
<copy-icon class="copy-icon" size="20px" @click="copyText(item.host + ':' + item.port)"></copy-icon>
</div>
<div class="local-box" v-if="item.url">
<server-filled-icon class="local-icon" size="20px"></server-filled-icon>
<strong class="local" >{{ item.url }}</strong>
<copy-icon class="copy-icon" size="20px" @click="copyText(item.url)"></copy-icon>
</div>
</t-card>
<t-collapse :default-value="[0]" expand-mutex style="margin-top:10px;" class="info-coll">
<t-collapse-panel header="基础信息">
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
class="setting-base-info">
<t-descriptions-item v-if="item.token" label="连接密钥">
<div v-if="mediumScreen.matches||largeScreen.matches" class="token-view">
<span>{{ showToken ? item.token : '******' }}</span>
<browse-icon class="browse-icon" v-if="showToken" size="18px"
@click="showToken = false"></browse-icon>
<browse-off-icon class="browse-icon" v-else size="18px"
@click="showToken = true"></browse-off-icon>
</div>
<div v-else>
<t-popup :showArrow="true" trigger="click">
<t-tag theme="primary">点击查看</t-tag>
<template #content>
<div @click="copyText(item.token)">{{item.token}}</div>
</template>
</t-popup>
</div>
</t-descriptions-item>
<t-descriptions-item label="消息格式">{{ item.messagePostFormat }}</t-descriptions-item>
</t-descriptions>
</t-collapse-panel>
<t-collapse-panel header="状态信息">
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
class="setting-base-info">
<t-descriptions-item v-if="item.hasOwnProperty('debug')" label="调试日志">
<t-tag class="tag-item" :theme="item.debug ? 'success' : 'danger'">
{{ item.debug ? '开启' : '关闭' }}</t-tag>
</t-descriptions-item>
<t-descriptions-item v-if="item.hasOwnProperty('enableWebsocket')"
label="Websocket 功能">
<t-tag class="tag-item" :theme="item.enableWebsocket ? 'success' : 'danger'">
{{ item.enableWebsocket ? '启用' : '禁用' }}</t-tag>
</t-descriptions-item>
<t-descriptions-item v-if="item.hasOwnProperty('enableCors')" label="跨域放行">
<t-tag class="tag-item" :theme="item.enableCors ? 'success' : 'danger'">
{{ item.enableCors ? '开启' : '关闭' }}</t-tag>
</t-descriptions-item>
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
label="上报自身消息">
<t-tag class="tag-item" :theme="item.reportSelfMessage ? 'success' : 'danger'">
{{ item.reportSelfMessage ? '开启' : '关闭' }}</t-tag>
</t-descriptions-item>
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
label="强制推送事件">
<t-tag class="tag-item"
:theme="item.enableForcePushEvent ? 'success' : 'danger'">
{{ item.enableForcePushEvent ? '开启' : '关闭' }}</t-tag>
</t-descriptions-item>
</t-descriptions>
</t-collapse-panel>
</t-collapse>
</div> </div>
</t-tab-panel> </t-card>
</t-tabs> </div>
</template> <div style="height: 20vh"></div>
<template v-else> </div>
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" /> <t-card v-else>
</template> <t-empty class="card-none" title="暂无网络配置"> </t-empty>
<t-dialog </t-card>
v-model:visible="isDialogVisible" </div>
header="添加网络配置" <t-dialog v-model:visible="visibleBody" :header="dialogTitle" :destroy-on-close="true"
@close="isDialogVisible = false" :show-in-attached-element="true" placement="center" :on-confirm="saveConfig" class=".t-dialog__ctx .t-dialog--defaul">
@confirm="addTab" <div slot="body" class="dialog-body" >
> <t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
<t-form ref="form" :model="newTab"> <t-form-item style="text-align: left" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name"> label="名称" name="name">
<t-input v-model="newTab.name" /> <t-input v-model="newTab.name" />
</t-form-item> </t-form-item>
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type"> <t-form-item style="text-align: left" :rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
<t-select v-model="newTab.type"> label="类型" name="type">
<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="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>
<component :is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
:config="newTab.data" />
</div>
</t-form> </t-form>
</t-dialog> </div>
</t-space> </t-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue'; import { AddIcon, DeleteIcon, Edit2Icon, ServerFilledIcon, CopyIcon, BrowseOffIcon, BrowseIcon } from 'tdesign-icons-vue-next';
import { MessagePlugin } from 'tdesign-vue-next'; import { onMounted, onUnmounted, ref, resolveDynamicComponent } from 'vue';
import emitter from '@/ts/event-bus';
import { import {
httpServerDefaultConfigs, mergeNetworkDefaultConfig,
httpClientDefaultConfigs, mergeOneBotConfigs,
websocketServerDefaultConfigs,
websocketClientDefaultConfigs,
HttpClientConfig,
HttpServerConfig,
WebsocketClientConfig,
WebsocketServerConfig,
NetworkConfig, NetworkConfig,
OneBotConfig, OneBotConfig,
mergeOneBotConfigs,
} from '../../../src/onebot/config/config'; } from '../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue'; import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue'; import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue'; import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue'; import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue'; import { MessagePlugin } from 'tdesign-vue-next';
import { QQLoginManager } from '@/backend/shell';
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients'; const showToken = ref<boolean>(false);
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig; const infoOneCol = ref<boolean>(true);
type ComponentUnion = const tabsWidth = ref<number>(0);
const menuWidth = ref<number>(0);
const cardWidth = ref<number>(0);
const cardHeight = ref<number>(0);
const mediumScreen = window.matchMedia('(min-width: 768px) and (max-width: 1024px)');
const largeScreen = window.matchMedia('(min-width: 1025px)');
const headerBox = ref<HTMLDivElement | null>(null);
const setting = ref<HTMLDivElement | null>(null);
const loadPage = ref<boolean>(false);
const visibleBody = ref<boolean>(false);
const newTab = ref<{ name: string; data: any; type: string }>({ name: '', data: {}, type: '' });
const dialogTitle = ref<string>('');
type ComponentKey = keyof typeof mergeNetworkDefaultConfig;
const componentMap: Record<
ComponentKey,
| typeof HttpServerComponent | typeof HttpServerComponent
| typeof HttpClientComponent | typeof HttpClientComponent
| typeof WebsocketServerComponent | typeof WebsocketServerComponent
| typeof WebsocketClientComponent; | typeof WebsocketClientComponent
> = {
const componentMap: Record<ConfigKey, ComponentUnion> = {
httpServers: HttpServerComponent, httpServers: HttpServerComponent,
httpClients: HttpClientComponent, httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent, websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent, websocketClients: WebsocketClientComponent,
}; };
const defaultConfigMap: Record<ConfigKey, ConfigUnion> = { //操作类型
httpServers: httpServerDefaultConfigs, const operateType = ref<string>('');
httpClients: httpClientDefaultConfigs, //配置项索引
websocketServers: websocketServerDefaultConfigs, const configIndex = ref<number>(0);
websocketClients: websocketClientDefaultConfigs, //保存时所用数据
const networkConfig: NetworkConfig & { [key: string]: any; } = {
websocketClients: [],
websocketServers: [],
httpClients: [],
httpServers: [],
}; };
interface ConfigMap { //挂载的数据
httpServers: HttpServerConfig; const WebConfg = ref(
httpClients: HttpClientConfig; new Map<string, Array<null>>([
websocketServers: WebsocketServerConfig; ['all', []],
websocketClients: WebsocketClientConfig; ['httpServers', []],
} ['httpClients', []],
['websocketServers', []],
interface ClientPanel<K extends ConfigKey = ConfigKey> { ['websocketClients', []],
name: string; ])
key: K; );
data: ConfigMap[K]; const typeCh: Record<ComponentKey, string> = {
} httpServers: 'HTTP 服务器',
httpClients: 'HTTP 客户端',
const activeTab = ref<number>(0); websocketServers: 'WebSocket 服务器',
const isDialogVisible = ref(false); websocketClients: 'WebSocket 客户端',
const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' }); };
const clientPanelData: Ref<ClientPanel[]> = ref([]); const cardConfig = ref<any>([]);
const getComponent = (type: ComponentKey) => {
const getComponent = (type: ConfigKey) => {
return componentMap[type]; return componentMap[type];
}; };
const getKeyByValue = (obj: typeof typeCh, value: string): string | undefined => {
return Object.entries(obj).find(([_, v]) => v === value)?.[0];
};
const addConfig = () => {
dialogTitle.value = '添加配置';
newTab.value = { name: '', data: {}, type: '' };
operateType.value = 'add';
visibleBody.value = true;
};
const editConfig = (item: any) => {
dialogTitle.value = '修改配置';
const type = getKeyByValue(typeCh, item.type);
if (type) {
newTab.value = { name: item.name, data: item, type: type };
}
operateType.value = 'edit';
configIndex.value = networkConfig[newTab.value.type].findIndex((obj: any) => obj.name === item.name);
visibleBody.value = true;
};
const delConfig = (item: any) => {
const type = getKeyByValue(typeCh, item.type);
if (type) {
newTab.value = { name: item.name, data: item, type: type };
}
configIndex.value = configIndex.value = networkConfig[newTab.value.type].findIndex(
(obj: any) => obj.name === item.name
);
operateType.value = 'delete';
saveConfig();
};
const selectType = (key: ComponentKey) => {
cardConfig.value = WebConfg.value.get(key);
};
const onloadDefault = (key: ComponentKey) => {
console.log(key);
newTab.value.data = structuredClone(mergeNetworkDefaultConfig[key]);
};
//检测重名
const checkName = (name: string) => {
const allConfigs = WebConfg.value.get('all')?.findIndex((obj: any) => obj.name === name);
if (newTab.value.name === '' || newTab.value.type === '') {
MessagePlugin.error('请填写完整信息');
return false;
} else if (allConfigs === -1 || newTab.value.data.name === name) {
return true;
} else {
MessagePlugin.error('名称已存在');
return false;
}
};
//保存
const saveConfig = async () => {
if (operateType.value == 'add') {
if (!checkName(newTab.value.name)) return;
newTab.value.data.name = newTab.value.name;
networkConfig[newTab.value.type].push(newTab.value.data);
} else if (operateType.value == 'edit') {
if (!checkName(newTab.value.name)) return;
newTab.value.data.name = newTab.value.name;
networkConfig[newTab.value.type][configIndex.value] = newTab.value.data;
} else if (operateType.value == 'delete') {
networkConfig[newTab.value.type].splice(configIndex.value, 1);
}
const userConfig = await getOB11Config();
if (!userConfig) return;
userConfig.network = networkConfig;
const success = await setOB11Config(userConfig);
if (success) {
operateType.value = '';
configIndex.value = 0;
MessagePlugin.success('配置保存成功');
await loadConfig();
visibleBody.value = false;
} else {
MessagePlugin.error('配置保存失败');
}
};
const getOB11Config = async (): Promise<OneBotConfig | undefined> => { const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth'); const storedCredential = localStorage.getItem('auth');
if (!storedCredential) { if (!storedCredential) {
@@ -137,27 +315,27 @@ const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
return await loginManager.SetOB11Config(config); return await loginManager.SetOB11Config(config);
}; };
const addToPanel = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => { //获取卡片数据
configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key })); const getAllData = (data: NetworkConfig) => {
}; cardConfig.value = [];
WebConfg.value.set('all', []);
const addConfigDataToPanel = (data: NetworkConfig) => { for (const key in data) {
(Object.keys(data) as ConfigKey[]).forEach((key) => { const configs = data[key as keyof NetworkConfig];
addToPanel(data[key], key); if (key in mergeNetworkDefaultConfig) {
}); networkConfig[key] = [...configs];
}; const newConfigsArray = configs.map((config: any) => ({
...config,
const parsePanelData = (): NetworkConfig => { type: typeCh[key as ComponentKey],
const result: NetworkConfig = { }));
httpServers: [], WebConfg.value.set(key, newConfigsArray);
httpClients: [], const allConfigs = WebConfg.value.get('all');
websocketServers: [], if (allConfigs) {
websocketClients: [], const newAllConfigs = [...allConfigs, ...newConfigsArray];
}; WebConfg.value.set('all', newAllConfigs);
clientPanelData.value.forEach((panel) => { }
(result[panel.key] as Array<typeof panel.data>).push(panel.data); cardConfig.value = WebConfg.value.get('all');
}); }
return result; }
}; };
const loadConfig = async () => { const loadConfig = async () => {
@@ -165,85 +343,198 @@ const loadConfig = async () => {
const userConfig = await getOB11Config(); const userConfig = await getOB11Config();
if (!userConfig) return; if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig); const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network); getAllData(mergedConfig.network);
} catch (error) { } catch (error) {
console.error('Error loading config:', error); console.error('Error loading config:', error);
} }
}; };
const saveConfig = async () => { const copyText = async (text: string) => {
const config = parsePanelData(); const input = document.createElement('input');
const userConfig = await getOB11Config(); input.value = text;
if (!userConfig) { document.body.appendChild(input);
await MessagePlugin.error('无法获取配置!'); input.select();
return; await navigator.clipboard.writeText(text);
} document.body.removeChild(input);
userConfig.network = config; MessagePlugin.success('复制成功');
const success = await setOB11Config(userConfig); };
if (success) {
await MessagePlugin.success('配置保存成功'); const handleResize = () => {
// 得根据卡片宽度改,懒得改了;先不管了
// if(window.innerWidth < 540) {
// infoOneCol.value= true
// } else {
// infoOneCol.value= false
// }
tabsWidth.value = window.innerWidth - 41 - menuWidth.value;
if (mediumScreen.matches) {
cardWidth.value = (tabsWidth.value - 20) / 2;
} else if (largeScreen.matches) {
cardWidth.value = (tabsWidth.value - 40) / 3;
} else { } else {
await MessagePlugin.error('配置保存失败'); cardWidth.value = tabsWidth.value;
} }
loadPage.value = true;
setTimeout(() => {
cardHeight.value = window.innerHeight - (headerBox.value?.offsetHeight ?? 0) - (setting.value?.offsetHeight ?? 0) - 21;
}, 300);
}; };
emitter.on('sendWidth', (width) => {
const showAddTabDialog = () => { if (typeof width === 'number' && !isNaN(width)) {
newTab.value = { name: '', type: 'httpServers' }; menuWidth.value = width;
isDialogVisible.value = true; handleResize();
};
const addTab = async () => {
const { name, type } = newTab.value;
if (clientPanelData.value.some((panel) => panel.name === name)) {
await MessagePlugin.error('选项卡名称已存在');
return;
} }
const defaultConfig = structuredClone(defaultConfigMap[type]); });
defaultConfig.name = name;
clientPanelData.value.push({ name, data: defaultConfig, key: type });
isDialogVisible.value = false;
await nextTick();
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(); loadConfig();
const cachedWidth = localStorage.getItem('menuWidth');
if (cachedWidth) {
menuWidth.value = parseInt(cachedWidth);
setTimeout(() => {
handleResize();
}, 300);
}
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
}); });
</script> </script>
<style scoped> <style scoped>
.full-space { .title {
width: 100%; padding: 20px 20px 0 20px;
height: 100%;
display: flex; display: flex;
flex-direction: column; justify-content: space-between;
align-items: flex-start;
justify-content: flex-start;
} }
.full-tabs { .setting {
width: 100%; margin: 0 20px;
height: 100%;
display: flex;
flex-direction: column;
} }
.full-tab-panel { .setting-box {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
overflow-y: auto;
}
.setting-card {
width: 100%;
text-align: left;
}
.setting-content {
width: 100%;
}
.card-address svg {
fill: var(--td-brand-color);
cursor: pointer;
}
.local-box {
display: flex;
margin-top: 2px;
}
.local-icon{
flex: 1; flex: 1;
display: flex; }
flex-direction: column; .local {
flex: 6;
margin: 0 10px 0 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.button-container {
.copy-icon {
flex: 1;
cursor: pointer;
flex-direction: row;
}
.token-view {
display: flex; display: flex;
justify-content: center; align-items: center;
margin-top: 20px; }
.token-view span {
flex: 5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.browse-icon{
flex: 2;
}
:global(.t-dialog__ctx .t-dialog--defaul) {
margin: 0 20px;
}
@media (max-width: 1024px) {
.setting-box {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 786px) {
.setting-box {
grid-template-columns: 1fr;
}
}
.card-box {
margin: 10px 20px 0 20px;
}
.card-none {
line-height: 400px !important;
}
.dialog-body {
max-height: 60vh;
overflow-y: auto;
}
::-webkit-scrollbar {
width: 0;
background: transparent;
}
</style>
<style>
.setting-card .t-card__title {
text-align: left !important;
}
.setting-card .t-card__description {
margin-bottom: 0;
font-size: 12px;
}
.card-address .t-card__body {
display: flex;
flex-direction: row;
align-items: center;
}
.setting-base-info .t-descriptions__header {
font-size: 15px;
margin-bottom: 0;
}
.setting-base-info .t-descriptions__label {
padding: 0 var(--td-comp-paddingLR-l) !important;
}
.setting-base-info tr>td:last-child {
text-align: right;
}
.info-coll .t-collapse-panel__wrapper .t-collapse-panel__content {
padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-l);
} }
</style> </style>

View File

@@ -1,25 +1,24 @@
<template> <template>
<div> <div class="title">
<t-divider content="其余配置" align="left" /> <t-divider content="其余配置" align="left" />
</div> </div>
<div class="other-config-container"> <t-card class="card">
<div class="other-config"> <div class="other-config-container">
<t-form ref="form" :model="otherConfig" class="form"> <div class="other-config">
<t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item"> <t-form ref="form" :model="otherConfig" :label-align="labelAlign" label-width="auto" colon>
<t-input v-model="otherConfig.musicSignUrl" /> <t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item">
</t-form-item> <t-input v-model="otherConfig.musicSignUrl" />
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item"> </t-form-item>
<t-switch v-model="otherConfig.enableLocalFile2Url" /> <t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
</t-form-item> <t-switch v-model="otherConfig.enableLocalFile2Url" />
<t-form-item label="启用上报解析合并消息" name="parseMultMsg" class="form-item"> </t-form-item>
<t-switch v-model="otherConfig.parseMultMsg" /> </t-form>
</t-form-item> <div class="button-container">
</t-form> <t-button @click="saveConfig">保存</t-button>
<div class="button-container"> </div>
<t-button @click="saveConfig">保存</t-button>
</div> </div>
</div> </div>
</div> </t-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -31,9 +30,9 @@ import { QQLoginManager } from '@/backend/shell';
const otherConfig = ref<Partial<OneBotConfig>>({ const otherConfig = ref<Partial<OneBotConfig>>({
musicSignUrl: '', musicSignUrl: '',
enableLocalFile2Url: false, enableLocalFile2Url: false,
parseMultMsg: true
}); });
const labelAlign = ref<string>();
const getOB11Config = async (): Promise<OneBotConfig | undefined> => { const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth'); const storedCredential = localStorage.getItem('auth');
if (!storedCredential) { if (!storedCredential) {
@@ -60,7 +59,6 @@ const loadConfig = async () => {
if (userConfig) { if (userConfig) {
otherConfig.value.musicSignUrl = userConfig.musicSignUrl; otherConfig.value.musicSignUrl = userConfig.musicSignUrl;
otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url; otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url;
otherConfig.value.parseMultMsg = userConfig.parseMultMsg;
} }
} catch (error) { } catch (error) {
console.error('Error loading config:', error); console.error('Error loading config:', error);
@@ -73,7 +71,6 @@ const saveConfig = async () => {
if (userConfig) { if (userConfig) {
userConfig.musicSignUrl = otherConfig.value.musicSignUrl || ''; userConfig.musicSignUrl = otherConfig.value.musicSignUrl || '';
userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false; userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false;
userConfig.parseMultMsg = otherConfig.value.parseMultMsg ?? true;
const success = await setOB11Config(userConfig); const success = await setOB11Config(userConfig);
if (success) { if (success) {
MessagePlugin.success('配置保存成功'); MessagePlugin.success('配置保存成功');
@@ -86,55 +83,60 @@ const saveConfig = async () => {
MessagePlugin.error('配置保存失败'); MessagePlugin.error('配置保存失败');
} }
}; };
onMounted(() => { onMounted(() => {
loadConfig(); loadConfig();
const mediaQuery = window.matchMedia('(max-width: 768px)');
const handleMediaChange = (e: MediaQueryListEvent) => {
if (e.matches) {
labelAlign.value = 'top';
} else {
labelAlign.value = 'left';
}
};
mediaQuery.addEventListener('change', handleMediaChange);
const event = new Event('change');
Object.defineProperty(event, 'matches', {
value: mediaQuery.matches,
writable: false,
});
mediaQuery.dispatchEvent(event);
return () => {
mediaQuery.removeEventListener('change', handleMediaChange);
};
}); });
</script> </script>
<style scoped> <style scoped>
.title {
padding: 20px 20px 0 20px;
}
.card {
margin: 0 20px;
padding-top: 20px;
padding-bottom: 20px;
}
.other-config-container { .other-config-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-start; align-items: flex-start;
padding: 20px;
box-sizing: border-box; box-sizing: border-box;
} }
.other-config { .other-config {
width: 100%; width: 100%;
max-width: 600px; max-width: 500px;
background: #fff;
padding: 20px;
border-radius: 8px; border-radius: 8px;
} }
.form {
display: flex;
flex-direction: column;
}
.form-item { .form-item {
display: flex;
flex-direction: column;
margin-bottom: 20px; margin-bottom: 20px;
text-align: left;
} }
.button-container { .button-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
} margin-top: 20px;
@media (min-width: 768px) {
.form-item {
flex-direction: row;
align-items: center;
}
.form-item t-input,
.form-item t-switch {
flex: 1;
margin-left: 20px;
}
} }
</style> </style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="empty-state">
<p>当前没有网络配置</p>
<t-button @click="showAddTabDialog">添加网络配置</t-button>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
defineProps<{ showAddTabDialog: () => void }>();
</script>
<style scoped>
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
}
</style>

View File

@@ -1,28 +1,25 @@
<template> <template>
<div class="container"> <div>
<div class="form-container"> <t-form labelAlign="left">
<h3>HTTP Client 配置</h3> <t-form-item label="启用">
<t-form> <t-checkbox v-model="config.enable" />
<t-form-item label="启用"> </t-form-item>
<t-checkbox v-model="config.enable" /> <t-form-item label="URL">
</t-form-item> <t-input v-model="config.url" />
<t-form-item label="URL"> </t-form-item>
<t-input v-model="config.url" /> <t-form-item label="消息格式">
</t-form-item> <t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
<t-form-item label="消息格式"> </t-form-item>
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" /> <t-form-item label="报告自身消息">
</t-form-item> <t-checkbox v-model="config.reportSelfMessage" />
<t-form-item label="报告自身消息"> </t-form-item>
<t-checkbox v-model="config.reportSelfMessage" /> <t-form-item label="Token">
</t-form-item> <t-input v-model="config.token" />
<t-form-item label="Token"> </t-form-item>
<t-input v-model="config.token" /> <t-form-item label="调试模式">
</t-form-item> <t-checkbox v-model="config.debug" />
<t-form-item label="调试模式"> </t-form-item>
<t-checkbox v-model="config.debug" /> </t-form>
</t-form-item>
</t-form>
</div>
</div> </div>
</template> </template>
@@ -49,20 +46,4 @@ watch(
); );
</script> </script>
<style scoped> <style scoped></style>
.container {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px;
box-sizing: border-box;
}
.form-container {
width: 100%;
max-width: 600px;
background: #fff;
padding: 20px;
border-radius: 8px;
}
</style>

View File

@@ -1,34 +1,31 @@
<template> <template>
<div class="container"> <div>
<div class="form-container"> <t-form labelAlign="left">
<h3>HTTP Server 配置</h3> <t-form-item label="启用">
<t-form> <t-checkbox v-model="config.enable" />
<t-form-item label="启用"> </t-form-item>
<t-checkbox v-model="config.enable" /> <t-form-item label="端口">
</t-form-item> <t-input v-model.number="config.port" type="number" />
<t-form-item label="端口"> </t-form-item>
<t-input v-model.number="config.port" type="number" /> <t-form-item label="主机">
</t-form-item> <t-input v-model="config.host" type="text" />
<t-form-item label="主机"> </t-form-item>
<t-input v-model="config.host" type="text" /> <t-form-item label="启用 CORS">
</t-form-item> <t-checkbox v-model="config.enableCors" />
<t-form-item label="启用 CORS"> </t-form-item>
<t-checkbox v-model="config.enableCors" /> <t-form-item label="启用 WS">
</t-form-item> <t-checkbox v-model="config.enableWebsocket" />
<t-form-item label="启用 WS"> </t-form-item>
<t-checkbox v-model="config.enableWebsocket" /> <t-form-item label="消息格式">
</t-form-item> <t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
<t-form-item label="消息格式"> </t-form-item>
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" /> <t-form-item label="Token">
</t-form-item> <t-input v-model="config.token" type="text" />
<t-form-item label="Token"> </t-form-item>
<t-input v-model="config.token" type="text" /> <t-form-item label="调试模式">
</t-form-item> <t-checkbox v-model="config.debug" />
<t-form-item label="调试模式"> </t-form-item>
<t-checkbox v-model="config.debug" /> </t-form>
</t-form-item>
</t-form>
</div>
</div> </div>
</template> </template>
@@ -55,20 +52,4 @@ watch(
); );
</script> </script>
<style scoped> <style scoped></style>
.container {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px;
box-sizing: border-box;
}
.form-container {
width: 100%;
max-width: 600px;
background: #fff;
padding: 20px;
border-radius: 8px;
}
</style>

View File

@@ -1,31 +1,28 @@
<template> <template>
<div class="container"> <div>
<div class="form-container"> <t-form labelAlign="left">
<h3>WebSocket Client 配置</h3> <t-form-item label="启用">
<t-form> <t-checkbox v-model="config.enable" />
<t-form-item label="启用"> </t-form-item>
<t-checkbox v-model="config.enable" /> <t-form-item label="URL">
</t-form-item> <t-input v-model="config.url" />
<t-form-item label="URL"> </t-form-item>
<t-input v-model="config.url" /> <t-form-item label="消息格式">
</t-form-item> <t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
<t-form-item label="消息格式"> </t-form-item>
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" /> <t-form-item label="报告自身消息">
</t-form-item> <t-checkbox v-model="config.reportSelfMessage" />
<t-form-item label="报告自身消息"> </t-form-item>
<t-checkbox v-model="config.reportSelfMessage" /> <t-form-item label="Token">
</t-form-item> <t-input v-model="config.token" />
<t-form-item label="Token"> </t-form-item>
<t-input v-model="config.token" /> <t-form-item label="调试模式">
</t-form-item> <t-checkbox v-model="config.debug" />
<t-form-item label="调试模式"> </t-form-item>
<t-checkbox v-model="config.debug" /> <t-form-item label="心跳间隔">
</t-form-item> <t-input v-model.number="config.heartInterval" type="number" />
<t-form-item label="心跳间隔"> </t-form-item>
<t-input v-model.number="config.heartInterval" type="number" /> </t-form>
</t-form-item>
</t-form>
</div>
</div> </div>
</template> </template>
@@ -52,20 +49,4 @@ watch(
); );
</script> </script>
<style scoped> <style scoped></style>
.container {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px;
box-sizing: border-box;
}
.form-container {
width: 100%;
max-width: 600px;
background: #fff;
padding: 20px;
border-radius: 8px;
}
</style>

View File

@@ -1,37 +1,34 @@
<template> <template>
<div class="container"> <div>
<div class="form-container"> <t-form labelAlign="left">
<h3>WebSocket Server 配置</h3> <t-form-item label="启用">
<t-form> <t-checkbox v-model="config.enable" />
<t-form-item label="启用"> </t-form-item>
<t-checkbox v-model="config.enable" /> <t-form-item label="主机">
</t-form-item> <t-input v-model="config.host" />
<t-form-item label="主机"> </t-form-item>
<t-input v-model="config.host" /> <t-form-item label="端口">
</t-form-item> <t-input v-model.number="config.port" type="number" />
<t-form-item label="端口"> </t-form-item>
<t-input v-model.number="config.port" type="number" /> <t-form-item label="消息格式">
</t-form-item> <t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
<t-form-item label="消息格式"> </t-form-item>
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" /> <t-form-item label="上报自身消息">
</t-form-item> <t-checkbox v-model="config.reportSelfMessage" />
<t-form-item label="上报自身消息"> </t-form-item>
<t-checkbox v-model="config.reportSelfMessage" /> <t-form-item label="Token">
</t-form-item> <t-input v-model="config.token" />
<t-form-item label="Token"> </t-form-item>
<t-input v-model="config.token" /> <t-form-item label="强制推送事件">
</t-form-item> <t-checkbox v-model="config.enableForcePushEvent" />
<t-form-item label="强制推送事件"> </t-form-item>
<t-checkbox v-model="config.enableForcePushEvent" /> <t-form-item label="调试模式">
</t-form-item> <t-checkbox v-model="config.debug" />
<t-form-item label="调试模式"> </t-form-item>
<t-checkbox v-model="config.debug" /> <t-form-item label="心跳间隔">
</t-form-item> <t-input v-model.number="config.heartInterval" type="number" />
<t-form-item label="心跳间隔"> </t-form-item>
<t-input v-model.number="config.heartInterval" type="number" /> </t-form>
</t-form-item>
</t-form>
</div>
</div> </div>
</template> </template>
@@ -58,20 +55,4 @@ watch(
); );
</script> </script>
<style scoped> <style scoped></style>
.container {
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px;
box-sizing: border-box;
}
.form-container {
width: 100%;
max-width: 600px;
background: #fff;
padding: 20px;
border-radius: 8px;
}
</style>

View File

@@ -0,0 +1,3 @@
import mitt from 'mitt';
const emitter = mitt();
export default emitter;

View File

@@ -182,7 +182,6 @@ export enum FileUriType {
} }
export async function checkUriType(Uri: string) { export async function checkUriType(Uri: string) {
const LocalFileRet = await solveProblem((uri: string) => { const LocalFileRet = await solveProblem((uri: string) => {
if (fs.existsSync(uri)) { if (fs.existsSync(uri)) {
return { Uri: uri, Type: FileUriType.Local }; return { Uri: uri, Type: FileUriType.Local };
@@ -200,14 +199,7 @@ export async function checkUriType(Uri: string) {
return { Uri: uri, Type: FileUriType.Base64 }; return { Uri: uri, Type: FileUriType.Base64 };
} }
if (uri.startsWith('file://')) { if (uri.startsWith('file://')) {
let filePath: string; let filePath: string = uri.slice(7);
const pathname = decodeURIComponent(new URL(uri).pathname + new URL(uri).hash);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
return { Uri: filePath, Type: FileUriType.Local }; return { Uri: filePath, Type: FileUriType.Local };
} }
if (uri.startsWith('data:')) { if (uri.startsWith('data:')) {
@@ -222,14 +214,16 @@ export async function checkUriType(Uri: string) {
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> { export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri); const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
//解析失败 //解析失败
const tempName = randomUUID(); const tempName = randomUUID();
if (!filename) filename = randomUUID(); if (!filename) filename = randomUUID();
//解析Http和Https协议
//解析Http和Https协议
if (UriType == FileUriType.Unknown) { if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' }; return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
} }
//解析File协议和本地文件 //解析File协议和本地文件
if (UriType == FileUriType.Local) { if (UriType == FileUriType.Local) {
const fileExt = path.extname(HandledUri); const fileExt = path.extname(HandledUri);
@@ -241,8 +235,8 @@ export async function uri2local(dir: string, uri: string, filename: string | und
fs.copyFileSync(HandledUri, filePath); fs.copyFileSync(HandledUri, filePath);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath }; return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
} }
//接下来都要有文件名 //接下来都要有文件名
if (UriType == FileUriType.Remote) { if (UriType == FileUriType.Remote) {
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname)); const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
if (pathInfo.name) { if (pathInfo.name) {
@@ -260,6 +254,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
fs.writeFileSync(filePath, buffer, { flag: 'wx' }); fs.writeFileSync(filePath, buffer, { flag: 'wx' });
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath }; return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
} }
//解析Base64 //解析Base64
if (UriType == FileUriType.Base64) { if (UriType == FileUriType.Base64) {
const base64 = HandledUri.replace(/^base64:\/\//, ''); const base64 = HandledUri.replace(/^base64:\/\//, '');

View File

@@ -54,6 +54,16 @@ export const PushMsg = {
generalFlag: ProtoField(9, ScalarType.INT32, true), generalFlag: ProtoField(9, ScalarType.INT32, true),
}; };
export const GroupChange = {
groupUin: ProtoField(1, ScalarType.UINT32),
flag: ProtoField(2, ScalarType.UINT32),
memberUid: ProtoField(3, ScalarType.STRING, true),
decreaseType: ProtoField(4, ScalarType.UINT32),
operatorUid: ProtoField(5, ScalarType.STRING, true),
increaseType: ProtoField(6, ScalarType.UINT32),
field7: ProtoField(7, ScalarType.BYTES, true),
}
export const PushMsgBody = { export const PushMsgBody = {
responseHead: ProtoField(1, () => ResponseHead), responseHead: ProtoField(1, () => ResponseHead),
contentHead: ProtoField(2, () => ContentHead), contentHead: ProtoField(2, () => ContentHead),

View File

@@ -23,7 +23,6 @@ import { OB11GroupCardEvent } from '@/onebot/event/notice/OB11GroupCardEvent';
import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent'; import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent'; import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent'; import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
import { OB11EmitEventContent } from '../network';
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent'; import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { FileNapCatOneBotUUID } from '@/common/helper'; import { FileNapCatOneBotUUID } from '@/common/helper';
@@ -93,40 +92,40 @@ export class OneBotGroupApi {
return undefined; return undefined;
} }
async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) { // async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement; // const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined; // if (!groupElement) return undefined;
const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid); // const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
const memberUin = member?.uin; // const memberUin = member?.uin;
const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid); // const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
if (memberUin) { // if (memberUin) {
const operatorUin = adminMember?.uin ?? memberUin; // const operatorUin = adminMember?.uin ?? memberUin;
return new OB11GroupIncreaseEvent( // return new OB11GroupIncreaseEvent(
this.core, // this.core,
parseInt(GroupCode), // parseInt(GroupCode),
parseInt(memberUin), // parseInt(memberUin),
parseInt(operatorUin), // parseInt(operatorUin),
); // );
} else { // } else {
return undefined; // return undefined;
} // }
} // }
async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) { // async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
const groupElement = grayTipElement?.groupElement; // const groupElement = grayTipElement?.groupElement;
if (!groupElement) return undefined; // if (!groupElement) return undefined;
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid)); // const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
if (adminUin) { // if (adminUin) {
return new OB11GroupDecreaseEvent( // return new OB11GroupDecreaseEvent(
this.core, // this.core,
parseInt(GroupCode), // parseInt(GroupCode),
parseInt(this.core.selfInfo.uin), // parseInt(this.core.selfInfo.uin),
parseInt(adminUin), // parseInt(adminUin),
'kick_me', // 'kick_me',
); // );
} // }
return undefined; // return undefined;
} // }
async parseGroupEmojiLikeEventByGrayTip( async parseGroupEmojiLikeEventByGrayTip(
groupCode: string, groupCode: string,
@@ -188,30 +187,30 @@ export class OneBotGroupApi {
return undefined; return undefined;
} }
async parseGroupElement(msg: RawMessage, groupElement: TipGroupElement, elementWrapper: GrayTipElement) { // async parseGroupElement(msg: RawMessage, groupElement: TipGroupElement, elementWrapper: GrayTipElement) {
if (groupElement.type == TipGroupElementType.KMEMBERADD) { // if (groupElement.type == TipGroupElementType.KMEMBERADD) {
const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, elementWrapper); // const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, elementWrapper);
if (MemberIncreaseEvent) return MemberIncreaseEvent; // if (MemberIncreaseEvent) return MemberIncreaseEvent;
} else if (groupElement.type === TipGroupElementType.KSHUTUP) { // } else if (groupElement.type === TipGroupElementType.KSHUTUP) {
const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, elementWrapper); // const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, elementWrapper);
if (BanEvent) return BanEvent; // if (BanEvent) return BanEvent;
} else if (groupElement.type == TipGroupElementType.KQUITTE) { // } else if (groupElement.type == TipGroupElementType.KQUITTE) {
this.core.apis.GroupApi.quitGroup(msg.peerUid).then(); // this.core.apis.GroupApi.quitGroup(msg.peerUid).then();
try { // try {
const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, elementWrapper); // const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, elementWrapper);
if (KickEvent) return KickEvent; // if (KickEvent) return KickEvent;
} catch (e) { // } catch (e) {
return new OB11GroupDecreaseEvent( // return new OB11GroupDecreaseEvent(
this.core, // this.core,
parseInt(msg.peerUid), // parseInt(msg.peerUid),
parseInt(this.core.selfInfo.uin), // parseInt(this.core.selfInfo.uin),
0, // 0,
'leave', // 'leave',
); // );
} // }
} // }
return undefined; // return undefined;
} // }
async parsePaiYiPai(msg: RawMessage, jsonStr: string) { async parsePaiYiPai(msg: RawMessage, jsonStr: string) {
const json = JSON.parse(jsonStr); const json = JSON.parse(jsonStr);
@@ -298,8 +297,8 @@ export class OneBotGroupApi {
async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) { async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) {
if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) { if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) {
// 解析群组事件 // 解析群组事件 由sysmsg解析
return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement); // return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement);
} else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) { } else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
// 筛选出表情回应 事件 // 筛选出表情回应 事件

View File

@@ -32,6 +32,10 @@ import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNot
// import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike'; // import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
import { ForwardMsgBuilder } from "@/common/forward-msg-builder"; import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
import { decodeSysMessage } from "@/core/helper/adaptDecoder"; import { decodeSysMessage } from "@/core/helper/adaptDecoder";
import { GroupChange, PushMsgBody } from "@/core/packet/transformer/proto";
import { NapProtoMsg } from '@napneko/nap-proto-core';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { OB11GroupDecreaseEvent, GroupDecreaseSubType } from '../event/notice/OB11GroupDecreaseEvent';
type RawToOb11Converters = { type RawToOb11Converters = {
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: ( [Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
@@ -953,16 +957,51 @@ export class OneBotMsgApi {
return { path, fileName: inputdata.name ?? fileName }; return { path, fileName: inputdata.name ?? fileName };
} }
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
switch (type) {
case 130:
return 'kick';
case 131:
return 'leave';
case 3:
return 'kick_me';
default:
return 'kick';
}
}
async parseSysMessage(msg: number[]) { async parseSysMessage(msg: number[]) {
const sysMsg = decodeSysMessage(Uint8Array.from(msg)); // Todo Refactor
if (sysMsg.msgSpec.length === 0) { // const sysMsg = decodeSysMessage(Uint8Array.from(msg));
return; // if (sysMsg.msgSpec.length === 0) {
} // return;
const { msgType, subType, subSubType } = sysMsg.msgSpec[0]; // }
if (msgType === 528 && subType === 39 && subSubType === 39) { // const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
if (!sysMsg.bodyWrapper) return; // if (msgType === 528 && subType === 39 && subSubType === 39) {
return await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody); // if (!sysMsg.bodyWrapper) return;
// return await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody);
// }
let SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) {
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
console.log(JSON.stringify(groupChange));
return new OB11GroupIncreaseEvent(
this.core,
groupChange.groupUin,
groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0,
groupChange.operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.operatorUid) : 0,
groupChange.decreaseType == 131 ? 'invite' : 'approve',
);
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
// console.log(JSON.stringify(groupChange),JSON.stringify(SysMessage));
return new OB11GroupDecreaseEvent(
this.core,
groupChange.groupUin,
+this.core.selfInfo.uin,
groupChange.operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.operatorUid) : 0,
this.groupChangDecreseType2String(groupChange.decreaseType),
);
} }
/* /*
if (msgType === 732 && subType === 16 && subSubType === 16) { if (msgType === 732 && subType === 16 && subSubType === 16) {

View File

@@ -42,7 +42,7 @@ import { MessageUnique } from '@/common/message-unique';
import { proxiedListenerOf } from '@/common/proxy-handler'; import { proxiedListenerOf } from '@/common/proxy-handler';
import { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest'; import { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest';
import { OB11GroupAdminNoticeEvent } from '@/onebot/event/notice/OB11GroupAdminNoticeEvent'; import { OB11GroupAdminNoticeEvent } from '@/onebot/event/notice/OB11GroupAdminNoticeEvent';
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent'; // import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
import { OB11GroupRequestEvent } from '@/onebot/event/request/OB11GroupRequest'; import { OB11GroupRequestEvent } from '@/onebot/event/request/OB11GroupRequest';
import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent'; import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent';
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent'; import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
@@ -195,6 +195,16 @@ export class NapCatOneBot11Adapter {
nowConfig: NetworkConfigAdapter[], nowConfig: NetworkConfigAdapter[],
adapterClass: new (...args: any[]) => IOB11NetworkAdapter adapterClass: new (...args: any[]) => IOB11NetworkAdapter
): Promise<void> { ): Promise<void> {
// 比较旧的在新的找不到的回收
for (const adapterConfig of prevConfig) {
const existingAdapter = nowConfig.find((e) => e.name === adapterConfig.name);
if (!existingAdapter) {
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
if (existingAdapter) {
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
}
}
}
// 通知新配置重载 删除关闭的 加入新开的 // 通知新配置重载 删除关闭的 加入新开的
for (const adapterConfig of nowConfig) { for (const adapterConfig of nowConfig) {
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name); const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
@@ -203,21 +213,11 @@ export class NapCatOneBot11Adapter {
if (networkChange === OB11NetworkReloadType.NetWorkClose) { if (networkChange === OB11NetworkReloadType.NetWorkClose) {
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]); await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
} }
} else { } else if(adapterConfig.enable) {
const newAdapter = new adapterClass(adapterConfig.name, adapterConfig, this.core, this.actions); const newAdapter = new adapterClass(adapterConfig.name, adapterConfig, this.core, this.actions);
await this.networkManager.registerAdapterAndOpen(newAdapter); await this.networkManager.registerAdapterAndOpen(newAdapter);
} }
} }
// 比较旧的找不到的回收
for (const adapterConfig of prevConfig) {
const existingAdapter = nowConfig.find((e) => e.name === adapterConfig.name);
if (!existingAdapter) {
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
if (existingAdapter) {
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
}
}
}
} }
private initMsgListener() { private initMsgListener() {
@@ -406,102 +406,104 @@ export class NapCatOneBot11Adapter {
this.core.apis.GroupApi.getGroup(notify.group.groupCode) this.core.apis.GroupApi.getGroup(notify.group.groupCode)
); );
} }
} else if ( } else
notify.type == GroupNotifyMsgType.MEMBER_LEAVE_NOTIFY_ADMIN || // if (
notify.type == GroupNotifyMsgType.KICK_MEMBER_NOTIFY_ADMIN // notify.type == GroupNotifyMsgType.MEMBER_LEAVE_NOTIFY_ADMIN ||
) { // notify.type == GroupNotifyMsgType.KICK_MEMBER_NOTIFY_ADMIN
this.context.logger.logDebug('有成员退出通知', notify); // ) {
const member1Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid); // this.context.logger.logDebug('有成员退出通知', notify);
let operatorId = member1Uin; // const member1Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
let subType: GroupDecreaseSubType = 'leave'; // let operatorId = member1Uin;
if (notify.user2.uid) { // let subType: GroupDecreaseSubType = 'leave';
// 是被踢的 // if (notify.user2.uid) {
const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid); // // 是被踢的
if (member2Uin) { // const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid);
operatorId = member2Uin; // if (member2Uin) {
// operatorId = member2Uin;
// }
// subType = 'kick';
// }
// const groupDecreaseEvent = new OB11GroupDecreaseEvent(
// this.core,
// parseInt(notify.group.groupCode),
// parseInt(member1Uin),
// parseInt(operatorId),
// subType
// );
// this.networkManager
// .emitEvent(groupDecreaseEvent)
// .catch((e) =>
// this.context.logger.logError.bind(this.context.logger)('处理群成员退出失败', e)
// );
// // notify.status == 1 表示未处理 2表示处理完成
// } else
if (
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
notify.status == GroupNotifyMsgStatus.KUNHANDLE
) {
this.context.logger.logDebug('有加群请求');
try {
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
if (isNaN(parseInt(requestUin))) {
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
}
const groupRequestEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),
parseInt(requestUin),
'add',
notify.postscript,
flag
);
this.networkManager
.emitEvent(groupRequestEvent)
.catch((e) =>
this.context.logger.logError.bind(this.context.logger)('处理加群请求失败', e)
);
} catch (e) {
this.context.logger.logError.bind(this.context.logger)(
'获取加群人QQ号失败 Uid:',
notify.user1.uid,
e
);
} }
subType = 'kick'; } else if (
} notify.type == GroupNotifyMsgType.INVITED_BY_MEMBER &&
const groupDecreaseEvent = new OB11GroupDecreaseEvent( notify.status == GroupNotifyMsgStatus.KUNHANDLE
this.core, ) {
parseInt(notify.group.groupCode), this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
parseInt(member1Uin), const groupInviteEvent = new OB11GroupRequestEvent(
parseInt(operatorId),
subType
);
this.networkManager
.emitEvent(groupDecreaseEvent)
.catch((e) =>
this.context.logger.logError.bind(this.context.logger)('处理群成员退出失败', e)
);
// notify.status == 1 表示未处理 2表示处理完成
} else if (
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
notify.status == GroupNotifyMsgStatus.KUNHANDLE
) {
this.context.logger.logDebug('有加群请求');
try {
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
if (isNaN(parseInt(requestUin))) {
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
}
const groupRequestEvent = new OB11GroupRequestEvent(
this.core, this.core,
parseInt(notify.group.groupCode), parseInt(notify.group.groupCode),
parseInt(requestUin), parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid)),
'invite',
notify.postscript,
flag
);
this.networkManager
.emitEvent(groupInviteEvent)
.catch((e) =>
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
);
} else if (
notify.type == GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS &&
notify.status == GroupNotifyMsgStatus.KUNHANDLE
) {
this.context.logger.logDebug(`收到群员邀请加群通知:${notify}`);
const groupInviteEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid)),
'add', 'add',
notify.postscript, notify.postscript,
flag flag
); );
this.networkManager this.networkManager
.emitEvent(groupRequestEvent) .emitEvent(groupInviteEvent)
.catch((e) => .catch((e) =>
this.context.logger.logError.bind(this.context.logger)('处理加群请求失败', e) this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
); );
} catch (e) {
this.context.logger.logError.bind(this.context.logger)(
'获取加群人QQ号失败 Uid:',
notify.user1.uid,
e
);
} }
} else if (
notify.type == GroupNotifyMsgType.INVITED_BY_MEMBER &&
notify.status == GroupNotifyMsgStatus.KUNHANDLE
) {
this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
const groupInviteEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid)),
'invite',
notify.postscript,
flag
);
this.networkManager
.emitEvent(groupInviteEvent)
.catch((e) =>
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
);
} else if (
notify.type == GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS &&
notify.status == GroupNotifyMsgStatus.KUNHANDLE
) {
this.context.logger.logDebug(`收到群员邀请加群通知:${notify}`);
const groupInviteEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid)),
'add',
notify.postscript,
flag
);
this.networkManager
.emitEvent(groupInviteEvent)
.catch((e) =>
this.context.logger.logError.bind(this.context.logger)('处理邀请本人加群失败', e)
);
}
} }
} }
}; };