mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-28 07:40:27 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
377c780d1a | ||
|
|
aefa8985b1 | ||
|
|
b034940dfd | ||
|
|
cb8e10cc7e | ||
|
|
afed164ba1 | ||
|
|
a34a86288b | ||
|
|
50bcd71144 | ||
|
|
fa3a229827 | ||
|
|
e56b912bbd | ||
|
|
da0dd01460 | ||
|
|
578dda2f17 | ||
|
|
649165bf00 | ||
|
|
c4f7107038 | ||
|
|
7f81bf45ee | ||
|
|
7e6035d98b | ||
|
|
2405cb03d8 | ||
|
|
32d3ff6998 | ||
|
|
84f0e0f9a0 | ||
|
|
8697061a90 | ||
|
|
872a3e0100 | ||
|
|
4fcbdc4d89 | ||
|
|
176af14915 | ||
|
|
81cf1fd98e | ||
|
|
5189099146 | ||
|
|
7fc17d45ba | ||
|
|
c54f74609e | ||
|
|
a2d7ac4878 | ||
|
|
fd0afa3b25 | ||
|
|
7685cc3dfc | ||
|
|
f9c0b9d106 | ||
|
|
d31f0a45b4 | ||
|
|
7c701781a1 | ||
|
|
3c612e03ff | ||
|
|
f27db01145 | ||
|
|
ae97cfba03 | ||
|
|
162ddc1bf5 | ||
|
|
afb6ef421a | ||
|
|
173a165c4b | ||
|
|
d525f9b03d | ||
|
|
f2ba789cc0 | ||
|
|
2cdc9bdc09 | ||
|
|
c123b34d5f | ||
|
|
d25b43ebf2 | ||
|
|
8fe4a9e6ac | ||
|
|
09da80aad5 | ||
|
|
3d3f718fd5 | ||
|
|
6068abdec0 | ||
|
|
3957d7af5a | ||
|
|
a2837974fe | ||
|
|
6f8edfe570 | ||
|
|
0b655db4dd | ||
|
|
d800466a30 | ||
|
|
fa80441e36 | ||
|
|
1990761ad6 | ||
|
|
ef63812391 | ||
|
|
0f033b0ac8 | ||
|
|
9fdef3cde9 | ||
|
|
20e8643193 | ||
|
|
8645ed4d9d | ||
|
|
c0b9817ff5 | ||
|
|
b147e57c1c | ||
|
|
ad4a108781 | ||
|
|
df824d77ae | ||
|
|
19888d52dc | ||
|
|
4dc8b3ed3b | ||
|
|
8df54d5cd3 | ||
|
|
aa982b3071 | ||
|
|
8e71dec63a | ||
|
|
31bb1e5dee |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Bug 反馈
|
||||
description: 报告可能的 NapCat 异常行为
|
||||
title: '[BUG] '
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -10,6 +10,10 @@ body:
|
||||
在提交新的 Bug 反馈前,请确保您:
|
||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||
* 不与现有的某一 issue 重复
|
||||
* **不接受因发送不当内容而导致的问题报告**
|
||||
- 包括但不限于:多媒体发送失败、转发消息失败、消息被拦截等因 18+ 内容、违规内容或触发风控的问题
|
||||
- 提交 issue 前,请确认您发送的多媒体内容、链接、文本等均为正常合规内容,不会触发平台风控机制
|
||||
- 因违规内容导致的问题,一律不予受理
|
||||
- type: input
|
||||
id: system-version
|
||||
attributes:
|
||||
|
||||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -21,6 +21,8 @@ jobs:
|
||||
run: |
|
||||
npm i -g pnpm
|
||||
pnpm i
|
||||
pnpm run typecheck || exit 1
|
||||
pnpm test || exit 1
|
||||
pnpm --filter napcat-webui-frontend run build || exit 1
|
||||
pnpm run build:framework
|
||||
mv packages/napcat-framework/dist framework-dist
|
||||
@@ -45,6 +47,8 @@ jobs:
|
||||
run: |
|
||||
npm i -g pnpm
|
||||
pnpm i
|
||||
pnpm run typecheck || exit 1
|
||||
pnpm test || exit 1
|
||||
pnpm --filter napcat-webui-frontend run build || exit 1
|
||||
pnpm run build:shell
|
||||
mv packages/napcat-shell/dist shell-dist
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -10,7 +10,7 @@ permissions: write-all
|
||||
|
||||
env:
|
||||
OPENROUTER_API_URL: https://91vip.futureppo.top/v1/chat/completions
|
||||
OPENROUTER_MODEL: "glm-4.6-turbo"
|
||||
OPENROUTER_MODEL: "kimi-k2-0905-turbo"
|
||||
RELEASE_NAME: "NapCat"
|
||||
|
||||
jobs:
|
||||
|
||||
12
.vscode/launch.json
vendored
Normal file
12
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"name": "调试程序",
|
||||
"command": "pnpm run dev:shell",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
42
.vscode/settings.json
vendored
42
.vscode/settings.json
vendored
@@ -1,9 +1,37 @@
|
||||
{
|
||||
"debug.node.sourceMapPathOverrides": {
|
||||
"../../napcat-onebot/*": "${workspaceFolder}/packages/napcat-onebot/*",
|
||||
"../../napcat-core/*": "${workspaceFolder}/packages/napcat-core/*",
|
||||
"../../napcat-common/*": "${workspaceFolder}/packages/napcat-common/*",
|
||||
"../../napcat-webui-backend/*": "${workspaceFolder}/packages/napcat-webui-backend/*",
|
||||
"../../napcat-pty/*": "${workspaceFolder}/packages/napcat-pty/*"
|
||||
}
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
".env.universal": ".env.*",
|
||||
"vite.config.ts": "vite*.ts",
|
||||
"README.md": "CODE_OF_CONDUCT.md, RELEASES.md, CONTRIBUTING.md, CHANGELOG.md, SECURITY.md",
|
||||
"tsconfig.json": "tsconfig.*.json, env.d.ts",
|
||||
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
|
||||
},
|
||||
"css.customData": [
|
||||
".vscode/tailwindcss.json"
|
||||
],
|
||||
"editor.detectIndentation": false,
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": false,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnSaveMode": "file",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "always"
|
||||
},
|
||||
"files.autoSave": "onFocusChange",
|
||||
"javascript.preferences.quoteStyle": "single",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"javascript.format.semicolons": "insert",
|
||||
"typescript.format.semicolons": "insert",
|
||||
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
|
||||
"typescript.format.insertSpaceBeforeFunctionParenthesis": true,
|
||||
"typescript.format.insertSpaceAfterConstructor": true,
|
||||
"javascript.format.insertSpaceAfterConstructor": true,
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.importModuleSpecifierEnding": "minimal",
|
||||
"javascript.preferences.importModuleSpecifier": "non-relative",
|
||||
"javascript.preferences.importModuleSpecifierEnding": "minimal",
|
||||
"typescript.disableAutomaticTypeAcquisition": true
|
||||
}
|
||||
55
.vscode/tailwindcss.json
vendored
Normal file
55
.vscode/tailwindcss.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"version": 1.1,
|
||||
"atDirectives": [
|
||||
{
|
||||
"name": "@tailwind",
|
||||
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@apply",
|
||||
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@responsive",
|
||||
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@screen",
|
||||
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@variants",
|
||||
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -43,7 +43,7 @@ _Modern protocol-side framework implemented based on NTQQ._
|
||||
|
||||
**首次使用**请务必查看如下文档看使用教程
|
||||
|
||||
> 项目非盈利,对接问题/基础问题/下层框架问题 请自行搜索解决,本项目社区不提供此类解答。
|
||||
> 项目非盈利,涉及 对接问题/基础问题/下层框架问题 请自行搜索解决,本项目社区不提供此类解答。
|
||||
|
||||
## Link
|
||||
|
||||
|
||||
52
eslint.config.js
Normal file
52
eslint.config.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import neostandard from 'neostandard';
|
||||
|
||||
/** 尾随逗号 */
|
||||
const commaDangle = val => {
|
||||
if (val?.rules?.['@stylistic/comma-dangle']?.[0] === 'warn') {
|
||||
const rule = val?.rules?.['@stylistic/comma-dangle']?.[1];
|
||||
Object.keys(rule).forEach(key => {
|
||||
rule[key] = 'always-multiline';
|
||||
});
|
||||
val.rules['@stylistic/comma-dangle'][1] = rule;
|
||||
}
|
||||
|
||||
/** 三元表达式 */
|
||||
if (val?.rules?.['@stylistic/indent']) {
|
||||
val.rules['@stylistic/indent'][2] = {
|
||||
...val.rules?.['@stylistic/indent']?.[2],
|
||||
flatTernaryExpressions: true,
|
||||
offsetTernaryExpressions: false,
|
||||
};
|
||||
}
|
||||
|
||||
/** 支持下划线 - 禁用 camelcase 规则 */
|
||||
if (val?.rules?.camelcase) {
|
||||
val.rules.camelcase = 'off';
|
||||
}
|
||||
|
||||
/** 未使用的变量强制报错 */
|
||||
if (val?.rules?.['@typescript-eslint/no-unused-vars']) {
|
||||
val.rules['@typescript-eslint/no-unused-vars'] = ['error', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
}];
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
/** 忽略的文件 */
|
||||
const ignores = [
|
||||
'node_modules',
|
||||
'**/dist/**',
|
||||
'launcher',
|
||||
];
|
||||
|
||||
const options = neostandard({
|
||||
ts: true,
|
||||
ignores,
|
||||
semi: true, // 强制使用分号
|
||||
}).map(commaDangle);
|
||||
|
||||
export default options;
|
||||
14
package.json
14
package.json
@@ -9,16 +9,22 @@
|
||||
"build:framework": "pnpm --filter napcat-framework run build || exit 1",
|
||||
"build:webui": "pnpm --filter napcat-webui-frontend run build || exit 1",
|
||||
"dev:shell": "pnpm --filter napcat-develop run dev || exit 1",
|
||||
"typecheck": "pnpm -w -r --filter napcat-shell --filter napcat-framework run typecheck"
|
||||
"typecheck": "pnpm -r --if-present run typecheck",
|
||||
"test": "pnpm --filter napcat-test run test",
|
||||
"test:ui": "pnpm --filter napcat-test run test:ui",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@vitejs/plugin-react-swc": "^4.2.2",
|
||||
"inversify": "^7.10.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"@vitest/ui": "^4.0.9",
|
||||
"eslint": "^9.39.1",
|
||||
"neostandard": "^0.12.2",
|
||||
"typescript": "^5.3.0",
|
||||
"vite": "^6.4.1",
|
||||
"vite-plugin-cp": "^6.0.3"
|
||||
"vite-plugin-cp": "^6.0.3",
|
||||
"vitest": "^4.0.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.0.0",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts"
|
||||
@@ -13,14 +16,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"compressing": "^1.10.1",
|
||||
"json5": "^2.2.3",
|
||||
"ajv": "^8.13.0",
|
||||
"file-type": "^21.0.0",
|
||||
"napcat-image-size": "workspace:*",
|
||||
"napcat-core": "workspace:*",
|
||||
"silk-wasm": "^3.6.1",
|
||||
"winston": "^3.17.0"
|
||||
"silk-wasm": "^3.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Peer } from 'napcat-core/index';
|
||||
import { Peer } from './types';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
class TimeBasedCache<K, V> {
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import crypto, { randomUUID } from 'crypto';
|
||||
import path from 'node:path';
|
||||
import { solveProblem } from '@/napcat-common/helper';
|
||||
import { solveProblem } from '@/napcat-common/src/helper';
|
||||
|
||||
export interface HttpDownloadOptions {
|
||||
url: string;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'fs';
|
||||
import os from 'node:os';
|
||||
import { QQLevel } from 'napcat-core/index';
|
||||
import { QQVersionConfigType } from './types';
|
||||
import { QQVersionConfigType, QQLevel } from './types';
|
||||
import { RequestUtil } from './request';
|
||||
|
||||
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) => {
|
||||
@@ -212,3 +212,81 @@ export function parseAppidFromMajor (nodeMajor: string): string | undefined {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const baseUrl = 'https://github.com/NapNeko/NapCatQQ.git/info/refs?service=git-upload-pack';
|
||||
const urls = [
|
||||
'https://j.1win.ggff.net/' + baseUrl,
|
||||
'https://git.yylx.win/' + baseUrl,
|
||||
'https://ghfile.geekertao.top/' + baseUrl,
|
||||
'https://gh-proxy.net/' + baseUrl,
|
||||
'https://ghm.078465.xyz/' + baseUrl,
|
||||
'https://gitproxy.127731.xyz/' + baseUrl,
|
||||
'https://jiashu.1win.eu.org/' + baseUrl,
|
||||
baseUrl,
|
||||
];
|
||||
|
||||
async function testUrl (url: string): Promise<boolean> {
|
||||
try {
|
||||
await PromiseTimer(RequestUtil.HttpGetText(url), 5000);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function findAvailableUrl (): Promise<string | null> {
|
||||
for (const url of urls) {
|
||||
if (await testUrl(url)) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getAllTags (): Promise<string[]> {
|
||||
const availableUrl = await findAvailableUrl();
|
||||
if (!availableUrl) {
|
||||
throw new Error('No available URL for fetching tags');
|
||||
}
|
||||
const raw = await RequestUtil.HttpGetText(availableUrl);
|
||||
return raw
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/refs\/tags\/(.+)$/);
|
||||
return match ? match[1] : null;
|
||||
})
|
||||
.filter(tag => tag !== null && !tag!.endsWith('^{}')) as string[];
|
||||
}
|
||||
|
||||
|
||||
export async function getLatestTag (): Promise<string> {
|
||||
const tags = await getAllTags();
|
||||
|
||||
tags.sort((a, b) => compareVersion(a, b));
|
||||
|
||||
const latest = tags.at(-1);
|
||||
if (!latest) {
|
||||
throw new Error('No tags found');
|
||||
}
|
||||
// 去掉开头的 v
|
||||
return latest.replace(/^v/, '');
|
||||
}
|
||||
|
||||
|
||||
function compareVersion (a: string, b: string): number {
|
||||
const normalize = (v: string) =>
|
||||
v.replace(/^v/, '') // 去掉开头的 v
|
||||
.split('.')
|
||||
.map(n => parseInt(n) || 0);
|
||||
|
||||
const pa = normalize(a);
|
||||
const pb = normalize(b);
|
||||
const len = Math.max(pa.length, pb.length);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const na = pa[i] || 0;
|
||||
const nb = pb[i] || 0;
|
||||
if (na !== nb) return na - nb;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
24
packages/napcat-common/src/log-interface.ts
Normal file
24
packages/napcat-common/src/log-interface.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal',
|
||||
}
|
||||
export interface ILogWrapper {
|
||||
fileLogEnabled: boolean;
|
||||
consoleLogEnabled: boolean;
|
||||
cleanOldLogs (logDir: string): void;
|
||||
setFileAndConsoleLogLevel (fileLogLevel: LogLevel, consoleLogLevel: LogLevel): void;
|
||||
setLogSelfInfo (selfInfo: { nick: string; uid: string; }): void;
|
||||
setFileLogEnabled (isEnabled: boolean): void;
|
||||
setConsoleLogEnabled (isEnabled: boolean): void;
|
||||
formatMsg (msg: any[]): string;
|
||||
_log (level: LogLevel, ...args: any[]): void;
|
||||
log (...args: any[]): void;
|
||||
logDebug (...args: any[]): void;
|
||||
logError (...args: any[]): void;
|
||||
logWarn (...args: any[]): void;
|
||||
logFatal (...args: any[]): void;
|
||||
logMessage (msg: unknown, selfInfo: unknown): void;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Peer } from 'napcat-core/index';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { Peer } from './types';
|
||||
export class LimitedHashTable<K, V> {
|
||||
private readonly keyToValue: Map<K, V> = new Map();
|
||||
private readonly valueToKey: Map<V, K> = new Map();
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
/**
|
||||
* 性能监控器 - 用于统计函数调用次数、耗时等信息
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface FunctionStats {
|
||||
name: string;
|
||||
callCount: number;
|
||||
totalTime: number;
|
||||
averageTime: number;
|
||||
minTime: number;
|
||||
maxTime: number;
|
||||
fileName?: string;
|
||||
lineNumber?: number;
|
||||
}
|
||||
|
||||
export class PerformanceMonitor {
|
||||
private static instance: PerformanceMonitor;
|
||||
private stats = new Map<string, FunctionStats>();
|
||||
private startTimes = new Map<string, number>();
|
||||
private reportInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
static getInstance (): PerformanceMonitor {
|
||||
if (!PerformanceMonitor.instance) {
|
||||
PerformanceMonitor.instance = new PerformanceMonitor();
|
||||
// 启动定时统计报告
|
||||
PerformanceMonitor.instance.startPeriodicReport();
|
||||
}
|
||||
return PerformanceMonitor.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始定时统计报告 (每60秒)
|
||||
*/
|
||||
private startPeriodicReport (): void {
|
||||
if (this.reportInterval) {
|
||||
clearInterval(this.reportInterval);
|
||||
}
|
||||
|
||||
this.reportInterval = setInterval(() => {
|
||||
if (this.stats.size > 0) {
|
||||
this.printPeriodicReport();
|
||||
this.writeDetailedLogToFile();
|
||||
}
|
||||
}, 60000); // 60秒
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时统计报告
|
||||
*/
|
||||
stopPeriodicReport (): void {
|
||||
if (this.reportInterval) {
|
||||
clearInterval(this.reportInterval);
|
||||
this.reportInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印定时统计报告 (简化版本)
|
||||
*/
|
||||
private printPeriodicReport (): void {
|
||||
const now = new Date().toLocaleString();
|
||||
console.log(`\n=== 性能监控定时报告 [${now}] ===`);
|
||||
|
||||
const totalFunctions = this.stats.size;
|
||||
const totalCalls = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.callCount, 0);
|
||||
const totalTime = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.totalTime, 0);
|
||||
|
||||
console.log(`📊 总览: ${totalFunctions} 个函数, ${totalCalls} 次调用, 总耗时: ${totalTime.toFixed(2)}ms`);
|
||||
|
||||
// 显示Top 5最活跃的函数
|
||||
console.log('\n🔥 最活跃函数 (Top 5):');
|
||||
this.getTopByCallCount(5).forEach((stat, index) => {
|
||||
console.log(`${index + 1}. ${stat.name} - 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
// 显示Top 5最耗时的函数
|
||||
console.log('\n⏱️ 最耗时函数 (Top 5):');
|
||||
this.getTopByTotalTime(5).forEach((stat, index) => {
|
||||
console.log(`${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均: ${stat.averageTime.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
console.log('===============================\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将详细统计数据写入日志文件
|
||||
*/
|
||||
private writeDetailedLogToFile (): void {
|
||||
try {
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().replace(/[:.]/g, '-').split('T')[0];
|
||||
const timeStr = now.toTimeString().split(' ')[0]?.replace(/:/g, '-') || 'unknown-time';
|
||||
const timestamp = `${dateStr}_${timeStr}`;
|
||||
const fileName = `${timestamp}.log.txt`;
|
||||
const logPath = path.join(process.cwd(), 'logs', fileName);
|
||||
|
||||
// 确保logs目录存在
|
||||
const logsDir = path.dirname(logPath);
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const totalFunctions = this.stats.size;
|
||||
const totalCalls = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.callCount, 0);
|
||||
const totalTime = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.totalTime, 0);
|
||||
|
||||
let logContent = '';
|
||||
logContent += '=== 性能监控详细报告 ===\n';
|
||||
logContent += `生成时间: ${now.toLocaleString()}\n`;
|
||||
logContent += '统计周期: 60秒\n';
|
||||
logContent += `总览: ${totalFunctions} 个函数, ${totalCalls} 次调用, 总耗时: ${totalTime.toFixed(2)}ms\n\n`;
|
||||
|
||||
// 详细函数统计
|
||||
logContent += '=== 所有函数详细统计 ===\n';
|
||||
const allStats = this.getStats().sort((a, b) => b.totalTime - a.totalTime);
|
||||
|
||||
allStats.forEach((stat, index) => {
|
||||
logContent += `${index + 1}. 函数: ${stat.name}\n`;
|
||||
logContent += ` 文件: ${stat.fileName || 'N/A'}\n`;
|
||||
logContent += ` 行号: ${stat.lineNumber || 'N/A'}\n`;
|
||||
logContent += ` 调用次数: ${stat.callCount}\n`;
|
||||
logContent += ` 总耗时: ${stat.totalTime.toFixed(4)}ms\n`;
|
||||
logContent += ` 平均耗时: ${stat.averageTime.toFixed(4)}ms\n`;
|
||||
logContent += ` 最小耗时: ${stat.minTime === Infinity ? 'N/A' : stat.minTime.toFixed(4)}ms\n`;
|
||||
logContent += ` 最大耗时: ${stat.maxTime.toFixed(4)}ms\n`;
|
||||
logContent += ` 性能占比: ${((stat.totalTime / totalTime) * 100).toFixed(2)}%\n`;
|
||||
logContent += '\n';
|
||||
});
|
||||
|
||||
// 排行榜统计
|
||||
logContent += '=== 总耗时排行榜 (Top 20) ===\n';
|
||||
this.getTopByTotalTime(20).forEach((stat, index) => {
|
||||
logContent += `${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 调用: ${stat.callCount}次, 平均: ${stat.averageTime.toFixed(2)}ms\n`;
|
||||
});
|
||||
|
||||
logContent += '\n=== 调用次数排行榜 (Top 20) ===\n';
|
||||
this.getTopByCallCount(20).forEach((stat, index) => {
|
||||
logContent += `${index + 1}. ${stat.name} - 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均: ${stat.averageTime.toFixed(2)}ms\n`;
|
||||
});
|
||||
|
||||
logContent += '\n=== 平均耗时排行榜 (Top 20) ===\n';
|
||||
this.getTopByAverageTime(20).forEach((stat, index) => {
|
||||
logContent += `${index + 1}. ${stat.name} - 平均: ${stat.averageTime.toFixed(2)}ms, 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms\n`;
|
||||
});
|
||||
|
||||
logContent += '\n=== 性能热点分析 ===\n';
|
||||
// 找出最耗时的前10个函数
|
||||
const hotSpots = this.getTopByTotalTime(10);
|
||||
hotSpots.forEach((stat, index) => {
|
||||
const efficiency = stat.callCount / stat.totalTime; // 每毫秒的调用次数
|
||||
logContent += `${index + 1}. ${stat.name}\n`;
|
||||
logContent += ` 性能影响: ${((stat.totalTime / totalTime) * 100).toFixed(2)}%\n`;
|
||||
logContent += ` 调用效率: ${efficiency.toFixed(4)} 调用/ms\n`;
|
||||
logContent += ` 优化建议: ${stat.averageTime > 10
|
||||
? '考虑优化此函数的执行效率'
|
||||
: stat.callCount > 1000
|
||||
? '考虑减少此函数的调用频率'
|
||||
: '性能表现良好'}\n\n`;
|
||||
});
|
||||
|
||||
logContent += '=== 报告结束 ===\n';
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(logPath, logContent, 'utf8');
|
||||
console.log(`📄 详细性能报告已保存到: ${logPath}`);
|
||||
} catch (error) {
|
||||
console.error('写入性能日志文件时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始记录函数调用
|
||||
*/
|
||||
startFunction (functionName: string, fileName?: string, lineNumber?: number): string {
|
||||
const callId = `${functionName}_${Date.now()}_${Math.random()}`;
|
||||
this.startTimes.set(callId, performance.now());
|
||||
|
||||
// 初始化或更新统计信息
|
||||
if (!this.stats.has(functionName)) {
|
||||
this.stats.set(functionName, {
|
||||
name: functionName,
|
||||
callCount: 0,
|
||||
totalTime: 0,
|
||||
averageTime: 0,
|
||||
minTime: Infinity,
|
||||
maxTime: 0,
|
||||
fileName,
|
||||
lineNumber,
|
||||
});
|
||||
}
|
||||
|
||||
const stat = this.stats.get(functionName)!;
|
||||
stat.callCount++;
|
||||
|
||||
return callId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束记录函数调用
|
||||
*/
|
||||
endFunction (callId: string, functionName: string): void {
|
||||
const startTime = this.startTimes.get(callId);
|
||||
if (!startTime) return;
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
this.startTimes.delete(callId);
|
||||
|
||||
const stat = this.stats.get(functionName);
|
||||
if (!stat) return;
|
||||
|
||||
stat.totalTime += duration;
|
||||
stat.averageTime = stat.totalTime / stat.callCount;
|
||||
stat.minTime = Math.min(stat.minTime, duration);
|
||||
stat.maxTime = Math.max(stat.maxTime, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有统计信息
|
||||
*/
|
||||
getStats (): FunctionStats[] {
|
||||
return Array.from(this.stats.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排行榜 - 按总耗时排序
|
||||
*/
|
||||
getTopByTotalTime (limit = 20): FunctionStats[] {
|
||||
return this.getStats()
|
||||
.sort((a, b) => b.totalTime - a.totalTime)
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排行榜 - 按调用次数排序
|
||||
*/
|
||||
getTopByCallCount (limit = 20): FunctionStats[] {
|
||||
return this.getStats()
|
||||
.sort((a, b) => b.callCount - a.callCount)
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取排行榜 - 按平均耗时排序
|
||||
*/
|
||||
getTopByAverageTime (limit = 20): FunctionStats[] {
|
||||
return this.getStats()
|
||||
.sort((a, b) => b.averageTime - a.averageTime)
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空统计数据
|
||||
*/
|
||||
clear (): void {
|
||||
this.stats.clear();
|
||||
this.startTimes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印统计报告
|
||||
*/
|
||||
printReport (): void {
|
||||
console.log('\n=== 函数性能监控报告 ===');
|
||||
|
||||
console.log('\n🔥 总耗时排行榜 (Top 10):');
|
||||
this.getTopByTotalTime(10).forEach((stat, index) => {
|
||||
console.log(`${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 调用次数: ${stat.callCount}, 平均耗时: ${stat.averageTime.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
console.log('\n📈 调用次数排行榜 (Top 10):');
|
||||
this.getTopByCallCount(10).forEach((stat, index) => {
|
||||
console.log(`${index + 1}. ${stat.name} - 调用次数: ${stat.callCount}, 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均耗时: ${stat.averageTime.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
console.log('\n⏱️ 平均耗时排行榜 (Top 10):');
|
||||
this.getTopByAverageTime(10).forEach((stat, index) => {
|
||||
console.log(`${index + 1}. ${stat.name} - 平均耗时: ${stat.averageTime.toFixed(2)}ms, 调用次数: ${stat.callCount}, 总耗时: ${stat.totalTime.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
console.log('\n========================\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JSON格式的统计数据
|
||||
*/
|
||||
toJSON (): FunctionStats[] {
|
||||
return this.getStats();
|
||||
}
|
||||
}
|
||||
|
||||
// 全局性能监控器实例
|
||||
export const performanceMonitor = PerformanceMonitor.getInstance();
|
||||
|
||||
// 在进程退出时打印报告并停止定时器
|
||||
if (typeof process !== 'undefined') {
|
||||
process.on('exit', () => {
|
||||
performanceMonitor.stopPeriodicReport();
|
||||
performanceMonitor.printReport();
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
performanceMonitor.stopPeriodicReport();
|
||||
performanceMonitor.printReport();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
performanceMonitor.stopPeriodicReport();
|
||||
performanceMonitor.printReport();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
24
packages/napcat-common/src/status-interface.ts
Normal file
24
packages/napcat-common/src/status-interface.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
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 interface IStatusHelperSubscription {
|
||||
on (event: 'statusUpdate', listener: (status: SystemStatus) => void): this;
|
||||
off (event: 'statusUpdate', listener: (status: SystemStatus) => void): this;
|
||||
emit (event: 'statusUpdate', status: SystemStatus): boolean;
|
||||
}
|
||||
@@ -19,4 +19,4 @@ class Store {
|
||||
|
||||
const store = new Store();
|
||||
|
||||
export default store;
|
||||
export default store;
|
||||
|
||||
6
packages/napcat-common/src/subscription-interface.ts
Normal file
6
packages/napcat-common/src/subscription-interface.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export type LogListener = (msg: string) => void;
|
||||
export interface ISubscription {
|
||||
subscribe (listener: LogListener): void;
|
||||
unsubscribe (listener: LogListener): void;
|
||||
notify (msg: string): void;
|
||||
}
|
||||
@@ -15,3 +15,14 @@ export type QQVersionConfigType = {
|
||||
export type QQAppidTableType = {
|
||||
[key: string]: { appid: string, qua: string };
|
||||
};
|
||||
export interface Peer {
|
||||
chatType: number; // 聊天类型
|
||||
peerUid: string; // 对等方的唯一标识符
|
||||
guildId?: string; // 可选的频道ID
|
||||
}
|
||||
export interface QQLevel {
|
||||
crownNum: number;
|
||||
sunNum: number;
|
||||
moonNum: number;
|
||||
starNum: number;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
// @ts-ignore
|
||||
export const napCatVersion = (typeof import.meta?.env !== 'undefined' && import.meta.env.VITE_NAPCAT_VERSION) || 'alpha';
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"rootDir": ".",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
@@ -36,8 +36,8 @@
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/napcat-common/*": [
|
||||
"src/*"
|
||||
"@/*": [
|
||||
"../*"
|
||||
]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
|
||||
@@ -5,7 +5,7 @@ export class NodeIDependsAdapter {
|
||||
|
||||
}
|
||||
|
||||
onMSFSsoError (_args: unknown) {
|
||||
onMSFSsoError (_code: number, _desc: string) {
|
||||
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@ export class NodeIDependsAdapter {
|
||||
|
||||
// console.log('[NodeIDependsAdapter] onSendMsfReply', _seq, _cmd, _uk1, _uk2, Buffer.from(_rsp.pbBuffer).toString('hex'));
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,7 @@ import {
|
||||
IMAGE_HTTP_HOST_NT,
|
||||
Peer,
|
||||
PicElement,
|
||||
PicSubType,
|
||||
RawMessage,
|
||||
SendFileElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendVideoElement,
|
||||
} from '@/napcat-core/types';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
@@ -19,16 +14,9 @@ import { InstanceContext, NapCatCore, SearchResultItem } from '@/napcat-core/ind
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import { RkeyManager } from '@/napcat-core/helper/rkey';
|
||||
import { calculateFileMD5 } from 'napcat-common/src/file';
|
||||
import pathLib from 'node:path';
|
||||
import { defaultVideoThumbB64 } from 'napcat-common/src/video';
|
||||
import { encodeSilk } from 'napcat-common/src/audio';
|
||||
import { SendMessageContext } from 'napcat-onebot/api/msg';
|
||||
import { getFileTypeForSendType } from '../helper/msg';
|
||||
import { FFmpegService } from 'napcat-common/src/ffmpeg';
|
||||
import { rkeyDataType } from '../types/file';
|
||||
import { NapProtoMsg } from 'napcat-protobuf';
|
||||
import { FileId } from '../packet/transformer/proto/misc/fileid';
|
||||
import { imageSizeFallBack } from 'napcat-image-size';
|
||||
|
||||
export class NTQQFileApi {
|
||||
context: InstanceContext;
|
||||
@@ -45,7 +33,7 @@ export class NTQQFileApi {
|
||||
'http://ss.xingzhige.com/music_card/rkey',
|
||||
'https://secret-service.bietiaop.com/rkeys',
|
||||
],
|
||||
this.context.logger
|
||||
this.context.logger
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,165 +169,6 @@ export class NTQQFileApi {
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendFileElement (context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
|
||||
const {
|
||||
fileName: _fileName,
|
||||
path,
|
||||
fileSize,
|
||||
} = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: '',
|
||||
fileElement: {
|
||||
fileName: fileName || _fileName,
|
||||
folderId,
|
||||
filePath: path,
|
||||
fileSize: fileSize.toString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPicElement (context: SendMessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const imageSize = await imageSizeFallBack(picPath);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.PIC,
|
||||
elementId: '',
|
||||
picElement: {
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize.toString(),
|
||||
picWidth: imageSize.width,
|
||||
picHeight: imageSize.height,
|
||||
fileName,
|
||||
sourcePath: path,
|
||||
original: true,
|
||||
picType: await getFileTypeForSendType(picPath),
|
||||
picSubType: subType,
|
||||
fileUuid: '',
|
||||
fileSubId: '',
|
||||
thumbFileSize: 0,
|
||||
summary,
|
||||
} as PicElement,
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendVideoElement (context: SendMessageContext, filePath: string, fileName: string = '', _diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
let videoInfo = {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 15,
|
||||
format: 'mp4',
|
||||
size: 0,
|
||||
filePath,
|
||||
};
|
||||
let fileExt = 'mp4';
|
||||
try {
|
||||
const tempExt = (await fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.context.logger.logError('获取文件类型失败', e);
|
||||
}
|
||||
const newFilePath = `${filePath}.${fileExt}`;
|
||||
fs.copyFileSync(filePath, newFilePath);
|
||||
context.deleteAfterSentFiles.push(newFilePath);
|
||||
filePath = newFilePath;
|
||||
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
|
||||
fs.mkdirSync(pathLib.dirname(thumbDir), { recursive: true });
|
||||
const thumbPath = pathLib.join(pathLib.dirname(thumbDir), `${md5}_0.png`);
|
||||
try {
|
||||
videoInfo = await FFmpegService.getVideoInfo(filePath, thumbPath);
|
||||
if (!fs.existsSync(thumbPath)) {
|
||||
this.context.logger.logError('获取视频缩略图失败', new Error('缩略图不存在'));
|
||||
throw new Error('获取视频缩略图失败');
|
||||
}
|
||||
} catch (e) {
|
||||
this.context.logger.logError('获取视频信息失败', e);
|
||||
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
|
||||
}
|
||||
if (_diyThumbPath) {
|
||||
try {
|
||||
await this.copyFile(_diyThumbPath, thumbPath);
|
||||
} catch (e) {
|
||||
this.context.logger.logError('复制自定义缩略图失败', e);
|
||||
}
|
||||
}
|
||||
context.deleteAfterSentFiles.push(thumbPath);
|
||||
const thumbSize = (await fsPromises.stat(thumbPath)).size;
|
||||
const thumbMd5 = await calculateFileMD5(thumbPath);
|
||||
context.deleteAfterSentFiles.push(thumbPath);
|
||||
|
||||
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith(`.${fileExt.toLocaleLowerCase()}`) ? (fileName || _fileName) : `${fileName || _fileName}.${fileExt}`;
|
||||
return {
|
||||
elementType: ElementType.VIDEO,
|
||||
elementId: '',
|
||||
videoElement: {
|
||||
fileName: uploadName,
|
||||
filePath: path,
|
||||
videoMd5: md5,
|
||||
thumbMd5,
|
||||
fileTime: videoInfo.time,
|
||||
thumbPath: new Map([[0, thumbPath]]),
|
||||
thumbSize,
|
||||
thumbWidth: videoInfo.width,
|
||||
thumbHeight: videoInfo.height,
|
||||
fileSize: fileSize.toString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPttElement (_context: SendMessageContext, pttPath: string): Promise<SendPttElement> {
|
||||
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
|
||||
if (!silkPath) {
|
||||
throw new Error('语音转换失败, 请检查语音文件是否正常');
|
||||
}
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
if (converted) {
|
||||
fsPromises.unlink(silkPath).then().catch((e) => this.context.logger.logError('删除临时文件失败', e));
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
elementId: '',
|
||||
pttElement: {
|
||||
fileName,
|
||||
filePath: path,
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize.toString(),
|
||||
duration: duration ?? 1,
|
||||
formatType: 1,
|
||||
voiceType: 1,
|
||||
voiceChangeType: 0,
|
||||
canConvert2Text: true,
|
||||
waveAmplitudes: [
|
||||
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
|
||||
],
|
||||
fileSubId: '',
|
||||
playState: 1,
|
||||
autoConvertText: 0,
|
||||
storeID: 0,
|
||||
otherBusinessInfo: {
|
||||
aiVoiceType: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async downloadFileForModelId (peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) {
|
||||
const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelRichMediaService/downloadFileForModelId',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FriendRequest, FriendV2 } from 'napcat-core/types';
|
||||
import { BuddyListReqType, InstanceContext, NapCatCore } from 'napcat-core/index';
|
||||
import { FriendRequest, FriendV2 } from '@/napcat-core/types';
|
||||
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/napcat-core/index';
|
||||
import { LimitedHashTable } from 'napcat-common/src/message-unique';
|
||||
|
||||
export class NTQQFriendApi {
|
||||
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
} from '@/napcat-core/index';
|
||||
import { isNumeric, solveAsyncProblem } from 'napcat-common/src/helper';
|
||||
import { LimitedHashTable } from 'napcat-common/src/message-unique';
|
||||
import { NTEventWrapper } from 'napcat-common/src/event';
|
||||
import { CancelableTask, TaskExecutor } from 'napcat-common/src/cancel-task';
|
||||
import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data';
|
||||
import { NTEventWrapper } from '../helper/event';
|
||||
|
||||
export class NTQQGroupApi {
|
||||
context: InstanceContext;
|
||||
@@ -395,7 +395,7 @@ export class NTQQGroupApi {
|
||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||
[groupCode, [uid], forced],
|
||||
(ret) => ret.result === 0,
|
||||
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid),
|
||||
(params: string, _: any, members: Map<string, GroupMember>) => params === GroupCode && members.size > 0 && members.has(uid),
|
||||
1,
|
||||
forced ? 2500 : 250
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as os from 'os';
|
||||
import offset from '@/napcat-core/external/napi2native.json';
|
||||
import { InstanceContext, NapCatCore } from '@/napcat-core/index';
|
||||
import { LogWrapper } from 'napcat-common/src/log';
|
||||
import { PacketClientSession } from '@/napcat-core/packet/clientSession';
|
||||
import { napCatVersion } from 'napcat-common/src/version';
|
||||
import { LogWrapper } from '../helper/log';
|
||||
|
||||
interface OffsetType {
|
||||
[key: string]: {
|
||||
|
||||
32
packages/napcat-core/external/appid.json
vendored
32
packages/napcat-core/external/appid.json
vendored
@@ -466,5 +466,37 @@
|
||||
"6.9.85-42086": {
|
||||
"appid": 537320237,
|
||||
"qua": "V1_MAC_NQ_6.9.85_42086_GW_B"
|
||||
},
|
||||
"9.9.23-42430": {
|
||||
"appid": 537320212,
|
||||
"qua": "V1_WIN_NQ_9.9.23_42430_GW_B"
|
||||
},
|
||||
"9.9.25-42744": {
|
||||
"appid": 537328470,
|
||||
"qua": "V1_WIN_NQ_9.9.23_42744_GW_B"
|
||||
},
|
||||
"6.9.86-42744": {
|
||||
"appid": 537328495,
|
||||
"qua": "V1_MAC_NQ_6.9.85_42744_GW_B"
|
||||
},
|
||||
"9.9.25-42905": {
|
||||
"appid": 537328521,
|
||||
"qua": "V1_WIN_NQ_9.9.25_42905_GW_B"
|
||||
},
|
||||
"6.9.86-42905": {
|
||||
"appid": 537328546,
|
||||
"qua": "V1_MAC_NQ_6.9.86_42905_GW_B"
|
||||
},
|
||||
"3.2.22-42941": {
|
||||
"appid": 537328659,
|
||||
"qua": "V1_LNX_NQ_3.2.22_42941_GW_B"
|
||||
},
|
||||
"9.9.25-42941": {
|
||||
"appid": 537328623,
|
||||
"qua": "V1_WIN_NQ_9.9.25_42941_GW_B"
|
||||
},
|
||||
"6.9.86-42941": {
|
||||
"appid": 537328648,
|
||||
"qua": "V1_MAC_NQ_6.9.86_42941_GW_B"
|
||||
}
|
||||
}
|
||||
36
packages/napcat-core/external/napi2native.json
vendored
36
packages/napcat-core/external/napi2native.json
vendored
@@ -90,5 +90,41 @@
|
||||
"3.2.21-42086-x64": {
|
||||
"send": "5B42CF0",
|
||||
"recv": "2FDA6F0"
|
||||
},
|
||||
"9.9.23-42430-x64": {
|
||||
"send": "0A01A34",
|
||||
"recv": "1D1CFF9"
|
||||
},
|
||||
"9.9.25-42744-x64": {
|
||||
"send": "0A0D104",
|
||||
"recv": "1D3E7F9"
|
||||
},
|
||||
"6.9.85-42744-arm64": {
|
||||
"send": "23DFEF0",
|
||||
"recv": "095FD80"
|
||||
},
|
||||
"9.9.25-42905-x64": {
|
||||
"send": "0A12E74",
|
||||
"recv": "1D450FD"
|
||||
},
|
||||
"6.9.86-42905-arm64": {
|
||||
"send": "2342408",
|
||||
"recv": "09639B8"
|
||||
},
|
||||
"3.2.22-42941-x64": {
|
||||
"send": "5BC1630",
|
||||
"recv": "3011E00"
|
||||
},
|
||||
"3.2.22-42941-arm64": {
|
||||
"send": "3DC90AC",
|
||||
"recv": "1497A70"
|
||||
},
|
||||
"9.9.25-42941-x64": {
|
||||
"send": "0A131D4",
|
||||
"recv": "1D4547D"
|
||||
},
|
||||
"6.9.86-42941-arm64": {
|
||||
"send": "2346108",
|
||||
"recv": "09675F0"
|
||||
}
|
||||
}
|
||||
36
packages/napcat-core/external/packet.json
vendored
36
packages/napcat-core/external/packet.json
vendored
@@ -602,5 +602,41 @@
|
||||
"3.2.21-42086-arm64": {
|
||||
"send": "6B13038",
|
||||
"recv": "6B169C8"
|
||||
},
|
||||
"9.9.23-42430-x64": {
|
||||
"send": "2C9A4A0",
|
||||
"recv": "2C9DA20"
|
||||
},
|
||||
"9.9.25-42744-x64": {
|
||||
"send": "2CD8E40",
|
||||
"recv": "2CDC3C0"
|
||||
},
|
||||
"6.9.86-42744-arm64": {
|
||||
"send": "3DCC840",
|
||||
"recv": "3DCF150"
|
||||
},
|
||||
"9.9.25-42905-x64": {
|
||||
"send": "2CE46A0",
|
||||
"recv": "2CE7C20"
|
||||
},
|
||||
"6.9.86-42905-arm64": {
|
||||
"send": "3DD6098",
|
||||
"recv": "3DD89A8"
|
||||
},
|
||||
"3.2.22-42941-x64": {
|
||||
"send": "A8AD8A0",
|
||||
"recv": "A8B1320"
|
||||
},
|
||||
"9.9.25-42941-x64": {
|
||||
"send": "2CE4DA0",
|
||||
"recv": "2CE8320"
|
||||
},
|
||||
"3.2.22-42941-arm64": {
|
||||
"send": "6BC95E8",
|
||||
"recv": "6BCCF78"
|
||||
},
|
||||
"6.9.86-42941-arm64": {
|
||||
"send": "3DDDAD0",
|
||||
"recv": "3DE03E0"
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@ import fsPromise from 'fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||
import { LogWrapper } from '@/napcat-common/log';
|
||||
import { EncodeArgs } from '@/napcat-common/audio-worker';
|
||||
import { FFmpegService } from '@/napcat-common/ffmpeg';
|
||||
import { runTask } from './worker';
|
||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||
import { EncodeArgs } from 'napcat-common/src/audio-worker';
|
||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
||||
import { runTask } from 'napcat-common/src/worker';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import type { NapCatCore } from 'napcat-core';
|
||||
import type { NapCatCore } from '@/napcat-core';
|
||||
import json5 from 'json5';
|
||||
import Ajv, { AnySchema, ValidateFunction } from 'ajv';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigBase } from 'napcat-common/src/config-base';
|
||||
import { ConfigBase } from '@/napcat-core/helper/config-base';
|
||||
import { NapCatCore } from '@/napcat-core/index';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { AnySchema } from 'ajv';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NodeIQQNTWrapperSession } from 'napcat-core/wrapper';
|
||||
import { NodeIQQNTWrapperSession } from '@/napcat-core/wrapper';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ListenerNamingMapping, ServiceNamingMapping } from 'napcat-core/index';
|
||||
import { ListenerNamingMapping, ServiceNamingMapping } from '@/napcat-core/index';
|
||||
|
||||
interface InternalMapKey {
|
||||
timeout: number;
|
||||
@@ -25,18 +25,18 @@ export class NTEventWrapper {
|
||||
private readonly listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); // ListenerName-Unique -> Listener实例
|
||||
private readonly EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); // tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
wrapperSession: NodeIQQNTWrapperSession
|
||||
) {
|
||||
this.WrapperSession = wrapperSession;
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
createProxyDispatch (ListenerMainName: string) {
|
||||
const dispatcherListenerFunc = this.dispatcherListener.bind(this);
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target: any, prop: any, receiver: any) {
|
||||
get (target: any, prop: any, receiver: any) {
|
||||
if (typeof target[prop] === 'undefined') {
|
||||
// 如果方法不存在,返回一个函数,这个函数调用existentMethod
|
||||
return (...args: any[]) => {
|
||||
@@ -94,7 +94,7 @@ export class NTEventWrapper {
|
||||
}
|
||||
|
||||
// 统一回调清理事件
|
||||
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
async dispatcherListener (ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
|
||||
this.EventTask.get(ListenerMainName)
|
||||
?.get(ListenerSubName)
|
||||
?.forEach((task, uuid) => {
|
||||
@@ -137,7 +137,7 @@ export class NTEventWrapper {
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined;
|
||||
|
||||
function sendDataCallback() {
|
||||
function sendDataCallback () {
|
||||
if (complete === 0) {
|
||||
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
|
||||
} else {
|
||||
@@ -191,7 +191,7 @@ export class NTEventWrapper {
|
||||
let retData: Parameters<ListenerType> | undefined;
|
||||
let retEvent: any = {};
|
||||
|
||||
function sendDataCallback(resolve: any, reject: any) {
|
||||
function sendDataCallback (resolve: any, reject: any) {
|
||||
if (complete === 0) {
|
||||
reject(
|
||||
new Error(
|
||||
@@ -6,7 +6,7 @@ import * as os from 'os';
|
||||
import * as compressing from 'compressing'; // 修正导入方式
|
||||
import { pipeline } from 'stream/promises';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { LogWrapper } from './log';
|
||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||
|
||||
const downloadOri = 'https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip';
|
||||
const urls = [
|
||||
@@ -3,7 +3,7 @@
|
||||
* 自动检测并选择最佳的 FFmpeg 适配器
|
||||
*/
|
||||
|
||||
import { LogWrapper } from './log';
|
||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||
import { FFmpegAddonAdapter } from './ffmpeg-addon-adapter';
|
||||
import { FFmpegExecAdapter } from './ffmpeg-exec-adapter';
|
||||
import type { IFFmpegAdapter } from './ffmpeg-adapter-interface';
|
||||
@@ -68,13 +68,13 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
|
||||
const addon = this.ensureAddon();
|
||||
const info = await addon.getVideoInfo(videoPath);
|
||||
|
||||
let format = info.format.includes(',') ? info.format.split(',')[0] ?? info.format : info.format;
|
||||
const format = info.format.includes(',') ? info.format.split(',')[0] ?? info.format : info.format;
|
||||
console.log('[FFmpegAddonAdapter] Detected format:', format);
|
||||
return {
|
||||
width: info.width,
|
||||
height: info.height,
|
||||
duration: info.duration,
|
||||
format: format,
|
||||
format,
|
||||
thumbnail: info.image,
|
||||
};
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { promisify } from 'util';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import { imageSizeFallBack } from 'napcat-image-size/src/index';
|
||||
import { downloadFFmpegIfNotExists } from './download-ffmpeg';
|
||||
import { LogWrapper } from './log';
|
||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||
import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
@@ -3,7 +3,7 @@ import path from 'path';
|
||||
import type { VideoInfo } from './video';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import { platform } from 'node:os';
|
||||
import { LogWrapper } from './log';
|
||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||
import { FFmpegAdapterFactory } from './ffmpeg-adapter-factory';
|
||||
import type { IFFmpegAdapter } from './ffmpeg-adapter-interface';
|
||||
|
||||
@@ -53,7 +53,6 @@ export class FFmpegService {
|
||||
throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.');
|
||||
}
|
||||
return this.adapter.name;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as crypto from 'node:crypto';
|
||||
import { PacketMsg } from 'napcat-core/packet/message/message';
|
||||
import { PacketMsg } from '@/napcat-core/packet/message/message';
|
||||
|
||||
interface ForwardMsgJson {
|
||||
app: string
|
||||
@@ -1,8 +1,9 @@
|
||||
import winston, { format, transports } from 'winston';
|
||||
import { truncateString } from './helper';
|
||||
import { truncateString } from 'napcat-common/src/helper';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from 'napcat-core/index';
|
||||
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/napcat-core/index';
|
||||
import { ILogWrapper } from 'napcat-common/src/log-interface';
|
||||
import EventEmitter from 'node:events';
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
@@ -56,7 +57,7 @@ class Subscription {
|
||||
|
||||
export const logSubscription = new Subscription();
|
||||
|
||||
export class LogWrapper {
|
||||
export class LogWrapper implements ILogWrapper {
|
||||
fileLogEnabled = true;
|
||||
consoleLogEnabled = true;
|
||||
logger: winston.Logger;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogWrapper } from '@/napcat-common/log';
|
||||
import { LogWrapper } from '@/napcat-core/helper/log';
|
||||
|
||||
export function proxyHandlerOf (logger: LogWrapper) {
|
||||
return {
|
||||
@@ -1,10 +1,10 @@
|
||||
import fs from 'node:fs';
|
||||
import { systemPlatform } from '@/napcat-common/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper';
|
||||
import AppidTable from 'napcat-core/external/appid.json';
|
||||
import { LogWrapper } from '@/napcat-common/log';
|
||||
import { getMajorPath } from 'napcat-core';
|
||||
import { QQAppidTableType, QQPackageInfoType, QQVersionConfigType } from './types';
|
||||
import { systemPlatform } from 'napcat-common/src/system';
|
||||
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from 'napcat-common/src/helper';
|
||||
import AppidTable from '@/napcat-core/external/appid.json';
|
||||
import { LogWrapper } from './log';
|
||||
import { getMajorPath } from '@/napcat-core/index';
|
||||
import { QQAppidTableType, QQPackageInfoType, QQVersionConfigType } from 'napcat-common/src/types';
|
||||
|
||||
export class QQBasicInfoWrapper {
|
||||
QQMainPath: string | undefined;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LogWrapper } from 'napcat-common/src/log';
|
||||
import { RequestUtil } from 'napcat-common/src/request';
|
||||
import { LogWrapper } from './log';
|
||||
|
||||
interface ServerRkeyData {
|
||||
group_rkey: string;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import os from 'node:os';
|
||||
import EventEmitter from 'node:events';
|
||||
|
||||
import { IStatusHelperSubscription } from 'napcat-common/src/status-interface';
|
||||
export interface SystemStatus {
|
||||
cpu: {
|
||||
model: string,
|
||||
speed: string
|
||||
speed: string;
|
||||
usage: {
|
||||
system: string
|
||||
qq: string
|
||||
system: string;
|
||||
qq: string;
|
||||
},
|
||||
core: number
|
||||
core: number;
|
||||
},
|
||||
memory: {
|
||||
total: string
|
||||
total: string;
|
||||
usage: {
|
||||
system: string
|
||||
qq: string
|
||||
}
|
||||
system: string;
|
||||
qq: string;
|
||||
};
|
||||
},
|
||||
arch: string
|
||||
arch: string;
|
||||
}
|
||||
|
||||
export class StatusHelper {
|
||||
@@ -101,7 +101,7 @@ export class StatusHelper {
|
||||
}
|
||||
}
|
||||
|
||||
class StatusHelperSubscription extends EventEmitter {
|
||||
class StatusHelperSubscription extends EventEmitter implements IStatusHelperSubscription {
|
||||
private statusHelper: StatusHelper;
|
||||
private interval: NodeJS.Timeout | null = null;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
NTQQSystemApi,
|
||||
NTQQUserApi,
|
||||
NTQQWebApi,
|
||||
} from 'napcat-core/apis';
|
||||
} from '@/napcat-core/apis';
|
||||
import { NTQQCollectionApi } from '@/napcat-core/apis/collection';
|
||||
import {
|
||||
NodeIQQNTWrapperSession,
|
||||
@@ -16,22 +16,24 @@ import {
|
||||
WrapperNodeApi,
|
||||
WrapperSessionInitConfig,
|
||||
} from '@/napcat-core/wrapper';
|
||||
import { LogLevel, LogWrapper } from 'napcat-common/src/log';
|
||||
import { LogLevel, LogWrapper } from '@/napcat-core/helper/log';
|
||||
import { NodeIKernelLoginService } from '@/napcat-core/services';
|
||||
import { QQBasicInfoWrapper } from 'napcat-common/src/qq-basic-info';
|
||||
import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info';
|
||||
import { NapCatPathWrapper } from 'napcat-common/src/path';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { hostname, systemName, systemVersion } from 'napcat-common/src/system';
|
||||
import { NTEventWrapper } from 'napcat-common/src/event';
|
||||
import { NTEventWrapper } from '@/napcat-core/helper/event';
|
||||
import { KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/napcat-core/types';
|
||||
import { NapCatConfigLoader, NapcatConfigSchema } from '@/napcat-core/helper/config';
|
||||
import os from 'node:os';
|
||||
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/napcat-core/listeners';
|
||||
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
|
||||
import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
|
||||
import { NTQQPacketApi } from './apis/packet';
|
||||
import { NativePacketHandler } from './packet/handler/client';
|
||||
import { container, ReceiverServiceRegistry } from './packet/handler/serviceRegister';
|
||||
import { appEvent } from './packet/handler/eventList';
|
||||
import { TypedEventEmitter } from './packet/handler/typeEvent';
|
||||
export * from './wrapper';
|
||||
export * from './types/index';
|
||||
export * from './services/index';
|
||||
@@ -93,6 +95,7 @@ export function getMajorPath (QQVersion: string): string {
|
||||
export class NapCatCore {
|
||||
readonly context: InstanceContext;
|
||||
readonly eventWrapper: NTEventWrapper;
|
||||
event = appEvent;
|
||||
NapCatDataPath: string = '';
|
||||
NapCatTempPath: string = '';
|
||||
apis: StableNTApiWrapper;
|
||||
@@ -120,9 +123,10 @@ export class NapCatCore {
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
};
|
||||
container.bind(NapCatCore).toConstantValue(this);
|
||||
container.bind(TypedEventEmitter).toConstantValue(this.event);
|
||||
ReceiverServiceRegistry.forEach((ServiceClass, serviceName) => {
|
||||
container.bind(ServiceClass).toSelf();
|
||||
console.log(`Registering service handler for: ${serviceName}`);
|
||||
//console.log(`Registering service handler for: ${serviceName}`);
|
||||
this.context.packetHandler.onCmd(serviceName, ({ seq, hex_data }) => {
|
||||
const serviceInstance = container.get(ServiceClass);
|
||||
return serviceInstance.handler(seq, hex_data);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChatType, KickedOffLineInfo, RawMessage } from '@/napcat-core/types';
|
||||
import { CommonFileInfo } from 'napcat-core/index';
|
||||
import { CommonFileInfo } from '@/napcat-core/index';
|
||||
|
||||
export interface OnRichMediaDownloadCompleteParams {
|
||||
fileModelId: string,
|
||||
|
||||
@@ -3,7 +3,7 @@ export class NodeIKernelStorageCleanListener {
|
||||
|
||||
}
|
||||
|
||||
onScanCacheProgressChanged (_args: unknown): any {
|
||||
onScanCacheProgressChanged (_current_progress: number, _total_progress: number): any {
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export class NodeIKernelStorageCleanListener {
|
||||
|
||||
}
|
||||
|
||||
onFinishScan (_args: unknown): any {
|
||||
onFinishScan (_sizes: Array<`${number}`>): any {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts"
|
||||
@@ -13,15 +16,18 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"winston": "^3.17.0",
|
||||
"json5": "^2.2.3",
|
||||
"inversify": "^7.10.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"@protobuf-ts/runtime": "^2.11.1",
|
||||
"napcat-protobuf": "workspace:*",
|
||||
"ajv": "^8.13.0",
|
||||
"@sinclair/typebox": "^0.34.38",
|
||||
"file-type": "^21.0.0",
|
||||
"compressing": "^1.10.1",
|
||||
"napcat-image-size": "workspace:*",
|
||||
"napcat-core": "workspace:*",
|
||||
"napcat-common": "workspace:*",
|
||||
"napcat-onebot": "workspace:*"
|
||||
"napcat-protobuf": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LogLevel, LogWrapper } from 'napcat-common/src/log';
|
||||
import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext';
|
||||
import { LogWrapper, LogLevel } from '@/napcat-core/helper/log';
|
||||
|
||||
// TODO: check bind?
|
||||
export class PacketLogger {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
PacketMsgReplyElement,
|
||||
PacketMsgVideoElement,
|
||||
} from '@/napcat-core/packet/message/element';
|
||||
import { ChatType, MsgSourceType, NTMsgType, RawMessage } from 'napcat-core/index';
|
||||
import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/napcat-core/index';
|
||||
import { MiniAppRawData, MiniAppReqParams } from '@/napcat-core/packet/entities/miniApp';
|
||||
import { AIVoiceChatType } from '@/napcat-core/packet/entities/aiChat';
|
||||
import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from 'napcat-protobuf';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PacketHighwayContext } from '@/napcat-core/packet/highway/highwayContext';
|
||||
import { NapCatCore } from 'napcat-core/index';
|
||||
import { NapCatCore } from '@/napcat-core/index';
|
||||
import { PacketLogger } from '@/napcat-core/packet/context/loggerContext';
|
||||
import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext';
|
||||
import { PacketClientContext } from '@/napcat-core/packet/context/clientContext';
|
||||
|
||||
@@ -2,8 +2,8 @@ import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import { constants } from 'node:os';
|
||||
import { LogWrapper } from 'napcat-common/src/log';
|
||||
import offset from '@/napcat-core/external/packet.json';
|
||||
import { LogWrapper } from '../../helper/log';
|
||||
interface OffsetType {
|
||||
[key: string]: {
|
||||
recv: string;
|
||||
@@ -50,7 +50,6 @@ export class NativePacketHandler {
|
||||
this.logger.logError('NativePacketClient 加载出错:', error);
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
6
packages/napcat-core/packet/handler/eventList.ts
Normal file
6
packages/napcat-core/packet/handler/eventList.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { TypedEventEmitter } from './typeEvent';
|
||||
|
||||
export interface AppEvents {
|
||||
'event:emoji_like': { groupId: string; senderUin: string; emojiId: string, msgSeq: string, isAdd: boolean, count: number };
|
||||
}
|
||||
export const appEvent = new TypedEventEmitter<AppEvents>();
|
||||
@@ -1,24 +1,28 @@
|
||||
import "reflect-metadata";
|
||||
import { Container, injectable } from "inversify";
|
||||
import { NapCatCore } from "../..";
|
||||
import 'reflect-metadata';
|
||||
import { Container, injectable } from 'inversify';
|
||||
import { NapCatCore } from '../..';
|
||||
import { TypedEventEmitter } from './typeEvent';
|
||||
|
||||
export const container = new Container();
|
||||
|
||||
export const ReceiverServiceRegistry = new Map<string, new (...args: any[]) => ServiceBase>();
|
||||
|
||||
export abstract class ServiceBase {
|
||||
get core(): NapCatCore {
|
||||
return container.get(NapCatCore);
|
||||
}
|
||||
get core (): NapCatCore {
|
||||
return container.get(NapCatCore);
|
||||
}
|
||||
|
||||
abstract handler(seq: number, hex_data: string): Promise<void> | void;
|
||||
get event () {
|
||||
return container.get(TypedEventEmitter);
|
||||
}
|
||||
|
||||
abstract handler (seq: number, hex_data: string): Promise<void> | void;
|
||||
}
|
||||
|
||||
export function ReceiveService(serviceName: string) {
|
||||
return function <T extends new (...args: any[]) => ServiceBase>(constructor: T) {
|
||||
injectable()(constructor);
|
||||
ReceiverServiceRegistry.set(serviceName, constructor);
|
||||
return constructor;
|
||||
};
|
||||
export function ReceiveService (serviceName: string) {
|
||||
return function <T extends new (...args: any[]) => ServiceBase>(constructor: T) {
|
||||
injectable()(constructor);
|
||||
ReceiverServiceRegistry.set(serviceName, constructor);
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
22
packages/napcat-core/packet/handler/typeEvent.ts
Normal file
22
packages/napcat-core/packet/handler/typeEvent.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
|
||||
export class TypedEventEmitter<E extends Record<string, any>> {
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
on<K extends keyof E>(event: K, listener: (payload: E[K]) => void) {
|
||||
this.emitter.on(event as string, listener);
|
||||
return () => this.off(event, listener);
|
||||
}
|
||||
|
||||
once<K extends keyof E>(event: K, listener: (payload: E[K]) => void) {
|
||||
this.emitter.once(event as string, listener);
|
||||
}
|
||||
|
||||
off<K extends keyof E>(event: K, listener: (payload: E[K]) => void) {
|
||||
this.emitter.off(event as string, listener);
|
||||
}
|
||||
|
||||
emit<K extends keyof E>(event: K, payload: E[K]) {
|
||||
this.emitter.emit(event as string, payload);
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,8 @@ import {
|
||||
SendTextElement,
|
||||
SendVideoElement,
|
||||
Peer,
|
||||
} from 'napcat-core/index';
|
||||
import { ForwardMsgBuilder } from 'napcat-common/src/forward-msg-builder';
|
||||
} from '@/napcat-core/index';
|
||||
import { ForwardMsgBuilder } from '@/napcat-core/helper/forward-msg-builder';
|
||||
import { PacketMsg, PacketSendMsgElement } from '@/napcat-core/packet/message/message';
|
||||
|
||||
export type ParseElementFnR = [MessageElement, NapProtoDecodeStructType<typeof Elem> | null] | undefined;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IPacketMsgElement } from '@/napcat-core/packet/message/element';
|
||||
import { SendMessageElement, SendMultiForwardMsgElement } from 'napcat-core/index';
|
||||
import { SendMessageElement, SendMultiForwardMsgElement } from '@/napcat-core/index';
|
||||
|
||||
export type PacketSendMsgElement = SendMessageElement | SendMultiForwardMsgElement;
|
||||
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
import { ReceiveService, ServiceBase } from "../packet/handler/serviceRegister";
|
||||
import { NapProtoMsg } from 'napcat-protobuf';
|
||||
import { ReceiveService, ServiceBase } from '../packet/handler/serviceRegister';
|
||||
import { GroupReactNotify, PushMsg } from '../packet/transformer/proto';
|
||||
|
||||
// @ReceiveService('trpc.msg.olpush.OlPushService.MsgPush')
|
||||
// export class OlPushService extends ServiceBase {
|
||||
// async handler(seq: number, hex_data: string) {
|
||||
// console.log(`OlPushService handler called with seq: ${seq} and data: ${hex_data}`);
|
||||
// }
|
||||
// }
|
||||
@ReceiveService('trpc.msg.olpush.OlPushService.MsgPush')
|
||||
export class OlPushService extends ServiceBase {
|
||||
async handler (_seq: number, hex_data: string) {
|
||||
const data = new NapProtoMsg(PushMsg).decode(Buffer.from(hex_data, 'hex'));
|
||||
if (data.message.contentHead.type === 732 && data.message.contentHead.subType === 16) {
|
||||
const pbNotify = data.message.body?.msgContent?.slice(7);
|
||||
if (!pbNotify) {
|
||||
return;
|
||||
}
|
||||
// 开始解析Notify
|
||||
const notify = new NapProtoMsg(GroupReactNotify).decode(pbNotify);
|
||||
if ((notify.field13 ?? 0) === 35) {
|
||||
// Group React Notify
|
||||
const groupCode = notify.groupUin?.toString() ?? '';
|
||||
const operatorUid = notify.groupReactionData?.data?.data?.groupReactionDataContent?.operatorUid ?? '';
|
||||
const type = notify.groupReactionData?.data?.data?.groupReactionDataContent?.type ?? 0;
|
||||
const seq = notify.groupReactionData?.data?.data?.groupReactionTarget?.seq?.toString() ?? '';
|
||||
const code = notify.groupReactionData?.data?.data?.groupReactionDataContent?.code ?? '';
|
||||
const count = notify.groupReactionData?.data?.data?.groupReactionDataContent?.count ?? 0;
|
||||
const senderUin = await this.core.apis.UserApi.getUinByUidV2(operatorUid);
|
||||
this.event.emit('event:emoji_like', {
|
||||
groupId: groupCode,
|
||||
senderUin,
|
||||
emojiId: code,
|
||||
msgSeq: seq,
|
||||
isAdd: type === 1,
|
||||
count,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ export interface NodeIKernelBuddyService {
|
||||
}>;
|
||||
}>;
|
||||
|
||||
|
||||
getBuddyListFromCache (reqType: BuddyListReqType): Promise<Array<
|
||||
{
|
||||
categoryId: number, // 9999为特别关心
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AnyCnameRecord } from 'node:dns';
|
||||
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUin, UserDetailInfoListenerArg, UserDetailSource } from 'napcat-core/index';
|
||||
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUin, UserDetailInfoListenerArg, UserDetailSource } from '@/napcat-core/index';
|
||||
import { GeneralCallResult } from '@/napcat-core/services/common';
|
||||
|
||||
export interface NodeIKernelProfileService {
|
||||
|
||||
@@ -3,39 +3,56 @@ import { GeneralCallResult } from './common';
|
||||
|
||||
export interface NodeIKernelStorageCleanService {
|
||||
|
||||
addKernelStorageCleanListener(listener: NodeIKernelStorageCleanListener): number;
|
||||
addKernelStorageCleanListener (listener: NodeIKernelStorageCleanListener): number;
|
||||
|
||||
removeKernelStorageCleanListener(listenerId: number): void;
|
||||
removeKernelStorageCleanListener (listenerId: number): void;
|
||||
// [
|
||||
// "hotUpdate",
|
||||
// [
|
||||
// "C:\\Users\\nanaeo\\AppData\\Roaming\\QQ\\packages"
|
||||
// ]
|
||||
// ],
|
||||
// [
|
||||
// "tmp",
|
||||
// [
|
||||
// "C:\\Users\\nanaeo\\AppData\\Roaming\\QQ\\tmp"
|
||||
// ]
|
||||
// ],
|
||||
// [
|
||||
// "SilentCacheappSessionPartation9212",
|
||||
// [
|
||||
// "C:\\Users\\nanaeo\\AppData\\Roaming\\QQ\\Partitions\\qqnt_9212"
|
||||
// ]
|
||||
// ]
|
||||
addCacheScanedPaths (paths: Map<`tmp` | `SilentCacheappSessionPartation9212` | `hotUpdate`, unknown>): unknown;
|
||||
|
||||
addCacheScanedPaths(arg: unknown): unknown;
|
||||
addFilesScanedPaths (arg: unknown): unknown;
|
||||
|
||||
addFilesScanedPaths(arg: unknown): unknown;
|
||||
|
||||
scanCache(): Promise<GeneralCallResult & {
|
||||
size: string[]
|
||||
scanCache (): Promise<GeneralCallResult & {
|
||||
size: string[];
|
||||
}>;
|
||||
|
||||
addReportData(arg: unknown): unknown;
|
||||
addReportData (arg: unknown): unknown;
|
||||
|
||||
reportData(): unknown;
|
||||
reportData (): unknown;
|
||||
|
||||
getChatCacheInfo(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown): unknown;
|
||||
getChatCacheInfo (arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown): unknown;
|
||||
|
||||
getFileCacheInfo(arg1: unknown, arg2: unknown, arg3: unknown, arg44: unknown, args5: unknown): unknown;
|
||||
getFileCacheInfo (arg1: unknown, arg2: unknown, arg3: unknown, arg44: unknown, args5: unknown): unknown;
|
||||
|
||||
clearChatCacheInfo(arg1: unknown, arg2: unknown): unknown;
|
||||
clearChatCacheInfo (arg1: unknown, arg2: unknown): unknown;
|
||||
|
||||
clearCacheDataByKeys(arg: unknown): unknown;
|
||||
clearCacheDataByKeys (keys: Array<string>): Promise<GeneralCallResult>;
|
||||
|
||||
setSilentScan(arg: unknown): unknown;
|
||||
setSilentScan (is_silent: boolean): unknown;
|
||||
|
||||
closeCleanWindow(): unknown;
|
||||
closeCleanWindow (): unknown;
|
||||
|
||||
clearAllChatCacheInfo(): unknown;
|
||||
clearAllChatCacheInfo (): unknown;
|
||||
|
||||
endScan(arg: unknown): unknown;
|
||||
endScan (arg: unknown): unknown;
|
||||
|
||||
addNewDownloadOrUploadFile(arg: unknown): unknown;
|
||||
addNewDownloadOrUploadFile (arg: unknown): unknown;
|
||||
|
||||
isNull(): boolean;
|
||||
isNull (): boolean;
|
||||
}
|
||||
|
||||
@@ -1,48 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/napcat-core/*": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"*.ts",
|
||||
"**/*.ts"
|
||||
|
||||
@@ -23,13 +23,13 @@ type ElementBase<
|
||||
K extends keyof ElementFullBase,
|
||||
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
|
||||
> = {
|
||||
[P in K]:
|
||||
S[P] extends Array<infer U>
|
||||
[P in K]:
|
||||
S[P] extends Array<infer U>
|
||||
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
|
||||
: S[P] extends keyof NonNullable<ElementFullBase[P]>
|
||||
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
|
||||
: NonNullable<ElementFullBase[P]>;
|
||||
};
|
||||
? Pick<NonNullable<ElementFullBase[P]>, S[P]>
|
||||
: NonNullable<ElementFullBase[P]>;
|
||||
};
|
||||
|
||||
export interface TextElement {
|
||||
content: string;
|
||||
|
||||
@@ -10,5 +10,5 @@ const NAPCAT_MJS_PATH = path.join(BASE_DIR, 'napcat', 'napcat.mjs');
|
||||
process.env.NAPCAT_WRAPPER_PATH = WRAPPER_NODE_PATH;
|
||||
process.env.NAPCAT_QQ_PACKAGE_INFO_PATH = PACKAGE_JSON_PATH;
|
||||
process.env.NAPCAT_QQ_VERSION_CONFIG_PATH = CONFIG_JSON_PATH;
|
||||
process.env.NAPCAT_DISABLE_PIPE = '1';
|
||||
import(pathToFileURL(NAPCAT_MJS_PATH).href);
|
||||
process.env.NAPCAT_DISABLE_PIPE = '1';
|
||||
import(pathToFileURL(NAPCAT_MJS_PATH).href);
|
||||
|
||||
@@ -12,15 +12,28 @@ if (!mainPath) {
|
||||
const versionsDir = path.join(mainPath, 'versions');
|
||||
console.log(`Looking for version folders in: ${versionsDir}`);
|
||||
const versionFolders = fs.readdirSync(versionsDir).filter(f => fs.statSync(path.join(versionsDir, f)).isDirectory());
|
||||
if (versionFolders.length !== 1) {
|
||||
console.error('versions 文件夹下必须且只能有一个版本目录');
|
||||
let selectedFolder;
|
||||
if (versionFolders.length === 0) {
|
||||
console.error('versions 文件夹下没有找到版本目录');
|
||||
process.exit(1);
|
||||
} else if (versionFolders.length === 1) {
|
||||
selectedFolder = versionFolders[0];
|
||||
} else {
|
||||
// 获取时间最新的文件夹
|
||||
const stats = versionFolders.map(folder => {
|
||||
const folderPath = path.join(versionsDir, folder);
|
||||
return { folder, mtime: fs.statSync(folderPath).mtime };
|
||||
});
|
||||
stats.sort((a, b) => b.mtime - a.mtime);
|
||||
selectedFolder = stats[0].folder;
|
||||
console.log(`多个版本文件夹存在,选择最新的: ${selectedFolder}`);
|
||||
}
|
||||
|
||||
const BASE_DIR = path.join(versionsDir, versionFolders[0], 'resources', 'app');
|
||||
const BASE_DIR = path.join(versionsDir, selectedFolder, 'resources', 'app');
|
||||
console.log(`BASE_DIR: ${BASE_DIR}`);
|
||||
const TARGET_DIR = path.join(__dirname, 'dist');
|
||||
const QQNT_FILE = path.join(__dirname, 'QQNT.dll');
|
||||
const NAPCAT_MJS_PATH = path.join(__dirname, '..', 'napcat-shell','dist', 'napcat.mjs');
|
||||
const NAPCAT_MJS_PATH = path.join(__dirname, '..', 'napcat-shell', 'dist', 'napcat.mjs');
|
||||
|
||||
const itemsToCopy = [
|
||||
'avif_convert.dll',
|
||||
@@ -32,15 +45,15 @@ const itemsToCopy = [
|
||||
'opencv.dll',
|
||||
'package.json',
|
||||
'QBar.dll',
|
||||
'wrapper.node'
|
||||
'wrapper.node',
|
||||
];
|
||||
|
||||
async function copyAll () {
|
||||
const qqntDllPath = path.join(TARGET_DIR, 'QQNT.dll');
|
||||
const configPath = path.join(TARGET_DIR, 'config.json');
|
||||
const allItemsExist = await fs.pathExists(qqntDllPath)
|
||||
&& await fs.pathExists(configPath)
|
||||
&& (await Promise.all(itemsToCopy.map(item => fs.pathExists(path.join(TARGET_DIR, item))))).every(exists => exists);
|
||||
const allItemsExist = await fs.pathExists(qqntDllPath) &&
|
||||
await fs.pathExists(configPath) &&
|
||||
(await Promise.all(itemsToCopy.map(item => fs.pathExists(path.join(TARGET_DIR, item))))).every(exists => exists);
|
||||
|
||||
if (!allItemsExist) {
|
||||
console.log('Copying required files...');
|
||||
@@ -61,9 +74,11 @@ async function copyAll () {
|
||||
process.env.NAPCAT_QQ_VERSION_CONFIG_PATH = path.join(TARGET_DIR, 'config.json');
|
||||
process.env.NAPCAT_DISABLE_PIPE = '1';
|
||||
process.env.NAPCAT_WORKDIR = TARGET_DIR;
|
||||
|
||||
// 开发环境使用固定密钥
|
||||
process.env.NAPCAT_WEBUI_JWT_SECRET_KEY = 'napcat_dev_secret_key';
|
||||
process.env.NAPCAT_WEBUI_SECRET_KEY = 'napcat';
|
||||
console.log('Loading NapCat module...');
|
||||
await import(pathToFileURL(NAPCAT_MJS_PATH).href);
|
||||
}
|
||||
|
||||
copyAll().catch(console.error);
|
||||
copyAll().catch(console.error);
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.cjs",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "powershell ./nodeTest.ps1"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./index.cjs"
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": {
|
||||
"require": "./*"
|
||||
|
||||
@@ -1,46 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "./",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": "./",
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.cjs"
|
||||
"*.cjs",
|
||||
"**/*.cjs",
|
||||
"**/*.ts",
|
||||
"*.js",
|
||||
"**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { NapCatPathWrapper } from 'napcat-common/src/path';
|
||||
import { LogWrapper } from 'napcat-common/src/log';
|
||||
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
|
||||
import { QQBasicInfoWrapper } from 'napcat-common/src/qq-basic-info';
|
||||
import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from 'napcat-core/index';
|
||||
import { SelfInfo } from 'napcat-core/types';
|
||||
import { NodeIKernelLoginListener } from 'napcat-core/listeners';
|
||||
import { NodeIKernelLoginService } from 'napcat-core/services';
|
||||
import { NodeIQQNTWrapperSession, WrapperNodeApi } from 'napcat-core/wrapper';
|
||||
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/src/index';
|
||||
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index';
|
||||
import { NapCatOneBot11Adapter } from 'napcat-onebot/index';
|
||||
import { FFmpegService } from 'napcat-common/src/ffmpeg';
|
||||
import { NativePacketHandler } from 'napcat-core/packet/handler/client';
|
||||
import { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg';
|
||||
import { logSubscription, LogWrapper } from 'napcat-core/helper/log';
|
||||
import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info';
|
||||
import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv, NodeIKernelLoginListener, NodeIKernelLoginService, NodeIQQNTWrapperSession, SelfInfo, WrapperNodeApi } from '@/napcat-core';
|
||||
import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
|
||||
import { statusHelperSubscription } from '@/napcat-core/helper/status';
|
||||
import { applyPendingUpdates } from '@/napcat-webui-backend/src/api/UpdateNapCat';
|
||||
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
|
||||
|
||||
// Framework ES入口文件
|
||||
export async function getWebUiUrl () {
|
||||
@@ -35,6 +34,7 @@ export async function NCoreInitFramework (
|
||||
});
|
||||
|
||||
const pathWrapper = new NapCatPathWrapper();
|
||||
await applyPendingUpdates(pathWrapper);
|
||||
const logger = new LogWrapper(pathWrapper.logsPath);
|
||||
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
||||
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
|
||||
@@ -76,9 +76,13 @@ export async function NCoreInitFramework (
|
||||
await loaderObject.core.initCore();
|
||||
|
||||
// 启动WebUi
|
||||
InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e));
|
||||
WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
|
||||
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
|
||||
// 初始化LLNC的Onebot实现
|
||||
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
|
||||
const oneBotAdapter = new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper);
|
||||
// 注册到 WebUiDataRuntime,供调试功能使用
|
||||
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
||||
await oneBotAdapter.InitOneBot();
|
||||
}
|
||||
|
||||
export class NapCatFramework {
|
||||
|
||||
@@ -1,49 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/napcat-onebot/*": ["../napcat-onebot/*"],
|
||||
"@/napcat-core/*": ["../napcat-core/*"],
|
||||
"@/napcat-common/*": ["../napcat-common/src/*"],
|
||||
"@/napcat-webui-backend/*": ["../napcat-webui-backend/src/*"]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"*.ts",
|
||||
"**/*.ts"
|
||||
|
||||
@@ -2,23 +2,23 @@ import cp from 'vite-plugin-cp';
|
||||
import { defineConfig, PluginOption, UserConfig } from 'vite';
|
||||
import path, { resolve } from 'path';
|
||||
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||
import { autoIncludeTSPlugin } from "napcat-vite/vite-auto-include.js";
|
||||
import { autoIncludeTSPlugin } from 'napcat-vite/vite-auto-include.js';
|
||||
import { builtinModules } from 'module';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import napcatVersion from "napcat-vite/vite-plugin-version.js";
|
||||
//依赖排除
|
||||
import napcatVersion from 'napcat-vite/vite-plugin-version.js';
|
||||
// 依赖排除
|
||||
const external = [
|
||||
'silk-wasm',
|
||||
'ws',
|
||||
'express'
|
||||
'express',
|
||||
];
|
||||
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
|
||||
const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
autoIncludeTSPlugin({
|
||||
entries: [
|
||||
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-core/protocol') },
|
||||
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') }
|
||||
]
|
||||
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') },
|
||||
],
|
||||
}),
|
||||
react({ tsDecorators: true }),
|
||||
cp({
|
||||
@@ -46,10 +46,10 @@ const FrameworkBaseConfig = () =>
|
||||
conditions: ['node', 'default'],
|
||||
alias: {
|
||||
'@/napcat-core': resolve(__dirname, '../napcat-core'),
|
||||
'@/napcat-common': resolve(__dirname, '../napcat-common/src'),
|
||||
'@/napcat-common': resolve(__dirname, '../napcat-common'),
|
||||
'@/napcat-onebot': resolve(__dirname, '../napcat-onebot'),
|
||||
'@/napcat-pty': resolve(__dirname, '../napcat-pty'),
|
||||
'@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend/src'),
|
||||
'@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'),
|
||||
'@/image-size': resolve(__dirname, '../image-size'),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,48 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/napcat-webui-backend/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
import { OneBotAction } from './OneBotAction';
|
||||
export const AutoRegisterRouter: Array<new (...args: any[]) => OneBotAction<unknown, unknown>> = [];
|
||||
|
||||
export function ActionHandler(target: new (...args: any[]) => OneBotAction<unknown, unknown>) {
|
||||
AutoRegisterRouter.push(target);
|
||||
export function ActionHandler (target: new (...args: any[]) => OneBotAction<unknown, unknown>) {
|
||||
AutoRegisterRouter.push(target);
|
||||
}
|
||||
|
||||
@@ -6,23 +6,23 @@ import { Static, Type } from '@sinclair/typebox';
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||
phoneNumber: Type.String({ default: '' }),
|
||||
phone_number: Type.String({ default: '' }),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class SharePeer extends OneBotAction<Payload, GeneralCallResult & {
|
||||
export class SharePeerBase extends OneBotAction<Payload, GeneralCallResult & {
|
||||
arkMsg?: string;
|
||||
arkJson?: string;
|
||||
}> {
|
||||
override actionName = ActionName.SharePeer;
|
||||
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
if (payload.group_id) {
|
||||
return await this.core.apis.GroupApi.getGroupRecommendContactArkJson(payload.group_id.toString());
|
||||
} else if (payload.user_id) {
|
||||
return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id.toString(), payload.phoneNumber);
|
||||
return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id.toString(), payload.phone_number);
|
||||
}
|
||||
throw new Error('group_id or user_id is required');
|
||||
}
|
||||
@@ -31,14 +31,25 @@ export class SharePeer extends OneBotAction<Payload, GeneralCallResult & {
|
||||
const SchemaDataGroupEx = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
});
|
||||
|
||||
export class SharePeer extends SharePeerBase {
|
||||
override actionName = ActionName.SharePeer;
|
||||
}
|
||||
type PayloadGroupEx = Static<typeof SchemaDataGroupEx>;
|
||||
|
||||
export class ShareGroupEx extends OneBotAction<PayloadGroupEx, string> {
|
||||
override actionName = ActionName.ShareGroupEx;
|
||||
export class ShareGroupExBase extends OneBotAction<PayloadGroupEx, string> {
|
||||
override payloadSchema = SchemaDataGroupEx;
|
||||
|
||||
async _handle (payload: PayloadGroupEx) {
|
||||
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString());
|
||||
}
|
||||
}
|
||||
export class ShareGroupEx extends ShareGroupExBase {
|
||||
override actionName = ActionName.ShareGroupEx;
|
||||
}
|
||||
export class SendGroupArkShare extends ShareGroupExBase {
|
||||
override actionName = ActionName.SendGroupArkShare;
|
||||
}
|
||||
|
||||
export class SendArkShare extends SharePeerBase {
|
||||
override actionName = ActionName.SendArkShare;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { promises as fs } from 'fs';
|
||||
import { decode } from 'silk-wasm';
|
||||
import { FFmpegService } from 'napcat-common/src/ffmpeg';
|
||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
||||
|
||||
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
|
||||
|
||||
|
||||
@@ -14,10 +14,11 @@ const SchemaData = Type.Object({
|
||||
user_id: Type.String(),
|
||||
message_seq: Type.Optional(Type.String()),
|
||||
count: Type.Number({ default: 20 }),
|
||||
reverseOrder: Type.Boolean({ default: false }),
|
||||
reverse_order: Type.Boolean({ default: false }),
|
||||
disable_get_url: Type.Boolean({ default: false }),
|
||||
parse_mult_msg: Type.Boolean({ default: true }),
|
||||
quick_reply: Type.Boolean({ default: false }),
|
||||
reverseOrder: Type.Boolean({ default: false }),// @deprecated 兼容旧版本
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -35,7 +36,7 @@ export default class GetFriendMsgHistory extends OneBotAction<Payload, Response>
|
||||
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
|
||||
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
|
||||
const msgList = hasMessageSeq
|
||||
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverseOrder)).msgList
|
||||
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverse_order || payload.reverseOrder)).msgList
|
||||
: (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList;
|
||||
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
|
||||
// 转换序号
|
||||
|
||||
@@ -14,10 +14,11 @@ const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
message_seq: Type.Optional(Type.String()),
|
||||
count: Type.Number({ default: 20 }),
|
||||
reverseOrder: Type.Boolean({ default: false }),
|
||||
reverse_order: Type.Boolean({ default: false }),
|
||||
disable_get_url: Type.Boolean({ default: false }),
|
||||
parse_mult_msg: Type.Boolean({ default: true }),
|
||||
quick_reply: Type.Boolean({ default: false }),
|
||||
reverseOrder: Type.Boolean({ default: false }),// @deprecated 兼容旧版本
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -32,7 +33,7 @@ export default class GoCQHTTPGetGroupMsgHistory extends OneBotAction<Payload, Re
|
||||
// 拉取消息
|
||||
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
|
||||
const msgList = hasMessageSeq
|
||||
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverseOrder)).msgList
|
||||
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverse_order || payload.reverseOrder)).msgList
|
||||
: (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList;
|
||||
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
|
||||
// 转换序号
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class GoCQHTTPUploadGroupFile extends OneBotAction<Payload, Uploa
|
||||
peer,
|
||||
deleteAfterSentFiles: [],
|
||||
};
|
||||
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id);
|
||||
const sendFileEle = await this.obContext.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id);
|
||||
msgContext.deleteAfterSentFiles.push(downloadResult.path);
|
||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], msgContext.deleteAfterSentFiles);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class GoCQHTTPUploadPrivateFile extends OneBotAction<Payload, Upl
|
||||
}, ContextMode.Private),
|
||||
deleteAfterSentFiles: [],
|
||||
};
|
||||
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name);
|
||||
const sendFileEle: SendFileElement = await this.obContext.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name);
|
||||
msgContext.deleteAfterSentFiles.push(downloadResult.path);
|
||||
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], msgContext.deleteAfterSentFiles);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ import { GetOnlineClient } from './go-cqhttp/GetOnlineClient';
|
||||
import { IOCRImage, OCRImage } from './extends/OCRImage';
|
||||
import { TranslateEnWordToZn } from './extends/TranslateEnWordToZn';
|
||||
import { SetQQProfile } from './go-cqhttp/SetQQProfile';
|
||||
import { ShareGroupEx, SharePeer } from './extends/ShareContact';
|
||||
import { SendArkShare, SendGroupArkShare, ShareGroupEx, SharePeer } from './extends/ShareContact';
|
||||
import { CreateCollection } from './extends/CreateCollection';
|
||||
import { SetLongNick } from './extends/SetLongNick';
|
||||
import DelEssenceMsg from './group/DelEssenceMsg';
|
||||
@@ -138,7 +138,7 @@ import { TestDownloadStream } from './stream/TestStreamDownload';
|
||||
import { UploadFileStream } from './stream/UploadFileStream';
|
||||
import { AutoRegisterRouter } from './auto-register';
|
||||
|
||||
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
const actionHandlers = [
|
||||
new CleanStreamTempFile(obContext, core),
|
||||
new DownloadFileStream(obContext, core),
|
||||
@@ -170,6 +170,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
new SetQQProfile(obContext, core),
|
||||
new ShareGroupEx(obContext, core),
|
||||
new SharePeer(obContext, core),
|
||||
new SendGroupArkShare(obContext, core),
|
||||
new SendArkShare(obContext, core),
|
||||
new CreateCollection(obContext, core),
|
||||
new SetLongNick(obContext, core),
|
||||
new ForwardFriendSingleMsg(obContext, core),
|
||||
@@ -315,7 +317,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
// function get<K extends keyof MapType>(key: K): MapType[K];
|
||||
// function get<K extends keyof MapType>(key: K): null;
|
||||
// function get<K extends keyof MapType>(key: K): HandlerUnion | null | MapType[K]
|
||||
function get<K extends keyof MapType>(key: K): MapType[K] | undefined {
|
||||
function get<K extends keyof MapType> (key: K): MapType[K] | undefined {
|
||||
return _map.get(key as keyof MapType) as MapType[K] | undefined;
|
||||
}
|
||||
return { get };
|
||||
|
||||
@@ -11,7 +11,7 @@ import { decodeCQCode } from '@/napcat-onebot/helper/cqcode';
|
||||
import { MessageUnique } from 'napcat-common/src/message-unique';
|
||||
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from 'napcat-core';
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ForwardMsgBuilder } from 'napcat-common/src/forward-msg-builder';
|
||||
import { ForwardMsgBuilder } from '@/napcat-core/helper/forward-msg-builder';
|
||||
import { stringifyWithBigInt } from 'napcat-common/src/helper';
|
||||
import { PacketMsg } from 'napcat-core/packet/message/message';
|
||||
import { rawMsgWithSendMsg } from 'napcat-core/packet/message/converter';
|
||||
|
||||
@@ -125,8 +125,11 @@ export const ActionName = {
|
||||
// 以下为扩展napcat扩展
|
||||
Unknown: 'unknown',
|
||||
SetDiyOnlineStatus: 'set_diy_online_status',
|
||||
SharePeer: 'ArkSharePeer',
|
||||
ShareGroupEx: 'ArkShareGroup',
|
||||
SharePeer: 'ArkSharePeer',// @deprecated
|
||||
ShareGroupEx: 'ArkShareGroup',// @deprecated
|
||||
// 标准化接口
|
||||
SendGroupArkShare: 'send_group_ark_share',
|
||||
SendArkShare: 'send_ark_share',
|
||||
// RebootNormal : 'reboot_normal', //无快速登录重新启动
|
||||
GetRobotUinRange: 'get_robot_uin_range',
|
||||
SetOnlineStatus: 'set_online_status',
|
||||
|
||||
@@ -5,7 +5,7 @@ import { NetworkAdapterConfig } from '@/napcat-onebot/config/config';
|
||||
import { StreamPacket, StreamStatus } from './StreamBasic';
|
||||
import fs from 'fs';
|
||||
import { decode } from 'silk-wasm';
|
||||
import { FFmpegService } from 'napcat-common/src/ffmpeg';
|
||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
||||
import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream';
|
||||
|
||||
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
|
||||
|
||||
180
packages/napcat-onebot/api/file.ts
Normal file
180
packages/napcat-onebot/api/file.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
|
||||
import { encodeSilk } from '@/napcat-core/helper/audio';
|
||||
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
|
||||
import { calculateFileMD5 } from 'napcat-common/src/file';
|
||||
import { ElementType, NapCatCore, PicElement, PicSubType, SendFileElement, SendPicElement, SendPttElement, SendVideoElement } from 'napcat-core';
|
||||
import { getFileTypeForSendType } from 'napcat-core/helper/msg';
|
||||
import { imageSizeFallBack } from 'napcat-image-size';
|
||||
import { SendMessageContext } from './msg';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import pathLib from 'node:path';
|
||||
import fsPromises from 'fs/promises';
|
||||
import fs from 'fs';
|
||||
import { defaultVideoThumbB64 } from '@/napcat-core/helper/ffmpeg/video';
|
||||
export class OneBotFileApi {
|
||||
obContext: NapCatOneBot11Adapter;
|
||||
core: NapCatCore;
|
||||
constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
this.obContext = obContext;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async createValidSendFileElement (context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
|
||||
const {
|
||||
fileName: _fileName,
|
||||
path,
|
||||
fileSize,
|
||||
} = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: '',
|
||||
fileElement: {
|
||||
fileName: fileName || _fileName,
|
||||
folderId,
|
||||
filePath: path,
|
||||
fileSize: fileSize.toString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPicElement (context: SendMessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const imageSize = await imageSizeFallBack(picPath);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.PIC,
|
||||
elementId: '',
|
||||
picElement: {
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize.toString(),
|
||||
picWidth: imageSize.width,
|
||||
picHeight: imageSize.height,
|
||||
fileName,
|
||||
sourcePath: path,
|
||||
original: true,
|
||||
picType: await getFileTypeForSendType(picPath),
|
||||
picSubType: subType,
|
||||
fileUuid: '',
|
||||
fileSubId: '',
|
||||
thumbFileSize: 0,
|
||||
summary,
|
||||
} as PicElement,
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendVideoElement (context: SendMessageContext, filePath: string, fileName: string = '', _diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||
let videoInfo = {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
time: 15,
|
||||
format: 'mp4',
|
||||
size: 0,
|
||||
filePath,
|
||||
};
|
||||
let fileExt = 'mp4';
|
||||
try {
|
||||
const tempExt = (await fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.core.context.logger.logError('获取文件类型失败', e);
|
||||
}
|
||||
const newFilePath = `${filePath}.${fileExt}`;
|
||||
fs.copyFileSync(filePath, newFilePath);
|
||||
context.deleteAfterSentFiles.push(newFilePath);
|
||||
filePath = newFilePath;
|
||||
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
|
||||
fs.mkdirSync(pathLib.dirname(thumbDir), { recursive: true });
|
||||
const thumbPath = pathLib.join(pathLib.dirname(thumbDir), `${md5}_0.png`);
|
||||
try {
|
||||
videoInfo = await FFmpegService.getVideoInfo(filePath, thumbPath);
|
||||
if (!fs.existsSync(thumbPath)) {
|
||||
this.core.context.logger.logError('获取视频缩略图失败', new Error('缩略图不存在'));
|
||||
throw new Error('获取视频缩略图失败');
|
||||
}
|
||||
} catch (e) {
|
||||
this.core.context.logger.logError('获取视频信息失败', e);
|
||||
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
|
||||
}
|
||||
if (_diyThumbPath) {
|
||||
try {
|
||||
await this.core.apis.FileApi.copyFile(_diyThumbPath, thumbPath);
|
||||
} catch (e) {
|
||||
this.core.context.logger.logError('复制自定义缩略图失败', e);
|
||||
}
|
||||
}
|
||||
context.deleteAfterSentFiles.push(thumbPath);
|
||||
const thumbSize = (await fsPromises.stat(thumbPath)).size;
|
||||
const thumbMd5 = await calculateFileMD5(thumbPath);
|
||||
context.deleteAfterSentFiles.push(thumbPath);
|
||||
|
||||
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith(`.${fileExt.toLocaleLowerCase()}`) ? (fileName || _fileName) : `${fileName || _fileName}.${fileExt}`;
|
||||
return {
|
||||
elementType: ElementType.VIDEO,
|
||||
elementId: '',
|
||||
videoElement: {
|
||||
fileName: uploadName,
|
||||
filePath: path,
|
||||
videoMd5: md5,
|
||||
thumbMd5,
|
||||
fileTime: videoInfo.time,
|
||||
thumbPath: new Map([[0, thumbPath]]),
|
||||
thumbSize,
|
||||
thumbWidth: videoInfo.width,
|
||||
thumbHeight: videoInfo.height,
|
||||
fileSize: fileSize.toString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async createValidSendPttElement (_context: SendMessageContext, pttPath: string): Promise<SendPttElement> {
|
||||
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
|
||||
if (!silkPath) {
|
||||
throw new Error('语音转换失败, 请检查语音文件是否正常');
|
||||
}
|
||||
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
if (converted) {
|
||||
fsPromises.unlink(silkPath).then().catch((e) => this.core.context.logger.logError('删除临时文件失败', e));
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.PTT,
|
||||
elementId: '',
|
||||
pttElement: {
|
||||
fileName,
|
||||
filePath: path,
|
||||
md5HexStr: md5,
|
||||
fileSize: fileSize.toString(),
|
||||
duration: duration ?? 1,
|
||||
formatType: 1,
|
||||
voiceType: 1,
|
||||
voiceChangeType: 0,
|
||||
canConvert2Text: true,
|
||||
waveAmplitudes: [
|
||||
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
|
||||
],
|
||||
fileSubId: '',
|
||||
playState: 1,
|
||||
autoConvertText: 0,
|
||||
storeID: 0,
|
||||
otherBusinessInfo: {
|
||||
aiVoiceType: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
TipGroupElement,
|
||||
TipGroupElementType,
|
||||
} from 'napcat-core';
|
||||
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
|
||||
import { OB11GroupBanEvent } from '@/napcat-onebot/event/notice/OB11GroupBanEvent';
|
||||
import fastXmlParser from 'fast-xml-parser';
|
||||
import { OB11GroupMsgEmojiLikeEvent } from '@/napcat-onebot/event/notice/OB11MsgEmojiLikeEvent';
|
||||
@@ -26,6 +25,7 @@ import { FileNapCatOneBotUUID } from 'napcat-common/src/file-uuid';
|
||||
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||
import { NapProtoMsg } from 'napcat-protobuf';
|
||||
import { GroupReactNotify, PushMsg } from 'napcat-core/packet/transformer/proto';
|
||||
import { NapCatOneBot11Adapter } from '..';
|
||||
|
||||
export class OneBotGroupApi {
|
||||
obContext: NapCatOneBot11Adapter;
|
||||
@@ -172,6 +172,22 @@ export class OneBotGroupApi {
|
||||
});
|
||||
}
|
||||
|
||||
async registerParseGroupReactEventByCore () {
|
||||
this.core.event.on('event:emoji_like', async (data) => {
|
||||
const event = await this.createGroupEmojiLikeEvent(
|
||||
data.groupId,
|
||||
data.senderUin,
|
||||
data.msgSeq,
|
||||
data.emojiId,
|
||||
data.isAdd,
|
||||
data.count
|
||||
);
|
||||
if (event) {
|
||||
this.obContext.networkManager.emitEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async parsePaiYiPai (msg: RawMessage, jsonStr: string) {
|
||||
const json = JSON.parse(jsonStr);
|
||||
// 判断业务类型
|
||||
|
||||
@@ -36,7 +36,7 @@ import { uriToLocalFile } from 'napcat-common/src/file';
|
||||
import { RequestUtil } from 'napcat-common/src/request';
|
||||
import fsPromise from 'node:fs/promises';
|
||||
import { OB11FriendAddNoticeEvent } from '@/napcat-onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||
import { ForwardMsgBuilder } from 'napcat-common/src/forward-msg-builder';
|
||||
import { ForwardMsgBuilder } from '@/napcat-core/helper/forward-msg-builder';
|
||||
import { NapProtoMsg } from 'napcat-protobuf';
|
||||
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
|
||||
@@ -666,7 +666,7 @@ export class OneBotMsgApi {
|
||||
|
||||
// File service
|
||||
[OB11MessageDataType.image]: async (sendMsg, context) => {
|
||||
return await this.core.apis.FileApi.createValidSendPicElement(
|
||||
return await this.obContext.apis.FileApi.createValidSendPicElement(
|
||||
context,
|
||||
(await this.handleOb11FileLikeMessage(sendMsg, context)).path,
|
||||
sendMsg.data.summary,
|
||||
@@ -676,7 +676,7 @@ export class OneBotMsgApi {
|
||||
|
||||
[OB11MessageDataType.file]: async (sendMsg, context) => {
|
||||
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
|
||||
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName);
|
||||
return await this.obContext.apis.FileApi.createValidSendFileElement(context, path, fileName);
|
||||
},
|
||||
|
||||
[OB11MessageDataType.video]: async (sendMsg, context) => {
|
||||
@@ -691,11 +691,11 @@ export class OneBotMsgApi {
|
||||
}
|
||||
}
|
||||
|
||||
return await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb);
|
||||
return await this.obContext.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb);
|
||||
},
|
||||
|
||||
[OB11MessageDataType.voice]: async (sendMsg, context) =>
|
||||
this.core.apis.FileApi.createValidSendPttElement(context,
|
||||
this.obContext.apis.FileApi.createValidSendPttElement(context,
|
||||
(await this.handleOb11FileLikeMessage(sendMsg, context)).path),
|
||||
|
||||
[OB11MessageDataType.json]: async ({ data: { data } }) => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigBase } from 'napcat-common/src/config-base';
|
||||
import { ConfigBase } from 'napcat-core/helper/config-base';
|
||||
import type { NapCatCore } from 'napcat-core';
|
||||
import { OneBotConfig } from './config';
|
||||
import { AnySchema } from 'ajv';
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
NTMsgAtType,
|
||||
} from 'napcat-core';
|
||||
import { OB11ConfigLoader } from '@/napcat-onebot/config';
|
||||
import { pendingTokenToSend } from 'napcat-webui-backend/src/index';
|
||||
import { pendingTokenToSend } from 'napcat-webui-backend/index';
|
||||
import {
|
||||
OB11HttpClientAdapter,
|
||||
OB11WebSocketClientAdapter,
|
||||
@@ -38,7 +38,6 @@ import { ActionMap, createActionMap } from '@/napcat-onebot/action';
|
||||
import { WebUiDataRuntime } from 'napcat-webui-backend/src/helper/Data';
|
||||
import { OB11InputStatusEvent } from '@/napcat-onebot/event/notice/OB11InputStatusEvent';
|
||||
import { MessageUnique } from 'napcat-common/src/message-unique';
|
||||
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
|
||||
import { OB11FriendRequestEvent } from '@/napcat-onebot/event/request/OB11FriendRequest';
|
||||
import { OB11GroupRequestEvent } from '@/napcat-onebot/event/request/OB11GroupRequest';
|
||||
import { OB11FriendRecallNoticeEvent } from '@/napcat-onebot/event/notice/OB11FriendRecallNoticeEvent';
|
||||
@@ -54,14 +53,24 @@ import { IOB11NetworkAdapter } from '@/napcat-onebot/network/adapter';
|
||||
import { OB11HttpSSEServerAdapter } from './network/http-server-sse';
|
||||
import { OB11PluginMangerAdapter } from './network/plugin-manger';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
|
||||
import { OneBotFileApi } from './api/file';
|
||||
|
||||
interface ApiListType {
|
||||
GroupApi: OneBotGroupApi;
|
||||
UserApi: OneBotUserApi;
|
||||
FriendApi: OneBotFriendApi;
|
||||
MsgApi: OneBotMsgApi;
|
||||
QuickActionApi: OneBotQuickActionApi;
|
||||
FileApi: OneBotFileApi;
|
||||
}
|
||||
// OneBot实现类
|
||||
export class NapCatOneBot11Adapter {
|
||||
readonly core: NapCatCore;
|
||||
readonly context: InstanceContext;
|
||||
|
||||
configLoader: OB11ConfigLoader;
|
||||
public readonly apis;
|
||||
public apis: ApiListType;
|
||||
networkManager: OB11NetworkManager;
|
||||
actions: ActionMap;
|
||||
private readonly bootTime = Date.now() / 1000;
|
||||
@@ -76,6 +85,7 @@ export class NapCatOneBot11Adapter {
|
||||
FriendApi: new OneBotFriendApi(this, core),
|
||||
MsgApi: new OneBotMsgApi(this, core),
|
||||
QuickActionApi: new OneBotQuickActionApi(this, core),
|
||||
FileApi: new OneBotFileApi(this, core),
|
||||
} as const;
|
||||
this.actions = createActionMap(this, core);
|
||||
this.networkManager = new OB11NetworkManager();
|
||||
@@ -218,7 +228,7 @@ export class NapCatOneBot11Adapter {
|
||||
// this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`);
|
||||
await this.reloadNetwork(prev, newConfig);
|
||||
});
|
||||
this.apis.GroupApi.registerParseGroupReactEvent().catch(e =>
|
||||
this.apis.GroupApi.registerParseGroupReactEventByCore().catch(e =>
|
||||
this.context.logger.logError('注册群消息反应表情失败', e)
|
||||
);
|
||||
}
|
||||
@@ -236,7 +246,7 @@ export class NapCatOneBot11Adapter {
|
||||
await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter);
|
||||
}
|
||||
|
||||
private async handleConfigChange<CT extends NetworkAdapterConfig> (
|
||||
private async handleConfigChange<CT extends NetworkAdapterConfig>(
|
||||
prevConfig: NetworkAdapterConfig[],
|
||||
nowConfig: NetworkAdapterConfig[],
|
||||
adapterClass: new (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NetworkAdapterConfig } from '@/napcat-onebot/config/config';
|
||||
import { LogWrapper } from 'napcat-common/src/log';
|
||||
import { LogWrapper } from 'napcat-core/helper/log';
|
||||
import { NapCatCore } from 'napcat-core';
|
||||
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
|
||||
import { ActionMap } from '@/napcat-onebot/action';
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts"
|
||||
|
||||
@@ -1,48 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/napcat-onebot/*": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"*.ts",
|
||||
"**/*.ts"
|
||||
|
||||
@@ -1,43 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"noEmit": false,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
},
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"*.ts",
|
||||
"**/*.ts"
|
||||
|
||||
@@ -7,18 +7,18 @@ type LowerCamelCase<S extends string> = CamelCaseHelper<S, false, true>;
|
||||
type CamelCaseHelper<
|
||||
S extends string,
|
||||
CapNext extends boolean,
|
||||
IsFirstChar extends boolean,
|
||||
IsFirstChar extends boolean
|
||||
> = S extends `${infer F}${infer R}`
|
||||
? F extends '_'
|
||||
? CamelCaseHelper<R, true, false>
|
||||
: F extends `${number}`
|
||||
? `${F}${CamelCaseHelper<R, true, false>}`
|
||||
: CapNext extends true
|
||||
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: IsFirstChar extends true
|
||||
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: `${F}${CamelCaseHelper<R, false, false>}`
|
||||
: '';
|
||||
? F extends '_'
|
||||
? CamelCaseHelper<R, true, false>
|
||||
: F extends `${number}`
|
||||
? `${F}${CamelCaseHelper<R, true, false>}`
|
||||
: CapNext extends true
|
||||
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: IsFirstChar extends true
|
||||
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
|
||||
: `${F}${CamelCaseHelper<R, false, false>}`
|
||||
: '';
|
||||
|
||||
type ScalarTypeToTsType<T extends ScalarType> = T extends
|
||||
| ScalarType.DOUBLE
|
||||
@@ -28,36 +28,36 @@ type ScalarTypeToTsType<T extends ScalarType> = T extends
|
||||
| ScalarType.UINT32
|
||||
| ScalarType.SFIXED32
|
||||
| ScalarType.SINT32
|
||||
? number
|
||||
: T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64
|
||||
? bigint
|
||||
: T extends ScalarType.BOOL
|
||||
? boolean
|
||||
: T extends ScalarType.STRING
|
||||
? string
|
||||
: T extends ScalarType.BYTES
|
||||
? Uint8Array
|
||||
: never;
|
||||
? number
|
||||
: T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64
|
||||
? bigint
|
||||
: T extends ScalarType.BOOL
|
||||
? boolean
|
||||
: T extends ScalarType.STRING
|
||||
? string
|
||||
: T extends ScalarType.BYTES
|
||||
? Uint8Array
|
||||
: never;
|
||||
|
||||
interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> {
|
||||
kind: 'scalar' | 'message';
|
||||
no: number;
|
||||
type: T;
|
||||
optional: O;
|
||||
repeat: R;
|
||||
kind: 'scalar' | 'message';
|
||||
no: number;
|
||||
type: T;
|
||||
optional: O;
|
||||
repeat: R;
|
||||
}
|
||||
|
||||
export interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean>
|
||||
extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'scalar';
|
||||
extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'scalar';
|
||||
}
|
||||
|
||||
export interface MessageProtoFieldType<
|
||||
T extends () => ProtoMessageType,
|
||||
O extends boolean,
|
||||
R extends O extends true ? false : boolean,
|
||||
R extends O extends true ? false : boolean
|
||||
> extends BaseProtoFieldType<T, O, R> {
|
||||
kind: 'message';
|
||||
kind: 'message';
|
||||
}
|
||||
|
||||
type ProtoFieldType =
|
||||
@@ -65,62 +65,62 @@ type ProtoFieldType =
|
||||
| MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>;
|
||||
|
||||
type ProtoMessageType = {
|
||||
[key: string]: ProtoFieldType;
|
||||
[key: string]: ProtoFieldType;
|
||||
};
|
||||
|
||||
export function ProtoField<
|
||||
T extends ScalarType,
|
||||
O extends boolean = false,
|
||||
R extends O extends true ? false : boolean = false,
|
||||
>(no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
|
||||
R extends O extends true ? false : boolean = false
|
||||
> (no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
|
||||
export function ProtoField<
|
||||
T extends () => ProtoMessageType,
|
||||
O extends boolean = false,
|
||||
R extends O extends true ? false : boolean = false,
|
||||
>(no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
|
||||
export function ProtoField(
|
||||
no: number,
|
||||
type: ScalarType | (() => ProtoMessageType),
|
||||
optional?: boolean,
|
||||
repeat?: boolean
|
||||
R extends O extends true ? false : boolean = false
|
||||
> (no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
|
||||
export function ProtoField (
|
||||
no: number,
|
||||
type: ScalarType | (() => ProtoMessageType),
|
||||
optional?: boolean,
|
||||
repeat?: boolean
|
||||
): ProtoFieldType {
|
||||
if (typeof type === 'function') {
|
||||
return { kind: 'message', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
} else {
|
||||
return { kind: 'scalar', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
}
|
||||
if (typeof type === 'function') {
|
||||
return { kind: 'message', no, type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
} else {
|
||||
return { kind: 'scalar', no, type, optional: optional ?? false, repeat: repeat ?? false };
|
||||
}
|
||||
}
|
||||
|
||||
type ProtoFieldReturnType<T, E extends boolean> =
|
||||
NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
|
||||
? ScalarTypeToTsType<S>
|
||||
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
|
||||
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
|
||||
: never;
|
||||
NonNullable<T> extends ScalarProtoFieldType<infer S, infer _O, infer _R>
|
||||
? ScalarTypeToTsType<S>
|
||||
: T extends NonNullable<MessageProtoFieldType<infer S, infer _O, infer _R>>
|
||||
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
|
||||
: never;
|
||||
|
||||
type RequiredFieldsBaseType<T, E extends boolean> = {
|
||||
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]: T[K] extends {
|
||||
repeat: true;
|
||||
}
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>;
|
||||
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]: T[K] extends {
|
||||
repeat: true;
|
||||
}
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>;
|
||||
};
|
||||
|
||||
type OptionalFieldsBaseType<T, E extends boolean> = {
|
||||
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?: T[K] extends {
|
||||
repeat: true;
|
||||
}
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>;
|
||||
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?: T[K] extends {
|
||||
repeat: true;
|
||||
}
|
||||
? ProtoFieldReturnType<T[K], E>[]
|
||||
: ProtoFieldReturnType<T[K], E>;
|
||||
};
|
||||
|
||||
type RequiredFieldsType<T, E extends boolean> = E extends true
|
||||
? Partial<RequiredFieldsBaseType<T, E>>
|
||||
: RequiredFieldsBaseType<T, E>;
|
||||
? Partial<RequiredFieldsBaseType<T, E>>
|
||||
: RequiredFieldsBaseType<T, E>;
|
||||
|
||||
type OptionalFieldsType<T, E extends boolean> = E extends true
|
||||
? Partial<OptionalFieldsBaseType<T, E>>
|
||||
: OptionalFieldsBaseType<T, E>;
|
||||
? Partial<OptionalFieldsBaseType<T, E>>
|
||||
: OptionalFieldsBaseType<T, E>;
|
||||
|
||||
type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
|
||||
|
||||
@@ -129,72 +129,74 @@ export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
|
||||
export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
|
||||
|
||||
class NapProtoRealMsg<T extends ProtoMessageType> {
|
||||
private readonly _field: PartialFieldInfo[];
|
||||
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
|
||||
private static cache = new WeakMap<ProtoMessageType, NapProtoRealMsg<any>>();
|
||||
private readonly _field: PartialFieldInfo[];
|
||||
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
|
||||
private static cache = new WeakMap<ProtoMessageType, NapProtoRealMsg<any>>();
|
||||
|
||||
private constructor(fields: T) {
|
||||
this._field = Object.keys(fields).map((key) => {
|
||||
const field = fields[key];
|
||||
if (field.kind === 'scalar') {
|
||||
const repeatType = field.repeat
|
||||
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
|
||||
? RepeatType.UNPACKED
|
||||
: RepeatType.PACKED
|
||||
: RepeatType.NO;
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'scalar',
|
||||
T: field.type,
|
||||
opt: field.optional,
|
||||
repeat: repeatType,
|
||||
};
|
||||
} else if (field.kind === 'message') {
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'message',
|
||||
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
|
||||
T: () => NapProtoRealMsg.getInstance(field.type())._proto_msg,
|
||||
};
|
||||
}
|
||||
}) as PartialFieldInfo[];
|
||||
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
|
||||
}
|
||||
private constructor (fields: T) {
|
||||
this._field = Object.keys(fields).map((key) => {
|
||||
const field = fields[key];
|
||||
if (field.kind === 'scalar') {
|
||||
const repeatType = field.repeat
|
||||
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
|
||||
? RepeatType.UNPACKED
|
||||
: RepeatType.PACKED
|
||||
: RepeatType.NO;
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'scalar',
|
||||
T: field.type,
|
||||
opt: field.optional,
|
||||
repeat: repeatType,
|
||||
};
|
||||
} else if (field.kind === 'message') {
|
||||
return {
|
||||
no: field.no,
|
||||
name: key,
|
||||
kind: 'message',
|
||||
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
|
||||
T: () => NapProtoRealMsg.getInstance(field.type())._proto_msg,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unknown field kind: ${field.kind}`);
|
||||
}
|
||||
}) as PartialFieldInfo[];
|
||||
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
|
||||
}
|
||||
|
||||
static getInstance<T extends ProtoMessageType>(fields: T): NapProtoRealMsg<T> {
|
||||
let instance = this.cache.get(fields);
|
||||
if (!instance) {
|
||||
instance = new NapProtoRealMsg(fields);
|
||||
this.cache.set(fields, instance);
|
||||
}
|
||||
return instance;
|
||||
static getInstance<T extends ProtoMessageType>(fields: T): NapProtoRealMsg<T> {
|
||||
let instance = this.cache.get(fields);
|
||||
if (!instance) {
|
||||
instance = new NapProtoRealMsg(fields);
|
||||
this.cache.set(fields, instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
|
||||
}
|
||||
encode (data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
|
||||
}
|
||||
|
||||
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
|
||||
}
|
||||
decode (data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
|
||||
}
|
||||
}
|
||||
|
||||
export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
private realMsg: NapProtoRealMsg<T>;
|
||||
private realMsg: NapProtoRealMsg<T>;
|
||||
|
||||
constructor(fields: T) {
|
||||
this.realMsg = NapProtoRealMsg.getInstance(fields);
|
||||
}
|
||||
constructor (fields: T) {
|
||||
this.realMsg = NapProtoRealMsg.getInstance(fields);
|
||||
}
|
||||
|
||||
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this.realMsg.encode(data);
|
||||
}
|
||||
encode (data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this.realMsg.encode(data);
|
||||
}
|
||||
|
||||
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this.realMsg.decode(data);
|
||||
}
|
||||
decode (data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this.realMsg.decode(data);
|
||||
}
|
||||
}
|
||||
|
||||
export { ScalarType } from '@protobuf-ts/runtime';
|
||||
|
||||
@@ -14,12 +14,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@homebridge/node-pty-prebuilt-multiarch":"^0.12.0",
|
||||
"napcat-core": "workspace:*",
|
||||
"napcat-common": "workspace:*",
|
||||
"napcat-onebot": "workspace:*",
|
||||
"napcat-webui-backend": "workspace:*",
|
||||
"napcat-qrcode": "workspace:*"
|
||||
"@homebridge/node-pty-prebuilt-multiarch":"^0.12.0"
|
||||
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user