mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
feat: 类型规范
This commit is contained in:
@@ -1,70 +1,30 @@
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import _import from "eslint-plugin-import";
|
||||
import { fixupPluginRules } from "@eslint/compat";
|
||||
import eslint from '@eslint/js';
|
||||
import tsEslintPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import tsEslintParser from '@typescript-eslint/parser';
|
||||
import globals from "globals";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const filename = fileURLToPath(import.meta.url);
|
||||
const dirname = path.dirname(filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
export default [{
|
||||
ignores: ["src/core/proto/"],
|
||||
}, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), {
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
import: fixupPluginRules(_import),
|
||||
},
|
||||
|
||||
const customTsFlatConfig = [
|
||||
{
|
||||
name: 'typescript-eslint/base',
|
||||
languageOptions: {
|
||||
parser: tsEslintParser,
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
|
||||
settings: {
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts"],
|
||||
},
|
||||
|
||||
"import/resolver": {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
indent: ["error", 4],
|
||||
semi: ["error", "always"],
|
||||
"no-unused-vars": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
...tsEslintPlugin.configs.recommended.rules,
|
||||
'quotes': ['error', 'single'], // 使用单引号
|
||||
'semi': ['error', 'always'], // 强制使用分号
|
||||
'indent': ['error', 4], // 使用 4 空格缩进
|
||||
},
|
||||
}, {
|
||||
files: ["**/.eslintrc.{js,cjs}"],
|
||||
plugins: {
|
||||
'@typescript-eslint': tsEslintPlugin,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
ecmaVersion: 5,
|
||||
sourceType: "commonjs",
|
||||
},
|
||||
}];
|
||||
export default [eslint.configs.recommended, ...customTsFlatConfig];
|
@@ -28,11 +28,11 @@ async function handleWavFile(
|
||||
file: Buffer,
|
||||
filePath: string,
|
||||
pcmPath: string,
|
||||
logger: LogWrapper
|
||||
_logger: LogWrapper
|
||||
): Promise<{ input: Buffer; sampleRate: number }> {
|
||||
const { fmt } = getWavFileInfo(file);
|
||||
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
||||
return { input: await FFmpegService.convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
return { input: await FFmpegService.convert(filePath, pcmPath), sampleRate: 24000 };
|
||||
}
|
||||
return { input: file, sampleRate: fmt.sampleRate };
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
const pcmPath = `${pttPath}.pcm`;
|
||||
const { input, sampleRate } = isWav(file)
|
||||
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||
: { input: await FFmpegService.convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
: { input: await FFmpegService.convert(filePath, pcmPath), sampleRate: 24000 };
|
||||
const silk = await piscina.run({ input: input, sampleRate: sampleRate });
|
||||
await fsPromise.writeFile(pttPath, Buffer.from(silk.data));
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
|
@@ -73,40 +73,3 @@ export class CancelableTask<T> {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function demoAwait() {
|
||||
const executor: TaskExecutor<number> = async (resolve, reject, onCancel) => {
|
||||
let count = 0;
|
||||
const intervalId = setInterval(() => {
|
||||
count++;
|
||||
console.log(`Task is running... Count: ${count}`);
|
||||
if (count === 5) {
|
||||
clearInterval(intervalId);
|
||||
resolve(count);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
onCancel(() => {
|
||||
clearInterval(intervalId);
|
||||
console.log('Task has been canceled.');
|
||||
reject(new Error('Task was canceled'));
|
||||
});
|
||||
};
|
||||
|
||||
const task = new CancelableTask(executor);
|
||||
|
||||
task.onCancel(() => {
|
||||
console.log('Cancel listener triggered.');
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
task.cancel(); // 取消任务
|
||||
}, 6000);
|
||||
|
||||
try {
|
||||
const result = await task;
|
||||
console.log(`Task completed with result: ${result}`);
|
||||
} catch (error) {
|
||||
console.error('Task failed:', error);
|
||||
}
|
||||
}
|
@@ -60,12 +60,16 @@ export class NTEventWrapper {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> };
|
||||
};
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const serviceName = 'get' + (eventNameArr[0]?.replace('NodeIKernel', '') ?? '');
|
||||
const eventName = eventNameArr[1];
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]();
|
||||
const services = (this.WrapperSession as unknown as eventType)[serviceName]?.();
|
||||
if (!services || !eventName) {
|
||||
return undefined;
|
||||
}
|
||||
let event = services[eventName];
|
||||
|
||||
//重新绑定this
|
||||
event = event.bind(services);
|
||||
event = event?.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
@@ -126,8 +130,8 @@ export class NTEventWrapper {
|
||||
) {
|
||||
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
const ListenerMainName = ListenerNameList[0] ?? '';
|
||||
const ListenerSubName = ListenerNameList[1] ?? '';
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
@@ -205,8 +209,8 @@ export class NTEventWrapper {
|
||||
}
|
||||
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
const ListenerMainName = ListenerNameList[0]??"";
|
||||
const ListenerSubName = ListenerNameList[1]??"";
|
||||
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
(resolve, reject) => {
|
||||
|
@@ -1,18 +1,17 @@
|
||||
import { FFmpeg } from '@ffmpeg.wasm/main';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { readFileSync, statSync, writeFileSync } from 'fs';
|
||||
import type { LogWrapper } from './log';
|
||||
import type { VideoInfo } from './video';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import imageSize from 'image-size';
|
||||
class FFmpegService {
|
||||
class FFmpegService {
|
||||
public static async extractThumbnail(videoPath: string, thumbnailPath: string): Promise<void> {
|
||||
const ffmpegInstance = await FFmpeg.create({ core: '@ffmpeg.wasm/core-mt' });
|
||||
const videoFileName = `${randomUUID()}.mp4`;
|
||||
const outputFileName = `${randomUUID()}.jpg`;
|
||||
try {
|
||||
ffmpegInstance.fs.writeFile(videoFileName, readFileSync(videoPath));
|
||||
let code = await ffmpegInstance.run('-i', videoFileName, '-ss', '00:00:01.000', '-vframes', '1', outputFileName);
|
||||
const code = await ffmpegInstance.run('-i', videoFileName, '-ss', '00:00:01.000', '-vframes', '1', outputFileName);
|
||||
if (code !== 0) {
|
||||
throw new Error('Error extracting thumbnail: FFmpeg process exited with code ' + code);
|
||||
}
|
||||
@@ -44,7 +43,7 @@ import imageSize from 'image-size';
|
||||
const params = format === 'amr'
|
||||
? ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFileName, '-ar', '8000', '-b:a', '12.2k', outputFileName]
|
||||
: ['-f', 's16le', '-ar', '24000', '-ac', '1', '-i', inputFileName, outputFileName];
|
||||
let code = await ffmpegInstance.run(...params);
|
||||
const code = await ffmpegInstance.run(...params);
|
||||
if (code !== 0) {
|
||||
throw new Error('Error extracting thumbnail: FFmpeg process exited with code ' + code);
|
||||
}
|
||||
@@ -74,7 +73,7 @@ import imageSize from 'image-size';
|
||||
try {
|
||||
ffmpegInstance.fs.writeFile(inputFileName, readFileSync(filePath));
|
||||
const params = ['-y', '-i', inputFileName, '-ar', '24000', '-ac', '1', '-f', 's16le', outputFileName];
|
||||
let code = await ffmpegInstance.run(...params);
|
||||
const code = await ffmpegInstance.run(...params);
|
||||
if (code !== 0) {
|
||||
throw new Error('FFmpeg process exited with code ' + code);
|
||||
}
|
||||
@@ -99,24 +98,24 @@ import imageSize from 'image-size';
|
||||
|
||||
public static async getVideoInfo(videoPath: string, thumbnailPath: string): Promise<VideoInfo> {
|
||||
await FFmpegService.extractThumbnail(videoPath, thumbnailPath);
|
||||
let fileType = (await fileTypeFromFile(videoPath))?.ext ?? 'mp4';
|
||||
const fileType = (await fileTypeFromFile(videoPath))?.ext ?? 'mp4';
|
||||
const inputFileName = `${randomUUID()}.${fileType}`;
|
||||
const ffmpegInstance = await FFmpeg.create({ core: '@ffmpeg.wasm/core-mt' });
|
||||
ffmpegInstance.fs.writeFile(inputFileName, readFileSync(videoPath));
|
||||
ffmpegInstance.setLogging(true);
|
||||
let duration = 60;
|
||||
ffmpegInstance.setLogger((level, ...msg) => {
|
||||
ffmpegInstance.setLogger((_level, ...msg) => {
|
||||
const message = msg.join(' ');
|
||||
const durationMatch = message.match(/Duration: (\d+):(\d+):(\d+\.\d+)/);
|
||||
if (durationMatch) {
|
||||
const hours = parseInt(durationMatch[1], 10);
|
||||
const minutes = parseInt(durationMatch[2], 10);
|
||||
const seconds = parseFloat(durationMatch[3]);
|
||||
const hours = parseInt(durationMatch[1] ?? "0", 10);
|
||||
const minutes = parseInt(durationMatch[2] ?? "0", 10);
|
||||
const seconds = parseFloat(durationMatch[3] ?? "0");
|
||||
duration = hours * 3600 + minutes * 60 + seconds;
|
||||
}
|
||||
});
|
||||
await ffmpegInstance.run('-i', inputFileName);
|
||||
let image = imageSize(thumbnailPath);
|
||||
const image = imageSize(thumbnailPath);
|
||||
ffmpegInstance.fs.unlink(inputFileName);
|
||||
const fileSize = statSync(videoPath).size;
|
||||
return {
|
||||
@@ -126,7 +125,7 @@ import imageSize from 'image-size';
|
||||
format: fileType,
|
||||
size: fileSize,
|
||||
filePath: videoPath
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
type FFmpegMethod = 'extractThumbnail' | 'convertFile' | 'convert' | 'getVideoInfo';
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import Piscina from "piscina";
|
||||
import { VideoInfo } from "./video";
|
||||
import type { LogWrapper } from "./log";
|
||||
|
||||
type EncodeArgs = {
|
||||
method: 'extractThumbnail' | 'convertFile' | 'convert' | 'getVideoInfo';
|
||||
|
@@ -190,7 +190,7 @@ export async function uriToLocalFile(dir: string, uri: string, filename: string
|
||||
}
|
||||
|
||||
case FileUriType.Remote: {
|
||||
const buffer = await httpDownload({ url: HandledUri, headers: headers });
|
||||
const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {} });
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
|
@@ -166,7 +166,7 @@ export function calcQQLevel(level?: QQLevel) {
|
||||
}
|
||||
|
||||
export function stringifyWithBigInt(obj: any) {
|
||||
return JSON.stringify(obj, (key, value) =>
|
||||
return JSON.stringify(obj, (_key, value) =>
|
||||
typeof value === 'bigint' ? value.toString() : value
|
||||
);
|
||||
}
|
||||
|
@@ -68,8 +68,11 @@ export class LimitedHashTable<K, V> {
|
||||
const listSize = Math.min(size, keyList.length);
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
const key = keyList[listSize - i];
|
||||
if (key !== undefined) {
|
||||
result.push({ key, value: this.keyToValue.get(key)! });
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -96,8 +99,10 @@ class MessageUniqueWrapper {
|
||||
createUniqueMsgId(peer: Peer, msgId: string) {
|
||||
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
|
||||
const hash = crypto.createHash('md5').update(key).digest();
|
||||
if (hash[0]) {
|
||||
//设置第一个bit为0 保证shortId为正数
|
||||
hash[0] &= 0x7f;
|
||||
}
|
||||
const shortId = hash.readInt32BE(0);
|
||||
//减少性能损耗
|
||||
this.msgIdMap.set(msgId, shortId);
|
||||
@@ -110,11 +115,11 @@ class MessageUniqueWrapper {
|
||||
if (data) {
|
||||
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
||||
const peer: Peer = {
|
||||
chatType: parseInt(chatTypeStr),
|
||||
peerUid,
|
||||
chatType: parseInt(chatTypeStr ?? '0'),
|
||||
peerUid: peerUid ?? '',
|
||||
guildId: '',
|
||||
};
|
||||
return { MsgId: msgId, Peer: peer };
|
||||
return { MsgId: msgId ?? '0', Peer: peer };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@@ -41,12 +41,14 @@ export class RequestUtil {
|
||||
|
||||
private static extractCookies(setCookieHeaders: string[], cookies: { [key: string]: string }) {
|
||||
setCookieHeaders.forEach((cookie) => {
|
||||
const parts = cookie.split(';')[0].split('=');
|
||||
const parts = cookie.split(';')[0]?.split('=');
|
||||
if (parts) {
|
||||
const key = parts[0];
|
||||
const value = parts[1];
|
||||
if (key && value && key.length > 0 && value.length > 0) {
|
||||
cookies[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { OB11EmitEventContent } from './index';
|
||||
import { Request, Response } from 'express';
|
||||
import { OB11Response } from '@/onebot/action/OneBotAction';
|
||||
import { OB11HttpServerAdapter } from './http-server';
|
||||
|
||||
export class OB11HttpSSEServerAdapter extends OB11HttpServerAdapter {
|
||||
private sseClients: Response[] = [];
|
||||
|
||||
async handleRequest(req: Request, res: Response): Promise<any> {
|
||||
override async handleRequest(req: Request, res: Response): Promise<any> {
|
||||
if (req.path === '/_events') {
|
||||
return this.createSseSupport(req, res);
|
||||
} else {
|
||||
@@ -26,7 +25,7 @@ export class OB11HttpSSEServerAdapter extends OB11HttpServerAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
onEvent<T extends OB11EmitEventContent>(event: T) {
|
||||
override onEvent<T extends OB11EmitEventContent>(event: T) {
|
||||
this.sseClients.forEach((res) => {
|
||||
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
||||
});
|
||||
|
@@ -18,7 +18,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
|
||||
super(name, config, core, obContext, actions);
|
||||
}
|
||||
|
||||
onEvent<T extends OB11EmitEventContent>(event: T) {
|
||||
onEvent<T extends OB11EmitEventContent>(_event: T) {
|
||||
// http server is passive, no need to emit event
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
|
||||
return res.status(400).send('Invalid JSON');
|
||||
}
|
||||
});
|
||||
req.on('error', (err) => {
|
||||
req.on('error', (_err) => {
|
||||
return res.status(400).send('Invalid JSON');
|
||||
});
|
||||
});
|
||||
|
6
src/universal/LiteLoader.d.ts
vendored
6
src/universal/LiteLoader.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
declare global {
|
||||
namespace globalThis {
|
||||
var LiteLoader: symbol;
|
||||
}
|
||||
}
|
||||
export {};
|
@@ -1,6 +1,13 @@
|
||||
import { NCoreInitShell } from "@/shell/base";
|
||||
export * from "@/framework/napcat";
|
||||
export * from "@/shell/base";
|
||||
if (global.LiteLoader == undefined) {
|
||||
|
||||
interface LiteLoaderGlobal extends Global {
|
||||
LiteLoader?: unknown;
|
||||
}
|
||||
|
||||
declare const global: LiteLoaderGlobal;
|
||||
|
||||
if (global.LiteLoader === undefined) {
|
||||
NCoreInitShell();
|
||||
}
|
@@ -18,8 +18,8 @@
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"paths": {
|
||||
@@ -28,8 +28,22 @@
|
||||
],
|
||||
"@webapi/*": [
|
||||
"./src/webui/src/*"
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": false, // false for now
|
||||
"noPropertyAccessFromIndexSignature": false, // false for now
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"noImplicitOverride": true,
|
||||
"strictPropertyInitialization": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
Reference in New Issue
Block a user