diff --git a/eslint.config.mjs b/eslint.config.mjs index adf72e5e..6859bd8b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -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), - }, - - languageOptions: { - globals: { - ...globals.browser, - ...globals.node, - }, - - parser: tsParser, - ecmaVersion: "latest", - sourceType: "module", - }, - - settings: { - "import/parsers": { - "@typescript-eslint/parser": [".ts"], - }, - - "import/resolver": { - typescript: { - alwaysTryTypes: true, +const customTsFlatConfig = [ + { + name: 'typescript-eslint/base', + languageOptions: { + parser: tsEslintParser, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, }, }, - }, - - 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"], - }, -}, { - files: ["**/.eslintrc.{js,cjs}"], - - languageOptions: { - globals: { - ...globals.node, + files: ['**/*.{ts,tsx}'], + rules: { + ...tsEslintPlugin.configs.recommended.rules, + 'quotes': ['error', 'single'], // 使用单引号 + 'semi': ['error', 'always'], // 强制使用分号 + 'indent': ['error', 4], // 使用 4 空格缩进 + }, + plugins: { + '@typescript-eslint': tsEslintPlugin, }, - ecmaVersion: 5, - sourceType: "commonjs", }, -}]; +]; + +export default [eslint.configs.recommended, ...customTsFlatConfig]; \ No newline at end of file diff --git a/src/common/audio.ts b/src/common/audio.ts index 2cd7bbf6..771c57f3 100644 --- a/src/common/audio.ts +++ b/src/common/audio.ts @@ -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); diff --git a/src/common/cancel-task.ts b/src/common/cancel-task.ts index 9fa8161a..454f2a62 100644 --- a/src/common/cancel-task.ts +++ b/src/common/cancel-task.ts @@ -73,40 +73,3 @@ export class CancelableTask { }; } } - -async function demoAwait() { - const executor: TaskExecutor = 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); - } -} \ No newline at end of file diff --git a/src/common/event.ts b/src/common/event.ts index 33492011..ed2e36af 100644 --- a/src/common/event.ts +++ b/src/common/event.ts @@ -60,12 +60,16 @@ export class NTEventWrapper { [key: string]: () => { [key: string]: (...params: Parameters) => Promise> }; }; 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>((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 | 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>, ...Parameters]>( (resolve, reject) => { diff --git a/src/common/ffmpeg-worker.ts b/src/common/ffmpeg-worker.ts index 7ee7b2d8..a97cdd4a 100644 --- a/src/common/ffmpeg-worker.ts +++ b/src/common/ffmpeg-worker.ts @@ -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 { 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); } @@ -96,27 +95,27 @@ import imageSize from 'image-size'; } } } - + public static async getVideoInfo(videoPath: string, thumbnailPath: string): Promise { 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'; @@ -137,15 +136,15 @@ interface FFmpegTask { } export default async function handleFFmpegTask({ method, args }: FFmpegTask): Promise { switch (method) { - case 'extractThumbnail': - return await FFmpegService.extractThumbnail(...args as [string, string]); - case 'convertFile': - return await FFmpegService.convertFile(...args as [string, string, string]); - case 'convert': - return await FFmpegService.convert(...args as [string, string]); - case 'getVideoInfo': - return await FFmpegService.getVideoInfo(...args as [string, string]); - default: - throw new Error(`Unknown method: ${method}`); + case 'extractThumbnail': + return await FFmpegService.extractThumbnail(...args as [string, string]); + case 'convertFile': + return await FFmpegService.convertFile(...args as [string, string, string]); + case 'convert': + return await FFmpegService.convert(...args as [string, string]); + case 'getVideoInfo': + return await FFmpegService.getVideoInfo(...args as [string, string]); + default: + throw new Error(`Unknown method: ${method}`); } } \ No newline at end of file diff --git a/src/common/ffmpeg.ts b/src/common/ffmpeg.ts index efc72571..57e15e22 100644 --- a/src/common/ffmpeg.ts +++ b/src/common/ffmpeg.ts @@ -1,6 +1,5 @@ import Piscina from "piscina"; import { VideoInfo } from "./video"; -import type { LogWrapper } from "./log"; type EncodeArgs = { method: 'extractThumbnail' | 'convertFile' | 'convert' | 'getVideoInfo'; diff --git a/src/common/file.ts b/src/common/file.ts index 0bb65789..4ffe8386 100644 --- a/src/common/file.ts +++ b/src/common/file.ts @@ -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 }; } diff --git a/src/common/helper.ts b/src/common/helper.ts index f426cf6c..24da598d 100644 --- a/src/common/helper.ts +++ b/src/common/helper.ts @@ -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 ); } diff --git a/src/common/message-unique.ts b/src/common/message-unique.ts index cc4187e7..3f6785f2 100644 --- a/src/common/message-unique.ts +++ b/src/common/message-unique.ts @@ -68,7 +68,10 @@ export class LimitedHashTable { const listSize = Math.min(size, keyList.length); for (let i = 0; i < listSize; i++) { const key = keyList[listSize - i]; - result.push({ key, value: this.keyToValue.get(key)! }); + 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(); - //设置第一个bit为0 保证shortId为正数 - hash[0] &= 0x7f; + 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; } diff --git a/src/common/request.ts b/src/common/request.ts index 11523842..b089b28d 100644 --- a/src/common/request.ts +++ b/src/common/request.ts @@ -41,11 +41,13 @@ export class RequestUtil { private static extractCookies(setCookieHeaders: string[], cookies: { [key: string]: string }) { setCookieHeaders.forEach((cookie) => { - const parts = cookie.split(';')[0].split('='); - const key = parts[0]; - const value = parts[1]; - if (key && value && key.length > 0 && value.length > 0) { - cookies[key] = value; + 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; + } } }); } diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts index a9122391..b8629a6d 100644 --- a/src/core/apis/file.ts +++ b/src/core/apis/file.ts @@ -40,7 +40,7 @@ export class NTQQFileApi { this.rkeyManager = new RkeyManager([ 'https://rkey.napneko.icu/rkeys' ], - this.context.logger + this.context.logger ); } @@ -274,18 +274,18 @@ export class NTQQFileApi { element.elementType === ElementType.FILE ) { switch (element.elementType) { - case ElementType.PIC: + case ElementType.PIC: element.picElement!.sourcePath = elementResults[elementIndex]; - break; - case ElementType.VIDEO: + break; + case ElementType.VIDEO: element.videoElement!.filePath = elementResults[elementIndex]; - break; - case ElementType.PTT: + break; + case ElementType.PTT: element.pttElement!.filePath = elementResults[elementIndex]; - break; - case ElementType.FILE: + break; + case ElementType.FILE: element.fileElement!.filePath = elementResults[elementIndex]; - break; + break; } elementIndex++; } diff --git a/src/onebot/network/http-server-sse.ts b/src/onebot/network/http-server-sse.ts index 893dfce7..6b4839ed 100644 --- a/src/onebot/network/http-server-sse.ts +++ b/src/onebot/network/http-server-sse.ts @@ -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 { + override async handleRequest(req: Request, res: Response): Promise { if (req.path === '/_events') { return this.createSseSupport(req, res); } else { @@ -26,7 +25,7 @@ export class OB11HttpSSEServerAdapter extends OB11HttpServerAdapter { }); } - onEvent(event: T) { + override onEvent(event: T) { this.sseClients.forEach((res) => { res.write(`data: ${JSON.stringify(event)}\n\n`); }); diff --git a/src/onebot/network/http-server.ts b/src/onebot/network/http-server.ts index 0b659901..f4f5b207 100644 --- a/src/onebot/network/http-server.ts +++ b/src/onebot/network/http-server.ts @@ -18,7 +18,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter super(name, config, core, obContext, actions); } - onEvent(event: T) { + onEvent(_event: T) { // http server is passive, no need to emit event } @@ -65,7 +65,7 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter return res.status(400).send('Invalid JSON'); } }); - req.on('error', (err) => { + req.on('error', (_err) => { return res.status(400).send('Invalid JSON'); }); }); diff --git a/src/universal/LiteLoader.d.ts b/src/universal/LiteLoader.d.ts deleted file mode 100644 index df151232..00000000 --- a/src/universal/LiteLoader.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare global { - namespace globalThis { - var LiteLoader: symbol; - } -} -export {}; \ No newline at end of file diff --git a/src/universal/napcat.ts b/src/universal/napcat.ts index 016a4158..d5fd0d5b 100644 --- a/src/universal/napcat.ts +++ b/src/universal/napcat.ts @@ -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(); } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 48537ae5..c5c47900 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"