mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
@@ -1,13 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<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" />
|
||||
<title>NapCat WebUI</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/main.ts"></script>
|
||||
</body>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"tdesign-icons-vue-next": "^0.3.3",
|
||||
"tdesign-vue-next": "^1.10.3",
|
||||
"vue": "^3.5.12",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
BIN
napcat.webui/public/logo_webui.png
Normal file
BIN
napcat.webui/public/logo_webui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
@@ -1,7 +1,112 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div id="app" theme-mode="dark">
|
||||
<router-view />
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
BIN
napcat.webui/src/assets/logo_webui.png
Normal file
BIN
napcat.webui/src/assets/logo_webui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
@@ -74,7 +74,7 @@ export class QQLoginManager {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public async checkQQLoginStatusWithQrcode(): Promise<{ qrcodeurl: string, isLogin: string } | undefined> {
|
||||
public async checkQQLoginStatusWithQrcode(): Promise<{ qrcodeurl: string; isLogin: string } | undefined> {
|
||||
try {
|
||||
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/CheckLoginStatus`, {
|
||||
method: 'POST',
|
||||
|
@@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
|
||||
<div class="content">
|
||||
<router-view />
|
||||
<t-layout class="dashboard-container">
|
||||
<div ref="menuRef">
|
||||
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
|
||||
</div>
|
||||
</div>
|
||||
<t-layout>
|
||||
<router-view />
|
||||
</t-layout>
|
||||
</t-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import SidebarMenu from './webui/Nav.vue';
|
||||
|
||||
import emitter from '@/ts/event-bus';
|
||||
interface MenuItem {
|
||||
value: string;
|
||||
icon: string;
|
||||
@@ -25,6 +27,14 @@ const menuItems = ref<MenuItem[]>([
|
||||
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
|
||||
{ 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>
|
||||
|
||||
<style scoped>
|
||||
@@ -32,6 +42,7 @@ const menuItems = ref<MenuItem[]>([
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
@@ -39,14 +50,6 @@ const menuItems = ref<MenuItem[]>([
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
/* padding: 20px; */
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content {
|
||||
padding: 10px;
|
||||
|
@@ -1,22 +1,43 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<h2 class="sotheby-font">QQ Login</h2>
|
||||
<div class="login-methods">
|
||||
<t-button id="quick-login" class="login-method" :class="{ active: loginMethod === 'quick' }"
|
||||
@click="loginMethod = 'quick'">Quick Login</t-button>
|
||||
<t-button id="qrcode-login" class="login-method" :class="{ active: loginMethod === 'qrcode' }"
|
||||
@click="loginMethod = 'qrcode'">QR Code</t-button>
|
||||
<t-card class="layout">
|
||||
<div class="login-container">
|
||||
<h2 class="sotheby-font">QQ Login</h2>
|
||||
<div class="login-methods">
|
||||
<t-tooltip content="快速登录">
|
||||
<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 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>
|
||||
<t-footer class="footer">Power By NapCat.WebUi</t-footer>
|
||||
</t-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -88,14 +109,16 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
height: 100vh;
|
||||
}
|
||||
.login-container {
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@@ -154,7 +177,5 @@ onMounted(() => {
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,20 +1,22 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<h2 class="sotheby-font">WebUi Login</h2>
|
||||
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
||||
<t-form-item name="password">
|
||||
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
|
||||
<template #prefix-icon>
|
||||
<lock-on-icon />
|
||||
</template>
|
||||
</t-input>
|
||||
</t-form-item>
|
||||
<t-form-item>
|
||||
<t-button theme="primary" type="submit" block>登录</t-button>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
<div class="footer">Power By NapCat.WebUi</div>
|
||||
<t-card class="layout">
|
||||
<div class="login-container">
|
||||
<h2 class="sotheby-font">WebUi Login</h2>
|
||||
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
||||
<t-form-item name="password">
|
||||
<t-input v-model="formData.token" type="password" clearable placeholder="请输入Token">
|
||||
<template #prefix-icon>
|
||||
<lock-on-icon />
|
||||
</template>
|
||||
</t-input>
|
||||
</t-form-item>
|
||||
<t-form-item>
|
||||
<t-button theme="primary" type="submit" block>登录</t-button>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
<t-footer class="footer">Power By NapCat.WebUi</t-footer>
|
||||
</t-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -94,14 +96,16 @@ const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
height: 100vh;
|
||||
}
|
||||
.login-container {
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
max-width: 400px;
|
||||
min-width: 300px;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@@ -145,7 +149,5 @@ const onSubmit = async ({ validateResult }: { validateResult: boolean }) => {
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,16 +1,31 @@
|
||||
<template>
|
||||
<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">
|
||||
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
|
||||
<template #icon>
|
||||
<t-icon :name="item.icon" />
|
||||
</template>
|
||||
{{ item.label }}
|
||||
</t-menu-item>
|
||||
<t-tooltip :disabled="!collapsed" :content="item.label" placement="right">
|
||||
<t-menu-item :value="item.value" :disabled="item.disabled" class="menu-item">
|
||||
<template #icon>
|
||||
<t-icon :name="item.icon" />
|
||||
</template>
|
||||
{{ item.label }}
|
||||
</t-menu-item>
|
||||
</t-tooltip>
|
||||
</router-link>
|
||||
<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>
|
||||
</t-button>
|
||||
</template>
|
||||
@@ -18,7 +33,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps } from 'vue';
|
||||
import { ref, defineProps, onMounted, watch } from 'vue';
|
||||
import emitter from '@/ts/event-bus';
|
||||
|
||||
type MenuItem = {
|
||||
value: string;
|
||||
@@ -31,15 +47,39 @@ type MenuItem = {
|
||||
defineProps<{
|
||||
menuItems: MenuItem[];
|
||||
}>();
|
||||
|
||||
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
|
||||
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
|
||||
const disBtn = ref<boolean>(false);
|
||||
|
||||
const changeCollapsed = (): void => {
|
||||
collapsed.value = !collapsed.value;
|
||||
iconName.value = collapsed.value ? 'menu-unfold' : 'menu-fold';
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
@@ -57,12 +97,28 @@ const changeCollapsed = (): void => {
|
||||
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 {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 22px;
|
||||
font-family: Sotheby, Helvetica, monospace;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
|
@@ -19,6 +19,10 @@ import {
|
||||
List as TList,
|
||||
Alert as TAlert,
|
||||
Tag as TTag,
|
||||
Descriptions as TDescriptionsProps,
|
||||
DescriptionsItem as TDescriptionsItem,
|
||||
Collapse as TCollapse,
|
||||
CollapsePanel as TCollapsePanel,
|
||||
ListItem as TListItem,
|
||||
Tabs as TTabs,
|
||||
TabPanel as TTabPanel,
|
||||
@@ -27,10 +31,18 @@ import {
|
||||
Popup as TPopup,
|
||||
Dialog as TDialog,
|
||||
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';
|
||||
import { router } from './router';
|
||||
import 'tdesign-vue-next/es/style/index.css';
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(TButton);
|
||||
@@ -51,6 +63,10 @@ app.use(TLink);
|
||||
app.use(TList);
|
||||
app.use(TAlert);
|
||||
app.use(TTag);
|
||||
app.use(TDescriptionsProps);
|
||||
app.use(TDescriptionsItem);
|
||||
app.use(TCollapse);
|
||||
app.use(TCollapsePanel);
|
||||
app.use(TListItem);
|
||||
app.use(TTabs);
|
||||
app.use(TTabPanel);
|
||||
@@ -59,4 +75,13 @@ app.use(TCheckbox);
|
||||
app.use(TPopup);
|
||||
app.use(TDialog);
|
||||
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');
|
||||
|
@@ -1,122 +1,300 @@
|
||||
<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 ref="headerBox" class="title">
|
||||
<t-divider content="网络配置" align="left" />
|
||||
<t-divider align="right">
|
||||
<t-button @click="addConfig()">
|
||||
<template #icon><add-icon /></template>
|
||||
添加配置</t-button>
|
||||
</t-divider>
|
||||
</div>
|
||||
<div v-if="loadPage" ref="setting" class="setting">
|
||||
<t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
|
||||
<t-tab-panel value="all" label="全部"></t-tab-panel>
|
||||
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
|
||||
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
|
||||
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
|
||||
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
|
||||
</t-tabs>
|
||||
</div>
|
||||
<div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
|
||||
<div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
|
||||
<div v-for="(item, index) in cardConfig" :key="index">
|
||||
<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>
|
||||
</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-card>
|
||||
</div>
|
||||
<div style="height: 20vh"></div>
|
||||
</div>
|
||||
<t-card v-else>
|
||||
<t-empty class="card-none" title="暂无网络配置"> </t-empty>
|
||||
</t-card>
|
||||
</div>
|
||||
<t-dialog v-model:visible="visibleBody" :header="dialogTitle" :destroy-on-close="true"
|
||||
:show-in-attached-element="true" placement="center" :on-confirm="saveConfig" class=".t-dialog__ctx .t-dialog--defaul">
|
||||
<div slot="body" class="dialog-body" >
|
||||
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
|
||||
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
|
||||
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-form-item style="text-align: left" :rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
|
||||
label="类型" name="type">
|
||||
<t-select v-model="newTab.type" @change="onloadDefault">
|
||||
<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>
|
||||
<div>
|
||||
<component :is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
|
||||
:config="newTab.data" />
|
||||
</div>
|
||||
</t-form>
|
||||
</t-dialog>
|
||||
</t-space>
|
||||
</div>
|
||||
</t-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue';
|
||||
import { MessagePlugin } from 'tdesign-vue-next';
|
||||
import { AddIcon, DeleteIcon, Edit2Icon, ServerFilledIcon, CopyIcon, BrowseOffIcon, BrowseIcon } from 'tdesign-icons-vue-next';
|
||||
import { onMounted, onUnmounted, ref, resolveDynamicComponent } from 'vue';
|
||||
import emitter from '@/ts/event-bus';
|
||||
import {
|
||||
httpServerDefaultConfigs,
|
||||
httpClientDefaultConfigs,
|
||||
websocketServerDefaultConfigs,
|
||||
websocketClientDefaultConfigs,
|
||||
HttpClientConfig,
|
||||
HttpServerConfig,
|
||||
WebsocketClientConfig,
|
||||
WebsocketServerConfig,
|
||||
mergeNetworkDefaultConfig,
|
||||
mergeOneBotConfigs,
|
||||
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';
|
||||
import { MessagePlugin } from 'tdesign-vue-next';
|
||||
import { QQLoginManager } from '@/backend/shell';
|
||||
|
||||
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
|
||||
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
|
||||
type ComponentUnion =
|
||||
const showToken = ref<boolean>(false);
|
||||
const infoOneCol = ref<boolean>(true);
|
||||
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 HttpClientComponent
|
||||
| typeof WebsocketServerComponent
|
||||
| typeof WebsocketClientComponent;
|
||||
|
||||
const componentMap: Record<ConfigKey, ComponentUnion> = {
|
||||
| typeof WebsocketClientComponent
|
||||
> = {
|
||||
httpServers: HttpServerComponent,
|
||||
httpClients: HttpClientComponent,
|
||||
websocketServers: WebsocketServerComponent,
|
||||
websocketClients: WebsocketClientComponent,
|
||||
};
|
||||
|
||||
const defaultConfigMap: Record<ConfigKey, ConfigUnion> = {
|
||||
httpServers: httpServerDefaultConfigs,
|
||||
httpClients: httpClientDefaultConfigs,
|
||||
websocketServers: websocketServerDefaultConfigs,
|
||||
websocketClients: websocketClientDefaultConfigs,
|
||||
//操作类型
|
||||
const operateType = ref<string>('');
|
||||
//配置项索引
|
||||
const configIndex = ref<number>(0);
|
||||
//保存时所用数据
|
||||
const networkConfig: NetworkConfig & { [key: string]: any; } = {
|
||||
websocketClients: [],
|
||||
websocketServers: [],
|
||||
httpClients: [],
|
||||
httpServers: [],
|
||||
};
|
||||
|
||||
interface ConfigMap {
|
||||
httpServers: HttpServerConfig;
|
||||
httpClients: HttpClientConfig;
|
||||
websocketServers: WebsocketServerConfig;
|
||||
websocketClients: WebsocketClientConfig;
|
||||
}
|
||||
|
||||
interface ClientPanel<K extends ConfigKey = ConfigKey> {
|
||||
name: string;
|
||||
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) => {
|
||||
//挂载的数据
|
||||
const WebConfg = ref(
|
||||
new Map<string, Array<null>>([
|
||||
['all', []],
|
||||
['httpServers', []],
|
||||
['httpClients', []],
|
||||
['websocketServers', []],
|
||||
['websocketClients', []],
|
||||
])
|
||||
);
|
||||
const typeCh: Record<ComponentKey, string> = {
|
||||
httpServers: 'HTTP 服务器',
|
||||
httpClients: 'HTTP 客户端',
|
||||
websocketServers: 'WebSocket 服务器',
|
||||
websocketClients: 'WebSocket 客户端',
|
||||
};
|
||||
const cardConfig = ref<any>([]);
|
||||
const getComponent = (type: ComponentKey) => {
|
||||
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 storedCredential = localStorage.getItem('auth');
|
||||
if (!storedCredential) {
|
||||
@@ -137,27 +315,27 @@ const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
|
||||
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 addConfigDataToPanel = (data: NetworkConfig) => {
|
||||
(Object.keys(data) as ConfigKey[]).forEach((key) => {
|
||||
addToPanel(data[key], key);
|
||||
});
|
||||
};
|
||||
|
||||
const parsePanelData = (): NetworkConfig => {
|
||||
const result: NetworkConfig = {
|
||||
httpServers: [],
|
||||
httpClients: [],
|
||||
websocketServers: [],
|
||||
websocketClients: [],
|
||||
};
|
||||
clientPanelData.value.forEach((panel) => {
|
||||
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
|
||||
});
|
||||
return result;
|
||||
//获取卡片数据
|
||||
const getAllData = (data: NetworkConfig) => {
|
||||
cardConfig.value = [];
|
||||
WebConfg.value.set('all', []);
|
||||
for (const key in data) {
|
||||
const configs = data[key as keyof NetworkConfig];
|
||||
if (key in mergeNetworkDefaultConfig) {
|
||||
networkConfig[key] = [...configs];
|
||||
const newConfigsArray = configs.map((config: any) => ({
|
||||
...config,
|
||||
type: typeCh[key as ComponentKey],
|
||||
}));
|
||||
WebConfg.value.set(key, newConfigsArray);
|
||||
const allConfigs = WebConfg.value.get('all');
|
||||
if (allConfigs) {
|
||||
const newAllConfigs = [...allConfigs, ...newConfigsArray];
|
||||
WebConfg.value.set('all', newAllConfigs);
|
||||
}
|
||||
cardConfig.value = WebConfg.value.get('all');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadConfig = async () => {
|
||||
@@ -165,85 +343,198 @@ const loadConfig = async () => {
|
||||
const userConfig = await getOB11Config();
|
||||
if (!userConfig) return;
|
||||
const mergedConfig = mergeOneBotConfigs(userConfig);
|
||||
addConfigDataToPanel(mergedConfig.network);
|
||||
getAllData(mergedConfig.network);
|
||||
} catch (error) {
|
||||
console.error('Error loading config:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const saveConfig = async () => {
|
||||
const config = parsePanelData();
|
||||
const userConfig = await getOB11Config();
|
||||
if (!userConfig) {
|
||||
await MessagePlugin.error('无法获取配置!');
|
||||
return;
|
||||
}
|
||||
userConfig.network = config;
|
||||
const success = await setOB11Config(userConfig);
|
||||
if (success) {
|
||||
await MessagePlugin.success('配置保存成功');
|
||||
const copyText = async (text: string) => {
|
||||
const input = document.createElement('input');
|
||||
input.value = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
await navigator.clipboard.writeText(text);
|
||||
document.body.removeChild(input);
|
||||
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 {
|
||||
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);
|
||||
};
|
||||
|
||||
const showAddTabDialog = () => {
|
||||
newTab.value = { name: '', type: 'httpServers' };
|
||||
isDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const addTab = async () => {
|
||||
const { name, type } = newTab.value;
|
||||
if (clientPanelData.value.some((panel) => panel.name === name)) {
|
||||
await MessagePlugin.error('选项卡名称已存在');
|
||||
return;
|
||||
emitter.on('sendWidth', (width) => {
|
||||
if (typeof width === 'number' && !isNaN(width)) {
|
||||
menuWidth.value = width;
|
||||
handleResize();
|
||||
}
|
||||
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(() => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
.full-space {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.title {
|
||||
padding: 20px 20px 0 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.full-tabs {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.setting {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
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;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
@@ -1,25 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="title">
|
||||
<t-divider content="其余配置" align="left" />
|
||||
</div>
|
||||
<div class="other-config-container">
|
||||
<div class="other-config">
|
||||
<t-form ref="form" :model="otherConfig" class="form">
|
||||
<t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item">
|
||||
<t-input v-model="otherConfig.musicSignUrl" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
|
||||
<t-switch v-model="otherConfig.enableLocalFile2Url" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用上报解析合并消息" name="parseMultMsg" class="form-item">
|
||||
<t-switch v-model="otherConfig.parseMultMsg" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<div class="button-container">
|
||||
<t-button @click="saveConfig">保存</t-button>
|
||||
<t-card class="card">
|
||||
<div class="other-config-container">
|
||||
<div class="other-config">
|
||||
<t-form ref="form" :model="otherConfig" :label-align="labelAlign" label-width="auto" colon>
|
||||
<t-form-item label="音乐签名地址" name="musicSignUrl" class="form-item">
|
||||
<t-input v-model="otherConfig.musicSignUrl" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
|
||||
<t-switch v-model="otherConfig.enableLocalFile2Url" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<div class="button-container">
|
||||
<t-button @click="saveConfig">保存</t-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -31,9 +30,9 @@ import { QQLoginManager } from '@/backend/shell';
|
||||
const otherConfig = ref<Partial<OneBotConfig>>({
|
||||
musicSignUrl: '',
|
||||
enableLocalFile2Url: false,
|
||||
parseMultMsg: true
|
||||
});
|
||||
|
||||
const labelAlign = ref<string>();
|
||||
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
|
||||
const storedCredential = localStorage.getItem('auth');
|
||||
if (!storedCredential) {
|
||||
@@ -60,7 +59,6 @@ const loadConfig = async () => {
|
||||
if (userConfig) {
|
||||
otherConfig.value.musicSignUrl = userConfig.musicSignUrl;
|
||||
otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url;
|
||||
otherConfig.value.parseMultMsg = userConfig.parseMultMsg;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading config:', error);
|
||||
@@ -73,7 +71,6 @@ const saveConfig = async () => {
|
||||
if (userConfig) {
|
||||
userConfig.musicSignUrl = otherConfig.value.musicSignUrl || '';
|
||||
userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false;
|
||||
userConfig.parseMultMsg = otherConfig.value.parseMultMsg ?? true;
|
||||
const success = await setOB11Config(userConfig);
|
||||
if (success) {
|
||||
MessagePlugin.success('配置保存成功');
|
||||
@@ -86,55 +83,60 @@ const saveConfig = async () => {
|
||||
MessagePlugin.error('配置保存失败');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
padding: 20px 20px 0 20px;
|
||||
}
|
||||
.card {
|
||||
margin: 0 20px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.other-config-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.other-config {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
max-width: 500px;
|
||||
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -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>
|
@@ -1,28 +1,25 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<h3>HTTP Client 配置</h3>
|
||||
<t-form>
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="URL">
|
||||
<t-input v-model="config.url" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="报告自身消息">
|
||||
<t-checkbox v-model="config.reportSelfMessage" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
<div>
|
||||
<t-form labelAlign="left">
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="URL">
|
||||
<t-input v-model="config.url" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="报告自身消息">
|
||||
<t-checkbox v-model="config.reportSelfMessage" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -49,20 +46,4 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
||||
<style scoped></style>
|
||||
|
@@ -1,34 +1,31 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<h3>HTTP Server 配置</h3>
|
||||
<t-form>
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="端口">
|
||||
<t-input v-model.number="config.port" type="number" />
|
||||
</t-form-item>
|
||||
<t-form-item label="主机">
|
||||
<t-input v-model="config.host" type="text" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用 CORS">
|
||||
<t-checkbox v-model="config.enableCors" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用 WS">
|
||||
<t-checkbox v-model="config.enableWebsocket" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" type="text" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
<div>
|
||||
<t-form labelAlign="left">
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="端口">
|
||||
<t-input v-model.number="config.port" type="number" />
|
||||
</t-form-item>
|
||||
<t-form-item label="主机">
|
||||
<t-input v-model="config.host" type="text" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用 CORS">
|
||||
<t-checkbox v-model="config.enableCors" />
|
||||
</t-form-item>
|
||||
<t-form-item label="启用 WS">
|
||||
<t-checkbox v-model="config.enableWebsocket" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" type="text" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -55,20 +52,4 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
||||
<style scoped></style>
|
||||
|
@@ -1,31 +1,28 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<h3>WebSocket Client 配置</h3>
|
||||
<t-form>
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="URL">
|
||||
<t-input v-model="config.url" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="报告自身消息">
|
||||
<t-checkbox v-model="config.reportSelfMessage" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
<t-form-item label="心跳间隔">
|
||||
<t-input v-model.number="config.heartInterval" type="number" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
<div>
|
||||
<t-form labelAlign="left">
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="URL">
|
||||
<t-input v-model="config.url" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="报告自身消息">
|
||||
<t-checkbox v-model="config.reportSelfMessage" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
<t-form-item label="心跳间隔">
|
||||
<t-input v-model.number="config.heartInterval" type="number" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -52,20 +49,4 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
||||
<style scoped></style>
|
||||
|
@@ -1,37 +1,34 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<h3>WebSocket Server 配置</h3>
|
||||
<t-form>
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="主机">
|
||||
<t-input v-model="config.host" />
|
||||
</t-form-item>
|
||||
<t-form-item label="端口">
|
||||
<t-input v-model.number="config.port" type="number" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="上报自身消息">
|
||||
<t-checkbox v-model="config.reportSelfMessage" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" />
|
||||
</t-form-item>
|
||||
<t-form-item label="强制推送事件">
|
||||
<t-checkbox v-model="config.enableForcePushEvent" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
<t-form-item label="心跳间隔">
|
||||
<t-input v-model.number="config.heartInterval" type="number" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
<div>
|
||||
<t-form labelAlign="left">
|
||||
<t-form-item label="启用">
|
||||
<t-checkbox v-model="config.enable" />
|
||||
</t-form-item>
|
||||
<t-form-item label="主机">
|
||||
<t-input v-model="config.host" />
|
||||
</t-form-item>
|
||||
<t-form-item label="端口">
|
||||
<t-input v-model.number="config.port" type="number" />
|
||||
</t-form-item>
|
||||
<t-form-item label="消息格式">
|
||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||
</t-form-item>
|
||||
<t-form-item label="上报自身消息">
|
||||
<t-checkbox v-model="config.reportSelfMessage" />
|
||||
</t-form-item>
|
||||
<t-form-item label="Token">
|
||||
<t-input v-model="config.token" />
|
||||
</t-form-item>
|
||||
<t-form-item label="强制推送事件">
|
||||
<t-checkbox v-model="config.enableForcePushEvent" />
|
||||
</t-form-item>
|
||||
<t-form-item label="调试模式">
|
||||
<t-checkbox v-model="config.debug" />
|
||||
</t-form-item>
|
||||
<t-form-item label="心跳间隔">
|
||||
<t-input v-model.number="config.heartInterval" type="number" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -58,20 +55,4 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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>
|
||||
<style scoped></style>
|
||||
|
3
napcat.webui/src/ts/event-bus.ts
Normal file
3
napcat.webui/src/ts/event-bus.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import mitt from 'mitt';
|
||||
const emitter = mitt();
|
||||
export default emitter;
|
@@ -182,7 +182,6 @@ export enum FileUriType {
|
||||
}
|
||||
|
||||
export async function checkUriType(Uri: string) {
|
||||
|
||||
const LocalFileRet = await solveProblem((uri: string) => {
|
||||
if (fs.existsSync(uri)) {
|
||||
return { Uri: uri, Type: FileUriType.Local };
|
||||
@@ -200,14 +199,7 @@ export async function checkUriType(Uri: string) {
|
||||
return { Uri: uri, Type: FileUriType.Base64 };
|
||||
}
|
||||
if (uri.startsWith('file://')) {
|
||||
let filePath: string;
|
||||
const pathname = decodeURIComponent(new URL(uri).pathname + new URL(uri).hash);
|
||||
if (process.platform === 'win32') {
|
||||
filePath = pathname.slice(1);
|
||||
} else {
|
||||
filePath = pathname;
|
||||
}
|
||||
|
||||
let filePath: string = uri.slice(7);
|
||||
return { Uri: filePath, Type: FileUriType.Local };
|
||||
}
|
||||
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> {
|
||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||
|
||||
//解析失败
|
||||
const tempName = randomUUID();
|
||||
if (!filename) filename = randomUUID();
|
||||
//解析Http和Https协议
|
||||
|
||||
//解析Http和Https协议
|
||||
if (UriType == FileUriType.Unknown) {
|
||||
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
||||
}
|
||||
|
||||
//解析File协议和本地文件
|
||||
if (UriType == FileUriType.Local) {
|
||||
const fileExt = path.extname(HandledUri);
|
||||
@@ -241,8 +235,8 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
||||
fs.copyFileSync(HandledUri, filePath);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
|
||||
//接下来都要有文件名
|
||||
|
||||
if (UriType == FileUriType.Remote) {
|
||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||
if (pathInfo.name) {
|
||||
@@ -260,6 +254,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
||||
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
}
|
||||
|
||||
//解析Base64
|
||||
if (UriType == FileUriType.Base64) {
|
||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||
|
@@ -54,6 +54,16 @@ export const PushMsg = {
|
||||
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 = {
|
||||
responseHead: ProtoField(1, () => ResponseHead),
|
||||
contentHead: ProtoField(2, () => ContentHead),
|
||||
|
@@ -23,7 +23,6 @@ import { OB11GroupCardEvent } from '@/onebot/event/notice/OB11GroupCardEvent';
|
||||
import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
|
||||
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
|
||||
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
|
||||
import { OB11EmitEventContent } from '../network';
|
||||
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||
@@ -93,40 +92,40 @@ export class OneBotGroupApi {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
const groupElement = grayTipElement?.groupElement;
|
||||
if (!groupElement) return undefined;
|
||||
const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
|
||||
const memberUin = member?.uin;
|
||||
const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
|
||||
if (memberUin) {
|
||||
const operatorUin = adminMember?.uin ?? memberUin;
|
||||
return new OB11GroupIncreaseEvent(
|
||||
this.core,
|
||||
parseInt(GroupCode),
|
||||
parseInt(memberUin),
|
||||
parseInt(operatorUin),
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
// async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// const groupElement = grayTipElement?.groupElement;
|
||||
// if (!groupElement) return undefined;
|
||||
// const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
|
||||
// const memberUin = member?.uin;
|
||||
// const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
|
||||
// if (memberUin) {
|
||||
// const operatorUin = adminMember?.uin ?? memberUin;
|
||||
// return new OB11GroupIncreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(GroupCode),
|
||||
// parseInt(memberUin),
|
||||
// parseInt(operatorUin),
|
||||
// );
|
||||
// } else {
|
||||
// return undefined;
|
||||
// }
|
||||
// }
|
||||
|
||||
async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
const groupElement = grayTipElement?.groupElement;
|
||||
if (!groupElement) return undefined;
|
||||
const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
|
||||
if (adminUin) {
|
||||
return new OB11GroupDecreaseEvent(
|
||||
this.core,
|
||||
parseInt(GroupCode),
|
||||
parseInt(this.core.selfInfo.uin),
|
||||
parseInt(adminUin),
|
||||
'kick_me',
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// const groupElement = grayTipElement?.groupElement;
|
||||
// if (!groupElement) return undefined;
|
||||
// const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
|
||||
// if (adminUin) {
|
||||
// return new OB11GroupDecreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(GroupCode),
|
||||
// parseInt(this.core.selfInfo.uin),
|
||||
// parseInt(adminUin),
|
||||
// 'kick_me',
|
||||
// );
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
async parseGroupEmojiLikeEventByGrayTip(
|
||||
groupCode: string,
|
||||
@@ -188,30 +187,30 @@ export class OneBotGroupApi {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async parseGroupElement(msg: RawMessage, groupElement: TipGroupElement, elementWrapper: GrayTipElement) {
|
||||
if (groupElement.type == TipGroupElementType.KMEMBERADD) {
|
||||
const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, elementWrapper);
|
||||
if (MemberIncreaseEvent) return MemberIncreaseEvent;
|
||||
} else if (groupElement.type === TipGroupElementType.KSHUTUP) {
|
||||
const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, elementWrapper);
|
||||
if (BanEvent) return BanEvent;
|
||||
} else if (groupElement.type == TipGroupElementType.KQUITTE) {
|
||||
this.core.apis.GroupApi.quitGroup(msg.peerUid).then();
|
||||
try {
|
||||
const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, elementWrapper);
|
||||
if (KickEvent) return KickEvent;
|
||||
} catch (e) {
|
||||
return new OB11GroupDecreaseEvent(
|
||||
this.core,
|
||||
parseInt(msg.peerUid),
|
||||
parseInt(this.core.selfInfo.uin),
|
||||
0,
|
||||
'leave',
|
||||
);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// async parseGroupElement(msg: RawMessage, groupElement: TipGroupElement, elementWrapper: GrayTipElement) {
|
||||
// if (groupElement.type == TipGroupElementType.KMEMBERADD) {
|
||||
// const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, elementWrapper);
|
||||
// if (MemberIncreaseEvent) return MemberIncreaseEvent;
|
||||
// } else if (groupElement.type === TipGroupElementType.KSHUTUP) {
|
||||
// const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, elementWrapper);
|
||||
// if (BanEvent) return BanEvent;
|
||||
// } else if (groupElement.type == TipGroupElementType.KQUITTE) {
|
||||
// this.core.apis.GroupApi.quitGroup(msg.peerUid).then();
|
||||
// try {
|
||||
// const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, elementWrapper);
|
||||
// if (KickEvent) return KickEvent;
|
||||
// } catch (e) {
|
||||
// return new OB11GroupDecreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(msg.peerUid),
|
||||
// parseInt(this.core.selfInfo.uin),
|
||||
// 0,
|
||||
// 'leave',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
async parsePaiYiPai(msg: RawMessage, jsonStr: string) {
|
||||
const json = JSON.parse(jsonStr);
|
||||
@@ -298,8 +297,8 @@ export class OneBotGroupApi {
|
||||
|
||||
async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) {
|
||||
if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) {
|
||||
// 解析群组事件
|
||||
return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement);
|
||||
// 解析群组事件 由sysmsg解析
|
||||
// return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement);
|
||||
|
||||
} else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
|
||||
// 筛选出表情回应 事件
|
||||
|
@@ -32,6 +32,10 @@ import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNot
|
||||
// import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
|
||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||
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 = {
|
||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||
@@ -953,16 +957,51 @@ export class OneBotMsgApi {
|
||||
|
||||
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[]) {
|
||||
const sysMsg = decodeSysMessage(Uint8Array.from(msg));
|
||||
if (sysMsg.msgSpec.length === 0) {
|
||||
return;
|
||||
}
|
||||
const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
|
||||
if (msgType === 528 && subType === 39 && subSubType === 39) {
|
||||
if (!sysMsg.bodyWrapper) return;
|
||||
return await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody);
|
||||
// Todo Refactor
|
||||
// const sysMsg = decodeSysMessage(Uint8Array.from(msg));
|
||||
// if (sysMsg.msgSpec.length === 0) {
|
||||
// return;
|
||||
// }
|
||||
// const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
|
||||
// if (msgType === 528 && subType === 39 && subSubType === 39) {
|
||||
// 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) {
|
||||
|
@@ -42,7 +42,7 @@ import { MessageUnique } from '@/common/message-unique';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
import { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest';
|
||||
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 { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent';
|
||||
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
||||
@@ -195,6 +195,16 @@ export class NapCatOneBot11Adapter {
|
||||
nowConfig: NetworkConfigAdapter[],
|
||||
adapterClass: new (...args: any[]) => IOB11NetworkAdapter
|
||||
): 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) {
|
||||
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
|
||||
@@ -203,21 +213,11 @@ export class NapCatOneBot11Adapter {
|
||||
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
|
||||
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
|
||||
}
|
||||
} else {
|
||||
} else if(adapterConfig.enable) {
|
||||
const newAdapter = new adapterClass(adapterConfig.name, adapterConfig, this.core, this.actions);
|
||||
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() {
|
||||
@@ -406,102 +406,104 @@ export class NapCatOneBot11Adapter {
|
||||
this.core.apis.GroupApi.getGroup(notify.group.groupCode)
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
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);
|
||||
let operatorId = member1Uin;
|
||||
let subType: GroupDecreaseSubType = 'leave';
|
||||
if (notify.user2.uid) {
|
||||
// 是被踢的
|
||||
const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid);
|
||||
if (member2Uin) {
|
||||
operatorId = member2Uin;
|
||||
} else
|
||||
// if (
|
||||
// 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);
|
||||
// let operatorId = member1Uin;
|
||||
// let subType: GroupDecreaseSubType = 'leave';
|
||||
// if (notify.user2.uid) {
|
||||
// // 是被踢的
|
||||
// const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid);
|
||||
// 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';
|
||||
}
|
||||
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(
|
||||
} 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(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',
|
||||
notify.postscript,
|
||||
flag
|
||||
);
|
||||
this.networkManager
|
||||
.emitEvent(groupRequestEvent)
|
||||
.emitEvent(groupInviteEvent)
|
||||
.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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user