mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-21 09:36:35 +00:00
Compare commits
7 Commits
f74ef273de
...
5e750d4ee9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5e750d4ee9 | ||
![]() |
50fb32f81c | ||
![]() |
6c46cdd947 | ||
![]() |
372452fbee | ||
![]() |
417ef5d335 | ||
![]() |
9c534f8afd | ||
![]() |
ecd426bb80 |
@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.1.3",
|
"version": "4.1.5",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted, reactive, Reactive } from 'vue';
|
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue';
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
import {
|
import {
|
||||||
httpServerDefaultConfigs,
|
httpServerDefaultConfigs,
|
||||||
@ -74,44 +74,46 @@ import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.v
|
|||||||
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
|
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
|
||||||
|
|
||||||
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
|
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
|
||||||
|
|
||||||
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
|
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
|
||||||
|
type ComponentUnion =
|
||||||
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
|
|
||||||
httpServers: httpServerDefaultConfigs,
|
|
||||||
httpClients: httpClientDefaultConfigs,
|
|
||||||
websocketServers: websocketServerDefaultConfigs,
|
|
||||||
websocketClients: websocketClientDefaultConfigs,
|
|
||||||
};
|
|
||||||
|
|
||||||
const componentMap: Record<
|
|
||||||
ConfigKey,
|
|
||||||
| 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ClientPanel {
|
const defaultConfigMap: Record<ConfigKey, ConfigUnion> = {
|
||||||
name: string;
|
httpServers: httpServerDefaultConfigs,
|
||||||
key: ConfigKey;
|
httpClients: httpClientDefaultConfigs,
|
||||||
data: Ref<ConfigUnion>;
|
websocketServers: websocketServerDefaultConfigs,
|
||||||
|
websocketClients: websocketClientDefaultConfigs,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ConfigMap {
|
||||||
|
httpServers: HttpServerConfig;
|
||||||
|
httpClients: HttpClientConfig;
|
||||||
|
websocketServers: WebsocketServerConfig;
|
||||||
|
websocketClients: WebsocketClientConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComponentKey = keyof typeof componentMap;
|
interface ClientPanel<K extends ConfigKey = ConfigKey> {
|
||||||
|
name: string;
|
||||||
|
key: K;
|
||||||
|
data: ConfigMap[K];
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: store these state in global store (aka pinia)
|
|
||||||
const activeTab = ref<number>(0);
|
const activeTab = ref<number>(0);
|
||||||
const isDialogVisible = ref(false);
|
const isDialogVisible = ref(false);
|
||||||
const newTab = ref<{ name: string; type: ComponentKey }>({ name: '', type: 'httpServers' });
|
const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' });
|
||||||
const clientPanelData: Reactive<Array<ClientPanel>> = reactive([]);
|
const clientPanelData: Ref<ClientPanel[]> = ref([]);
|
||||||
|
|
||||||
const getComponent = (type: ComponentKey) => {
|
const getComponent = (type: ConfigKey) => {
|
||||||
return componentMap[type];
|
return componentMap[type];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,33 +137,27 @@ const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
|
|||||||
return await loginManager.SetOB11Config(config);
|
return await loginManager.SetOB11Config(config);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addToPanel = <T extends ConfigUnion>(configs: T[], key: ConfigKey) => {
|
const addToPanel = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => {
|
||||||
configs.forEach((config) => clientPanelData.push({ name: config.name, data: config, key: key }));
|
configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addConfigDataToPanel = (data: NetworkConfig) => {
|
const addConfigDataToPanel = (data: NetworkConfig) => {
|
||||||
Object.entries(data).forEach(([key, configs]) => {
|
(Object.keys(data) as ConfigKey[]).forEach((key) => {
|
||||||
if (key in defaultConfigs) {
|
addToPanel(data[key], key);
|
||||||
addToPanel(configs as ConfigUnion[], key as ConfigKey);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsePanelData = (): NetworkConfig => {
|
const parsePanelData = (): NetworkConfig => {
|
||||||
return {
|
const result: NetworkConfig = {
|
||||||
websocketClients: clientPanelData
|
httpServers: [],
|
||||||
.filter((panel) => panel.key === 'websocketClients')
|
httpClients: [],
|
||||||
.map((panel) => panel.data as WebsocketClientConfig),
|
websocketServers: [],
|
||||||
websocketServers: clientPanelData
|
websocketClients: [],
|
||||||
.filter((panel) => panel.key === 'websocketServers')
|
|
||||||
.map((panel) => panel.data as WebsocketServerConfig),
|
|
||||||
httpClients: clientPanelData
|
|
||||||
.filter((panel) => panel.key === 'httpClients')
|
|
||||||
.map((panel) => panel.data as HttpClientConfig),
|
|
||||||
httpServers: clientPanelData
|
|
||||||
.filter((panel) => panel.key === 'httpServers')
|
|
||||||
.map((panel) => panel.data as HttpServerConfig),
|
|
||||||
};
|
};
|
||||||
|
clientPanelData.value.forEach((panel) => {
|
||||||
|
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadConfig = async () => {
|
const loadConfig = async () => {
|
||||||
@ -175,11 +171,13 @@ const loadConfig = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// It's better to "saveConfig" instead of using deep watch
|
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
const config = parsePanelData();
|
const config = parsePanelData();
|
||||||
const userConfig = await getOB11Config();
|
const userConfig = await getOB11Config();
|
||||||
if (!userConfig) return;
|
if (!userConfig) {
|
||||||
|
await MessagePlugin.error('无法获取配置!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
userConfig.network = config;
|
userConfig.network = config;
|
||||||
const success = await setOB11Config(userConfig);
|
const success = await setOB11Config(userConfig);
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -196,21 +194,21 @@ const showAddTabDialog = () => {
|
|||||||
|
|
||||||
const addTab = async () => {
|
const addTab = async () => {
|
||||||
const { name, type } = newTab.value;
|
const { name, type } = newTab.value;
|
||||||
if (clientPanelData.some((panel) => panel.name === name)) {
|
if (clientPanelData.value.some((panel) => panel.name === name)) {
|
||||||
await MessagePlugin.error('选项卡名称已存在');
|
await MessagePlugin.error('选项卡名称已存在');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const defaultConfig = structuredClone(defaultConfigs[type]);
|
const defaultConfig = structuredClone(defaultConfigMap[type]);
|
||||||
defaultConfig.name = name;
|
defaultConfig.name = name;
|
||||||
clientPanelData.push({ name, data: defaultConfig, key: type });
|
clientPanelData.value.push({ name, data: defaultConfig, key: type });
|
||||||
isDialogVisible.value = false;
|
isDialogVisible.value = false;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
activeTab.value = clientPanelData.length - 1;
|
activeTab.value = clientPanelData.value.length - 1;
|
||||||
await MessagePlugin.success('选项卡添加成功');
|
await MessagePlugin.success('选项卡添加成功');
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
|
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
|
||||||
clientPanelData.splice(payload.index, 1);
|
clientPanelData.value.splice(payload.index, 1);
|
||||||
activeTab.value = Math.max(0, activeTab.value - 1);
|
activeTab.value = Math.max(0, activeTab.value - 1);
|
||||||
await saveConfig();
|
await saveConfig();
|
||||||
};
|
};
|
||||||
|
@ -1,274 +0,0 @@
|
|||||||
<template>
|
|
||||||
<t-space class="full-space">
|
|
||||||
<t-tabs
|
|
||||||
v-model="activeTab"
|
|
||||||
:addable="true"
|
|
||||||
theme="card"
|
|
||||||
@add="showAddTabDialog"
|
|
||||||
@remove="removeTab"
|
|
||||||
class="full-tabs"
|
|
||||||
>
|
|
||||||
<t-tab-panel
|
|
||||||
v-for="(config, idx) in clientPanelData"
|
|
||||||
:key="idx"
|
|
||||||
:label="config.name"
|
|
||||||
:removable="true"
|
|
||||||
:value="idx"
|
|
||||||
class="full-tab-panel"
|
|
||||||
>
|
|
||||||
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
|
|
||||||
<div class="button-container">
|
|
||||||
<t-button @click="saveConfig" style="width: 100px; height: 40px">保存</t-button>
|
|
||||||
</div>
|
|
||||||
</t-tab-panel>
|
|
||||||
</t-tabs>
|
|
||||||
<t-dialog
|
|
||||||
v-model:visible="isDialogVisible"
|
|
||||||
header="添加新选项卡"
|
|
||||||
@close="isDialogVisible = false"
|
|
||||||
@confirm="addTab"
|
|
||||||
>
|
|
||||||
<t-form ref="form" :model="newTab">
|
|
||||||
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
|
|
||||||
<t-input v-model="newTab.name" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
|
|
||||||
<t-select v-model="newTab.type">
|
|
||||||
<t-option value="httpServers">HTTP 服务器</t-option>
|
|
||||||
<t-option value="httpClients">HTTP 客户端</t-option>
|
|
||||||
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
|
||||||
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
|
||||||
</t-select>
|
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</t-dialog>
|
|
||||||
</t-space>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted, reactive, Reactive } from 'vue';
|
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
|
||||||
import {
|
|
||||||
httpServerDefaultConfigs,
|
|
||||||
httpClientDefaultConfigs,
|
|
||||||
websocketServerDefaultConfigs,
|
|
||||||
websocketClientDefaultConfigs,
|
|
||||||
HttpClientConfig,
|
|
||||||
HttpServerConfig,
|
|
||||||
WebsocketClientConfig,
|
|
||||||
WebsocketServerConfig,
|
|
||||||
NetworkConfig,
|
|
||||||
OneBotConfig,
|
|
||||||
mergeOneBotConfigs,
|
|
||||||
} from '../../../../src/onebot/config/config';
|
|
||||||
import { QQLoginManager } from '@/backend/shell';
|
|
||||||
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
|
|
||||||
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
|
|
||||||
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
|
|
||||||
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
|
|
||||||
|
|
||||||
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
|
|
||||||
|
|
||||||
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
|
|
||||||
|
|
||||||
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
|
|
||||||
httpServers: httpServerDefaultConfigs,
|
|
||||||
httpClients: httpClientDefaultConfigs,
|
|
||||||
websocketServers: websocketServerDefaultConfigs,
|
|
||||||
websocketClients: websocketClientDefaultConfigs,
|
|
||||||
};
|
|
||||||
|
|
||||||
const componentMap: Record<
|
|
||||||
ConfigKey,
|
|
||||||
| typeof HttpServerComponent
|
|
||||||
| typeof HttpClientComponent
|
|
||||||
| typeof WebsocketServerComponent
|
|
||||||
| typeof WebsocketClientComponent
|
|
||||||
> = {
|
|
||||||
httpServers: HttpServerComponent,
|
|
||||||
httpClients: HttpClientComponent,
|
|
||||||
websocketServers: WebsocketServerComponent,
|
|
||||||
websocketClients: WebsocketClientComponent,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ClientPanel {
|
|
||||||
name: string;
|
|
||||||
key: ConfigKey;
|
|
||||||
data: Ref<ConfigUnion>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ComponentKey = keyof typeof componentMap;
|
|
||||||
|
|
||||||
// TODO: store these state in global store (aka pinia)
|
|
||||||
const activeTab = ref<number>(0);
|
|
||||||
const isDialogVisible = ref(false);
|
|
||||||
const newTab = ref<{ name: string; type: ComponentKey }>({ name: '', type: 'httpServers' });
|
|
||||||
const clientPanelData: Reactive<Array<ClientPanel>> = reactive([]);
|
|
||||||
|
|
||||||
const getComponent = (type: ComponentKey) => {
|
|
||||||
return componentMap[type];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
|
|
||||||
const storedCredential = localStorage.getItem('auth');
|
|
||||||
if (!storedCredential) {
|
|
||||||
console.error('No stored credential found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const loginManager = new QQLoginManager(storedCredential);
|
|
||||||
return await loginManager.GetOB11Config();
|
|
||||||
};
|
|
||||||
|
|
||||||
const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
|
|
||||||
const storedCredential = localStorage.getItem('auth');
|
|
||||||
if (!storedCredential) {
|
|
||||||
console.error('No stored credential found');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const loginManager = new QQLoginManager(storedCredential);
|
|
||||||
return await loginManager.SetOB11Config(config);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addToPanel = <T extends ConfigUnion>(configs: T[], key: ConfigKey) => {
|
|
||||||
configs.forEach((config) => clientPanelData.push({ name: config.name, data: config, key: key }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const addConfigDataToPanel = (data: NetworkConfig) => {
|
|
||||||
Object.entries(data).forEach(([key, configs]) => {
|
|
||||||
if (key in defaultConfigs) {
|
|
||||||
addToPanel(configs as ConfigUnion[], key as ConfigKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const parsePanelData = (): NetworkConfig => {
|
|
||||||
return {
|
|
||||||
websocketClients: clientPanelData
|
|
||||||
.filter((panel) => panel.key === 'websocketClients')
|
|
||||||
.map((panel) => panel.data as WebsocketClientConfig),
|
|
||||||
websocketServers: clientPanelData
|
|
||||||
.filter((panel) => panel.key === 'websocketServers')
|
|
||||||
.map((panel) => panel.data as WebsocketServerConfig),
|
|
||||||
httpClients: clientPanelData
|
|
||||||
.filter((panel) => panel.key === 'httpClients')
|
|
||||||
.map((panel) => panel.data as HttpClientConfig),
|
|
||||||
httpServers: clientPanelData
|
|
||||||
.filter((panel) => panel.key === 'httpServers')
|
|
||||||
.map((panel) => panel.data as HttpServerConfig),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadConfig = async () => {
|
|
||||||
try {
|
|
||||||
const userConfig = await getOB11Config();
|
|
||||||
if (!userConfig) return;
|
|
||||||
const mergedConfig = mergeOneBotConfigs(userConfig);
|
|
||||||
addConfigDataToPanel(mergedConfig.network);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading config:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// It's better to "saveConfig" instead of using deep watch
|
|
||||||
const saveConfig = async () => {
|
|
||||||
const config = parsePanelData();
|
|
||||||
const userConfig = await getOB11Config();
|
|
||||||
if (!userConfig) return;
|
|
||||||
userConfig.network = config;
|
|
||||||
const success = await setOB11Config(userConfig);
|
|
||||||
if (success) {
|
|
||||||
MessagePlugin.success('配置保存成功');
|
|
||||||
} else {
|
|
||||||
MessagePlugin.error('配置保存失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const showAddTabDialog = () => {
|
|
||||||
newTab.value = { name: '', type: 'httpServers' };
|
|
||||||
isDialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addTab = async () => {
|
|
||||||
const { name, type } = newTab.value;
|
|
||||||
if (clientPanelData.some((panel) => panel.name === name)) {
|
|
||||||
MessagePlugin.error('选项卡名称已存在');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const defaultConfig = structuredClone(defaultConfigs[type]);
|
|
||||||
defaultConfig.name = name;
|
|
||||||
clientPanelData.push({ name, data: defaultConfig, key: type });
|
|
||||||
isDialogVisible.value = false;
|
|
||||||
await nextTick();
|
|
||||||
activeTab.value = clientPanelData.length - 1;
|
|
||||||
MessagePlugin.success('选项卡添加成功');
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
|
|
||||||
clientPanelData.splice(payload.index, 1);
|
|
||||||
activeTab.value = Math.max(0, activeTab.value - 1);
|
|
||||||
await saveConfig();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadConfig();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.full-space {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-tabs {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-tab-panel {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style scoped>
|
|
||||||
.full-space {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-tabs {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-tab-panel {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.1.3",
|
"version": "4.1.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework",
|
"build:framework": "npm run build:webui && vite build --mode framework",
|
||||||
"build:shell": "npm run build:webui && vite build --mode shell",
|
"build:shell": "npm run build:webui && vite build --mode shell",
|
||||||
|
@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.1.3';
|
export const napCatVersion = '4.1.5';
|
||||||
|
@ -8,6 +8,9 @@ import {
|
|||||||
WebHonorType,
|
WebHonorType,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { NapCatCore } from '..';
|
import { NapCatCore } from '..';
|
||||||
|
import { createReadStream, readFileSync, statSync } from 'node:fs';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
import { basename } from 'node:path';
|
||||||
|
|
||||||
export class NTQQWebApi {
|
export class NTQQWebApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
@ -303,4 +306,110 @@ export class NTQQWebApi {
|
|||||||
}
|
}
|
||||||
return (hash & 0x7FFFFFFF).toString();
|
return (hash & 0x7FFFFFFF).toString();
|
||||||
}
|
}
|
||||||
|
async createQunAlbumSession(gc: string, sAlbumID: string, sAlbumName: string, path: string, skey: string, pskey: string, uin: string) {
|
||||||
|
const img = readFileSync(path);
|
||||||
|
const img_md5 = createHash('md5').update(img).digest('hex');
|
||||||
|
const img_size = img.length;
|
||||||
|
const img_name = basename(path);
|
||||||
|
const time = Math.floor(Date.now() / 1000);
|
||||||
|
const GTK = this.getBknFromSKey(pskey);
|
||||||
|
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
|
||||||
|
const body = {
|
||||||
|
control_req: [{
|
||||||
|
uin: uin,
|
||||||
|
token: {
|
||||||
|
type: 4,
|
||||||
|
data: pskey,
|
||||||
|
appid: 5
|
||||||
|
},
|
||||||
|
appid: "qun",
|
||||||
|
checksum: img_md5,
|
||||||
|
check_type: 0,
|
||||||
|
file_len: img_size,
|
||||||
|
env: {
|
||||||
|
refer: "qzone",
|
||||||
|
deviceInfo: "h5"
|
||||||
|
},
|
||||||
|
model: 0,
|
||||||
|
biz_req: {
|
||||||
|
sPicTitle: img_name,
|
||||||
|
sPicDesc: "",
|
||||||
|
sAlbumName: sAlbumName,
|
||||||
|
sAlbumID: sAlbumID,
|
||||||
|
iAlbumTypeID: 0,
|
||||||
|
iBitmap: 0,
|
||||||
|
iUploadType: 0,
|
||||||
|
iUpPicType: 0,
|
||||||
|
iBatchID: time,
|
||||||
|
sPicPath: "",
|
||||||
|
iPicWidth: 0,
|
||||||
|
iPicHight: 0,
|
||||||
|
iWaterType: 0,
|
||||||
|
iDistinctUse: 0,
|
||||||
|
iNeedFeeds: 1,
|
||||||
|
iUploadTime: time,
|
||||||
|
mapExt: {
|
||||||
|
appid: "qun",
|
||||||
|
userid: gc
|
||||||
|
}
|
||||||
|
},
|
||||||
|
session: "",
|
||||||
|
asy_upload: 0,
|
||||||
|
cmd: "FileUpload"
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileBatchControl/${img_md5}?g_tk=${GTK}`;
|
||||||
|
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
|
||||||
|
"Cookie": cookie,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
});
|
||||||
|
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadQunAlbumSlice(path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) {
|
||||||
|
const img_size = statSync(path).size;
|
||||||
|
const img_name = basename(path);
|
||||||
|
let seq = 0;
|
||||||
|
let offset = 0;
|
||||||
|
const GTK = this.getBknFromSKey(pskey);
|
||||||
|
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
|
||||||
|
|
||||||
|
const stream = createReadStream(path, { highWaterMark: slice_size });
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
const end = Math.min(offset + chunk.length, img_size);
|
||||||
|
const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
|
||||||
|
const formData = await RequestUtil.createFormData(boundary, path);
|
||||||
|
|
||||||
|
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileUpload?seq=${seq}&retry=0&offset=${offset}&end=${end}&total=${img_size}&type=form&g_tk=${GTK}`;
|
||||||
|
const body = {
|
||||||
|
uin: uin,
|
||||||
|
appid: "qun",
|
||||||
|
session: session,
|
||||||
|
offset: offset,
|
||||||
|
data: formData,
|
||||||
|
checksum: "",
|
||||||
|
check_type: 0,
|
||||||
|
retry: 0,
|
||||||
|
seq: seq,
|
||||||
|
end: end,
|
||||||
|
cmd: "FileUpload",
|
||||||
|
slice_size: slice_size,
|
||||||
|
"biz_req.iUploadType": 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
|
||||||
|
"Cookie": cookie,
|
||||||
|
"Content-Type": `multipart/form-data; boundary=${boundary}`
|
||||||
|
});
|
||||||
|
|
||||||
|
offset += chunk.length;
|
||||||
|
seq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async uploadQunAlbum(path: string, albumId: string, group: string, skey: string, pskey: string, uin: string) {
|
||||||
|
const session = (await this.createQunAlbumSession(group, albumId, group, path, skey, pskey, uin) as { data: { session: string } }).data.session;
|
||||||
|
return await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 1024 * 1024);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -543,26 +543,27 @@ export class NapCatOneBot11Adapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async emitMsg(message: RawMessage) {
|
private async emitMsg(message: RawMessage) {
|
||||||
const network = Object.values(this.configLoader.configData.network) as Array<AdapterConfigWrap>;
|
const network = Object.values(this.configLoader.configData.network).flat() as Array<AdapterConfigWrap>;
|
||||||
this.context.logger.logDebug('收到新消息 RawMessage', message);
|
this.context.logger.logDebug('收到新消息 RawMessage', message);
|
||||||
|
await this.handleMsg(message, network);
|
||||||
|
await this.handleGroupEvent(message);
|
||||||
|
await this.handlePrivateMsgEvent(message);
|
||||||
|
}
|
||||||
|
private async handleMsg(message: RawMessage, network: Array<AdapterConfigWrap>) {
|
||||||
try {
|
try {
|
||||||
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message);
|
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message);
|
||||||
if (!ob11Msg) return;
|
if (ob11Msg) {
|
||||||
|
|
||||||
const isSelfMsg = this.isSelfMessage(ob11Msg);
|
const isSelfMsg = this.isSelfMessage(ob11Msg);
|
||||||
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
|
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
|
||||||
|
|
||||||
const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message);
|
const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message);
|
||||||
this.handleDebugNetwork(network, msgMap, message);
|
this.handleDebugNetwork(network, msgMap, message);
|
||||||
this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg);
|
this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg);
|
||||||
|
|
||||||
this.networkManager.emitEventByNames(msgMap);
|
this.networkManager.emitEventByNames(msgMap);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError('constructMessage error: ', e);
|
this.context.logger.logError('constructMessage error: ', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleGroupEvent(message);
|
|
||||||
this.handlePrivateMsgEvent(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isSelfMessage(ob11Msg: {
|
private isSelfMessage(ob11Msg: {
|
||||||
@ -575,7 +576,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
|
|
||||||
private createMsgMap(network: Array<AdapterConfigWrap>, ob11Msg: any, isSelfMsg: boolean, message: RawMessage): Map<string, OB11Message> {
|
private createMsgMap(network: Array<AdapterConfigWrap>, ob11Msg: any, isSelfMsg: boolean, message: RawMessage): Map<string, OB11Message> {
|
||||||
const msgMap: Map<string, OB11Message> = new Map();
|
const msgMap: Map<string, OB11Message> = new Map();
|
||||||
network.flat().filter(e => e.enable).forEach(e => {
|
network.filter(e => e.enable).forEach(e => {
|
||||||
if (e.messagePostFormat == 'string') {
|
if (e.messagePostFormat == 'string') {
|
||||||
msgMap.set(e.name, structuredClone(ob11Msg.stringMsg));
|
msgMap.set(e.name, structuredClone(ob11Msg.stringMsg));
|
||||||
} else {
|
} else {
|
||||||
@ -590,7 +591,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleDebugNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, message: RawMessage) {
|
private handleDebugNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, message: RawMessage) {
|
||||||
const debugNetwork = network.flat().filter(e => e.enable && e.debug);
|
const debugNetwork = network.filter(e => e.enable && e.debug);
|
||||||
if (debugNetwork.length > 0) {
|
if (debugNetwork.length > 0) {
|
||||||
debugNetwork.forEach(adapter => {
|
debugNetwork.forEach(adapter => {
|
||||||
const msg = msgMap.get(adapter.name);
|
const msg = msgMap.get(adapter.name);
|
||||||
@ -605,7 +606,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
|
|
||||||
private handleNotReportSelfNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, isSelfMsg: boolean) {
|
private handleNotReportSelfNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, isSelfMsg: boolean) {
|
||||||
if (isSelfMsg) {
|
if (isSelfMsg) {
|
||||||
const notReportSelfNetwork = network.flat().filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e)));
|
const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e)));
|
||||||
notReportSelfNetwork.forEach(adapter => {
|
notReportSelfNetwork.forEach(adapter => {
|
||||||
msgMap.delete(adapter.name);
|
msgMap.delete(adapter.name);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user