mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
05043f8dfc | ||
![]() |
dc2b45fa00 | ||
![]() |
cc70fc766a | ||
![]() |
d2e9db5571 | ||
![]() |
8e01638a36 | ||
![]() |
e8b8eae8a9 | ||
![]() |
0f0275243b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,12 +1,14 @@
|
|||||||
# Develop
|
# Develop
|
||||||
node_modules/
|
node_modules/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
/src/core.lib/common/
|
/src/core.lib/common/
|
||||||
/localdebug/
|
/localdebug/
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.idea/*
|
.idea/*
|
||||||
|
|
||||||
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"explorer.fileNesting.enabled": true,
|
|
||||||
"explorer.fileNesting.expand": false,
|
|
||||||
"explorer.fileNesting.patterns": {
|
|
||||||
".env.universal": ".env.*",
|
|
||||||
"tsconfig.json": "tsconfig.*.json, env.d.ts, vite.config.ts",
|
|
||||||
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
|
|
||||||
}
|
|
||||||
}
|
|
20
README.md
20
README.md
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,25 +30,11 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
|
|
||||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
[Cloudflare.Pages](https://napneko.pages.dev/)
|
||||||
|
|
||||||
[Server.Other](https://docs.napcat.cyou/)
|
[Server.Other](https://napcat.cyou/)
|
||||||
|
|
||||||
[NapCat.Wiki](https://www.napcat.wiki)
|
|
||||||
|
|
||||||
## 回家旅途
|
## 回家旅途
|
||||||
[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
|
[QQ Group](https://qm.qq.com/q/I6LU87a0Yq)
|
||||||
|
|
||||||
[QQ Group#2](https://qm.qq.com/q/HaRcfrHpUk)
|
|
||||||
|
|
||||||
[Telegram](https://t.me/MelodicMoonlight)
|
|
||||||
|
|
||||||
> QQ Group#2 准许Bot / Telegram与QQ Group#2 为新建Group
|
|
||||||
|
|
||||||
## 性能设计/协议标准
|
|
||||||
NapCat 已实现90%+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
|
|
||||||
|
|
||||||
由此设计带来一系列好处,在开发中,获取群员列表通常小于50Ms,单条文本消息发送在320Ms以内,在1k+的群聊流畅运行,同时带来一些副作用,消息Id无法持久,无法上报撤回消息原始内容。
|
|
||||||
|
|
||||||
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
|
|
||||||
|
|
||||||
## 感谢他们
|
## 感谢他们
|
||||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
BIN
external/logo.png
vendored
BIN
external/logo.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 204 KiB |
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "qq-chat",
|
"name": "qq-chat",
|
||||||
"version": "9.9.17-30899",
|
"version": "9.9.16-29927",
|
||||||
"verHash": "ececf273",
|
"verHash": "3e273e30",
|
||||||
"linuxVersion": "3.2.15-30899",
|
"linuxVersion": "3.2.13-29927",
|
||||||
"linuxVerHash": "63c751e8",
|
"linuxVerHash": "833d113c",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "QQ",
|
"description": "QQ",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"qd": "externals/devtools/cli/index.js"
|
"qd": "externals/devtools/cli/index.js"
|
||||||
},
|
},
|
||||||
"main": "./loadNapCat.js",
|
"main": "./loadNapCat.js",
|
||||||
"buildVersion": "30899",
|
"buildVersion": "29927",
|
||||||
"isPureShell": true,
|
"isPureShell": true,
|
||||||
"isByteCodeShell": true,
|
"isByteCodeShell": true,
|
||||||
"platform": "win32",
|
"platform": "win32",
|
||||||
|
BIN
logo.png
BIN
logo.png
Binary file not shown.
Before Width: | Height: | Size: 684 KiB After Width: | Height: | Size: 335 KiB |
@@ -4,12 +4,16 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.3.8",
|
"version": "4.2.12",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "NapNeko",
|
"name": "MliKiowa",
|
||||||
"link": "https://github.com/NapNeko"
|
"link": "https://github.com/MliKiowa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Young",
|
||||||
|
"link": "https://github.com/Wesley-Young"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@@ -5,14 +5,12 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"webui:lint": "eslint --fix src/**/*.{js,ts,vue}",
|
"webui:lint": "eslint --fix src/**/*.{js,ts,vue}",
|
||||||
"webui:dev": "vite --host",
|
"webui:dev": "vite",
|
||||||
"webui:build": "vite build",
|
"webui:build": "vite build",
|
||||||
"webui:preview": "vite preview"
|
"webui:preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"event-source-polyfill": "^1.0.31",
|
|
||||||
"mitt": "^3.0.1",
|
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"tdesign-icons-vue-next": "^0.3.3",
|
"tdesign-icons-vue-next": "^0.3.3",
|
||||||
"tdesign-vue-next": "^1.10.3",
|
"tdesign-vue-next": "^1.10.3",
|
||||||
@@ -22,7 +20,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@types/event-source-polyfill": "^1.0.5",
|
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@vitejs/plugin-legacy": "^5.4.3",
|
"@vitejs/plugin-legacy": "^5.4.3",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
|
@@ -109,4 +109,4 @@ onUnmounted(() => {
|
|||||||
window.removeEventListener('resize', haddingFbars);
|
window.removeEventListener('resize', haddingFbars);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style></style>
|
<style scoped></style>
|
||||||
|
Binary file not shown.
@@ -1,66 +0,0 @@
|
|||||||
export class githubApiManager {
|
|
||||||
public async GetBaseData(): Promise<Response | null> {
|
|
||||||
try {
|
|
||||||
const ConfigResponse = await fetch('https://api.github.com/repos/NapNeko/NapCatQQ', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (ConfigResponse.status == 200) {
|
|
||||||
return await ConfigResponse.json();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting github data :', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public async GetReleasesData(): Promise<Response | null> {
|
|
||||||
try {
|
|
||||||
const ConfigResponse = await fetch('https://api.github.com/repos/NapNeko/NapCatQQ/releases', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (ConfigResponse.status == 200) {
|
|
||||||
return await ConfigResponse.json();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting releases data:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public async GetPullsData(): Promise<Response | null> {
|
|
||||||
try {
|
|
||||||
const ConfigResponse = await fetch('https://api.github.com/repos/NapNeko/NapCatQQ/pulls', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (ConfigResponse.status == 200) {
|
|
||||||
return await ConfigResponse.json();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting Pulls data:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public async GetContributors(): Promise<Response | null> {
|
|
||||||
try {
|
|
||||||
const ConfigResponse = await fetch('https://api.github.com/repos/NapNeko/NapCatQQ/contributors', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (ConfigResponse.status == 200) {
|
|
||||||
return await ConfigResponse.json();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting Pulls data:', error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
||||||
type LogListItem = string;
|
|
||||||
type LogListData = LogListItem[];
|
|
||||||
let eventSourcePoly: EventSourcePolyfill | null = null;
|
|
||||||
export class LogManager {
|
|
||||||
private readonly retCredential: string;
|
|
||||||
private readonly apiPrefix: string;
|
|
||||||
|
|
||||||
//调试时http://127.0.0.1:6099/api 打包时 ../api
|
|
||||||
constructor(retCredential: string, apiPrefix: string = '../api') {
|
|
||||||
this.retCredential = retCredential;
|
|
||||||
this.apiPrefix = apiPrefix;
|
|
||||||
}
|
|
||||||
public async GetLogList(): Promise<LogListData> {
|
|
||||||
try {
|
|
||||||
const ConfigResponse = await fetch(`${this.apiPrefix}/Log/GetLogList`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + this.retCredential,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (ConfigResponse.status == 200) {
|
|
||||||
const ConfigResponseJson = await ConfigResponse.json();
|
|
||||||
if (ConfigResponseJson.code == 0) {
|
|
||||||
return ConfigResponseJson?.data as LogListData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting LogList:', error);
|
|
||||||
}
|
|
||||||
return [] as LogListData;
|
|
||||||
}
|
|
||||||
public async GetLog(FileName: string): Promise<string> {
|
|
||||||
try {
|
|
||||||
const ConfigResponse = await fetch(`${this.apiPrefix}/Log/GetLog?id=${FileName}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + this.retCredential,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (ConfigResponse.status == 200) {
|
|
||||||
const ConfigResponseJson = await ConfigResponse.json();
|
|
||||||
if (ConfigResponseJson.code == 0) {
|
|
||||||
return ConfigResponseJson?.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting LogData:', error);
|
|
||||||
}
|
|
||||||
return 'null';
|
|
||||||
}
|
|
||||||
public async getRealTimeLogs(): Promise<EventSourcePolyfill | null> {
|
|
||||||
this.creatEventSource();
|
|
||||||
return eventSourcePoly;
|
|
||||||
}
|
|
||||||
private creatEventSource() {
|
|
||||||
try {
|
|
||||||
eventSourcePoly = new EventSourcePolyfill(`${this.apiPrefix}/Log/GetLogRealTime`, {
|
|
||||||
heartbeatTimeout: 3 * 60 * 1000,
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + this.retCredential,
|
|
||||||
Accept: 'text/event-stream',
|
|
||||||
},
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建SSE连接出错:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +1,5 @@
|
|||||||
import { OneBotConfig } from '../../../src/onebot/config/config';
|
import { OneBotConfig } from '../../../src/onebot/config/config';
|
||||||
import { ResponseCode } from '../../../src/webui/src/const/status';
|
|
||||||
export class QQLoginManager {
|
export class QQLoginManager {
|
||||||
private retCredential: string;
|
private retCredential: string;
|
||||||
private readonly apiPrefix: string;
|
private readonly apiPrefix: string;
|
||||||
@@ -22,8 +22,8 @@ export class QQLoginManager {
|
|||||||
});
|
});
|
||||||
if (ConfigResponse.status == 200) {
|
if (ConfigResponse.status == 200) {
|
||||||
const ConfigResponseJson = await ConfigResponse.json();
|
const ConfigResponseJson = await ConfigResponse.json();
|
||||||
if (ConfigResponseJson.code == ResponseCode.Success) {
|
if (ConfigResponseJson.code == 0) {
|
||||||
return ConfigResponseJson.data;
|
return ConfigResponseJson?.data as OneBotConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -1,28 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-layout class="dashboard-container">
|
<t-layout class="dashboard-container">
|
||||||
<div v-if="!mediaQuery.matches">
|
<div ref="menuRef">
|
||||||
<SidebarMenu
|
<SidebarMenu :menu-items="menuItems" class="sidebar-menu" />
|
||||||
:menu-items="menuItems"
|
|
||||||
class="sidebar-menu"
|
|
||||||
:menu-width="sidebarWidth"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<t-layout>
|
<t-layout>
|
||||||
<router-view />
|
<router-view />
|
||||||
</t-layout>
|
</t-layout>
|
||||||
<div v-if="mediaQuery.matches" class="bottom-menu">
|
|
||||||
<BottomMenu :menu-items="menuItems" />
|
|
||||||
</div>
|
|
||||||
</t-layout>
|
</t-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import SidebarMenu from './webui/Nav.vue';
|
import SidebarMenu from './webui/Nav.vue';
|
||||||
import BottomMenu from './webui/NavBottom.vue';
|
|
||||||
import emitter from '@/ts/event-bus';
|
import emitter from '@/ts/event-bus';
|
||||||
const mediaQuery = window.matchMedia('(max-width: 768px)');
|
|
||||||
const sidebarWidth = ['232px', '64px'];
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
value: string;
|
value: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -37,18 +27,13 @@ const menuItems = ref<MenuItem[]>([
|
|||||||
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
|
{ value: 'item5', icon: 'system-log', label: '日志查看', route: '/dashboard/log-view' },
|
||||||
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
|
{ value: 'item6', icon: 'info-circle', label: '关于我们', route: '/dashboard/about-us' },
|
||||||
]);
|
]);
|
||||||
|
const menuRef = ref<HTMLDivElement | null>(null);
|
||||||
emitter.on('sendMenu', (event) => {
|
emitter.on('sendMenu', (event) => {
|
||||||
const menuWidth = event ? sidebarWidth[1] : sidebarWidth[0];
|
emitter.emit('sendWidth', menuRef.value?.offsetWidth);
|
||||||
emitter.emit('sendWidth', menuWidth);
|
localStorage.setItem('menuWidth', menuRef.value?.offsetWidth?.toString() || '0');
|
||||||
localStorage.setItem('menuWidth', menuWidth.toString() || '0');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (mediaQuery.matches){
|
localStorage.setItem('menuWidth', menuRef.value?.offsetWidth?.toString() || '0');
|
||||||
localStorage.setItem('menuWidth', '0');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -64,12 +49,6 @@ onUnmounted(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.bottom-menu {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.content {
|
.content {
|
||||||
@@ -77,19 +56,3 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.t-head-menu__inner .t-menu:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
.t-head-menu__inner{
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.t-head-menu .t-menu{
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
.t-menu__content{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,20 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-card class="layout" :bordered="false">
|
<t-card class="layout">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<h2 class="sotheby-font">QQ Login</h2>
|
<h2 class="sotheby-font">QQ Login</h2>
|
||||||
<div class="login-methods">
|
<div class="login-methods">
|
||||||
<t-tooltip content="快速登录">
|
<t-tooltip content="快速登录">
|
||||||
<t-button id="quick-login" class="login-method" :class="{ active: loginMethod === 'quick' }"
|
<t-button
|
||||||
@click="loginMethod = 'quick'">Quick Login</t-button>
|
id="quick-login"
|
||||||
|
class="login-method"
|
||||||
|
:class="{ active: loginMethod === 'quick' }"
|
||||||
|
@click="loginMethod = 'quick'"
|
||||||
|
>Quick Login</t-button
|
||||||
|
>
|
||||||
</t-tooltip>
|
</t-tooltip>
|
||||||
<t-tooltip content="二维码登录">
|
<t-tooltip content="二维码登录">
|
||||||
<t-button id="qrcode-login" class="login-method" :class="{ active: loginMethod === 'qrcode' }"
|
<t-button
|
||||||
@click="loginMethod = 'qrcode'">QR Code</t-button>
|
id="qrcode-login"
|
||||||
|
class="login-method"
|
||||||
|
:class="{ active: loginMethod === 'qrcode' }"
|
||||||
|
@click="loginMethod = 'qrcode'"
|
||||||
|
>QR Code</t-button
|
||||||
|
>
|
||||||
</t-tooltip>
|
</t-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
|
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
|
||||||
<t-select id="quick-login-select" v-model="selectedAccount" placeholder="Select Account"
|
<t-select
|
||||||
@change="selectAccount">
|
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-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
|
||||||
</t-select>
|
</t-select>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +41,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import * as QRCode from 'qrcode';
|
import * as QRCode from 'qrcode';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
@@ -41,7 +55,6 @@ const qrcodeCanvas = ref<HTMLCanvasElement | null>(null);
|
|||||||
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
|
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
|
||||||
let heartBeatTimer: number | null = null;
|
let heartBeatTimer: number | null = null;
|
||||||
let qrcodeUrl: string = '';
|
let qrcodeUrl: string = '';
|
||||||
|
|
||||||
const selectAccount = async (accountName: string): Promise<void> => {
|
const selectAccount = async (accountName: string): Promise<void> => {
|
||||||
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
|
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -75,6 +88,10 @@ const HeartBeat = async (): Promise<void> => {
|
|||||||
if (heartBeatTimer) {
|
if (heartBeatTimer) {
|
||||||
clearInterval(heartBeatTimer);
|
clearInterval(heartBeatTimer);
|
||||||
}
|
}
|
||||||
|
// //判断是否已经调转
|
||||||
|
// if (router.currentRoute.value.path !== '/dashboard/basic-info') {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
await MessagePlugin.success('登录成功即将跳转');
|
await MessagePlugin.success('登录成功即将跳转');
|
||||||
await router.push({ path: '/dashboard/basic-info' });
|
await router.push({ path: '/dashboard/basic-info' });
|
||||||
} else if (isLogined?.qrcodeurl && qrcodeUrl !== isLogined.qrcodeurl) {
|
} else if (isLogined?.qrcodeurl && qrcodeUrl !== isLogined.qrcodeurl) {
|
||||||
@@ -86,38 +103,19 @@ const HeartBeat = async (): Promise<void> => {
|
|||||||
const InitPages = async (): Promise<void> => {
|
const InitPages = async (): Promise<void> => {
|
||||||
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
|
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
|
||||||
qrcodeUrl = await qqLoginManager.getQQLoginQrcode();
|
qrcodeUrl = await qqLoginManager.getQQLoginQrcode();
|
||||||
await nextTick();
|
|
||||||
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
|
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
|
||||||
|
heartBeatTimer = window.setInterval(HeartBeat, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
InitPages().then().catch((err) => {
|
InitPages();
|
||||||
console.error('InitPages Error:', err);
|
|
||||||
});
|
});
|
||||||
heartBeatTimer = window.setInterval(HeartBeat, 3000);
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (heartBeatTimer) {
|
|
||||||
clearInterval(heartBeatTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(loginMethod, async (newMethod) => {
|
|
||||||
if (newMethod === 'qrcode') {
|
|
||||||
await nextTick();
|
|
||||||
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.layout {
|
.layout {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-container {
|
.login-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-card class="layout" :bordered="false">
|
<t-card class="layout">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<h2 class="sotheby-font">WebUi Login</h2>
|
<h2 class="sotheby-font">WebUi Login</h2>
|
||||||
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
<t-form ref="form" :data="formData" colon :label-width="0" @submit="onSubmit">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<t-menu theme="light" :width="menuWidth" :collapsed="collapsed" class="sidebar-menu">
|
<t-menu theme="light" default-value="2-1" :collapsed="collapsed" class="sidebar-menu">
|
||||||
<template #logo>
|
<template #logo>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img class="logo-img" :width="collapsed ? 35 : 'auto'" src="@/assets/logo_webui.png" alt="logo" />
|
<img class="logo-img" :width="collapsed ? 35 : 'auto'" src="@/assets/logo_webui.png" alt="logo" />
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, defineProps, onMounted, watch } from 'vue';
|
||||||
import emitter from '@/ts/event-bus';
|
import emitter from '@/ts/event-bus';
|
||||||
|
|
||||||
type MenuItem = {
|
type MenuItem = {
|
||||||
@@ -43,11 +43,10 @@ type MenuItem = {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
menuItems: MenuItem[];
|
menuItems: MenuItem[];
|
||||||
menuWidth: string | number | Array<string | number>;
|
|
||||||
}>();
|
}>();
|
||||||
const mediaQuery = window.matchMedia('(max-width: 800px)');
|
|
||||||
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
|
const collapsed = ref<boolean>(localStorage.getItem('sidebar-collapsed') === 'true');
|
||||||
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
|
const iconName = ref<string>(collapsed.value ? 'menu-unfold' : 'menu-fold');
|
||||||
const disBtn = ref<boolean>(false);
|
const disBtn = ref<boolean>(false);
|
||||||
@@ -58,10 +57,12 @@ const changeCollapsed = (): void => {
|
|||||||
localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
|
localStorage.setItem('sidebar-collapsed', collapsed.value.toString());
|
||||||
};
|
};
|
||||||
watch(collapsed, (newValue, oldValue) => {
|
watch(collapsed, (newValue, oldValue) => {
|
||||||
|
setTimeout(() => {
|
||||||
emitter.emit('sendMenu', collapsed.value);
|
emitter.emit('sendMenu', collapsed.value);
|
||||||
|
}, 300);
|
||||||
});
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.emit('sendMenu', collapsed.value);
|
const mediaQuery = window.matchMedia('(max-width: 800px)');
|
||||||
const handleMediaChange = (e: MediaQueryListEvent) => {
|
const handleMediaChange = (e: MediaQueryListEvent) => {
|
||||||
disBtn.value = e.matches;
|
disBtn.value = e.matches;
|
||||||
if (e.matches) {
|
if (e.matches) {
|
||||||
|
@@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<t-head-menu theme="light" class="bottom-menu">
|
|
||||||
<router-link v-for="item in menuItems" :key="item.value" :to="item.route">
|
|
||||||
<t-tooltip :content="item.label" placement="top">
|
|
||||||
<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>
|
|
||||||
</t-head-menu>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
type MenuItem = {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
route: string;
|
|
||||||
icon?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
menuItems: MenuItem[];
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.bottom-menu {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
border-top: 0.8px solid #ddd;
|
|
||||||
}
|
|
||||||
</style>
|
|
@@ -4,10 +4,3 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@font-face {
|
|
||||||
font-family: 'ProtoNerdFontItalic';
|
|
||||||
src: url('../assets/0xProtoNerdFont-Italic.ttf') format('truetype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -40,13 +40,8 @@ import {
|
|||||||
Aside as TAside,
|
Aside as TAside,
|
||||||
Popconfirm as Tpopconfirm,
|
Popconfirm as Tpopconfirm,
|
||||||
Empty as TEmpty,
|
Empty as TEmpty,
|
||||||
Dropdown as TDropdown,
|
|
||||||
Typography as TTypographyText,
|
|
||||||
TreeSelect as TTreeSelect,
|
|
||||||
Loading as TLoading,
|
|
||||||
HeadMenu as THeadMenu
|
|
||||||
} from 'tdesign-vue-next';
|
} from 'tdesign-vue-next';
|
||||||
import router from './router';
|
import { router } from './router';
|
||||||
import 'tdesign-vue-next/es/style/index.css';
|
import 'tdesign-vue-next/es/style/index.css';
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
@@ -89,9 +84,4 @@ app.use(TFooter);
|
|||||||
app.use(TAside);
|
app.use(TAside);
|
||||||
app.use(Tpopconfirm);
|
app.use(Tpopconfirm);
|
||||||
app.use(TEmpty);
|
app.use(TEmpty);
|
||||||
app.use(TDropdown);
|
|
||||||
app.use(TTypographyText);
|
|
||||||
app.use(TTreeSelect);
|
|
||||||
app.use(TLoading);
|
|
||||||
app.use(THeadMenu);
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
@@ -1,101 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="about-us">
|
<div class="about-us">
|
||||||
<div>
|
<div>
|
||||||
<t-divider content="面板关于信息" align="left">
|
<t-divider content="面板关于信息" align="left" />
|
||||||
<template #content>
|
<t-alert theme="success" message="NapCat.WebUi is running" />
|
||||||
<div style="display: flex; justify-content: center; align-items: center">
|
<t-list class="list">
|
||||||
<info-circle-icon></info-circle-icon>
|
<t-list-item class="list-item">
|
||||||
<div style="margin-left: 5px">面板关于信息</div>
|
<span class="item-label">开发人员:</span>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</t-divider>
|
|
||||||
<t-alert theme="success" class="header" message="NapCat.WebUi is running" />
|
|
||||||
<t-list>
|
|
||||||
<t-list-item>
|
|
||||||
<div class="label-box">
|
|
||||||
<star-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Star:</span>
|
|
||||||
</div>
|
|
||||||
<span class="item-content">
|
<span class="item-content">
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/stargazers">{{
|
<t-link href="mailto:nanaeonn@outlook.com">Mlikiowa</t-link>
|
||||||
githubBastData?.stargazers_count
|
|
||||||
}}</t-link>
|
|
||||||
</span>
|
</span>
|
||||||
</t-list-item>
|
</t-list-item>
|
||||||
<t-list-item>
|
<t-list-item class="list-item">
|
||||||
<tips-filled-icon class="item-icon" size="large" />
|
<span class="item-label">版本信息:</span>
|
||||||
<span class="item-label">issues:</span>
|
|
||||||
<span class="item-content">
|
<span class="item-content">
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/issues">{{
|
<t-tag class="tag-item" theme="success"> WebUi: {{ pkg.version }} </t-tag>
|
||||||
githubBastData?.open_issues_count
|
<t-tag class="tag-item" theme="success"> NapCat: {{ napCatVersion }} </t-tag>
|
||||||
}}</t-link>
|
<t-tag class="tag-item" theme="success">
|
||||||
</span>
|
TDesign: {{ pkg.dependencies['tdesign-vue-next'] }}
|
||||||
</t-list-item>
|
|
||||||
<t-list-item>
|
|
||||||
<git-pull-request-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Pull Requests:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/pulls">{{githubPullData?.length
|
|
||||||
}}</t-link>
|
|
||||||
</span>
|
|
||||||
</t-list-item>
|
|
||||||
<t-list-item >
|
|
||||||
<bookmark-add-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Releases:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/releases">{{
|
|
||||||
githubReleasesData&&githubReleasesData[0]?timeDifference(githubReleasesData[0].published_at) + '前更新':''
|
|
||||||
}}</t-link>
|
|
||||||
</span>
|
|
||||||
</t-list-item>
|
|
||||||
<t-list-item>
|
|
||||||
<usergroup-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Contributors:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/graphs/contributors">{{githubContributorsData?.length}}</t-link>
|
|
||||||
</span>
|
|
||||||
</t-list-item>
|
|
||||||
<t-list-item>
|
|
||||||
<browse-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Watchers:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/watchers">{{
|
|
||||||
githubBastData?.watchers
|
|
||||||
}}</t-link>
|
|
||||||
</span>
|
|
||||||
</t-list-item>
|
|
||||||
<t-list-item>
|
|
||||||
<fork-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Fork:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ/fork">{{
|
|
||||||
githubBastData?.forks_count
|
|
||||||
}}</t-link>
|
|
||||||
</span>
|
|
||||||
</t-list-item>
|
|
||||||
<t-list-item>
|
|
||||||
<statue-of-jesus-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">License:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-link class="link-text" href="https://github.com/NapNeko/NapCatQQ#License-1-ov-file">{{
|
|
||||||
githubBastData?.license.key
|
|
||||||
}}</t-link>
|
|
||||||
</span>
|
|
||||||
</t-list-item>
|
|
||||||
<t-list-item>
|
|
||||||
<component-layout-filled-icon class="item-icon" size="large" />
|
|
||||||
<span class="item-label">Version:</span>
|
|
||||||
<span class="item-content">
|
|
||||||
<t-tag class="tag-item pgk-color"> WebUi: {{ pkg.version }} </t-tag>
|
|
||||||
<t-tag class="tag-item nc-color">
|
|
||||||
NapCat:
|
|
||||||
{{ napCatVersion }}
|
|
||||||
</t-tag>
|
</t-tag>
|
||||||
<t-tag v-if="githubReleasesData&&githubReleasesData[0] ?.tag_name" class="tag-item nc-color">
|
|
||||||
New NapCat:
|
|
||||||
{{ githubReleasesData[0].tag_name }}
|
|
||||||
</t-tag>
|
|
||||||
<t-tag class="tag-item td-color"> TDesign: {{ pkg.dependencies['tdesign-vue-next'] }} </t-tag>
|
|
||||||
</span>
|
</span>
|
||||||
</t-list-item>
|
</t-list-item>
|
||||||
</t-list>
|
</t-list>
|
||||||
@@ -106,51 +28,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import pkg from '../../package.json';
|
import pkg from '../../package.json';
|
||||||
import { napCatVersion } from '../../../src/common/version';
|
import { napCatVersion } from '../../../src/common/version';
|
||||||
import {
|
|
||||||
InfoCircleIcon,
|
|
||||||
TipsFilledIcon,
|
|
||||||
StarFilledIcon,
|
|
||||||
GitPullRequestFilledIcon,
|
|
||||||
ForkFilledIcon,
|
|
||||||
StatueOfJesusFilledIcon,
|
|
||||||
BookmarkAddFilledIcon,
|
|
||||||
UsergroupFilledIcon,
|
|
||||||
BrowseFilledIcon,
|
|
||||||
ComponentLayoutFilledIcon,
|
|
||||||
} from 'tdesign-icons-vue-next';
|
|
||||||
import { githubApiManager } from '@/backend/githubApi';
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
const githubApi = new githubApiManager();
|
|
||||||
const githubBastData = ref<any>(null);
|
|
||||||
const githubReleasesData = ref<any>(null);
|
|
||||||
const githubContributorsData = ref<any>(null);
|
|
||||||
const githubPullData = ref<any>(null);
|
|
||||||
const getBaseData = async () => {
|
|
||||||
githubBastData.value = await githubApi.GetBaseData();
|
|
||||||
githubReleasesData.value = await githubApi.GetReleasesData();
|
|
||||||
githubContributorsData.value = await githubApi.GetContributors();
|
|
||||||
githubPullData.value = await githubApi.GetPullsData();
|
|
||||||
};
|
|
||||||
const timeDifference = (timestamp: string): string => {
|
|
||||||
const givenTime = new Date(timestamp);
|
|
||||||
const currentTime = new Date();
|
|
||||||
const diffInMilliseconds = currentTime.getTime() - givenTime.getTime();
|
|
||||||
|
|
||||||
const seconds = Math.floor(diffInMilliseconds / 1000);
|
|
||||||
const minutes = Math.floor(seconds / 60);
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
|
|
||||||
if (hours > 0) {
|
|
||||||
return `${hours}小时`;
|
|
||||||
} else if (minutes > 0) {
|
|
||||||
return `${minutes}分钟`;
|
|
||||||
} else {
|
|
||||||
return `${seconds}秒`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
|
||||||
getBaseData();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -158,26 +35,23 @@ onMounted(() => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.label-box {
|
|
||||||
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.item-icon {
|
|
||||||
padding: 5px;
|
|
||||||
color: #ffffff;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-image: linear-gradient(-225deg, #2cd8d5 0%, #c5c1ff 56%, #ffbac3 100%);
|
|
||||||
}
|
|
||||||
.item-label {
|
.item-label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 8px;
|
font-weight: bold;
|
||||||
box-sizing: border-box;
|
|
||||||
height: auto;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-content {
|
.item-content {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -190,37 +64,3 @@ onMounted(() => {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
|
||||||
.t-list-item {
|
|
||||||
padding: 5px var(--td-comp-paddingLR-l);
|
|
||||||
}
|
|
||||||
.item-label {
|
|
||||||
flex: 2;
|
|
||||||
background-image: linear-gradient(to right, #fa709a 0%, #fee140 100%);
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
.pgk-color {
|
|
||||||
color: white;
|
|
||||||
background-image: linear-gradient(-225deg, #9be15d 0%, #00e3ae 100%);
|
|
||||||
}
|
|
||||||
.nc-color {
|
|
||||||
color: white;
|
|
||||||
background-image: linear-gradient(-225deg, #2cd8d5 0%, #c5c1ff 56%, #ffbac3 100%);
|
|
||||||
}
|
|
||||||
.td-color {
|
|
||||||
color: white;
|
|
||||||
background-image: linear-gradient(225deg, #0acffe 0%, #495aff 100%);
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-image: linear-gradient(225deg, #dfffcd 0%, #90f9c4 48%, #39f3bb 100%) !important;
|
|
||||||
}
|
|
||||||
.link-text{
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(-225deg, #B6CEE8 0%, #F578DC 100%);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,600 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="title">
|
<div class="log-view">
|
||||||
<t-divider content="日志查看" align="left">
|
<h1>面板日志信息</h1>
|
||||||
<template #content>
|
<p>这里显示面板的日志信息。</p>
|
||||||
<div style="display: flex; justify-content: center; align-items: center">
|
|
||||||
<system-log-icon></system-log-icon>
|
|
||||||
<div style="margin-left: 5px">日志查看</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</t-divider>
|
|
||||||
</div>
|
|
||||||
<div class="tab-box">
|
|
||||||
<t-tabs default-value="realtime" @change="selectType">
|
|
||||||
<t-tab-panel value="realtime" label="实时日志"></t-tab-panel>
|
|
||||||
<t-tab-panel value="history" label="历史日志"></t-tab-panel>
|
|
||||||
</t-tabs>
|
|
||||||
</div>
|
|
||||||
<div class="card-box">
|
|
||||||
<t-card class="card" :bordered="true">
|
|
||||||
<template #actions>
|
|
||||||
<t-row :align="'middle'" justify="center" :style="{ gap: smallScreen.matches ? '5px' : '24px' }">
|
|
||||||
<t-col flex="auto" style="display: inline-flex; justify-content: center">
|
|
||||||
<t-tooltip content="清理日志">
|
|
||||||
<t-button variant="text" shape="square" @click="clearLogs">
|
|
||||||
<clear-icon></clear-icon>
|
|
||||||
</t-button>
|
|
||||||
</t-tooltip>
|
|
||||||
</t-col>
|
|
||||||
<t-col flex="auto" style="display: inline-flex; justify-content: center">
|
|
||||||
<t-tooltip content="下载日志">
|
|
||||||
<t-button variant="text" shape="square" @click="downloadText">
|
|
||||||
<download-icon></download-icon>
|
|
||||||
</t-button>
|
|
||||||
</t-tooltip>
|
|
||||||
</t-col>
|
|
||||||
<t-col
|
|
||||||
v-if="LogDataType === 'history'"
|
|
||||||
flex="auto"
|
|
||||||
style="display: inline-flex; justify-content: center">
|
|
||||||
<t-tooltip content="历史日志">
|
|
||||||
<t-button variant="text" shape="square" @click="historyLog">
|
|
||||||
<history-icon></history-icon>
|
|
||||||
</t-button>
|
|
||||||
</t-tooltip>
|
|
||||||
</t-col>
|
|
||||||
<t-col flex="auto" style="display: inline-flex; justify-content: center">
|
|
||||||
<div class="tag-box">
|
|
||||||
<t-tag class="t-tag" :style="{ backgroundImage: typeKey[optValue.description] }">{{
|
|
||||||
optValue.content }}</t-tag>
|
|
||||||
</div>
|
|
||||||
<t-dropdown :options="options" :min-column-width="112" @click="openTypeList">
|
|
||||||
<t-button variant="text" shape="square">
|
|
||||||
<more-icon />
|
|
||||||
</t-button>
|
|
||||||
</t-dropdown>
|
|
||||||
</t-col>
|
|
||||||
</t-row>
|
|
||||||
</template>
|
|
||||||
<template #content>
|
|
||||||
<div class="content" ref="contentBox">
|
|
||||||
<div v-for="item in LogDataType === 'realtime'
|
|
||||||
? realtimeLogHtmlList.get(optValue.description)
|
|
||||||
: historyLogHtmlList.get(optValue.description)">
|
|
||||||
<span>{{ item.time }}</span><span :id="item.type">{{ item.content }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</t-card>
|
|
||||||
</div>
|
|
||||||
<t-dialog v-model:visible="visibleBody" header="历史日志" :destroy-on-close="true" :show-in-attached-element="true"
|
|
||||||
:on-confirm="GetLogList" class=".t-dialog__ctx .t-dialog__position">
|
|
||||||
<t-select v-model="value" :options="logFileData" placeholder="请选择日志" :multiple="true"
|
|
||||||
style="text-align: left" />
|
|
||||||
</t-dialog>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { MoreIcon, ClearIcon, DownloadIcon, HistoryIcon, SystemLogIcon } from 'tdesign-icons-vue-next';
|
|
||||||
import { nextTick, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
|
||||||
import { LogManager } from '@/backend/log';
|
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
|
||||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
||||||
const smallScreen = window.matchMedia('(max-width: 768px)');
|
|
||||||
const LogDataType = ref<string>('realtime');
|
|
||||||
const visibleBody = ref<boolean>(false);
|
|
||||||
const contentBox = ref<HTMLElement | null>(null);
|
|
||||||
let isMouseEntered = false;
|
|
||||||
const logManager = new LogManager(localStorage.getItem('auth') || '');
|
|
||||||
const eventSource = ref<EventSourcePolyfill | null>(null);
|
|
||||||
const intervalId = ref<number | null>(null);
|
|
||||||
const isPaused = ref(false);
|
|
||||||
interface OptionItem {
|
|
||||||
content: string;
|
|
||||||
value: number;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
const options = ref<OptionItem[]>([
|
|
||||||
{
|
|
||||||
content: '全部',
|
|
||||||
value: 1,
|
|
||||||
description: 'all',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '调试',
|
|
||||||
value: 2,
|
|
||||||
description: 'debug',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '提示',
|
|
||||||
value: 3,
|
|
||||||
description: 'info',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '警告',
|
|
||||||
value: 4,
|
|
||||||
description: 'warn',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '错误',
|
|
||||||
value: 5,
|
|
||||||
description: 'error',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: '致命',
|
|
||||||
value: 5,
|
|
||||||
description: 'fatal',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const typeKey = ref<Record<string, string>>({
|
|
||||||
all: 'linear-gradient(60deg,#16a085 0%, #f4d03f 100%)',
|
|
||||||
debug: 'linear-gradient(-225deg, #5271c4 0%, #b19fff 48%, #eca1fe 100%)',
|
|
||||||
info: 'linear-gradient(-225deg, #22e1ff 0%, #1d8fe1 48%, #625eb1 100%)',
|
|
||||||
warn: 'linear-gradient(to right, #e14fad 0%, #f9d423 48%, #e37318 100%)',
|
|
||||||
error: 'linear-gradient(to left, #ffe29f 0%, #ffa99f 48%, #d94541 100%)',
|
|
||||||
fatal: 'linear-gradient(-225deg, #fd0700, #ec567f)',
|
|
||||||
});
|
|
||||||
interface logHtml {
|
|
||||||
type?: string;
|
|
||||||
content: string;
|
|
||||||
color?: string;
|
|
||||||
time?: string;
|
|
||||||
}
|
|
||||||
type LogHtmlMap = Map<string, logHtml[]>;
|
|
||||||
const realtimeLogHtmlList = ref<LogHtmlMap>(
|
|
||||||
new Map([
|
|
||||||
['all', []],
|
|
||||||
['debug', []],
|
|
||||||
['info', []],
|
|
||||||
['warn', []],
|
|
||||||
['error', []],
|
|
||||||
['fatal', []],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
const historyLogHtmlList = ref<LogHtmlMap>(
|
|
||||||
new Map([
|
|
||||||
['all', []],
|
|
||||||
['debug', []],
|
|
||||||
['info', []],
|
|
||||||
['warn', []],
|
|
||||||
['error', []],
|
|
||||||
['fatal', []],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
const logFileData = ref<{ label: string; value: string }[]>([]);
|
|
||||||
const value = ref([]);
|
|
||||||
const optValue = ref<OptionItem>({
|
|
||||||
content: '全部',
|
|
||||||
value: 1,
|
|
||||||
description: 'all',
|
|
||||||
});
|
|
||||||
const openTypeList = (data: OptionItem) => {
|
|
||||||
optValue.value = data;
|
|
||||||
};
|
|
||||||
const logType = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
||||||
//清理log
|
|
||||||
const clearLogs = () => {
|
|
||||||
if (LogDataType.value === 'realtime') {
|
|
||||||
clearAllLogs(realtimeLogHtmlList);
|
|
||||||
} else {
|
|
||||||
clearAllLogs(historyLogHtmlList);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const clearAllLogs = (logList: Ref<Map<string, Array<logHtml>>>) => {
|
|
||||||
if ((optValue.value && optValue.value.description === 'all') || !optValue.value) {
|
|
||||||
logList.value = new Map([
|
|
||||||
['all', []],
|
|
||||||
['debug', []],
|
|
||||||
['info', []],
|
|
||||||
['warn', []],
|
|
||||||
['error', []],
|
|
||||||
['fatal', []],
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
logList.value.set(optValue.value.description, []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//定时清理log
|
|
||||||
|
|
||||||
const TimerClear = () => {
|
|
||||||
clearAllLogs(realtimeLogHtmlList);
|
|
||||||
};
|
|
||||||
const startTimer = () => {
|
|
||||||
if (!isPaused.value) {
|
|
||||||
intervalId.value = window.setInterval(TimerClear, 0.5 * 60 * 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const pauseTimer = () => {
|
|
||||||
if (intervalId.value) {
|
|
||||||
window.clearInterval(intervalId.value);
|
|
||||||
isPaused.value = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const resumeTimer = () => {
|
|
||||||
if (isPaused.value) {
|
|
||||||
startTimer();
|
|
||||||
isPaused.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const stopTimer = () => {
|
|
||||||
if (intervalId.value) {
|
|
||||||
window.clearInterval(intervalId.value);
|
|
||||||
intervalId.value = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const extractContent = (text: string): string | null => {
|
|
||||||
const regex = /\[([^\]]+)]/;
|
|
||||||
const match = regex.exec(text);
|
|
||||||
if (match && match[1]) {
|
|
||||||
const extracted = match[1].toLowerCase();
|
|
||||||
if (logType.includes(extracted)) {
|
|
||||||
return match[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
const loadData = (text: string, loadType: string) => {
|
|
||||||
const lines = text.split(/\r\n/);
|
|
||||||
lines.forEach((line) => {
|
|
||||||
if (loadType === 'realtime') {
|
|
||||||
let remoteJson = JSON.parse(line) as { message: string, level: string };
|
|
||||||
const type = remoteJson.level;
|
|
||||||
const actualType = type || 'other';
|
|
||||||
const color = actualType && typeKey.value[actualType] ? typeKey.value[actualType] : undefined;
|
|
||||||
const data: logHtml = {
|
|
||||||
type: actualType,
|
|
||||||
content: remoteJson.message,
|
|
||||||
color: color,
|
|
||||||
time: '',
|
|
||||||
};
|
|
||||||
updateLogList(realtimeLogHtmlList, actualType, data);
|
|
||||||
} else if (loadType === 'history') {
|
|
||||||
const type = extractContent(line);
|
|
||||||
const actualType = type || 'other';
|
|
||||||
const timeRegex = /(\d{2}-\d{2} \d{2}:\d{2}:\d{2})|(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/;
|
|
||||||
const match = timeRegex.exec(line);
|
|
||||||
let time = match ? match[0] : null;
|
|
||||||
const color = actualType && typeKey.value[actualType] ? typeKey.value[actualType] : undefined;
|
|
||||||
const data: logHtml = {
|
|
||||||
type: actualType,
|
|
||||||
content: line.slice(match ? match[0].length : 0) || '',
|
|
||||||
color: color,
|
|
||||||
time: time ? time + ' ' : '',
|
|
||||||
};
|
|
||||||
updateLogList(historyLogHtmlList, actualType, data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateLogList = (logList: Ref<Map<string, Array<logHtml>>>, actualType: string, data: logHtml) => {
|
|
||||||
const allLogs = logList.value.get('all');
|
|
||||||
if (Array.isArray(allLogs)) {
|
|
||||||
allLogs.push(data);
|
|
||||||
}
|
|
||||||
if (actualType !== 'other') {
|
|
||||||
const typeLogs = logList.value.get(actualType);
|
|
||||||
if (Array.isArray(typeLogs)) {
|
|
||||||
typeLogs.push(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const selectType = (key: string) => {
|
|
||||||
LogDataType.value = key;
|
|
||||||
};
|
|
||||||
interface CustomURL extends URL {
|
|
||||||
recycleObjectURL: (url: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isCompatibleWithCustomURL = (obj: any): obj is CustomURL => {
|
|
||||||
return typeof obj === 'object' && obj !== null && typeof (obj as any).recycleObjectURL === 'function';
|
|
||||||
};
|
|
||||||
|
|
||||||
const recycleURL = (url: string) => {
|
|
||||||
if (isCompatibleWithCustomURL(window.URL)) {
|
|
||||||
const customURL = window.URL as CustomURL;
|
|
||||||
customURL.recycleObjectURL(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const generateTXT = (textContent: string, fileName: string) => {
|
|
||||||
try {
|
|
||||||
const blob = new Blob([textContent], { type: 'text/plain' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = fileName;
|
|
||||||
a.click();
|
|
||||||
recycleURL(url);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载文本时出现错误:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const downloadText = () => {
|
|
||||||
if (LogDataType.value === 'realtime') {
|
|
||||||
const logs = realtimeLogHtmlList.value.get(optValue.value.description);
|
|
||||||
if (logs && logs.length > 0) {
|
|
||||||
const result = logs.map((obj) => obj.content).join('\r\n');
|
|
||||||
generateTXT(result, '实时日志');
|
|
||||||
} else {
|
|
||||||
MessagePlugin.error('暂无可下载日志');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const logs = historyLogHtmlList.value.get(optValue.value.description);
|
|
||||||
if (logs && logs.length > 0) {
|
|
||||||
const result = logs.map((obj) => obj.content).join('\r\n');
|
|
||||||
generateTXT(result, '历史日志');
|
|
||||||
} else {
|
|
||||||
MessagePlugin.error('暂无可下载日志');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const historyLog = async () => {
|
|
||||||
value.value = [];
|
|
||||||
visibleBody.value = true;
|
|
||||||
const res = await logManager.GetLogList();
|
|
||||||
clearAllLogs(historyLogHtmlList);
|
|
||||||
if (res.length > 0) {
|
|
||||||
logFileData.value = res.map((ele: string) => {
|
|
||||||
return { label: ele, value: ele };
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logFileData.value = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const GetLogList = async () => {
|
|
||||||
if (value.value.length > 0) {
|
|
||||||
for (const ele of value.value) {
|
|
||||||
try {
|
|
||||||
const data = await logManager.GetLog(ele);
|
|
||||||
if (data && data !== 'null') {
|
|
||||||
loadData(data, 'history');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`获取日志 ${ele} 时出现错误:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visibleBody.value = false;
|
|
||||||
} else {
|
|
||||||
MessagePlugin.error('请选择日志');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchRealTimeLogs = async () => {
|
|
||||||
eventSource.value = await logManager.getRealTimeLogs();
|
|
||||||
if (eventSource.value) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
//@ts-expect-error
|
|
||||||
eventSource.value.onmessage = (event: MessageEvent) => {
|
|
||||||
console.log(event.data)
|
|
||||||
loadData(event.data, 'realtime');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const closeRealTimeLogs = async () => {
|
|
||||||
if (eventSource.value) {
|
|
||||||
eventSource.value.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
if (!isMouseEntered) {
|
|
||||||
nextTick(() => {
|
|
||||||
if (contentBox.value) {
|
|
||||||
contentBox.value.scrollTop = contentBox.value.scrollHeight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const observeDOMChanges = () => {
|
|
||||||
if (contentBox.value) {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
});
|
|
||||||
observer.observe(contentBox.value, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const showScrollbar = () => {
|
|
||||||
if (contentBox.value) {
|
|
||||||
contentBox.value.style.overflow = 'auto';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const hideScrollbar = () => {
|
|
||||||
if (contentBox.value) {
|
|
||||||
contentBox.value.style.overflow = 'hidden';
|
|
||||||
if (!isMouseEntered) {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
realtimeLogHtmlList,
|
|
||||||
() => {
|
|
||||||
if (!isMouseEntered) {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
watch(
|
|
||||||
historyLogHtmlList,
|
|
||||||
() => {
|
|
||||||
if (!isMouseEntered) {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchRealTimeLogs();
|
|
||||||
startTimer();
|
|
||||||
contentBox.value = document.querySelector('.content');
|
|
||||||
if (contentBox.value) {
|
|
||||||
contentBox.value.style.overflow = 'hidden';
|
|
||||||
contentBox.value.addEventListener('mouseenter', () => {
|
|
||||||
isMouseEntered = true;
|
|
||||||
showScrollbar();
|
|
||||||
pauseTimer();
|
|
||||||
});
|
|
||||||
contentBox.value.addEventListener('mouseleave', () => {
|
|
||||||
isMouseEntered = false;
|
|
||||||
hideScrollbar();
|
|
||||||
resumeTimer();
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
observeDOMChanges();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
closeRealTimeLogs();
|
|
||||||
stopTimer();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.title {
|
|
||||||
padding: 20px 20px 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-box {
|
|
||||||
margin: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-box {
|
|
||||||
margin: 10px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
height: 56vh;
|
|
||||||
background-image: url('@/assets/logo.png');
|
|
||||||
border: 1px solid #ddd6d6 !important;
|
|
||||||
padding: 5px 10px;
|
|
||||||
text-align: left;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: -10px;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content span {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeInOnce {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeOutOnce {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content div {
|
|
||||||
animation: fadeInOnce 0.5s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
background: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #888888;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-box {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-tag {
|
|
||||||
min-width: 60px;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
#debug {
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(-225deg, #5271c4 0%, #b19fff 48%, #eca1fe 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#info {
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(-225deg, #22e1ff 0%, #1d8fe1 48%, #625eb1 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#warn {
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(225deg, #e14fad 0%, #f9d423 48%, #e37318 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#error {
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(to left, #ffe29f 0%, #ffa99f 48%, #d94541 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#fatal {
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(to right, #fd0700, #ec567f);
|
|
||||||
}
|
|
||||||
|
|
||||||
#other {
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-image: linear-gradient(to top, #3f51b1 0%, #5a55ae 13%, #7b5fac 25%, #8f6aae 38%, #a86aa4 50%, #cc6b8e 62%, #f18271 75%, #f3a469 87%, #f7c978 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 786px) {
|
|
||||||
.content {
|
|
||||||
height: 50vh;
|
|
||||||
font-family: ProtoNerdFontItalic, monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 14.3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style>
|
|
||||||
.card {
|
|
||||||
padding: 5px 10px 20px 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 786px) {
|
|
||||||
.card {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,97 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="headerBox" class="title">
|
<div ref="headerBox" class="title">
|
||||||
<t-divider content="网络配置" align="left">
|
<t-divider content="网络配置" align="left" />
|
||||||
<template #content>
|
|
||||||
<div style="display: flex; justify-content: center; align-items: center">
|
|
||||||
<wifi1-icon />
|
|
||||||
<div style="margin-left: 5px">网络配置</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</t-divider>
|
|
||||||
<t-divider align="right">
|
<t-divider align="right">
|
||||||
<t-button @click="addConfig()">
|
<t-button @click="addConfig()">
|
||||||
<template #icon><add-icon /></template>
|
<template #icon><add-icon /></template>
|
||||||
添加配置</t-button
|
添加配置</t-button>
|
||||||
>
|
|
||||||
</t-divider>
|
</t-divider>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="loadPage" ref="setting" class="setting">
|
<div v-if="loadPage" ref="setting" class="setting">
|
||||||
<t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
|
<t-tabs ref="tabsRef" :style="{ width: tabsWidth + 'px' }" default-value="all" @change="selectType">
|
||||||
<t-tab-panel value="all" label="全部"></t-tab-panel>
|
<t-tab-panel value="all" label="全部"></t-tab-panel>
|
||||||
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
|
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
|
||||||
<t-tab-panel value="httpSeeServers" label="HTTP SSE 服务器"></t-tab-panel>
|
|
||||||
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
|
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
|
||||||
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
|
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
|
||||||
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
|
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
|
||||||
|
|
||||||
</t-tabs>
|
</t-tabs>
|
||||||
</div>
|
</div>
|
||||||
<t-loading attach="#alice" :loading="!loadPage" :showOverlay="false">
|
|
||||||
<div id="alice" v-if="!loadPage" style="height: 80vh;position: relative" ></div>
|
|
||||||
</t-loading>
|
|
||||||
<div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
|
<div v-if="loadPage" class="card-box" :style="{ width: tabsWidth + 'px' }">
|
||||||
<div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
|
<div class="setting-box" :style="{ maxHeight: cardHeight + 'px' }" v-if="cardConfig.length > 0">
|
||||||
<div v-for="(item, index) in cardConfig" :key="index">
|
<div v-for="(item, index) in cardConfig" :key="index">
|
||||||
<t-card
|
<t-card :title="item.name" :description="item.type" :style="{ width: cardWidth + 'px' }"
|
||||||
:title="item.name"
|
:header-bordered="true" class="setting-card">
|
||||||
:description="item.type"
|
|
||||||
:style="{ width: cardWidth + 'px' }"
|
|
||||||
:header-bordered="true"
|
|
||||||
class="setting-card"
|
|
||||||
>
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<t-space>
|
<t-space>
|
||||||
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
|
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
|
||||||
<t-popconfirm content="确认删除" @confirm="delConfig(item)">
|
<t-popconfirm theme="danger" content="确认删除" @confirm="delConfig(item)">
|
||||||
<delete-icon size="20px"></delete-icon>
|
<delete-icon size="20px"></delete-icon>
|
||||||
</t-popconfirm>
|
</t-popconfirm>
|
||||||
</t-space>
|
</t-space>
|
||||||
</template>
|
</template>
|
||||||
<div class="setting-content">
|
<div class="setting-content">
|
||||||
<t-card
|
<t-card class="card-address" :style="{
|
||||||
class="card-address"
|
borderLeft: '7px solid ' + (item.enable ?
|
||||||
:style="{
|
'var(--td-success-color)' :
|
||||||
borderLeft:
|
'var(--td-error-color)')
|
||||||
'7px solid ' + (item.enable ? 'var(--td-success-color)' : 'var(--td-error-color)'),
|
}">
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="local-box" v-if="item.host&&item.port">
|
<div class="local-box" v-if="item.host&&item.port">
|
||||||
<server-filled-icon class="local-icon" size="20px" @click="toggleProperty(item, 'enable')"></server-filled-icon>
|
<server-filled-icon class="local-icon" size="20px"></server-filled-icon>
|
||||||
<strong class="local">{{ item.host }}:{{ item.port }}</strong>
|
<strong class="local">{{ item.host }}:{{ item.port }}</strong>
|
||||||
<copy-icon
|
<copy-icon class="copy-icon" size="20px" @click="copyText(item.host + ':' + item.port)"></copy-icon>
|
||||||
class="copy-icon"
|
|
||||||
size="20px"
|
|
||||||
@click="copyText(item.host + ':' + item.port)"
|
|
||||||
></copy-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="local-box" v-if="item.url">
|
<div class="local-box" v-if="item.url">
|
||||||
<server-filled-icon class="local-icon" size="20px" @click="toggleProperty(item, 'enable')"></server-filled-icon>
|
<server-filled-icon class="local-icon" size="20px"></server-filled-icon>
|
||||||
<strong class="local" >{{ item.url }}</strong>
|
<strong class="local" >{{ item.url }}</strong>
|
||||||
<copy-icon class="copy-icon" size="20px" @click="copyText(item.url)"></copy-icon>
|
<copy-icon class="copy-icon" size="20px" @click="copyText(item.url)"></copy-icon>
|
||||||
</div>
|
</div>
|
||||||
</t-card>
|
</t-card>
|
||||||
<t-collapse :default-value="[0]" expand-mutex style="margin-top: 10px" class="info-coll">
|
<t-collapse :default-value="[0]" expand-mutex style="margin-top:10px;" class="info-coll">
|
||||||
<t-collapse-panel header="基础信息">
|
<t-collapse-panel header="基础信息">
|
||||||
<t-descriptions
|
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
|
||||||
size="small"
|
class="setting-base-info">
|
||||||
:layout="infoOneCol ? 'vertical' : 'horizontal'"
|
|
||||||
class="setting-base-info"
|
|
||||||
>
|
|
||||||
<t-descriptions-item v-if="item.token" label="连接密钥">
|
<t-descriptions-item v-if="item.token" label="连接密钥">
|
||||||
<div v-if="mediumScreen.matches||largeScreen.matches" class="token-view">
|
<div v-if="mediumScreen.matches||largeScreen.matches" class="token-view">
|
||||||
<span>{{ showToken ? item.token : '******' }}</span>
|
<span>{{ showToken ? item.token : '******' }}</span>
|
||||||
<browse-icon
|
<browse-icon class="browse-icon" v-if="showToken" size="18px"
|
||||||
class="browse-icon"
|
@click="showToken = false"></browse-icon>
|
||||||
v-if="showToken"
|
<browse-off-icon class="browse-icon" v-else size="18px"
|
||||||
size="18px"
|
@click="showToken = true"></browse-off-icon>
|
||||||
@click="showToken = false"
|
|
||||||
></browse-icon>
|
|
||||||
<browse-off-icon
|
|
||||||
class="browse-icon"
|
|
||||||
v-else
|
|
||||||
size="18px"
|
|
||||||
@click="showToken = true"
|
|
||||||
></browse-off-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<t-popup :showArrow="true" trigger="click">
|
<t-popup :showArrow="true" trigger="click">
|
||||||
@@ -102,66 +67,35 @@
|
|||||||
</t-popup>
|
</t-popup>
|
||||||
</div>
|
</div>
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item label="消息格式">{{
|
<t-descriptions-item label="消息格式">{{ item.messagePostFormat }}</t-descriptions-item>
|
||||||
item.messagePostFormat
|
|
||||||
}}</t-descriptions-item>
|
|
||||||
</t-descriptions>
|
</t-descriptions>
|
||||||
</t-collapse-panel>
|
</t-collapse-panel>
|
||||||
<t-collapse-panel header="状态信息">
|
<t-collapse-panel header="状态信息">
|
||||||
<t-descriptions
|
<t-descriptions size="small" :layout="infoOneCol ? 'vertical' : 'horizontal'"
|
||||||
size="small"
|
class="setting-base-info">
|
||||||
:layout="infoOneCol ? 'vertical' : 'horizontal'"
|
|
||||||
class="setting-base-info"
|
|
||||||
>
|
|
||||||
<t-descriptions-item v-if="item.hasOwnProperty('debug')" label="调试日志">
|
<t-descriptions-item v-if="item.hasOwnProperty('debug')" label="调试日志">
|
||||||
<t-tag
|
<t-tag class="tag-item" :theme="item.debug ? 'success' : 'danger'">
|
||||||
:class="item.debug ? 'tag-item-on' : 'tag-item-off'"
|
{{ item.debug ? '开启' : '关闭' }}</t-tag>
|
||||||
@click="toggleProperty(item, 'debug')"
|
|
||||||
>
|
|
||||||
{{ item.debug ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableWebsocket')"
|
||||||
v-if="item.hasOwnProperty('enableWebsocket')"
|
label="Websocket 功能">
|
||||||
label="Websocket 功能"
|
<t-tag class="tag-item" :theme="item.enableWebsocket ? 'success' : 'danger'">
|
||||||
>
|
{{ item.enableWebsocket ? '启用' : '禁用' }}</t-tag>
|
||||||
<t-tag
|
|
||||||
:class="item.enableWebsocket ? 'tag-item-on' : 'tag-item-off'"
|
|
||||||
@click="toggleProperty(item, 'enableWebsocket')"
|
|
||||||
>
|
|
||||||
{{ item.enableWebsocket ? '启用' : '禁用' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableCors')" label="跨域放行">
|
||||||
v-if="item.hasOwnProperty('enableCors')"
|
<t-tag class="tag-item" :theme="item.enableCors ? 'success' : 'danger'">
|
||||||
label="跨域放行"
|
{{ item.enableCors ? '开启' : '关闭' }}</t-tag>
|
||||||
>
|
|
||||||
<t-tag :class="item.enableCors ? 'tag-item-on' : 'tag-item-off'" @click="toggleProperty(item, 'enableCors')">
|
|
||||||
{{ item.enableCors ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
|
||||||
v-if="item.hasOwnProperty('enableForcePushEvent')"
|
label="上报自身消息">
|
||||||
label="上报自身消息"
|
<t-tag class="tag-item" :theme="item.reportSelfMessage ? 'success' : 'danger'">
|
||||||
>
|
{{ item.reportSelfMessage ? '开启' : '关闭' }}</t-tag>
|
||||||
<t-tag
|
|
||||||
:class="item.reportSelfMessage ? 'tag-item-on' : 'tag-item-off'"
|
|
||||||
@click="toggleProperty(item, 'reportSelfMessage')"
|
|
||||||
>
|
|
||||||
{{ item.reportSelfMessage ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
<t-descriptions-item
|
<t-descriptions-item v-if="item.hasOwnProperty('enableForcePushEvent')"
|
||||||
v-if="item.hasOwnProperty('enableForcePushEvent')"
|
label="强制推送事件">
|
||||||
label="强制推送事件"
|
<t-tag class="tag-item"
|
||||||
>
|
:theme="item.enableForcePushEvent ? 'success' : 'danger'">
|
||||||
<t-tag
|
{{ item.enableForcePushEvent ? '开启' : '关闭' }}</t-tag>
|
||||||
class="tag-item"
|
|
||||||
:class="item.enableForcePushEvent ? 'tag-item-on' : 'tag-item-off'"
|
|
||||||
@click="toggleProperty(item, 'enableForcePushEvent')"
|
|
||||||
>
|
|
||||||
{{ item.enableForcePushEvent ? '开启' : '关闭' }}</t-tag
|
|
||||||
>
|
|
||||||
</t-descriptions-item>
|
</t-descriptions-item>
|
||||||
</t-descriptions>
|
</t-descriptions>
|
||||||
</t-collapse-panel>
|
</t-collapse-panel>
|
||||||
@@ -175,43 +109,26 @@
|
|||||||
<t-empty class="card-none" title="暂无网络配置"> </t-empty>
|
<t-empty class="card-none" title="暂无网络配置"> </t-empty>
|
||||||
</t-card>
|
</t-card>
|
||||||
</div>
|
</div>
|
||||||
<t-dialog
|
<t-dialog v-model:visible="visibleBody" :header="dialogTitle" :destroy-on-close="true"
|
||||||
v-model:visible="visibleBody"
|
:show-in-attached-element="true" placement="center" :on-confirm="saveConfig" class=".t-dialog__ctx .t-dialog--defaul">
|
||||||
:header="dialogTitle"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:show-in-attached-element="true"
|
|
||||||
:on-confirm="saveConfig"
|
|
||||||
class=".t-dialog__ctx .t-dialog__position"
|
|
||||||
>
|
|
||||||
<div slot="body" class="dialog-body" >
|
<div slot="body" class="dialog-body" >
|
||||||
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
|
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab">
|
||||||
<t-form-item
|
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
|
||||||
style="text-align: left"
|
label="名称" name="name">
|
||||||
:rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]"
|
|
||||||
label="名称"
|
|
||||||
name="name"
|
|
||||||
>
|
|
||||||
<t-input v-model="newTab.name" />
|
<t-input v-model="newTab.name" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item
|
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
|
||||||
style="text-align: left"
|
label="类型" name="type">
|
||||||
:rules="[{ required: true, message: '请选择类型', trigger: 'change' }]"
|
|
||||||
label="类型"
|
|
||||||
name="type"
|
|
||||||
>
|
|
||||||
<t-select v-model="newTab.type" @change="onloadDefault">
|
<t-select v-model="newTab.type" @change="onloadDefault">
|
||||||
<t-option value="httpServers">HTTP 服务器</t-option>
|
<t-option value="httpServers">HTTP 服务器</t-option>
|
||||||
<t-option value="httpSseServers">HTTP SSE 服务器</t-option>
|
|
||||||
<t-option value="httpClients">HTTP 客户端</t-option>
|
<t-option value="httpClients">HTTP 客户端</t-option>
|
||||||
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
<t-option value="websocketServers">WebSocket 服务器</t-option>
|
||||||
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
<t-option value="websocketClients">WebSocket 客户端</t-option>
|
||||||
</t-select>
|
</t-select>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<div>
|
<div>
|
||||||
<component
|
<component :is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
|
||||||
:is="resolveDynamicComponent(getComponent(newTab.type as ComponentKey))"
|
:config="newTab.data" />
|
||||||
:config="newTab.data"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</t-form>
|
</t-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,17 +136,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { AddIcon, DeleteIcon, Edit2Icon, ServerFilledIcon, CopyIcon, BrowseOffIcon, BrowseIcon } from 'tdesign-icons-vue-next';
|
||||||
AddIcon,
|
import { onMounted, onUnmounted, ref, resolveDynamicComponent } from 'vue';
|
||||||
DeleteIcon,
|
|
||||||
Edit2Icon,
|
|
||||||
ServerFilledIcon,
|
|
||||||
CopyIcon,
|
|
||||||
BrowseOffIcon,
|
|
||||||
BrowseIcon,
|
|
||||||
Wifi1Icon,
|
|
||||||
} from 'tdesign-icons-vue-next';
|
|
||||||
import { onMounted, onUnmounted, ref, resolveDynamicComponent, watch } from 'vue';
|
|
||||||
import emitter from '@/ts/event-bus';
|
import emitter from '@/ts/event-bus';
|
||||||
import {
|
import {
|
||||||
mergeNetworkDefaultConfig,
|
mergeNetworkDefaultConfig,
|
||||||
@@ -269,7 +177,6 @@ const componentMap: Record<
|
|||||||
| typeof WebsocketClientComponent
|
| typeof WebsocketClientComponent
|
||||||
> = {
|
> = {
|
||||||
httpServers: HttpServerComponent,
|
httpServers: HttpServerComponent,
|
||||||
httpSseServers: HttpServerComponent,
|
|
||||||
httpClients: HttpClientComponent,
|
httpClients: HttpClientComponent,
|
||||||
websocketServers: WebsocketServerComponent,
|
websocketServers: WebsocketServerComponent,
|
||||||
websocketClients: WebsocketClientComponent,
|
websocketClients: WebsocketClientComponent,
|
||||||
@@ -280,10 +187,9 @@ const operateType = ref<string>('');
|
|||||||
//配置项索引
|
//配置项索引
|
||||||
const configIndex = ref<number>(0);
|
const configIndex = ref<number>(0);
|
||||||
//保存时所用数据
|
//保存时所用数据
|
||||||
const networkConfig: NetworkConfig & { [key: string]: any } = {
|
const networkConfig: NetworkConfig & { [key: string]: any; } = {
|
||||||
websocketClients: [],
|
websocketClients: [],
|
||||||
websocketServers: [],
|
websocketServers: [],
|
||||||
httpSseServers: [],
|
|
||||||
httpClients: [],
|
httpClients: [],
|
||||||
httpServers: [],
|
httpServers: [],
|
||||||
};
|
};
|
||||||
@@ -294,7 +200,6 @@ const WebConfg = ref(
|
|||||||
['all', []],
|
['all', []],
|
||||||
['httpServers', []],
|
['httpServers', []],
|
||||||
['httpClients', []],
|
['httpClients', []],
|
||||||
['httpSseServers', []],
|
|
||||||
['websocketServers', []],
|
['websocketServers', []],
|
||||||
['websocketClients', []],
|
['websocketClients', []],
|
||||||
])
|
])
|
||||||
@@ -302,7 +207,6 @@ const WebConfg = ref(
|
|||||||
const typeCh: Record<ComponentKey, string> = {
|
const typeCh: Record<ComponentKey, string> = {
|
||||||
httpServers: 'HTTP 服务器',
|
httpServers: 'HTTP 服务器',
|
||||||
httpClients: 'HTTP 客户端',
|
httpClients: 'HTTP 客户端',
|
||||||
httpSseServers: 'HTTP SSE 服务器',
|
|
||||||
websocketServers: 'WebSocket 服务器',
|
websocketServers: 'WebSocket 服务器',
|
||||||
websocketClients: 'WebSocket 客户端',
|
websocketClients: 'WebSocket 客户端',
|
||||||
};
|
};
|
||||||
@@ -331,18 +235,6 @@ const editConfig = (item: any) => {
|
|||||||
configIndex.value = networkConfig[newTab.value.type].findIndex((obj: any) => obj.name === item.name);
|
configIndex.value = networkConfig[newTab.value.type].findIndex((obj: any) => obj.name === item.name);
|
||||||
visibleBody.value = true;
|
visibleBody.value = true;
|
||||||
};
|
};
|
||||||
const toggleProperty = async (item: any, tagData: string) => {
|
|
||||||
const type = getKeyByValue(typeCh, item.type);
|
|
||||||
const newData = { ...item };
|
|
||||||
newData[tagData] = !item[tagData];
|
|
||||||
if (type) {
|
|
||||||
newTab.value = { name: item.name, data: newData, type: type };
|
|
||||||
}
|
|
||||||
operateType.value = 'edit';
|
|
||||||
configIndex.value = networkConfig[newTab.value.type].findIndex((obj: any) => obj.name === item.name);
|
|
||||||
await saveConfig();
|
|
||||||
};
|
|
||||||
|
|
||||||
const delConfig = (item: any) => {
|
const delConfig = (item: any) => {
|
||||||
const type = getKeyByValue(typeCh, item.type);
|
const type = getKeyByValue(typeCh, item.type);
|
||||||
if (type) {
|
if (type) {
|
||||||
@@ -360,6 +252,7 @@ const selectType = (key: ComponentKey) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onloadDefault = (key: ComponentKey) => {
|
const onloadDefault = (key: ComponentKey) => {
|
||||||
|
console.log(key);
|
||||||
newTab.value.data = structuredClone(mergeNetworkDefaultConfig[key]);
|
newTab.value.data = structuredClone(mergeNetworkDefaultConfig[key]);
|
||||||
};
|
};
|
||||||
//检测重名
|
//检测重名
|
||||||
@@ -457,21 +350,22 @@ const loadConfig = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const copyText = async (text: string) => {
|
const copyText = async (text: string) => {
|
||||||
const textarea = document.createElement('textarea');
|
const input = document.createElement('input');
|
||||||
textarea.value = text;
|
input.value = text;
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(input);
|
||||||
textarea.select();
|
input.select();
|
||||||
try {
|
await navigator.clipboard.writeText(text);
|
||||||
document.execCommand('copy');
|
document.body.removeChild(input);
|
||||||
MessagePlugin.success('复制成功');
|
MessagePlugin.success('复制成功');
|
||||||
} catch (err) {
|
|
||||||
console.error('复制失败', err);
|
|
||||||
} finally {
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
|
// 得根据卡片宽度改,懒得改了;先不管了
|
||||||
|
// if(window.innerWidth < 540) {
|
||||||
|
// infoOneCol.value= true
|
||||||
|
// } else {
|
||||||
|
// infoOneCol.value= false
|
||||||
|
// }
|
||||||
tabsWidth.value = window.innerWidth - 41 - menuWidth.value;
|
tabsWidth.value = window.innerWidth - 41 - menuWidth.value;
|
||||||
if (mediumScreen.matches) {
|
if (mediumScreen.matches) {
|
||||||
cardWidth.value = (tabsWidth.value - 20) / 2;
|
cardWidth.value = (tabsWidth.value - 20) / 2;
|
||||||
@@ -483,19 +377,13 @@ const handleResize = () => {
|
|||||||
loadPage.value = true;
|
loadPage.value = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cardHeight.value = window.innerHeight - (headerBox.value?.offsetHeight ?? 0) - (setting.value?.offsetHeight ?? 0) - 21;
|
cardHeight.value = window.innerHeight - (headerBox.value?.offsetHeight ?? 0) - (setting.value?.offsetHeight ?? 0) - 21;
|
||||||
},300)
|
}, 300);
|
||||||
};
|
};
|
||||||
emitter.on('sendWidth', (width) => {
|
emitter.on('sendWidth', (width) => {
|
||||||
if (typeof width === 'string') {
|
if (typeof width === 'number' && !isNaN(width)) {
|
||||||
const strWidth = width as string;
|
menuWidth.value = width;
|
||||||
menuWidth.value = parseInt(strWidth);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
watch(menuWidth, (newValue, oldValue) => {
|
|
||||||
loadPage.value = false;
|
|
||||||
setTimeout(()=>{
|
|
||||||
handleResize();
|
handleResize();
|
||||||
},300)
|
}
|
||||||
});
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
@@ -504,20 +392,12 @@ onMounted(() => {
|
|||||||
menuWidth.value = parseInt(cachedWidth);
|
menuWidth.value = parseInt(cachedWidth);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handleResize();
|
handleResize();
|
||||||
},300)
|
}, 300);
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', ()=>{
|
window.addEventListener('resize', handleResize);
|
||||||
setTimeout(()=>{
|
|
||||||
handleResize();
|
|
||||||
},300)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', ()=>{
|
window.removeEventListener('resize', handleResize);
|
||||||
setTimeout(()=>{
|
|
||||||
handleResize();
|
|
||||||
},300)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -568,12 +448,14 @@ onUnmounted(() => {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.copy-icon {
|
.copy-icon {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.token-view {
|
.token-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -585,22 +467,11 @@ onUnmounted(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-item-on{
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
background-image: linear-gradient(to top, #0ba360 0%, #3cba92 100%) !important;
|
|
||||||
}
|
|
||||||
.tag-item-off{
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
background-image: linear-gradient(to top, rgba(255, 8, 68, 0.93) 0%, #D54941 100%) !important;
|
|
||||||
}
|
|
||||||
.browse-icon{
|
.browse-icon{
|
||||||
flex: 2;
|
flex: 2;
|
||||||
}
|
}
|
||||||
:global(.t-dialog__ctx .t-dialog__position) {
|
:global(.t-dialog__ctx .t-dialog--defaul) {
|
||||||
padding: 48px 10px;
|
margin: 0 20px;
|
||||||
}
|
}
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.setting-box {
|
.setting-box {
|
||||||
@@ -612,6 +483,7 @@ onUnmounted(() => {
|
|||||||
.setting-box {
|
.setting-box {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-box {
|
.card-box {
|
||||||
@@ -622,8 +494,9 @@ onUnmounted(() => {
|
|||||||
line-height: 400px !important;
|
line-height: 400px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.dialog-body {
|
.dialog-body {
|
||||||
max-height: 50vh;
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -642,6 +515,12 @@ onUnmounted(() => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-address .t-card__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-base-info .t-descriptions__header {
|
.setting-base-info .t-descriptions__header {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<t-divider content="其余配置" align="left">
|
<t-divider content="其余配置" align="left" />
|
||||||
<template #content>
|
|
||||||
<div style="display: flex; justify-content: center; align-items: center">
|
|
||||||
<setting-icon />
|
|
||||||
<div style="margin-left: 5px">其余配置</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</t-divider>
|
|
||||||
</div>
|
</div>
|
||||||
<t-card class="card">
|
<t-card class="card">
|
||||||
<div class="other-config-container">
|
<div class="other-config-container">
|
||||||
@@ -36,12 +29,11 @@ import { ref, onMounted } from 'vue';
|
|||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
import { MessagePlugin } from 'tdesign-vue-next';
|
||||||
import { OneBotConfig } from '../../../src/onebot/config/config';
|
import { OneBotConfig } from '../../../src/onebot/config/config';
|
||||||
import { QQLoginManager } from '@/backend/shell';
|
import { QQLoginManager } from '@/backend/shell';
|
||||||
import { SettingIcon } from 'tdesign-icons-vue-next';
|
|
||||||
|
|
||||||
const otherConfig = ref<Partial<OneBotConfig>>({
|
const otherConfig = ref<Partial<OneBotConfig>>({
|
||||||
musicSignUrl: '',
|
musicSignUrl: '',
|
||||||
enableLocalFile2Url: false,
|
enableLocalFile2Url: false,
|
||||||
parseMultMsg: true,
|
parseMultMsg: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelAlign = ref<string>();
|
const labelAlign = ref<string>();
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<t-form labelAlign="left">
|
<t-form labelAlign="left">
|
||||||
<t-form-item label="启用">
|
<t-form-item label="启用">
|
||||||
<t-switch v-model="config.enable" />
|
<t-checkbox v-model="config.enable" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="URL">
|
<t-form-item label="URL">
|
||||||
<t-input v-model="config.url" />
|
<t-input v-model="config.url" />
|
||||||
@@ -11,20 +11,20 @@
|
|||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="报告自身消息">
|
<t-form-item label="报告自身消息">
|
||||||
<t-switch v-model="config.reportSelfMessage" />
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="Token">
|
<t-form-item label="Token">
|
||||||
<t-input v-model="config.token" />
|
<t-input v-model="config.token" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="调试模式">
|
<t-form-item label="调试模式">
|
||||||
<t-switch v-model="config.debug" />
|
<t-checkbox v-model="config.debug" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
</t-form>
|
</t-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { defineProps, ref, watch } from 'vue';
|
||||||
import { HttpClientConfig } from '../../../../src/onebot/config/config';
|
import { HttpClientConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<t-form labelAlign="left">
|
<t-form labelAlign="left">
|
||||||
<t-form-item label="启用">
|
<t-form-item label="启用">
|
||||||
<t-switch v-model="config.enable" />
|
<t-checkbox v-model="config.enable" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="端口">
|
<t-form-item label="端口">
|
||||||
<t-input v-model.number="config.port" type="number" />
|
<t-input v-model.number="config.port" type="number" />
|
||||||
@@ -11,10 +11,10 @@
|
|||||||
<t-input v-model="config.host" type="text" />
|
<t-input v-model="config.host" type="text" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="启用 CORS">
|
<t-form-item label="启用 CORS">
|
||||||
<t-switch v-model="config.enableCors" />
|
<t-checkbox v-model="config.enableCors" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="启用 WS">
|
<t-form-item label="启用 WS">
|
||||||
<t-switch v-model="config.enableWebsocket" />
|
<t-checkbox v-model="config.enableWebsocket" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="消息格式">
|
<t-form-item label="消息格式">
|
||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
<t-input v-model="config.token" type="text" />
|
<t-input v-model="config.token" type="text" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="调试模式">
|
<t-form-item label="调试模式">
|
||||||
<t-switch v-model="config.debug" />
|
<t-checkbox v-model="config.debug" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
</t-form>
|
</t-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { defineProps, ref, watch } from 'vue';
|
||||||
import { HttpServerConfig } from '../../../../src/onebot/config/config';
|
import { HttpServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<t-form labelAlign="left">
|
|
||||||
<t-form-item label="启用">
|
|
||||||
<t-switch v-model="config.enable" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="端口">
|
|
||||||
<t-input v-model.number="config.port" type="number" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="主机">
|
|
||||||
<t-input v-model="config.host" type="text" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="报告自身消息">
|
|
||||||
<t-switch v-model="config.reportSelfMessage" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="启用 CORS">
|
|
||||||
<t-switch v-model="config.enableCors" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="启用 WS">
|
|
||||||
<t-switch v-model="config.enableWebsocket" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="消息格式">
|
|
||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="Token">
|
|
||||||
<t-input v-model="config.token" type="text" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="调试模式">
|
|
||||||
<t-switch v-model="config.debug" />
|
|
||||||
</t-form-item>
|
|
||||||
</t-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import { HttpSseServerConfig } from '../../../../src/onebot/config/config';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
config: HttpSseServerConfig;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const messageFormatOptions = ref([
|
|
||||||
{ label: 'Array', value: 'array' },
|
|
||||||
{ label: 'String', value: 'string' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.config.messagePostFormat,
|
|
||||||
(newValue) => {
|
|
||||||
if (newValue !== 'array' && newValue !== 'string') {
|
|
||||||
props.config.messagePostFormat = 'array';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<t-form labelAlign="left">
|
<t-form labelAlign="left">
|
||||||
<t-form-item label="启用">
|
<t-form-item label="启用">
|
||||||
<t-switch v-model="config.enable" />
|
<t-checkbox v-model="config.enable" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="URL">
|
<t-form-item label="URL">
|
||||||
<t-input v-model="config.url" />
|
<t-input v-model="config.url" />
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="报告自身消息">
|
<t-form-item label="报告自身消息">
|
||||||
<t-switch v-model="config.reportSelfMessage" />
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="Token">
|
<t-form-item label="Token">
|
||||||
<t-input v-model="config.token" />
|
<t-input v-model="config.token" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="调试模式">
|
<t-form-item label="调试模式">
|
||||||
<t-switch v-model="config.debug" />
|
<t-checkbox v-model="config.debug" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="心跳间隔">
|
<t-form-item label="心跳间隔">
|
||||||
<t-input v-model.number="config.heartInterval" type="number" />
|
<t-input v-model.number="config.heartInterval" type="number" />
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { defineProps, ref, watch } from 'vue';
|
||||||
import { WebsocketClientConfig } from '../../../../src/onebot/config/config';
|
import { WebsocketClientConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<t-form labelAlign="left">
|
<t-form labelAlign="left">
|
||||||
<t-form-item label="启用">
|
<t-form-item label="启用">
|
||||||
<t-switch v-model="config.enable" />
|
<t-checkbox v-model="config.enable" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="主机">
|
<t-form-item label="主机">
|
||||||
<t-input v-model="config.host" />
|
<t-input v-model="config.host" />
|
||||||
@@ -14,16 +14,16 @@
|
|||||||
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
<t-select v-model="config.messagePostFormat" :options="messageFormatOptions" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="上报自身消息">
|
<t-form-item label="上报自身消息">
|
||||||
<t-switch v-model="config.reportSelfMessage" />
|
<t-checkbox v-model="config.reportSelfMessage" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="Token">
|
<t-form-item label="Token">
|
||||||
<t-input v-model="config.token" />
|
<t-input v-model="config.token" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="强制推送事件">
|
<t-form-item label="强制推送事件">
|
||||||
<t-switch v-model="config.enableForcePushEvent" />
|
<t-checkbox v-model="config.enableForcePushEvent" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="调试模式">
|
<t-form-item label="调试模式">
|
||||||
<t-switch v-model="config.debug" />
|
<t-checkbox v-model="config.debug" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="心跳间隔">
|
<t-form-item label="心跳间隔">
|
||||||
<t-input v-model.number="config.heartInterval" type="number" />
|
<t-input v-model.number="config.heartInterval" type="number" />
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { defineProps, ref, watch } from 'vue';
|
||||||
import { WebsocketServerConfig } from '../../../../src/onebot/config/config';
|
import { WebsocketServerConfig } from '../../../../src/onebot/config/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@@ -7,8 +7,6 @@ import NetWork from '../pages/NetWork.vue';
|
|||||||
import QQLogin from '../components/QQLogin.vue';
|
import QQLogin from '../components/QQLogin.vue';
|
||||||
import WebUiLogin from '../components/WebUiLogin.vue';
|
import WebUiLogin from '../components/WebUiLogin.vue';
|
||||||
import OtherConfig from '../pages/OtherConfig.vue';
|
import OtherConfig from '../pages/OtherConfig.vue';
|
||||||
import { MessagePlugin } from 'tdesign-vue-next';
|
|
||||||
import { QQLoginManager } from '@/backend/shell';
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{ path: '/', redirect: '/webui' },
|
{ path: '/', redirect: '/webui' },
|
||||||
@@ -28,27 +26,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
|
||||||
const isPublicRoute = ['/webui', '/qqlogin'].includes(to.path);
|
|
||||||
const token = localStorage.getItem('auth');
|
|
||||||
|
|
||||||
if (!isPublicRoute) {
|
|
||||||
if (!token) {
|
|
||||||
MessagePlugin.error('请先登录');
|
|
||||||
return next('/webui');
|
|
||||||
}
|
|
||||||
const login = await new QQLoginManager(token).checkWebUiLogined();
|
|
||||||
if (!login) {
|
|
||||||
MessagePlugin.error('请先登录');
|
|
||||||
return next('/webui');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
|
@@ -3,15 +3,22 @@
|
|||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"jsxImportSource": "vue",
|
"jsxImportSource": "vue",
|
||||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
"lib": [
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"types": ["vite/client"],
|
"types": [
|
||||||
|
"vite/client"
|
||||||
|
],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
|
16
package.json
16
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.3.8",
|
"version": "4.2.12",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
@@ -13,21 +13,18 @@
|
|||||||
"dev:shell": "vite build --mode shell",
|
"dev:shell": "vite build --mode shell",
|
||||||
"dev:webui": "cd napcat.webui && npm run webui:dev",
|
"dev:webui": "cd napcat.webui && npm run webui:dev",
|
||||||
"lint": "eslint --fix src/**/*.{js,ts,vue}",
|
"lint": "eslint --fix src/**/*.{js,ts,vue}",
|
||||||
"depend": "cd dist && npm install --omit=dev",
|
"depend": "cd dist && npm install --omit=dev"
|
||||||
"dev:depend": "npm i && cd napcat.webui && npm i"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "0.24.0",
|
|
||||||
"@babel/preset-typescript": "^7.24.7",
|
"@babel/preset-typescript": "^7.24.7",
|
||||||
"@eslint/compat": "^1.2.2",
|
"@eslint/compat": "^1.2.2",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
"@napneko/nap-proto-core": "^0.0.4",
|
"@napneko/nap-proto-core": "^0.0.4",
|
||||||
"@rollup/plugin-typescript": "^12.1.2",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@sinclair/typebox": "^0.34.9",
|
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/fluent-ffmpeg": "^2.1.24",
|
"@types/fluent-ffmpeg": "^2.1.24",
|
||||||
"@types/node": "^22.0.1",
|
"@types/node": "^22.0.1",
|
||||||
@@ -37,7 +34,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
"ajv": "^8.13.0",
|
"ajv": "^8.13.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"commander": "^13.0.0",
|
"commander": "^12.1.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
@@ -51,7 +48,8 @@
|
|||||||
"vite": "^6.0.1",
|
"vite": "^6.0.1",
|
||||||
"vite-plugin-cp": "^4.0.8",
|
"vite-plugin-cp": "^4.0.8",
|
||||||
"vite-tsconfig-paths": "^5.1.0",
|
"vite-tsconfig-paths": "^5.1.0",
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0",
|
||||||
|
"@sinclair/typebox": "^0.34.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
|
@@ -1,107 +0,0 @@
|
|||||||
export type TaskExecutor<T> = (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void, onCancel: (callback: () => void) => void) => void;
|
|
||||||
|
|
||||||
export class CancelableTask<T> {
|
|
||||||
private promise: Promise<T>;
|
|
||||||
private cancelCallback: (() => void) | null = null;
|
|
||||||
private isCanceled = false;
|
|
||||||
private cancelListeners: Array<() => void> = [];
|
|
||||||
|
|
||||||
constructor(executor: TaskExecutor<T>) {
|
|
||||||
this.promise = new Promise<T>((resolve, reject) => {
|
|
||||||
const onCancel = (callback: () => void) => {
|
|
||||||
this.cancelCallback = callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
executor(
|
|
||||||
(value) => {
|
|
||||||
if (!this.isCanceled) {
|
|
||||||
resolve(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(reason) => {
|
|
||||||
if (!this.isCanceled) {
|
|
||||||
reject(reason);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onCancel
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancel() {
|
|
||||||
if (this.cancelCallback) {
|
|
||||||
this.cancelCallback();
|
|
||||||
}
|
|
||||||
this.isCanceled = true;
|
|
||||||
this.cancelListeners.forEach(listener => listener());
|
|
||||||
}
|
|
||||||
|
|
||||||
public isTaskCanceled(): boolean {
|
|
||||||
return this.isCanceled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public onCancel(listener: () => void) {
|
|
||||||
this.cancelListeners.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public then<TResult1 = T, TResult2 = never>(
|
|
||||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
|
||||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
|
||||||
): Promise<TResult1 | TResult2> {
|
|
||||||
return this.promise.then(onfulfilled, onrejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public catch<TResult = never>(
|
|
||||||
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
|
|
||||||
): Promise<T | TResult> {
|
|
||||||
return this.promise.catch(onrejected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public finally(onfinally?: (() => void) | undefined | null): Promise<T> {
|
|
||||||
return this.promise.finally(onfinally);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.asyncIterator]() {
|
|
||||||
return {
|
|
||||||
next: () => this.promise.then(value => ({ value, done: true })),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function demoAwait() {
|
|
||||||
const executor: TaskExecutor<number> = (resolve, reject, onCancel) => {
|
|
||||||
let count = 0;
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
count++;
|
|
||||||
console.log(`Task is running... Count: ${count}`);
|
|
||||||
if (count === 5) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(count);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
onCancel(() => {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
console.log('Task has been canceled.');
|
|
||||||
reject(new Error('Task was canceled'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const task = new CancelableTask(executor);
|
|
||||||
|
|
||||||
task.onCancel(() => {
|
|
||||||
console.log('Cancel listener triggered.');
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
task.cancel(); // 取消任务
|
|
||||||
}, 6000);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await task;
|
|
||||||
console.log(`Task completed with result: ${result}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Task failed:', error);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
// decoratorAsyncMethod(this,function,wrapper)
|
|
||||||
async function decoratorMethod<T, R>(
|
|
||||||
target: T,
|
|
||||||
method: () => Promise<R>,
|
|
||||||
wrapper: (result: R) => Promise<any>,
|
|
||||||
executeImmediately: boolean = true
|
|
||||||
): Promise<any> {
|
|
||||||
const execute = async () => {
|
|
||||||
try {
|
|
||||||
const result = await method.call(target);
|
|
||||||
return wrapper(result);
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (executeImmediately) {
|
|
||||||
return execute();
|
|
||||||
} else {
|
|
||||||
return execute;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,43 +0,0 @@
|
|||||||
type Handler<T> = () => T | Promise<T>;
|
|
||||||
type Checker<T> = (result: T) => T | Promise<T>;
|
|
||||||
|
|
||||||
export class Fallback<T> {
|
|
||||||
private handlers: Handler<T>[] = [];
|
|
||||||
private checker: Checker<T>;
|
|
||||||
|
|
||||||
constructor(checker?: Checker<T>) {
|
|
||||||
this.checker = checker || (async (result: T) => result);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(handler: Handler<T>): this {
|
|
||||||
this.handlers.push(handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行处理程序链
|
|
||||||
async run(): Promise<T> {
|
|
||||||
const errors: Error[] = [];
|
|
||||||
for (const handler of this.handlers) {
|
|
||||||
try {
|
|
||||||
const result = await handler();
|
|
||||||
const data = await this.checker(result);
|
|
||||||
if (data) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new AggregateError(errors, 'All handlers failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class FallbackUtil {
|
|
||||||
static boolchecker<T>(value: T, condition: boolean): T {
|
|
||||||
if (condition) {
|
|
||||||
return value;
|
|
||||||
} else {
|
|
||||||
throw new Error('Condition is false, throwing error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,121 +0,0 @@
|
|||||||
import { Peer } from '@/core';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
|
|
||||||
class TimeBasedCache<K, V> {
|
|
||||||
private cache = new Map<K, { value: V, timestamp: number, frequency: number }>();
|
|
||||||
private keyList = new Set<K>();
|
|
||||||
private operationCount = 0;
|
|
||||||
|
|
||||||
constructor(private maxCapacity: number, private ttl: number = 30 * 1000 * 60, private cleanupCount: number = 10) {}
|
|
||||||
|
|
||||||
public put(key: K, value: V): void {
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const cacheEntry = { value, timestamp, frequency: 1 };
|
|
||||||
this.cache.set(key, cacheEntry);
|
|
||||||
this.keyList.add(key);
|
|
||||||
this.operationCount++;
|
|
||||||
if (this.keyList.size > this.maxCapacity) this.evict();
|
|
||||||
if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(key: K): V | undefined {
|
|
||||||
const entry = this.cache.get(key);
|
|
||||||
if (entry && Date.now() - entry.timestamp < this.ttl) {
|
|
||||||
entry.timestamp = Date.now();
|
|
||||||
entry.frequency++;
|
|
||||||
this.operationCount++;
|
|
||||||
if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount);
|
|
||||||
return entry.value;
|
|
||||||
} else {
|
|
||||||
this.deleteKey(key);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private cleanup(count: number): void {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
let cleaned = 0;
|
|
||||||
for (const key of this.keyList) {
|
|
||||||
if (cleaned >= count) break;
|
|
||||||
const entry = this.cache.get(key);
|
|
||||||
if (entry && currentTime - entry.timestamp >= this.ttl) {
|
|
||||||
this.deleteKey(key);
|
|
||||||
cleaned++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.operationCount = 0; // 重置操作计数器
|
|
||||||
}
|
|
||||||
|
|
||||||
private deleteKey(key: K): void {
|
|
||||||
this.cache.delete(key);
|
|
||||||
this.keyList.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private evict(): void {
|
|
||||||
while (this.keyList.size > this.maxCapacity) {
|
|
||||||
let oldestKey: K | undefined;
|
|
||||||
let minFrequency = Infinity;
|
|
||||||
for (const key of this.keyList) {
|
|
||||||
const entry = this.cache.get(key);
|
|
||||||
if (entry && entry.frequency < minFrequency) {
|
|
||||||
minFrequency = entry.frequency;
|
|
||||||
oldestKey = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldestKey !== undefined) this.deleteKey(oldestKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FileUUIDData {
|
|
||||||
peer: Peer;
|
|
||||||
modelId?: string;
|
|
||||||
fileId?: string;
|
|
||||||
msgId?: string;
|
|
||||||
elementId?: string;
|
|
||||||
fileUUID?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileUUIDManager {
|
|
||||||
private cache: TimeBasedCache<string, FileUUIDData>;
|
|
||||||
|
|
||||||
constructor(ttl: number) {
|
|
||||||
this.cache = new TimeBasedCache<string, FileUUIDData>(5000, ttl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public encode(data: FileUUIDData, endString: string = "", customUUID?: string): string {
|
|
||||||
const uuid = customUUID ? customUUID : randomUUID().replace(/-/g, '') + endString;
|
|
||||||
this.cache.put(uuid, data);
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public decode(uuid: string): FileUUIDData | undefined {
|
|
||||||
return this.cache.get(uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FileNapCatOneBotUUIDWrap {
|
|
||||||
private manager: FileUUIDManager;
|
|
||||||
|
|
||||||
constructor(ttl: number = 86400000) {
|
|
||||||
this.manager = new FileUUIDManager(ttl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = "", customUUID?: string): string {
|
|
||||||
return this.manager.encode({ peer, modelId, fileId, fileUUID }, endString, customUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decodeModelId(uuid: string): FileUUIDData | undefined {
|
|
||||||
return this.manager.decode(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", customUUID?: string): string {
|
|
||||||
return this.manager.encode({ peer, msgId, elementId, fileUUID }, "", customUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public decode(uuid: string): FileUUIDData | undefined {
|
|
||||||
return this.manager.decode(uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FileNapCatOneBotUUID = new FileNapCatOneBotUUIDWrap();
|
|
@@ -1,7 +1,9 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { stat } from 'fs/promises';
|
import { stat } from 'fs/promises';
|
||||||
import crypto, { randomUUID } from 'crypto';
|
import crypto, { randomUUID } from 'crypto';
|
||||||
|
import util from 'util';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import * as fileType from 'file-type';
|
||||||
import { solveProblem } from '@/common/helper';
|
import { solveProblem } from '@/common/helper';
|
||||||
|
|
||||||
export interface HttpDownloadOptions {
|
export interface HttpDownloadOptions {
|
||||||
@@ -13,6 +15,7 @@ type Uri2LocalRes = {
|
|||||||
success: boolean,
|
success: boolean,
|
||||||
errMsg: string,
|
errMsg: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
|
ext: string,
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +73,27 @@ async function checkFile(path: string): Promise<void> {
|
|||||||
// 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身
|
// 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function file2base64(path: string) {
|
||||||
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
const result = {
|
||||||
|
err: '',
|
||||||
|
data: '',
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
await checkFileExist(path, 5000);
|
||||||
|
} catch (e: any) {
|
||||||
|
result.err = e.toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const data = await readFile(path);
|
||||||
|
result.data = data.toString('base64');
|
||||||
|
} catch (err: any) {
|
||||||
|
result.err = err.toString();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function calculateFileMD5(filePath: string): Promise<string> {
|
export function calculateFileMD5(filePath: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 创建一个流式读取器
|
// 创建一个流式读取器
|
||||||
@@ -136,6 +160,20 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
|||||||
return Buffer.from(buffer);
|
return Buffer.from(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkFileV2(filePath: string) {
|
||||||
|
try {
|
||||||
|
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||||
|
if (ext) {
|
||||||
|
fs.renameSync(filePath, filePath + `.${ext}`);
|
||||||
|
filePath += `.${ext}`;
|
||||||
|
return { success: true, ext: ext, path: filePath };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// log("获取文件类型失败", filePath,e.stack)
|
||||||
|
}
|
||||||
|
return { success: false, ext: '', path: filePath };
|
||||||
|
}
|
||||||
|
|
||||||
export enum FileUriType {
|
export enum FileUriType {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Local = 1,
|
Local = 1,
|
||||||
@@ -175,34 +213,63 @@ export async function checkUriType(Uri: string) {
|
|||||||
return { Uri: Uri, Type: FileUriType.Unknown };
|
return { Uri: Uri, Type: FileUriType.Unknown };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uriToLocalFile(dir: string, uri: string, filename: string = randomUUID(), headers?: Record<string, string>): Promise<Uri2LocalRes> {
|
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
||||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||||
|
|
||||||
const filePath = path.join(dir, filename);
|
//解析失败
|
||||||
|
const tempName = randomUUID();
|
||||||
|
if (!filename) filename = randomUUID();
|
||||||
|
|
||||||
switch (UriType) {
|
//解析Http和Https协议
|
||||||
case FileUriType.Local: {
|
if (UriType == FileUriType.Unknown) {
|
||||||
|
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析File协议和本地文件
|
||||||
|
if (UriType == FileUriType.Local) {
|
||||||
const fileExt = path.extname(HandledUri);
|
const fileExt = path.extname(HandledUri);
|
||||||
const localFileName = path.basename(HandledUri, fileExt) + fileExt;
|
let filename = path.basename(HandledUri, fileExt);
|
||||||
const tempFilePath = path.join(dir, filename + fileExt);
|
filename += fileExt;
|
||||||
fs.copyFileSync(HandledUri, tempFilePath);
|
//复制文件到临时文件并保持后缀
|
||||||
return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath };
|
const filenameTemp = tempName + fileExt;
|
||||||
|
const filePath = path.join(dir, filenameTemp);
|
||||||
|
fs.copyFileSync(HandledUri, filePath);
|
||||||
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||||
}
|
}
|
||||||
|
|
||||||
case FileUriType.Remote: {
|
//接下来都要有文件名
|
||||||
const buffer = await httpDownload({ url: HandledUri, headers: headers });
|
if (UriType == FileUriType.Remote) {
|
||||||
|
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||||
|
if (pathInfo.name) {
|
||||||
|
const pathlen = 200 - dir.length - pathInfo.name.length;
|
||||||
|
filename = pathlen > 0 ? pathInfo.name.substring(0, pathlen) : pathInfo.name.substring(pathInfo.name.length, pathInfo.name.length - 10);//过长截断
|
||||||
|
if (pathInfo.ext) {
|
||||||
|
filename += pathInfo.ext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
|
||||||
|
const fileExt = path.extname(HandledUri).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
|
||||||
|
const filePath = path.join(dir, tempName + fileExt);
|
||||||
|
const buffer = await httpDownload(HandledUri);
|
||||||
|
//没有文件就创建
|
||||||
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
||||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||||
}
|
}
|
||||||
|
|
||||||
case FileUriType.Base64: {
|
//解析Base64
|
||||||
|
if (UriType == FileUriType.Base64) {
|
||||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||||
const base64Buffer = Buffer.from(base64, 'base64');
|
const buffer = Buffer.from(base64, 'base64');
|
||||||
fs.writeFileSync(filePath, base64Buffer, { flag: 'wx' });
|
let filePath = path.join(dir, filename);
|
||||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
let fileExt = '';
|
||||||
|
fs.writeFileSync(filePath, buffer);
|
||||||
|
const { success, ext, path: fileTypePath } = await checkFileV2(filePath);
|
||||||
|
if (success) {
|
||||||
|
filePath = fileTypePath;
|
||||||
|
fileExt = ext;
|
||||||
|
filename = filename + '.' + ext;
|
||||||
}
|
}
|
||||||
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||||
default:
|
|
||||||
return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' };
|
|
||||||
}
|
}
|
||||||
|
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { QQLevel } from '@/core';
|
import { Peer, QQLevel } from '@/core';
|
||||||
|
|
||||||
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
|
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
|
||||||
return new Promise<ReturnType<T> | undefined>((resolve) => {
|
return new Promise<ReturnType<T> | undefined>((resolve) => {
|
||||||
@@ -24,6 +24,81 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FileNapCatOneBotUUID {
|
||||||
|
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
|
||||||
|
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
|
||||||
|
//前四个字节塞data长度
|
||||||
|
const length = Buffer.alloc(4 + data.length);
|
||||||
|
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
|
||||||
|
length.write(data, 4);
|
||||||
|
return length.toString('hex') + endString;
|
||||||
|
}
|
||||||
|
|
||||||
|
static decodeModelId(uuid: string): undefined | {
|
||||||
|
peer: Peer,
|
||||||
|
modelId: string,
|
||||||
|
fileId: string,
|
||||||
|
fileUUID?: string
|
||||||
|
} {
|
||||||
|
//前四个字节是data长度
|
||||||
|
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||||
|
//根据length计算需要读取的长度
|
||||||
|
const dataId = uuid.slice(8, 8 + length);
|
||||||
|
//hex还原为string
|
||||||
|
const realData = Buffer.from(dataId, 'hex').toString();
|
||||||
|
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
||||||
|
const data = realData.split('|');
|
||||||
|
if (data.length < 6) return undefined; // compatibility requirement
|
||||||
|
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
|
||||||
|
return {
|
||||||
|
peer: {
|
||||||
|
chatType: +chatType,
|
||||||
|
peerUid: peerUid,
|
||||||
|
},
|
||||||
|
modelId,
|
||||||
|
fileId,
|
||||||
|
fileUUID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
|
||||||
|
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
|
||||||
|
//前四个字节塞data长度
|
||||||
|
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
|
||||||
|
const length = Buffer.alloc(4 + data.length);
|
||||||
|
length.writeUInt32BE(data.length * 2, 0);
|
||||||
|
length.write(data, 4);
|
||||||
|
return length.toString('hex') + endString;
|
||||||
|
}
|
||||||
|
|
||||||
|
static decode(uuid: string): undefined | {
|
||||||
|
peer: Peer,
|
||||||
|
msgId: string,
|
||||||
|
elementId: string,
|
||||||
|
fileUUID?: string
|
||||||
|
} {
|
||||||
|
//前四个字节是data长度
|
||||||
|
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||||
|
//根据length计算需要读取的长度
|
||||||
|
const dataId = uuid.slice(8, 8 + length);
|
||||||
|
//hex还原为string
|
||||||
|
const realData = Buffer.from(dataId, 'hex').toString();
|
||||||
|
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
||||||
|
const data = realData.split('|');
|
||||||
|
if (data.length < 6) return undefined; // compatibility requirement
|
||||||
|
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
|
||||||
|
return {
|
||||||
|
peer: {
|
||||||
|
chatType: +chatType,
|
||||||
|
peerUid: peerUid,
|
||||||
|
},
|
||||||
|
msgId,
|
||||||
|
elementId,
|
||||||
|
fileUUID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function sleep(ms: number): Promise<void> {
|
export function sleep(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
@@ -78,7 +78,7 @@ class MessageUniqueWrapper {
|
|||||||
private readonly msgDataMap: LimitedHashTable<string, number>;
|
private readonly msgDataMap: LimitedHashTable<string, number>;
|
||||||
private readonly msgIdMap: LimitedHashTable<string, number>;
|
private readonly msgIdMap: LimitedHashTable<string, number>;
|
||||||
|
|
||||||
constructor(maxMap: number = 5000) {
|
constructor(maxMap: number = 1000) {
|
||||||
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
this.msgIdMap = new LimitedHashTable<string, number>(maxMap);
|
||||||
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
|
this.msgDataMap = new LimitedHashTable<string, number>(maxMap);
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.3.8';
|
export const napCatVersion = '4.2.12';
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
Peer,
|
Peer,
|
||||||
PicElement,
|
PicElement,
|
||||||
PicSubType,
|
PicSubType,
|
||||||
|
PicType,
|
||||||
RawMessage,
|
RawMessage,
|
||||||
SendFileElement,
|
SendFileElement,
|
||||||
SendPicElement,
|
SendPicElement,
|
||||||
@@ -16,7 +17,7 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import fsPromises from 'fs/promises';
|
import fsPromises from 'fs/promises';
|
||||||
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
|
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
|
||||||
import { fileTypeFromFile } from 'file-type';
|
import * as fileType from 'file-type';
|
||||||
import imageSize from 'image-size';
|
import imageSize from 'image-size';
|
||||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||||
import { RkeyManager } from '@/core/helper/rkey';
|
import { RkeyManager } from '@/core/helper/rkey';
|
||||||
@@ -61,7 +62,7 @@ export class NTQQFileApi {
|
|||||||
|
|
||||||
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
|
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
|
||||||
const fileMd5 = await calculateFileMD5(filePath);
|
const fileMd5 = await calculateFileMD5(filePath);
|
||||||
const extOrEmpty = await fileTypeFromFile(filePath).then(e => e?.ext ?? '').catch(e => '');
|
const extOrEmpty = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||||
const ext = extOrEmpty ? `.${extOrEmpty}` : '';
|
const ext = extOrEmpty ? `.${extOrEmpty}` : '';
|
||||||
let fileName = `${path.basename(filePath)}`;
|
let fileName = `${path.basename(filePath)}`;
|
||||||
if (fileName.indexOf('.') === -1) {
|
if (fileName.indexOf('.') === -1) {
|
||||||
@@ -157,7 +158,7 @@ export class NTQQFileApi {
|
|||||||
|
|
||||||
let fileExt = 'mp4';
|
let fileExt = 'mp4';
|
||||||
try {
|
try {
|
||||||
const tempExt = (await fileTypeFromFile(filePath))?.ext;
|
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||||
if (tempExt) fileExt = tempExt;
|
if (tempExt) fileExt = tempExt;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError('获取文件类型失败', e);
|
this.context.logger.logError('获取文件类型失败', e);
|
||||||
@@ -462,7 +463,7 @@ export class NTQQFileApi {
|
|||||||
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
rkeyData.private_rkey = tempRkeyData.private_rkey;
|
||||||
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logDebug('获取rkey失败 Fallback Old Mode', e);
|
this.context.logger.logError('获取rkey失败 Fallback Old Mode', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { FriendRequest, FriendV2 } from '@/core/types';
|
import { FriendV2 } from '@/core/types';
|
||||||
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
|
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
|
||||||
import { LimitedHashTable } from '@/common/message-unique';
|
import { LimitedHashTable } from '@/common/message-unique';
|
||||||
|
|
||||||
@@ -79,10 +79,16 @@ export class NTQQFriendApi {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleFriendRequest(notify: FriendRequest, accept: boolean) {
|
async handleFriendRequest(flag: string, accept: boolean) {
|
||||||
|
const data = flag.split('|');
|
||||||
|
if (data.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const friendUid = data[0];
|
||||||
|
const reqTime = data[1];
|
||||||
this.context.session.getBuddyService()?.approvalFriendRequest({
|
this.context.session.getBuddyService()?.approvalFriendRequest({
|
||||||
friendUid: notify.friendUid,
|
friendUid: friendUid,
|
||||||
reqTime: notify.reqTime,
|
reqTime: reqTime,
|
||||||
accept,
|
accept,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
GeneralCallResult,
|
GeneralCallResult,
|
||||||
|
Group,
|
||||||
GroupMember,
|
GroupMember,
|
||||||
NTGroupMemberRole,
|
NTGroupMemberRole,
|
||||||
NTGroupRequestOperateTypes,
|
NTGroupRequestOperateTypes,
|
||||||
@@ -7,8 +8,6 @@ import {
|
|||||||
KickMemberV2Req,
|
KickMemberV2Req,
|
||||||
MemberExtSourceType,
|
MemberExtSourceType,
|
||||||
NapCatCore,
|
NapCatCore,
|
||||||
GroupNotify,
|
|
||||||
GroupInfoSource,
|
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { isNumeric, solveAsyncProblem } from '@/common/helper';
|
import { isNumeric, solveAsyncProblem } from '@/common/helper';
|
||||||
import { LimitedHashTable } from '@/common/message-unique';
|
import { LimitedHashTable } from '@/common/message-unique';
|
||||||
@@ -17,35 +16,35 @@ import { NTEventWrapper } from '@/common/event';
|
|||||||
export class NTQQGroupApi {
|
export class NTQQGroupApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
|
groupCache: Map<string, Group> = new Map<string, Group>();
|
||||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||||
|
groups: Group[] = [];
|
||||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||||
|
session: any;
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchGroupDetail(groupCode: string) {
|
|
||||||
const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2(
|
|
||||||
'NodeIKernelGroupService/getGroupDetailInfo',
|
|
||||||
'NodeIKernelGroupListener/onGroupDetailInfoChange',
|
|
||||||
[groupCode, GroupInfoSource.KDATACARD],
|
|
||||||
(ret) => ret.result === 0,
|
|
||||||
(detailInfo) => detailInfo.groupCode === groupCode,
|
|
||||||
1,
|
|
||||||
5000
|
|
||||||
);
|
|
||||||
return detailInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
async initApi() {
|
async initApi() {
|
||||||
this.initCache().then().catch(e => this.context.logger.logError(e));
|
this.initCache().then().catch(e => this.context.logger.logError(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
async initCache() {
|
async initCache() {
|
||||||
for (const group of await this.getGroups(true)) {
|
this.groups = await this.getGroups();
|
||||||
this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e));
|
for (const group of this.groups) {
|
||||||
|
this.groupCache.set(group.groupCode, group);
|
||||||
|
this.refreshGroupMemberCache(group.groupCode).then().catch(e => this.context.logger.logError(e));
|
||||||
}
|
}
|
||||||
|
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||||
|
// process.pid 调试点
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCoreAndBaseInfo(uids: string[]) {
|
||||||
|
return await this.core.eventWrapper.callNoListenerEvent(
|
||||||
|
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||||
|
'nodeStore',
|
||||||
|
uids,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchGroupEssenceList(groupCode: string) {
|
async fetchGroupEssenceList(groupCode: string) {
|
||||||
@@ -63,15 +62,15 @@ export class NTQQGroupApi {
|
|||||||
return (await data)[1];
|
return (await data)[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearGroupNotifiesUnreadCount(doubt: boolean) {
|
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(doubt);
|
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setGroupAvatar(groupCode: string, filePath: string) {
|
async setGroupAvatar(gc: string, filePath: string) {
|
||||||
return this.context.session.getGroupService().setHeader(groupCode, filePath);
|
return this.context.session.getGroupService().setHeader(gc, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroups(forced: boolean = false) {
|
async getGroups(forced = false) {
|
||||||
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getGroupList',
|
'NodeIKernelGroupService/getGroupList',
|
||||||
'NodeIKernelGroupListener/onGroupListUpdate',
|
'NodeIKernelGroupListener/onGroupListUpdate',
|
||||||
@@ -80,9 +79,9 @@ export class NTQQGroupApi {
|
|||||||
return groupList;
|
return groupList;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupExtFE0Info(groupCodes: Array<string>, forced = true) {
|
async getGroupExtFE0Info(groupCode: string[], forced = true) {
|
||||||
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
||||||
groupCodes,
|
groupCode,
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
bindGuildId: 1,
|
bindGuildId: 1,
|
||||||
@@ -122,51 +121,81 @@ export class NTQQGroupApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGroup(groupCode: string, forced = false) {
|
||||||
|
let group = this.groupCache.get(groupCode.toString());
|
||||||
|
if (!group) {
|
||||||
|
try {
|
||||||
|
const groupList = await this.getGroups(forced);
|
||||||
|
if (groupList.length) {
|
||||||
|
groupList.forEach(g => {
|
||||||
|
this.groupCache.set(g.groupCode, g);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group = this.groupCache.get(groupCode.toString());
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
async getGroupMemberAll(groupCode: string, forced = false) {
|
async getGroupMemberAll(groupCode: string, forced = false) {
|
||||||
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshGroupMemberCache(groupCode: string, isWait = true) {
|
async refreshGroupMemberCache(groupCode: string) {
|
||||||
const updateCache = async () => {
|
|
||||||
try {
|
try {
|
||||||
const members = await this.getGroupMemberAll(groupCode, true);
|
const members = await this.getGroupMemberAll(groupCode, true);
|
||||||
|
// 首先填入基础信息
|
||||||
|
const existingMembers = this.groupMemberCache.get(groupCode) ?? new Map<string, GroupMember>();
|
||||||
|
members.result.infos.forEach((value, key) => {
|
||||||
|
existingMembers.set(value.uid, { ...value, ...existingMembers.get(value.uid) });
|
||||||
|
});
|
||||||
|
// 后台补全复杂信息
|
||||||
|
let event = (async () => {
|
||||||
|
let data = (await Promise.allSettled(members.result.ids.map(e => this.core.apis.UserApi.getUserDetailInfo(e.uid)))).filter(e => e.status === 'fulfilled').map(e => e.value);
|
||||||
|
data.forEach(e => {
|
||||||
|
const existingMember = members.result.infos.get(e.uid);
|
||||||
|
if (existingMember) {
|
||||||
|
members.result.infos.set(e.uid, { ...existingMember, ...e });
|
||||||
|
}
|
||||||
|
});
|
||||||
this.groupMemberCache.set(groupCode, members.result.infos);
|
this.groupMemberCache.set(groupCode, members.result.infos);
|
||||||
|
})().then().catch(e => this.context.logger.logError(e));
|
||||||
|
// 处理首次空缺
|
||||||
|
if (!this.groupMemberCache.get(groupCode)) {
|
||||||
|
await event;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`);
|
this.context.logger.logError(`刷新群成员缓存失败, ${e}`);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (isWait) {
|
|
||||||
await updateCache();
|
|
||||||
} else {
|
|
||||||
updateCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.groupMemberCache.get(groupCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
||||||
const groupCodeStr = groupCode.toString();
|
const groupCodeStr = groupCode.toString();
|
||||||
const memberUinOrUidStr = memberUinOrUid.toString();
|
const memberUinOrUidStr = memberUinOrUid.toString();
|
||||||
|
|
||||||
// 获取群成员缓存
|
// 检查群缓存
|
||||||
let members = this.groupMemberCache.get(groupCodeStr);
|
let members = this.groupMemberCache.get(groupCodeStr);
|
||||||
if (!members) {
|
if (!members) {
|
||||||
members = (await this.refreshGroupMemberCache(groupCodeStr, true));
|
await this.refreshGroupMemberCache(groupCodeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMember = () => {
|
function getMember() {
|
||||||
|
let member: GroupMember | undefined;
|
||||||
if (isNumeric(memberUinOrUidStr)) {
|
if (isNumeric(memberUinOrUidStr)) {
|
||||||
return Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
||||||
} else {
|
} else {
|
||||||
return members!.get(memberUinOrUidStr);
|
member = members!.get(memberUinOrUidStr);
|
||||||
}
|
}
|
||||||
};
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let member = getMember();
|
let member = getMember();
|
||||||
// 如果缓存中不存在该成员,尝试刷新缓存
|
// 不存在群友缓存 尝试刷新
|
||||||
if (!member) {
|
if (!member) {
|
||||||
members = (await this.refreshGroupMemberCache(groupCodeStr, true));
|
await this.refreshGroupMemberCache(groupCode.toString());
|
||||||
member = getMember();
|
member = getMember();
|
||||||
}
|
}
|
||||||
return member;
|
return member;
|
||||||
@@ -176,26 +205,26 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async creatGroupFileFolder(groupCode: string, folderName: string) {
|
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
||||||
return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName);
|
return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delGroupFile(groupCode: string, files: Array<string>) {
|
async DelGroupFile(groupCode: string, files: string[]) {
|
||||||
return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files);
|
return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delGroupFileFolder(groupCode: string, folderId: string) {
|
async DelGroupFileFolder(groupCode: string, folderId: string) {
|
||||||
return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId);
|
return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addGroupEssence(groupCode: string, msgId: string) {
|
async addGroupEssence(GroupCode: string, msgId: string) {
|
||||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||||
chatType: 2,
|
chatType: 2,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
peerUid: groupCode,
|
peerUid: GroupCode,
|
||||||
}, msgId, 1, false);
|
}, msgId, 1, false);
|
||||||
const param = {
|
const param = {
|
||||||
groupCode: groupCode,
|
groupCode: GroupCode,
|
||||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||||
};
|
};
|
||||||
@@ -206,9 +235,9 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().kickMemberV2(param);
|
return this.context.session.getGroupService().kickMemberV2(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteGroupBulletin(groupCode: string, noticeId: string) {
|
async deleteGroupBulletin(GroupCode: string, noticeId: string) {
|
||||||
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||||
return this.context.session.getGroupService().deleteGroupBulletin(groupCode, psKey, noticeId);
|
return this.context.session.getGroupService().deleteGroupBulletin(GroupCode, psKey, noticeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
||||||
@@ -219,37 +248,37 @@ export class NTQQGroupApi {
|
|||||||
return this.context.session.getGroupService().quitGroupV2(param);
|
return this.context.session.getGroupService().quitGroupV2(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeGroupEssenceBySeq(groupCode: string, msgRandom: string, msgSeq: string) {
|
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
|
||||||
const param = {
|
const param = {
|
||||||
groupCode: groupCode,
|
groupCode: GroupCode,
|
||||||
msgRandom: parseInt(msgRandom),
|
msgRandom: parseInt(msgRandom),
|
||||||
msgSeq: parseInt(msgSeq),
|
msgSeq: parseInt(msgSeq),
|
||||||
};
|
};
|
||||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeGroupEssence(groupCode: string, msgId: string) {
|
async removeGroupEssence(GroupCode: string, msgId: string) {
|
||||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||||
chatType: 2,
|
chatType: 2,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
peerUid: groupCode,
|
peerUid: GroupCode,
|
||||||
}, msgId, 1, false);
|
}, msgId, 1, false);
|
||||||
const param = {
|
const param = {
|
||||||
groupCode: groupCode,
|
groupCode: GroupCode,
|
||||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||||
};
|
};
|
||||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSingleScreenNotifies(doubt: boolean, count: number) {
|
async getSingleScreenNotifies(doubt: boolean, num: number) {
|
||||||
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getSingleScreenNotifies',
|
'NodeIKernelGroupService/getSingleScreenNotifies',
|
||||||
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
||||||
[
|
[
|
||||||
doubt,
|
doubt,
|
||||||
'',
|
'',
|
||||||
count,
|
num,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
return notifies;
|
return notifies;
|
||||||
@@ -273,89 +302,95 @@ export class NTQQGroupApi {
|
|||||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupMemberEx(groupCode: string, uid: string, forced: boolean = false, retry: number = 2) {
|
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||||
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||||
return eventWrapper.callNormalEventV2(
|
return eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getMemberInfo',
|
'NodeIKernelGroupService/getMemberInfo',
|
||||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||||
[groupCode, [uid], forced],
|
[GroupCode, [uid], forced],
|
||||||
(ret) => ret.result === 0,
|
(ret) => ret.result === 0,
|
||||||
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
||||||
1,
|
1,
|
||||||
forced ? 2500 : 250
|
forced ? 2500 : 250
|
||||||
);
|
);
|
||||||
}, this.core.eventWrapper, groupCode, uid, forced);
|
}, this.core.eventWrapper, GroupCode, uid, forced);
|
||||||
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
if (data && data[3] instanceof Map && data[3].has(uid)) {
|
||||||
return data[3].get(uid);
|
return data[3].get(uid);
|
||||||
}
|
}
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
const trydata = await this.getGroupMemberEx(groupCode, uid, true, retry - 1) as GroupMember | undefined;
|
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||||
if (trydata) return trydata;
|
if (trydata) return trydata;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupFileCount(groupCodes: Array<string>) {
|
async getGroupFileCount(group_ids: Array<string>) {
|
||||||
return this.context.session.getRichMediaService().batchGetGroupFileCount(groupCodes);
|
return this.context.session.getRichMediaService().batchGetGroupFileCount(group_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getArkJsonGroupShare(groupCode: string) {
|
async getArkJsonGroupShare(GroupCode: string) {
|
||||||
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
||||||
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
||||||
groupCode,
|
GroupCode,
|
||||||
) as GeneralCallResult & { arkJson: string };
|
) as GeneralCallResult & { arkJson: string };
|
||||||
return ret.arkJson;
|
return ret.arkJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadGroupBulletinPic(groupCode: string, imageurl: string) {
|
//需要异常处理
|
||||||
|
async uploadGroupBulletinPic(GroupCode: string, imageurl: string) {
|
||||||
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||||
return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl);
|
return this.context.session.getGroupService().uploadGroupBulletinPic(GroupCode, _Pskey, imageurl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleGroupRequest(notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
async handleGroupRequest(flag: string, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||||||
|
const flagitem = flag.split('|');
|
||||||
|
const groupCode = flagitem[0];
|
||||||
|
const seq = flagitem[1];
|
||||||
|
const type = parseInt(flagitem[2]);
|
||||||
|
|
||||||
return this.context.session.getGroupService().operateSysNotify(
|
return this.context.session.getGroupService().operateSysNotify(
|
||||||
false,
|
false,
|
||||||
{
|
{
|
||||||
operateType: operateType,
|
operateType: operateType,
|
||||||
targetMsg: {
|
targetMsg: {
|
||||||
seq: notify.seq, // 通知序列号
|
seq: seq, // 通知序列号
|
||||||
type: notify.type,
|
type: type,
|
||||||
groupCode: notify.group.groupCode,
|
groupCode: groupCode,
|
||||||
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async quitGroup(groupCode: string) {
|
async quitGroup(groupQQ: string) {
|
||||||
return this.context.session.getGroupService().quitGroup(groupCode);
|
return this.context.session.getGroupService().quitGroup(groupQQ);
|
||||||
}
|
}
|
||||||
|
|
||||||
async kickMember(groupCode: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
||||||
return this.context.session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason);
|
return this.context.session.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
||||||
// timeStamp为秒数, 0为解除禁言
|
// timeStamp为秒数, 0为解除禁言
|
||||||
return this.context.session.getGroupService().setMemberShutUp(groupCode, memList);
|
return this.context.session.getGroupService().setMemberShutUp(groupQQ, memList);
|
||||||
}
|
}
|
||||||
|
|
||||||
async banGroup(groupCode: string, shutUp: boolean) {
|
async banGroup(groupQQ: string, shutUp: boolean) {
|
||||||
return this.context.session.getGroupService().setGroupShutUp(groupCode, shutUp);
|
return this.context.session.getGroupService().setGroupShutUp(groupQQ, shutUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMemberCard(groupCode: string, memberUid: string, cardName: string) {
|
async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
||||||
return this.context.session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName);
|
return this.context.session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMemberRole(groupCode: string, memberUid: string, role: NTGroupMemberRole) {
|
async setMemberRole(groupQQ: string, memberUid: string, role: NTGroupMemberRole) {
|
||||||
return this.context.session.getGroupService().modifyMemberRole(groupCode, memberUid, role);
|
return this.context.session.getGroupService().modifyMemberRole(groupQQ, memberUid, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setGroupName(groupCode: string, groupName: string) {
|
async setGroupName(groupQQ: string, groupName: string) {
|
||||||
return this.context.session.getGroupService().modifyGroupName(groupCode, groupName, false);
|
return this.context.session.getGroupService().modifyGroupName(groupQQ, groupName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async publishGroupBulletin(groupCode: string, content: string, picInfo: {
|
async publishGroupBulletin(groupQQ: string, content: string, picInfo: {
|
||||||
id: string,
|
id: string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number
|
||||||
@@ -369,11 +404,11 @@ export class NTQQGroupApi {
|
|||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
confirmRequired: confirmRequired,
|
confirmRequired: confirmRequired,
|
||||||
};
|
};
|
||||||
return this.context.session.getGroupService().publishGroupBulletin(groupCode, psKey!, data);
|
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, psKey!, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupRemainAtTimes(groupCode: string) {
|
async getGroupRemainAtTimes(GroupCode: string) {
|
||||||
return this.context.session.getGroupService().getGroupRemainAtTimes(groupCode);
|
return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||||
|
25
src/core/apis/sign.ts
Normal file
25
src/core/apis/sign.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { InstanceContext, NapCatCore } from '..';
|
||||||
|
|
||||||
|
export class NTQQMusicSignApi {
|
||||||
|
context: InstanceContext;
|
||||||
|
core: NapCatCore;
|
||||||
|
|
||||||
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
|
this.context = context;
|
||||||
|
this.core = core;
|
||||||
|
}
|
||||||
|
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
|
||||||
|
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
|
||||||
|
|
||||||
|
//外域名不行得走qgroup中转
|
||||||
|
//https://proxy.gtimg.cn/tx_tls_gate=y.qq.com/music/photo_new/T002R800x800M000000y5gq7449K9I.jpg
|
||||||
|
|
||||||
|
//可外域名
|
||||||
|
//https://pic.ugcimg.cn/500955bdd6657ecc8e82e02d2df06800/jpg1
|
||||||
|
|
||||||
|
//QQ音乐gtimg接口
|
||||||
|
//https://y.gtimg.cn/music/photo_new/T002R800x800M000000y5gq7449K9I.jpg?max_age=2592000
|
||||||
|
|
||||||
|
//还有一处公告上传可以上传高质量图片 持久为qq域名
|
||||||
|
}
|
||||||
|
|
@@ -2,7 +2,6 @@ import { ModifyProfileParams, User, UserDetailSource } from '@/core/types';
|
|||||||
import { RequestUtil } from '@/common/request';
|
import { RequestUtil } from '@/common/request';
|
||||||
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
|
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
|
||||||
import { solveAsyncProblem } from '@/common/helper';
|
import { solveAsyncProblem } from '@/common/helper';
|
||||||
import { Fallback, FallbackUtil } from '@/common/fall-back';
|
|
||||||
|
|
||||||
export class NTQQUserApi {
|
export class NTQQUserApi {
|
||||||
context: InstanceContext;
|
context: InstanceContext;
|
||||||
@@ -12,15 +11,13 @@ export class NTQQUserApi {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
|
//self_tind格式
|
||||||
async getCoreAndBaseInfo(uids: string[]) {
|
async createUidFromTinyId(tinyId: string) {
|
||||||
return await this.core.eventWrapper.callNoListenerEvent(
|
return this.context.session.getMsgService().createUidFromTinyId(this.core.selfInfo.uin, tinyId);
|
||||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
}
|
||||||
'nodeStore',
|
async getStatusByUid(uid: string) {
|
||||||
uids,
|
return this.context.session.getProfileService().getStatus(uid);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认获取自己的 type = 2 获取别人 type = 1
|
// 默认获取自己的 type = 2 获取别人 type = 1
|
||||||
async getProfileLike(uid: string, start: number, count: number, type: number = 2) {
|
async getProfileLike(uid: string, start: number, count: number, type: number = 2) {
|
||||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||||
@@ -107,19 +104,6 @@ export class NTQQUserApi {
|
|||||||
return retUser;
|
return retUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserDetailInfoV2(uid: string): Promise<User> {
|
|
||||||
const fallback = new Fallback<User>((user) => FallbackUtil.boolchecker(user, user !== undefined && user.uin !== '0'))
|
|
||||||
.add(() => this.fetchUserDetailInfo(uid, UserDetailSource.KDB))
|
|
||||||
.add(() => this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER));
|
|
||||||
const retUser = await fallback.run().then(async (user) => {
|
|
||||||
if (user && user.uin === '0') {
|
|
||||||
user.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0';
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
});
|
|
||||||
return retUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
async modifySelfProfile(param: ModifyProfileParams) {
|
async modifySelfProfile(param: ModifyProfileParams) {
|
||||||
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
|
||||||
}
|
}
|
||||||
@@ -177,39 +161,35 @@ export class NTQQUserApi {
|
|||||||
if (!skey) {
|
if (!skey) {
|
||||||
throw new Error('SKey is Empty');
|
throw new Error('SKey is Empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
return skey;
|
return skey;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUidByUinV2(uin: string) {
|
//后期改成流水线处理
|
||||||
if (!uin) {
|
async getUidByUinV2(Uin: string) {
|
||||||
return '';
|
let uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
|
||||||
|
if (uid) return uid;
|
||||||
|
uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
|
||||||
|
if (uid) return uid;
|
||||||
|
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
||||||
|
if (uid) return uid;
|
||||||
|
const unverifiedUid = (await this.getUserDetailInfoByUin(Uin)).detail.uid;//从QQ Native 特殊转换
|
||||||
|
if (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
|
||||||
|
//if (uid) return uid;
|
||||||
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback =
|
//后期改成流水线处理
|
||||||
new Fallback<string | undefined>((uid) => FallbackUtil.boolchecker(uid, uid !== undefined && uid.indexOf('*') === -1 && uid !== ''))
|
async getUinByUidV2(Uid: string) {
|
||||||
.add(() => this.context.session.getUixConvertService().getUid([uin]).then((data) => data.uidInfo.get(uin)))
|
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
|
||||||
.add(() => this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [uin]).get(uin))
|
if (uin && uin !== '0') return uin;
|
||||||
.add(() => this.context.session.getGroupService().getUidByUins([uin]).then((data) => data.uids.get(uin)))
|
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
|
||||||
.add(() => this.getUserDetailInfoByUin(uin).then((data) => data.detail.uid));
|
if (uin && uin !== '0') return uin;
|
||||||
|
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
|
||||||
const uid = await fallback.run().catch(() => '');
|
if (uin && uin !== '0') return uin;
|
||||||
return uid ?? '';
|
uin = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getKey(Uid);
|
||||||
}
|
if (uin && uin !== '0') return uin;
|
||||||
|
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
||||||
async getUinByUidV2(uid: string) {
|
return uin;
|
||||||
if (!uid) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
const fallback = new Fallback<string | undefined>((uin) => FallbackUtil.boolchecker(uin, uin !== undefined && uin !== '0' && uin !== ''))
|
|
||||||
.add(() => this.context.session.getUixConvertService().getUin([uid]).then((data) => data.uinInfo.get(uid)))
|
|
||||||
.add(() => this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [uid]).get(uid))
|
|
||||||
.add(() => this.context.session.getGroupService().getUinByUids([uid]).then((data) => data.uins.get(uid)))
|
|
||||||
.add(() => this.getUserDetailInfo(uid).then((data) => data.uin));
|
|
||||||
|
|
||||||
const uin = await fallback.run().catch(() => '0');
|
|
||||||
return uin ?? '0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecentContactListSnapShot(count: number) {
|
async getRecentContactListSnapShot(count: number) {
|
||||||
|
@@ -8,7 +8,7 @@ import {
|
|||||||
WebHonorType,
|
WebHonorType,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { NapCatCore } from '..';
|
import { NapCatCore } from '..';
|
||||||
import { readFileSync } from 'node:fs';
|
import { createReadStream, readFileSync, statSync } from 'node:fs';
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import { basename } from 'node:path';
|
import { basename } from 'node:path';
|
||||||
|
|
||||||
@@ -366,4 +366,50 @@ export class NTQQWebApi {
|
|||||||
|
|
||||||
return post;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
80
src/core/external/appid.json
vendored
80
src/core/external/appid.json
vendored
@@ -98,85 +98,5 @@
|
|||||||
"6.9.61-29927": {
|
"6.9.61-29927": {
|
||||||
"appid": 537255836,
|
"appid": 537255836,
|
||||||
"qua": "V1_MAC_NQ_6.9.61_29927_GW_B"
|
"qua": "V1_MAC_NQ_6.9.61_29927_GW_B"
|
||||||
},
|
|
||||||
"9.9.17-30366": {
|
|
||||||
"appid": 537258389,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_30366_GW_B"
|
|
||||||
},
|
|
||||||
"3.2.15-30366": {
|
|
||||||
"appid": 537258413,
|
|
||||||
"qua": "V1_LNX_NQ_3.2.15_30366_GW_B"
|
|
||||||
},
|
|
||||||
"6.9.62-30366": {
|
|
||||||
"appid": 537258401,
|
|
||||||
"qua": "V1_MAC_NQ_6.9.62_30366_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-30483": {
|
|
||||||
"appid": 537258439,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_30483_GW_B"
|
|
||||||
},
|
|
||||||
"6.9.62-30483": {
|
|
||||||
"appid": 537258463,
|
|
||||||
"qua": "V1_MAC_NQ_6.9.62_30483_GW_B"
|
|
||||||
},
|
|
||||||
"3.2.15-30483": {
|
|
||||||
"appid": 537258474,
|
|
||||||
"qua": "V1_LNX_NQ_3.2.15_30483_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-30594": {
|
|
||||||
"appid": 537258439,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_30594_GW_B"
|
|
||||||
},
|
|
||||||
"6.9.62-30594": {
|
|
||||||
"appid": 537258463,
|
|
||||||
"qua": "V1_MAC_NQ_6.9.62_30594_GW_B"
|
|
||||||
},
|
|
||||||
"3.2.15-30594": {
|
|
||||||
"appid": 537258474,
|
|
||||||
"qua": "V1_LNX_NQ_3.2.15_30594_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-30851": {
|
|
||||||
"appid": 537263796,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_30851_GW_B"
|
|
||||||
},
|
|
||||||
"3.2.15-30851": {
|
|
||||||
"appid": 537263831,
|
|
||||||
"qua": "V1_LNX_NQ_3.2.15_30851_GW_B"
|
|
||||||
},
|
|
||||||
"6.9.63-30851": {
|
|
||||||
"appid": 537263820,
|
|
||||||
"qua": "V1_MAC_NQ_6.9.63_30851_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-30899": {
|
|
||||||
"appid": 537263796,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_30899_GW_B"
|
|
||||||
},
|
|
||||||
"3.2.15-30899": {
|
|
||||||
"appid": 537263831,
|
|
||||||
"qua": "V1_LNX_NQ_3.2.15_30899_GW_B"
|
|
||||||
},
|
|
||||||
"6.9.63-30899": {
|
|
||||||
"appid": 537263820,
|
|
||||||
"qua": "V1_MAC_NQ_6.9.63_30899_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-31219": {
|
|
||||||
"appid": 537266450,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_31219_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-31245": {
|
|
||||||
"appid": 537266450,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_31245_GW_B"
|
|
||||||
},
|
|
||||||
"3.2.15-31245": {
|
|
||||||
"appid": 537266485,
|
|
||||||
"qua": "V1_LNX_NQ_3.2.15_31245_GW_B"
|
|
||||||
},
|
|
||||||
"6.9.63-31245": {
|
|
||||||
"appid": 537266474,
|
|
||||||
"qua": "V1_MAC_NQ_6.9.63_31245_GW_B"
|
|
||||||
},
|
|
||||||
"9.9.17-31363": {
|
|
||||||
"appid": 537266500,
|
|
||||||
"qua": "V1_WIN_NQ_9.9.17_31363_GW_B"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
128
src/core/external/offset.json
vendored
128
src/core/external/offset.json
vendored
@@ -102,133 +102,5 @@
|
|||||||
"6.9.61-29927-arm64": {
|
"6.9.61-29927-arm64": {
|
||||||
"send": "4038740",
|
"send": "4038740",
|
||||||
"recv": "403AF58"
|
"recv": "403AF58"
|
||||||
},
|
|
||||||
"9.9.17-30366-x64": {
|
|
||||||
"send": "39AB0B0",
|
|
||||||
"recv": "39AF4E4"
|
|
||||||
},
|
|
||||||
"3.2.15-30366-x64": {
|
|
||||||
"send": "A402380",
|
|
||||||
"recv": "A405C80"
|
|
||||||
},
|
|
||||||
"3.2.15-30366-arm64": {
|
|
||||||
"send": "70C3FA8",
|
|
||||||
"recv": "70C77E0"
|
|
||||||
},
|
|
||||||
"6.9.62-30366-x64": {
|
|
||||||
"send": "4669760",
|
|
||||||
"recv": "466BFCC"
|
|
||||||
},
|
|
||||||
"6.9.62-30366-arm64": {
|
|
||||||
"send": "4189770",
|
|
||||||
"recv": "418BF88"
|
|
||||||
},
|
|
||||||
"9.9.17-30483-x64": {
|
|
||||||
"send": "39AC1B0",
|
|
||||||
"recv": "39B05E4"
|
|
||||||
},
|
|
||||||
"6.9.62-30483-arm64": {
|
|
||||||
"send": "41896B0",
|
|
||||||
"recv": "418bec8"
|
|
||||||
},
|
|
||||||
"6.9.62-30483-x64": {
|
|
||||||
"send": "4669460",
|
|
||||||
"recv": "466BCCC"
|
|
||||||
},
|
|
||||||
"3.2.15-30483-x64": {
|
|
||||||
"send": "A402540",
|
|
||||||
"recv": "A405E40"
|
|
||||||
},
|
|
||||||
"3.2.15-30483-arm64": {
|
|
||||||
"send": "70C40E8",
|
|
||||||
"recv": "70C7920"
|
|
||||||
},
|
|
||||||
"9.9.17-30594-x64": {
|
|
||||||
"send": "39AC1B0",
|
|
||||||
"recv": "39B05E4"
|
|
||||||
},
|
|
||||||
"6.9.62-30594-arm64": {
|
|
||||||
"send": "41896B0",
|
|
||||||
"recv": "418bec8"
|
|
||||||
},
|
|
||||||
"6.9.62-30594-x64": {
|
|
||||||
"send": "4669460",
|
|
||||||
"recv": "466BCCC"
|
|
||||||
},
|
|
||||||
"3.2.15-30594-x64": {
|
|
||||||
"send": "A402540",
|
|
||||||
"recv": "A405E40"
|
|
||||||
},
|
|
||||||
"3.2.15-30594-arm64": {
|
|
||||||
"send": "70C40E8",
|
|
||||||
"recv": "70C7920"
|
|
||||||
},
|
|
||||||
"9.9.17-30851-x64": {
|
|
||||||
"send": "395C150",
|
|
||||||
"recv": "3960584"
|
|
||||||
},
|
|
||||||
"3.2.15-30851-x64": {
|
|
||||||
"send": "A4A03E0",
|
|
||||||
"recv": "A4A3CE0"
|
|
||||||
},
|
|
||||||
"3.2.15-30851-arm64": {
|
|
||||||
"send": "713A318",
|
|
||||||
"recv": "713DB50"
|
|
||||||
},
|
|
||||||
"6.9.63.30851-x64": {
|
|
||||||
"send": "46C8040",
|
|
||||||
"recv": "46CA8AC"
|
|
||||||
},
|
|
||||||
"6.9.63-30851-arm64": {
|
|
||||||
"send": "41DCBD8",
|
|
||||||
"recv": "41DF3F0"
|
|
||||||
},
|
|
||||||
"9.9.17-30899-x64": {
|
|
||||||
"send": "395C150",
|
|
||||||
"recv": "3960584"
|
|
||||||
},
|
|
||||||
"3.2.15-30899-x64": {
|
|
||||||
"send": "A4A03E0",
|
|
||||||
"recv": "A4A3CE0"
|
|
||||||
},
|
|
||||||
"3.2.15-30899-arm64": {
|
|
||||||
"send": "713A318",
|
|
||||||
"recv": "713DB50"
|
|
||||||
},
|
|
||||||
"6.9.63.30899-x64": {
|
|
||||||
"send": "46C8040",
|
|
||||||
"recv": "46CA8AC"
|
|
||||||
},
|
|
||||||
"6.9.63-30899-arm64": {
|
|
||||||
"send": "41DCBD8",
|
|
||||||
"recv": "41DF3F0"
|
|
||||||
},
|
|
||||||
"9.9.17-31219-x64": {
|
|
||||||
"send": "39C1350",
|
|
||||||
"recv": "39C5784"
|
|
||||||
},
|
|
||||||
"9.9.17-31245-x64": {
|
|
||||||
"send": "39C1350",
|
|
||||||
"recv": "39C5784"
|
|
||||||
},
|
|
||||||
"6.9.63.31245-x64": {
|
|
||||||
"send": "4720A40",
|
|
||||||
"recv": "47232AC"
|
|
||||||
},
|
|
||||||
"6.9.63-31245-arm64": {
|
|
||||||
"send": "41DCBD8",
|
|
||||||
"recv": "422D4E8"
|
|
||||||
},
|
|
||||||
"3.2.15-31245-x64": {
|
|
||||||
"send": "A550F80",
|
|
||||||
"recv": "A554880"
|
|
||||||
},
|
|
||||||
"3.2.15-31245-arm64": {
|
|
||||||
"send": "71BEBB8",
|
|
||||||
"recv": "71C23F0"
|
|
||||||
},
|
|
||||||
"9.9.17-31363-x64": {
|
|
||||||
"send": "39C1910",
|
|
||||||
"recv": "39C5d44"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
49
src/core/helper/adaptSysMessageDecoder.ts
Normal file
49
src/core/helper/adaptSysMessageDecoder.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// TODO: further refactor in NapCat.Packet v2
|
||||||
|
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
|
|
||||||
|
const BodyInner = {
|
||||||
|
msgType: ProtoField(1, ScalarType.UINT32, true),
|
||||||
|
subType: ProtoField(2, ScalarType.UINT32, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoifyData = {
|
||||||
|
skip: ProtoField(1, ScalarType.BYTES, true),
|
||||||
|
innerData: ProtoField(2, ScalarType.BYTES, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
const MsgHead = {
|
||||||
|
bodyInner: ProtoField(2, () => BodyInner, true),
|
||||||
|
noifyData: ProtoField(3, () => NoifyData, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
const Message = {
|
||||||
|
msgHead: ProtoField(1, () => MsgHead)
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubDetail = {
|
||||||
|
msgSeq: ProtoField(1, ScalarType.UINT32),
|
||||||
|
msgTime: ProtoField(2, ScalarType.UINT32),
|
||||||
|
senderUid: ProtoField(6, ScalarType.STRING)
|
||||||
|
};
|
||||||
|
|
||||||
|
const RecallDetails = {
|
||||||
|
operatorUid: ProtoField(1, ScalarType.STRING),
|
||||||
|
subDetail: ProtoField(3, () => SubDetail)
|
||||||
|
};
|
||||||
|
|
||||||
|
const RecallGroup = {
|
||||||
|
type: ProtoField(1, ScalarType.INT32),
|
||||||
|
peerUid: ProtoField(4, ScalarType.UINT32),
|
||||||
|
recallDetails: ProtoField(11, () => RecallDetails),
|
||||||
|
grayTipsSeq: ProtoField(37, ScalarType.UINT32)
|
||||||
|
};
|
||||||
|
|
||||||
|
export function decodeMessage(buffer: Uint8Array) {
|
||||||
|
const msg = new NapProtoMsg(Message);
|
||||||
|
return msg.decode(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeRecallGroup(buffer: Uint8Array){
|
||||||
|
const msg = new NapProtoMsg(RecallGroup);
|
||||||
|
return msg.decode(buffer);
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { fileTypeFromFile } from 'file-type';
|
import * as fileType from 'file-type';
|
||||||
import { PicType } from '../types';
|
import { PicType } from '../types';
|
||||||
export async function getFileTypeForSendType(picPath: string): Promise<PicType> {
|
export async function getFileTypeForSendType(picPath: string): Promise<PicType> {
|
||||||
const fileTypeResult = (await fileTypeFromFile(picPath))?.ext ?? 'jpg';
|
const fileTypeResult = (await fileType.fileTypeFromFile(picPath))?.ext ?? 'jpg';
|
||||||
const picTypeMap: { [key: string]: PicType } = {
|
const picTypeMap: { [key: string]: PicType } = {
|
||||||
//'webp': PicType.NEWPIC_WEBP,
|
//'webp': PicType.NEWPIC_WEBP,
|
||||||
'gif': PicType.NEWPIC_GIF,
|
'gif': PicType.NEWPIC_GIF,
|
||||||
|
@@ -15,10 +15,6 @@ export class RkeyManager {
|
|||||||
private_rkey: '',
|
private_rkey: '',
|
||||||
expired_time: 0,
|
expired_time: 0,
|
||||||
};
|
};
|
||||||
private failureCount: number = 0;
|
|
||||||
private lastFailureTimestamp: number = 0;
|
|
||||||
private readonly FAILURE_LIMIT: number = 8;
|
|
||||||
private readonly ONE_DAY: number = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
constructor(serverUrl: string[], logger: LogWrapper) {
|
constructor(serverUrl: string[], logger: LogWrapper) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -26,21 +22,11 @@ export class RkeyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getRkey() {
|
async getRkey() {
|
||||||
const now = new Date().getTime();
|
|
||||||
if (now - this.lastFailureTimestamp > this.ONE_DAY) {
|
|
||||||
this.failureCount = 0; // 重置失败计数器
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.failureCount >= this.FAILURE_LIMIT) {
|
|
||||||
this.logger.logError(`[Rkey] 服务存在异常, 图片使用FallBack机制`);
|
|
||||||
throw new Error('获取rkey失败次数过多,请稍后再试');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isExpired()) {
|
if (this.isExpired()) {
|
||||||
try {
|
try {
|
||||||
await this.refreshRkey();
|
await this.refreshRkey();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`${e}`);//外抛
|
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.rkeyData;
|
return this.rkeyData;
|
||||||
@@ -48,6 +34,7 @@ export class RkeyManager {
|
|||||||
|
|
||||||
isExpired(): boolean {
|
isExpired(): boolean {
|
||||||
const now = new Date().getTime() / 1000;
|
const now = new Date().getTime() / 1000;
|
||||||
|
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`);
|
||||||
return now > this.rkeyData.expired_time;
|
return now > this.rkeyData.expired_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,17 +48,14 @@ export class RkeyManager {
|
|||||||
private_rkey: temp.private_rkey.slice(6),
|
private_rkey: temp.private_rkey.slice(6),
|
||||||
expired_time: temp.expired_time
|
expired_time: temp.expired_time
|
||||||
};
|
};
|
||||||
this.failureCount = 0;
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.logError(`[Rkey] 异常服务 ${url} 异常 / `, e);
|
this.logger.logError(`[Rkey] Get Rkey ${url} Error `, e);
|
||||||
this.failureCount++;
|
|
||||||
this.lastFailureTimestamp = new Date().getTime();
|
|
||||||
//是否为最后一个url
|
//是否为最后一个url
|
||||||
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
if (url === this.serverUrl[this.serverUrl.length - 1]) {
|
||||||
throw new Error(`获取rkey失败: ${e}`);//外抛
|
throw new Error(`获取rkey失败: ${e}`);//外抛
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,138 +0,0 @@
|
|||||||
import os from "node:os";
|
|
||||||
import EventEmitter from "node:events";
|
|
||||||
|
|
||||||
export interface SystemStatus {
|
|
||||||
cpu: {
|
|
||||||
model: string,
|
|
||||||
speed: string
|
|
||||||
usage: {
|
|
||||||
system: string
|
|
||||||
qq: string
|
|
||||||
},
|
|
||||||
core: number
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
total: string
|
|
||||||
usage: {
|
|
||||||
system: string
|
|
||||||
qq: string
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arch: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StatusHelper {
|
|
||||||
private psCpuUsage = process.cpuUsage();
|
|
||||||
private psCurrentTime = process.hrtime();
|
|
||||||
private cpuTimes = os.cpus().map(cpu => cpu.times);
|
|
||||||
|
|
||||||
private replaceNaN(value: number) {
|
|
||||||
return isNaN(value) ? 0 : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sysCpuInfo() {
|
|
||||||
const currentTimes = os.cpus().map(cpu => cpu.times);
|
|
||||||
const { total, active } = currentTimes.map((times, index) => {
|
|
||||||
const prevTimes = this.cpuTimes[index];
|
|
||||||
const totalCurrent = times.user + times.nice + times.sys + times.idle + times.irq;
|
|
||||||
const totalPrev = prevTimes.user + prevTimes.nice + prevTimes.sys + prevTimes.idle + prevTimes.irq;
|
|
||||||
const activeCurrent = totalCurrent - times.idle;
|
|
||||||
const activePrev = totalPrev - prevTimes.idle;
|
|
||||||
return {
|
|
||||||
total: totalCurrent - totalPrev,
|
|
||||||
active: activeCurrent - activePrev
|
|
||||||
};
|
|
||||||
}).reduce((acc, cur) => ({
|
|
||||||
total: acc.total + cur.total,
|
|
||||||
active: acc.active + cur.active
|
|
||||||
}), { total: 0, active: 0 });
|
|
||||||
this.cpuTimes = currentTimes;
|
|
||||||
return {
|
|
||||||
usage: this.replaceNaN(((active / total) * 100)).toFixed(2),
|
|
||||||
model: os.cpus()[0].model,
|
|
||||||
speed: os.cpus()[0].speed,
|
|
||||||
core: os.cpus().length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private sysMemoryUsage() {
|
|
||||||
const { total, free } = { total: os.totalmem(), free: os.freemem() };
|
|
||||||
return ((total - free) / 1024 / 1024).toFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private qqUsage() {
|
|
||||||
const mem = process.memoryUsage();
|
|
||||||
const numCpus = os.cpus().length;
|
|
||||||
const usageDiff = process.cpuUsage(this.psCpuUsage);
|
|
||||||
const endTime = process.hrtime(this.psCurrentTime);
|
|
||||||
this.psCpuUsage = process.cpuUsage();
|
|
||||||
this.psCurrentTime = process.hrtime();
|
|
||||||
const usageMS = (usageDiff.user + usageDiff.system) / 1e3;
|
|
||||||
const totalMS = endTime[0] * 1e3 + endTime[1] / 1e6;
|
|
||||||
const normPercent = (usageMS / totalMS / numCpus) * 100;
|
|
||||||
return {
|
|
||||||
cpu: this.replaceNaN(normPercent).toFixed(2),
|
|
||||||
memory: ((mem.heapTotal + mem.external + mem.arrayBuffers) / 1024 / 1024).toFixed(2)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
systemStatus(): SystemStatus {
|
|
||||||
const qqUsage = this.qqUsage();
|
|
||||||
const sysCpuInfo = this.sysCpuInfo();
|
|
||||||
return {
|
|
||||||
cpu: {
|
|
||||||
core: sysCpuInfo.core,
|
|
||||||
model: sysCpuInfo.model,
|
|
||||||
speed: (sysCpuInfo.speed / 1000).toFixed(2),
|
|
||||||
usage: {
|
|
||||||
system: sysCpuInfo.usage,
|
|
||||||
qq: qqUsage.cpu
|
|
||||||
},
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
total: (os.totalmem() / 1024 / 1024).toFixed(2),
|
|
||||||
usage: {
|
|
||||||
system: this.sysMemoryUsage(),
|
|
||||||
qq: qqUsage.memory
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arch: `${os.platform()} ${os.arch()} ${os.release()}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StatusHelperSubscription extends EventEmitter {
|
|
||||||
private statusHelper: StatusHelper;
|
|
||||||
private interval: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
constructor(time: number = 3000) {
|
|
||||||
super();
|
|
||||||
this.statusHelper = new StatusHelper();
|
|
||||||
this.on('newListener', (event: string) => {
|
|
||||||
if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) {
|
|
||||||
this.startInterval(time);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.on('removeListener', (event: string) => {
|
|
||||||
if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) {
|
|
||||||
this.stopInterval();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private startInterval(time: number) {
|
|
||||||
this.interval ??= setInterval(() => {
|
|
||||||
const status = this.statusHelper.systemStatus();
|
|
||||||
this.emit('statusUpdate', status);
|
|
||||||
}, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopInterval() {
|
|
||||||
if (this.interval) {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
this.interval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const statusHelperSubscription = new StatusHelperSubscription();
|
|
@@ -152,7 +152,6 @@ export class NapCatCore {
|
|||||||
// Renamed from 'InitDataListener'
|
// Renamed from 'InitDataListener'
|
||||||
async initNapCatCoreListeners() {
|
async initNapCatCoreListeners() {
|
||||||
const msgListener = new NodeIKernelMsgListener();
|
const msgListener = new NodeIKernelMsgListener();
|
||||||
|
|
||||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||||
// 下线通知
|
// 下线通知
|
||||||
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { DataSource, Group, GroupDetailInfo, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/types';
|
import { DataSource, Group, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/types';
|
||||||
|
|
||||||
export class NodeIKernelGroupListener {
|
export class NodeIKernelGroupListener {
|
||||||
onGroupListInited(listEmpty: boolean): any { }
|
onGroupListInited(listEmpty: boolean): any { }
|
||||||
@@ -28,7 +28,7 @@ export class NodeIKernelGroupListener {
|
|||||||
onGroupConfMemberChange(...args: unknown[]): any {
|
onGroupConfMemberChange(...args: unknown[]): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
onGroupDetailInfoChange(detailInfo: GroupDetailInfo): any {
|
onGroupDetailInfoChange(...args: unknown[]): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
onGroupExtListUpdate(...args: unknown[]): any {
|
onGroupExtListUpdate(...args: unknown[]): any {
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { LRUCache } from "@/common/lru-cache";
|
import { LRUCache } from "@/common/lru-cache";
|
||||||
import crypto, { createHash } from "crypto";
|
import crypto, { createHash } from "crypto";
|
||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { OidbPacket, PacketHexStr } from "@/core/packet/transformer/base";
|
import { OidbPacket, PacketHexStr } from "@/core/packet/transformer/base";
|
||||||
import { LogStack } from "@/core/packet/context/clientContext";
|
import { LogStack } from "@/core/packet/context/clientContext";
|
||||||
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
|
||||||
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
|
||||||
|
|
||||||
export interface RecvPacket {
|
export interface RecvPacket {
|
||||||
type: string, // 仅recv
|
type: string, // 仅recv
|
||||||
@@ -28,15 +27,13 @@ function randText(len: number): string {
|
|||||||
|
|
||||||
|
|
||||||
export abstract class IPacketClient {
|
export abstract class IPacketClient {
|
||||||
protected readonly napcore: NapCoreContext;
|
protected readonly context: PacketContext;
|
||||||
protected readonly logger: PacketLogger;
|
|
||||||
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||||
logStack: LogStack;
|
logStack: LogStack;
|
||||||
available: boolean = false;
|
available: boolean = false;
|
||||||
|
|
||||||
protected constructor(napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) {
|
protected constructor(context: PacketContext, logStack: LogStack) {
|
||||||
this.napcore = napCore;
|
this.context = context;
|
||||||
this.logger = logger;
|
|
||||||
this.logStack = logStack;
|
this.logStack = logStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +81,7 @@ export abstract class IPacketClient {
|
|||||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||||
const trace_id = (randText(4) + md5 + data).slice(0, data.length / 2);
|
const trace_id = (randText(4) + md5 + data).slice(0, data.length / 2);
|
||||||
return this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
return this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
||||||
await this.napcore.sendSsoCmdReqByContend(cmd, trace_id);
|
await this.context.napcore.sendSsoCmdReqByContend(cmd, trace_id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,9 +5,8 @@ import fs from "fs";
|
|||||||
import { IPacketClient } from "@/core/packet/client/baseClient";
|
import { IPacketClient } from "@/core/packet/client/baseClient";
|
||||||
import { constants } from "node:os";
|
import { constants } from "node:os";
|
||||||
import { LRUCache } from "@/common/lru-cache";
|
import { LRUCache } from "@/common/lru-cache";
|
||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { LogStack } from "@/core/packet/context/clientContext";
|
import { LogStack } from "@/core/packet/context/clientContext";
|
||||||
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
|
||||||
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
|
||||||
|
|
||||||
// 0 send 1 recv
|
// 0 send 1 recv
|
||||||
export interface NativePacketExportType {
|
export interface NativePacketExportType {
|
||||||
@@ -20,8 +19,8 @@ export class NativePacketClient extends IPacketClient {
|
|||||||
private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
||||||
private readonly sendEvent = new LRUCache<number, string>(500); // seq->trace_id
|
private readonly sendEvent = new LRUCache<number, string>(500); // seq->trace_id
|
||||||
|
|
||||||
constructor(napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) {
|
constructor(context: PacketContext, logStack: LogStack) {
|
||||||
super(napCore, logger, logStack);
|
super(context, logStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
check(): boolean {
|
check(): boolean {
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { Data, WebSocket, ErrorEvent } from "ws";
|
import { Data, WebSocket, ErrorEvent } from "ws";
|
||||||
import { IPacketClient, RecvPacket } from "@/core/packet/client/baseClient";
|
import { IPacketClient, RecvPacket } from "@/core/packet/client/baseClient";
|
||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { LogStack } from "@/core/packet/context/clientContext";
|
import { LogStack } from "@/core/packet/context/clientContext";
|
||||||
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
|
||||||
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
|
||||||
|
|
||||||
export class WsPacketClient extends IPacketClient {
|
export class WsPacketClient extends IPacketClient {
|
||||||
private websocket: WebSocket | null = null;
|
private websocket: WebSocket | null = null;
|
||||||
@@ -14,15 +13,15 @@ export class WsPacketClient extends IPacketClient {
|
|||||||
private isInitialized: boolean = false;
|
private isInitialized: boolean = false;
|
||||||
private initPayload: { pid: number, recv: string, send: string } | null = null;
|
private initPayload: { pid: number, recv: string, send: string } | null = null;
|
||||||
|
|
||||||
constructor(napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) {
|
constructor(context: PacketContext, logStack: LogStack) {
|
||||||
super(napCore, logger, logStack);
|
super(context, logStack);
|
||||||
this.clientUrl = this.napcore.config.packetServer
|
this.clientUrl = this.context.napcore.config.packetServer
|
||||||
? this.clientUrlWrap(this.napcore.config.packetServer)
|
? this.clientUrlWrap(this.context.napcore.config.packetServer)
|
||||||
: this.clientUrlWrap('127.0.0.1:8083');
|
: this.clientUrlWrap('127.0.0.1:8083');
|
||||||
}
|
}
|
||||||
|
|
||||||
check(): boolean {
|
check(): boolean {
|
||||||
if (!this.napcore.config.packetServer) {
|
if (!this.context.napcore.config.packetServer) {
|
||||||
this.logStack.pushLogWarn(`wsPacketClient 未配置服务器地址`);
|
this.logStack.pushLogWarn(`wsPacketClient 未配置服务器地址`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -68,7 +67,7 @@ export class WsPacketClient extends IPacketClient {
|
|||||||
this.websocket.onopen = () => {
|
this.websocket.onopen = () => {
|
||||||
this.available = true;
|
this.available = true;
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.logger.info(`wsPacketClient 已连接到 ${this.clientUrl}`);
|
this.context.logger.info(`wsPacketClient 已连接到 ${this.clientUrl}`);
|
||||||
if (!this.isInitialized && this.initPayload) {
|
if (!this.isInitialized && this.initPayload) {
|
||||||
this.websocket!.send(JSON.stringify({
|
this.websocket!.send(JSON.stringify({
|
||||||
action: 'init',
|
action: 'init',
|
||||||
@@ -80,15 +79,15 @@ export class WsPacketClient extends IPacketClient {
|
|||||||
};
|
};
|
||||||
this.websocket.onclose = () => {
|
this.websocket.onclose = () => {
|
||||||
this.available = false;
|
this.available = false;
|
||||||
this.logger.warn(`WebSocket 连接关闭,尝试重连...`);
|
this.context.logger.warn(`WebSocket 连接关闭,尝试重连...`);
|
||||||
reject(new Error('WebSocket 连接关闭'));
|
reject(new Error('WebSocket 连接关闭'));
|
||||||
};
|
};
|
||||||
this.websocket.onmessage = (event) => this.handleMessage(event.data).catch(err => {
|
this.websocket.onmessage = (event) => this.handleMessage(event.data).catch(err => {
|
||||||
this.logger.error(`处理消息时出错: ${err}`);
|
this.context.logger.error(`处理消息时出错: ${err}`);
|
||||||
});
|
});
|
||||||
this.websocket.onerror = (event: ErrorEvent) => {
|
this.websocket.onerror = (event: ErrorEvent) => {
|
||||||
this.available = false;
|
this.available = false;
|
||||||
this.logger.error(`WebSocket 出错: ${event.message}`);
|
this.context.logger.error(`WebSocket 出错: ${event.message}`);
|
||||||
this.websocket?.close();
|
this.websocket?.close();
|
||||||
reject(new Error(`WebSocket 出错: ${event.message}`));
|
reject(new Error(`WebSocket 出错: ${event.message}`));
|
||||||
};
|
};
|
||||||
@@ -107,7 +106,7 @@ export class WsPacketClient extends IPacketClient {
|
|||||||
const event = this.cb.get(`${trace_id_md5}${action}`);
|
const event = this.cb.get(`${trace_id_md5}${action}`);
|
||||||
if (event) await event(json.data);
|
if (event) await event(json.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`解析ws消息时出错: ${(error as Error).message}`);
|
this.context.logger.error(`解析ws消息时出错: ${(error as Error).message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { IPacketClient } from "@/core/packet/client/baseClient";
|
import { IPacketClient } from "@/core/packet/client/baseClient";
|
||||||
import { NativePacketClient } from "@/core/packet/client/nativeClient";
|
import { NativePacketClient } from "@/core/packet/client/nativeClient";
|
||||||
import { WsPacketClient } from "@/core/packet/client/wsClient";
|
import { WsPacketClient } from "@/core/packet/client/wsClient";
|
||||||
import { OidbPacket } from "@/core/packet/transformer/base";
|
import { OidbPacket } from "@/core/packet/transformer/base";
|
||||||
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
||||||
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
|
||||||
|
|
||||||
type clientPriority = {
|
type clientPriority = {
|
||||||
[key: number]: (napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) => IPacketClient;
|
[key: number]: (context: PacketContext, logStack: LogStack) => IPacketClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientPriority: clientPriority = {
|
const clientPriority: clientPriority = {
|
||||||
10: (napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) => new NativePacketClient(napCore, logger, logStack),
|
10: (context: PacketContext, logStack: LogStack) => new NativePacketClient(context, logStack),
|
||||||
1: (napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) => new WsPacketClient(napCore, logger, logStack),
|
1: (context: PacketContext, logStack: LogStack) => new WsPacketClient(context, logStack),
|
||||||
};
|
};
|
||||||
|
|
||||||
export class LogStack {
|
export class LogStack {
|
||||||
@@ -51,15 +51,13 @@ export class LogStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PacketClientContext {
|
export class PacketClientContext {
|
||||||
private readonly napCore: NapCoreContext;
|
private readonly context: PacketContext;
|
||||||
private readonly logger: PacketLogger;
|
|
||||||
private readonly logStack: LogStack;
|
private readonly logStack: LogStack;
|
||||||
private readonly _client: IPacketClient;
|
private readonly _client: IPacketClient;
|
||||||
|
|
||||||
constructor(napCore: NapCoreContext, logger: PacketLogger) {
|
constructor(context: PacketContext) {
|
||||||
this.napCore = napCore;
|
this.context = context;
|
||||||
this.logger = logger;
|
this.logStack = new LogStack(context.logger);
|
||||||
this.logStack = new LogStack(logger);
|
|
||||||
this._client = this.newClient();
|
this._client = this.newClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,23 +79,23 @@ export class PacketClientContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private newClient(): IPacketClient {
|
private newClient(): IPacketClient {
|
||||||
const prefer = this.napCore.config.packetBackend;
|
const prefer = this.context.napcore.config.packetBackend;
|
||||||
let client: IPacketClient | null;
|
let client: IPacketClient | null;
|
||||||
switch (prefer) {
|
switch (prefer) {
|
||||||
case "native":
|
case "native":
|
||||||
this.logger.info("使用指定的 NativePacketClient 作为后端");
|
this.context.logger.info("使用指定的 NativePacketClient 作为后端");
|
||||||
client = new NativePacketClient(this.napCore, this.logger, this.logStack);
|
client = new NativePacketClient(this.context, this.logStack);
|
||||||
break;
|
break;
|
||||||
case "frida":
|
case "frida":
|
||||||
this.logger.info("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
|
this.context.logger.info("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
|
||||||
client = new WsPacketClient(this.napCore, this.logger, this.logStack);
|
client = new WsPacketClient(this.context, this.logStack);
|
||||||
break;
|
break;
|
||||||
case "auto":
|
case "auto":
|
||||||
case undefined:
|
case undefined:
|
||||||
client = this.judgeClient();
|
client = this.judgeClient();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`);
|
this.context.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`);
|
||||||
client = null;
|
client = null;
|
||||||
}
|
}
|
||||||
if (!client?.check()) {
|
if (!client?.check()) {
|
||||||
@@ -112,7 +110,7 @@ export class PacketClientContext {
|
|||||||
private judgeClient(): IPacketClient {
|
private judgeClient(): IPacketClient {
|
||||||
const sortedClients = Object.entries(clientPriority)
|
const sortedClients = Object.entries(clientPriority)
|
||||||
.map(([priority, clientFactory]) => {
|
.map(([priority, clientFactory]) => {
|
||||||
const client = clientFactory(this.napCore, this.logger, this.logStack);
|
const client = clientFactory(this.context, this.logStack);
|
||||||
const score = +priority * +client.check();
|
const score = +priority * +client.check();
|
||||||
return { client, score };
|
return { client, score };
|
||||||
})
|
})
|
||||||
@@ -122,7 +120,7 @@ export class PacketClientContext {
|
|||||||
if (!selectedClient) {
|
if (!selectedClient) {
|
||||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||||
}
|
}
|
||||||
this.logger.info(`自动选择 ${selectedClient.constructor.name} 作为后端`);
|
this.context.logger.info(`自动选择 ${selectedClient.constructor.name} 作为后端`);
|
||||||
return selectedClient;
|
return selectedClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { LogLevel, LogWrapper } from "@/common/log";
|
import { LogLevel, LogWrapper } from "@/common/log";
|
||||||
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
|
||||||
// TODO: check bind?
|
// TODO: check bind?
|
||||||
export class PacketLogger {
|
export class PacketLogger {
|
||||||
private readonly napLogger: LogWrapper;
|
private readonly napLogger: LogWrapper;
|
||||||
|
|
||||||
constructor(napcore: NapCoreContext) {
|
constructor(context: PacketContext) {
|
||||||
this.napLogger = napcore.logger;
|
this.napLogger = context.napcore.logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _log(level: LogLevel, ...msg: any[]): void {
|
private _log(level: LogLevel, ...msg: any[]): void {
|
||||||
|
@@ -13,22 +13,15 @@ import { MiniAppRawData, MiniAppReqParams } from "@/core/packet/entities/miniApp
|
|||||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||||
import { NapProtoDecodeStructType, NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
import { NapProtoDecodeStructType, NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||||
import { IndexNode, MsgInfo } from "@/core/packet/transformer/proto";
|
import { IndexNode, MsgInfo } from "@/core/packet/transformer/proto";
|
||||||
import { OidbPacket } from "@/core/packet/transformer/base";
|
|
||||||
import { ImageOcrResult } from "@/core/packet/entities/ocrResult";
|
|
||||||
|
|
||||||
export class PacketOperationContext {
|
export class PacketOperationContext {
|
||||||
private readonly context: PacketContext;
|
private readonly context: PacketContext;
|
||||||
|
|
||||||
constructor(context: PacketContext) {
|
constructor(context: PacketContext) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendPacket<T extends boolean = false>(pkt: OidbPacket, rsp?: T): Promise<T extends true ? Buffer : void> {
|
|
||||||
return await this.context.client.sendOidbPacket(pkt, rsp);
|
|
||||||
}
|
|
||||||
|
|
||||||
async GroupPoke(groupUin: number, uin: number) {
|
async GroupPoke(groupUin: number, uin: number) {
|
||||||
const req = trans.SendPoke.build(uin, groupUin);
|
const req = trans.SendPoke.build(groupUin, uin);
|
||||||
await this.context.client.sendOidbPacket(req);
|
await this.context.client.sendOidbPacket(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,46 +90,6 @@ export class PacketOperationContext {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async UploadImage(img: PacketMsgPicElement) {
|
|
||||||
await this.context.highway.uploadImage({
|
|
||||||
chatType: ChatType.KCHATTYPEC2C,
|
|
||||||
peerUid: this.context.napcore.basicInfo.uid
|
|
||||||
}, img);
|
|
||||||
const index = img.msgInfo?.msgInfoBody?.at(0)?.index;
|
|
||||||
if (!index) {
|
|
||||||
throw new Error('img.msgInfo?.msgInfoBody![0].index! is undefined');
|
|
||||||
}
|
|
||||||
return await this.GetImageUrl(this.context.napcore.basicInfo.uid, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
async GetImageUrl(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>) {
|
|
||||||
const req = trans.DownloadImage.build(selfUid, node);
|
|
||||||
const resp = await this.context.client.sendOidbPacket(req, true);
|
|
||||||
const res = trans.DownloadImage.parse(resp);
|
|
||||||
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async ImageOCR(imgUrl: string) {
|
|
||||||
const req = trans.ImageOCR.build(imgUrl);
|
|
||||||
const resp = await this.context.client.sendOidbPacket(req, true);
|
|
||||||
const res = trans.ImageOCR.parse(resp);
|
|
||||||
return {
|
|
||||||
texts: res.ocrRspBody.textDetections.map((item) => {
|
|
||||||
return {
|
|
||||||
text: item.detectedText,
|
|
||||||
confidence: item.confidence,
|
|
||||||
coordinates: item.polygon.coordinates.map((c) => {
|
|
||||||
return {
|
|
||||||
x: c.x,
|
|
||||||
y: c.y
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
language: res.ocrRspBody.language
|
|
||||||
} as ImageOcrResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
await this.UploadResources(msg, groupUin);
|
await this.UploadResources(msg, groupUin);
|
||||||
const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin);
|
const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin);
|
||||||
|
@@ -7,19 +7,19 @@ import { PacketOperationContext } from "@/core/packet/context/operationContext";
|
|||||||
import { PacketMsgConverter } from "@/core/packet/message/converter";
|
import { PacketMsgConverter } from "@/core/packet/message/converter";
|
||||||
|
|
||||||
export class PacketContext {
|
export class PacketContext {
|
||||||
readonly msgConverter: PacketMsgConverter;
|
|
||||||
readonly napcore: NapCoreContext;
|
readonly napcore: NapCoreContext;
|
||||||
readonly logger: PacketLogger;
|
readonly logger: PacketLogger;
|
||||||
readonly client: PacketClientContext;
|
readonly client: PacketClientContext;
|
||||||
readonly highway: PacketHighwayContext;
|
readonly highway: PacketHighwayContext;
|
||||||
|
readonly msgConverter: PacketMsgConverter;
|
||||||
readonly operation: PacketOperationContext;
|
readonly operation: PacketOperationContext;
|
||||||
|
|
||||||
constructor(core: NapCatCore) {
|
constructor(core: NapCatCore) {
|
||||||
this.msgConverter = new PacketMsgConverter();
|
|
||||||
this.napcore = new NapCoreContext(core);
|
this.napcore = new NapCoreContext(core);
|
||||||
this.logger = new PacketLogger(this.napcore);
|
this.logger = new PacketLogger(this);
|
||||||
this.client = new PacketClientContext(this.napcore, this.logger);
|
this.client = new PacketClientContext(this);
|
||||||
this.highway = new PacketHighwayContext(this.napcore, this.logger, this.client);
|
this.highway = new PacketHighwayContext(this);
|
||||||
|
this.msgConverter = new PacketMsgConverter();
|
||||||
this.operation = new PacketOperationContext(this);
|
this.operation = new PacketOperationContext(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ export interface MiniAppReqCustomParams {
|
|||||||
desc: string;
|
desc: string;
|
||||||
picUrl: string;
|
picUrl: string;
|
||||||
jumpUrl: string;
|
jumpUrl: string;
|
||||||
webUrl?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MiniAppReqTemplateParams {
|
export interface MiniAppReqTemplateParams {
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
export interface ImageOcrResult {
|
|
||||||
texts: TextDetection[];
|
|
||||||
language: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextDetection {
|
|
||||||
text: string;
|
|
||||||
confidence: number;
|
|
||||||
coordinates: Coordinate[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Coordinate {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
@@ -1,4 +1,5 @@
|
|||||||
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
||||||
import FetchSessionKey from "@/core/packet/transformer/highway/FetchSessionKey";
|
import FetchSessionKey from "@/core/packet/transformer/highway/FetchSessionKey";
|
||||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
||||||
@@ -15,8 +16,6 @@ import { NapProtoMsg } from "@napneko/nap-proto-core";
|
|||||||
import * as proto from "@/core/packet/transformer/proto";
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
import * as trans from "@/core/packet/transformer";
|
import * as trans from "@/core/packet/transformer";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
|
||||||
import { PacketClientContext } from "@/core/packet/context/clientContext";
|
|
||||||
|
|
||||||
export const BlockSize = 1024 * 1024;
|
export const BlockSize = 1024 * 1024;
|
||||||
|
|
||||||
@@ -34,25 +33,23 @@ export interface PacketHighwaySig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PacketHighwayContext {
|
export class PacketHighwayContext {
|
||||||
private readonly napcore: NapCoreContext;
|
private readonly context: PacketContext;
|
||||||
private readonly client: PacketClientContext;
|
|
||||||
protected sig: PacketHighwaySig;
|
protected sig: PacketHighwaySig;
|
||||||
protected logger: PacketLogger;
|
protected logger: PacketLogger;
|
||||||
protected hwClient: PacketHighwayClient;
|
protected hwClient: PacketHighwayClient;
|
||||||
private cachedPrepareReq: Promise<void> | null = null;
|
private cachedPrepareReq: Promise<void> | null = null;
|
||||||
|
|
||||||
constructor(napcore: NapCoreContext, logger: PacketLogger, client: PacketClientContext) {
|
constructor(context: PacketContext) {
|
||||||
this.napcore = napcore;
|
this.context = context;
|
||||||
this.client = client;
|
|
||||||
this.sig = {
|
this.sig = {
|
||||||
uin: String(this.napcore.basicInfo.uin),
|
uin: String(context.napcore.basicInfo.uin),
|
||||||
uid: this.napcore.basicInfo.uid,
|
uid: context.napcore.basicInfo.uid,
|
||||||
sigSession: null,
|
sigSession: null,
|
||||||
sessionKey: null,
|
sessionKey: null,
|
||||||
serverAddr: [],
|
serverAddr: [],
|
||||||
};
|
};
|
||||||
this.logger = logger;
|
this.logger = context.logger;
|
||||||
this.hwClient = new PacketHighwayClient(this.sig, this.logger);
|
this.hwClient = new PacketHighwayClient(this.sig, context.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkAvailable() {
|
private async checkAvailable() {
|
||||||
@@ -69,7 +66,7 @@ export class PacketHighwayContext {
|
|||||||
private async prepareUpload(): Promise<void> {
|
private async prepareUpload(): Promise<void> {
|
||||||
this.logger.debug('[Highway] on prepareUpload!');
|
this.logger.debug('[Highway] on prepareUpload!');
|
||||||
const packet = FetchSessionKey.build();
|
const packet = FetchSessionKey.build();
|
||||||
const req = await this.client.sendOidbPacket(packet, true);
|
const req = await this.context.client.sendOidbPacket(packet, true);
|
||||||
const rsp = FetchSessionKey.parse(req);
|
const rsp = FetchSessionKey.parse(req);
|
||||||
this.sig.sigSession = rsp.httpConn.sigSession;
|
this.sig.sigSession = rsp.httpConn.sigSession;
|
||||||
this.sig.sessionKey = rsp.httpConn.sessionKey;
|
this.sig.sessionKey = rsp.httpConn.sessionKey;
|
||||||
@@ -139,7 +136,7 @@ export class PacketHighwayContext {
|
|||||||
private async uploadGroupImage(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
private async uploadGroupImage(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||||
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||||
const req = UploadGroupImage.build(groupUin, img);
|
const req = UploadGroupImage.build(groupUin, img);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = UploadGroupImage.parse(resp);
|
const preRespData = UploadGroupImage.parse(resp);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
@@ -176,7 +173,7 @@ export class PacketHighwayContext {
|
|||||||
private async uploadC2CImage(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
private async uploadC2CImage(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
||||||
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||||
const req = trans.UploadPrivateImage.build(peerUid, img);
|
const req = trans.UploadPrivateImage.build(peerUid, img);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadPrivateImage.parse(resp);
|
const preRespData = trans.UploadPrivateImage.parse(resp);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
@@ -214,7 +211,7 @@ export class PacketHighwayContext {
|
|||||||
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||||
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||||
const req = trans.UploadGroupVideo.build(groupUin, video);
|
const req = trans.UploadGroupVideo.build(groupUin, video);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadGroupVideo.parse(resp);
|
const preRespData = trans.UploadGroupVideo.parse(resp);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
@@ -279,7 +276,7 @@ export class PacketHighwayContext {
|
|||||||
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||||
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||||
const req = trans.UploadPrivateVideo.build(peerUid, video);
|
const req = trans.UploadPrivateVideo.build(peerUid, video);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadPrivateVideo.parse(resp);
|
const preRespData = trans.UploadPrivateVideo.parse(resp);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
@@ -342,7 +339,7 @@ export class PacketHighwayContext {
|
|||||||
private async uploadGroupPtt(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
private async uploadGroupPtt(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||||
const req = trans.UploadGroupPtt.build(groupUin, ptt);
|
const req = trans.UploadGroupPtt.build(groupUin, ptt);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadGroupPtt.parse(resp);
|
const preRespData = trans.UploadGroupPtt.parse(resp);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
@@ -378,7 +375,7 @@ export class PacketHighwayContext {
|
|||||||
private async uploadC2CPtt(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
private async uploadC2CPtt(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||||
const req = trans.UploadPrivatePtt.build(peerUid, ptt);
|
const req = trans.UploadPrivatePtt.build(peerUid, ptt);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadPrivatePtt.parse(resp);
|
const preRespData = trans.UploadPrivatePtt.parse(resp);
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
@@ -416,7 +413,7 @@ export class PacketHighwayContext {
|
|||||||
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||||
file.fileSha1 = await calculateSha1(file.filePath);
|
file.fileSha1 = await calculateSha1(file.filePath);
|
||||||
const req = trans.UploadGroupFile.build(groupUin, file);
|
const req = trans.UploadGroupFile.build(groupUin, file);
|
||||||
const resp = await this.client.sendOidbPacket(req, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadGroupFile.parse(resp);
|
const preRespData = trans.UploadGroupFile.parse(resp);
|
||||||
if (!preRespData?.upload?.boolFileExist) {
|
if (!preRespData?.upload?.boolFileExist) {
|
||||||
this.logger.debug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
|
this.logger.debug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
|
||||||
@@ -479,7 +476,7 @@ export class PacketHighwayContext {
|
|||||||
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||||
file.fileSha1 = await calculateSha1(file.filePath);
|
file.fileSha1 = await calculateSha1(file.filePath);
|
||||||
const req = await trans.UploadPrivateFile.build(this.sig.uid, peerUid, file);
|
const req = await trans.UploadPrivateFile.build(this.sig.uid, peerUid, file);
|
||||||
const res = await this.client.sendOidbPacket(req, true);
|
const res = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preRespData = trans.UploadPrivateFile.parse(res);
|
const preRespData = trans.UploadPrivateFile.parse(res);
|
||||||
if (!preRespData.upload?.boolFileExist) {
|
if (!preRespData.upload?.boolFileExist) {
|
||||||
this.logger.debug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
|
this.logger.debug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
|
||||||
@@ -534,7 +531,7 @@ export class PacketHighwayContext {
|
|||||||
file.fileUuid = preRespData.upload?.uuid;
|
file.fileUuid = preRespData.upload?.uuid;
|
||||||
file.fileHash = preRespData.upload?.fileAddon;
|
file.fileHash = preRespData.upload?.fileAddon;
|
||||||
const fileExistReq = trans.DownloadOfflineFile.build(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
|
const fileExistReq = trans.DownloadOfflineFile.build(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
|
||||||
const fileExistRes = await this.client.sendOidbPacket(fileExistReq, true);
|
const fileExistRes = await this.context.client.sendOidbPacket(fileExistReq, true);
|
||||||
file._e37_800_rsp = trans.DownloadOfflineFile.parse(fileExistRes);
|
file._e37_800_rsp = trans.DownloadOfflineFile.parse(fileExistRes);
|
||||||
file._private_send_uid = this.sig.uid;
|
file._private_send_uid = this.sig.uid;
|
||||||
file._private_recv_uid = peerUid;
|
file._private_recv_uid = peerUid;
|
||||||
|
@@ -124,7 +124,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isGroupReply(): boolean {
|
get isGroupReply(): boolean {
|
||||||
return this.messageClientSeq === 0;
|
return this.messageClientSeq !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||||
@@ -256,8 +256,6 @@ export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
picType: PicType;
|
picType: PicType;
|
||||||
picSubType: number;
|
|
||||||
summary: string;
|
|
||||||
sha1: string | null = null;
|
sha1: string | null = null;
|
||||||
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||||
groupPicExt: NapProtoEncodeStructType<typeof CustomFace> | null = null;
|
groupPicExt: NapProtoEncodeStructType<typeof CustomFace> | null = null;
|
||||||
@@ -272,10 +270,6 @@ export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
|||||||
this.width = element.picElement.picWidth;
|
this.width = element.picElement.picWidth;
|
||||||
this.height = element.picElement.picHeight;
|
this.height = element.picElement.picHeight;
|
||||||
this.picType = element.picElement.picType;
|
this.picType = element.picElement.picType;
|
||||||
this.picSubType = element.picElement.picSubType ?? 0;
|
|
||||||
this.summary = element.picElement.summary === '' ? (
|
|
||||||
element.picElement.picSubType === 0 ? '[图片]' : '[动画表情]'
|
|
||||||
) : element.picElement.summary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get valid(): boolean {
|
get valid(): boolean {
|
||||||
@@ -294,7 +288,7 @@ export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toPreview(): string {
|
toPreview(): string {
|
||||||
return this.summary;
|
return "[图片]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,7 +30,7 @@ class GetMiniAppAdaptShareInfo extends PacketTransformer<typeof proto.MiniAppAda
|
|||||||
shareType: req.shareType,
|
shareType: req.shareType,
|
||||||
versionId: req.versionId,
|
versionId: req.versionId,
|
||||||
withShareTicket: req.withShareTicket,
|
withShareTicket: req.withShareTicket,
|
||||||
webURL: req.webUrl ?? "",
|
webURL: "",
|
||||||
appidRich: Buffer.alloc(0),
|
appidRich: Buffer.alloc(0),
|
||||||
template: {
|
template: {
|
||||||
templateId: "",
|
templateId: "",
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
import * as proto from "@/core/packet/transformer/proto";
|
|
||||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
|
||||||
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
|
||||||
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
|
||||||
|
|
||||||
class ImageOCR extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0xE07_0_Response> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
build(url: string): OidbPacket {
|
|
||||||
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0xE07_0).encode(
|
|
||||||
{
|
|
||||||
version: 1,
|
|
||||||
client: 0,
|
|
||||||
entrance: 1,
|
|
||||||
ocrReqBody: {
|
|
||||||
imageUrl: url,
|
|
||||||
originMd5: "",
|
|
||||||
afterCompressMd5: "",
|
|
||||||
afterCompressFileSize: "",
|
|
||||||
afterCompressWeight: "",
|
|
||||||
afterCompressHeight: "",
|
|
||||||
isCut: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return OidbBase.build(0XEB7, 1, body, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(data: Buffer) {
|
|
||||||
const base = OidbBase.parse(data);
|
|
||||||
return new NapProtoMsg(proto.OidbSvcTrpcTcp0xE07_0_Response).decode(base.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new ImageOCR();
|
|
@@ -5,4 +5,3 @@ export { default as GroupSign } from './GroupSign';
|
|||||||
export { default as GetStrangerInfo } from './GetStrangerInfo';
|
export { default as GetStrangerInfo } from './GetStrangerInfo';
|
||||||
export { default as SendPoke } from './SendPoke';
|
export { default as SendPoke } from './SendPoke';
|
||||||
export { default as SetSpecialTitle } from './SetSpecialTitle';
|
export { default as SetSpecialTitle } from './SetSpecialTitle';
|
||||||
export { default as ImageOCR } from './ImageOCR';
|
|
||||||
|
@@ -1,51 +0,0 @@
|
|||||||
import * as proto from "@/core/packet/transformer/proto";
|
|
||||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
|
||||||
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
|
||||||
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
|
||||||
import { IndexNode } from "@/core/packet/transformer/proto";
|
|
||||||
|
|
||||||
class DownloadImage extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
build(selfUid: string, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
|
|
||||||
const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
|
|
||||||
reqHead: {
|
|
||||||
common: {
|
|
||||||
requestId: 1,
|
|
||||||
command: 100
|
|
||||||
},
|
|
||||||
scene: {
|
|
||||||
requestType: 2,
|
|
||||||
businessType: 1,
|
|
||||||
sceneType: 1,
|
|
||||||
c2C: {
|
|
||||||
accountType: 2,
|
|
||||||
targetUid: selfUid
|
|
||||||
},
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
agentType: 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
download: {
|
|
||||||
node: node,
|
|
||||||
download: {
|
|
||||||
video: {
|
|
||||||
busiType: 0,
|
|
||||||
sceneType: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return OidbBase.build(0x11C5, 200, body, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(data: Buffer) {
|
|
||||||
const oidbBody = OidbBase.parse(data).body;
|
|
||||||
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new DownloadImage();
|
|
@@ -58,11 +58,8 @@ class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp>
|
|||||||
compatQMsgSceneType: 2,
|
compatQMsgSceneType: 2,
|
||||||
extBizInfo: {
|
extBizInfo: {
|
||||||
pic: {
|
pic: {
|
||||||
bizType: img.picSubType,
|
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
||||||
bytesPbReserveTroop: {
|
textSummary: "Nya~", // TODO:
|
||||||
subType: img.picSubType,
|
|
||||||
},
|
|
||||||
textSummary: img.summary,
|
|
||||||
},
|
},
|
||||||
video: {
|
video: {
|
||||||
bytesPbReserve: Buffer.alloc(0),
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
@@ -58,11 +58,8 @@ class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaRes
|
|||||||
compatQMsgSceneType: 1,
|
compatQMsgSceneType: 1,
|
||||||
extBizInfo: {
|
extBizInfo: {
|
||||||
pic: {
|
pic: {
|
||||||
bizType: img.picSubType,
|
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
||||||
bytesPbReserveC2C: {
|
textSummary: "Nya~", // TODO:
|
||||||
subType: img.picSubType,
|
|
||||||
},
|
|
||||||
textSummary: img.summary,
|
|
||||||
},
|
},
|
||||||
video: {
|
video: {
|
||||||
bytesPbReserve: Buffer.alloc(0),
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
@@ -11,4 +11,3 @@ export { default as UploadPrivateFile } from './UploadPrivateFile';
|
|||||||
export { default as UploadPrivateImage } from './UploadPrivateImage';
|
export { default as UploadPrivateImage } from './UploadPrivateImage';
|
||||||
export { default as UploadPrivatePtt } from './UploadPrivatePtt';
|
export { default as UploadPrivatePtt } from './UploadPrivatePtt';
|
||||||
export { default as UploadPrivateVideo } from './UploadPrivateVideo';
|
export { default as UploadPrivateVideo } from './UploadPrivateVideo';
|
||||||
export { default as DownloadImage } from './DownloadImage';
|
|
||||||
|
@@ -29,4 +29,3 @@ export * from "./oidb/Oidb.0xEB7";
|
|||||||
export * from "./oidb/Oidb.0xED3_1";
|
export * from "./oidb/Oidb.0xED3_1";
|
||||||
export * from "./oidb/Oidb.0XFE1_2";
|
export * from "./oidb/Oidb.0XFE1_2";
|
||||||
export * from "./oidb/OidbBase";
|
export * from "./oidb/OidbBase";
|
||||||
export * from "./oidb/Oidb.0xE07";
|
|
||||||
|
@@ -3,16 +3,16 @@ import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
|||||||
export const GroupAdminExtra = {
|
export const GroupAdminExtra = {
|
||||||
adminUid: ProtoField(1, ScalarType.STRING),
|
adminUid: ProtoField(1, ScalarType.STRING),
|
||||||
isPromote: ProtoField(2, ScalarType.BOOL),
|
isPromote: ProtoField(2, ScalarType.BOOL),
|
||||||
};
|
}
|
||||||
|
|
||||||
export const GroupAdminBody = {
|
export const GroupAdminBody = {
|
||||||
extraDisable: ProtoField(1, () => GroupAdminExtra),
|
extraDisable: ProtoField(1, () => GroupAdminExtra),
|
||||||
extraEnable: ProtoField(2, () => GroupAdminExtra),
|
extraEnable: ProtoField(2, () => GroupAdminExtra),
|
||||||
};
|
}
|
||||||
|
|
||||||
export const GroupAdmin = {
|
export const GroupAdmin = {
|
||||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||||
flag: ProtoField(2, ScalarType.UINT32),
|
flag: ProtoField(2, ScalarType.UINT32),
|
||||||
isPromote: ProtoField(3, ScalarType.BOOL),
|
isPromote: ProtoField(3, ScalarType.BOOL),
|
||||||
body: ProtoField(4, () => GroupAdminBody),
|
body: ProtoField(4, () => GroupAdminBody),
|
||||||
};
|
}
|
@@ -54,32 +54,16 @@ export const PushMsg = {
|
|||||||
generalFlag: ProtoField(9, ScalarType.INT32, true),
|
generalFlag: ProtoField(9, ScalarType.INT32, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupChangeInfo = {
|
|
||||||
operator: ProtoField(1, () => GroupChangeOperator, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GroupChangeOperator = {
|
|
||||||
operatorUid: ProtoField(1, ScalarType.STRING, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GroupChange = {
|
export const GroupChange = {
|
||||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||||
flag: ProtoField(2, ScalarType.UINT32),
|
flag: ProtoField(2, ScalarType.UINT32),
|
||||||
memberUid: ProtoField(3, ScalarType.STRING, true),
|
memberUid: ProtoField(3, ScalarType.STRING, true),
|
||||||
decreaseType: ProtoField(4, ScalarType.UINT32),
|
decreaseType: ProtoField(4, ScalarType.UINT32),
|
||||||
operatorInfo: ProtoField(5, ScalarType.BYTES, true),
|
operatorUid: ProtoField(5, ScalarType.STRING, true),
|
||||||
increaseType: ProtoField(6, ScalarType.UINT32),
|
increaseType: ProtoField(6, ScalarType.UINT32),
|
||||||
field7: ProtoField(7, ScalarType.BYTES, true),
|
field7: ProtoField(7, ScalarType.BYTES, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GroupInvite = {
|
|
||||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
|
||||||
field2: ProtoField(2, ScalarType.UINT32),
|
|
||||||
field3: ProtoField(2, ScalarType.UINT32),
|
|
||||||
field4: ProtoField(2, ScalarType.UINT32),
|
|
||||||
invitorUid: ProtoField(5, ScalarType.STRING),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PushMsgBody = {
|
export const PushMsgBody = {
|
||||||
responseHead: ProtoField(1, () => ResponseHead),
|
responseHead: ProtoField(1, () => ResponseHead),
|
||||||
contentHead: ProtoField(2, () => ContentHead),
|
contentHead: ProtoField(2, () => ContentHead),
|
||||||
|
@@ -1,59 +0,0 @@
|
|||||||
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0xE07_0 = {
|
|
||||||
version: ProtoField(1, ScalarType.UINT32),
|
|
||||||
client: ProtoField(2, ScalarType.UINT32),
|
|
||||||
entrance: ProtoField(3, ScalarType.UINT32),
|
|
||||||
ocrReqBody: ProtoField(10, () => OcrReqBody, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OcrReqBody = {
|
|
||||||
imageUrl: ProtoField(1, ScalarType.STRING),
|
|
||||||
languageType: ProtoField(2, ScalarType.UINT32),
|
|
||||||
scene: ProtoField(3, ScalarType.UINT32),
|
|
||||||
originMd5: ProtoField(10, ScalarType.STRING),
|
|
||||||
afterCompressMd5: ProtoField(11, ScalarType.STRING),
|
|
||||||
afterCompressFileSize: ProtoField(12, ScalarType.STRING),
|
|
||||||
afterCompressWeight: ProtoField(13, ScalarType.STRING),
|
|
||||||
afterCompressHeight: ProtoField(14, ScalarType.STRING),
|
|
||||||
isCut: ProtoField(15, ScalarType.BOOL),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0xE07_0_Response = {
|
|
||||||
retCode: ProtoField(1, ScalarType.INT32),
|
|
||||||
errMsg: ProtoField(2, ScalarType.STRING),
|
|
||||||
wording: ProtoField(3, ScalarType.STRING),
|
|
||||||
ocrRspBody: ProtoField(10, () => OcrRspBody),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OcrRspBody = {
|
|
||||||
textDetections: ProtoField(1, () => TextDetection, false, true),
|
|
||||||
language: ProtoField(2, ScalarType.STRING),
|
|
||||||
requestId: ProtoField(3, ScalarType.STRING),
|
|
||||||
ocrLanguageList: ProtoField(101, ScalarType.STRING, false, true),
|
|
||||||
dstTranslateLanguageList: ProtoField(102, ScalarType.STRING, false, true),
|
|
||||||
languageList: ProtoField(103, () => Language, false, true),
|
|
||||||
afterCompressWeight: ProtoField(111, ScalarType.UINT32),
|
|
||||||
afterCompressHeight: ProtoField(112, ScalarType.UINT32),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TextDetection = {
|
|
||||||
detectedText: ProtoField(1, ScalarType.STRING),
|
|
||||||
confidence: ProtoField(2, ScalarType.UINT32),
|
|
||||||
polygon: ProtoField(3, () => Polygon),
|
|
||||||
advancedInfo: ProtoField(4, ScalarType.STRING),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Polygon = {
|
|
||||||
coordinates: ProtoField(1, () => Coordinate, false, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Coordinate = {
|
|
||||||
x: ProtoField(1, ScalarType.INT32),
|
|
||||||
y: ProtoField(2, ScalarType.INT32),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Language = {
|
|
||||||
languageCode: ProtoField(1, ScalarType.STRING),
|
|
||||||
languageDesc: ProtoField(2, ScalarType.STRING),
|
|
||||||
};
|
|
@@ -189,8 +189,8 @@ export const VideoExtBizInfo = {
|
|||||||
export const PicExtBizInfo = {
|
export const PicExtBizInfo = {
|
||||||
BizType: ProtoField(1, ScalarType.UINT32),
|
BizType: ProtoField(1, ScalarType.UINT32),
|
||||||
TextSummary: ProtoField(2, ScalarType.STRING),
|
TextSummary: ProtoField(2, ScalarType.STRING),
|
||||||
BytesPbReserveC2c: ProtoField(11, () => BytesPbReserveC2c),
|
BytesPbReserveC2c: ProtoField(11, ScalarType.BYTES),
|
||||||
BytesPbReserveTroop: ProtoField(12, () => BytesPbReserveTroop),
|
BytesPbReserveTroop: ProtoField(12, ScalarType.BYTES),
|
||||||
FromScene: ProtoField(1001, ScalarType.UINT32),
|
FromScene: ProtoField(1001, ScalarType.UINT32),
|
||||||
ToScene: ProtoField(1002, ScalarType.UINT32),
|
ToScene: ProtoField(1002, ScalarType.UINT32),
|
||||||
OldFileId: ProtoField(1003, ScalarType.UINT32),
|
OldFileId: ProtoField(1003, ScalarType.UINT32),
|
||||||
@@ -211,27 +211,3 @@ export const UploadInfo = {
|
|||||||
FileInfo: ProtoField(1, () => FileInfo),
|
FileInfo: ProtoField(1, () => FileInfo),
|
||||||
SubFileType: ProtoField(2, ScalarType.UINT32),
|
SubFileType: ProtoField(2, ScalarType.UINT32),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BytesPbReserveC2c = {
|
|
||||||
subType: ProtoField(1, ScalarType.UINT32),
|
|
||||||
field3: ProtoField(3, ScalarType.UINT32),
|
|
||||||
field4: ProtoField(4, ScalarType.UINT32),
|
|
||||||
field8: ProtoField(8, ScalarType.STRING),
|
|
||||||
field10: ProtoField(10, ScalarType.UINT32),
|
|
||||||
field12: ProtoField(12, ScalarType.STRING),
|
|
||||||
field18: ProtoField(18, ScalarType.STRING),
|
|
||||||
field19: ProtoField(19, ScalarType.STRING),
|
|
||||||
field20: ProtoField(20, ScalarType.BYTES),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BytesPbReserveTroop = {
|
|
||||||
subType: ProtoField(1, ScalarType.UINT32),
|
|
||||||
field3: ProtoField(3, ScalarType.UINT32),
|
|
||||||
field4: ProtoField(4, ScalarType.UINT32),
|
|
||||||
field9: ProtoField(9, ScalarType.STRING),
|
|
||||||
field10: ProtoField(10, ScalarType.UINT32),
|
|
||||||
field12: ProtoField(12, ScalarType.STRING),
|
|
||||||
field18: ProtoField(18, ScalarType.STRING),
|
|
||||||
field19: ProtoField(19, ScalarType.STRING),
|
|
||||||
field21: ProtoField(21, ScalarType.BYTES),
|
|
||||||
};
|
|
||||||
|
@@ -149,7 +149,7 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
getGroupExtList(force: boolean): Promise<GeneralCallResult>;
|
getGroupExtList(force: boolean): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise<GeneralCallResult>;
|
getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise<unknown>;
|
||||||
|
|
||||||
getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req
|
getMemberExtInfo(param: GroupExtParam): Promise<unknown>;//req
|
||||||
|
|
||||||
@@ -187,13 +187,13 @@ export interface NodeIKernelGroupService {
|
|||||||
|
|
||||||
destroyGroup(groupCode: string): void;
|
destroyGroup(groupCode: string): void;
|
||||||
|
|
||||||
getSingleScreenNotifies(doubt: boolean, startSeq: string, count: number): Promise<GeneralCallResult>;
|
getSingleScreenNotifies(doubted: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
clearGroupNotifies(groupCode: string): void;
|
clearGroupNotifies(groupCode: string): void;
|
||||||
|
|
||||||
getGroupNotifiesUnreadCount(doubt: boolean): Promise<GeneralCallResult>;
|
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
|
||||||
|
|
||||||
clearGroupNotifiesUnreadCount(doubt: boolean): void;
|
clearGroupNotifiesUnreadCount(unknown: boolean): void;
|
||||||
|
|
||||||
operateSysNotify(
|
operateSysNotify(
|
||||||
doubt: boolean,
|
doubt: boolean,
|
||||||
|
@@ -4,7 +4,6 @@ import { GeneralCallResult } from '@/core/services/common';
|
|||||||
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '@/core/types/msg';
|
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '@/core/types/msg';
|
||||||
|
|
||||||
export interface NodeIKernelMsgService {
|
export interface NodeIKernelMsgService {
|
||||||
buildMultiForwardMsg(req: { srcMsgIds: Array<string>, srcContact: Peer }): Promise<GeneralCallResult & { rspInfo: { elements: unknown } }>;
|
|
||||||
|
|
||||||
generateMsgUniqueId(chatType: number, time: string): string;
|
generateMsgUniqueId(chatType: number, time: string): string;
|
||||||
|
|
||||||
|
@@ -9,9 +9,9 @@ export interface NodeIKernelProfileService {
|
|||||||
|
|
||||||
getRelationFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
getRelationFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||||
|
|
||||||
getUidByUin(callfrom: string, uin: Array<string>): Map<string, string>;
|
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;
|
||||||
|
|
||||||
getUinByUid(callfrom: string, uid: Array<string>): Map<string, string>;
|
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>>;
|
||||||
|
|
||||||
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>;
|
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>;
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ export interface NodeIKernelSearchService {
|
|||||||
penetrate: string
|
penetrate: string
|
||||||
}): Promise<GeneralCallResult>;// needs 1 arguments
|
}): Promise<GeneralCallResult>;// needs 1 arguments
|
||||||
|
|
||||||
searchLocalInfo(keywords: string, type: number/*4*/): unknown;
|
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;
|
||||||
|
|
||||||
cancelSearchLocalInfo(...args: any[]): unknown;// needs 3 arguments
|
cancelSearchLocalInfo(...args: any[]): unknown;// needs 3 arguments
|
||||||
|
|
||||||
|
@@ -29,7 +29,6 @@ export interface TextElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FaceElement {
|
export interface FaceElement {
|
||||||
pokeType?: number;
|
|
||||||
faceIndex: number;
|
faceIndex: number;
|
||||||
faceType: FaceType;
|
faceType: FaceType;
|
||||||
faceText?: string;
|
faceText?: string;
|
||||||
@@ -56,7 +55,6 @@ export interface GrayTipElement {
|
|||||||
aioOpGrayTipElement: TipAioOpGrayTipElement;
|
aioOpGrayTipElement: TipAioOpGrayTipElement;
|
||||||
groupElement: TipGroupElement;
|
groupElement: TipGroupElement;
|
||||||
xmlElement: {
|
xmlElement: {
|
||||||
busiId: string;
|
|
||||||
content: string;
|
content: string;
|
||||||
templId: string;
|
templId: string;
|
||||||
};
|
};
|
||||||
|
@@ -17,160 +17,7 @@ export enum GroupInfoSource {
|
|||||||
KRECENTCONTACT,
|
KRECENTCONTACT,
|
||||||
KMOREPANEL
|
KMOREPANEL
|
||||||
}
|
}
|
||||||
export interface GroupDetailInfo {
|
|
||||||
groupCode: string;
|
|
||||||
groupUin: string;
|
|
||||||
ownerUid: string;
|
|
||||||
ownerUin: string;
|
|
||||||
groupFlag: number;
|
|
||||||
groupFlagExt: number;
|
|
||||||
maxMemberNum: number;
|
|
||||||
memberNum: number;
|
|
||||||
groupOption: number;
|
|
||||||
classExt: number;
|
|
||||||
groupName: string;
|
|
||||||
fingerMemo: string;
|
|
||||||
groupQuestion: string;
|
|
||||||
certType: number;
|
|
||||||
richFingerMemo: string;
|
|
||||||
tagRecord: any[];
|
|
||||||
shutUpAllTimestamp: number;
|
|
||||||
shutUpMeTimestamp: number;
|
|
||||||
groupTypeFlag: number;
|
|
||||||
privilegeFlag: number;
|
|
||||||
groupSecLevel: number;
|
|
||||||
groupFlagExt3: number;
|
|
||||||
isConfGroup: number;
|
|
||||||
isModifyConfGroupFace: number;
|
|
||||||
isModifyConfGroupName: number;
|
|
||||||
groupFlagExt4: number;
|
|
||||||
groupMemo: string;
|
|
||||||
cmdUinMsgSeq: number;
|
|
||||||
cmdUinJoinTime: number;
|
|
||||||
cmdUinUinFlag: number;
|
|
||||||
cmdUinMsgMask: number;
|
|
||||||
groupSecLevelInfo: number;
|
|
||||||
cmdUinPrivilege: number;
|
|
||||||
cmdUinFlagEx2: number;
|
|
||||||
appealDeadline: number;
|
|
||||||
remarkName: string;
|
|
||||||
isTop: boolean;
|
|
||||||
groupFace: number;
|
|
||||||
groupGeoInfo: {
|
|
||||||
ownerUid: string;
|
|
||||||
SetTime: number;
|
|
||||||
CityId: number;
|
|
||||||
Longitude: string;
|
|
||||||
Latitude: string;
|
|
||||||
GeoContent: string;
|
|
||||||
poiId: string;
|
|
||||||
};
|
|
||||||
certificationText: string;
|
|
||||||
cmdUinRingtoneId: number;
|
|
||||||
longGroupName: string;
|
|
||||||
autoAgreeJoinGroupUserNumForConfGroup: number;
|
|
||||||
autoAgreeJoinGroupUserNumForNormalGroup: number;
|
|
||||||
cmdUinFlagExt3Grocery: number;
|
|
||||||
groupCardPrefix: {
|
|
||||||
introduction: string;
|
|
||||||
rptPrefix: any[];
|
|
||||||
};
|
|
||||||
groupExt: {
|
|
||||||
groupInfoExtSeq: number;
|
|
||||||
reserve: number;
|
|
||||||
luckyWordId: string;
|
|
||||||
lightCharNum: number;
|
|
||||||
luckyWord: string;
|
|
||||||
starId: number;
|
|
||||||
essentialMsgSwitch: number;
|
|
||||||
todoSeq: number;
|
|
||||||
blacklistExpireTime: number;
|
|
||||||
isLimitGroupRtc: number;
|
|
||||||
companyId: number;
|
|
||||||
hasGroupCustomPortrait: number;
|
|
||||||
bindGuildId: string;
|
|
||||||
groupOwnerId: {
|
|
||||||
memberUin: string;
|
|
||||||
memberUid: string;
|
|
||||||
memberQid: string;
|
|
||||||
};
|
|
||||||
essentialMsgPrivilege: number;
|
|
||||||
msgEventSeq: string;
|
|
||||||
inviteRobotSwitch: number;
|
|
||||||
gangUpId: string;
|
|
||||||
qqMusicMedalSwitch: number;
|
|
||||||
showPlayTogetherSwitch: number;
|
|
||||||
groupFlagPro1: string;
|
|
||||||
groupBindGuildIds: {
|
|
||||||
guildIds: any[];
|
|
||||||
};
|
|
||||||
viewedMsgDisappearTime: string;
|
|
||||||
groupExtFlameData: {
|
|
||||||
switchState: number;
|
|
||||||
state: number;
|
|
||||||
dayNums: any[];
|
|
||||||
version: number;
|
|
||||||
updateTime: string;
|
|
||||||
isDisplayDayNum: boolean;
|
|
||||||
};
|
|
||||||
groupBindGuildSwitch: number;
|
|
||||||
groupAioBindGuildId: string;
|
|
||||||
groupExcludeGuildIds: {
|
|
||||||
guildIds: any[];
|
|
||||||
};
|
|
||||||
fullGroupExpansionSwitch: number;
|
|
||||||
fullGroupExpansionSeq: string;
|
|
||||||
inviteRobotMemberSwitch: number;
|
|
||||||
inviteRobotMemberExamine: number;
|
|
||||||
groupSquareSwitch: number;
|
|
||||||
};
|
|
||||||
msgLimitFrequency: number;
|
|
||||||
hlGuildAppid: number;
|
|
||||||
hlGuildSubType: number;
|
|
||||||
isAllowRecallMsg: number;
|
|
||||||
confUin: string;
|
|
||||||
confMaxMsgSeq: number;
|
|
||||||
confToGroupTime: number;
|
|
||||||
groupSchoolInfo: {
|
|
||||||
location: string;
|
|
||||||
grade: number;
|
|
||||||
school: string;
|
|
||||||
};
|
|
||||||
activeMemberNum: number;
|
|
||||||
groupGrade: number;
|
|
||||||
groupCreateTime: number;
|
|
||||||
subscriptionUin: string;
|
|
||||||
subscriptionUid: string;
|
|
||||||
noFingerOpenFlag: number;
|
|
||||||
noCodeFingerOpenFlag: number;
|
|
||||||
isGroupFreeze: number;
|
|
||||||
allianceId: string;
|
|
||||||
groupExtOnly: {
|
|
||||||
tribeId: number;
|
|
||||||
moneyForAddGroup: number;
|
|
||||||
};
|
|
||||||
isAllowConfGroupMemberModifyGroupName: number;
|
|
||||||
isAllowConfGroupMemberNick: number;
|
|
||||||
isAllowConfGroupMemberAtAll: number;
|
|
||||||
groupClassText: string;
|
|
||||||
groupFreezeReason: number;
|
|
||||||
headPortraitSeq: number;
|
|
||||||
groupHeadPortrait: {
|
|
||||||
portraitCnt: number;
|
|
||||||
portraitInfo: any[];
|
|
||||||
defaultId: number;
|
|
||||||
verifyingPortraitCnt: number;
|
|
||||||
verifyingPortraitInfo: any[];
|
|
||||||
};
|
|
||||||
cmdUinJoinMsgSeq: number;
|
|
||||||
cmdUinJoinRealMsgSeq: number;
|
|
||||||
groupAnswer: string;
|
|
||||||
groupAdminMaxNum: number;
|
|
||||||
inviteNoAuthNumLimit: string;
|
|
||||||
hlGuildOrgId: number;
|
|
||||||
isAllowHlGuildBinary: number;
|
|
||||||
localExitGroupReason: number;
|
|
||||||
}
|
|
||||||
export interface GroupExt0xEF0InfoFilter {
|
export interface GroupExt0xEF0InfoFilter {
|
||||||
bindGuildId: number;
|
bindGuildId: number;
|
||||||
blacklistExpireTime: number;
|
blacklistExpireTime: number;
|
||||||
|
@@ -10,4 +10,3 @@ export * from './element';
|
|||||||
export * from './constant';
|
export * from './constant';
|
||||||
export * from './graytip';
|
export * from './graytip';
|
||||||
export * from './emoji';
|
export * from './emoji';
|
||||||
export * from './service';
|
|
@@ -508,7 +508,7 @@ export interface RawMessage {
|
|||||||
*/
|
*/
|
||||||
export interface QueryMsgsParams {
|
export interface QueryMsgsParams {
|
||||||
chatInfo: Peer;
|
chatInfo: Peer;
|
||||||
filterMsgType: Array<{ type: NTMsgType, subType: Array<number> }>;
|
filterMsgType: [];
|
||||||
filterSendersUid: string[];
|
filterSendersUid: string[];
|
||||||
filterMsgFromTime: string;
|
filterMsgFromTime: string;
|
||||||
filterMsgToTime: string;
|
filterMsgToTime: string;
|
||||||
|
@@ -1,35 +0,0 @@
|
|||||||
export enum LoginErrorCode {
|
|
||||||
KLOGINERRORACCOUNTNOTUIN = 140022018,
|
|
||||||
KLOGINERRORACCOUNTORPASSWORDERROR = 140022013,
|
|
||||||
KLOGINERRORBLACKACCOUNT = 150022021,
|
|
||||||
KLOGINERRORDEFAULT = 140022000,
|
|
||||||
KLOGINERROREXPIRETICKET = 140022014,
|
|
||||||
KLOGINERRORFROZEN = 140022005,
|
|
||||||
KLOGINERRORILLAGETICKET = 140022016,
|
|
||||||
KLOGINERRORINVAILDCOOKIE = 140022012,
|
|
||||||
KLOGINERRORINVALIDPARAMETER = 140022001,
|
|
||||||
KLOGINERRORKICKEDTICKET = 140022015,
|
|
||||||
KLOGINERRORMUTIPLEPASSWORDINCORRECT = 150022029,
|
|
||||||
KLOGINERRORNEEDUPDATE = 140022004,
|
|
||||||
KLOGINERRORNEEDVERIFYREALNAME = 140022019,
|
|
||||||
KLOGINERRORNEWDEVICE = 140022010,
|
|
||||||
KLOGINERRORNICEACCOUNTEXPIRED = 150022020,
|
|
||||||
KLOGINERRORNICEACCOUNTPARENTCHILDEXPIRED = 150022025,
|
|
||||||
KLOGINERRORPASSWORD = 2,
|
|
||||||
KLOGINERRORPROOFWATER = 140022008,
|
|
||||||
KLOGINERRORPROTECT = 140022006,
|
|
||||||
KLOGINERRORREFUSEPASSOWRDLOGIN = 140022009,
|
|
||||||
KLOGINERRORREMINDCANAELLATEDSTATUS = 150022028,
|
|
||||||
KLOGINERRORSCAN = 1,
|
|
||||||
KLOGINERRORSCCESS = 0,
|
|
||||||
KLOGINERRORSECBEAT = 140022017,
|
|
||||||
KLOGINERRORSMSINVALID = 150022026,
|
|
||||||
KLOGINERRORSTRICK = 140022007,
|
|
||||||
KLOGINERRORSYSTEMFAILED = 140022002,
|
|
||||||
KLOGINERRORTGTGTEXCHAGEA1FORBID = 150022027,
|
|
||||||
KLOGINERRORTIMEOUTRETRY = 140022003,
|
|
||||||
KLOGINERRORTOOMANYTIMESTODAY = 150022023,
|
|
||||||
KLOGINERRORTOOOFTEN = 150022022,
|
|
||||||
KLOGINERRORUNREGISTERED = 150022024,
|
|
||||||
KLOGINERRORUNUSUALDEVICE = 140022011,
|
|
||||||
}
|
|
@@ -18,7 +18,7 @@ export interface BuddyCategoryType {
|
|||||||
export interface CoreInfo {
|
export interface CoreInfo {
|
||||||
uid: string;
|
uid: string;
|
||||||
uin: string;
|
uin: string;
|
||||||
nick?: string;
|
nick: string;
|
||||||
remark: string;
|
remark: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
//LiteLoader需要提供部分IPC接口,以便于其他插件调用
|
//LiteLoader需要提供部分IPC接口,以便于其他插件调用
|
||||||
const { ipcMain, BrowserWindow } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
const napcat = require('./napcat.cjs');
|
const napcat = require('./napcat.cjs');
|
||||||
const { shell } = require('electron');
|
const { shell } = require('electron');
|
||||||
ipcMain.handle('napcat_get_webtoken', async (event, arg) => {
|
ipcMain.handle('napcat_get_webtoken', async (event, arg) => {
|
||||||
@@ -14,13 +14,3 @@ ipcMain.handle('napcat_get_reactweb', async (event, arg) => {
|
|||||||
let token = url.searchParams.get('token');
|
let token = url.searchParams.get('token');
|
||||||
return `https://napcat.152710.xyz/web_login?back=http://127.0.0.1:${port}&token=${token}`;
|
return `https://napcat.152710.xyz/web_login?back=http://127.0.0.1:${port}&token=${token}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('napcat_open_inner_url', (event, url) => {
|
|
||||||
const win = new BrowserWindow({
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
});
|
|
||||||
win.loadURL(url);
|
|
||||||
win.webContents.setWindowOpenHandler(details => {
|
|
||||||
win.loadURL(details.url)
|
|
||||||
})
|
|
||||||
});
|
|
@@ -27,7 +27,6 @@ export async function NCoreInitFramework(
|
|||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
|
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
console.log('[NapCat] [Error] unhandledRejection:', reason);
|
console.log('[NapCat] [Error] unhandledRejection:', reason);
|
||||||
});
|
});
|
||||||
|
@@ -6,9 +6,6 @@ const napcat = {
|
|||||||
openExternalUrl: async (url) => {
|
openExternalUrl: async (url) => {
|
||||||
ipcRenderer.send('open_external_url', url);
|
ipcRenderer.send('open_external_url', url);
|
||||||
},
|
},
|
||||||
openInnerUrl: async (url) => {
|
|
||||||
ipcRenderer.send('napcat_open_inner_url', url);
|
|
||||||
},
|
|
||||||
getWebUiUrlReact: async () => {
|
getWebUiUrlReact: async () => {
|
||||||
return ipcRenderer.invoke('napcat_get_reactweb');
|
return ipcRenderer.invoke('napcat_get_reactweb');
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ export const onSettingWindowCreated = async (view) => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
view.querySelector('.nc_openwebui').addEventListener('click', () => {
|
view.querySelector('.nc_openwebui').addEventListener('click', () => {
|
||||||
window.napcat.openInnerUrl(webui);
|
window.open(webui, '_blank');
|
||||||
});
|
});
|
||||||
view.querySelector('.nc_openwebui_ex').addEventListener('click', () => {
|
view.querySelector('.nc_openwebui_ex').addEventListener('click', () => {
|
||||||
window.napcat.openExternalUrl(webui);
|
window.napcat.openExternalUrl(webui);
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
import '@/universal/napcat';
|
|
@@ -29,7 +29,7 @@ export class OB11Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
||||||
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
actionName: ActionName = ActionName.Unknown;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
private validate: ValidateFunction<any> | undefined = undefined;
|
private validate: ValidateFunction<any> | undefined = undefined;
|
||||||
payloadSchema: any = undefined;
|
payloadSchema: any = undefined;
|
||||||
@@ -83,5 +83,5 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract _handle(payload: PayloadType, adaptername: string): Promise<ReturnDataType>;
|
abstract _handle(payload: PayloadType, adaptername: string): PromiseLike<ReturnDataType>;
|
||||||
}
|
}
|
@@ -1,14 +0,0 @@
|
|||||||
import { ActionName } from '@/onebot/action/router';
|
|
||||||
import { OneBotAction } from '../OneBotAction';
|
|
||||||
|
|
||||||
interface GetClientkeyResponse {
|
|
||||||
clientkey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetClientkey extends OneBotAction<void, GetClientkeyResponse> {
|
|
||||||
actionName = ActionName.GetClientkey;
|
|
||||||
|
|
||||||
async _handle() {
|
|
||||||
return { clientkey: (await this.core.apis.UserApi.forceFetchClientKey()).clientKey };
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,36 +1,32 @@
|
|||||||
import { GroupNotifyMsgStatus } from '@/core';
|
import { GroupNotifyMsgStatus } from '@/core';
|
||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { Notify } from '@/onebot/types';
|
|
||||||
|
|
||||||
export default class GetGroupAddRequest extends OneBotAction<null, Notify[] | null> {
|
interface OB11GroupRequestNotify {
|
||||||
|
group_id: number,
|
||||||
|
user_id: number,
|
||||||
|
flag: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class GetGroupAddRequest extends OneBotAction<null, OB11GroupRequestNotify[] | null> {
|
||||||
actionName = ActionName.GetGroupIgnoreAddRequest;
|
actionName = ActionName.GetGroupIgnoreAddRequest;
|
||||||
|
|
||||||
async _handle(payload: null): Promise<Notify[] | null> {
|
async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> {
|
||||||
const NTQQUserApi = this.core.apis.UserApi;
|
const ignoredNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(true, 10);
|
||||||
const NTQQGroupApi = this.core.apis.GroupApi;
|
const retData: any = {
|
||||||
const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10);
|
join_requests: await Promise.all(
|
||||||
const retData: Notify[] = [];
|
ignoredNotifies
|
||||||
|
|
||||||
const notifyPromises = ignoredNotifies
|
|
||||||
.filter(notify => notify.type === 7)
|
.filter(notify => notify.type === 7)
|
||||||
.map(async SSNotify => {
|
.map(async SSNotify => ({
|
||||||
const invitorUin = SSNotify.user1?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
|
request_id: SSNotify.seq,
|
||||||
const actorUin = SSNotify.user2?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user2.uid) : 0;
|
requester_uin: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||||
retData.push({
|
requester_nick: SSNotify.user1?.nickName,
|
||||||
request_id: +SSNotify.seq,
|
group_id: SSNotify.group?.groupCode,
|
||||||
invitor_uin: invitorUin,
|
|
||||||
invitor_nick: SSNotify.user1?.nickName,
|
|
||||||
group_id: +SSNotify.group?.groupCode,
|
|
||||||
message: SSNotify?.postscript,
|
|
||||||
group_name: SSNotify.group?.groupName,
|
group_name: SSNotify.group?.groupName,
|
||||||
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
|
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
|
||||||
actor: actorUin,
|
actor: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
|
||||||
requester_nick: SSNotify.user1?.nickName,
|
}))),
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(notifyPromises);
|
|
||||||
|
|
||||||
return retData;
|
return retData;
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,7 @@ const SchemaData = Type.Union([
|
|||||||
desc: Type.String(),
|
desc: Type.String(),
|
||||||
picUrl: Type.String(),
|
picUrl: Type.String(),
|
||||||
jumpUrl: Type.String(),
|
jumpUrl: Type.String(),
|
||||||
webUrl: Type.Optional(Type.String()),
|
rawArkData: Type.Optional(Type.Union([Type.Boolean(), Type.String()]))
|
||||||
rawArkData: Type.Optional(Type.Union([Type.String()]))
|
|
||||||
}),
|
}),
|
||||||
Type.Object({
|
Type.Object({
|
||||||
title: Type.String(),
|
title: Type.String(),
|
||||||
@@ -20,7 +19,6 @@ const SchemaData = Type.Union([
|
|||||||
picUrl: Type.String(),
|
picUrl: Type.String(),
|
||||||
jumpUrl: Type.String(),
|
jumpUrl: Type.String(),
|
||||||
iconUrl: Type.String(),
|
iconUrl: Type.String(),
|
||||||
webUrl: Type.Optional(Type.String()),
|
|
||||||
appId: Type.String(),
|
appId: Type.String(),
|
||||||
scene: Type.Union([Type.Number(), Type.String()]),
|
scene: Type.Union([Type.Number(), Type.String()]),
|
||||||
templateType: Type.Union([Type.Number(), Type.String()]),
|
templateType: Type.Union([Type.Number(), Type.String()]),
|
||||||
@@ -30,7 +28,7 @@ const SchemaData = Type.Union([
|
|||||||
versionId: Type.String(),
|
versionId: Type.String(),
|
||||||
sdkId: Type.String(),
|
sdkId: Type.String(),
|
||||||
withShareTicket: Type.Union([Type.Number(), Type.String()]),
|
withShareTicket: Type.Union([Type.Number(), Type.String()]),
|
||||||
rawArkData: Type.Optional(Type.Union([Type.String()]))
|
rawArkData: Type.Optional(Type.Union([Type.Boolean(), Type.String()]))
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
type Payload = Static<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
@@ -47,8 +45,7 @@ export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
|||||||
title: payload.title,
|
title: payload.title,
|
||||||
desc: payload.desc,
|
desc: payload.desc,
|
||||||
picUrl: payload.picUrl,
|
picUrl: payload.picUrl,
|
||||||
jumpUrl: payload.jumpUrl,
|
jumpUrl: payload.jumpUrl
|
||||||
webUrl: payload.webUrl,
|
|
||||||
} as MiniAppReqCustomParams;
|
} as MiniAppReqCustomParams;
|
||||||
if ('type' in payload) {
|
if ('type' in payload) {
|
||||||
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
|
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
|
||||||
@@ -66,13 +63,13 @@ export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
|||||||
verType: +verType,
|
verType: +verType,
|
||||||
shareType: +shareType,
|
shareType: +shareType,
|
||||||
versionId: versionId,
|
versionId: versionId,
|
||||||
withShareTicket: +withShareTicket,
|
withShareTicket: +withShareTicket
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const arkData = await this.core.apis.PacketApi.pkt.operation.GetMiniAppAdaptShareInfo(reqParam);
|
const arkData = await this.core.apis.PacketApi.pkt.operation.GetMiniAppAdaptShareInfo(reqParam);
|
||||||
return {
|
return {
|
||||||
data: payload.rawArkData === 'true' ? arkData : MiniAppInfoHelper.RawToSend(arkData)
|
data: payload.rawArkData ? arkData : MiniAppInfoHelper.RawToSend(arkData)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { checkFileExist, uriToLocalFile } from '@/common/file';
|
import { checkFileExist, uri2local } from '@/common/file';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
@@ -10,34 +10,30 @@ const SchemaData = Type.Object({
|
|||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
class OCRImageBase extends OneBotAction<Payload, any> {
|
export class OCRImage extends OneBotAction<Payload, any> {
|
||||||
|
actionName = ActionName.OCRImage;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const { path, success } = await uriToLocalFile(this.core.NapCatTempPath, payload.image);
|
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`);
|
throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`);
|
||||||
}
|
}
|
||||||
if (path) {
|
if (path) {
|
||||||
try {
|
|
||||||
await checkFileExist(path, 5000); // 避免崩溃
|
await checkFileExist(path, 5000); // 避免崩溃
|
||||||
const ret = await this.core.apis.SystemApi.ocrImage(path);
|
const ret = await this.core.apis.SystemApi.ocrImage(path);
|
||||||
|
fs.unlink(path, () => { });
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
throw new Error(`OCR ${payload.image}失败`);
|
throw new Error(`OCR ${payload.image}失败`);
|
||||||
}
|
}
|
||||||
return ret.result;
|
return ret.result;
|
||||||
} finally {
|
}
|
||||||
fs.unlink(path, () => { });
|
fs.unlink(path, () => { });
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`OCR ${payload.image}失败,文件可能不存在`);
|
throw new Error(`OCR ${payload.image}失败,文件可能不存在`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OCRImage extends OCRImageBase {
|
export class IOCRImage extends OCRImage {
|
||||||
actionName = ActionName.OCRImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IOCRImage extends OCRImageBase {
|
|
||||||
actionName = ActionName.IOCRImage;
|
actionName = ActionName.IOCRImage;
|
||||||
}
|
}
|
@@ -1,21 +0,0 @@
|
|||||||
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
|
|
||||||
import { ActionName } from '@/onebot/action/router';
|
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
|
||||||
cmd: Type.String(),
|
|
||||||
data: Type.String(),
|
|
||||||
rsp: Type.Union([Type.String(), Type.Boolean()], { default: true }),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
|
||||||
|
|
||||||
export class SendPacket extends GetPacketStatusDepends<Payload, any> {
|
|
||||||
payloadSchema = SchemaData;
|
|
||||||
actionName = ActionName.SendPacket;
|
|
||||||
async _handle(payload: Payload) {
|
|
||||||
const rsp = typeof payload.rsp === 'boolean' ? payload.rsp : payload.rsp === 'true';
|
|
||||||
const data = await this.core.apis.PacketApi.pkt.operation.sendPacket({ cmd: payload.cmd, data: payload.data as any }, rsp);
|
|
||||||
return typeof data === 'object' ? data.toString('hex') : undefined;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -8,18 +8,14 @@ const SchemaData = Type.Object({
|
|||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type Payload = Static<typeof SchemaData>;
|
||||||
|
|
||||||
class SetGroupSignBase extends GetPacketStatusDepends<Payload, any> {
|
export class SetGroupSign extends GetPacketStatusDepends<Payload, any> {
|
||||||
|
actionName = ActionName.SetGroupSign;
|
||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id);
|
return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class SendGroupSign extends SetGroupSign {
|
||||||
export class SetGroupSign extends SetGroupSignBase {
|
|
||||||
actionName = ActionName.SetGroupSign;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SendGroupSign extends SetGroupSignBase {
|
|
||||||
actionName = ActionName.SendGroupSign;
|
actionName = ActionName.SendGroupSign;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user