diff --git a/src/common/audio-worker.ts b/src/common/audio-worker.ts index c1984946..1a20e185 100644 --- a/src/common/audio-worker.ts +++ b/src/common/audio-worker.ts @@ -2,19 +2,19 @@ import { encode } from 'silk-wasm'; import { parentPort } from 'worker_threads'; export interface EncodeArgs { - input: ArrayBufferView | ArrayBuffer - sampleRate: number + input: ArrayBufferView | ArrayBuffer + sampleRate: number } -export function recvTask(cb: (taskData: T) => Promise) { - parentPort?.on('message', async (taskData: T) => { - try { - let ret = await cb(taskData); - parentPort?.postMessage(ret); - } catch (error: unknown) { - parentPort?.postMessage({ error: (error as Error).message }); - } - }); +export function recvTask (cb: (taskData: T) => Promise) { + parentPort?.on('message', async (taskData: T) => { + try { + const ret = await cb(taskData); + parentPort?.postMessage(ret); + } catch (error: unknown) { + parentPort?.postMessage({ error: (error as Error).message }); + } + }); } recvTask(async ({ input, sampleRate }) => { - return await encode(input, sampleRate); -}); \ No newline at end of file + return await encode(input, sampleRate); +}); diff --git a/src/common/audio.ts b/src/common/audio.ts index 030048bd..fbfe8872 100644 --- a/src/common/audio.ts +++ b/src/common/audio.ts @@ -10,76 +10,75 @@ import { fileURLToPath } from 'node:url'; const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000]; -function getWorkerPath() { - //return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href; - return path.join(path.dirname(fileURLToPath(import.meta.url)), 'audio-worker.mjs'); +function getWorkerPath () { + // return new URL(/* @vite-ignore */ './audio-worker.mjs', import.meta.url).href; + return path.join(path.dirname(fileURLToPath(import.meta.url)), 'audio-worker.mjs'); } - -async function guessDuration(pttPath: string, logger: LogWrapper) { - const pttFileInfo = await fsPromise.stat(pttPath); - const duration = Math.max(1, Math.floor(pttFileInfo.size / 1024 / 3)); // 3kb/s - logger.log('通过文件大小估算语音的时长:', duration); - return duration; +async function guessDuration (pttPath: string, logger: LogWrapper) { + const pttFileInfo = await fsPromise.stat(pttPath); + const duration = Math.max(1, Math.floor(pttFileInfo.size / 1024 / 3)); // 3kb/s + logger.log('通过文件大小估算语音的时长:', duration); + return duration; } -async function handleWavFile( - file: Buffer, - filePath: string, - pcmPath: string +async function handleWavFile ( + file: Buffer, + filePath: string, + pcmPath: string ): Promise<{ input: Buffer; sampleRate: number }> { - const { fmt } = getWavFileInfo(file); - if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) { - const result = await FFmpegService.convert(filePath, pcmPath); - return { input: await fsPromise.readFile(pcmPath), sampleRate: result.sampleRate }; - } - return { input: file, sampleRate: fmt.sampleRate }; + const { fmt } = getWavFileInfo(file); + if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) { + const result = await FFmpegService.convert(filePath, pcmPath); + return { input: await fsPromise.readFile(pcmPath), sampleRate: result.sampleRate }; + } + return { input: file, sampleRate: fmt.sampleRate }; } -export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) { - try { - const file = await fsPromise.readFile(filePath); - const pttPath = path.join(TEMP_DIR, randomUUID()); - if (!isSilk(file)) { - logger.log(`语音文件${filePath}需要转换成silk`); - const pcmPath = `${pttPath}.pcm`; - // const { input, sampleRate } = isWav(file) ? await handleWavFile(file, filePath, pcmPath): { input: await FFmpegService.convert(filePath, pcmPath) ? await fsPromise.readFile(pcmPath) : Buffer.alloc(0), sampleRate: 24000 }; - let input: Buffer; - let sampleRate: number; - if (isWav(file)) { - const result = await handleWavFile(file, filePath, pcmPath); - input = result.input; - sampleRate = result.sampleRate; - } else { - const result = await FFmpegService.convert(filePath, pcmPath); - input = await fsPromise.readFile(pcmPath); - sampleRate = result.sampleRate; - } - const silk = await runTask(getWorkerPath(), { input: input, sampleRate: sampleRate }); - fsPromise.unlink(pcmPath).catch((e) => logger.logError('删除临时文件失败', pcmPath, e)); - await fsPromise.writeFile(pttPath, Buffer.from(silk.data)); - logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration); - return { - converted: true, - path: pttPath, - duration: silk.duration / 1000, - }; - } else { - let duration = 0; - try { - duration = getDuration(file) / 1000; - } catch (e: unknown) { - logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, (e as Error).stack); - duration = await guessDuration(filePath, logger); - } - return { - converted: false, - path: filePath, - duration, - }; - } - } catch (error: unknown) { - logger.logError('convert silk failed', error); - return {}; +export async function encodeSilk (filePath: string, TEMP_DIR: string, logger: LogWrapper) { + try { + const file = await fsPromise.readFile(filePath); + const pttPath = path.join(TEMP_DIR, randomUUID()); + if (!isSilk(file)) { + logger.log(`语音文件${filePath}需要转换成silk`); + const pcmPath = `${pttPath}.pcm`; + // const { input, sampleRate } = isWav(file) ? await handleWavFile(file, filePath, pcmPath): { input: await FFmpegService.convert(filePath, pcmPath) ? await fsPromise.readFile(pcmPath) : Buffer.alloc(0), sampleRate: 24000 }; + let input: Buffer; + let sampleRate: number; + if (isWav(file)) { + const result = await handleWavFile(file, filePath, pcmPath); + input = result.input; + sampleRate = result.sampleRate; + } else { + const result = await FFmpegService.convert(filePath, pcmPath); + input = await fsPromise.readFile(pcmPath); + sampleRate = result.sampleRate; + } + const silk = await runTask(getWorkerPath(), { input, sampleRate }); + fsPromise.unlink(pcmPath).catch((e) => logger.logError('删除临时文件失败', pcmPath, e)); + await fsPromise.writeFile(pttPath, Buffer.from(silk.data)); + logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration); + return { + converted: true, + path: pttPath, + duration: silk.duration / 1000, + }; + } else { + let duration = 0; + try { + duration = getDuration(file) / 1000; + } catch (e: unknown) { + logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, (e as Error).stack); + duration = await guessDuration(filePath, logger); + } + return { + converted: false, + path: filePath, + duration, + }; } + } catch (error: unknown) { + logger.logError('convert silk failed', error); + return {}; + } } diff --git a/src/common/cancel-task.ts b/src/common/cancel-task.ts index b81a64a2..4be12365 100644 --- a/src/common/cancel-task.ts +++ b/src/common/cancel-task.ts @@ -1,80 +1,79 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ export type TaskExecutor = (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, onCancel: (callback: () => void) => void) => void | Promise; export class CancelableTask { - private promise: Promise; - private cancelCallback: (() => void) | null = null; - private isCanceled = false; - private cancelListeners: Array<() => void> = []; + private promise: Promise; + private cancelCallback: (() => void) | null = null; + private isCanceled = false; + private cancelListeners: Array<() => void> = []; - constructor(executor: TaskExecutor) { - this.promise = new Promise((resolve, reject) => { - const onCancel = (callback: () => void) => { - this.cancelCallback = callback; - }; + constructor (executor: TaskExecutor) { + this.promise = new Promise((resolve, reject) => { + const onCancel = (callback: () => void) => { + this.cancelCallback = callback; + }; - const execute = async () => { - try { - await executor( - (value) => { - if (!this.isCanceled) { - resolve(value); - } - }, - (reason) => { - if (!this.isCanceled) { - reject(reason); - } - }, - onCancel - ); - } catch (error) { - if (!this.isCanceled) { - reject(error); - } - } - }; - - execute(); - }); - } - - public cancel() { - if (this.cancelCallback) { - this.cancelCallback(); + const execute = async () => { + try { + await executor( + (value) => { + if (!this.isCanceled) { + resolve(value); + } + }, + (reason) => { + if (!this.isCanceled) { + reject(reason); + } + }, + onCancel + ); + } catch (error) { + if (!this.isCanceled) { + reject(error); + } } - this.isCanceled = true; - this.cancelListeners.forEach(listener => listener()); - } + }; - public isTaskCanceled(): boolean { - return this.isCanceled; - } + execute(); + }); + } - public onCancel(listener: () => void) { - this.cancelListeners.push(listener); + public cancel () { + if (this.cancelCallback) { + this.cancelCallback(); } + this.isCanceled = true; + this.cancelListeners.forEach(listener => listener()); + } - public then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null - ): Promise { - return this.promise.then(onfulfilled, onrejected); - } + public isTaskCanceled (): boolean { + return this.isCanceled; + } - public catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null - ): Promise { - return this.promise.catch(onrejected); - } + public onCancel (listener: () => void) { + this.cancelListeners.push(listener); + } - public finally(onfinally?: (() => void) | undefined | null): Promise { - return this.promise.finally(onfinally); - } + public then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null + ): Promise { + return this.promise.then(onfulfilled, onrejected); + } - [Symbol.asyncIterator]() { - return { - next: () => this.promise.then(value => ({ value, done: true })), - }; - } -} \ No newline at end of file + public catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null + ): Promise { + return this.promise.catch(onrejected); + } + + public finally (onfinally?: (() => void) | undefined | null): Promise { + return this.promise.finally(onfinally); + } + + [Symbol.asyncIterator] () { + return { + next: () => this.promise.then(value => ({ value, done: true })), + }; + } +} diff --git a/src/common/clean-task.ts b/src/common/clean-task.ts index 56c8567e..01fff452 100644 --- a/src/common/clean-task.ts +++ b/src/common/clean-task.ts @@ -2,228 +2,228 @@ import fs from 'fs'; // generate Claude 3.7 Sonet Thinking interface FileRecord { - filePath: string; - addedTime: number; - retries: number; + filePath: string; + addedTime: number; + retries: number; } interface CleanupTask { - fileRecord: FileRecord; - timer: NodeJS.Timeout; + fileRecord: FileRecord; + timer: NodeJS.Timeout; } class CleanupQueue { - private tasks: Map = new Map(); - private readonly MAX_RETRIES = 3; - private isProcessing: boolean = false; - private pendingOperations: Array<() => void> = []; + private tasks: Map = new Map(); + private readonly MAX_RETRIES = 3; + private isProcessing: boolean = false; + private pendingOperations: Array<() => void> = []; - /** + /** * 执行队列中的待处理操作,确保异步安全 */ - private executeNextOperation(): void { - if (this.pendingOperations.length === 0) { - this.isProcessing = false; - return; - } - - this.isProcessing = true; - const operation = this.pendingOperations.shift(); - operation?.(); - - // 使用 setImmediate 允许事件循环继续,防止阻塞 - setImmediate(() => this.executeNextOperation()); + private executeNextOperation (): void { + if (this.pendingOperations.length === 0) { + this.isProcessing = false; + return; } - /** + this.isProcessing = true; + const operation = this.pendingOperations.shift(); + operation?.(); + + // 使用 setImmediate 允许事件循环继续,防止阻塞 + setImmediate(() => this.executeNextOperation()); + } + + /** * 安全执行操作,防止竞态条件 * @param operation 要执行的操作 */ - private safeExecute(operation: () => void): void { - this.pendingOperations.push(operation); - if (!this.isProcessing) { - this.executeNextOperation(); - } + private safeExecute (operation: () => void): void { + this.pendingOperations.push(operation); + if (!this.isProcessing) { + this.executeNextOperation(); } + } - /** + /** * 检查文件是否存在 * @param filePath 文件路径 * @returns 文件是否存在 */ - private fileExists(filePath: string): boolean { - try { - return fs.existsSync(filePath); - } catch (error) { - //console.log(`检查文件存在出错: ${filePath}`, error); - return false; - } + private fileExists (filePath: string): boolean { + try { + return fs.existsSync(filePath); + } catch (error) { + // console.log(`检查文件存在出错: ${filePath}`, error); + return false; } + } - /** + /** * 添加文件到清理队列 * @param filePath 文件路径 * @param cleanupDelay 清理延迟时间(毫秒) */ - addFile(filePath: string, cleanupDelay: number): void { - this.safeExecute(() => { - // 如果文件已在队列中,取消原来的计时器 - if (this.tasks.has(filePath)) { - this.cancelCleanup(filePath); - } + addFile (filePath: string, cleanupDelay: number): void { + this.safeExecute(() => { + // 如果文件已在队列中,取消原来的计时器 + if (this.tasks.has(filePath)) { + this.cancelCleanup(filePath); + } - // 创建新的文件记录 - const fileRecord: FileRecord = { - filePath, - addedTime: Date.now(), - retries: 0 - }; + // 创建新的文件记录 + const fileRecord: FileRecord = { + filePath, + addedTime: Date.now(), + retries: 0, + }; - // 设置计时器 - const timer = setTimeout(() => { - this.cleanupFile(fileRecord, cleanupDelay); - }, cleanupDelay); + // 设置计时器 + const timer = setTimeout(() => { + this.cleanupFile(fileRecord, cleanupDelay); + }, cleanupDelay); - // 添加到任务队列 - this.tasks.set(filePath, { fileRecord, timer }); - }); - } + // 添加到任务队列 + this.tasks.set(filePath, { fileRecord, timer }); + }); + } - /** + /** * 批量添加文件到清理队列 * @param filePaths 文件路径数组 * @param cleanupDelay 清理延迟时间(毫秒) */ - addFiles(filePaths: string[], cleanupDelay: number): void { - this.safeExecute(() => { - for (const filePath of filePaths) { - // 内部直接处理,不通过 safeExecute 以保证批量操作的原子性 - if (this.tasks.has(filePath)) { - // 取消已有的计时器,但不使用 cancelCleanup 方法以避免重复的安全检查 - const existingTask = this.tasks.get(filePath); - if (existingTask) { - clearTimeout(existingTask.timer); - } - } + addFiles (filePaths: string[], cleanupDelay: number): void { + this.safeExecute(() => { + for (const filePath of filePaths) { + // 内部直接处理,不通过 safeExecute 以保证批量操作的原子性 + if (this.tasks.has(filePath)) { + // 取消已有的计时器,但不使用 cancelCleanup 方法以避免重复的安全检查 + const existingTask = this.tasks.get(filePath); + if (existingTask) { + clearTimeout(existingTask.timer); + } + } - const fileRecord: FileRecord = { - filePath, - addedTime: Date.now(), - retries: 0 - }; + const fileRecord: FileRecord = { + filePath, + addedTime: Date.now(), + retries: 0, + }; - const timer = setTimeout(() => { - this.cleanupFile(fileRecord, cleanupDelay); - }, cleanupDelay); + const timer = setTimeout(() => { + this.cleanupFile(fileRecord, cleanupDelay); + }, cleanupDelay); - this.tasks.set(filePath, { fileRecord, timer }); - } - }); - } + this.tasks.set(filePath, { fileRecord, timer }); + } + }); + } - /** + /** * 清理文件 * @param record 文件记录 * @param delay 延迟时间,用于重试 */ - private cleanupFile(record: FileRecord, delay: number): void { - this.safeExecute(() => { - // 首先检查文件是否存在,不存在则视为清理成功 - if (!this.fileExists(record.filePath)) { - //console.log(`文件已不存在,跳过清理: ${record.filePath}`); - this.tasks.delete(record.filePath); - return; - } + private cleanupFile (record: FileRecord, delay: number): void { + this.safeExecute(() => { + // 首先检查文件是否存在,不存在则视为清理成功 + if (!this.fileExists(record.filePath)) { + // console.log(`文件已不存在,跳过清理: ${record.filePath}`); + this.tasks.delete(record.filePath); + return; + } - try { - // 尝试删除文件 - fs.unlinkSync(record.filePath); - // 删除成功,从队列中移除任务 - this.tasks.delete(record.filePath); - } catch (error) { - const err = error as NodeJS.ErrnoException; + try { + // 尝试删除文件 + fs.unlinkSync(record.filePath); + // 删除成功,从队列中移除任务 + this.tasks.delete(record.filePath); + } catch (error) { + const err = error as NodeJS.ErrnoException; - // 明确处理文件不存在的情况 - if (err.code === 'ENOENT') { - //console.log(`文件在删除时不存在,视为清理成功: ${record.filePath}`); - this.tasks.delete(record.filePath); - return; - } + // 明确处理文件不存在的情况 + if (err.code === 'ENOENT') { + // console.log(`文件在删除时不存在,视为清理成功: ${record.filePath}`); + this.tasks.delete(record.filePath); + return; + } - // 文件没有访问权限等情况 - if (err.code === 'EACCES' || err.code === 'EPERM') { - //console.error(`没有权限删除文件: ${record.filePath}`, err); - } + // 文件没有访问权限等情况 + if (err.code === 'EACCES' || err.code === 'EPERM') { + // console.error(`没有权限删除文件: ${record.filePath}`, err); + } - // 其他删除失败情况,考虑重试 - if (record.retries < this.MAX_RETRIES - 1) { - // 还有重试机会,增加重试次数 - record.retries++; - //console.log(`清理文件失败,将重试(${record.retries}/${this.MAX_RETRIES}): ${record.filePath}`); + // 其他删除失败情况,考虑重试 + if (record.retries < this.MAX_RETRIES - 1) { + // 还有重试机会,增加重试次数 + record.retries++; + // console.log(`清理文件失败,将重试(${record.retries}/${this.MAX_RETRIES}): ${record.filePath}`); - // 设置相同的延迟时间再次尝试 - const timer = setTimeout(() => { - this.cleanupFile(record, delay); - }, delay); + // 设置相同的延迟时间再次尝试 + const timer = setTimeout(() => { + this.cleanupFile(record, delay); + }, delay); - // 更新任务 - this.tasks.set(record.filePath, { fileRecord: record, timer }); - } else { - // 已达到最大重试次数,从队列中移除任务 - this.tasks.delete(record.filePath); - //console.error(`清理文件失败,已达最大重试次数(${this.MAX_RETRIES}): ${record.filePath}`, error); - } - } - }); - } + // 更新任务 + this.tasks.set(record.filePath, { fileRecord: record, timer }); + } else { + // 已达到最大重试次数,从队列中移除任务 + this.tasks.delete(record.filePath); + // console.error(`清理文件失败,已达最大重试次数(${this.MAX_RETRIES}): ${record.filePath}`, error); + } + } + }); + } - /** + /** * 取消文件的清理任务 * @param filePath 文件路径 * @returns 是否成功取消 */ - cancelCleanup(filePath: string): boolean { - let cancelled = false; - this.safeExecute(() => { - const task = this.tasks.get(filePath); - if (task) { - clearTimeout(task.timer); - this.tasks.delete(filePath); - cancelled = true; - } - }); - return cancelled; - } + cancelCleanup (filePath: string): boolean { + let cancelled = false; + this.safeExecute(() => { + const task = this.tasks.get(filePath); + if (task) { + clearTimeout(task.timer); + this.tasks.delete(filePath); + cancelled = true; + } + }); + return cancelled; + } - /** + /** * 获取队列中的文件数量 * @returns 文件数量 */ - getQueueSize(): number { - return this.tasks.size; - } + getQueueSize (): number { + return this.tasks.size; + } - /** + /** * 获取所有待清理的文件 * @returns 文件路径数组 */ - getPendingFiles(): string[] { - return Array.from(this.tasks.keys()); - } + getPendingFiles (): string[] { + return Array.from(this.tasks.keys()); + } - /** + /** * 清空所有清理任务 */ - clearAll(): void { - this.safeExecute(() => { - // 取消所有定时器 - for (const task of this.tasks.values()) { - clearTimeout(task.timer); - } - this.tasks.clear(); - //console.log('已清空所有清理任务'); - }); - } + clearAll (): void { + this.safeExecute(() => { + // 取消所有定时器 + for (const task of this.tasks.values()) { + clearTimeout(task.timer); + } + this.tasks.clear(); + // console.log('已清空所有清理任务'); + }); + } } -export const cleanTaskQueue = new CleanupQueue(); \ No newline at end of file +export const cleanTaskQueue = new CleanupQueue(); diff --git a/src/common/config-base.ts b/src/common/config-base.ts index 8f39a4b0..26f8da50 100644 --- a/src/common/config-base.ts +++ b/src/common/config-base.ts @@ -5,70 +5,70 @@ import json5 from 'json5'; import Ajv, { AnySchema, ValidateFunction } from 'ajv'; export abstract class ConfigBase { - name: string; - core: NapCatCore; - configPath: string; - configData: T = {} as T; - ajv: Ajv; - validate: ValidateFunction; + name: string; + core: NapCatCore; + configPath: string; + configData: T = {} as T; + ajv: Ajv; + validate: ValidateFunction; - protected constructor(name: string, core: NapCatCore, configPath: string, ConfigSchema: AnySchema) { - this.name = name; - this.core = core; - this.configPath = configPath; - this.ajv = new Ajv({ useDefaults: true, coerceTypes: true }); - this.validate = this.ajv.compile(ConfigSchema); - fs.mkdirSync(this.configPath, { recursive: true }); - this.read(); - } + protected constructor (name: string, core: NapCatCore, configPath: string, ConfigSchema: AnySchema) { + this.name = name; + this.core = core; + this.configPath = configPath; + this.ajv = new Ajv({ useDefaults: true, coerceTypes: true }); + this.validate = this.ajv.compile(ConfigSchema); + fs.mkdirSync(this.configPath, { recursive: true }); + this.read(); + } - getConfigPath(pathName?: string): string { - const filename = pathName ? `${this.name}_${pathName}.json` : `${this.name}.json`; - return path.join(this.configPath, filename); - } + getConfigPath (pathName?: string): string { + const filename = pathName ? `${this.name}_${pathName}.json` : `${this.name}.json`; + return path.join(this.configPath, filename); + } - read(): T { - const configPath = this.getConfigPath(this.core.selfInfo.uin); - const defaultConfigPath = this.getConfigPath(); - if (!fs.existsSync(configPath)) { - if (fs.existsSync(defaultConfigPath)) { - this.configData = this.loadConfig(defaultConfigPath); - } - this.save(); - return this.configData; - } - return this.loadConfig(configPath); + read (): T { + const configPath = this.getConfigPath(this.core.selfInfo.uin); + const defaultConfigPath = this.getConfigPath(); + if (!fs.existsSync(configPath)) { + if (fs.existsSync(defaultConfigPath)) { + this.configData = this.loadConfig(defaultConfigPath); + } + this.save(); + return this.configData; } + return this.loadConfig(configPath); + } - private loadConfig(configPath: string): T { - try { - let newConfigData = json5.parse(fs.readFileSync(configPath, 'utf-8')); - this.validate(newConfigData); - this.configData = newConfigData; - this.core.context.logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData); - return this.configData; - } catch (e: unknown) { - this.handleError(e, '读取配置文件时发生错误'); - return {} as T; - } + private loadConfig (configPath: string): T { + try { + const newConfigData = json5.parse(fs.readFileSync(configPath, 'utf-8')); + this.validate(newConfigData); + this.configData = newConfigData; + this.core.context.logger.logDebug(`[Core] [Config] 配置文件${configPath}加载`, this.configData); + return this.configData; + } catch (e: unknown) { + this.handleError(e, '读取配置文件时发生错误'); + return {} as T; } + } - save(newConfigData: T = this.configData): void { - const configPath = this.getConfigPath(this.core.selfInfo.uin); - this.validate(newConfigData); - this.configData = newConfigData; - try { - fs.writeFileSync(configPath, JSON.stringify(this.configData, null, 2)); - } catch (e: unknown) { - this.handleError(e, `保存配置文件 ${configPath} 时发生错误:`); - } + save (newConfigData: T = this.configData): void { + const configPath = this.getConfigPath(this.core.selfInfo.uin); + this.validate(newConfigData); + this.configData = newConfigData; + try { + fs.writeFileSync(configPath, JSON.stringify(this.configData, null, 2)); + } catch (e: unknown) { + this.handleError(e, `保存配置文件 ${configPath} 时发生错误:`); } + } - private handleError(e: unknown, message: string): void { - if (e instanceof SyntaxError) { - this.core.context.logger.logError('[Core] [Config] 操作配置文件格式错误,请检查配置文件:', e.message); - } else { - this.core.context.logger.logError(`[Core] [Config] ${message}:`, (e as Error).message); - } + private handleError (e: unknown, message: string): void { + if (e instanceof SyntaxError) { + this.core.context.logger.logError('[Core] [Config] 操作配置文件格式错误,请检查配置文件:', e.message); + } else { + this.core.context.logger.logError(`[Core] [Config] ${message}:`, (e as Error).message); } -} \ No newline at end of file + } +} diff --git a/src/common/download-ffmpeg.ts b/src/common/download-ffmpeg.ts index 734c5e85..f2bbbcb4 100644 --- a/src/common/download-ffmpeg.ts +++ b/src/common/download-ffmpeg.ts @@ -8,17 +8,17 @@ import { pipeline } from 'stream/promises'; import { fileURLToPath } from 'url'; import { LogWrapper } from './log'; -const downloadOri = "https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip" +const downloadOri = 'https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip'; const urls = [ - "https://j.1win.ggff.net/" + downloadOri, - "https://git.yylx.win/" + downloadOri, - "https://ghfile.geekertao.top/" + downloadOri, - "https://gh-proxy.net/" + downloadOri, - "https://ghm.078465.xyz/" + downloadOri, - "https://gitproxy.127731.xyz/" + downloadOri, - "https://jiashu.1win.eu.org/" + downloadOri, - "https://github.tbedu.top/" + downloadOri, - downloadOri + 'https://j.1win.ggff.net/' + downloadOri, + 'https://git.yylx.win/' + downloadOri, + 'https://ghfile.geekertao.top/' + downloadOri, + 'https://gh-proxy.net/' + downloadOri, + 'https://ghm.078465.xyz/' + downloadOri, + 'https://gitproxy.127731.xyz/' + downloadOri, + 'https://jiashu.1win.eu.org/' + downloadOri, + 'https://github.tbedu.top/' + downloadOri, + downloadOri, ]; /** @@ -26,49 +26,49 @@ const urls = [ * @param url 待测试的URL * @returns 如果URL可访问返回true,否则返回false */ -async function testUrl(url: string): Promise { - return new Promise((resolve) => { - const req = https.get(url, { timeout: 5000 }, (res) => { - // 检查状态码是否表示成功 - const statusCode = res.statusCode || 0; - if (statusCode >= 200 && statusCode < 300) { - // 终止请求并返回true - req.destroy(); - resolve(true); - } else { - req.destroy(); - resolve(false); - } - }); - - req.on('error', () => { - resolve(false); - }); - - req.on('timeout', () => { - req.destroy(); - resolve(false); - }); +async function testUrl (url: string): Promise { + return new Promise((resolve) => { + const req = https.get(url, { timeout: 5000 }, (res) => { + // 检查状态码是否表示成功 + const statusCode = res.statusCode || 0; + if (statusCode >= 200 && statusCode < 300) { + // 终止请求并返回true + req.destroy(); + resolve(true); + } else { + req.destroy(); + resolve(false); + } }); + + req.on('error', () => { + resolve(false); + }); + + req.on('timeout', () => { + req.destroy(); + resolve(false); + }); + }); } /** * 查找第一个可用的URL * @returns 返回第一个可用的URL,如果都不可用则返回null */ -async function findAvailableUrl(): Promise { - for (const url of urls) { - try { - const available = await testUrl(url); - if (available) { - return url; - } - } catch (error) { - // 忽略错误 - } +async function findAvailableUrl (): Promise { + for (const url of urls) { + try { + const available = await testUrl(url); + if (available) { + return url; + } + } catch (error) { + // 忽略错误 } + } - return null; + return null; } /** * 下载文件 @@ -76,68 +76,67 @@ async function findAvailableUrl(): Promise { * @param destPath 目标保存路径 * @returns 成功返回true,失败返回false */ -async function downloadFile(url: string, destPath: string, progressCallback?: (percent: number) => void): Promise { - return new Promise((resolve) => { - const file = fs.createWriteStream(destPath); +async function downloadFile (url: string, destPath: string, progressCallback?: (percent: number) => void): Promise { + return new Promise((resolve) => { + const file = fs.createWriteStream(destPath); - const req = https.get(url, (res) => { - const statusCode = res.statusCode || 0; + const req = https.get(url, (res) => { + const statusCode = res.statusCode || 0; - if (statusCode >= 200 && statusCode < 300) { - // 获取文件总大小 - const totalSize = parseInt(res.headers['content-length'] || '0', 10); - let downloadedSize = 0; - let lastReportedPercent = -1; // 上次报告的百分比 - let lastReportTime = 0; // 上次报告的时间戳 + if (statusCode >= 200 && statusCode < 300) { + // 获取文件总大小 + const totalSize = parseInt(res.headers['content-length'] || '0', 10); + let downloadedSize = 0; + let lastReportedPercent = -1; // 上次报告的百分比 + let lastReportTime = 0; // 上次报告的时间戳 - // 如果有内容长度和进度回调,则添加数据监听 - if (totalSize > 0 && progressCallback) { - // 初始报告 0% - progressCallback(0); - lastReportTime = Date.now(); + // 如果有内容长度和进度回调,则添加数据监听 + if (totalSize > 0 && progressCallback) { + // 初始报告 0% + progressCallback(0); + lastReportTime = Date.now(); - res.on('data', (chunk) => { - downloadedSize += chunk.length; - const currentPercent = Math.floor((downloadedSize / totalSize) * 100); - const now = Date.now(); + res.on('data', (chunk) => { + downloadedSize += chunk.length; + const currentPercent = Math.floor((downloadedSize / totalSize) * 100); + const now = Date.now(); - // 只在以下条件触发回调: - // 1. 百分比变化至少为1% - // 2. 距离上次报告至少500毫秒 - // 3. 确保报告100%完成 - if ((currentPercent !== lastReportedPercent && + // 只在以下条件触发回调: + // 1. 百分比变化至少为1% + // 2. 距离上次报告至少500毫秒 + // 3. 确保报告100%完成 + if ((currentPercent !== lastReportedPercent && (currentPercent - lastReportedPercent >= 1 || currentPercent === 100)) && (now - lastReportTime >= 1000 || currentPercent === 100)) { - - progressCallback(currentPercent); - lastReportedPercent = currentPercent; - lastReportTime = now; - } - }); - } - - pipeline(res, file) - .then(() => { - // 确保最后报告100% - if (progressCallback && lastReportedPercent !== 100) { - progressCallback(100); - } - resolve(true); - }) - .catch(() => resolve(false)); - } else { - file.close(); - fs.unlink(destPath, () => { }); - resolve(false); + progressCallback(currentPercent); + lastReportedPercent = currentPercent; + lastReportTime = now; } - }); + }); + } - req.on('error', () => { - file.close(); - fs.unlink(destPath, () => { }); - resolve(false); - }); + pipeline(res, file) + .then(() => { + // 确保最后报告100% + if (progressCallback && lastReportedPercent !== 100) { + progressCallback(100); + } + resolve(true); + }) + .catch(() => resolve(false)); + } else { + file.close(); + fs.unlink(destPath, () => { }); + resolve(false); + } }); + + req.on('error', () => { + file.close(); + fs.unlink(destPath, () => { }); + resolve(false); + }); + }); } /** @@ -146,61 +145,61 @@ async function downloadFile(url: string, destPath: string, progressCallback?: (p * @param zipPath 压缩文件路径 * @param extractDir 解压目标路径 */ -async function extractBinDirectory(zipPath: string, extractDir: string): Promise { - try { - // 确保目标目录存在 - if (!fs.existsSync(extractDir)) { - fs.mkdirSync(extractDir, { recursive: true }); - } - - // 解压文件 - const zipStream = new compressing.zip.UncompressStream({ source: zipPath }); - - return new Promise((resolve, reject) => { - // 监听条目事件 - zipStream.on('entry', (header, stream, next) => { - // 获取文件路径 - const filePath = header.name; - - // 匹配内层bin目录中的文件 - // 例如:ffmpeg-n7.1.1-6-g48c0f071d4-win64-lgpl-7.1/bin/ffmpeg.exe - if (filePath.includes('/bin/') && filePath.endsWith('.exe')) { - // 提取文件名 - const fileName = path.basename(filePath); - const targetPath = path.join(extractDir, fileName); - - // 创建写入流 - const writeStream = fs.createWriteStream(targetPath); - - // 将流管道连接到文件 - stream.pipe(writeStream); - - // 监听写入完成事件 - writeStream.on('finish', () => { - next(); - }); - - writeStream.on('error', () => { - next(); - }); - } else { - // 跳过不需要的文件 - stream.resume(); - next(); - } - }); - - zipStream.on('error', (err) => { - reject(err); - }); - - zipStream.on('finish', () => { - resolve(); - }); - }); - } catch (err) { - throw err; +async function extractBinDirectory (zipPath: string, extractDir: string): Promise { + try { + // 确保目标目录存在 + if (!fs.existsSync(extractDir)) { + fs.mkdirSync(extractDir, { recursive: true }); } + + // 解压文件 + const zipStream = new compressing.zip.UncompressStream({ source: zipPath }); + + return new Promise((resolve, reject) => { + // 监听条目事件 + zipStream.on('entry', (header, stream, next) => { + // 获取文件路径 + const filePath = header.name; + + // 匹配内层bin目录中的文件 + // 例如:ffmpeg-n7.1.1-6-g48c0f071d4-win64-lgpl-7.1/bin/ffmpeg.exe + if (filePath.includes('/bin/') && filePath.endsWith('.exe')) { + // 提取文件名 + const fileName = path.basename(filePath); + const targetPath = path.join(extractDir, fileName); + + // 创建写入流 + const writeStream = fs.createWriteStream(targetPath); + + // 将流管道连接到文件 + stream.pipe(writeStream); + + // 监听写入完成事件 + writeStream.on('finish', () => { + next(); + }); + + writeStream.on('error', () => { + next(); + }); + } else { + // 跳过不需要的文件 + stream.resume(); + next(); + } + }); + + zipStream.on('error', (err) => { + reject(err); + }); + + zipStream.on('finish', () => { + resolve(); + }); + }); + } catch (err) { + throw err; + } } /** @@ -209,82 +208,82 @@ async function extractBinDirectory(zipPath: string, extractDir: string): Promise * @param tempDir 临时文件目录,默认为系统临时目录 * @returns 返回ffmpeg可执行文件的路径,如果失败则返回null */ -export async function downloadFFmpeg( - destDir?: string, - tempDir?: string, - progressCallback?: (percent: number, stage: string) => void +export async function downloadFFmpeg ( + destDir?: string, + tempDir?: string, + progressCallback?: (percent: number, stage: string) => void ): Promise { - // 仅限Windows - if (os.platform() !== 'win32') { - return null; + // 仅限Windows + if (os.platform() !== 'win32') { + return null; + } + + const destinationDir = destDir || path.join(os.tmpdir(), 'ffmpeg'); + const tempDirectory = tempDir || os.tmpdir(); + const zipFilePath = path.join(tempDirectory, 'ffmpeg.zip'); // 临时下载到指定临时目录 + const ffmpegExePath = path.join(destinationDir, 'ffmpeg.exe'); + + // 确保目录存在 + if (!fs.existsSync(destinationDir)) { + fs.mkdirSync(destinationDir, { recursive: true }); + } + + // 确保临时目录存在 + if (!fs.existsSync(tempDirectory)) { + fs.mkdirSync(tempDirectory, { recursive: true }); + } + + // 如果ffmpeg已经存在,直接返回路径 + if (fs.existsSync(ffmpegExePath)) { + if (progressCallback) progressCallback(100, '已找到FFmpeg'); + return ffmpegExePath; + } + + // 查找可用URL + if (progressCallback) progressCallback(0, '查找可用下载源'); + const availableUrl = await findAvailableUrl(); + if (!availableUrl) { + return null; + } + + // 下载文件 + if (progressCallback) progressCallback(5, '开始下载FFmpeg'); + const downloaded = await downloadFile( + availableUrl, + zipFilePath, + (percent) => { + // 下载占总进度的70% + if (progressCallback) progressCallback(5 + Math.floor(percent * 0.7), '下载FFmpeg'); } + ); - const destinationDir = destDir || path.join(os.tmpdir(), 'ffmpeg'); - const tempDirectory = tempDir || os.tmpdir(); - const zipFilePath = path.join(tempDirectory, 'ffmpeg.zip'); // 临时下载到指定临时目录 - const ffmpegExePath = path.join(destinationDir, 'ffmpeg.exe'); + if (!downloaded) { + return null; + } - // 确保目录存在 - if (!fs.existsSync(destinationDir)) { - fs.mkdirSync(destinationDir, { recursive: true }); - } - - // 确保临时目录存在 - if (!fs.existsSync(tempDirectory)) { - fs.mkdirSync(tempDirectory, { recursive: true }); - } - - // 如果ffmpeg已经存在,直接返回路径 - if (fs.existsSync(ffmpegExePath)) { - if (progressCallback) progressCallback(100, '已找到FFmpeg'); - return ffmpegExePath; - } - - // 查找可用URL - if (progressCallback) progressCallback(0, '查找可用下载源'); - const availableUrl = await findAvailableUrl(); - if (!availableUrl) { - return null; - } - - // 下载文件 - if (progressCallback) progressCallback(5, '开始下载FFmpeg'); - const downloaded = await downloadFile( - availableUrl, - zipFilePath, - (percent) => { - // 下载占总进度的70% - if (progressCallback) progressCallback(5 + Math.floor(percent * 0.7), '下载FFmpeg'); - } - ); - - if (!downloaded) { - return null; - } + try { + // 直接解压bin目录文件到目标目录 + if (progressCallback) progressCallback(75, '解压FFmpeg'); + await extractBinDirectory(zipFilePath, destinationDir); + // 清理下载文件 + if (progressCallback) progressCallback(95, '清理临时文件'); try { - // 直接解压bin目录文件到目标目录 - if (progressCallback) progressCallback(75, '解压FFmpeg'); - await extractBinDirectory(zipFilePath, destinationDir); - - // 清理下载文件 - if (progressCallback) progressCallback(95, '清理临时文件'); - try { - fs.unlinkSync(zipFilePath); - } catch (err) { - // 忽略清理临时文件失败的错误 - } - - // 检查ffmpeg.exe是否成功解压 - if (fs.existsSync(ffmpegExePath)) { - if (progressCallback) progressCallback(100, 'FFmpeg安装完成'); - return ffmpegExePath; - } else { - return null; - } + fs.unlinkSync(zipFilePath); } catch (err) { - return null; + // 忽略清理临时文件失败的错误 } + + // 检查ffmpeg.exe是否成功解压 + if (fs.existsSync(ffmpegExePath)) { + if (progressCallback) progressCallback(100, 'FFmpeg安装完成'); + return ffmpegExePath; + } else { + return null; + } + } catch (err) { + return null; + } } /** @@ -292,73 +291,73 @@ export async function downloadFFmpeg( * @param executable 可执行文件名 * @returns 如果找到返回完整路径,否则返回null */ -function findExecutableInPath(executable: string): string | null { - // 仅适用于Windows系统 - if (os.platform() !== 'win32') return null; +function findExecutableInPath (executable: string): string | null { + // 仅适用于Windows系统 + if (os.platform() !== 'win32') return null; - // 获取PATH环境变量 - const pathEnv = process.env['PATH'] || ''; - const pathDirs = pathEnv.split(';'); + // 获取PATH环境变量 + const pathEnv = process.env['PATH'] || ''; + const pathDirs = pathEnv.split(';'); - // 检查每个目录 - for (const dir of pathDirs) { - if (!dir) continue; - try { - const filePath = path.join(dir, executable); - if (fs.existsSync(filePath)) { - return filePath; - } - } catch (error) { - continue; - } + // 检查每个目录 + for (const dir of pathDirs) { + if (!dir) continue; + try { + const filePath = path.join(dir, executable); + if (fs.existsSync(filePath)) { + return filePath; + } + } catch (error) { + continue; } + } - return null; + return null; } -export async function downloadFFmpegIfNotExists(log: LogWrapper) { - // 仅限Windows - if (os.platform() !== 'win32') { - return { - path: null, - reset: false - }; - } - const ffmpegInPath = findExecutableInPath('ffmpeg.exe'); - const ffprobeInPath = findExecutableInPath('ffprobe.exe'); - - if (ffmpegInPath && ffprobeInPath) { - const ffmpegDir = path.dirname(ffmpegInPath); - return { - path: ffmpegDir, - reset: true - }; - } - - // 如果环境变量中没有,检查项目目录中是否存在 - const currentPath = path.dirname(fileURLToPath(import.meta.url)); - const ffmpeg_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffmpeg.exe')); - const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe')); - - if (!ffmpeg_exist || !ffprobe_exist) { - let url = await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => { - log.log(`[FFmpeg] [Download] ${percentage}% - ${message}`); - }); - if (!url) { - log.log('[FFmpeg] [Error] 下载FFmpeg失败'); - return { - path: null, - reset: false - }; - } - return { - path: path.join(currentPath, 'ffmpeg'), - reset: true - } - } - +export async function downloadFFmpegIfNotExists (log: LogWrapper) { + // 仅限Windows + if (os.platform() !== 'win32') { return { - path: path.join(currentPath, 'ffmpeg'), - reset: true + path: null, + reset: false, + }; + } + const ffmpegInPath = findExecutableInPath('ffmpeg.exe'); + const ffprobeInPath = findExecutableInPath('ffprobe.exe'); + + if (ffmpegInPath && ffprobeInPath) { + const ffmpegDir = path.dirname(ffmpegInPath); + return { + path: ffmpegDir, + reset: true, + }; + } + + // 如果环境变量中没有,检查项目目录中是否存在 + const currentPath = path.dirname(fileURLToPath(import.meta.url)); + const ffmpeg_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffmpeg.exe')); + const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe')); + + if (!ffmpeg_exist || !ffprobe_exist) { + const url = await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => { + log.log(`[FFmpeg] [Download] ${percentage}% - ${message}`); + }); + if (!url) { + log.log('[FFmpeg] [Error] 下载FFmpeg失败'); + return { + path: null, + reset: false, + }; } -} \ No newline at end of file + return { + path: path.join(currentPath, 'ffmpeg'), + reset: true, + }; + } + + return { + path: path.join(currentPath, 'ffmpeg'), + reset: true, + }; +} diff --git a/src/common/event.ts b/src/common/event.ts index ca0e2405..69b6f7c2 100644 --- a/src/common/event.ts +++ b/src/common/event.ts @@ -1,20 +1,19 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { NodeIQQNTWrapperSession } from '@/core/wrapper'; import { randomUUID } from 'crypto'; import { ListenerNamingMapping, ServiceNamingMapping } from '@/core'; interface InternalMapKey { - timeout: number; - createtime: number; - func: (...arg: any[]) => any; - checker: ((...args: any[]) => boolean) | undefined; + timeout: number; + createtime: number; + func: (...arg: any[]) => any; + checker: ((...args: any[]) => boolean) | undefined; } type EnsureFunc = T extends (...args: any) => any ? T : never; type FuncKeys = Extract< { - [K in keyof T]: EnsureFunc extends never ? never : K; + [K in keyof T]: EnsureFunc extends never ? never : K; }[keyof T], string >; @@ -22,156 +21,156 @@ type FuncKeys = Extract< export type ListenerClassBase = Record; export class NTEventWrapper { - private readonly WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession - private readonly listenerManager: Map = new Map(); //ListenerName-Unique -> Listener实例 - private readonly EventTask = new Map>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} + private readonly WrapperSession: NodeIQQNTWrapperSession | undefined; // WrapperSession + private readonly listenerManager: Map = new Map(); // ListenerName-Unique -> Listener实例 + private readonly EventTask = new Map>>(); // tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} - constructor( - wrapperSession: NodeIQQNTWrapperSession, - ) { - this.WrapperSession = wrapperSession; - } + constructor ( + wrapperSession: NodeIQQNTWrapperSession + ) { + this.WrapperSession = wrapperSession; + } - createProxyDispatch(ListenerMainName: string) { - const dispatcherListenerFunc = this.dispatcherListener.bind(this); - return new Proxy( - {}, - { - get(target: any, prop: any, receiver: any) { - if (typeof target[prop] === 'undefined') { - // 如果方法不存在,返回一个函数,这个函数调用existentMethod - return (...args: any[]) => { - dispatcherListenerFunc(ListenerMainName, prop, ...args).then(); - }; - } - // 如果方法存在,正常返回 - return Reflect.get(target, prop, receiver); - }, - }, - ); - } + createProxyDispatch (ListenerMainName: string) { + const dispatcherListenerFunc = this.dispatcherListener.bind(this); + return new Proxy( + {}, + { + get (target: any, prop: any, receiver: any) { + if (typeof target[prop] === 'undefined') { + // 如果方法不存在,返回一个函数,这个函数调用existentMethod + return (...args: any[]) => { + dispatcherListenerFunc(ListenerMainName, prop, ...args).then(); + }; + } + // 如果方法存在,正常返回 + return Reflect.get(target, prop, receiver); + }, + } + ); + } - createEventFunction< + createEventFunction< Service extends keyof ServiceNamingMapping, ServiceMethod extends FuncKeys, - T extends (...args: any) => any = EnsureFunc, + T extends (...args: any) => any = EnsureFunc >(eventName: `${Service}/${ServiceMethod}`): T | undefined { - const eventNameArr = eventName.split('/'); + const eventNameArr = eventName.split('/'); type eventType = { - [key: string]: () => { [key: string]: (...params: Parameters) => Promise> }; + [key: string]: () => { [key: string]: (...params: Parameters) => Promise> }; }; if (eventNameArr.length > 1) { - const serviceName = 'get' + (eventNameArr[0]?.replace('NodeIKernel', '') ?? ''); - const eventName = eventNameArr[1]; - const services = (this.WrapperSession as unknown as eventType)[serviceName]?.(); - if (!services || !eventName) { - return undefined; - } - let event = services[eventName]; - - //重新绑定this - event = event?.bind(services); - if (event) { - return event as T; - } + const serviceName = 'get' + (eventNameArr[0]?.replace('NodeIKernel', '') ?? ''); + const eventName = eventNameArr[1]; + const services = (this.WrapperSession as unknown as eventType)[serviceName]?.(); + if (!services || !eventName) { return undefined; + } + let event = services[eventName]; + + // 重新绑定this + event = event?.bind(services); + if (event) { + return event as T; + } + return undefined; } return undefined; - } + } - createListenerFunction(listenerMainName: string, uniqueCode: string = ''): T { - const existListener = this.listenerManager.get(listenerMainName + uniqueCode); - if (!existListener) { - const Listener = this.createProxyDispatch(listenerMainName); - const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1]; - const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener`; - // eslint-disable-next-line + createListenerFunction(listenerMainName: string, uniqueCode: string = ''): T { + const existListener = this.listenerManager.get(listenerMainName + uniqueCode); + if (!existListener) { + const Listener = this.createProxyDispatch(listenerMainName); + const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1]; + const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener`; + // eslint-disable-next-line // @ts-ignore - this.createEventFunction(Service)(Listener as T); - this.listenerManager.set(listenerMainName + uniqueCode, Listener); - return Listener as T; + this.createEventFunction(Service)(Listener as T); + this.listenerManager.set(listenerMainName + uniqueCode, Listener); + return Listener as T; + } + return existListener as T; + } + + // 统一回调清理事件 + async dispatcherListener (ListenerMainName: string, ListenerSubName: string, ...args: any[]) { + this.EventTask.get(ListenerMainName) + ?.get(ListenerSubName) + ?.forEach((task, uuid) => { + if (task.createtime + task.timeout < Date.now()) { + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid); + return; } - return existListener as T; - } + if (task?.checker?.(...args)) { + task.func(...args); + } + }); + } - //统一回调清理事件 - async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) { - this.EventTask.get(ListenerMainName) - ?.get(ListenerSubName) - ?.forEach((task, uuid) => { - if (task.createtime + task.timeout < Date.now()) { - this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid); - return; - } - if (task?.checker?.(...args)) { - task.func(...args); - } - }); - } - - async callNoListenerEvent< + async callNoListenerEvent< Service extends keyof ServiceNamingMapping, ServiceMethod extends FuncKeys, - EventType extends (...args: any) => any = EnsureFunc, + EventType extends (...args: any) => any = EnsureFunc >( - serviceAndMethod: `${Service}/${ServiceMethod}`, - ...args: Parameters - ): Promise>> { - return (this.createEventFunction(serviceAndMethod))!(...args); - } + serviceAndMethod: `${Service}/${ServiceMethod}`, + ...args: Parameters + ): Promise>> { + return (this.createEventFunction(serviceAndMethod))!(...args); + } - async registerListen< + async registerListen< Listener extends keyof ListenerNamingMapping, ListenerMethod extends FuncKeys, - ListenerType extends (...args: any) => any = EnsureFunc, + ListenerType extends (...args: any) => any = EnsureFunc >( - listenerAndMethod: `${Listener}/${ListenerMethod}`, - checker: (...args: Parameters) => boolean, - waitTimes = 1, - timeout = 5000, - ) { - return new Promise>((resolve, reject) => { - const ListenerNameList = listenerAndMethod.split('/'); - const ListenerMainName = ListenerNameList[0] ?? ''; - const ListenerSubName = ListenerNameList[1] ?? ''; - const id = randomUUID(); - let complete = 0; - let retData: Parameters | undefined = undefined; + listenerAndMethod: `${Listener}/${ListenerMethod}`, + checker: (...args: Parameters) => boolean, + waitTimes = 1, + timeout = 5000 + ) { + return new Promise>((resolve, reject) => { + const ListenerNameList = listenerAndMethod.split('/'); + const ListenerMainName = ListenerNameList[0] ?? ''; + const ListenerSubName = ListenerNameList[1] ?? ''; + const id = randomUUID(); + let complete = 0; + let retData: Parameters | undefined; - function sendDataCallback() { - if (complete == 0) { - reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout')); - } else { - resolve(retData!); - } - } + function sendDataCallback () { + if (complete == 0) { + reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout')); + } else { + resolve(retData!); + } + } - const timeoutRef = setTimeout(sendDataCallback, timeout); - const eventCallback = { - timeout: timeout, - createtime: Date.now(), - checker: checker, - func: (...args: Parameters) => { - complete++; - retData = args; - if (complete >= waitTimes) { - clearTimeout(timeoutRef); - sendDataCallback(); - } - }, - }; - if (!this.EventTask.get(ListenerMainName)) { - this.EventTask.set(ListenerMainName, new Map()); - } - if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) { - this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()); - } - this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); - this.createListenerFunction(ListenerMainName); - }); - } + const timeoutRef = setTimeout(sendDataCallback, timeout); + const eventCallback = { + timeout, + createtime: Date.now(), + checker, + func: (...args: Parameters) => { + complete++; + retData = args; + if (complete >= waitTimes) { + clearTimeout(timeoutRef); + sendDataCallback(); + } + }, + }; + if (!this.EventTask.get(ListenerMainName)) { + this.EventTask.set(ListenerMainName, new Map()); + } + if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) { + this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()); + } + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); + this.createListenerFunction(ListenerMainName); + }); + } - async callNormalEventV2< + async callNormalEventV2< Service extends keyof ServiceNamingMapping, ServiceMethod extends FuncKeys, Listener extends keyof ListenerNamingMapping, @@ -179,95 +178,95 @@ export class NTEventWrapper { EventType extends (...args: any) => any = EnsureFunc, ListenerType extends (...args: any) => any = EnsureFunc >( - serviceAndMethod: `${Service}/${ServiceMethod}`, - listenerAndMethod: `${Listener}/${ListenerMethod}`, - args: Parameters, - checkerEvent: (ret: Awaited>) => boolean = () => true, - checkerListener: (...args: Parameters) => boolean = () => true, - callbackTimesToWait = 1, - timeout = 5000, - ) { - const id = randomUUID(); - let complete = 0; - let retData: Parameters | undefined = undefined; - let retEvent: any = {}; + serviceAndMethod: `${Service}/${ServiceMethod}`, + listenerAndMethod: `${Listener}/${ListenerMethod}`, + args: Parameters, + checkerEvent: (ret: Awaited>) => boolean = () => true, + checkerListener: (...args: Parameters) => boolean = () => true, + callbackTimesToWait = 1, + timeout = 5000 + ) { + const id = randomUUID(); + let complete = 0; + let retData: Parameters | undefined; + let retEvent: any = {}; - function sendDataCallback(resolve: any, reject: any) { - if (complete == 0) { - reject( - new Error( - 'Timeout: NTEvent serviceAndMethod:' + + function sendDataCallback (resolve: any, reject: any) { + if (complete == 0) { + reject( + new Error( + 'Timeout: NTEvent serviceAndMethod:' + serviceAndMethod + ' ListenerName:' + listenerAndMethod + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + - '\n', - ), - ); - } else { - resolve([retEvent as Awaited>, ...retData!]); + '\n' + ) + ); + } else { + resolve([retEvent as Awaited>, ...retData!]); + } + } + + const ListenerNameList = listenerAndMethod.split('/'); + const ListenerMainName = ListenerNameList[0] ?? ''; + const ListenerSubName = ListenerNameList[1] ?? ''; + + return new Promise<[EventRet: Awaited>, ...Parameters]>( + (resolve, reject) => { + const timeoutRef = setTimeout(() => sendDataCallback(resolve, reject), timeout); + + const eventCallback = { + timeout, + createtime: Date.now(), + checker: checkerListener, + func: (...args: any[]) => { + complete++; + retData = args as Parameters; + if (complete >= callbackTimesToWait) { + clearTimeout(timeoutRef); + sendDataCallback(resolve, reject); } + }, + }; + if (!this.EventTask.get(ListenerMainName)) { + this.EventTask.set(ListenerMainName, new Map()); } + if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) { + this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()); + } + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); + this.createListenerFunction(ListenerMainName); - const ListenerNameList = listenerAndMethod.split('/'); - const ListenerMainName = ListenerNameList[0]??''; - const ListenerSubName = ListenerNameList[1]??''; + const eventResult = this.createEventFunction(serviceAndMethod)!(...(args)); - return new Promise<[EventRet: Awaited>, ...Parameters]>( - (resolve, reject) => { - const timeoutRef = setTimeout(() => sendDataCallback(resolve, reject), timeout); - - const eventCallback = { - timeout: timeout, - createtime: Date.now(), - checker: checkerListener, - func: (...args: any[]) => { - complete++; - retData = args as Parameters; - if (complete >= callbackTimesToWait) { - clearTimeout(timeoutRef); - sendDataCallback(resolve, reject); - } - }, - }; - if (!this.EventTask.get(ListenerMainName)) { - this.EventTask.set(ListenerMainName, new Map()); - } - if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) { - this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()); - } - this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); - this.createListenerFunction(ListenerMainName); - - const eventResult = this.createEventFunction(serviceAndMethod)!(...(args)); - - const eventRetHandle = (eventData: any) => { - retEvent = eventData; - if (!checkerEvent(retEvent) && timeoutRef.hasRef()) { - clearTimeout(timeoutRef); - reject( - new Error( - 'EventChecker Failed: NTEvent serviceAndMethod:' + + const eventRetHandle = (eventData: any) => { + retEvent = eventData; + if (!checkerEvent(retEvent) && timeoutRef.hasRef()) { + clearTimeout(timeoutRef); + reject( + new Error( + 'EventChecker Failed: NTEvent serviceAndMethod:' + serviceAndMethod + ' ListenerName:' + listenerAndMethod + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + - '\n', - ), - ); - } - }; - if (eventResult instanceof Promise) { - eventResult.then((eventResult: any) => { - eventRetHandle(eventResult); - }) - .catch(reject); - } else { - eventRetHandle(eventResult); - } - }, - ); - } + '\n' + ) + ); + } + }; + if (eventResult instanceof Promise) { + eventResult.then((eventResult: any) => { + eventRetHandle(eventResult); + }) + .catch(reject); + } else { + eventRetHandle(eventResult); + } + } + ); + } } diff --git a/src/common/fall-back.ts b/src/common/fall-back.ts index f760705f..c3bd68d5 100644 --- a/src/common/fall-back.ts +++ b/src/common/fall-back.ts @@ -2,41 +2,41 @@ type Handler = () => T | Promise; type Checker = (result: T) => T | Promise; export class Fallback { - private handlers: Handler[] = []; - private checker: Checker; + private handlers: Handler[] = []; + private checker: Checker; - constructor(checker?: Checker) { - this.checker = checker || (async (result: T) => result); - } + constructor (checker?: Checker) { + this.checker = checker || (async (result: T) => result); + } - add(handler: Handler): this { - this.handlers.push(handler); - return this; - } + add (handler: Handler): this { + this.handlers.push(handler); + return this; + } - // 执行处理程序链 - async run(): Promise { - const errors: Error[] = []; - for (const handler of this.handlers) { - try { - const result = await handler(); - const data = await this.checker(result); - if (data) { - return data; - } - } catch (error) { - errors.push(error instanceof Error ? error : new Error(String(error))); - } + // 执行处理程序链 + async run (): Promise { + const errors: Error[] = []; + for (const handler of this.handlers) { + try { + const result = await handler(); + const data = await this.checker(result); + if (data) { + return data; } - throw new AggregateError(errors, 'All handlers failed'); + } catch (error) { + errors.push(error instanceof Error ? error : new Error(String(error))); + } } + throw new AggregateError(errors, 'All handlers failed'); + } } export class FallbackUtil { - static boolchecker(value: T, condition: boolean): T { - if (condition) { - return value; - } else { - throw new Error('Condition is false, throwing error'); - } + static boolchecker(value: T, condition: boolean): T { + if (condition) { + return value; + } else { + throw new Error('Condition is false, throwing error'); } -} \ No newline at end of file + } +} diff --git a/src/common/ffmpeg-adapter-factory.ts b/src/common/ffmpeg-adapter-factory.ts index 53e1b9d7..bf33a422 100644 --- a/src/common/ffmpeg-adapter-factory.ts +++ b/src/common/ffmpeg-adapter-factory.ts @@ -12,120 +12,119 @@ import type { IFFmpegAdapter } from './ffmpeg-adapter-interface'; * FFmpeg 适配器工厂 */ export class FFmpegAdapterFactory { - private static instance: IFFmpegAdapter | null = null; - private static initPromise: Promise | null = null; + private static instance: IFFmpegAdapter | null = null; + private static initPromise: Promise | null = null; - /** + /** * 初始化并获取最佳的 FFmpeg 适配器 * @param logger 日志记录器 * @param ffmpegPath FFmpeg 可执行文件路径(用于 Exec 适配器) * @param ffprobePath FFprobe 可执行文件路径(用于 Exec 适配器) * @param binaryPath 二进制文件路径(来自 pathWrapper.binaryPath,用于 Addon 适配器) */ - static async getAdapter( - logger: LogWrapper, - ffmpegPath: string = 'ffmpeg', - ffprobePath: string = 'ffprobe', - binaryPath?: string - ): Promise { - // 如果已经初始化,直接返回 - if (this.instance) { - return this.instance; - } - - // 如果正在初始化,等待初始化完成 - if (this.initPromise) { - return this.initPromise; - } - - // 开始初始化 - this.initPromise = this.initialize(logger, ffmpegPath, ffprobePath, binaryPath); - - try { - this.instance = await this.initPromise; - return this.instance; - } finally { - this.initPromise = null; - } + static async getAdapter ( + logger: LogWrapper, + ffmpegPath: string = 'ffmpeg', + ffprobePath: string = 'ffprobe', + binaryPath?: string + ): Promise { + // 如果已经初始化,直接返回 + if (this.instance) { + return this.instance; } - /** + // 如果正在初始化,等待初始化完成 + if (this.initPromise) { + return this.initPromise; + } + + // 开始初始化 + this.initPromise = this.initialize(logger, ffmpegPath, ffprobePath, binaryPath); + + try { + this.instance = await this.initPromise; + return this.instance; + } finally { + this.initPromise = null; + } + } + + /** * 初始化适配器 */ - private static async initialize( - logger: LogWrapper, - ffmpegPath: string, - ffprobePath: string, - binaryPath?: string - ): Promise { + private static async initialize ( + logger: LogWrapper, + ffmpegPath: string, + ffprobePath: string, + binaryPath?: string + ): Promise { + // 1. 优先尝试使用 Native Addon + if (binaryPath) { + const addonAdapter = new FFmpegAddonAdapter(binaryPath); - // 1. 优先尝试使用 Native Addon - if (binaryPath) { - const addonAdapter = new FFmpegAddonAdapter(binaryPath); + logger.log('[FFmpeg] 检查 Native Addon 可用性...'); + if (await addonAdapter.isAvailable()) { + logger.log('[FFmpeg] ✓ 使用 Native Addon 适配器'); + return addonAdapter; + } - logger.log('[FFmpeg] 检查 Native Addon 可用性...'); - if (await addonAdapter.isAvailable()) { - logger.log('[FFmpeg] ✓ 使用 Native Addon 适配器'); - return addonAdapter; - } - - logger.log('[FFmpeg] Native Addon 不可用,尝试使用命令行工具'); - } else { - logger.log('[FFmpeg] 未提供 binaryPath,跳过 Native Addon 检测'); - } - - // 2. 降级到 execFile 实现 - const execAdapter = new FFmpegExecAdapter(ffmpegPath, ffprobePath, binaryPath, logger); - - logger.log(`[FFmpeg] 检查命令行工具可用性: ${ffmpegPath}`); - if (await execAdapter.isAvailable()) { - logger.log('[FFmpeg] 使用命令行工具适配器 ✓'); - return execAdapter; - } - - // 3. 都不可用,返回 execAdapter 但会在使用时报错 - logger.logError('[FFmpeg] 警告: FFmpeg 不可用,将使用命令行适配器但可能失败'); - return execAdapter; + logger.log('[FFmpeg] Native Addon 不可用,尝试使用命令行工具'); + } else { + logger.log('[FFmpeg] 未提供 binaryPath,跳过 Native Addon 检测'); } - /** + // 2. 降级到 execFile 实现 + const execAdapter = new FFmpegExecAdapter(ffmpegPath, ffprobePath, binaryPath, logger); + + logger.log(`[FFmpeg] 检查命令行工具可用性: ${ffmpegPath}`); + if (await execAdapter.isAvailable()) { + logger.log('[FFmpeg] 使用命令行工具适配器 ✓'); + return execAdapter; + } + + // 3. 都不可用,返回 execAdapter 但会在使用时报错 + logger.logError('[FFmpeg] 警告: FFmpeg 不可用,将使用命令行适配器但可能失败'); + return execAdapter; + } + + /** * 重置适配器(用于测试或重新初始化) */ - static reset(): void { - this.instance = null; - this.initPromise = null; - } + static reset (): void { + this.instance = null; + this.initPromise = null; + } - /** + /** * 更新 FFmpeg 路径并重新初始化 * @param logger 日志记录器 * @param ffmpegPath FFmpeg 可执行文件路径 * @param ffprobePath FFprobe 可执行文件路径 */ - static async updateFFmpegPath( - logger: LogWrapper, - ffmpegPath: string, - ffprobePath: string - ): Promise { - // 如果当前使用的是 Exec 适配器,更新路径 - if (this.instance && this.instance instanceof FFmpegExecAdapter) { - logger.log(`[FFmpeg] 更新 FFmpeg 路径: ${ffmpegPath}`); - this.instance.setFFmpegPath(ffmpegPath); - this.instance.setFFprobePath(ffprobePath); + static async updateFFmpegPath ( + logger: LogWrapper, + ffmpegPath: string, + ffprobePath: string + ): Promise { + // 如果当前使用的是 Exec 适配器,更新路径 + if (this.instance && this.instance instanceof FFmpegExecAdapter) { + logger.log(`[FFmpeg] 更新 FFmpeg 路径: ${ffmpegPath}`); + this.instance.setFFmpegPath(ffmpegPath); + this.instance.setFFprobePath(ffprobePath); - // 验证新路径是否可用 - if (await this.instance.isAvailable()) { - logger.log('[FFmpeg] 新路径验证成功 ✓'); - } else { - logger.logError('[FFmpeg] 警告: 新 FFmpeg 路径不可用'); - } - } + // 验证新路径是否可用 + if (await this.instance.isAvailable()) { + logger.log('[FFmpeg] 新路径验证成功 ✓'); + } else { + logger.logError('[FFmpeg] 警告: 新 FFmpeg 路径不可用'); + } } + } - /** + /** * 获取当前适配器(不初始化) */ - static getCurrentAdapter(): IFFmpegAdapter | null { - return this.instance; - } + static getCurrentAdapter (): IFFmpegAdapter | null { + return this.instance; + } } diff --git a/src/common/ffmpeg-adapter-interface.ts b/src/common/ffmpeg-adapter-interface.ts index 1ad42227..9b795bf7 100644 --- a/src/common/ffmpeg-adapter-interface.ts +++ b/src/common/ffmpeg-adapter-interface.ts @@ -7,62 +7,62 @@ * 视频信息结果 */ export interface VideoInfoResult { - /** 视频宽度(像素) */ - width: number; - /** 视频高度(像素) */ - height: number; - /** 视频时长(秒) */ - duration: number; - /** 容器格式 */ - format: string; - /** 缩略图 Buffer */ - thumbnail?: Buffer; + /** 视频宽度(像素) */ + width: number; + /** 视频高度(像素) */ + height: number; + /** 视频时长(秒) */ + duration: number; + /** 容器格式 */ + format: string; + /** 缩略图 Buffer */ + thumbnail?: Buffer; } /** * FFmpeg 适配器接口 */ export interface IFFmpegAdapter { - /** 适配器名称 */ - readonly name: string; + /** 适配器名称 */ + readonly name: string; - /** 是否可用 */ - isAvailable(): Promise; + /** 是否可用 */ + isAvailable(): Promise; - /** + /** * 获取视频信息(包含缩略图) * @param videoPath 视频文件路径 * @returns 视频信息 */ - getVideoInfo(videoPath: string): Promise; + getVideoInfo(videoPath: string): Promise; - /** + /** * 获取音视频文件时长 * @param filePath 文件路径 * @returns 时长(秒) */ - getDuration(filePath: string): Promise; + getDuration(filePath: string): Promise; - /** + /** * 转换音频为 PCM 格式 * @param filePath 输入文件路径 * @param pcmPath 输出 PCM 文件路径 * @returns PCM 数据 Buffer */ - convertToPCM(filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }>; + convertToPCM(filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }>; - /** + /** * 转换音频文件 * @param inputFile 输入文件路径 * @param outputFile 输出文件路径 * @param format 目标格式 ('amr' | 'silk' 等) */ - convertFile(inputFile: string, outputFile: string, format: string): Promise; + convertFile(inputFile: string, outputFile: string, format: string): Promise; - /** + /** * 提取视频缩略图 * @param videoPath 视频文件路径 * @param thumbnailPath 缩略图输出路径 */ - extractThumbnail(videoPath: string, thumbnailPath: string): Promise; + extractThumbnail(videoPath: string, thumbnailPath: string): Promise; } diff --git a/src/common/ffmpeg-addon-adapter.ts b/src/common/ffmpeg-addon-adapter.ts index e4cb93dd..b2b71f86 100644 --- a/src/common/ffmpeg-addon-adapter.ts +++ b/src/common/ffmpeg-addon-adapter.ts @@ -15,108 +15,108 @@ import { dlopen } from 'node:process'; * 获取 Native Addon 路径 * @param binaryPath 二进制文件路径(来自 pathWrapper.binaryPath) */ -function getAddonPath(binaryPath: string): string { - const platformName = platform(); - const archName = arch(); +function getAddonPath (binaryPath: string): string { + const platformName = platform(); + const archName = arch(); - let addonFileName: string = process.platform + '.' + process.arch; - let addonPath = path.join(binaryPath, "./native/ffmpeg/", `ffmpegAddon.${addonFileName}.node`); - if (!existsSync(addonPath)) { - throw new Error(`Unsupported platform: ${platformName} ${archName} - Addon not found at ${addonPath}`); - } - return addonPath; + const addonFileName: string = process.platform + '.' + process.arch; + const addonPath = path.join(binaryPath, './native/ffmpeg/', `ffmpegAddon.${addonFileName}.node`); + if (!existsSync(addonPath)) { + throw new Error(`Unsupported platform: ${platformName} ${archName} - Addon not found at ${addonPath}`); + } + return addonPath; } /** * FFmpeg Native Addon 适配器实现 */ export class FFmpegAddonAdapter implements IFFmpegAdapter { - public readonly name = 'FFmpegAddon'; - private addon: FFmpeg | null = null; - private binaryPath: string; + public readonly name = 'FFmpegAddon'; + private addon: FFmpeg | null = null; + private binaryPath: string; - constructor(binaryPath: string) { - this.binaryPath = binaryPath; - } + constructor (binaryPath: string) { + this.binaryPath = binaryPath; + } - /** + /** * 检查 Addon 是否可用 */ - async isAvailable(): Promise { - try { - let temp_addon = { exports: {} }; - dlopen(temp_addon, getAddonPath(this.binaryPath)); - this.addon = temp_addon.exports as FFmpeg; - return this.addon !== null; - } catch (error) { - console.log('[FFmpegAddonAdapter] Failed to load addon:', error); - return false; - } + async isAvailable (): Promise { + try { + const temp_addon = { exports: {} }; + dlopen(temp_addon, getAddonPath(this.binaryPath)); + this.addon = temp_addon.exports as FFmpeg; + return this.addon !== null; + } catch (error) { + console.log('[FFmpegAddonAdapter] Failed to load addon:', error); + return false; } + } - private ensureAddon(): FFmpeg { - if (!this.addon) { - throw new Error('FFmpeg Addon is not available'); - } - return this.addon; + private ensureAddon (): FFmpeg { + if (!this.addon) { + throw new Error('FFmpeg Addon is not available'); } + return this.addon; + } - /** + /** * 获取视频信息 */ - async getVideoInfo(videoPath: string): Promise { - const addon = this.ensureAddon(); - const info = await addon.getVideoInfo(videoPath, 'bmp24'); + async getVideoInfo (videoPath: string): Promise { + const addon = this.ensureAddon(); + const info = await addon.getVideoInfo(videoPath, 'bmp24'); - return { - width: info.width, - height: info.height, - duration: info.duration, - format: info.format, - thumbnail: info.image, - }; - } + return { + width: info.width, + height: info.height, + duration: info.duration, + format: info.format, + thumbnail: info.image, + }; + } - /** + /** * 获取时长 */ - async getDuration(filePath: string): Promise { - const addon = this.ensureAddon(); - return addon.getDuration(filePath); - } + async getDuration (filePath: string): Promise { + const addon = this.ensureAddon(); + return addon.getDuration(filePath); + } - /** + /** * 转换为 PCM */ - async convertToPCM(filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> { - const addon = this.ensureAddon(); - const result = await addon.decodeAudioToPCM(filePath, pcmPath, 24000); + async convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> { + const addon = this.ensureAddon(); + const result = await addon.decodeAudioToPCM(filePath, pcmPath, 24000); - return result; - } + return result; + } - /** + /** * 转换文件 */ - async convertFile(inputFile: string, outputFile: string, format: string): Promise { - const addon = this.ensureAddon(); + async convertFile (inputFile: string, outputFile: string, format: string): Promise { + const addon = this.ensureAddon(); - if (format === 'silk' || format === 'ntsilk') { - // 使用 Addon 的 NTSILK 转换 - await addon.convertToNTSilkTct(inputFile, outputFile); - } else { - throw new Error(`Format '${format}' is not supported by FFmpeg Addon`); - } + if (format === 'silk' || format === 'ntsilk') { + // 使用 Addon 的 NTSILK 转换 + await addon.convertToNTSilkTct(inputFile, outputFile); + } else { + throw new Error(`Format '${format}' is not supported by FFmpeg Addon`); } + } - /** + /** * 提取缩略图 */ - async extractThumbnail(videoPath: string, thumbnailPath: string): Promise { - const addon = this.ensureAddon(); - const info = await addon.getVideoInfo(videoPath); + async extractThumbnail (videoPath: string, thumbnailPath: string): Promise { + const addon = this.ensureAddon(); + const info = await addon.getVideoInfo(videoPath); - // 将缩略图写入文件 - await writeFile(thumbnailPath, info.image); - } + // 将缩略图写入文件 + await writeFile(thumbnailPath, info.image); + } } diff --git a/src/common/ffmpeg-addon.ts b/src/common/ffmpeg-addon.ts index acc5dca3..0c068ff5 100644 --- a/src/common/ffmpeg-addon.ts +++ b/src/common/ffmpeg-addon.ts @@ -1,6 +1,6 @@ /** * FFmpeg Node.js Native Addon Type Definitions - * + * * This addon provides FFmpeg functionality for Node.js including: * - Video information extraction with thumbnail generation * - Audio/Video duration detection @@ -12,60 +12,60 @@ * Video information result object */ export interface VideoInfo { - /** Video width in pixels */ - width: number; + /** Video width in pixels */ + width: number; - /** Video height in pixels */ - height: number; + /** Video height in pixels */ + height: number; - /** Video duration in seconds */ - duration: number; + /** Video duration in seconds */ + duration: number; - /** Container format name (e.g., "mp4", "mkv", "avi") */ - format: string; + /** Container format name (e.g., "mp4", "mkv", "avi") */ + format: string; - /** Video codec name (e.g., "h264", "hevc", "vp9") */ - videoCodec: string; + /** Video codec name (e.g., "h264", "hevc", "vp9") */ + videoCodec: string; - /** First frame thumbnail as BMP image buffer */ - image: Buffer; + /** First frame thumbnail as BMP image buffer */ + image: Buffer; } /** * Audio PCM decoding result object */ export interface AudioPCMResult { - /** PCM audio data as 16-bit signed integer samples */ - pcm: Buffer; + /** PCM audio data as 16-bit signed integer samples */ + pcm: Buffer; - /** Sample rate in Hz (e.g., 44100, 48000, 24000) */ - sampleRate: number; + /** Sample rate in Hz (e.g., 44100, 48000, 24000) */ + sampleRate: number; - /** Number of audio channels (1 for mono, 2 for stereo) */ - channels: number; + /** Number of audio channels (1 for mono, 2 for stereo) */ + channels: number; } /** * FFmpeg interface providing all audio/video processing methods */ export interface FFmpeg { - /** + /** * Get video information including resolution, duration, format, codec and first frame thumbnail */ - getVideoInfo(filePath: string, format?: 'bmp' | 'bmp24'): Promise; + getVideoInfo(filePath: string, format?: 'bmp' | 'bmp24'): Promise; - /** + /** * Get duration of audio or video file in seconds */ - getDuration(filePath: string): Promise; + getDuration(filePath: string): Promise; - /** + /** * Convert audio file to NTSILK format (WeChat voice message format) */ - convertToNTSilkTct(inputPath: string, outputPath: string): Promise; + convertToNTSilkTct(inputPath: string, outputPath: string): Promise; - /** + /** * Decode audio file to raw PCM data */ - decodeAudioToPCM(filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number }>; -} \ No newline at end of file + decodeAudioToPCM(filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number }>; +} diff --git a/src/common/ffmpeg-exec-adapter.ts b/src/common/ffmpeg-exec-adapter.ts index 1a37c126..9d745547 100644 --- a/src/common/ffmpeg-exec-adapter.ts +++ b/src/common/ffmpeg-exec-adapter.ts @@ -18,227 +18,227 @@ const execFileAsync = promisify(execFile); /** * 确保目录存在 */ -function ensureDirExists(filePath: string): void { - const dir = dirname(filePath); - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } +function ensureDirExists (filePath: string): void { + const dir = dirname(filePath); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } } /** * FFmpeg 命令行适配器实现 */ export class FFmpegExecAdapter implements IFFmpegAdapter { - public readonly name = 'FFmpegExec'; - private downloadAttempted = false; - - constructor( - private ffmpegPath: string = 'ffmpeg', - private ffprobePath: string = 'ffprobe', - private binaryPath?: string, - private logger?: LogWrapper - ) {} + public readonly name = 'FFmpegExec'; + private downloadAttempted = false; - /** + constructor ( + private ffmpegPath: string = 'ffmpeg', + private ffprobePath: string = 'ffprobe', + private binaryPath?: string, + private logger?: LogWrapper + ) {} + + /** * 检查 FFmpeg 是否可用,如果不可用则尝试下载 */ - async isAvailable(): Promise { - // 首先检查当前路径 - try { - await execFileAsync(this.ffmpegPath, ['-version']); - return true; - } catch { - // 如果失败且未尝试下载,尝试下载 - if (!this.downloadAttempted && this.binaryPath && this.logger) { - this.downloadAttempted = true; - - if (process.env['NAPCAT_DISABLE_FFMPEG_DOWNLOAD']) { - return false; - } - - this.logger.log('[FFmpeg] 未找到可用的 FFmpeg,尝试自动下载...'); - const result = await downloadFFmpegIfNotExists(this.logger); - - if (result.path && result.reset) { - // 更新路径 - if (process.platform === 'win32') { - this.ffmpegPath = join(result.path, 'ffmpeg.exe'); - this.ffprobePath = join(result.path, 'ffprobe.exe'); - this.logger.log('[FFmpeg] 已更新路径:', this.ffmpegPath); - - // 再次检查 - try { - await execFileAsync(this.ffmpegPath, ['-version']); - return true; - } catch { - return false; - } - } - } - } - return false; - } - } + async isAvailable (): Promise { + // 首先检查当前路径 + try { + await execFileAsync(this.ffmpegPath, ['-version']); + return true; + } catch { + // 如果失败且未尝试下载,尝试下载 + if (!this.downloadAttempted && this.binaryPath && this.logger) { + this.downloadAttempted = true; - /** + if (process.env['NAPCAT_DISABLE_FFMPEG_DOWNLOAD']) { + return false; + } + + this.logger.log('[FFmpeg] 未找到可用的 FFmpeg,尝试自动下载...'); + const result = await downloadFFmpegIfNotExists(this.logger); + + if (result.path && result.reset) { + // 更新路径 + if (process.platform === 'win32') { + this.ffmpegPath = join(result.path, 'ffmpeg.exe'); + this.ffprobePath = join(result.path, 'ffprobe.exe'); + this.logger.log('[FFmpeg] 已更新路径:', this.ffmpegPath); + + // 再次检查 + try { + await execFileAsync(this.ffmpegPath, ['-version']); + return true; + } catch { + return false; + } + } + } + } + return false; + } + } + + /** * 设置 FFmpeg 路径 */ - setFFmpegPath(ffmpegPath: string): void { - this.ffmpegPath = ffmpegPath; - } + setFFmpegPath (ffmpegPath: string): void { + this.ffmpegPath = ffmpegPath; + } - /** + /** * 设置 FFprobe 路径 */ - setFFprobePath(ffprobePath: string): void { - this.ffprobePath = ffprobePath; - } + setFFprobePath (ffprobePath: string): void { + this.ffprobePath = ffprobePath; + } - /** + /** * 获取视频信息 */ - async getVideoInfo(videoPath: string): Promise { - // 获取文件大小和类型 - const [fileType, duration] = await Promise.all([ - fileTypeFromFile(videoPath).catch(() => null), - this.getDuration(videoPath) - ]); + async getVideoInfo (videoPath: string): Promise { + // 获取文件大小和类型 + const [fileType, duration] = await Promise.all([ + fileTypeFromFile(videoPath).catch(() => null), + this.getDuration(videoPath), + ]); - // 创建临时缩略图路径 - const thumbnailPath = `${videoPath}.thumbnail.bmp`; - let width = 100; - let height = 100; - let thumbnail: Buffer | undefined; + // 创建临时缩略图路径 + const thumbnailPath = `${videoPath}.thumbnail.bmp`; + let width = 100; + let height = 100; + let thumbnail: Buffer | undefined; - try { - await this.extractThumbnail(videoPath, thumbnailPath); - - // 获取图片尺寸 - const dimensions = await imageSizeFallBack(thumbnailPath); - width = dimensions.width ?? 100; - height = dimensions.height ?? 100; - - // 读取缩略图 - if (existsSync(thumbnailPath)) { - thumbnail = readFileSync(thumbnailPath); - } - } catch (error) { - // 使用默认值 - } + try { + await this.extractThumbnail(videoPath, thumbnailPath); - return { - width, - height, - duration, - format: fileType?.ext ?? 'mp4', - thumbnail, - }; + // 获取图片尺寸 + const dimensions = await imageSizeFallBack(thumbnailPath); + width = dimensions.width ?? 100; + height = dimensions.height ?? 100; + + // 读取缩略图 + if (existsSync(thumbnailPath)) { + thumbnail = readFileSync(thumbnailPath); + } + } catch (error) { + // 使用默认值 } - /** + return { + width, + height, + duration, + format: fileType?.ext ?? 'mp4', + thumbnail, + }; + } + + /** * 获取时长 */ - async getDuration(filePath: string): Promise { - try { - const { stdout } = await execFileAsync(this.ffprobePath, [ - '-v', 'error', - '-show_entries', 'format=duration', - '-of', 'default=noprint_wrappers=1:nokey=1', - filePath - ]); + async getDuration (filePath: string): Promise { + try { + const { stdout } = await execFileAsync(this.ffprobePath, [ + '-v', 'error', + '-show_entries', 'format=duration', + '-of', 'default=noprint_wrappers=1:nokey=1', + filePath, + ]); - const duration = parseFloat(stdout.trim()); - return isNaN(duration) ? 60 : duration; - } catch { - return 60; // 默认时长 - } + const duration = parseFloat(stdout.trim()); + return isNaN(duration) ? 60 : duration; + } catch { + return 60; // 默认时长 } + } - /** + /** * 转换为 PCM */ - async convertToPCM(filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> { - try { - ensureDirExists(pcmPath); + async convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> { + try { + ensureDirExists(pcmPath); - await execFileAsync(this.ffmpegPath, [ - '-y', - '-i', filePath, - '-ar', '24000', - '-ac', '1', - '-f', 's16le', - pcmPath - ]); + await execFileAsync(this.ffmpegPath, [ + '-y', + '-i', filePath, + '-ar', '24000', + '-ac', '1', + '-f', 's16le', + pcmPath, + ]); - if (!existsSync(pcmPath)) { - throw new Error('转换PCM失败,输出文件不存在'); - } + if (!existsSync(pcmPath)) { + throw new Error('转换PCM失败,输出文件不存在'); + } - return { result: true, sampleRate: 24000 }; - } catch (error: any) { - throw new Error(`FFmpeg处理转换出错: ${error.message}`); - } + return { result: true, sampleRate: 24000 }; + } catch (error: any) { + throw new Error(`FFmpeg处理转换出错: ${error.message}`); } + } - /** + /** * 转换文件 */ - async convertFile(inputFile: string, outputFile: string, format: string): Promise { - try { - ensureDirExists(outputFile); + async convertFile (inputFile: string, outputFile: string, format: string): Promise { + try { + ensureDirExists(outputFile); - const params = format === 'amr' - ? [ - '-f', 's16le', - '-ar', '24000', - '-ac', '1', - '-i', inputFile, - '-ar', '8000', - '-b:a', '12.2k', - '-y', - outputFile - ] - : [ - '-f', 's16le', - '-ar', '24000', - '-ac', '1', - '-i', inputFile, - '-y', - outputFile - ]; + const params = format === 'amr' + ? [ + '-f', 's16le', + '-ar', '24000', + '-ac', '1', + '-i', inputFile, + '-ar', '8000', + '-b:a', '12.2k', + '-y', + outputFile, + ] + : [ + '-f', 's16le', + '-ar', '24000', + '-ac', '1', + '-i', inputFile, + '-y', + outputFile, + ]; - await execFileAsync(this.ffmpegPath, params); + await execFileAsync(this.ffmpegPath, params); - if (!existsSync(outputFile)) { - throw new Error('转换失败,输出文件不存在'); - } - } catch (error) { - console.error('Error converting file:', error); - throw new Error(`文件转换失败: ${(error as Error).message}`); - } + if (!existsSync(outputFile)) { + throw new Error('转换失败,输出文件不存在'); + } + } catch (error) { + console.error('Error converting file:', error); + throw new Error(`文件转换失败: ${(error as Error).message}`); } + } - /** + /** * 提取缩略图 */ - async extractThumbnail(videoPath: string, thumbnailPath: string): Promise { - try { - ensureDirExists(thumbnailPath); + async extractThumbnail (videoPath: string, thumbnailPath: string): Promise { + try { + ensureDirExists(thumbnailPath); - const { stderr } = await execFileAsync(this.ffmpegPath, [ - '-i', videoPath, - '-ss', '00:00:01.000', - '-vframes', '1', - '-y', // 覆盖输出文件 - thumbnailPath - ]); + const { stderr } = await execFileAsync(this.ffmpegPath, [ + '-i', videoPath, + '-ss', '00:00:01.000', + '-vframes', '1', + '-y', // 覆盖输出文件 + thumbnailPath, + ]); - if (!existsSync(thumbnailPath)) { - throw new Error(`提取缩略图失败,输出文件不存在: ${stderr}`); - } - } catch (error) { - console.error('Error extracting thumbnail:', error); - throw new Error(`提取缩略图失败: ${(error as Error).message}`); - } + if (!existsSync(thumbnailPath)) { + throw new Error(`提取缩略图失败,输出文件不存在: ${stderr}`); + } + } catch (error) { + console.error('Error extracting thumbnail:', error); + throw new Error(`提取缩略图失败: ${(error as Error).message}`); } + } } diff --git a/src/common/ffmpeg.ts b/src/common/ffmpeg.ts index 4d120bb9..9ef60f08 100644 --- a/src/common/ffmpeg.ts +++ b/src/common/ffmpeg.ts @@ -8,137 +8,137 @@ import { FFmpegAdapterFactory } from './ffmpeg-adapter-factory'; import type { IFFmpegAdapter } from './ffmpeg-adapter-interface'; const getFFmpegPath = (tool: string, binaryPath?: string): string => { - if (process.platform === 'win32' && binaryPath) { - const exeName = `${tool}.exe`; - const localPath = path.join(binaryPath, 'ffmpeg', exeName); - const isLocalExeExists = existsSync(localPath); - return isLocalExeExists ? localPath : exeName; - } - return tool; + if (process.platform === 'win32' && binaryPath) { + const exeName = `${tool}.exe`; + const localPath = path.join(binaryPath, 'ffmpeg', exeName); + const isLocalExeExists = existsSync(localPath); + return isLocalExeExists ? localPath : exeName; + } + return tool; }; export let FFMPEG_CMD = 'ffmpeg'; export let FFPROBE_CMD = 'ffprobe'; export class FFmpegService { - private static adapter: IFFmpegAdapter | null = null; - private static initialized = false; + private static adapter: IFFmpegAdapter | null = null; + private static initialized = false; - /** + /** * 初始化 FFmpeg 服务 * @param binaryPath 二进制文件路径(来自 pathWrapper.binaryPath) * @param logger 日志记录器 */ - public static async init(binaryPath: string, logger: LogWrapper): Promise { - if (this.initialized) { - return; - } - - // 检查本地 ffmpeg 路径 - FFMPEG_CMD = getFFmpegPath('ffmpeg', binaryPath); - FFPROBE_CMD = getFFmpegPath('ffprobe', binaryPath); - - // 立即初始化适配器(会触发自动下载等逻辑) - this.adapter = await FFmpegAdapterFactory.getAdapter( - logger, - FFMPEG_CMD, - FFPROBE_CMD, - binaryPath - ); - - this.initialized = true; + public static async init (binaryPath: string, logger: LogWrapper): Promise { + if (this.initialized) { + return; } - /** + // 检查本地 ffmpeg 路径 + FFMPEG_CMD = getFFmpegPath('ffmpeg', binaryPath); + FFPROBE_CMD = getFFmpegPath('ffprobe', binaryPath); + + // 立即初始化适配器(会触发自动下载等逻辑) + this.adapter = await FFmpegAdapterFactory.getAdapter( + logger, + FFMPEG_CMD, + FFPROBE_CMD, + binaryPath + ); + + this.initialized = true; + } + + /** * 获取 FFmpeg 适配器 */ - private static async getAdapter(): Promise { - if (!this.adapter) { - throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.'); - } - return this.adapter; + private static async getAdapter (): Promise { + if (!this.adapter) { + throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.'); } + return this.adapter; + } - /** + /** * 设置 FFmpeg 路径并更新适配器 * @deprecated 建议使用 init() 方法初始化 */ - public static async setFfmpegPath(ffmpegPath: string, logger: LogWrapper): Promise { - if (platform() === 'win32') { - FFMPEG_CMD = path.join(ffmpegPath, 'ffmpeg.exe'); - FFPROBE_CMD = path.join(ffmpegPath, 'ffprobe.exe'); - logger.log('[Check] ffmpeg:', FFMPEG_CMD); - logger.log('[Check] ffprobe:', FFPROBE_CMD); + public static async setFfmpegPath (ffmpegPath: string, logger: LogWrapper): Promise { + if (platform() === 'win32') { + FFMPEG_CMD = path.join(ffmpegPath, 'ffmpeg.exe'); + FFPROBE_CMD = path.join(ffmpegPath, 'ffprobe.exe'); + logger.log('[Check] ffmpeg:', FFMPEG_CMD); + logger.log('[Check] ffprobe:', FFPROBE_CMD); - // 更新适配器路径 - await FFmpegAdapterFactory.updateFFmpegPath(logger, FFMPEG_CMD, FFPROBE_CMD); - } + // 更新适配器路径 + await FFmpegAdapterFactory.updateFFmpegPath(logger, FFMPEG_CMD, FFPROBE_CMD); } + } - /** + /** * 提取视频缩略图 */ - public static async extractThumbnail(videoPath: string, thumbnailPath: string): Promise { - const adapter = await this.getAdapter(); - await adapter.extractThumbnail(videoPath, thumbnailPath); - } + public static async extractThumbnail (videoPath: string, thumbnailPath: string): Promise { + const adapter = await this.getAdapter(); + await adapter.extractThumbnail(videoPath, thumbnailPath); + } - /** + /** * 转换音频文件 */ - public static async convertFile(inputFile: string, outputFile: string, format: string): Promise { - const adapter = await this.getAdapter(); - await adapter.convertFile(inputFile, outputFile, format); - } + public static async convertFile (inputFile: string, outputFile: string, format: string): Promise { + const adapter = await this.getAdapter(); + await adapter.convertFile(inputFile, outputFile, format); + } - /** + /** * 转换为 PCM 格式 */ - public static async convert(filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> { - const adapter = await this.getAdapter(); - return adapter.convertToPCM(filePath, pcmPath); - } + public static async convert (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> { + const adapter = await this.getAdapter(); + return adapter.convertToPCM(filePath, pcmPath); + } - /** + /** * 获取视频信息 */ - public static async getVideoInfo(videoPath: string, thumbnailPath: string): Promise { - const adapter = await this.getAdapter(); + public static async getVideoInfo (videoPath: string, thumbnailPath: string): Promise { + const adapter = await this.getAdapter(); - try { - // 获取文件大小 - const fileSize = statSync(videoPath).size; + try { + // 获取文件大小 + const fileSize = statSync(videoPath).size; - // 使用适配器获取视频信息 - const videoInfo = await adapter.getVideoInfo(videoPath); + // 使用适配器获取视频信息 + const videoInfo = await adapter.getVideoInfo(videoPath); - // 如果提供了缩略图路径且适配器返回了缩略图,保存到指定路径 - if (thumbnailPath && videoInfo.thumbnail) { - writeFileSync(thumbnailPath, videoInfo.thumbnail); - } + // 如果提供了缩略图路径且适配器返回了缩略图,保存到指定路径 + if (thumbnailPath && videoInfo.thumbnail) { + writeFileSync(thumbnailPath, videoInfo.thumbnail); + } - const result: VideoInfo = { - width: videoInfo.width, - height: videoInfo.height, - time: videoInfo.duration, - format: videoInfo.format, - size: fileSize, - filePath: videoPath - }; + const result: VideoInfo = { + width: videoInfo.width, + height: videoInfo.height, + time: videoInfo.duration, + format: videoInfo.format, + size: fileSize, + filePath: videoPath, + }; - return result; - } catch (error) { - // 降级处理:返回默认值 - const fileType = await fileTypeFromFile(videoPath).catch(() => null); - const fileSize = statSync(videoPath).size; + return result; + } catch (error) { + // 降级处理:返回默认值 + const fileType = await fileTypeFromFile(videoPath).catch(() => null); + const fileSize = statSync(videoPath).size; - return { - width: 100, - height: 100, - time: 60, - format: fileType?.ext ?? 'mp4', - size: fileSize, - filePath: videoPath - }; - } + return { + width: 100, + height: 100, + time: 60, + format: fileType?.ext ?? 'mp4', + size: fileSize, + filePath: videoPath, + }; } -} \ No newline at end of file + } +} diff --git a/src/common/file-uuid.ts b/src/common/file-uuid.ts index d96797c6..7186bbc4 100644 --- a/src/common/file-uuid.ts +++ b/src/common/file-uuid.ts @@ -2,120 +2,120 @@ import { Peer } from '@/core'; import { randomUUID } from 'crypto'; class TimeBasedCache { - private cache = new Map(); - private keyList = new Set(); - private operationCount = 0; + private cache = new Map(); + private keyList = new Set(); + private operationCount = 0; - constructor(private maxCapacity: number, private ttl: number = 30 * 1000 * 60, private cleanupCount: number = 10) {} + constructor (private maxCapacity: number, private ttl: number = 30 * 1000 * 60, private cleanupCount: number = 10) {} - public put(key: K, value: V): void { - const timestamp = Date.now(); - const cacheEntry = { value, timestamp, frequency: 1 }; - this.cache.set(key, cacheEntry); - this.keyList.add(key); - this.operationCount++; - if (this.keyList.size > this.maxCapacity) this.evict(); - if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount); + public put (key: K, value: V): void { + const timestamp = Date.now(); + const cacheEntry = { value, timestamp, frequency: 1 }; + this.cache.set(key, cacheEntry); + this.keyList.add(key); + this.operationCount++; + if (this.keyList.size > this.maxCapacity) this.evict(); + if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount); + } + + public get (key: K): V | undefined { + const entry = this.cache.get(key); + if (entry && Date.now() - entry.timestamp < this.ttl) { + entry.timestamp = Date.now(); + entry.frequency++; + this.operationCount++; + if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount); + return entry.value; + } else { + this.deleteKey(key); } + return undefined; + } - public get(key: K): V | undefined { + private cleanup (count: number): void { + const currentTime = Date.now(); + let cleaned = 0; + for (const key of this.keyList) { + if (cleaned >= count) break; + const entry = this.cache.get(key); + if (entry && currentTime - entry.timestamp >= this.ttl) { + this.deleteKey(key); + cleaned++; + } + } + this.operationCount = 0; // 重置操作计数器 + } + + private deleteKey (key: K): void { + this.cache.delete(key); + this.keyList.delete(key); + } + + private evict (): void { + while (this.keyList.size > this.maxCapacity) { + let oldestKey: K | undefined; + let minFrequency = Infinity; + for (const key of this.keyList) { const entry = this.cache.get(key); - if (entry && Date.now() - entry.timestamp < this.ttl) { - entry.timestamp = Date.now(); - entry.frequency++; - this.operationCount++; - if (this.operationCount >= this.cleanupCount) this.cleanup(this.cleanupCount); - return entry.value; - } else { - this.deleteKey(key); - } - return undefined; - } - - private cleanup(count: number): void { - const currentTime = Date.now(); - let cleaned = 0; - for (const key of this.keyList) { - if (cleaned >= count) break; - const entry = this.cache.get(key); - if (entry && currentTime - entry.timestamp >= this.ttl) { - this.deleteKey(key); - cleaned++; - } - } - this.operationCount = 0; // 重置操作计数器 - } - - private deleteKey(key: K): void { - this.cache.delete(key); - this.keyList.delete(key); - } - - private evict(): void { - while (this.keyList.size > this.maxCapacity) { - let oldestKey: K | undefined; - let minFrequency = Infinity; - for (const key of this.keyList) { - const entry = this.cache.get(key); - if (entry && entry.frequency < minFrequency) { - minFrequency = entry.frequency; - oldestKey = key; - } - } - if (oldestKey !== undefined) this.deleteKey(oldestKey); + if (entry && entry.frequency < minFrequency) { + minFrequency = entry.frequency; + oldestKey = key; } + } + if (oldestKey !== undefined) this.deleteKey(oldestKey); } + } } interface FileUUIDData { - peer: Peer; - modelId?: string; - fileId?: string; - msgId?: string; - elementId?: string; - fileUUID?: string; + peer: Peer; + modelId?: string; + fileId?: string; + msgId?: string; + elementId?: string; + fileUUID?: string; } class FileUUIDManager { - private cache: TimeBasedCache; + private cache: TimeBasedCache; - constructor(ttl: number) { - this.cache = new TimeBasedCache(5000, ttl); - } + constructor (ttl: number) { + this.cache = new TimeBasedCache(5000, ttl); + } - public encode(data: FileUUIDData, endString: string = '', customUUID?: string): string { - const uuid = customUUID ? customUUID : randomUUID().replace(/-/g, '') + endString; - this.cache.put(uuid, data); - return uuid; - } + public encode (data: FileUUIDData, endString: string = '', customUUID?: string): string { + const uuid = customUUID || randomUUID().replace(/-/g, '') + endString; + this.cache.put(uuid, data); + return uuid; + } - public decode(uuid: string): FileUUIDData | undefined { - return this.cache.get(uuid); - } + public decode (uuid: string): FileUUIDData | undefined { + return this.cache.get(uuid); + } } export class FileNapCatOneBotUUIDWrap { - private manager: FileUUIDManager; + private manager: FileUUIDManager; - constructor(ttl: number = 86400000) { - this.manager = new FileUUIDManager(ttl); - } + constructor (ttl: number = 86400000) { + this.manager = new FileUUIDManager(ttl); + } - public encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = '', endString: string = '', customUUID?: string): string { - return this.manager.encode({ peer, modelId, fileId, fileUUID }, endString, customUUID); - } + public encodeModelId (peer: Peer, modelId: string, fileId: string, fileUUID: string = '', endString: string = '', customUUID?: string): string { + return this.manager.encode({ peer, modelId, fileId, fileUUID }, endString, customUUID); + } - public decodeModelId(uuid: string): FileUUIDData | undefined { - return this.manager.decode(uuid); - } + public decodeModelId (uuid: string): FileUUIDData | undefined { + return this.manager.decode(uuid); + } - public encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = '', customUUID?: string): string { - return this.manager.encode({ peer, msgId, elementId, fileUUID }, '', customUUID); - } + public encode (peer: Peer, msgId: string, elementId: string, fileUUID: string = '', customUUID?: string): string { + return this.manager.encode({ peer, msgId, elementId, fileUUID }, '', customUUID); + } - public decode(uuid: string): FileUUIDData | undefined { - return this.manager.decode(uuid); - } + public decode (uuid: string): FileUUIDData | undefined { + return this.manager.decode(uuid); + } } -export const FileNapCatOneBotUUID = new FileNapCatOneBotUUIDWrap(); \ No newline at end of file +export const FileNapCatOneBotUUID = new FileNapCatOneBotUUIDWrap(); diff --git a/src/common/file.ts b/src/common/file.ts index b62ad7b0..a7dea7d2 100644 --- a/src/common/file.ts +++ b/src/common/file.ts @@ -5,205 +5,204 @@ import path from 'node:path'; import { solveProblem } from '@/common/helper'; export interface HttpDownloadOptions { - url: string; - headers?: Record | string; + url: string; + headers?: Record | string; } type Uri2LocalRes = { - success: boolean, - errMsg: string, - fileName: string, - path: string + success: boolean, + errMsg: string, + fileName: string, + path: string +}; + +// 定义一个异步函数来检查文件是否存在 +export function checkFileExist (path: string, timeout: number = 3000): Promise { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + function check () { + if (fs.existsSync(path)) { + resolve(); + } else if (Date.now() - startTime > timeout) { + reject(new Error(`文件不存在: ${path}`)); + } else { + setTimeout(check, 100); + } + } + + check(); + }); } // 定义一个异步函数来检查文件是否存在 -export function checkFileExist(path: string, timeout: number = 3000): Promise { - return new Promise((resolve, reject) => { - const startTime = Date.now(); - - function check() { - if (fs.existsSync(path)) { - resolve(); - } else if (Date.now() - startTime > timeout) { - reject(new Error(`文件不存在: ${path}`)); - } else { - setTimeout(check, 100); - } - } - - check(); - }); -} - -// 定义一个异步函数来检查文件是否存在 -export async function checkFileExistV2(path: string, timeout: number = 3000): Promise { - // 使用 Promise.race 来同时进行文件状态检查和超时计时 - // Promise.race 会返回第一个解决(resolve)或拒绝(reject)的 Promise - await Promise.race([ - checkFile(path), - timeoutPromise(timeout, `文件不存在: ${path}`), - ]); +export async function checkFileExistV2 (path: string, timeout: number = 3000): Promise { + // 使用 Promise.race 来同时进行文件状态检查和超时计时 + // Promise.race 会返回第一个解决(resolve)或拒绝(reject)的 Promise + await Promise.race([ + checkFile(path), + timeoutPromise(timeout, `文件不存在: ${path}`), + ]); } // 转换超时时间至 Promise -function timeoutPromise(timeout: number, errorMsg: string): Promise { - return new Promise((_, reject) => { - setTimeout(() => { - reject(new Error(errorMsg)); - }, timeout); - }); +function timeoutPromise (timeout: number, errorMsg: string): Promise { + return new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(errorMsg)); + }, timeout); + }); } // 异步检查文件是否存在 -async function checkFile(path: string): Promise { - try { - await stat(path); - } catch (error: unknown) { - if ((error as Error & { code: string }).code === 'ENOENT') { - // 如果文件不存在,则抛出一个错误 - throw new Error(`文件不存在: ${path}`); - } else { - // 对于 stat 调用的其他错误,重新抛出 - throw error; - } - } - // 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身 -} - -export function calculateFileMD5(filePath: string): Promise { - return new Promise((resolve, reject) => { - // 创建一个流式读取器 - const stream = fs.createReadStream(filePath); - const hash = crypto.createHash('md5'); - - stream.on('data', (data) => { - // 当读取到数据时,更新哈希对象的状态 - hash.update(data); - }); - - stream.on('end', () => { - // 文件读取完成,计算哈希 - const md5 = hash.digest('hex'); - resolve(md5); - }); - - stream.on('error', (err: Error) => { - // 处理可能的读取错误 - reject(err); - }); - }); -} - -async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise { - let url: string; - let headers: Record = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', - }; - if (typeof options === 'string') { - url = options; - headers['Host'] = new URL(url).hostname; +async function checkFile (path: string): Promise { + try { + await stat(path); + } catch (error: unknown) { + if ((error as Error & { code: string }).code === 'ENOENT') { + // 如果文件不存在,则抛出一个错误 + throw new Error(`文件不存在: ${path}`); } else { - url = options.url; - if (options.headers) { - if (typeof options.headers === 'string') { - headers = JSON.parse(options.headers); - } else { - headers = options.headers; - } - } + // 对于 stat 调用的其他错误,重新抛出 + throw error; } - if (useReferer && !headers['Referer']) { - headers['Referer'] = url; - } - const fetchRes = await fetch(url, { headers, redirect: 'follow' }).catch((err) => { - if (err.cause) { - throw err.cause; - } - throw err; - }); - return fetchRes; + } + // 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身 } -export async function httpDownload(options: string | HttpDownloadOptions): Promise { - const useReferer = typeof options === 'string'; - let resp = await tryDownload(options); - if (resp.status === 403 && useReferer) { - resp = await tryDownload(options, true); +export function calculateFileMD5 (filePath: string): Promise { + return new Promise((resolve, reject) => { + // 创建一个流式读取器 + const stream = fs.createReadStream(filePath); + const hash = crypto.createHash('md5'); + + stream.on('data', (data) => { + // 当读取到数据时,更新哈希对象的状态 + hash.update(data); + }); + + stream.on('end', () => { + // 文件读取完成,计算哈希 + const md5 = hash.digest('hex'); + resolve(md5); + }); + + stream.on('error', (err: Error) => { + // 处理可能的读取错误 + reject(err); + }); + }); +} + +async function tryDownload (options: string | HttpDownloadOptions, useReferer: boolean = false): Promise { + let url: string; + let headers: Record = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', + }; + if (typeof options === 'string') { + url = options; + headers['Host'] = new URL(url).hostname; + } else { + url = options.url; + if (options.headers) { + if (typeof options.headers === 'string') { + headers = JSON.parse(options.headers); + } else { + headers = options.headers; + } } - if (!resp.ok) throw new Error(`下载文件失败: ${resp.statusText}`); - const blob = await resp.blob(); - const buffer = await blob.arrayBuffer(); - return Buffer.from(buffer); + } + if (useReferer && !headers['Referer']) { + headers['Referer'] = url; + } + const fetchRes = await fetch(url, { headers, redirect: 'follow' }).catch((err) => { + if (err.cause) { + throw err.cause; + } + throw err; + }); + return fetchRes; +} + +export async function httpDownload (options: string | HttpDownloadOptions): Promise { + const useReferer = typeof options === 'string'; + let resp = await tryDownload(options); + if (resp.status === 403 && useReferer) { + resp = await tryDownload(options, true); + } + if (!resp.ok) throw new Error(`下载文件失败: ${resp.statusText}`); + const blob = await resp.blob(); + const buffer = await blob.arrayBuffer(); + return Buffer.from(buffer); } export enum FileUriType { - Unknown = 0, - Local = 1, - Remote = 2, - Base64 = 3 + Unknown = 0, + Local = 1, + Remote = 2, + Base64 = 3, } -export async function checkUriType(Uri: string) { - const LocalFileRet = await solveProblem((uri: string) => { - if (fs.existsSync(path.normalize(uri))) { - return { Uri: path.normalize(uri), Type: FileUriType.Local }; - } - return undefined; - }, Uri); - if (LocalFileRet) return LocalFileRet; - const OtherFileRet = await solveProblem((uri: string) => { - // 再判断是否是Http - if (uri.startsWith('http:') || uri.startsWith('https:')) { - return { Uri: uri, Type: FileUriType.Remote }; - } - // 再判断是否是Base64 - if (uri.startsWith('base64:')) { - return { Uri: uri, Type: FileUriType.Base64 }; - } - // 默认file:// - if (uri.startsWith('file:')) { - const filePath: string = decodeURIComponent(uri.startsWith('file:///') && process.platform === 'win32' ? uri.slice(8) : uri.slice(7)); - return { Uri: filePath, Type: FileUriType.Local }; - } - if (uri.startsWith('data:')) { - const data = uri.split(',')[1]; - if (data) return { Uri: data, Type: FileUriType.Base64 }; - } - return; - }, Uri); - if (OtherFileRet) return OtherFileRet; - - return { Uri: Uri, Type: FileUriType.Unknown }; -} - -export async function uriToLocalFile(dir: string, uri: string, filename: string = randomUUID(), headers?: Record): Promise { - const { Uri: HandledUri, Type: UriType } = await checkUriType(uri); - - const filePath = path.join(dir, filename); - - switch (UriType) { - case FileUriType.Local: { - const fileExt = path.extname(HandledUri); - const localFileName = path.basename(HandledUri, fileExt) + fileExt; - const tempFilePath = path.join(dir, filename + fileExt); - fs.copyFileSync(HandledUri, tempFilePath); - return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath }; - } - - case FileUriType.Remote: { - const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {} }); - fs.writeFileSync(filePath, buffer); - return { success: true, errMsg: '', fileName: filename, path: filePath }; - } - - case FileUriType.Base64: { - const base64 = HandledUri.replace(/^base64:\/\//, ''); - const base64Buffer = Buffer.from(base64, 'base64'); - fs.writeFileSync(filePath, base64Buffer); - return { success: true, errMsg: '', fileName: filename, path: filePath }; - } - - default: - return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' }; +export async function checkUriType (Uri: string) { + const LocalFileRet = await solveProblem((uri: string) => { + if (fs.existsSync(path.normalize(uri))) { + return { Uri: path.normalize(uri), Type: FileUriType.Local }; } + return undefined; + }, Uri); + if (LocalFileRet) return LocalFileRet; + const OtherFileRet = await solveProblem((uri: string) => { + // 再判断是否是Http + if (uri.startsWith('http:') || uri.startsWith('https:')) { + return { Uri: uri, Type: FileUriType.Remote }; + } + // 再判断是否是Base64 + if (uri.startsWith('base64:')) { + return { Uri: uri, Type: FileUriType.Base64 }; + } + // 默认file:// + if (uri.startsWith('file:')) { + const filePath: string = decodeURIComponent(uri.startsWith('file:///') && process.platform === 'win32' ? uri.slice(8) : uri.slice(7)); + return { Uri: filePath, Type: FileUriType.Local }; + } + if (uri.startsWith('data:')) { + const data = uri.split(',')[1]; + if (data) return { Uri: data, Type: FileUriType.Base64 }; + } + }, Uri); + if (OtherFileRet) return OtherFileRet; + + return { Uri, Type: FileUriType.Unknown }; +} + +export async function uriToLocalFile (dir: string, uri: string, filename: string = randomUUID(), headers?: Record): Promise { + const { Uri: HandledUri, Type: UriType } = await checkUriType(uri); + + const filePath = path.join(dir, filename); + + switch (UriType) { + case FileUriType.Local: { + const fileExt = path.extname(HandledUri); + const localFileName = path.basename(HandledUri, fileExt) + fileExt; + const tempFilePath = path.join(dir, filename + fileExt); + fs.copyFileSync(HandledUri, tempFilePath); + return { success: true, errMsg: '', fileName: localFileName, path: tempFilePath }; + } + + case FileUriType.Remote: { + const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {} }); + fs.writeFileSync(filePath, buffer); + return { success: true, errMsg: '', fileName: filename, path: filePath }; + } + + case FileUriType.Base64: { + const base64 = HandledUri.replace(/^base64:\/\//, ''); + const base64Buffer = Buffer.from(base64, 'base64'); + fs.writeFileSync(filePath, base64Buffer); + return { success: true, errMsg: '', fileName: filename, path: filePath }; + } + + default: + return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' }; + } } diff --git a/src/common/forward-msg-builder.ts b/src/common/forward-msg-builder.ts index bb30e04b..0d6b455a 100644 --- a/src/common/forward-msg-builder.ts +++ b/src/common/forward-msg-builder.ts @@ -2,113 +2,115 @@ import * as crypto from 'node:crypto'; import { PacketMsg } from '@/core/packet/message/message'; interface ForwardMsgJson { - app: string - config: ForwardMsgJsonConfig, - desc: string, - extra: ForwardMsgJsonExtra, - meta: ForwardMsgJsonMeta, - prompt: string, - ver: string, - view: string + app: string + config: ForwardMsgJsonConfig, + desc: string, + extra: ForwardMsgJsonExtra, + meta: ForwardMsgJsonMeta, + prompt: string, + ver: string, + view: string } interface ForwardMsgJsonConfig { - autosize: number, - forward: number, - round: number, - type: string, - width: number + autosize: number, + forward: number, + round: number, + type: string, + width: number } interface ForwardMsgJsonExtra { - filename: string, - tsum: number, + filename: string, + tsum: number, } interface ForwardMsgJsonMeta { - detail: ForwardMsgJsonMetaDetail + detail: ForwardMsgJsonMetaDetail } interface ForwardMsgJsonMetaDetail { - news: { - text: string - }[], - resid: string, - source: string, - summary: string, - uniseq: string + news: { + text: string + }[], + resid: string, + source: string, + summary: string, + uniseq: string } interface ForwardAdaptMsg { - senderName?: string; - isGroupMsg?: boolean; - msg?: ForwardAdaptMsgElement[]; + senderName?: string; + isGroupMsg?: boolean; + msg?: ForwardAdaptMsgElement[]; } interface ForwardAdaptMsgElement { - preview?: string; + preview?: string; } export class ForwardMsgBuilder { - private static build(resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson { - const id = crypto.randomUUID(); - const isGroupMsg = msg.some(m => m.isGroupMsg); - if (!source) { - source = msg.length === 0 ? '聊天记录' : (isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录'); - } - if (!news) { - news = msg.length === 0 ? [{ - text: 'Nya~ This message is send from NapCat.Packet!', - }] : msg.map(m => ({ - text: `${m.senderName}: ${m.msg?.map(msg => msg.preview).join('')}`, - })); - } - if (!summary) { - summary = `查看${msg.length}条转发消息`; - } - if (!prompt) { - prompt = '[聊天记录]'; - } - return { - app: 'com.tencent.multimsg', - config: { - autosize: 1, - forward: 1, - round: 1, - type: 'normal', - width: 300 - }, - desc: prompt, - extra: { - filename: id, - tsum: msg.length, - }, - meta: { - detail: { - news, - resid: resId, - source, - summary, - uniseq: id, - } - }, - prompt, - ver: '0.0.0.5', - view: 'contact', - }; + private static build (resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson { + const id = crypto.randomUUID(); + const isGroupMsg = msg.some(m => m.isGroupMsg); + if (!source) { + source = msg.length === 0 ? '聊天记录' : (isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录'); } + if (!news) { + news = msg.length === 0 + ? [{ + text: 'Nya~ This message is send from NapCat.Packet!', + }] + : msg.map(m => ({ + text: `${m.senderName}: ${m.msg?.map(msg => msg.preview).join('')}`, + })); + } + if (!summary) { + summary = `查看${msg.length}条转发消息`; + } + if (!prompt) { + prompt = '[聊天记录]'; + } + return { + app: 'com.tencent.multimsg', + config: { + autosize: 1, + forward: 1, + round: 1, + type: 'normal', + width: 300, + }, + desc: prompt, + extra: { + filename: id, + tsum: msg.length, + }, + meta: { + detail: { + news, + resid: resId, + source, + summary, + uniseq: id, + }, + }, + prompt, + ver: '0.0.0.5', + view: 'contact', + }; + } - static fromResId(resId: string): ForwardMsgJson { - return this.build(resId, []); - } + static fromResId (resId: string): ForwardMsgJson { + return this.build(resId, []); + } - static fromPacketMsg(resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson { - return this.build(resId, packetMsg.map(msg => ({ - senderName: msg.senderName, - isGroupMsg: msg.groupId !== undefined, - msg: msg.msg.map(m => ({ - preview: m.valid ? m.toPreview() : '[该消息类型暂不支持查看]', - })) - })), source, news, summary, prompt); - } + static fromPacketMsg (resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson { + return this.build(resId, packetMsg.map(msg => ({ + senderName: msg.senderName, + isGroupMsg: msg.groupId !== undefined, + msg: msg.msg.map(m => ({ + preview: m.valid ? m.toPreview() : '[该消息类型暂不支持查看]', + })), + })), source, news, summary, prompt); + } } diff --git a/src/common/health.ts b/src/common/health.ts index 139cffa8..5d384414 100644 --- a/src/common/health.ts +++ b/src/common/health.ts @@ -1,280 +1,280 @@ export interface ResourceConfig { - /** 资源获取函数 */ - resourceFn: (...args: T) => Promise; - /** 失败后禁用时间(毫秒),默认 30 秒 */ - disableTime?: number; - /** 最大重试次数,默认 3 次 */ - maxRetries?: number; - /** 主动测试间隔(毫秒),默认 60 秒 */ - healthCheckInterval?: number; - /** 最大健康检查失败次数,超过后永久禁用,默认 5 次 */ - maxHealthCheckFailures?: number; - /** 健康检查函数,如果提供则优先使用此函数进行健康检查 */ - healthCheckFn?: (...args: T) => Promise; - /** 测试参数(用于健康检查) */ - testArgs?: T; + /** 资源获取函数 */ + resourceFn: (...args: T) => Promise; + /** 失败后禁用时间(毫秒),默认 30 秒 */ + disableTime?: number; + /** 最大重试次数,默认 3 次 */ + maxRetries?: number; + /** 主动测试间隔(毫秒),默认 60 秒 */ + healthCheckInterval?: number; + /** 最大健康检查失败次数,超过后永久禁用,默认 5 次 */ + maxHealthCheckFailures?: number; + /** 健康检查函数,如果提供则优先使用此函数进行健康检查 */ + healthCheckFn?: (...args: T) => Promise; + /** 测试参数(用于健康检查) */ + testArgs?: T; } interface ResourceTypeState { - /** 资源配置 */ - config: { - resourceFn: (...args: any[]) => Promise; - healthCheckFn?: (...args: any[]) => Promise; - disableTime: number; - maxRetries: number; - healthCheckInterval: number; - maxHealthCheckFailures: number; - testArgs?: any[]; - }; - /** 是否启用 */ - isEnabled: boolean; - /** 禁用截止时间 */ - disableUntil: number; - /** 当前重试次数 */ - currentRetries: number; - /** 健康检查失败次数 */ - healthCheckFailureCount: number; - /** 是否永久禁用 */ - isPermanentlyDisabled: boolean; - /** 上次健康检查时间 */ - lastHealthCheckTime: number; - /** 成功次数统计 */ - successCount: number; - /** 失败次数统计 */ - failureCount: number; + /** 资源配置 */ + config: { + resourceFn: (...args: any[]) => Promise; + healthCheckFn?: (...args: any[]) => Promise; + disableTime: number; + maxRetries: number; + healthCheckInterval: number; + maxHealthCheckFailures: number; + testArgs?: any[]; + }; + /** 是否启用 */ + isEnabled: boolean; + /** 禁用截止时间 */ + disableUntil: number; + /** 当前重试次数 */ + currentRetries: number; + /** 健康检查失败次数 */ + healthCheckFailureCount: number; + /** 是否永久禁用 */ + isPermanentlyDisabled: boolean; + /** 上次健康检查时间 */ + lastHealthCheckTime: number; + /** 成功次数统计 */ + successCount: number; + /** 失败次数统计 */ + failureCount: number; } export class ResourceManager { - private resourceTypes = new Map(); - private destroyed = false; + private resourceTypes = new Map(); + private destroyed = false; - /** + /** * 调用资源(自动注册或复用已有配置) */ - async callResource( - type: string, - config: ResourceConfig, - ...args: T - ): Promise { - if (this.destroyed) { - throw new Error('ResourceManager has been destroyed'); - } - - // 获取或创建资源类型状态 - let state = this.resourceTypes.get(type); - - if (!state) { - // 首次注册 - state = { - config: { - resourceFn: config.resourceFn as (...args: any[]) => Promise, - healthCheckFn: config.healthCheckFn as ((...args: any[]) => Promise) | undefined, - disableTime: config.disableTime ?? 30000, - maxRetries: config.maxRetries ?? 3, - healthCheckInterval: config.healthCheckInterval ?? 60000, - maxHealthCheckFailures: config.maxHealthCheckFailures ?? 20, - testArgs: config.testArgs as any[] | undefined, - }, - isEnabled: true, - disableUntil: 0, - currentRetries: 0, - healthCheckFailureCount: 0, - isPermanentlyDisabled: false, - lastHealthCheckTime: 0, - successCount: 0, - failureCount: 0, - }; - this.resourceTypes.set(type, state); - } - - // 在调用前检查是否需要进行健康检查 - await this.checkAndPerformHealthCheck(state); - - // 检查资源状态 - if (state.isPermanentlyDisabled) { - throw new Error(`Resource type '${type}' is permanently disabled (success: ${state.successCount}, failure: ${state.failureCount})`); - } - - if (!this.isResourceAvailable(type)) { - const disableUntilDate = new Date(state.disableUntil).toISOString(); - throw new Error(`Resource type '${type}' is currently disabled until ${disableUntilDate} (success: ${state.successCount}, failure: ${state.failureCount})`); - } - - // 调用资源 - try { - const result = await config.resourceFn(...args); - this.onResourceSuccess(state); - return result; - } catch (error) { - this.onResourceFailure(state); - throw error; - } + async callResource( + type: string, + config: ResourceConfig, + ...args: T + ): Promise { + if (this.destroyed) { + throw new Error('ResourceManager has been destroyed'); } - /** + // 获取或创建资源类型状态 + let state = this.resourceTypes.get(type); + + if (!state) { + // 首次注册 + state = { + config: { + resourceFn: config.resourceFn as (...args: any[]) => Promise, + healthCheckFn: config.healthCheckFn as ((...args: any[]) => Promise) | undefined, + disableTime: config.disableTime ?? 30000, + maxRetries: config.maxRetries ?? 3, + healthCheckInterval: config.healthCheckInterval ?? 60000, + maxHealthCheckFailures: config.maxHealthCheckFailures ?? 20, + testArgs: config.testArgs as any[] | undefined, + }, + isEnabled: true, + disableUntil: 0, + currentRetries: 0, + healthCheckFailureCount: 0, + isPermanentlyDisabled: false, + lastHealthCheckTime: 0, + successCount: 0, + failureCount: 0, + }; + this.resourceTypes.set(type, state); + } + + // 在调用前检查是否需要进行健康检查 + await this.checkAndPerformHealthCheck(state); + + // 检查资源状态 + if (state.isPermanentlyDisabled) { + throw new Error(`Resource type '${type}' is permanently disabled (success: ${state.successCount}, failure: ${state.failureCount})`); + } + + if (!this.isResourceAvailable(type)) { + const disableUntilDate = new Date(state.disableUntil).toISOString(); + throw new Error(`Resource type '${type}' is currently disabled until ${disableUntilDate} (success: ${state.successCount}, failure: ${state.failureCount})`); + } + + // 调用资源 + try { + const result = await config.resourceFn(...args); + this.onResourceSuccess(state); + return result; + } catch (error) { + this.onResourceFailure(state); + throw error; + } + } + + /** * 检查资源类型是否可用 */ - isResourceAvailable(type: string): boolean { - const state = this.resourceTypes.get(type); - if (!state) { - return true; // 未注册的资源类型视为可用 - } - - if (state.isPermanentlyDisabled || !state.isEnabled) { - return false; - } - - return Date.now() >= state.disableUntil; + isResourceAvailable (type: string): boolean { + const state = this.resourceTypes.get(type); + if (!state) { + return true; // 未注册的资源类型视为可用 } - /** + if (state.isPermanentlyDisabled || !state.isEnabled) { + return false; + } + + return Date.now() >= state.disableUntil; + } + + /** * 获取资源类型统计信息 */ - getResourceStats(type: string): { successCount: number; failureCount: number; isEnabled: boolean; isPermanentlyDisabled: boolean } | null { - const state = this.resourceTypes.get(type); - if (!state) { - return null; - } - - return { - successCount: state.successCount, - failureCount: state.failureCount, - isEnabled: state.isEnabled, - isPermanentlyDisabled: state.isPermanentlyDisabled, - }; + getResourceStats (type: string): { successCount: number; failureCount: number; isEnabled: boolean; isPermanentlyDisabled: boolean } | null { + const state = this.resourceTypes.get(type); + if (!state) { + return null; } - /** + return { + successCount: state.successCount, + failureCount: state.failureCount, + isEnabled: state.isEnabled, + isPermanentlyDisabled: state.isPermanentlyDisabled, + }; + } + + /** * 获取所有资源类型统计 */ - getAllResourceStats(): Map { - const stats = new Map(); - for (const [type, state] of this.resourceTypes) { - stats.set(type, { - successCount: state.successCount, - failureCount: state.failureCount, - isEnabled: state.isEnabled, - isPermanentlyDisabled: state.isPermanentlyDisabled, - }); - } - return stats; + getAllResourceStats (): Map { + const stats = new Map(); + for (const [type, state] of this.resourceTypes) { + stats.set(type, { + successCount: state.successCount, + failureCount: state.failureCount, + isEnabled: state.isEnabled, + isPermanentlyDisabled: state.isPermanentlyDisabled, + }); } + return stats; + } - /** + /** * 注销资源类型 */ - unregister(type: string): boolean { - return this.resourceTypes.delete(type); - } + unregister (type: string): boolean { + return this.resourceTypes.delete(type); + } - /** + /** * 销毁管理器 */ - destroy(): void { - if (this.destroyed) { - return; - } - - this.resourceTypes.clear(); - this.destroyed = true; + destroy (): void { + if (this.destroyed) { + return; } - /** + this.resourceTypes.clear(); + this.destroyed = true; + } + + /** * 检查并执行健康检查(如果需要) */ - private async checkAndPerformHealthCheck(state: ResourceTypeState): Promise { - // 如果资源可用或已永久禁用,无需健康检查 - if (state.isEnabled && Date.now() >= state.disableUntil) { - return; - } - - if (state.isPermanentlyDisabled) { - return; - } - - const now = Date.now(); - - // 检查是否还在禁用期内 - if (now < state.disableUntil) { - return; - } - - // 检查是否需要进行健康检查(根据间隔时间) - if (now - state.lastHealthCheckTime < state.config.healthCheckInterval) { - return; - } - - // 执行健康检查 - await this.performHealthCheck(state); + private async checkAndPerformHealthCheck (state: ResourceTypeState): Promise { + // 如果资源可用或已永久禁用,无需健康检查 + if (state.isEnabled && Date.now() >= state.disableUntil) { + return; } - private async performHealthCheck(state: ResourceTypeState): Promise { - state.lastHealthCheckTime = Date.now(); - - try { - let healthCheckResult: boolean; - - if (state.config.healthCheckFn) { - const testArgs = state.config.testArgs || []; - healthCheckResult = await state.config.healthCheckFn(...testArgs); - } else { - const testArgs = state.config.testArgs || []; - await state.config.resourceFn(...testArgs); - healthCheckResult = true; - } - - if (healthCheckResult) { - // 健康检查成功,重新启用 - state.isEnabled = true; - state.disableUntil = 0; - state.currentRetries = 0; - state.healthCheckFailureCount = 0; - } else { - throw new Error('Health check function returned false'); - } - } catch { - // 健康检查失败,增加失败计数 - state.healthCheckFailureCount++; - - // 检查是否达到最大健康检查失败次数 - if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures) { - // 永久禁用资源 - state.isPermanentlyDisabled = true; - state.disableUntil = 0; - } else { - // 继续禁用一段时间 - state.disableUntil = Date.now() + state.config.disableTime; - } - } + if (state.isPermanentlyDisabled) { + return; } - private onResourceSuccess(state: ResourceTypeState): void { - state.currentRetries = 0; + const now = Date.now(); + + // 检查是否还在禁用期内 + if (now < state.disableUntil) { + return; + } + + // 检查是否需要进行健康检查(根据间隔时间) + if (now - state.lastHealthCheckTime < state.config.healthCheckInterval) { + return; + } + + // 执行健康检查 + await this.performHealthCheck(state); + } + + private async performHealthCheck (state: ResourceTypeState): Promise { + state.lastHealthCheckTime = Date.now(); + + try { + let healthCheckResult: boolean; + + if (state.config.healthCheckFn) { + const testArgs = state.config.testArgs || []; + healthCheckResult = await state.config.healthCheckFn(...testArgs); + } else { + const testArgs = state.config.testArgs || []; + await state.config.resourceFn(...testArgs); + healthCheckResult = true; + } + + if (healthCheckResult) { + // 健康检查成功,重新启用 + state.isEnabled = true; state.disableUntil = 0; + state.currentRetries = 0; state.healthCheckFailureCount = 0; - state.successCount++; - } + } else { + throw new Error('Health check function returned false'); + } + } catch { + // 健康检查失败,增加失败计数 + state.healthCheckFailureCount++; - private onResourceFailure(state: ResourceTypeState): void { - state.currentRetries++; - state.failureCount++; - - // 如果重试次数达到上限,禁用资源 - if (state.currentRetries >= state.config.maxRetries) { - state.disableUntil = Date.now() + state.config.disableTime; - state.currentRetries = 0; - } + // 检查是否达到最大健康检查失败次数 + if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures) { + // 永久禁用资源 + state.isPermanentlyDisabled = true; + state.disableUntil = 0; + } else { + // 继续禁用一段时间 + state.disableUntil = Date.now() + state.config.disableTime; + } } + } + + private onResourceSuccess (state: ResourceTypeState): void { + state.currentRetries = 0; + state.disableUntil = 0; + state.healthCheckFailureCount = 0; + state.successCount++; + } + + private onResourceFailure (state: ResourceTypeState): void { + state.currentRetries++; + state.failureCount++; + + // 如果重试次数达到上限,禁用资源 + if (state.currentRetries >= state.config.maxRetries) { + state.disableUntil = Date.now() + state.config.disableTime; + state.currentRetries = 0; + } + } } // 创建全局实例 export const resourceManager = new ResourceManager(); // 便捷函数 -export async function registerResource( - type: string, - config: ResourceConfig, - ...args: T +export async function registerResource ( + type: string, + config: ResourceConfig, + ...args: T ): Promise { - return resourceManager.callResource(type, config, ...args); -} \ No newline at end of file + return resourceManager.callResource(type, config, ...args); +} diff --git a/src/common/helper.ts b/src/common/helper.ts index 9c049059..cc9ad4c3 100644 --- a/src/common/helper.ts +++ b/src/common/helper.ts @@ -1,209 +1,208 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import path from 'node:path'; import fs from 'fs'; import os from 'node:os'; import { QQLevel } from '@/core'; import { QQVersionConfigType } from './types'; -export async function solveProblem any>(func: T, ...args: Parameters): Promise | undefined> { - return new Promise | undefined>((resolve) => { - try { - const result = func(...args); - resolve(result); - } catch { - resolve(undefined); +export async function solveProblem any> (func: T, ...args: Parameters): Promise | undefined> { + return new Promise | undefined>((resolve) => { + try { + const result = func(...args); + resolve(result); + } catch { + resolve(undefined); + } + }); +} + +export async function solveAsyncProblem Promise> (func: T, ...args: Parameters): Promise> | undefined> { + return new Promise> | undefined>((resolve) => { + func(...args).then((result) => { + resolve(result); + }).catch(() => { + resolve(undefined); + }); + }); +} + +export function sleep (ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function PromiseTimer (promise: Promise, ms: number): Promise { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms) + ); + return Promise.race([promise, timeoutPromise]); +} + +export async function runAllWithTimeout (tasks: Promise[], timeout: number): Promise { + const wrappedTasks = tasks.map((task) => + PromiseTimer(task, timeout).then( + (result) => ({ status: 'fulfilled', value: result }), + (error) => ({ status: 'rejected', reason: error }) + ) + ); + const results = await Promise.all(wrappedTasks); + return results + .filter((result) => result.status === 'fulfilled') + .map((result) => (result as { status: 'fulfilled'; value: T }).value); +} + +export function isNull (value: any) { + return value === undefined || value === null; +} + +export function isNumeric (str: string) { + return /^\d+$/.test(str); +} + +export function truncateString (obj: any, maxLength = 500) { + if (obj !== null && typeof obj === 'object') { + Object.keys(obj).forEach((key) => { + if (typeof obj[key] === 'string') { + // 如果是字符串且超过指定长度,则截断 + if (obj[key].length > maxLength) { + obj[key] = obj[key].substring(0, maxLength) + '...'; } + } else if (typeof obj[key] === 'object') { + // 如果是对象或数组,则递归调用 + truncateString(obj[key], maxLength); + } }); + } + return obj; } -export async function solveAsyncProblem Promise>(func: T, ...args: Parameters): Promise> | undefined> { - return new Promise> | undefined>((resolve) => { - func(...args).then((result) => { - resolve(result); - }).catch(() => { - resolve(undefined); - }); - }); +export function isEqual (obj1: any, obj2: any) { + if (obj1 === obj2) return true; + if (obj1 == null || obj2 == null) return false; + if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2; + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + for (const key of keys1) { + if (!isEqual(obj1[key], obj2[key])) return false; + } + return true; } -export function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export function PromiseTimer(promise: Promise, ms: number): Promise { - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms), - ); - return Promise.race([promise, timeoutPromise]); -} - -export async function runAllWithTimeout(tasks: Promise[], timeout: number): Promise { - const wrappedTasks = tasks.map((task) => - PromiseTimer(task, timeout).then( - (result) => ({ status: 'fulfilled', value: result }), - (error) => ({ status: 'rejected', reason: error }), - ), - ); - const results = await Promise.all(wrappedTasks); - return results - .filter((result) => result.status === 'fulfilled') - .map((result) => (result as { status: 'fulfilled'; value: T }).value); -} - -export function isNull(value: any) { - return value === undefined || value === null; -} - -export function isNumeric(str: string) { - return /^\d+$/.test(str); -} - -export function truncateString(obj: any, maxLength = 500) { - if (obj !== null && typeof obj === 'object') { - Object.keys(obj).forEach((key) => { - if (typeof obj[key] === 'string') { - // 如果是字符串且超过指定长度,则截断 - if (obj[key].length > maxLength) { - obj[key] = obj[key].substring(0, maxLength) + '...'; - } - } else if (typeof obj[key] === 'object') { - // 如果是对象或数组,则递归调用 - truncateString(obj[key], maxLength); - } - }); - } - return obj; -} - -export function isEqual(obj1: any, obj2: any) { - if (obj1 === obj2) return true; - if (obj1 == null || obj2 == null) return false; - if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2; - - const keys1 = Object.keys(obj1); - const keys2 = Object.keys(obj2); - - if (keys1.length !== keys2.length) return false; - - for (const key of keys1) { - if (!isEqual(obj1[key], obj2[key])) return false; - } - return true; -} - -export function getDefaultQQVersionConfigInfo(): QQVersionConfigType { - if (os.platform() === 'linux') { - return { - baseVersion: '3.2.12.28060', - curVersion: '3.2.12.28060', - prevVersion: '', - onErrorVersions: [], - buildId: '27254', - }; - } - if (os.platform() === 'darwin') { - return { - baseVersion: '6.9.53.28060', - curVersion: '6.9.53.28060', - prevVersion: '', - onErrorVersions: [], - buildId: '28060', - }; - } +export function getDefaultQQVersionConfigInfo (): QQVersionConfigType { + if (os.platform() === 'linux') { return { - baseVersion: '9.9.15-28131', - curVersion: '9.9.15-28131', - prevVersion: '', - onErrorVersions: [], - buildId: '28131', + baseVersion: '3.2.12.28060', + curVersion: '3.2.12.28060', + prevVersion: '', + onErrorVersions: [], + buildId: '27254', }; + } + if (os.platform() === 'darwin') { + return { + baseVersion: '6.9.53.28060', + curVersion: '6.9.53.28060', + prevVersion: '', + onErrorVersions: [], + buildId: '28060', + }; + } + return { + baseVersion: '9.9.15-28131', + curVersion: '9.9.15-28131', + prevVersion: '', + onErrorVersions: [], + buildId: '28131', + }; } -export function getQQPackageInfoPath(exePath: string = '', version?: string): string { - let packagePath; - if (os.platform() === 'darwin') { - packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json'); - } else if (os.platform() === 'linux') { - packagePath = path.join(path.dirname(exePath), './resources/app/package.json'); - } else { - packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json'); - } - //下面是老版本兼容 未来去掉 - if (!fs.existsSync(packagePath)) { - packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json'); - } - return packagePath; +export function getQQPackageInfoPath (exePath: string = '', version?: string): string { + let packagePath; + if (os.platform() === 'darwin') { + packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json'); + } else if (os.platform() === 'linux') { + packagePath = path.join(path.dirname(exePath), './resources/app/package.json'); + } else { + packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json'); + } + // 下面是老版本兼容 未来去掉 + if (!fs.existsSync(packagePath)) { + packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json'); + } + return packagePath; } -export function getQQVersionConfigPath(exePath: string = ''): string | undefined { - let configVersionInfoPath; - if (os.platform() === 'win32') { - configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json'); - } else if (os.platform() === 'darwin') { - const userPath = os.homedir(); - const appDataPath = path.resolve(userPath, './Library/Application Support/QQ'); - configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); - } else { - const userPath = os.homedir(); - const appDataPath = path.resolve(userPath, './.config/QQ'); - configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); - } - if (typeof configVersionInfoPath !== 'string') { - return undefined; - } - //老版本兼容 未来去掉 - if (!fs.existsSync(configVersionInfoPath)) { - configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json'); - } - if (!fs.existsSync(configVersionInfoPath)) { - return undefined; - } - return configVersionInfoPath; -} - -export function calcQQLevel(level?: QQLevel) { - if (!level) return 0; - //const { penguinNum, crownNum, sunNum, moonNum, starNum } = level; - const { crownNum, sunNum, moonNum, starNum } = level - //没补类型 - return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; -} - -export function stringifyWithBigInt(obj: any) { - return JSON.stringify(obj, (_key, value) => - typeof value === 'bigint' ? value.toString() : value - ); -} - -export function parseAppidFromMajor(nodeMajor: string): string | undefined { - const hexSequence = 'A4 09 00 00 00 35'; - const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ''), 'hex'); - const filePath = path.resolve(nodeMajor); - const fileContent = fs.readFileSync(filePath); - - let searchPosition = 0; - while (true) { - const index = fileContent.indexOf(sequenceBytes, searchPosition); - if (index === -1) { - break; - } - - const start = index + sequenceBytes.length - 1; - const end = fileContent.indexOf(0x00, start); - if (end === -1) { - break; - } - const content = fileContent.subarray(start, end); - if (!content.every(byte => byte === 0x00)) { - try { - return content.toString('utf-8'); - } catch { - break; - } - } - - searchPosition = end + 1; - } - +export function getQQVersionConfigPath (exePath: string = ''): string | undefined { + let configVersionInfoPath; + if (os.platform() === 'win32') { + configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json'); + } else if (os.platform() === 'darwin') { + const userPath = os.homedir(); + const appDataPath = path.resolve(userPath, './Library/Application Support/QQ'); + configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); + } else { + const userPath = os.homedir(); + const appDataPath = path.resolve(userPath, './.config/QQ'); + configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); + } + if (typeof configVersionInfoPath !== 'string') { return undefined; + } + // 老版本兼容 未来去掉 + if (!fs.existsSync(configVersionInfoPath)) { + configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json'); + } + if (!fs.existsSync(configVersionInfoPath)) { + return undefined; + } + return configVersionInfoPath; +} + +export function calcQQLevel (level?: QQLevel) { + if (!level) return 0; + // const { penguinNum, crownNum, sunNum, moonNum, starNum } = level; + const { crownNum, sunNum, moonNum, starNum } = level; + // 没补类型 + return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; +} + +export function stringifyWithBigInt (obj: any) { + return JSON.stringify(obj, (_key, value) => + typeof value === 'bigint' ? value.toString() : value + ); +} + +export function parseAppidFromMajor (nodeMajor: string): string | undefined { + const hexSequence = 'A4 09 00 00 00 35'; + const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ''), 'hex'); + const filePath = path.resolve(nodeMajor); + const fileContent = fs.readFileSync(filePath); + + let searchPosition = 0; + while (true) { + const index = fileContent.indexOf(sequenceBytes, searchPosition); + if (index === -1) { + break; + } + + const start = index + sequenceBytes.length - 1; + const end = fileContent.indexOf(0x00, start); + if (end === -1) { + break; + } + const content = fileContent.subarray(start, end); + if (!content.every(byte => byte === 0x00)) { + try { + return content.toString('utf-8'); + } catch { + break; + } + } + + searchPosition = end + 1; + } + + return undefined; } diff --git a/src/common/log.ts b/src/common/log.ts index 441179d0..95389f25 100644 --- a/src/common/log.ts +++ b/src/common/log.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import winston, { format, transports } from 'winston'; import { truncateString } from '@/common/helper'; import path from 'node:path'; @@ -6,314 +5,316 @@ import fs from 'node:fs/promises'; import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core'; import EventEmitter from 'node:events'; export enum LogLevel { - DEBUG = 'debug', - INFO = 'info', - WARN = 'warn', - ERROR = 'error', - FATAL = 'fatal', + DEBUG = 'debug', + INFO = 'info', + WARN = 'warn', + ERROR = 'error', + FATAL = 'fatal', } -function getFormattedTimestamp() { - const now = new Date(); - const year = now.getFullYear(); - const month = (now.getMonth() + 1).toString().padStart(2, '0'); - const day = now.getDate().toString().padStart(2, '0'); - const hours = now.getHours().toString().padStart(2, '0'); - const minutes = now.getMinutes().toString().padStart(2, '0'); - const seconds = now.getSeconds().toString().padStart(2, '0'); - const milliseconds = now.getMilliseconds().toString().padStart(3, '0'); - return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`; +function getFormattedTimestamp () { + const now = new Date(); + const year = now.getFullYear(); + const month = (now.getMonth() + 1).toString().padStart(2, '0'); + const day = now.getDate().toString().padStart(2, '0'); + const hours = now.getHours().toString().padStart(2, '0'); + const minutes = now.getMinutes().toString().padStart(2, '0'); + const seconds = now.getSeconds().toString().padStart(2, '0'); + const milliseconds = now.getMilliseconds().toString().padStart(3, '0'); + return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`; } const logEmitter = new EventEmitter(); export type LogListener = (msg: string) => void; class Subscription { - public static MAX_HISTORY = 100; - public static history: string[] = []; + public static MAX_HISTORY = 100; + public static history: string[] = []; - subscribe(listener: LogListener) { - for (const history of Subscription.history) { - try { - listener(history); - } catch { - // ignore - } - } - logEmitter.on('log', listener); + subscribe (listener: LogListener) { + for (const history of Subscription.history) { + try { + listener(history); + } catch { + // ignore + } } - unsubscribe(listener: LogListener) { - logEmitter.off('log', listener); - } - notify(msg: string) { - logEmitter.emit('log', msg); - if (Subscription.history.length >= Subscription.MAX_HISTORY) { - Subscription.history.shift(); - } - Subscription.history.push(msg); + logEmitter.on('log', listener); + } + + unsubscribe (listener: LogListener) { + logEmitter.off('log', listener); + } + + notify (msg: string) { + logEmitter.emit('log', msg); + if (Subscription.history.length >= Subscription.MAX_HISTORY) { + Subscription.history.shift(); } + Subscription.history.push(msg); + } } export const logSubscription = new Subscription(); export class LogWrapper { - fileLogEnabled = true; - consoleLogEnabled = true; - logger: winston.Logger; + fileLogEnabled = true; + consoleLogEnabled = true; + logger: winston.Logger; - constructor(logDir: string) { - const filename = `${getFormattedTimestamp()}.log`; - const logPath = path.join(logDir, filename); + constructor (logDir: string) { + const filename = `${getFormattedTimestamp()}.log`; + const logPath = path.join(logDir, filename); - this.logger = winston.createLogger({ - level: 'debug', - format: format.combine( - format.timestamp({ format: 'MM-DD HH:mm:ss' }), - format.printf(({ timestamp, level, message, ...meta }) => { - const userInfo = meta['userInfo'] ? `${meta['userInfo']} | ` : ''; - return `${timestamp} [${level}] ${userInfo}${message}`; - }) - ), - transports: [ - new transports.File({ - filename: logPath, - level: 'debug', - maxsize: 5 * 1024 * 1024, // 5MB - maxFiles: 5, - }), - new transports.Console({ - format: format.combine( - format.colorize(), - format.printf(({ timestamp, level, message, ...meta }) => { - const userInfo = meta['userInfo'] ? `${meta['userInfo']} | ` : ''; - return `${timestamp} [${level}] ${userInfo}${message}`; - }) - ), - }), - ], - }); - - this.setLogSelfInfo({ nick: '', uid: '' }); - this.cleanOldLogs(logDir); - } - - cleanOldLogs(logDir: string) { - const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; - fs.readdir(logDir).then((files) => { - files.forEach((file) => { - const filePath = path.join(logDir, file); - this.deleteOldLogFile(filePath, oneWeekAgo); - }); - }).catch((err) => { - this.logger.error('Failed to read log directory', err); - }); - } - - private deleteOldLogFile(filePath: string, oneWeekAgo: number) { - fs.stat(filePath).then((stats) => { - if (stats.mtime.getTime() < oneWeekAgo) { - fs.unlink(filePath).catch((err) => { - if (err) { - if (err.code === 'ENOENT') { - this.logger.warn(`File already deleted: ${filePath}`); - } else { - this.logger.error('Failed to delete old log file', err); - } - } else { - this.logger.info(`Deleted old log file: ${filePath}`); - } - }); - } - }).catch((err) => { - this.logger.error('Failed to get file stats', err); - }); - } - - setFileAndConsoleLogLevel(fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { - this.logger.transports.forEach((transport) => { - if (transport instanceof transports.File) { - transport.level = fileLogLevel; - } else if (transport instanceof transports.Console) { - transport.level = consoleLogLevel; - } - }); - } - - setLogSelfInfo(selfInfo: { nick: string; uid: string }) { - const userInfo = `${selfInfo.nick}`; - this.logger.defaultMeta = { userInfo }; - } - - setFileLogEnabled(isEnabled: boolean) { - this.fileLogEnabled = isEnabled; - this.logger.transports.forEach((transport) => { - if (transport instanceof transports.File) { - transport.silent = !isEnabled; - } - }); - } - - setConsoleLogEnabled(isEnabled: boolean) { - this.consoleLogEnabled = isEnabled; - this.logger.transports.forEach((transport) => { - if (transport instanceof transports.Console) { - transport.silent = !isEnabled; - } - }); - } - - formatMsg(msg: any[]) { - return msg - .map((msgItem) => { - if (msgItem instanceof Error) { - return msgItem.stack; - } else if (typeof msgItem === 'object') { - return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2)))); - } - return msgItem; + this.logger = winston.createLogger({ + level: 'debug', + format: format.combine( + format.timestamp({ format: 'MM-DD HH:mm:ss' }), + format.printf(({ timestamp, level, message, ...meta }) => { + const userInfo = meta['userInfo'] ? `${meta['userInfo']} | ` : ''; + return `${timestamp} [${level}] ${userInfo}${message}`; + }) + ), + transports: [ + new transports.File({ + filename: logPath, + level: 'debug', + maxsize: 5 * 1024 * 1024, // 5MB + maxFiles: 5, + }), + new transports.Console({ + format: format.combine( + format.colorize(), + format.printf(({ timestamp, level, message, ...meta }) => { + const userInfo = meta['userInfo'] ? `${meta['userInfo']} | ` : ''; + return `${timestamp} [${level}] ${userInfo}${message}`; }) - .join(' '); - } + ), + }), + ], + }); - _log(level: LogLevel, ...args: any[]) { - const message = this.formatMsg(args); - if (this.consoleLogEnabled && this.fileLogEnabled) { - this.logger.log(level, message); - } else if (this.consoleLogEnabled) { - this.logger.log(level, message); - } else if (this.fileLogEnabled) { - // eslint-disable-next-line no-control-regex - this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, '')); + this.setLogSelfInfo({ nick: '', uid: '' }); + this.cleanOldLogs(logDir); + } + + cleanOldLogs (logDir: string) { + const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; + fs.readdir(logDir).then((files) => { + files.forEach((file) => { + const filePath = path.join(logDir, file); + this.deleteOldLogFile(filePath, oneWeekAgo); + }); + }).catch((err) => { + this.logger.error('Failed to read log directory', err); + }); + } + + private deleteOldLogFile (filePath: string, oneWeekAgo: number) { + fs.stat(filePath).then((stats) => { + if (stats.mtime.getTime() < oneWeekAgo) { + fs.unlink(filePath).catch((err) => { + if (err) { + if (err.code === 'ENOENT') { + this.logger.warn(`File already deleted: ${filePath}`); + } else { + this.logger.error('Failed to delete old log file', err); + } + } else { + this.logger.info(`Deleted old log file: ${filePath}`); + } + }); + } + }).catch((err) => { + this.logger.error('Failed to get file stats', err); + }); + } + + setFileAndConsoleLogLevel (fileLogLevel: LogLevel, consoleLogLevel: LogLevel) { + this.logger.transports.forEach((transport) => { + if (transport instanceof transports.File) { + transport.level = fileLogLevel; + } else if (transport instanceof transports.Console) { + transport.level = consoleLogLevel; + } + }); + } + + setLogSelfInfo (selfInfo: { nick: string; uid: string }) { + const userInfo = `${selfInfo.nick}`; + this.logger.defaultMeta = { userInfo }; + } + + setFileLogEnabled (isEnabled: boolean) { + this.fileLogEnabled = isEnabled; + this.logger.transports.forEach((transport) => { + if (transport instanceof transports.File) { + transport.silent = !isEnabled; + } + }); + } + + setConsoleLogEnabled (isEnabled: boolean) { + this.consoleLogEnabled = isEnabled; + this.logger.transports.forEach((transport) => { + if (transport instanceof transports.Console) { + transport.silent = !isEnabled; + } + }); + } + + formatMsg (msg: any[]) { + return msg + .map((msgItem) => { + if (msgItem instanceof Error) { + return msgItem.stack; + } else if (typeof msgItem === 'object') { + return JSON.stringify(truncateString(JSON.parse(JSON.stringify(msgItem, null, 2)))); } - logSubscription.notify(JSON.stringify({ level, message })); + return msgItem; + }) + .join(' '); + } + + _log (level: LogLevel, ...args: any[]) { + const message = this.formatMsg(args); + if (this.consoleLogEnabled && this.fileLogEnabled) { + this.logger.log(level, message); + } else if (this.consoleLogEnabled) { + this.logger.log(level, message); + } else if (this.fileLogEnabled) { + // eslint-disable-next-line no-control-regex + this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, '')); + } + logSubscription.notify(JSON.stringify({ level, message })); + } + + log (...args: any[]) { + this._log(LogLevel.INFO, ...args); + } + + logDebug (...args: any[]) { + this._log(LogLevel.DEBUG, ...args); + } + + logError (...args: any[]) { + this._log(LogLevel.ERROR, ...args); + } + + logWarn (...args: any[]) { + this._log(LogLevel.WARN, ...args); + } + + logFatal (...args: any[]) { + this._log(LogLevel.FATAL, ...args); + } + + logMessage (msg: RawMessage, selfInfo: SelfInfo) { + const isSelfSent = msg.senderUin === selfInfo.uin; + + if (msg.elements[0]?.elementType === ElementType.GreyTip) { + return; } - log(...args: any[]) { - this._log(LogLevel.INFO, ...args); - } - - logDebug(...args: any[]) { - this._log(LogLevel.DEBUG, ...args); - } - - logError(...args: any[]) { - this._log(LogLevel.ERROR, ...args); - } - - logWarn(...args: any[]) { - this._log(LogLevel.WARN, ...args); - } - - logFatal(...args: any[]) { - this._log(LogLevel.FATAL, ...args); - } - - logMessage(msg: RawMessage, selfInfo: SelfInfo) { - const isSelfSent = msg.senderUin === selfInfo.uin; - - if (msg.elements[0]?.elementType === ElementType.GreyTip) { - return; - } - - this.log(`${isSelfSent ? '发送 ->' : '接收 <-'} ${rawMessageToText(msg)}`); - } + this.log(`${isSelfSent ? '发送 ->' : '接收 <-'} ${rawMessageToText(msg)}`); + } } -export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { - if (recursiveLevel > 2) { - return '...'; +export function rawMessageToText (msg: RawMessage, recursiveLevel = 0): string { + if (recursiveLevel > 2) { + return '...'; + } + + const tokens: string[] = []; + + if (msg.chatType == ChatType.KCHATTYPEC2C) { + tokens.push(`私聊 (${msg.peerUin})`); + } else if (msg.chatType == ChatType.KCHATTYPEGROUP) { + if (recursiveLevel < 1) { + tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`); } - - const tokens: string[] = []; - - if (msg.chatType == ChatType.KCHATTYPEC2C) { - tokens.push(`私聊 (${msg.peerUin})`); - } else if (msg.chatType == ChatType.KCHATTYPEGROUP) { - if (recursiveLevel < 1) { - tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`); - } - if (msg.senderUin !== '0') { - tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`); - } - } else if (msg.chatType == ChatType.KCHATTYPEDATALINE) { - tokens.push('移动设备'); - } else { - tokens.push(`临时消息 (${msg.peerUin})`); + if (msg.senderUin !== '0') { + tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`); } + } else if (msg.chatType == ChatType.KCHATTYPEDATALINE) { + tokens.push('移动设备'); + } else { + tokens.push(`临时消息 (${msg.peerUin})`); + } - for (const element of msg.elements) { - tokens.push(msgElementToText(element, msg, recursiveLevel)); - } + for (const element of msg.elements) { + tokens.push(msgElementToText(element, msg, recursiveLevel)); + } - return tokens.join(' '); + return tokens.join(' '); } -function msgElementToText(element: MessageElement, msg: RawMessage, recursiveLevel: number): string { - if (element.textElement) { - return textElementToText(element.textElement); - } +function msgElementToText (element: MessageElement, msg: RawMessage, recursiveLevel: number): string { + if (element.textElement) { + return textElementToText(element.textElement); + } - if (element.replyElement) { - return replyElementToText(element.replyElement, msg, recursiveLevel); - } + if (element.replyElement) { + return replyElementToText(element.replyElement, msg, recursiveLevel); + } - if (element.picElement) { - return '[图片]'; - } + if (element.picElement) { + return '[图片]'; + } - if (element.fileElement) { - return `[文件 ${element.fileElement.fileName}]`; - } + if (element.fileElement) { + return `[文件 ${element.fileElement.fileName}]`; + } - if (element.videoElement) { - return '[视频]'; - } + if (element.videoElement) { + return '[视频]'; + } - if (element.pttElement) { - return `[语音 ${element.pttElement.duration}s]`; - } + if (element.pttElement) { + return `[语音 ${element.pttElement.duration}s]`; + } - if (element.arkElement) { - return '[卡片消息]'; - } + if (element.arkElement) { + return '[卡片消息]'; + } - if (element.faceElement) { - return `[表情 ${element.faceElement.faceText ?? ''}]`; - } + if (element.faceElement) { + return `[表情 ${element.faceElement.faceText ?? ''}]`; + } - if (element.marketFaceElement) { - return element.marketFaceElement.faceName; - } + if (element.marketFaceElement) { + return element.marketFaceElement.faceName; + } - if (element.markdownElement) { - return '[Markdown 消息]'; - } + if (element.markdownElement) { + return '[Markdown 消息]'; + } - if (element.multiForwardMsgElement) { - return '[转发消息]'; - } + if (element.multiForwardMsgElement) { + return '[转发消息]'; + } - if (element.elementType === ElementType.GreyTip) { - return '[灰条消息]'; - } + if (element.elementType === ElementType.GreyTip) { + return '[灰条消息]'; + } - return `[未实现 (ElementType = ${element.elementType})]`; + return `[未实现 (ElementType = ${element.elementType})]`; } -function textElementToText(textElement: any): string { - if (textElement.atType === NTMsgAtType.ATTYPEUNKNOWN) { - const originalContentLines = textElement.content.split('\n'); - return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`; - } else if (textElement.atType === NTMsgAtType.ATTYPEALL) { - return '@全体成员'; - } else if (textElement.atType === NTMsgAtType.ATTYPEONE) { - return `${textElement.content} (${textElement.atUid})`; - } - return ''; +function textElementToText (textElement: any): string { + if (textElement.atType === NTMsgAtType.ATTYPEUNKNOWN) { + const originalContentLines = textElement.content.split('\n'); + return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`; + } else if (textElement.atType === NTMsgAtType.ATTYPEALL) { + return '@全体成员'; + } else if (textElement.atType === NTMsgAtType.ATTYPEONE) { + return `${textElement.content} (${textElement.atUid})`; + } + return ''; } -function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string { - const recordMsgOrNull = msg.records.find((record) => replyElement.sourceMsgIdInRecords === record.msgId); - return `[回复消息 ${recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020' +function replyElementToText (replyElement: any, msg: RawMessage, recursiveLevel: number): string { + const recordMsgOrNull = msg.records.find((record) => replyElement.sourceMsgIdInRecords === record.msgId); + return `[回复消息 ${recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020' ? rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})` }]`; diff --git a/src/common/lru-cache.ts b/src/common/lru-cache.ts index ba2cef1d..30520a96 100644 --- a/src/common/lru-cache.ts +++ b/src/common/lru-cache.ts @@ -1,42 +1,43 @@ export class LRUCache { - private capacity: number; - public cache: Map; + private capacity: number; + public cache: Map; - constructor(capacity: number) { - this.capacity = capacity; - this.cache = new Map(); - } + constructor (capacity: number) { + this.capacity = capacity; + this.cache = new Map(); + } - public get(key: K): V | undefined { - const value = this.cache.get(key); - if (value !== undefined) { - // Move the accessed key to the end to mark it as most recently used - this.cache.delete(key); - this.cache.set(key, value); - } - return value; + public get (key: K): V | undefined { + const value = this.cache.get(key); + if (value !== undefined) { + // Move the accessed key to the end to mark it as most recently used + this.cache.delete(key); + this.cache.set(key, value); } + return value; + } - public put(key: K, value: V): void { - if (this.cache.has(key)) { - // If the key already exists, move it to the end to mark it as most recently used - this.cache.delete(key); - } else if (this.cache.size >= this.capacity) { - // If the cache is full, remove the least recently used key (the first one in the map) - const firstKey = this.cache.keys().next().value; - if (firstKey !== undefined) { - this.cache.delete(firstKey); - } - } - this.cache.set(key, value); + public put (key: K, value: V): void { + if (this.cache.has(key)) { + // If the key already exists, move it to the end to mark it as most recently used + this.cache.delete(key); + } else if (this.cache.size >= this.capacity) { + // If the cache is full, remove the least recently used key (the first one in the map) + const firstKey = this.cache.keys().next().value; + if (firstKey !== undefined) { + this.cache.delete(firstKey); + } } - public resetCapacity(newCapacity: number): void { - this.capacity = newCapacity; - while (this.cache.size > this.capacity) { - const firstKey = this.cache.keys().next().value; - if (firstKey !== undefined) { - this.cache.delete(firstKey); - } - } + this.cache.set(key, value); + } + + public resetCapacity (newCapacity: number): void { + this.capacity = newCapacity; + while (this.cache.size > this.capacity) { + const firstKey = this.cache.keys().next().value; + if (firstKey !== undefined) { + this.cache.delete(firstKey); + } } -} \ No newline at end of file + } +} diff --git a/src/common/message-unique.ts b/src/common/message-unique.ts index 3f6785f2..9708c0bc 100644 --- a/src/common/message-unique.ts +++ b/src/common/message-unique.ts @@ -2,142 +2,141 @@ import { Peer } from '@/core'; import crypto from 'crypto'; export class LimitedHashTable { - private readonly keyToValue: Map = new Map(); - private readonly valueToKey: Map = new Map(); - private maxSize: number; + private readonly keyToValue: Map = new Map(); + private readonly valueToKey: Map = new Map(); + private maxSize: number; - constructor(maxSize: number) { - this.maxSize = maxSize; + constructor (maxSize: number) { + this.maxSize = maxSize; + } + + resize (count: number) { + this.maxSize = count; + } + + set (key: K, value: V): void { + this.keyToValue.set(key, value); + this.valueToKey.set(value, key); + while (this.keyToValue.size !== this.valueToKey.size) { + this.keyToValue.clear(); + this.valueToKey.clear(); } - - resize(count: number) { - this.maxSize = count; + while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) { + const oldestKey = this.keyToValue.keys().next().value; + if (oldestKey !== undefined) { + this.valueToKey.delete(this.keyToValue.get(oldestKey) as V); + this.keyToValue.delete(oldestKey); + } } + } - set(key: K, value: V): void { - this.keyToValue.set(key, value); - this.valueToKey.set(value, key); - while (this.keyToValue.size !== this.valueToKey.size) { - this.keyToValue.clear(); - this.valueToKey.clear(); - } - while (this.keyToValue.size > this.maxSize || this.valueToKey.size > this.maxSize) { - const oldestKey = this.keyToValue.keys().next().value; - if (oldestKey !== undefined) { - this.valueToKey.delete(this.keyToValue.get(oldestKey) as V); - this.keyToValue.delete(oldestKey); - } - } + getValue (key: K): V | undefined { + return this.keyToValue.get(key); + } + + getKey (value: V): K | undefined { + return this.valueToKey.get(value); + } + + deleteByValue (value: V): void { + const key = this.valueToKey.get(value); + if (key !== undefined) { + this.keyToValue.delete(key); + this.valueToKey.delete(value); } + } - getValue(key: K): V | undefined { - return this.keyToValue.get(key); + deleteByKey (key: K): void { + const value = this.keyToValue.get(key); + if (value !== undefined) { + this.keyToValue.delete(key); + this.valueToKey.delete(value); } + } - getKey(value: V): K | undefined { - return this.valueToKey.get(value); + getKeyList (): K[] { + return Array.from(this.keyToValue.keys()); + } + + // 获取最近刚写入的几个值 + getHeads (size: number): { key: K; value: V }[] | undefined { + const keyList = this.getKeyList(); + if (keyList.length === 0) { + return undefined; } - - deleteByValue(value: V): void { - const key = this.valueToKey.get(value); - if (key !== undefined) { - this.keyToValue.delete(key); - this.valueToKey.delete(value); - } - } - - deleteByKey(key: K): void { - const value = this.keyToValue.get(key); - if (value !== undefined) { - this.keyToValue.delete(key); - this.valueToKey.delete(value); - } - } - - getKeyList(): K[] { - return Array.from(this.keyToValue.keys()); - } - - //获取最近刚写入的几个值 - getHeads(size: number): { key: K; value: V }[] | undefined { - const keyList = this.getKeyList(); - if (keyList.length === 0) { - return undefined; - } - const result: { key: K; value: 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; + const result: { key: K; value: 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; + } } class MessageUniqueWrapper { - private readonly msgDataMap: LimitedHashTable; - private readonly msgIdMap: LimitedHashTable; + private readonly msgDataMap: LimitedHashTable; + private readonly msgIdMap: LimitedHashTable; - constructor(maxMap: number = 5000) { - this.msgIdMap = new LimitedHashTable(maxMap); - this.msgDataMap = new LimitedHashTable(maxMap); - } + constructor (maxMap: number = 5000) { + this.msgIdMap = new LimitedHashTable(maxMap); + this.msgDataMap = new LimitedHashTable(maxMap); + } - getRecentMsgIds(Peer: Peer, size: number): string[] { - const heads = this.msgIdMap.getHeads(size); - if (!heads) { - return []; - } - const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value)); - const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid); - return ret.map((t) => t?.MsgId).filter((t) => t !== undefined); + getRecentMsgIds (Peer: Peer, size: number): string[] { + const heads = this.msgIdMap.getHeads(size); + if (!heads) { + return []; } + const data = heads.map((t) => MessageUnique.getMsgIdAndPeerByShortId(t.value)); + const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid); + return ret.map((t) => t?.MsgId).filter((t) => t !== undefined); + } - 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); - this.msgDataMap.set(key, shortId); - return shortId; + 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); + this.msgDataMap.set(key, shortId); + return shortId; + } - getMsgIdAndPeerByShortId(shortId: number): { MsgId: string; Peer: Peer } | undefined { - const data = this.msgDataMap.getKey(shortId); - if (data) { - const [msgId, chatTypeStr, peerUid] = data.split('|'); - const peer: Peer = { - chatType: parseInt(chatTypeStr ?? '0'), - peerUid: peerUid ?? '', - guildId: '', - }; - return { MsgId: msgId ?? '0', Peer: peer }; - } - return undefined; + getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer } | undefined { + const data = this.msgDataMap.getKey(shortId); + if (data) { + const [msgId, chatTypeStr, peerUid] = data.split('|'); + const peer: Peer = { + chatType: parseInt(chatTypeStr ?? '0'), + peerUid: peerUid ?? '', + guildId: '', + }; + return { MsgId: msgId ?? '0', Peer: peer }; } + return undefined; + } - getShortIdByMsgId(msgId: string): number | undefined { - return this.msgIdMap.getValue(msgId); - } + getShortIdByMsgId (msgId: string): number | undefined { + return this.msgIdMap.getValue(msgId); + } - getPeerByMsgId(msgId: string) { - const shortId = this.msgIdMap.getValue(msgId); - if (!shortId) return undefined; - return this.getMsgIdAndPeerByShortId(shortId); - } + getPeerByMsgId (msgId: string) { + const shortId = this.msgIdMap.getValue(msgId); + if (!shortId) return undefined; + return this.getMsgIdAndPeerByShortId(shortId); + } - resize(maxSize: number): void { - this.msgIdMap.resize(maxSize); - this.msgDataMap.resize(maxSize); - } + resize (maxSize: number): void { + this.msgIdMap.resize(maxSize); + this.msgDataMap.resize(maxSize); + } } export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper(); diff --git a/src/common/path.ts b/src/common/path.ts index 79ffb2c3..cf9c2111 100644 --- a/src/common/path.ts +++ b/src/common/path.ts @@ -4,38 +4,38 @@ import fs from 'fs'; import os from 'os'; export class NapCatPathWrapper { - binaryPath: string; - logsPath: string; - configPath: string; - cachePath: string; - staticPath: string; - pluginPath: string; + binaryPath: string; + logsPath: string; + configPath: string; + cachePath: string; + staticPath: string; + pluginPath: string; - constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) { - this.binaryPath = mainPath; - let writePath: string; + constructor (mainPath: string = dirname(fileURLToPath(import.meta.url))) { + this.binaryPath = mainPath; + let writePath: string; - if (process.env['NAPCAT_WORKDIR']) { - writePath = process.env['NAPCAT_WORKDIR']; - } else if (os.platform() === 'darwin') { - writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat'); - } else { - writePath = this.binaryPath; - } - - this.logsPath = path.join(writePath, 'logs'); - this.configPath = path.join(writePath, 'config'); - this.pluginPath = path.join(writePath, 'plugins');//dynamic load - this.cachePath = path.join(writePath, 'cache'); - this.staticPath = path.join(this.binaryPath, 'static'); - if (!fs.existsSync(this.logsPath)) { - fs.mkdirSync(this.logsPath, { recursive: true }); - } - if (!fs.existsSync(this.configPath)) { - fs.mkdirSync(this.configPath, { recursive: true }); - } - if (!fs.existsSync(this.cachePath)) { - fs.mkdirSync(this.cachePath, { recursive: true }); - } + if (process.env['NAPCAT_WORKDIR']) { + writePath = process.env['NAPCAT_WORKDIR']; + } else if (os.platform() === 'darwin') { + writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat'); + } else { + writePath = this.binaryPath; } + + this.logsPath = path.join(writePath, 'logs'); + this.configPath = path.join(writePath, 'config'); + this.pluginPath = path.join(writePath, 'plugins');// dynamic load + this.cachePath = path.join(writePath, 'cache'); + this.staticPath = path.join(this.binaryPath, 'static'); + if (!fs.existsSync(this.logsPath)) { + fs.mkdirSync(this.logsPath, { recursive: true }); + } + if (!fs.existsSync(this.configPath)) { + fs.mkdirSync(this.configPath, { recursive: true }); + } + if (!fs.existsSync(this.cachePath)) { + fs.mkdirSync(this.cachePath, { recursive: true }); + } + } } diff --git a/src/common/performance-monitor.ts b/src/common/performance-monitor.ts index 7d818033..051c23ff 100644 --- a/src/common/performance-monitor.ts +++ b/src/common/performance-monitor.ts @@ -6,290 +6,291 @@ 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; + 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(); - private startTimes = new Map(); - private reportInterval: NodeJS.Timeout | null = null; + private static instance: PerformanceMonitor; + private stats = new Map(); + private startTimes = new Map(); + private reportInterval: NodeJS.Timeout | null = null; - static getInstance(): PerformanceMonitor { - if (!PerformanceMonitor.instance) { - PerformanceMonitor.instance = new PerformanceMonitor(); - // 启动定时统计报告 - PerformanceMonitor.instance.startPeriodicReport(); - } - return PerformanceMonitor.instance; + 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秒 + 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; - } + stopPeriodicReport (): void { + if (this.reportInterval) { + clearInterval(this.reportInterval); + this.reportInterval = null; } + } - /** + /** * 打印定时统计报告 (简化版本) */ - private printPeriodicReport(): void { - const now = new Date().toLocaleString(); - console.log(`\n=== 性能监控定时报告 [${now}] ===`); + 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); + 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`); + 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.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`); - }); + // 显示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'); - } + 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); + 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 }); - } + // 确保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); + 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`; + 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 += '=== 所有函数详细统计 ===\n'; + const allStats = this.getStats().sort((a, b) => b.totalTime - a.totalTime); - // 排行榜统计 - 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`; - }); + 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 += `\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 += '=== 总耗时排行榜 (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.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=== 调用次数排行榜 (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=== 性能热点分析 ===\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=== 平均耗时排行榜 (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`; + 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`; + }); - // 写入文件 - fs.writeFileSync(logPath, logContent, 'utf8'); - console.log(`📄 详细性能报告已保存到: ${logPath}`); + logContent += '=== 报告结束 ===\n'; - } catch (error) { - console.error('写入性能日志文件时出错:', error); - } + // 写入文件 + 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()); + 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; + // 初始化或更新统计信息 + 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; + endFunction (callId: string, functionName: string): void { + const startTime = this.startTimes.get(callId); + if (!startTime) return; - const endTime = performance.now(); - const duration = endTime - startTime; + const endTime = performance.now(); + const duration = endTime - startTime; - this.startTimes.delete(callId); + this.startTimes.delete(callId); - const stat = this.stats.get(functionName); - if (!stat) return; + 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); - } + 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()); - } + 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); - } + 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); - } + 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); - } + 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(); - } + clear (): void { + this.stats.clear(); + this.startTimes.clear(); + } - /** + /** * 打印统计报告 */ - printReport(): void { - console.log('\n=== 函数性能监控报告 ==='); + 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.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.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⏱️ 平均耗时排行榜 (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'); - } + console.log('\n========================\n'); + } - /** + /** * 获取JSON格式的统计数据 */ - toJSON(): FunctionStats[] { - return this.getStats(); - } + toJSON (): FunctionStats[] { + return this.getStats(); + } } // 全局性能监控器实例 @@ -297,20 +298,20 @@ export const performanceMonitor = PerformanceMonitor.getInstance(); // 在进程退出时打印报告并停止定时器 if (typeof process !== 'undefined') { - process.on('exit', () => { - performanceMonitor.stopPeriodicReport(); - performanceMonitor.printReport(); - }); + process.on('exit', () => { + performanceMonitor.stopPeriodicReport(); + performanceMonitor.printReport(); + }); - process.on('SIGINT', () => { - performanceMonitor.stopPeriodicReport(); - performanceMonitor.printReport(); - process.exit(0); - }); + process.on('SIGINT', () => { + performanceMonitor.stopPeriodicReport(); + performanceMonitor.printReport(); + process.exit(0); + }); - process.on('SIGTERM', () => { - performanceMonitor.stopPeriodicReport(); - performanceMonitor.printReport(); - process.exit(0); - }); + process.on('SIGTERM', () => { + performanceMonitor.stopPeriodicReport(); + performanceMonitor.printReport(); + process.exit(0); + }); } diff --git a/src/common/proxy-handler.ts b/src/common/proxy-handler.ts index 3bcaf1a3..88b73313 100644 --- a/src/common/proxy-handler.ts +++ b/src/common/proxy-handler.ts @@ -1,22 +1,21 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { LogWrapper } from '@/common/log'; -export function proxyHandlerOf(logger: LogWrapper) { - return { - get(target: any, prop: any, receiver: any) { - if (typeof target[prop] === 'undefined') { - // 如果方法不存在,返回一个函数,这个函数调用existentMethod - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return (..._args: unknown[]) => { - logger.logDebug(`${target.constructor.name} has no method ${prop}`); - }; - } - // 如果方法存在,正常返回 - return Reflect.get(target, prop, receiver); - }, - }; +export function proxyHandlerOf (logger: LogWrapper) { + return { + get (target: any, prop: any, receiver: any) { + if (typeof target[prop] === 'undefined') { + // 如果方法不存在,返回一个函数,这个函数调用existentMethod + + return (..._args: unknown[]) => { + logger.logDebug(`${target.constructor.name} has no method ${prop}`); + }; + } + // 如果方法存在,正常返回 + return Reflect.get(target, prop, receiver); + }, + }; } -export function proxiedListenerOf(listener: T, logger: LogWrapper) { - return new Proxy(listener, proxyHandlerOf(logger)); +export function proxiedListenerOf (listener: T, logger: LogWrapper) { + return new Proxy(listener, proxyHandlerOf(logger)); } diff --git a/src/common/qq-basic-info.ts b/src/common/qq-basic-info.ts index 3dc30122..e921587d 100644 --- a/src/common/qq-basic-info.ts +++ b/src/common/qq-basic-info.ts @@ -7,101 +7,100 @@ import { getMajorPath } from '@/core'; import { QQAppidTableType, QQPackageInfoType, QQVersionConfigType } from './types'; export class QQBasicInfoWrapper { - QQMainPath: string | undefined; - QQPackageInfoPath: string | undefined; - QQVersionConfigPath: string | undefined; - isQuickUpdate: boolean | undefined; - QQVersionConfig: QQVersionConfigType | undefined; - QQPackageInfo: QQPackageInfoType | undefined; - QQVersionAppid: string | undefined; - QQVersionQua: string | undefined; - context: { logger: LogWrapper }; + QQMainPath: string | undefined; + QQPackageInfoPath: string | undefined; + QQVersionConfigPath: string | undefined; + isQuickUpdate: boolean | undefined; + QQVersionConfig: QQVersionConfigType | undefined; + QQPackageInfo: QQPackageInfoType | undefined; + QQVersionAppid: string | undefined; + QQVersionQua: string | undefined; + context: { logger: LogWrapper }; - constructor(context: { logger: LogWrapper }) { - //基础目录获取 - this.context = context; - this.QQMainPath = process.execPath; - this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath); + constructor (context: { logger: LogWrapper }) { + // 基础目录获取 + this.context = context; + this.QQMainPath = process.execPath; + this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath); + // 基础信息获取 无快更则启用默认模板填充 + this.isQuickUpdate = !!this.QQVersionConfigPath; + this.QQVersionConfig = this.isQuickUpdate + ? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString()) + : getDefaultQQVersionConfigInfo(); - //基础信息获取 无快更则启用默认模板填充 - this.isQuickUpdate = !!this.QQVersionConfigPath; - this.QQVersionConfig = this.isQuickUpdate - ? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString()) - : getDefaultQQVersionConfigInfo(); + this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion); + this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString()); + const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2(); + this.QQVersionAppid = IQQVersionAppid; + this.QQVersionQua = IQQVersionQua; + } - this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion); - this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString()); - const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2(); - this.QQVersionAppid = IQQVersionAppid; - this.QQVersionQua = IQQVersionQua; + // 基础函数 + getQQBuildStr () { + return this.QQVersionConfig?.curVersion.split('-')[1] ?? this.QQPackageInfo?.buildVersion; + } + + getFullQQVersion () { + const version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version; + if (!version) throw new Error('QQ版本获取失败'); + return version; + } + + requireMinNTQQBuild (buildStr: string) { + const currentBuild = +(this.getQQBuildStr() ?? '0'); + if (currentBuild == 0) throw new Error('QQBuildStr获取失败'); + return currentBuild >= parseInt(buildStr); + } + + // 此方法不要直接使用 + getQUAFallback () { + const platformMapping: Partial> = { + win32: `V1_WIN_${this.getFullQQVersion()}_${this.getQQBuildStr()}_GW_B`, + darwin: `V1_MAC_${this.getFullQQVersion()}_${this.getQQBuildStr()}_GW_B`, + linux: `V1_LNX_${this.getFullQQVersion()}_${this.getQQBuildStr()}_GW_B`, + }; + return platformMapping[systemPlatform] ?? (platformMapping.win32)!; + } + + getAppIdFallback () { + const platformMapping: Partial> = { + win32: '537246092', + darwin: '537246140', + linux: '537246140', + }; + return platformMapping[systemPlatform] ?? '537246092'; + } + + getAppidV2 (): { appid: string; qua: string } { + // 通过已有表 性能好 + const appidTbale = AppidTable as unknown as QQAppidTableType; + const fullVersion = this.getFullQQVersion(); + if (fullVersion) { + const data = appidTbale[fullVersion]; + if (data) { + return data; + } } - - //基础函数 - getQQBuildStr() { - return this.QQVersionConfig?.curVersion.split('-')[1] ?? this.QQPackageInfo?.buildVersion; - } - - getFullQQVersion() { - const version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version; - if (!version) throw new Error('QQ版本获取失败'); - return version; - } - - requireMinNTQQBuild(buildStr: string) { - const currentBuild = +(this.getQQBuildStr() ?? '0'); - if (currentBuild == 0) throw new Error('QQBuildStr获取失败'); - return currentBuild >= parseInt(buildStr); - } - - //此方法不要直接使用 - getQUAFallback() { - const platformMapping: Partial> = { - win32: `V1_WIN_${this.getFullQQVersion()}_${this.getQQBuildStr()}_GW_B`, - darwin: `V1_MAC_${this.getFullQQVersion()}_${this.getQQBuildStr()}_GW_B`, - linux: `V1_LNX_${this.getFullQQVersion()}_${this.getQQBuildStr()}_GW_B`, - }; - return platformMapping[systemPlatform] ?? (platformMapping.win32)!; - } - - getAppIdFallback() { - const platformMapping: Partial> = { - win32: '537246092', - darwin: '537246140', - linux: '537246140', - }; - return platformMapping[systemPlatform] ?? '537246092'; - } - - getAppidV2(): { appid: string; qua: string } { - // 通过已有表 性能好 - const appidTbale = AppidTable as unknown as QQAppidTableType; - const fullVersion = this.getFullQQVersion(); - if (fullVersion) { - const data = appidTbale[fullVersion]; - if (data) { - return data; - } - } - // 通过Major拉取 性能差 - try { - const majorAppid = this.getAppidV2ByMajor(fullVersion); - if (majorAppid) { - this.context.logger.log('[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat'); - return { appid: majorAppid, qua: this.getQUAFallback() }; - } - } catch { - this.context.logger.log('[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常'); - } - // 最终兜底为老版本 - this.context.logger.log('[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常'); - this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`,); - return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() }; - } - getAppidV2ByMajor(QQVersion: string) { - const majorPath = getMajorPath(QQVersion); - const appid = parseAppidFromMajor(majorPath); - return appid; + // 通过Major拉取 性能差 + try { + const majorAppid = this.getAppidV2ByMajor(fullVersion); + if (majorAppid) { + this.context.logger.log('[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat'); + return { appid: majorAppid, qua: this.getQUAFallback() }; + } + } catch { + this.context.logger.log('[QQ版本兼容性检测] 通过Major 获取Appid异常 请检测NapCat/QQNT是否正常'); } + // 最终兜底为老版本 + this.context.logger.log('[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常'); + this.context.logger.log(`[QQ版本兼容性检测] ${fullVersion} 版本兼容性不佳,可能会导致一些功能无法正常使用`); + return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() }; + } + getAppidV2ByMajor (QQVersion: string) { + const majorPath = getMajorPath(QQVersion); + const appid = parseAppidFromMajor(majorPath); + return appid; + } } diff --git a/src/common/request.ts b/src/common/request.ts index 6a4e7faa..7deed22f 100644 --- a/src/common/request.ts +++ b/src/common/request.ts @@ -1,117 +1,115 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import https from 'node:https'; import http from 'node:http'; export class RequestUtil { - // 适用于获取服务器下发cookies时获取,仅GET - static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> { - const client = url.startsWith('https') ? https : http; - return new Promise((resolve, reject) => { - const req = client.get(url, (res) => { - const cookies: { [key: string]: string } = {}; + // 适用于获取服务器下发cookies时获取,仅GET + static async HttpsGetCookies (url: string): Promise<{ [key: string]: string }> { + const client = url.startsWith('https') ? https : http; + return new Promise((resolve, reject) => { + const req = client.get(url, (res) => { + const cookies: { [key: string]: string } = {}; - res.on('data', () => { }); // Necessary to consume the stream - res.on('end', () => { - this.handleRedirect(res, url, cookies) - .then(resolve) - .catch(reject); - }); - - if (res.headers['set-cookie']) { - this.extractCookies(res.headers['set-cookie'], cookies); - } - }); - - req.on('error', (error: Error) => { - reject(error); - }); + res.on('data', () => { }); // Necessary to consume the stream + res.on('end', () => { + this.handleRedirect(res, url, cookies) + .then(resolve) + .catch(reject); }); - } - private static async handleRedirect(res: http.IncomingMessage, url: string, cookies: { [key: string]: string }): Promise<{ [key: string]: string }> { - if (res.statusCode === 301 || res.statusCode === 302) { - if (res.headers.location) { - const redirectUrl = new URL(res.headers.location, url); - const redirectCookies = await this.HttpsGetCookies(redirectUrl.href); - // 合并重定向过程中的cookies - return { ...cookies, ...redirectCookies }; - } + if (res.headers['set-cookie']) { + this.extractCookies(res.headers['set-cookie'], cookies); } - return cookies; - } + }); - private static extractCookies(setCookieHeaders: string[], cookies: { [key: string]: string }) { - setCookieHeaders.forEach((cookie) => { - 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; - } - } + req.on('error', (error: Error) => { + reject(error); + }); + }); + } + + private static async handleRedirect (res: http.IncomingMessage, url: string, cookies: { [key: string]: string }): Promise<{ [key: string]: string }> { + if (res.statusCode === 301 || res.statusCode === 302) { + if (res.headers.location) { + const redirectUrl = new URL(res.headers.location, url); + const redirectCookies = await this.HttpsGetCookies(redirectUrl.href); + // 合并重定向过程中的cookies + return { ...cookies, ...redirectCookies }; + } + } + return cookies; + } + + private static extractCookies (setCookieHeaders: string[], cookies: { [key: string]: string }) { + setCookieHeaders.forEach((cookie) => { + 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; + } + } + }); + } + + // 请求和回复都是JSON data传原始内容 自动编码json + static async HttpGetJson(url: string, method: string = 'GET', data?: any, headers: { + [key: string]: string + } = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise { + const option = new URL(url); + const protocol = url.startsWith('https://') ? https : http; + const options = { + hostname: option.hostname, + port: option.port, + path: option.pathname + option.search, + method, + headers, + }; + // headers: { + // 'Content-Type': 'application/json', + // 'Content-Length': Buffer.byteLength(postData), + // }, + return new Promise((resolve, reject) => { + const req = protocol.request(options, (res: http.IncomingMessage) => { + let responseBody = ''; + res.on('data', (chunk: string | Buffer) => { + responseBody += chunk.toString(); }); - } - - // 请求和回复都是JSON data传原始内容 自动编码json - static async HttpGetJson(url: string, method: string = 'GET', data?: any, headers: { - [key: string]: string - } = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise { - const option = new URL(url); - const protocol = url.startsWith('https://') ? https : http; - const options = { - hostname: option.hostname, - port: option.port, - path: option.pathname + option.search, - method: method, - headers: headers, - }; - // headers: { - // 'Content-Type': 'application/json', - // 'Content-Length': Buffer.byteLength(postData), - // }, - return new Promise((resolve, reject) => { - const req = protocol.request(options, (res: http.IncomingMessage) => { - let responseBody = ''; - res.on('data', (chunk: string | Buffer) => { - responseBody += chunk.toString(); - }); - - res.on('end', () => { - try { - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - if (isJsonRet) { - const responseJson = JSON.parse(responseBody); - resolve(responseJson as T); - } else { - resolve(responseBody as T); - } - } else { - reject(new Error(`Unexpected status code: ${res.statusCode}`)); - } - } catch (parseError: unknown) { - reject(new Error((parseError as Error).message)); - } - }); - }); - - req.on('error', (error: Error) => { - reject(error); - }); - if (method === 'POST' || method === 'PUT' || method === 'PATCH') { - if (isArgJson) { - req.write(JSON.stringify(data)); - } else { - req.write(data); - } + res.on('end', () => { + try { + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + if (isJsonRet) { + const responseJson = JSON.parse(responseBody); + resolve(responseJson as T); + } else { + resolve(responseBody as T); + } + } else { + reject(new Error(`Unexpected status code: ${res.statusCode}`)); } - req.end(); + } catch (parseError: unknown) { + reject(new Error((parseError as Error).message)); + } }); - } + }); - // 请求返回都是原始内容 - static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) { - return this.HttpGetJson(url, method, data, headers, false, false); - } + req.on('error', (error: Error) => { + reject(error); + }); + if (method === 'POST' || method === 'PUT' || method === 'PATCH') { + if (isArgJson) { + req.write(JSON.stringify(data)); + } else { + req.write(data); + } + } + req.end(); + }); + } + + // 请求返回都是原始内容 + static async HttpGetText (url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) { + return this.HttpGetJson(url, method, data, headers, false, false); + } } diff --git a/src/common/store.ts b/src/common/store.ts index c88fb57a..ff6e6a59 100644 --- a/src/common/store.ts +++ b/src/common/store.ts @@ -1,32 +1,32 @@ export type StoreValueType = string | number | boolean | object | null; export type StoreValue = { - value: T; - expiresAt?: number; + value: T; + expiresAt?: number; }; class Store { - // 使用Map存储键值对 - private store: Map; - // 定时清理器 - private cleanerTimer: NodeJS.Timeout; - // 用于分批次扫描的游标 - private scanCursor: number = 0; + // 使用Map存储键值对 + private store: Map; + // 定时清理器 + private cleanerTimer: NodeJS.Timeout; + // 用于分批次扫描的游标 + private scanCursor: number = 0; - /** + /** * Store * @param cleanInterval 清理间隔 * @param scanLimit 扫描限制(每次最多检查的键数) */ - constructor( - cleanInterval: number = 1000, // 默认1秒执行一次 - private scanLimit: number = 100 // 每次最多检查100个键 - ) { - this.store = new Map(); - this.cleanerTimer = setInterval(() => this.cleanupExpired(), cleanInterval); - } + constructor ( + cleanInterval: number = 1000, // 默认1秒执行一次 + private scanLimit: number = 100 // 每次最多检查100个键 + ) { + this.store = new Map(); + this.cleanerTimer = setInterval(() => this.cleanupExpired(), cleanInterval); + } - /** + /** * 设置键值对 * @param key 键 * @param value 值 @@ -34,155 +34,155 @@ class Store { * @returns void * @example store.set('key', 'value', 60) */ - set(key: string, value: T, ttl?: number): void { - if (ttl && ttl <= 0) { - this.del(key); - return; - } - const expiresAt = ttl ? Date.now() + ttl * 1000 : undefined; - this.store.set(key, { value, expiresAt }); + set(key: string, value: T, ttl?: number): void { + if (ttl && ttl <= 0) { + this.del(key); + return; } + const expiresAt = ttl ? Date.now() + ttl * 1000 : undefined; + this.store.set(key, { value, expiresAt }); + } - /** + /** * 清理过期键 */ - private cleanupExpired(): void { - const now = Date.now(); - const keys = Array.from(this.store.keys()); - let scanned = 0; + private cleanupExpired (): void { + const now = Date.now(); + const keys = Array.from(this.store.keys()); + let scanned = 0; - // 分批次扫描 - while (scanned < this.scanLimit && this.scanCursor < keys.length) { - const key = keys[this.scanCursor++]; - const entry = this.store.get(key!)!; + // 分批次扫描 + while (scanned < this.scanLimit && this.scanCursor < keys.length) { + const key = keys[this.scanCursor++]; + const entry = this.store.get(key!)!; - if (entry.expiresAt && entry.expiresAt < now) { - this.store.delete(key!); - } + if (entry.expiresAt && entry.expiresAt < now) { + this.store.delete(key!); + } - scanned++; - } - - // 重置游标(环形扫描) - if (this.scanCursor >= keys.length) { - this.scanCursor = 0; - } + scanned++; } - /** + // 重置游标(环形扫描) + if (this.scanCursor >= keys.length) { + this.scanCursor = 0; + } + } + + /** * 获取键值 * @param key 键 * @returns T | null * @example store.get('key') */ - get(key: string): T | null { - this.checkKeyExpiry(key); // 每次访问都检查 - const entry = this.store.get(key); - return entry ? (entry.value as T) : null; - } + get(key: string): T | null { + this.checkKeyExpiry(key); // 每次访问都检查 + const entry = this.store.get(key); + return entry ? (entry.value as T) : null; + } - /** + /** * 检查键是否过期 * @param key 键 */ - private checkKeyExpiry(key: string): void { - const entry = this.store.get(key); - if (entry?.expiresAt && entry.expiresAt < Date.now()) { - this.store.delete(key); - } + private checkKeyExpiry (key: string): void { + const entry = this.store.get(key); + if (entry?.expiresAt && entry.expiresAt < Date.now()) { + this.store.delete(key); } + } - /** + /** * 检查键是否存在 * @param keys 键 * @returns number * @example store.exists('key1', 'key2') */ - exists(...keys: string[]): number { - return keys.filter((key) => { - this.checkKeyExpiry(key); - return this.store.has(key); - }).length; - } + exists (...keys: string[]): number { + return keys.filter((key) => { + this.checkKeyExpiry(key); + return this.store.has(key); + }).length; + } - /** + /** * 关闭存储器 */ - shutdown(): void { - clearInterval(this.cleanerTimer); - this.store.clear(); - } + shutdown (): void { + clearInterval(this.cleanerTimer); + this.store.clear(); + } - /** + /** * 删除键 * @param keys 键 * @returns number * @example store.del('key1', 'key2') */ - del(...keys: string[]): number { - return keys.reduce((count, key) => (this.store.delete(key) ? count + 1 : count), 0); - } + del (...keys: string[]): number { + return keys.reduce((count, key) => (this.store.delete(key) ? count + 1 : count), 0); + } - /** + /** * 设置键的过期时间 * @param key 键 * @param seconds 过期时间(秒) * @returns boolean * @example store.expire('key', 60) */ - expire(key: string, seconds: number): boolean { - const entry = this.store.get(key); - if (!entry) return false; + expire (key: string, seconds: number): boolean { + const entry = this.store.get(key); + if (!entry) return false; - entry.expiresAt = Date.now() + seconds * 1000; - return true; - } + entry.expiresAt = Date.now() + seconds * 1000; + return true; + } - /** + /** * 获取键的过期时间 * @param key 键 * @returns number | null * @example store.ttl('key') */ - ttl(key: string): number | null { - const entry = this.store.get(key); - if (!entry) return null; + ttl (key: string): number | null { + const entry = this.store.get(key); + if (!entry) return null; - if (!entry.expiresAt) return -1; - const remaining = entry.expiresAt - Date.now(); - return remaining > 0 ? Math.floor(remaining / 1000) : -2; - } + if (!entry.expiresAt) return -1; + const remaining = entry.expiresAt - Date.now(); + return remaining > 0 ? Math.floor(remaining / 1000) : -2; + } - /** + /** * 键值数字递增 * @param key 键 * @returns number * @example store.incr('key') */ - incr(key: string): number { - const current = this.get(key); + incr (key: string): number { + const current = this.get(key); - if (current === null) { - this.set(key, 1, 60); - return 1; - } - - let numericValue: number; - if (typeof current === 'number') { - numericValue = current; - } else if (typeof current === 'string') { - if (!/^-?\d+$/.test(current)) { - throw new Error('ERR value is not an integer'); - } - numericValue = parseInt(current, 10); - } else { - throw new Error('ERR value is not an integer'); - } - - const newValue = numericValue + 1; - this.set(key, newValue, 60); - return newValue; + if (current === null) { + this.set(key, 1, 60); + return 1; } + + let numericValue: number; + if (typeof current === 'number') { + numericValue = current; + } else if (typeof current === 'string') { + if (!/^-?\d+$/.test(current)) { + throw new Error('ERR value is not an integer'); + } + numericValue = parseInt(current, 10); + } else { + throw new Error('ERR value is not an integer'); + } + + const newValue = numericValue + 1; + this.set(key, newValue, 60); + return newValue; + } } const store = new Store(); diff --git a/src/common/system.ts b/src/common/system.ts index 09122fe2..703c781b 100644 --- a/src/common/system.ts +++ b/src/common/system.ts @@ -5,12 +5,11 @@ import path from 'node:path'; let osName: string; try { - osName = os.hostname(); + osName = os.hostname(); } catch { - osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4); + osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4); } - const homeDir = os.homedir(); export const systemPlatform = os.platform(); diff --git a/src/common/types.ts b/src/common/types.ts index 61b52867..0de67b2e 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,17 +1,17 @@ -//QQVersionType +// QQVersionType export type QQPackageInfoType = { - version: string; - buildVersion: string; - platform: string; - eleArch: string; -} + version: string; + buildVersion: string; + platform: string; + eleArch: string; +}; export type QQVersionConfigType = { - baseVersion: string; - curVersion: string; - prevVersion: string; - onErrorVersions: Array; - buildId: string; -} + baseVersion: string; + curVersion: string; + prevVersion: string; + onErrorVersions: Array; + buildId: string; +}; export type QQAppidTableType = { - [key: string]: { appid: string, qua: string }; -} + [key: string]: { appid: string, qua: string }; +}; diff --git a/src/common/video.ts b/src/common/video.ts index fb30e974..e9260297 100644 --- a/src/common/video.ts +++ b/src/common/video.ts @@ -2,10 +2,10 @@ export const defaultVideoThumbB64 = '/9j/4AAQSkZJRgABAQAAAQABAAD//gAXR2VuZXJhdGVkIGJ5IFNuaXBhc3Rl/9sAhAAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47AQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAF/APADAREAAhEBAxEB/8QBogAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLEAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+foBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxEAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDiAayNxwagBwNAC5oAM0xBmgBM0ANJoAjY0AQsaBkTGgCM0DEpAFAC0AFMBaACgAoEJTASgQlACUwCgQ4UAOFADhQA4UAOFADxQIkBqDQUGgBwagBQaBC5pgGaAELUAMLUARs1AETGgBhNAxhoASkAUALQIKYxaBBQAUwEoAQ0CEoASmAUAOoEKKAHCgBwoAeKAHigQ7NZmoZpgLmgBd1Ahd1ABupgNLUAMLUAMY0AMJoAYaAENACUCCgAoAWgAoAWgBKYCUAJQISgApgLQAooEOFACigB4oAeKBDxQAVmaiZpgGaAFzQAbqAE3UAIWpgNJoAYTQIaaAEoAQ0CEoASgBaACgBaACmAUAJQAlAgoAKYC0AKKBCigB4FADgKBDwKAHigBuazNRM0DEzTAM0AJmgAzQAhNAhpNACGmA2gQlACUCEoAKACgBaAFpgFACUAJQAUCCmAUALQIcBQA4CgB4FADgKBDhQA4UAMzWZqNzTGJQAZoATNABmgBKAEoEIaYCUCEoASgQlABQAtABQAtMBKACgAoEFABimAYoEKBQA4CgB4FADwKBDgKAFFADhQBCazNhKAEpgFACUAFACUAFAhDTAbQISgAoEJQAUALQAtMAoAKADFABigQYoAMUALimIUCgBwFAh4FADgKAHUALQAtAENZmwlACUwEoAKAEoAKACgQlMBpoEJQAUCCgBcUAFABTAXFAC4oAMUAGKBBigAxQIKYCigQ8UAOFADhQAtAC0ALQBDWZqJQMSgBKYBQAlABQISgBKYCGgQlAC0CCgBcUAFABTAUCkA7FMAxQAYoEJQAUCCmAooEOFADxQA4UAFAC0ALQBDWZqJQAlACUxhQAlABQIKAEoASmISgBcUCCgBaACgBcUAKBQAuKYC0CEoAQ0AJQISmAooEPFADhQA4UALQAtAC0AQ1maiUAFACUAJTAKAEoAKAEoAMUxBigAxQIWgAoAKAFAoAWgBaYBQIQ0ANNACUCCmIUUAOFADxQA4UALQAtABQBFWZqFACUAFACYpgFACUAFACUAFAgxTEFABQAUALQAooAWgAoAKYDTQIaaAEpiCgQ4UAOFAh4oGOFAC0ALSAKYEdZmglABQAUDDFACUwEoASgAoAKBBQIKYBQAUALQAtAC0AJQAhpgNJoENJoATNMQCgQ8UCHigB4oAWgYtABQAUAMrM0CgAoAKADFACUxiUAJQAlAgoAKYgoAKACgYtAC0AFAhDTAQmgBhNAhpNACZpiFBoEPFAEi0CHigB1ABQAUDEoAbWZoFABQAtABTAQ0ANNAxDQAlAhaAEpiCgAoGFAC0AFABmgBCaYhpNADCaBDSaBBmgABpiJFNAEimgB4NADqAFzQAlACE0AJWZoFAC0AFAC0wEIoAaaAG0AJQAUCCgApjCgAoAKADNABmgBpNMQ0mgBpNAhhNAgzQAoNADwaAHqaAJAaBDgaYC5oATNACZoAWszQKACgBaBDqYCGgBpoAYaBiUCCgBKYBQMKACgAoAM0AITQIaTQA0mmA0mgQ3NAhKAHCgBwNADwaAHg0AOBpiFzQAZoATNAD6zNAoAKAFoEOpgBoAaaAGGmAw0AJmgAzQMM0AGaADNABmgBM0AITQIaTQAhNMQw0AJQIKAFFADhQA4GgBwNADs0xC5oAM0CDNAEtZmoUCCgBaAHUwCgBppgRtQAw0ANzQAZoAM0AGaADNABmgBKAEoAQ0ANNMQhoEJQAlMBaQDgaAFBoAcDTAdmgQuaADNAgzQBPWZqFAgoAWgBaYC0CGmmBG1AyM0ANJoATNACZoAXNABmgAzQAUAJQAhoAQ0xDTQISmAUALQAUgHA0AKDTAdmgQuaBBQAtAFiszQKACgBaAFFMAoEIaYEbUDI2oAYaAEoASgAzQAuaACgAoAKAENMQ00AJTEFAhKACgAoAXNACg0AOBoAWgQtAC0AWazNAoAKACgBaYBQIQ0AMNMYw0AMIoAbQAlMAoAKACgAzSAKYhKAENACUxBQIKACgBKACgBaAHCgQ4UALQAUAWqzNAoAKACgApgFACGgQ00xjTQAwigBCKAG4pgJQAlABQAUCCgBKACgBKYgoEFABQISgAoAWgBRQA4UALQAUCLdZmoUAFABQAlMAoASgBDQA00wENACYoATFMBpFADSKAEoEJQAUAFABQAlMQtAgoASgQUAJQAUAKKAHCgBaBBQBbrM1CgAoAKACmAUAJQAlADaYBQAlACYpgIRQA0igBpFAhtABQAUAFMAoEFABQIKAEoASgQUALQAooAWgQUAW81mbC0CCgApgFACUAIaAEpgJQAUAFABQAhFMBpFADSKAGkUCExQAYoAMUAGKADFMQYoAMUCExSATFABQIKYBQAtABQIt5qDYM0ALmgQtIApgIaAENADaACmAlAC0ALQAUwGkUANIoAaRQAmKBBigAxQAYoAMUAGKBBigBMUAJigQmKAExTAKBC0AFAFnNQaig0AKDQAtAgoASgBDQAlMBKACgAFADhQAtMBCKAGkUAIRQAmKADFABigQmKADFACYoAXFABigQmKAExQAmKBCYpgJigAoAnzUGgZoAcDQAuaBC0AJQAhoASmAlABQAtADhQAtMAoATFACEUAJigAxQAYoATFAhMUAFABQAuKADFABigBpWgBCKBCYpgJigB+ag0DNADgaBDgaAFzQITNACUAJTAKACgBRQAopgOoAWgBKAEoAKACgAoASgBpoEJQAooAWgBaBhigBMUCEIoAQigBMUAJSLCgBQaBDgaQC5oEFACUwCgBKACmAtADhQA4UALQAUAJQAUAJQAUAJQAhoENoAWgBRQAooGLQAUAGKAGkUAIRQIZSKEoGKKBDhQAUCCgAoAKBBQAUwFoGKKAHCgBaACgAoASgAoASgBCaAEoEJmgAoAUGgBQaAHZoGFABQAUANoAjpDEoAWgBaAFoEFACUALQAUCCmAUAOFAxRQAtAC0AJQAUAJQAmaBDSaAEzQAmaYBmgBQaAHA0gFzQAuaBhmgAzQAlAEdIYUALQAtAgoAKAEoEFAC0AFMAoAUUDFFAC0ALQAUAJQAhoENNACE0wEoATNABmgBc0ALmgBc0gDNAC5oATNABmgBKRQlACigB1AgoASgQlABTAWgBKACgBaBi0ALQAZoAM0AFACGgQ00wENACUAJQAUCFzQMM0ALmgAzQAZoAM0AGaQC0igoAUUALQIWgBDQISmAUAFACUAFABQAuaBi5oAM0AGaBBmgBKAEpgIaAG0AJQAUCFoAM0DDNAC5oATNABmgAzQBJUlBQAooAWgQtACGmIaaACgAoASgBKACgBc0DCgQUAGaADNABTASgBDQAlACUAFAgoAKBhQAUAFABQAlAE1SUFAxRQIWgQtMBDQIQ0AJQAlAhKBiUAFABmgBc0AGaADNABTAKACgBKAEoASgQlABQAUAFAC0AFACUAFAE1SaBQAUCHCgQtMBKBCUAJQISgBDQA00DEzQAuaADNMBc0AGaADNABQAUAJQAlABQISgAoAKACgBaACgBKAEoAnqTQSgBRQIcKBC0xCUAJQISgBKAENADDQAmaYwzQAuaADNAC0AFABQAUAFAhKACgBKACgAoAWgAoELQAlAxKAJqk0EoAWgQooELTEFADaBCUABoENNMY00ANNAwzQAZoAXNAC0AFAC0CFoASgAoASgBKACgAoAWgQtABQAUANNAyWpNAoAKBCimIWgQUCEoASmIQ0ANNADTQMaaAEoGLmgAzQAtADhQIWgBaACgQhoASgYlACUALQIWgBaACgBKAENAyWpNBKYBQIcKBC0CEoEJTAKBCUANNADDQMQ0ANoGFAC5oAUGgBwNAhRQIWgBaAENACGgBtAwoAKAFzQIXNABmgAoAQ0DJKRoJQAtAhRQSLQIKYCUCCgBDQA00AMNAxpoGNoAM0AGaAFBoAcDQIcKBDqACgBDQAhoAQ0DEoAKADNAC5oEGaBhmgAoAkpGgUCCgQooELQIKYhKACgBKAGmgBpoGMNAxDQAlAwzQIUUAOFAhwoAcKBC0AJQAhoGNNACUAFABQAZoAXNABQAUAS0ixKACgQoNAhaYgoEFACUABoAaaAGmgYw0DENAxtABQAooEOFADhQIcKAFoASgBDQAhoGJQAUAFACUALQIKBi0CJDSLEoATNAhc0CHZpiCgQUAJQIKBjTQAhoGNNAxpoATFABigBQKAHCgBwoAWgAoAKACgBKAEoASgAoASgBaAAUAOoEONIoaTQAZoAUGmIUGgQtAgzQISgAoAQ0DGmgYlAxKACgAxQAtACigBRQAtAxaACgAoATFABigBCKAG0CEoAWgBRTAUUAf//Z'; export interface VideoInfo { - width: number; - height: number; - time: number; - format: string; - size: number; - filePath: string; + width: number; + height: number; + time: number; + format: string; + size: number; + filePath: string; } diff --git a/src/common/worker.ts b/src/common/worker.ts index da5c1321..d0535ea3 100644 --- a/src/common/worker.ts +++ b/src/common/worker.ts @@ -1,35 +1,34 @@ import { Worker } from 'worker_threads'; -export async function runTask(workerScript: string, taskData: T): Promise { - let worker = new Worker(workerScript); - try { - return await new Promise((resolve, reject) => { - worker.on('message', (result: R) => { - if ((result as any)?.log) { - console.error('Worker Log--->:', (result as { log: string }).log); - } - if ((result as any)?.error) { - reject(new Error("Worker error: " + (result as { error: string }).error)); - } - resolve(result); - }); +export async function runTask (workerScript: string, taskData: T): Promise { + const worker = new Worker(workerScript); + try { + return await new Promise((resolve, reject) => { + worker.on('message', (result: R) => { + if ((result as any)?.log) { + console.error('Worker Log--->:', (result as { log: string }).log); + } + if ((result as any)?.error) { + reject(new Error('Worker error: ' + (result as { error: string }).error)); + } + resolve(result); + }); - worker.on('error', (error) => { - reject(new Error(`Worker error: ${error.message}`)); - }); + worker.on('error', (error) => { + reject(new Error(`Worker error: ${error.message}`)); + }); - worker.on('exit', (code) => { - if (code !== 0) { - reject(new Error(`Worker stopped with exit code ${code}`)); - } - }); - worker.postMessage(taskData); - }); - } catch (error: unknown) { - throw new Error(`Failed to run task: ${(error as Error).message}`); - } finally { - // Ensure the worker is terminated after the promise is settled - worker.terminate(); - } + worker.on('exit', (code) => { + if (code !== 0) { + reject(new Error(`Worker stopped with exit code ${code}`)); + } + }); + worker.postMessage(taskData); + }); + } catch (error: unknown) { + throw new Error(`Failed to run task: ${(error as Error).message}`); + } finally { + // Ensure the worker is terminated after the promise is settled + worker.terminate(); + } } - diff --git a/src/core/adapters/NodeIDependsAdapter.ts b/src/core/adapters/NodeIDependsAdapter.ts index 36ca6cf6..0e1fb5ee 100644 --- a/src/core/adapters/NodeIDependsAdapter.ts +++ b/src/core/adapters/NodeIDependsAdapter.ts @@ -1,16 +1,15 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { MsfChangeReasonType, MsfStatusType } from '@/core/types/adapter'; export class NodeIDependsAdapter { - onMSFStatusChange(_statusType: MsfStatusType, _changeReasonType: MsfChangeReasonType) { + onMSFStatusChange (_statusType: MsfStatusType, _changeReasonType: MsfChangeReasonType) { - } + } - onMSFSsoError(_args: unknown) { + onMSFSsoError (_args: unknown) { - } + } - getGroupCode(_args: unknown) { + getGroupCode (_args: unknown) { - } + } } diff --git a/src/core/adapters/NodeIDispatcherAdapter.ts b/src/core/adapters/NodeIDispatcherAdapter.ts index a70182da..fb9d24bd 100644 --- a/src/core/adapters/NodeIDispatcherAdapter.ts +++ b/src/core/adapters/NodeIDispatcherAdapter.ts @@ -1,11 +1,10 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ export class NodeIDispatcherAdapter { - dispatchRequest(_arg: unknown) { - } + dispatchRequest (_arg: unknown) { + } - dispatchCall(_arg: unknown) { - } + dispatchCall (_arg: unknown) { + } - dispatchCallWithJson(_arg: unknown) { - } + dispatchCallWithJson (_arg: unknown) { + } } diff --git a/src/core/adapters/NodeIGlobalAdapter.ts b/src/core/adapters/NodeIGlobalAdapter.ts index 3ab6a33a..4df9b66e 100644 --- a/src/core/adapters/NodeIGlobalAdapter.ts +++ b/src/core/adapters/NodeIGlobalAdapter.ts @@ -1,26 +1,25 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ export class NodeIGlobalAdapter { - onLog(..._args: unknown[]) { - } + onLog (..._args: unknown[]) { + } - onGetSrvCalTime(..._args: unknown[]) { - } + onGetSrvCalTime (..._args: unknown[]) { + } - onShowErrUITips(..._args: unknown[]) { - } + onShowErrUITips (..._args: unknown[]) { + } - fixPicImgType(..._args: unknown[]) { - } + fixPicImgType (..._args: unknown[]) { + } - getAppSetting(..._args: unknown[]) { - } + getAppSetting (..._args: unknown[]) { + } - onInstallFinished(..._args: unknown[]) { - } + onInstallFinished (..._args: unknown[]) { + } - onUpdateGeneralFlag(..._args: unknown[]) { - } + onUpdateGeneralFlag (..._args: unknown[]) { + } - onGetOfflineMsg(..._args: unknown[]) { - } + onGetOfflineMsg (..._args: unknown[]) { + } } diff --git a/src/core/apis/collection.ts b/src/core/apis/collection.ts index c805b442..9139b4aa 100644 --- a/src/core/apis/collection.ts +++ b/src/core/apis/collection.ts @@ -1,60 +1,60 @@ import { InstanceContext, NapCatCore } from '@/core'; export class NTQQCollectionApi { - context: InstanceContext; - core: NapCatCore; + context: InstanceContext; + core: NapCatCore; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } - async createCollection(authorUin: string, authorUid: string, authorName: string, brief: string, rawData: string) { - return this.context.session.getCollectionService().createNewCollectionItem({ - commInfo: { - bid: 1, - category: 2, - author: { - type: 1, - numId: authorUin, - strId: authorName, - groupId: '0', - groupName: '', - uid: authorUid, - }, - customGroupId: '0', - createTime: Date.now().toString(), - sequence: Date.now().toString(), - }, - richMediaSummary: { - originalUri: '', - publisher: '', - richMediaVersion: 0, - subTitle: '', - title: '', - brief: brief, - picList: [], - contentType: 1, - }, - richMediaContent: { - rawData: rawData, - bizDataList: [], - picList: [], - fileList: [], - }, - need_share_url: false, - }); - } + async createCollection (authorUin: string, authorUid: string, authorName: string, brief: string, rawData: string) { + return this.context.session.getCollectionService().createNewCollectionItem({ + commInfo: { + bid: 1, + category: 2, + author: { + type: 1, + numId: authorUin, + strId: authorName, + groupId: '0', + groupName: '', + uid: authorUid, + }, + customGroupId: '0', + createTime: Date.now().toString(), + sequence: Date.now().toString(), + }, + richMediaSummary: { + originalUri: '', + publisher: '', + richMediaVersion: 0, + subTitle: '', + title: '', + brief, + picList: [], + contentType: 1, + }, + richMediaContent: { + rawData, + bizDataList: [], + picList: [], + fileList: [], + }, + need_share_url: false, + }); + } - async getAllCollection(category: number = 0, count: number = 50) { - return this.context.session.getCollectionService().getCollectionItemList({ - category: category, - groupId: -1, - forceSync: true, - forceFromDb: false, - timeStamp: '0', - count: count, - searchDown: true, - }); - } + async getAllCollection (category: number = 0, count: number = 50) { + return this.context.session.getCollectionService().getCollectionItemList({ + category, + groupId: -1, + forceSync: true, + forceFromDb: false, + timeStamp: '0', + count, + searchDown: true, + }); + } } diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts index bef08563..4504613f 100644 --- a/src/core/apis/file.ts +++ b/src/core/apis/file.ts @@ -1,16 +1,16 @@ import { - ChatType, - ElementType, - IMAGE_HTTP_HOST, - IMAGE_HTTP_HOST_NT, - Peer, - PicElement, - PicSubType, - RawMessage, - SendFileElement, - SendPicElement, - SendPttElement, - SendVideoElement, + ChatType, + ElementType, + IMAGE_HTTP_HOST, + IMAGE_HTTP_HOST_NT, + Peer, + PicElement, + PicSubType, + RawMessage, + SendFileElement, + SendPicElement, + SendPttElement, + SendVideoElement, } from '@/core/types'; import path from 'path'; import fs from 'fs'; @@ -31,525 +31,522 @@ import { FileId } from '../packet/transformer/proto/misc/fileid'; import { imageSizeFallBack } from '@/image-size'; export class NTQQFileApi { - context: InstanceContext; - core: NapCatCore; - rkeyManager: RkeyManager; - packetRkey: Array<{ rkey: string; time: number; type: number; ttl: bigint }> | undefined; - private fetchRkeyFailures: number = 0; - private readonly MAX_RKEY_FAILURES: number = 8; + context: InstanceContext; + core: NapCatCore; + rkeyManager: RkeyManager; + packetRkey: Array<{ rkey: string; time: number; type: number; ttl: bigint }> | undefined; + private fetchRkeyFailures: number = 0; + private readonly MAX_RKEY_FAILURES: number = 8; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - this.rkeyManager = new RkeyManager([ - 'http://ss.xingzhige.com/music_card/rkey', - 'https://secret-service.bietiaop.com/rkeys', + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + this.rkeyManager = new RkeyManager([ + 'http://ss.xingzhige.com/music_card/rkey', + 'https://secret-service.bietiaop.com/rkeys', + ], + this.context.logger + ); + } + + private async fetchRkeyWithRetry () { + if (this.fetchRkeyFailures >= this.MAX_RKEY_FAILURES) { + throw new Error('Native.FetchRkey 已被禁用'); + } + try { + const ret = await this.core.apis.PacketApi.pkt.operation.FetchRkey(); + this.fetchRkeyFailures = 0; // Reset failures on success + return ret; + } catch (error) { + this.fetchRkeyFailures++; + this.context.logger.logError('FetchRkey 失败', (error as Error).message); + throw error; + } + } + + async getFileUrl (chatType: ChatType, peer: string, fileUUID?: string, file10MMd5?: string | undefined, timeout: number = 5000) { + if (this.core.apis.PacketApi.packetStatus) { + try { + if (chatType === ChatType.KCHATTYPEGROUP && fileUUID) { + return this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+peer, fileUUID, timeout); + } else if (file10MMd5 && fileUUID) { + return this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(peer, fileUUID, file10MMd5, timeout); + } + } catch (error) { + this.context.logger.logError('获取文件URL失败', (error as Error).message); + } + } + throw new Error('fileUUID or file10MMd5 is undefined'); + } + + async getPttUrl (peer: string, fileUUID?: string, timeout: number = 5000) { + if (this.core.apis.PacketApi.packetStatus && fileUUID) { + const appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid; + try { + if (appid && appid === 1403) { + return this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+peer, { + fileUuid: fileUUID, + storeId: 1, + uploadTime: 0, + ttl: 0, + subType: 0, + }, timeout); + } else if (fileUUID) { + return this.core.apis.PacketApi.pkt.operation.GetPttUrl(peer, { + fileUuid: fileUUID, + storeId: 1, + uploadTime: 0, + ttl: 0, + subType: 0, + }, timeout); + } + } catch (error) { + this.context.logger.logError('获取文件URL失败', (error as Error).message); + } + } + throw new Error('packet cant get ptt url'); + } + + async getVideoUrlPacket (peer: string, fileUUID?: string, timeout: number = 5000) { + if (this.core.apis.PacketApi.packetStatus && fileUUID) { + const appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid; + try { + if (appid && appid === 1415) { + return this.core.apis.PacketApi.pkt.operation.GetGroupVideoUrl(+peer, { + fileUuid: fileUUID, + storeId: 1, + uploadTime: 0, + ttl: 0, + subType: 0, + }, timeout); + } else if (fileUUID) { + return this.core.apis.PacketApi.pkt.operation.GetVideoUrl(peer, { + fileUuid: fileUUID, + storeId: 1, + uploadTime: 0, + ttl: 0, + subType: 0, + }, timeout); + } + } catch (error) { + this.context.logger.logError('获取文件URL失败', (error as Error).message); + } + } + throw new Error('packet cant get video url'); + } + + async copyFile (filePath: string, destPath: string) { + await this.core.util.copyFile(filePath, destPath); + } + + async getFileSize (filePath: string): Promise { + return await this.core.util.getFileSize(filePath); + } + + async getVideoUrl (peer: Peer, msgId: string, elementId: string) { + return (await this.context.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { + downSourceType: 1, + triggerType: 1, + })).urlResult.domainUrl; + } + + async uploadFile (filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { + const fileMd5 = await calculateFileMD5(filePath); + const extOrEmpty = await fileTypeFromFile(filePath).then(e => e?.ext ?? '').catch(() => ''); + const ext = extOrEmpty ? `.${extOrEmpty}` : ''; + let fileName = `${path.basename(filePath)}`; + if (fileName.indexOf('.') === -1) { + fileName += ext; + } + + const mediaPath = this.context.session.getMsgService().getRichMediaFilePathForGuild({ + md5HexStr: fileMd5, + fileName, + elementType, + elementSubType, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '', + }); + + await this.copyFile(filePath, mediaPath); + const fileSize = await this.getFileSize(filePath); + return { + md5: fileMd5, + fileName, + path: mediaPath, + fileSize, + ext, + }; + } + + async createValidSendFileElement (context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = ''): Promise { + 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 { + 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 { + 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 { + 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, ], - this.context.logger - ); - } + fileSubId: '', + playState: 1, + autoConvertText: 0, + storeID: 0, + otherBusinessInfo: { + aiVoiceType: 0, + }, + }, + }; + } - private async fetchRkeyWithRetry() { - if (this.fetchRkeyFailures >= this.MAX_RKEY_FAILURES) { - throw new Error('Native.FetchRkey 已被禁用'); - } - try { - let ret = await this.core.apis.PacketApi.pkt.operation.FetchRkey(); - this.fetchRkeyFailures = 0; // Reset failures on success - return ret; - } catch (error) { - this.fetchRkeyFailures++; - this.context.logger.logError('FetchRkey 失败', (error as Error).message); - throw error; - } - } + async downloadFileForModelId (peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) { + const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelRichMediaService/downloadFileForModelId', + 'NodeIKernelMsgListener/onRichMediaDownloadComplete', + [peer, [modelId], unknown], + () => true, + (arg) => arg?.commonFileInfo?.fileModelId === modelId, + 1, + timeout + ); + return fileTransNotifyInfo.filePath; + } - async getFileUrl(chatType: ChatType, peer: string, fileUUID?: string, file10MMd5?: string | undefined, timeout: number = 5000) { - if (this.core.apis.PacketApi.packetStatus) { - try { - if (chatType === ChatType.KCHATTYPEGROUP && fileUUID) { - return this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+peer, fileUUID, timeout); - } else if (file10MMd5 && fileUUID) { - return this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(peer, fileUUID, file10MMd5, timeout); - } - } catch (error) { - this.context.logger.logError('获取文件URL失败', (error as Error).message); - } - } - throw new Error('fileUUID or file10MMd5 is undefined'); - } - - async getPttUrl(peer: string, fileUUID?: string, timeout: number = 5000) { - if (this.core.apis.PacketApi.packetStatus && fileUUID) { - let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid; - try { - if (appid && appid === 1403) { - return this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+peer, { - fileUuid: fileUUID, - storeId: 1, - uploadTime: 0, - ttl: 0, - subType: 0, - }, timeout); - } else if (fileUUID) { - return this.core.apis.PacketApi.pkt.operation.GetPttUrl(peer, { - fileUuid: fileUUID, - storeId: 1, - uploadTime: 0, - ttl: 0, - subType: 0, - }, timeout); - } - } catch (error) { - this.context.logger.logError('获取文件URL失败', (error as Error).message); - } - } - throw new Error('packet cant get ptt url'); - } - - async getVideoUrlPacket(peer: string, fileUUID?: string, timeout: number = 5000) { - if (this.core.apis.PacketApi.packetStatus && fileUUID) { - let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid; - try { - if (appid && appid === 1415) { - return this.core.apis.PacketApi.pkt.operation.GetGroupVideoUrl(+peer, { - fileUuid: fileUUID, - storeId: 1, - uploadTime: 0, - ttl: 0, - subType: 0, - }, timeout); - } else if (fileUUID) { - return this.core.apis.PacketApi.pkt.operation.GetVideoUrl(peer, { - fileUuid: fileUUID, - storeId: 1, - uploadTime: 0, - ttl: 0, - subType: 0, - }, timeout); - } - } catch (error) { - this.context.logger.logError('获取文件URL失败', (error as Error).message); - } - } - throw new Error('packet cant get video url'); - } - - async copyFile(filePath: string, destPath: string) { - await this.core.util.copyFile(filePath, destPath); - } - - async getFileSize(filePath: string): Promise { - return await this.core.util.getFileSize(filePath); - } - - async getVideoUrl(peer: Peer, msgId: string, elementId: string) { - return (await this.context.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { - downSourceType: 1, - triggerType: 1, - })).urlResult.domainUrl; - } - - async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { - const fileMd5 = await calculateFileMD5(filePath); - const extOrEmpty = await fileTypeFromFile(filePath).then(e => e?.ext ?? '').catch(() => ''); - const ext = extOrEmpty ? `.${extOrEmpty}` : ''; - let fileName = `${path.basename(filePath)}`; - if (fileName.indexOf('.') === -1) { - fileName += ext; - } - - const mediaPath = this.context.session.getMsgService().getRichMediaFilePathForGuild({ - md5HexStr: fileMd5, - fileName: fileName, - elementType: elementType, - elementSubType, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: '', - }); - - await this.copyFile(filePath, mediaPath); - const fileSize = await this.getFileSize(filePath); - return { - md5: fileMd5, - fileName, - path: mediaPath, - fileSize, - ext, - }; - } - - async createValidSendFileElement(context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise { - 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: folderId, - filePath: path, - fileSize: fileSize.toString(), - }, - }; - } - - async createValidSendPicElement(context: SendMessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise { - 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: 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 { - 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 { - - 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: 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', - 'NodeIKernelMsgListener/onRichMediaDownloadComplete', - [peer, [modelId], unknown], - () => true, - (arg) => arg?.commonFileInfo?.fileModelId === modelId, - 1, - timeout, - ); - return fileTransNotifyInfo.filePath; - } - - async downloadRawMsgMedia(msg: RawMessage[]) { - const res = await Promise.all( - msg.map(m => - Promise.all( - m.elements - .filter(element => - element.elementType === ElementType.PIC || + async downloadRawMsgMedia (msg: RawMessage[]) { + const res = await Promise.all( + msg.map(m => + Promise.all( + m.elements + .filter(element => + element.elementType === ElementType.PIC || element.elementType === ElementType.VIDEO || element.elementType === ElementType.PTT || element.elementType === ElementType.FILE - ) - .map(element => - this.downloadMedia(m.msgId, m.chatType, m.peerUid, element.elementId, '', '', 1000 * 60 * 2, true) - ) - ) ) - ); - msg.forEach((m, msgIndex) => { - const elementResults = res[msgIndex]; - let elementIndex = 0; - m.elements.forEach(element => { - if ( - element.elementType === ElementType.PIC || + .map(element => + this.downloadMedia(m.msgId, m.chatType, m.peerUid, element.elementId, '', '', 1000 * 60 * 2, true) + ) + ) + ) + ); + msg.forEach((m, msgIndex) => { + const elementResults = res[msgIndex]; + let elementIndex = 0; + m.elements.forEach(element => { + if ( + element.elementType === ElementType.PIC || element.elementType === ElementType.VIDEO || element.elementType === ElementType.PTT || element.elementType === ElementType.FILE - ) { - switch (element.elementType) { - case ElementType.PIC: - element.picElement!.sourcePath = elementResults?.[elementIndex] ?? ''; - break; - case ElementType.VIDEO: - element.videoElement!.filePath = elementResults?.[elementIndex] ?? ''; - break; - case ElementType.PTT: - element.pttElement!.filePath = elementResults?.[elementIndex] ?? ''; - break; - case ElementType.FILE: - element.fileElement!.filePath = elementResults?.[elementIndex] ?? ''; - break; - } - elementIndex++; - } - }); - }); - return res.flat(); - } - - async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { - // 用于下载文件 - if (sourcePath && fs.existsSync(sourcePath)) { - if (force) { - try { - await fsPromises.unlink(sourcePath); - } catch { - // - } - } else { - return sourcePath; - } + ) { + switch (element.elementType) { + case ElementType.PIC: + element.picElement!.sourcePath = elementResults?.[elementIndex] ?? ''; + break; + case ElementType.VIDEO: + element.videoElement!.filePath = elementResults?.[elementIndex] ?? ''; + break; + case ElementType.PTT: + element.pttElement!.filePath = elementResults?.[elementIndex] ?? ''; + break; + case ElementType.FILE: + element.fileElement!.filePath = elementResults?.[elementIndex] ?? ''; + break; + } + elementIndex++; } - const [, completeRetData] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelMsgService/downloadRichMedia', - 'NodeIKernelMsgListener/onRichMediaDownloadComplete', - [{ - fileModelId: '0', - downSourceType: 0, - downloadSourceType: 0, - triggerType: 1, - msgId: msgId, - chatType: chatType, - peerUid: peerUid, - elementId: elementId, - thumbSize: 0, - downloadType: 1, - filePath: thumbPath, - }], - () => true, - (arg) => arg.msgElementId === elementId && arg.msgId === msgId, - 1, - timeout, - ); - return completeRetData.filePath; - } - - - async searchForFile(keys: string[]): Promise { - const randomResultId = 100000 + Math.floor(Math.random() * 10000); - let searchId = 0; - const [, searchResult] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelFileAssistantService/searchFile', - 'NodeIKernelFileAssistantListener/onFileSearch', - [ - keys, - { resultType: 2, pageLimit: 1 }, - randomResultId, - ], - (ret) => { - searchId = ret; - return true; - }, - result => result.searchId === searchId && result.resultId === randomResultId, - ); - return searchResult.resultItems[0]; - } - - async downloadFileById( - fileId: string, - fileSize: number = 1024576, - estimatedTime: number = (fileSize * 1000 / 1024576) + 5000, - ) { - const [, fileData] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelFileAssistantService/downloadFile', - 'NodeIKernelFileAssistantListener/onFileStatusChanged', - [[fileId]], - ret => ret.result === 0, - status => status.fileStatus === 2 && status.fileProgress === '0', - 1, - estimatedTime, // estimate 1MB/s - ); - return fileData.filePath!; - } - - async getImageUrl(element: PicElement): Promise { - if (!element) { - return ''; - } - - const url: string = element.originImageUrl ?? ''; - - const md5HexStr = element.md5HexStr; - const fileMd5 = element.md5HexStr; - const parsedUrl = new URL(IMAGE_HTTP_HOST + url); - const imageAppid = parsedUrl.searchParams.get('appid'); - const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid); - const imageFileId = parsedUrl.searchParams.get('fileid'); - if (url && isNTV2 && imageFileId) { - const rkeyData = await this.getRkeyData(); - return this.getImageUrlFromParsedUrl(imageFileId, imageAppid, rkeyData); - } - return this.getImageUrlFromMd5(fileMd5, md5HexStr); - } - - private async getRkeyData() { - const rkeyData: rkeyDataType = { - private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4', - group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds', - online_rkey: false - }; + }); + }); + return res.flat(); + } + async downloadMedia (msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { + // 用于下载文件 + if (sourcePath && fs.existsSync(sourcePath)) { + if (force) { try { - if (this.core.apis.PacketApi.packetStatus) { - const rkey_expired_private = !this.packetRkey || (this.packetRkey[0] && this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000); - const rkey_expired_group = !this.packetRkey || (this.packetRkey[0] && this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000); - if (rkey_expired_private || rkey_expired_group) { - this.packetRkey = await this.fetchRkeyWithRetry(); - } - if (this.packetRkey && this.packetRkey.length > 0) { - rkeyData.group_rkey = this.packetRkey[1]?.rkey.slice(6) ?? ''; - rkeyData.private_rkey = this.packetRkey[0]?.rkey.slice(6) ?? ''; - rkeyData.online_rkey = true; - } - } - } catch (error: unknown) { - this.context.logger.logDebug('获取native.rkey失败', (error as Error).message); + await fsPromises.unlink(sourcePath); + } catch { + // } + } else { + return sourcePath; + } + } + const [, completeRetData] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelMsgService/downloadRichMedia', + 'NodeIKernelMsgListener/onRichMediaDownloadComplete', + [{ + fileModelId: '0', + downSourceType: 0, + downloadSourceType: 0, + triggerType: 1, + msgId, + chatType, + peerUid, + elementId, + thumbSize: 0, + downloadType: 1, + filePath: thumbPath, + }], + () => true, + (arg) => arg.msgElementId === elementId && arg.msgId === msgId, + 1, + timeout + ); + return completeRetData.filePath; + } - if (!rkeyData.online_rkey) { - try { - const tempRkeyData = await this.rkeyManager.getRkey(); - rkeyData.group_rkey = tempRkeyData.group_rkey; - rkeyData.private_rkey = tempRkeyData.private_rkey; - rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000; - } catch (error: unknown) { - this.context.logger.logDebug('获取remote.rkey失败', (error as Error).message); - } - } - // 进行 fallback.rkey 模式 - return rkeyData; + async searchForFile (keys: string[]): Promise { + const randomResultId = 100000 + Math.floor(Math.random() * 10000); + let searchId = 0; + const [, searchResult] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelFileAssistantService/searchFile', + 'NodeIKernelFileAssistantListener/onFileSearch', + [ + keys, + { resultType: 2, pageLimit: 1 }, + randomResultId, + ], + (ret) => { + searchId = ret; + return true; + }, + result => result.searchId === searchId && result.resultId === randomResultId + ); + return searchResult.resultItems[0]; + } + + async downloadFileById ( + fileId: string, + fileSize: number = 1024576, + estimatedTime: number = (fileSize * 1000 / 1024576) + 5000 + ) { + const [, fileData] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelFileAssistantService/downloadFile', + 'NodeIKernelFileAssistantListener/onFileStatusChanged', + [[fileId]], + ret => ret.result === 0, + status => status.fileStatus === 2 && status.fileProgress === '0', + 1, + estimatedTime // estimate 1MB/s + ); + return fileData.filePath!; + } + + async getImageUrl (element: PicElement): Promise { + if (!element) { + return ''; } - private getImageUrlFromParsedUrl(imageFileId: string, appid: string, rkeyData: rkeyDataType): string { - const rkey = appid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey; - if (rkeyData.online_rkey) { - return IMAGE_HTTP_HOST_NT + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}`; + const url: string = element.originImageUrl ?? ''; + + const md5HexStr = element.md5HexStr; + const fileMd5 = element.md5HexStr; + const parsedUrl = new URL(IMAGE_HTTP_HOST + url); + const imageAppid = parsedUrl.searchParams.get('appid'); + const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid); + const imageFileId = parsedUrl.searchParams.get('fileid'); + if (url && isNTV2 && imageFileId) { + const rkeyData = await this.getRkeyData(); + return this.getImageUrlFromParsedUrl(imageFileId, imageAppid, rkeyData); + } + return this.getImageUrlFromMd5(fileMd5, md5HexStr); + } + + private async getRkeyData () { + const rkeyData: rkeyDataType = { + private_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qEc3Rbib9LP4', + group_rkey: 'CAQSKAB6JWENi5LM_xp9vumLbuThJSaYf-yzMrbZsuq7Uz2qffcqm614gds', + online_rkey: false, + }; + + try { + if (this.core.apis.PacketApi.packetStatus) { + const rkey_expired_private = !this.packetRkey || (this.packetRkey[0] && this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000); + const rkey_expired_group = !this.packetRkey || (this.packetRkey[0] && this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000); + if (rkey_expired_private || rkey_expired_group) { + this.packetRkey = await this.fetchRkeyWithRetry(); } - return IMAGE_HTTP_HOST + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}&spec=0`; + if (this.packetRkey && this.packetRkey.length > 0) { + rkeyData.group_rkey = this.packetRkey[1]?.rkey.slice(6) ?? ''; + rkeyData.private_rkey = this.packetRkey[0]?.rkey.slice(6) ?? ''; + rkeyData.online_rkey = true; + } + } + } catch (error: unknown) { + this.context.logger.logDebug('获取native.rkey失败', (error as Error).message); } - private getImageUrlFromMd5(fileMd5: string | undefined, md5HexStr: string | undefined): string { - if (fileMd5 || md5HexStr) { - return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr ?? '').toUpperCase()}/0`; - } - - this.context.logger.logDebug('图片url获取失败', { fileMd5, md5HexStr }); - return ''; + if (!rkeyData.online_rkey) { + try { + const tempRkeyData = await this.rkeyManager.getRkey(); + rkeyData.group_rkey = tempRkeyData.group_rkey; + rkeyData.private_rkey = tempRkeyData.private_rkey; + rkeyData.online_rkey = tempRkeyData.expired_time > Date.now() / 1000; + } catch (error: unknown) { + this.context.logger.logDebug('获取remote.rkey失败', (error as Error).message); + } } + // 进行 fallback.rkey 模式 + return rkeyData; + } + + private getImageUrlFromParsedUrl (imageFileId: string, appid: string, rkeyData: rkeyDataType): string { + const rkey = appid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey; + if (rkeyData.online_rkey) { + return IMAGE_HTTP_HOST_NT + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}`; + } + return IMAGE_HTTP_HOST + `/download?appid=${appid}&fileid=${imageFileId}&rkey=${rkey}&spec=0`; + } + + private getImageUrlFromMd5 (fileMd5: string | undefined, md5HexStr: string | undefined): string { + if (fileMd5 || md5HexStr) { + return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr ?? '').toUpperCase()}/0`; + } + + this.context.logger.logDebug('图片url获取失败', { fileMd5, md5HexStr }); + return ''; + } } - diff --git a/src/core/apis/friend.ts b/src/core/apis/friend.ts index a8254ec1..d51a926d 100644 --- a/src/core/apis/friend.ts +++ b/src/core/apis/friend.ts @@ -3,114 +3,120 @@ import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core'; import { LimitedHashTable } from '@/common/message-unique'; export class NTQQFriendApi { - context: InstanceContext; - core: NapCatCore; + context: InstanceContext; + core: NapCatCore; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } - async setBuddyRemark(uid: string, remark: string) { - return this.context.session.getBuddyService().setBuddyRemark({ uid, remark }); - } - async getBuddyV2SimpleInfoMap() { - const buddyService = this.context.session.getBuddyService(); - const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL); - const uids = buddyListV2.data.flatMap(item => item.buddyUids); - return await this.core.eventWrapper.callNoListenerEvent( - 'NodeIKernelProfileService/getCoreAndBaseInfo', - 'nodeStore', - uids, - ); - } + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } - async getBuddy(): Promise { - return Array.from((await this.getBuddyV2SimpleInfoMap()).values()); - } + async setBuddyRemark (uid: string, remark: string) { + return this.context.session.getBuddyService().setBuddyRemark({ uid, remark }); + } - async getBuddyIdMap(): Promise> { - const retMap: LimitedHashTable = new LimitedHashTable(5000); - const data = await this.getBuddyV2SimpleInfoMap(); - data.forEach((value) => retMap.set(value.uin!, value.uid!)); - return retMap; - } - async delBuudy(uid: string, tempBlock = false, tempBothDel = false) { - return this.context.session.getBuddyService().delBuddy({ - friendUid: uid, - tempBlock: tempBlock, - tempBothDel: tempBothDel - }); - } - async getBuddyV2ExWithCate() { - const buddyService = this.context.session.getBuddyService(); - const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data; - const uids = buddyListV2.flatMap(item => { - return item.buddyUids; - }); - const data = await this.core.eventWrapper.callNoListenerEvent( - 'NodeIKernelProfileService/getCoreAndBaseInfo', - 'nodeStore', - uids, - ); - return buddyListV2.map(category => ({ - categoryId: category.categoryId, - categorySortId: category.categorySortId, - categoryName: category.categroyName, - categoryMbCount: category.categroyMbCount, - onlineCount: category.onlineCount, - buddyList: category.buddyUids.map(uid => data.get(uid)).filter(value => !!value), - })); - } + async getBuddyV2SimpleInfoMap () { + const buddyService = this.context.session.getBuddyService(); + const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL); + const uids = buddyListV2.data.flatMap(item => item.buddyUids); + return await this.core.eventWrapper.callNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', + 'nodeStore', + uids + ); + } - async isBuddy(uid: string) { - return this.context.session.getBuddyService().isBuddy(uid); - } + async getBuddy (): Promise { + return Array.from((await this.getBuddyV2SimpleInfoMap()).values()); + } - async clearBuddyReqUnreadCnt() { - return this.context.session.getBuddyService().clearBuddyReqUnreadCnt(); - } + async getBuddyIdMap (): Promise> { + const retMap: LimitedHashTable = new LimitedHashTable(5000); + const data = await this.getBuddyV2SimpleInfoMap(); + data.forEach((value) => retMap.set(value.uin!, value.uid!)); + return retMap; + } - async getBuddyReq() { - const [, ret] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelBuddyService/getBuddyReq', - 'NodeIKernelBuddyListener/onBuddyReqChange', - [], - ); - return ret; - } + async delBuudy (uid: string, tempBlock = false, tempBothDel = false) { + return this.context.session.getBuddyService().delBuddy({ + friendUid: uid, + tempBlock, + tempBothDel, + }); + } - async handleFriendRequest(notify: FriendRequest, accept: boolean) { - this.context.session.getBuddyService()?.approvalFriendRequest({ - friendUid: notify.friendUid, - reqTime: notify.reqTime, - accept, - }); - } - async handleDoubtFriendRequest(friendUid: string, str1: string = '', str2: string = '') { - this.context.session.getBuddyService().approvalDoubtBuddyReq(friendUid, str1, str2); - } - async getDoubtFriendRequest(count: number) { - let date = Date.now().toString(); - const [, ret] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelBuddyService/getDoubtBuddyReq', - 'NodeIKernelBuddyListener/onDoubtBuddyReqChange', - [date, count, ''], - () => true, - (data) => data.reqId === date - ); - let requests = Promise.all(ret.doubtList.map(async (item) => { - return { - flag: item.uid, //注意强制String 非isNumeric 不遵守则不符合设计 - uin: await this.core.apis.UserApi.getUinByUidV2(item.uid) ?? 0,// 信息字段 - nick: item.nick, // 信息字段 这个不是nickname 可能是来源的群内的昵称 - source: item.source, // 信息字段 - reason: item.reason, // 信息字段 - msg: item.msg, // 信息字段 - group_code: item.groupCode, // 信息字段 - time: item.reqTime, // 信息字段 - type: 'doubt' //保留字段 - }; - })) - return requests; - } + async getBuddyV2ExWithCate () { + const buddyService = this.context.session.getBuddyService(); + const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data; + const uids = buddyListV2.flatMap(item => { + return item.buddyUids; + }); + const data = await this.core.eventWrapper.callNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', + 'nodeStore', + uids + ); + return buddyListV2.map(category => ({ + categoryId: category.categoryId, + categorySortId: category.categorySortId, + categoryName: category.categroyName, + categoryMbCount: category.categroyMbCount, + onlineCount: category.onlineCount, + buddyList: category.buddyUids.map(uid => data.get(uid)).filter(value => !!value), + })); + } + + async isBuddy (uid: string) { + return this.context.session.getBuddyService().isBuddy(uid); + } + + async clearBuddyReqUnreadCnt () { + return this.context.session.getBuddyService().clearBuddyReqUnreadCnt(); + } + + async getBuddyReq () { + const [, ret] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelBuddyService/getBuddyReq', + 'NodeIKernelBuddyListener/onBuddyReqChange', + [] + ); + return ret; + } + + async handleFriendRequest (notify: FriendRequest, accept: boolean) { + this.context.session.getBuddyService()?.approvalFriendRequest({ + friendUid: notify.friendUid, + reqTime: notify.reqTime, + accept, + }); + } + + async handleDoubtFriendRequest (friendUid: string, str1: string = '', str2: string = '') { + this.context.session.getBuddyService().approvalDoubtBuddyReq(friendUid, str1, str2); + } + + async getDoubtFriendRequest (count: number) { + const date = Date.now().toString(); + const [, ret] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelBuddyService/getDoubtBuddyReq', + 'NodeIKernelBuddyListener/onDoubtBuddyReqChange', + [date, count, ''], + () => true, + (data) => data.reqId === date + ); + const requests = Promise.all(ret.doubtList.map(async (item) => { + return { + flag: item.uid, // 注意强制String 非isNumeric 不遵守则不符合设计 + uin: await this.core.apis.UserApi.getUinByUidV2(item.uid) ?? 0, // 信息字段 + nick: item.nick, // 信息字段 这个不是nickname 可能是来源的群内的昵称 + source: item.source, // 信息字段 + reason: item.reason, // 信息字段 + msg: item.msg, // 信息字段 + group_code: item.groupCode, // 信息字段 + time: item.reqTime, // 信息字段 + type: 'doubt', // 保留字段 + }; + })); + return requests; + } } diff --git a/src/core/apis/group.ts b/src/core/apis/group.ts index ca8de1c8..e688f31c 100644 --- a/src/core/apis/group.ts +++ b/src/core/apis/group.ts @@ -1,17 +1,17 @@ import { - GeneralCallResult, - GroupMember, - NTGroupMemberRole, - NTGroupRequestOperateTypes, - InstanceContext, - KickMemberV2Req, - MemberExtSourceType, - NapCatCore, - GroupNotify, - GroupInfoSource, - ShutUpGroupMember, - Peer, - ChatType, + GeneralCallResult, + GroupMember, + NTGroupMemberRole, + NTGroupRequestOperateTypes, + InstanceContext, + KickMemberV2Req, + MemberExtSourceType, + NapCatCore, + GroupNotify, + GroupInfoSource, + ShutUpGroupMember, + Peer, + ChatType, } from '@/core'; import { isNumeric, solveAsyncProblem } from '@/common/helper'; import { LimitedHashTable } from '@/common/message-unique'; @@ -20,505 +20,510 @@ import { CancelableTask, TaskExecutor } from '@/common/cancel-task'; import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data'; export class NTQQGroupApi { - context: InstanceContext; - core: NapCatCore; - groupMemberCache: Map> = new Map>(); - essenceLRU = new LimitedHashTable(1000); + context: InstanceContext; + core: NapCatCore; + groupMemberCache: Map> = new Map>(); + essenceLRU = new LimitedHashTable(1000); - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } + + async setGroupRemark (groupCode: string, remark: string) { + return this.context.session.getGroupService().modifyGroupRemark(groupCode, remark); + } + + async fetchGroupDetail (groupCode: string) { + const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelGroupService/getGroupDetailInfo', + 'NodeIKernelGroupListener/onGroupDetailInfoChange', + [groupCode, GroupInfoSource.KDATACARD], + (ret) => ret.result === 0, + (detailInfo) => detailInfo.groupCode === groupCode, + 1, + 5000 + ); + return detailInfo; + } + + async initApi () { + this.initCache().then().catch(e => this.context.logger.logError(e)); + } + + async createGrayTip (groupCode: string, tip: string) { + return this.context.session.getMsgService().addLocalJsonGrayTipMsg( + { + chatType: ChatType.KCHATTYPEGROUP, + peerUid: groupCode, + } as Peer, + { + busiId: 2201, + jsonStr: JSON.stringify({ align: 'center', items: [{ txt: tip, type: 'nor' }] }), + recentAbstract: tip, + isServer: false, + }, + true, + true + ); + } + + async initCache () { + for (const group of await this.getGroups(true)) { + this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e)); + } + } + + async fetchGroupEssenceList (groupCode: string) { + const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; + return this.context.session.getGroupService().fetchGroupEssenceList({ + groupCode, + pageStart: 0, + pageLimit: 300, + }, pskey); + } + + async getGroupShutUpMemberList (groupCode: string): Promise { + const executor: TaskExecutor = async (resolve, reject, onCancel) => { + this.core.eventWrapper.registerListen( + 'NodeIKernelGroupListener/onShutUpMemberListChanged', + (group_id) => group_id === groupCode, + 1, + 1000 + ).then((data) => { + resolve(data[1]); + }).catch(reject); + + onCancel(() => { + reject(new Error('Task was canceled')); + }); + }; + + const task = new CancelableTask(executor); + this.context.session.getGroupService().getGroupShutUpMemberList(groupCode).then(e => { + if (e.result !== 0) { + task.cancel(); + } + }); + return await task.catch(() => []); + } + + async clearGroupNotifiesUnreadCount (doubt: boolean) { + return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(doubt); + } + + async setGroupAvatar (groupCode: string, filePath: string) { + return this.context.session.getGroupService().setHeader(groupCode, filePath); + } + + // 0 0 无需管理员审核 + // 0 2 需要管理员审核 + // 1 2 禁止Bot入群( 最好只传一个1 ?) + async setGroupRobotAddOption (groupCode: string, robotMemberSwitch?: number, robotMemberExamine?: number) { + const extInfo = createGroupExtInfo(groupCode); + const groupExtFilter = createGroupExtFilter(); + if (robotMemberSwitch !== undefined) { + extInfo.extInfo.inviteRobotMemberSwitch = robotMemberSwitch; + groupExtFilter.inviteRobotMemberSwitch = 1; + } + if (robotMemberExamine !== undefined) { + extInfo.extInfo.inviteRobotMemberExamine = robotMemberExamine; + groupExtFilter.inviteRobotMemberExamine = 1; + } + return this.context.session.getGroupService().modifyGroupExtInfoV2(extInfo, groupExtFilter); + } + + async setGroupAddOption (groupCode: string, option: { + addOption: number; + groupQuestion?: string; + groupAnswer?: string; + }) { + const param = createGroupDetailInfoV2Param(groupCode); + // 设置要修改的目标 + param.filter.addOption = 1; + if (option.addOption == 4 || option.addOption == 5) { + // 4 问题进入答案 5 问题管理员批准 + param.filter.groupQuestion = 1; + param.filter.groupAnswer = option.addOption == 4 ? 1 : 0; + param.modifyInfo.groupQuestion = option.groupQuestion || ''; + param.modifyInfo.groupAnswer = option.addOption == 4 ? option.groupAnswer || '' : ''; + } + param.modifyInfo.addOption = option.addOption; + return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0); + } + + async setGroupSearch (groupCode: string, option: { + noCodeFingerOpenFlag?: number; + noFingerOpenFlag?: number; + }) { + const param = createGroupDetailInfoV2Param(groupCode); + if (option.noCodeFingerOpenFlag) { + param.filter.noCodeFingerOpenFlag = 1; + param.modifyInfo.noCodeFingerOpenFlag = option.noCodeFingerOpenFlag; + } + if (option.noFingerOpenFlag) { + param.filter.noFingerOpenFlag = 1; + param.modifyInfo.noFingerOpenFlag = option.noFingerOpenFlag; + } + return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0); + } + + async getGroups (forced: boolean = false) { + const [, , groupList] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelGroupService/getGroupList', + 'NodeIKernelGroupListener/onGroupListUpdate', + [forced] + ); + return groupList; + } + + async getGroupExtFE0Info (groupCodes: Array, forced = true) { + return this.context.session.getGroupService().getGroupExt0xEF0Info( + groupCodes, + [], + { + bindGuildId: 1, + blacklistExpireTime: 1, + companyId: 1, + essentialMsgPrivilege: 1, + essentialMsgSwitch: 1, + fullGroupExpansionSeq: 1, + fullGroupExpansionSwitch: 1, + gangUpId: 1, + groupAioBindGuildId: 1, + groupBindGuildIds: 1, + groupBindGuildSwitch: 1, + groupExcludeGuildIds: 1, + groupExtFlameData: 1, + groupFlagPro1: 1, + groupInfoExtSeq: 1, + groupOwnerId: 1, + groupSquareSwitch: 1, + hasGroupCustomPortrait: 1, + inviteRobotMemberExamine: 1, + inviteRobotMemberSwitch: 1, + inviteRobotSwitch: 1, + isLimitGroupRtc: 1, + lightCharNum: 1, + luckyWord: 1, + luckyWordId: 1, + msgEventSeq: 1, + qqMusicMedalSwitch: 1, + reserve: 1, + showPlayTogetherSwitch: 1, + starId: 1, + todoSeq: 1, + viewedMsgDisappearTime: 1, + }, + forced + ); + } + + async getGroupMemberAll (groupCode: string, forced = false) { + return this.context.session.getGroupService().getAllMemberList(groupCode, forced); + } + + async refreshGroupMemberCache (groupCode: string, isWait = true) { + const updateCache = async () => { + try { + const members = await this.getGroupMemberAll(groupCode, true); + this.groupMemberCache.set(groupCode, members.result.infos); + } catch (e) { + this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`); + } + }; + + if (isWait) { + await updateCache(); + } else { + updateCache(); } - async setGroupRemark(groupCode: string, remark: string) { - return this.context.session.getGroupService().modifyGroupRemark(groupCode, remark); + return this.groupMemberCache.get(groupCode); + } + + async refreshGroupMemberCachePartial (groupCode: string, uid: string) { + const member = await this.getGroupMemberEx(groupCode, uid, true); + if (member) { + this.groupMemberCache.get(groupCode)?.set(uid, member); } - async fetchGroupDetail(groupCode: string) { - const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelGroupService/getGroupDetailInfo', - 'NodeIKernelGroupListener/onGroupDetailInfoChange', - [groupCode, GroupInfoSource.KDATACARD], - (ret) => ret.result === 0, - (detailInfo) => detailInfo.groupCode === groupCode, - 1, - 5000 - ); - return detailInfo; + return member; + } + + async getGroupMember (groupCode: string | number, memberUinOrUid: string | number) { + const groupCodeStr = groupCode.toString(); + const memberUinOrUidStr = memberUinOrUid.toString(); + + // 获取群成员缓存 + let members = this.groupMemberCache.get(groupCodeStr); + if (!members) { + members = (await this.refreshGroupMemberCache(groupCodeStr, true)); } - async initApi() { - this.initCache().then().catch(e => this.context.logger.logError(e)); + const getMember = () => { + if (isNumeric(memberUinOrUidStr)) { + return Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr); + } else { + return members!.get(memberUinOrUidStr); + } + }; + + let member = getMember(); + // 如果缓存中不存在该成员,尝试刷新缓存 + if (!member) { + members = (await this.refreshGroupMemberCache(groupCodeStr, true)); + member = getMember(); } - async createGrayTip(groupCode: string, tip: string) { - return this.context.session.getMsgService().addLocalJsonGrayTipMsg( - { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: groupCode, - } as Peer, - { - busiId: 2201, - jsonStr: JSON.stringify({ "align": "center", "items": [{ "txt": tip, "type": "nor" }] }), - recentAbstract: tip, - isServer: false - }, - true, - true - ) + return member; + } + + async getGroupRecommendContactArkJson (groupCode: string) { + return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode); + } + + async creatGroupFileFolder (groupCode: string, folderName: string) { + return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName); + } + + async delGroupFile (groupCode: string, files: Array) { + return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files); + } + + async delGroupFileFolder (groupCode: string, folderId: string) { + return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId); + } + + async transGroupFile (groupCode: string, fileId: string) { + return this.context.session.getRichMediaService().transGroupFile(groupCode, fileId); + } + + async addGroupEssence (groupCode: string, msgId: string) { + const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({ + chatType: 2, + guildId: '', + peerUid: groupCode, + }, msgId, 1, false); + if (!MsgData.msgList[0]) { + throw new Error('消息不存在'); } - async initCache() { - for (const group of await this.getGroups(true)) { - this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e)); - } + const param = { + groupCode, + msgRandom: parseInt(MsgData.msgList[0].msgRandom), + msgSeq: parseInt(MsgData.msgList[0].msgSeq), + }; + return this.context.session.getGroupService().addGroupEssence(param); + } + + async kickMemberV2Inner (param: KickMemberV2Req) { + return this.context.session.getGroupService().kickMemberV2(param); + } + + async deleteGroupBulletin (groupCode: string, noticeId: string) { + const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; + return this.context.session.getGroupService().deleteGroupBulletin(groupCode, psKey, noticeId); + } + + async quitGroupV2 (GroupCode: string, needDeleteLocalMsg: boolean) { + const param = { + groupCode: GroupCode, + needDeleteLocalMsg, + }; + return this.context.session.getGroupService().quitGroupV2(param); + } + + async removeGroupEssenceBySeq (groupCode: string, msgRandom: string, msgSeq: string) { + const param = { + groupCode, + msgRandom: parseInt(msgRandom), + msgSeq: parseInt(msgSeq), + }; + return this.context.session.getGroupService().removeGroupEssence(param); + } + + async removeGroupEssence (groupCode: string, msgId: string) { + const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({ + chatType: 2, + guildId: '', + peerUid: groupCode, + }, msgId, 1, false); + if (!MsgData.msgList[0]) { + throw new Error('消息不存在'); } + const param = { + groupCode, + msgRandom: parseInt(MsgData.msgList[0].msgRandom), + msgSeq: parseInt(MsgData.msgList[0].msgSeq), + }; + return this.context.session.getGroupService().removeGroupEssence(param); + } - async fetchGroupEssenceList(groupCode: string) { - const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; - return this.context.session.getGroupService().fetchGroupEssenceList({ - groupCode: groupCode, - pageStart: 0, - pageLimit: 300, - }, pskey); + async getSingleScreenNotifies (doubt: boolean, count: number) { + const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelGroupService/getSingleScreenNotifies', + 'NodeIKernelGroupListener/onGroupSingleScreenNotifies', + [ + doubt, + '', + count, + ] + ); + return notifies; + } + + async searchGroup (groupCode: string) { + const [, ret] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelSearchService/searchGroup', + 'NodeIKernelSearchListener/onSearchGroupResult', + [{ + keyWords: groupCode, + groupNum: 25, + exactSearch: false, + penetrate: '', + }], + (ret) => ret.result === 0, + (params) => !!params.groupInfos.find(g => g.groupCode === groupCode), + 1, + 5000 + ); + return ret.groupInfos.find(g => g.groupCode === groupCode); + } + + async getGroupMemberEx (groupCode: string, uid: string, forced: boolean = false, retry: number = 2) { + const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => { + return eventWrapper.callNormalEventV2( + 'NodeIKernelGroupService/getMemberInfo', + 'NodeIKernelGroupListener/onMemberInfoChange', + [groupCode, [uid], forced], + (ret) => ret.result === 0, + (params, _, members) => params === GroupCode && members.size > 0 && members.has(uid), + 1, + forced ? 2500 : 250 + ); + }, this.core.eventWrapper, groupCode, uid, forced); + if (data && data[3] instanceof Map && data[3].has(uid)) { + return data[3].get(uid); } - - async getGroupShutUpMemberList(groupCode: string): Promise { - const executor: TaskExecutor = async (resolve, reject, onCancel) => { - this.core.eventWrapper.registerListen( - 'NodeIKernelGroupListener/onShutUpMemberListChanged', - (group_id) => group_id === groupCode, - 1, - 1000 - ).then((data) => { - resolve(data[1]); - }).catch(reject); - - onCancel(() => { - reject(new Error('Task was canceled')); - }); - }; - - const task = new CancelableTask(executor); - this.context.session.getGroupService().getGroupShutUpMemberList(groupCode).then(e => { - if (e.result !== 0) { - task.cancel(); - } - }); - return await task.catch(() => []); + if (retry > 0) { + const trydata = await this.getGroupMemberEx(groupCode, uid, true, retry - 1) as GroupMember | undefined; + if (trydata) return trydata; } + return undefined; + } - async clearGroupNotifiesUnreadCount(doubt: boolean) { - return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(doubt); - } + async getGroupFileCount (groupCodes: Array) { + return this.context.session.getRichMediaService().batchGetGroupFileCount(groupCodes); + } - async setGroupAvatar(groupCode: string, filePath: string) { - return this.context.session.getGroupService().setHeader(groupCode, filePath); - } + async getArkJsonGroupShare (groupCode: string) { + const ret = await this.core.eventWrapper.callNoListenerEvent( + 'NodeIKernelGroupService/getGroupRecommendContactArkJson', + groupCode + ) as GeneralCallResult & { arkJson: string }; + return ret.arkJson; + } - // 0 0 无需管理员审核 - // 0 2 需要管理员审核 - // 1 2 禁止Bot入群( 最好只传一个1 ?) - async setGroupRobotAddOption(groupCode: string, robotMemberSwitch?: number, robotMemberExamine?: number) { - let extInfo = createGroupExtInfo(groupCode); - let groupExtFilter = createGroupExtFilter(); - if (robotMemberSwitch !== undefined) { - extInfo.extInfo.inviteRobotMemberSwitch = robotMemberSwitch; - groupExtFilter.inviteRobotMemberSwitch = 1; - } - if (robotMemberExamine !== undefined) { - extInfo.extInfo.inviteRobotMemberExamine = robotMemberExamine; - groupExtFilter.inviteRobotMemberExamine = 1; - } - return this.context.session.getGroupService().modifyGroupExtInfoV2(extInfo, groupExtFilter); - } + async uploadGroupBulletinPic (groupCode: string, imageurl: string) { + const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; + return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl); + } - async setGroupAddOption(groupCode: string, option: { - addOption: number; - groupQuestion?: string; - groupAnswer?: string; - }) { - let param = createGroupDetailInfoV2Param(groupCode); - // 设置要修改的目标 - param.filter.addOption = 1; - if (option.addOption == 4 || option.addOption == 5) { - // 4 问题进入答案 5 问题管理员批准 - param.filter.groupQuestion = 1; - param.filter.groupAnswer = option.addOption == 4 ? 1 : 0; - param.modifyInfo.groupQuestion = option.groupQuestion || ''; - param.modifyInfo.groupAnswer = option.addOption == 4 ? option.groupAnswer || '' : ''; - } - param.modifyInfo.addOption = option.addOption; - return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0); - } + async handleGroupRequest (doubt: boolean, notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) { + return this.context.session.getGroupService().operateSysNotify( + doubt, + { + operateType, + targetMsg: { + seq: notify.seq, // 通知序列号 + type: notify.type, + groupCode: notify.group.groupCode, + postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格 + }, + }); + } - async setGroupSearch(groupCode: string, option: { - noCodeFingerOpenFlag?: number; - noFingerOpenFlag?: number; - }) { - let param = createGroupDetailInfoV2Param(groupCode); - if (option.noCodeFingerOpenFlag) { - param.filter.noCodeFingerOpenFlag = 1; - param.modifyInfo.noCodeFingerOpenFlag = option.noCodeFingerOpenFlag; - } - if (option.noFingerOpenFlag) { - param.filter.noFingerOpenFlag = 1; - param.modifyInfo.noFingerOpenFlag = option.noFingerOpenFlag; - } - return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0); - } + async quitGroup (groupCode: string) { + return this.context.session.getGroupService().quitGroup(groupCode); + } - async getGroups(forced: boolean = false) { - const [, , groupList] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelGroupService/getGroupList', - 'NodeIKernelGroupListener/onGroupListUpdate', - [forced], - ); - return groupList; - } + async kickMember (groupCode: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') { + return this.context.session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason); + } - async getGroupExtFE0Info(groupCodes: Array, forced = true) { - return this.context.session.getGroupService().getGroupExt0xEF0Info( - groupCodes, - [], - { - bindGuildId: 1, - blacklistExpireTime: 1, - companyId: 1, - essentialMsgPrivilege: 1, - essentialMsgSwitch: 1, - fullGroupExpansionSeq: 1, - fullGroupExpansionSwitch: 1, - gangUpId: 1, - groupAioBindGuildId: 1, - groupBindGuildIds: 1, - groupBindGuildSwitch: 1, - groupExcludeGuildIds: 1, - groupExtFlameData: 1, - groupFlagPro1: 1, - groupInfoExtSeq: 1, - groupOwnerId: 1, - groupSquareSwitch: 1, - hasGroupCustomPortrait: 1, - inviteRobotMemberExamine: 1, - inviteRobotMemberSwitch: 1, - inviteRobotSwitch: 1, - isLimitGroupRtc: 1, - lightCharNum: 1, - luckyWord: 1, - luckyWordId: 1, - msgEventSeq: 1, - qqMusicMedalSwitch: 1, - reserve: 1, - showPlayTogetherSwitch: 1, - starId: 1, - todoSeq: 1, - viewedMsgDisappearTime: 1, - }, - forced, - ); - } + async banMember (groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) { + // timeStamp为秒数, 0为解除禁言 + return this.context.session.getGroupService().setMemberShutUp(groupCode, memList); + } - async getGroupMemberAll(groupCode: string, forced = false) { - return this.context.session.getGroupService().getAllMemberList(groupCode, forced); - } + async banGroup (groupCode: string, shutUp: boolean) { + return this.context.session.getGroupService().setGroupShutUp(groupCode, shutUp); + } - async refreshGroupMemberCache(groupCode: string, isWait = true) { - const updateCache = async () => { - try { - const members = await this.getGroupMemberAll(groupCode, true); - this.groupMemberCache.set(groupCode, members.result.infos); - } catch (e) { - this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`); - } - }; + async setMemberCard (groupCode: string, memberUid: string, cardName: string) { + return this.context.session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName); + } - if (isWait) { - await updateCache(); - } else { - updateCache(); - } + async setMemberRole (groupCode: string, memberUid: string, role: NTGroupMemberRole) { + return this.context.session.getGroupService().modifyMemberRole(groupCode, memberUid, role); + } - return this.groupMemberCache.get(groupCode); - } - async refreshGroupMemberCachePartial(groupCode: string, uid: string) { - const member = await this.getGroupMemberEx(groupCode, uid, true); - if (member) { - this.groupMemberCache.get(groupCode)?.set(uid, member); - } - return member; - } - async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) { - const groupCodeStr = groupCode.toString(); - const memberUinOrUidStr = memberUinOrUid.toString(); + async setGroupName (groupCode: string, groupName: string) { + return this.context.session.getGroupService().modifyGroupName(groupCode, groupName, false); + } - // 获取群成员缓存 - let members = this.groupMemberCache.get(groupCodeStr); - if (!members) { - members = (await this.refreshGroupMemberCache(groupCodeStr, true)); - } + async publishGroupBulletin (groupCode: string, content: string, picInfo: { + id: string, + width: number, + height: number + } | undefined = undefined, pinned: number = 0, confirmRequired: number = 0) { + const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com'); + // text是content内容url编码 + const data = { + text: encodeURI(content), + picInfo, + oldFeedsId: '', + pinned, + confirmRequired, + }; + return this.context.session.getGroupService().publishGroupBulletin(groupCode, psKey!, data); + } - const getMember = () => { - if (isNumeric(memberUinOrUidStr)) { - return Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr); - } else { - return members!.get(memberUinOrUidStr); - } - }; + async getGroupRemainAtTimes (groupCode: string) { + return this.context.session.getGroupService().getGroupRemainAtTimes(groupCode); + } - let member = getMember(); - // 如果缓存中不存在该成员,尝试刷新缓存 - if (!member) { - members = (await this.refreshGroupMemberCache(groupCodeStr, true)); - member = getMember(); - } - return member; - } - - async getGroupRecommendContactArkJson(groupCode: string) { - return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode); - } - - async creatGroupFileFolder(groupCode: string, folderName: string) { - return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName); - } - - async delGroupFile(groupCode: string, files: Array) { - return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files); - } - - async delGroupFileFolder(groupCode: string, folderId: string) { - return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId); - } - - async transGroupFile(groupCode: string, fileId: string) { - return this.context.session.getRichMediaService().transGroupFile(groupCode, fileId); - } - - async addGroupEssence(groupCode: string, msgId: string) { - const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({ - chatType: 2, - guildId: '', - peerUid: groupCode, - }, msgId, 1, false); - if (!MsgData.msgList[0]) { - throw new Error('消息不存在'); - } - const param = { - groupCode: groupCode, - msgRandom: parseInt(MsgData.msgList[0].msgRandom), - msgSeq: parseInt(MsgData.msgList[0].msgSeq), - }; - return this.context.session.getGroupService().addGroupEssence(param); - } - - async kickMemberV2Inner(param: KickMemberV2Req) { - return this.context.session.getGroupService().kickMemberV2(param); - } - - async deleteGroupBulletin(groupCode: string, noticeId: string) { - const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; - return this.context.session.getGroupService().deleteGroupBulletin(groupCode, psKey, noticeId); - } - - async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) { - const param = { - groupCode: GroupCode, - needDeleteLocalMsg: needDeleteLocalMsg, - }; - return this.context.session.getGroupService().quitGroupV2(param); - } - - async removeGroupEssenceBySeq(groupCode: string, msgRandom: string, msgSeq: string) { - const param = { - groupCode: groupCode, - msgRandom: parseInt(msgRandom), - msgSeq: parseInt(msgSeq), - }; - return this.context.session.getGroupService().removeGroupEssence(param); - } - - async removeGroupEssence(groupCode: string, msgId: string) { - const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({ - chatType: 2, - guildId: '', - peerUid: groupCode, - }, msgId, 1, false); - if (!MsgData.msgList[0]) { - throw new Error('消息不存在'); - } - const param = { - groupCode: groupCode, - msgRandom: parseInt(MsgData.msgList[0].msgRandom), - msgSeq: parseInt(MsgData.msgList[0].msgSeq), - }; - return this.context.session.getGroupService().removeGroupEssence(param); - } - - async getSingleScreenNotifies(doubt: boolean, count: number) { - const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelGroupService/getSingleScreenNotifies', - 'NodeIKernelGroupListener/onGroupSingleScreenNotifies', - [ - doubt, - '', - count, - ], - ); - return notifies; - } - - async searchGroup(groupCode: string) { - const [, ret] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelSearchService/searchGroup', - 'NodeIKernelSearchListener/onSearchGroupResult', - [{ - keyWords: groupCode, - groupNum: 25, - exactSearch: false, - penetrate: '' - }], - (ret) => ret.result === 0, - (params) => !!params.groupInfos.find(g => g.groupCode === groupCode), - 1, - 5000 - ); - return ret.groupInfos.find(g => g.groupCode === groupCode); - } - - async getGroupMemberEx(groupCode: string, uid: string, forced: boolean = false, retry: number = 2) { - const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => { - return eventWrapper.callNormalEventV2( - 'NodeIKernelGroupService/getMemberInfo', - 'NodeIKernelGroupListener/onMemberInfoChange', - [groupCode, [uid], forced], - (ret) => ret.result === 0, - (params, _, members) => params === GroupCode && members.size > 0 && members.has(uid), - 1, - forced ? 2500 : 250 - ); - }, this.core.eventWrapper, groupCode, uid, forced); - if (data && data[3] instanceof Map && data[3].has(uid)) { - return data[3].get(uid); - } - if (retry > 0) { - const trydata = await this.getGroupMemberEx(groupCode, uid, true, retry - 1) as GroupMember | undefined; - if (trydata) return trydata; - } - return undefined; - } - - async getGroupFileCount(groupCodes: Array) { - return this.context.session.getRichMediaService().batchGetGroupFileCount(groupCodes); - } - - async getArkJsonGroupShare(groupCode: string) { - const ret = await this.core.eventWrapper.callNoListenerEvent( - 'NodeIKernelGroupService/getGroupRecommendContactArkJson', - groupCode, - ) as GeneralCallResult & { arkJson: string }; - return ret.arkJson; - } - - async uploadGroupBulletinPic(groupCode: string, imageurl: string) { - const _Pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!; - return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl); - } - - async handleGroupRequest(doubt: boolean, notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) { - return this.context.session.getGroupService().operateSysNotify( - doubt, - { - operateType: operateType, - targetMsg: { - seq: notify.seq, // 通知序列号 - type: notify.type, - groupCode: notify.group.groupCode, - postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格 - }, - }); - } - - async quitGroup(groupCode: string) { - return this.context.session.getGroupService().quitGroup(groupCode); - } - - async kickMember(groupCode: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') { - return this.context.session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason); - } - - async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) { - // timeStamp为秒数, 0为解除禁言 - return this.context.session.getGroupService().setMemberShutUp(groupCode, memList); - } - - async banGroup(groupCode: string, shutUp: boolean) { - return this.context.session.getGroupService().setGroupShutUp(groupCode, shutUp); - } - - async setMemberCard(groupCode: string, memberUid: string, cardName: string) { - return this.context.session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName); - } - - async setMemberRole(groupCode: string, memberUid: string, role: NTGroupMemberRole) { - return this.context.session.getGroupService().modifyMemberRole(groupCode, memberUid, role); - } - - async setGroupName(groupCode: string, groupName: string) { - return this.context.session.getGroupService().modifyGroupName(groupCode, groupName, false); - } - - async publishGroupBulletin(groupCode: string, content: string, picInfo: { - id: string, - width: number, - height: number - } | undefined = undefined, pinned: number = 0, confirmRequired: number = 0) { - const psKey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com'); - //text是content内容url编码 - const data = { - text: encodeURI(content), - picInfo: picInfo, - oldFeedsId: '', - pinned: pinned, - confirmRequired: confirmRequired, - }; - return this.context.session.getGroupService().publishGroupBulletin(groupCode, psKey!, data); - } - - async getGroupRemainAtTimes(groupCode: string) { - return this.context.session.getGroupService().getGroupRemainAtTimes(groupCode); - } - - async getMemberExtInfo(groupCode: string, uin: string) { - return this.context.session.getGroupService().getMemberExtInfo( - { - groupCode: groupCode, - sourceType: MemberExtSourceType.TITLETYPE, - beginUin: '0', - dataTime: '0', - uinList: [uin], - uinNum: '', - seq: '', - groupType: '', - richCardNameVer: '', - memberExtFilter: { - memberLevelInfoUin: 1, - memberLevelInfoPoint: 1, - memberLevelInfoActiveDay: 1, - memberLevelInfoLevel: 1, - memberLevelInfoName: 1, - levelName: 1, - dataTime: 1, - userShowFlag: 1, - sysShowFlag: 1, - timeToUpdate: 1, - nickName: 1, - specialTitle: 1, - levelNameNew: 1, - userShowFlagNew: 1, - msgNeedField: 1, - cmdUinFlagExt3Grocery: 1, - memberIcon: 1, - memberInfoSeq: 1, - }, - }, - ); - } + async getMemberExtInfo (groupCode: string, uin: string) { + return this.context.session.getGroupService().getMemberExtInfo( + { + groupCode, + sourceType: MemberExtSourceType.TITLETYPE, + beginUin: '0', + dataTime: '0', + uinList: [uin], + uinNum: '', + seq: '', + groupType: '', + richCardNameVer: '', + memberExtFilter: { + memberLevelInfoUin: 1, + memberLevelInfoPoint: 1, + memberLevelInfoActiveDay: 1, + memberLevelInfoLevel: 1, + memberLevelInfoName: 1, + levelName: 1, + dataTime: 1, + userShowFlag: 1, + sysShowFlag: 1, + timeToUpdate: 1, + nickName: 1, + specialTitle: 1, + levelNameNew: 1, + userShowFlagNew: 1, + msgNeedField: 1, + cmdUinFlagExt3Grocery: 1, + memberIcon: 1, + memberInfoSeq: 1, + }, + } + ); + } } diff --git a/src/core/apis/index.ts b/src/core/apis/index.ts index ccc437fe..1a423a5d 100644 --- a/src/core/apis/index.ts +++ b/src/core/apis/index.ts @@ -6,4 +6,4 @@ export * from './user'; export * from './webapi'; export * from './system'; export * from './packet'; -export * from './file'; \ No newline at end of file +export * from './file'; diff --git a/src/core/apis/msg.ts b/src/core/apis/msg.ts index 5edb20bf..7d2188d8 100644 --- a/src/core/apis/msg.ts +++ b/src/core/apis/msg.ts @@ -3,307 +3,311 @@ import { GroupFileInfoUpdateItem, InstanceContext, NapCatCore, NodeIKernelMsgSer import { GeneralCallResult } from '@/core/services/common'; export class NTQQMsgApi { + context: InstanceContext; + core: NapCatCore; + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } - context: InstanceContext; - core: NapCatCore; + async clickInlineKeyboardButton (...params: Parameters) { + return this.context.session.getMsgService().clickInlineKeyboardButton(...params); + } - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } + getMsgByClientSeqAndTime (peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) { + // https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType 可以用过特殊方式拉取 + return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime); + } - async clickInlineKeyboardButton(...params: Parameters) { - return this.context.session.getMsgService().clickInlineKeyboardButton(...params); - } + async getAioFirstViewLatestMsgs (peer: Peer, MsgCount: number) { + return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount); + } - getMsgByClientSeqAndTime(peer: Peer, replyMsgClientSeq: string, replyMsgTime: string) { - // https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType 可以用过特殊方式拉取 - return this.context.session.getMsgService().getMsgByClientSeqAndTime(peer, replyMsgClientSeq, replyMsgTime); - } - async getAioFirstViewLatestMsgs(peer: Peer, MsgCount: number) { - return this.context.session.getMsgService().getAioFirstViewLatestMsgs(peer, MsgCount); - } + async sendShowInputStatusReq (peer: Peer, eventType: number) { + return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid); + } - async sendShowInputStatusReq(peer: Peer, eventType: number) { - return this.context.session.getMsgService().sendShowInputStatusReq(peer.chatType, eventType, peer.peerUid); - } + async getSourceOfReplyMsgV2 (peer: Peer, clientSeq: string, time: string) { + return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time); + } - async getSourceOfReplyMsgV2(peer: Peer, clientSeq: string, time: string) { - return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time); - } + async getMsgEmojiLikesList (peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) { + // 注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa + return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count); + } - async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) { - //注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa - return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count); - } + async setEmojiLike (peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { + emojiId = emojiId.toString(); + return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set); + } - async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { - emojiId = emojiId.toString(); - return this.context.session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set); - } + async getMultiMsg (peer: Peer, rootMsgId: string, parentMsgId: string): Promise { + return this.context.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId); + } - async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string): Promise { - return this.context.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId); - } + async ForwardMsg (peer: Peer, msgIds: string[]) { + return this.context.session.getMsgService().forwardMsg(msgIds, peer, [peer], new Map()); + } - async ForwardMsg(peer: Peer, msgIds: string[]) { - return this.context.session.getMsgService().forwardMsg(msgIds, peer, [peer], new Map()); - } + async getMsgsByMsgId (peer: Peer | undefined, msgIds: string[] | undefined) { + if (!peer) throw new Error('peer is not allowed'); + if (!msgIds) throw new Error('msgIds is not allowed'); + // MliKiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得 + return await this.context.session.getMsgService().getMsgsByMsgId(peer, msgIds); + } - async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) { - if (!peer) throw new Error('peer is not allowed'); - if (!msgIds) throw new Error('msgIds is not allowed'); - //MliKiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得 - return await this.context.session.getMsgService().getMsgsByMsgId(peer, msgIds); - } + async getSingleMsg (peer: Peer, seq: string) { + return await this.context.session.getMsgService().getSingleMsg(peer, seq); + } - async getSingleMsg(peer: Peer, seq: string) { - return await this.context.session.getMsgService().getSingleMsg(peer, seq); - } + async fetchFavEmojiList (num: number) { + return this.context.session.getMsgService().fetchFavEmojiList('', num, true, true); + } - async fetchFavEmojiList(num: number) { - return this.context.session.getMsgService().fetchFavEmojiList('', num, true, true); - } + async queryMsgsWithFilterExWithSeq (peer: Peer, msgSeq: string) { + return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { + chatInfo: peer, + // searchFields: 3, + filterMsgType: [], + filterSendersUid: [], + filterMsgToTime: '0', + filterMsgFromTime: '0', + isReverseOrder: false, + isIncludeCurrent: true, + pageLimit: 1, + }); + } - async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) { - return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { - chatInfo: peer, - //searchFields: 3, - filterMsgType: [], - filterSendersUid: [], - filterMsgToTime: '0', - filterMsgFromTime: '0', - isReverseOrder: false, - isIncludeCurrent: true, - pageLimit: 1, - }); - } - async queryMsgsWithFilterExWithSeqV2(peer: Peer, msgSeq: string, MsgTime: string, SendersUid: string[]) { - return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { - chatInfo: peer, - filterMsgType: [], - //searchFields: 3, - filterSendersUid: SendersUid, - filterMsgToTime: MsgTime, - filterMsgFromTime: MsgTime, - isReverseOrder: false, - isIncludeCurrent: true, - pageLimit: 1, - }); - } - async queryMsgsWithFilterExWithSeqV3(peer: Peer, msgSeq: string, SendersUid: string[]) { - return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { - chatInfo: peer, - filterMsgType: [], - filterSendersUid: SendersUid, - filterMsgToTime: '0', - filterMsgFromTime: '0', - isReverseOrder: false, - //searchFields: 3, - isIncludeCurrent: true, - pageLimit: 1, - }); - } - async queryFirstMsgBySeq(peer: Peer, msgSeq: string) { - return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { - chatInfo: peer, - filterMsgType: [], - filterSendersUid: [], - filterMsgToTime: '0', - //searchFields: 3, - filterMsgFromTime: '0', - isReverseOrder: true, - isIncludeCurrent: true, - pageLimit: 1, - }); - } - // 客户端还在用别慌 - async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) { - return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder); - } - async getMsgExBySeq(peer: Peer, msgSeq: string) { - const DateNow = Math.floor(Date.now() / 1000); - const filterMsgFromTime = (DateNow - 300).toString(); - const filterMsgToTime = DateNow.toString(); - return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { - chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa - filterMsgType: [], - filterSendersUid: [], - //searchFields: 3, - filterMsgToTime: filterMsgToTime, - filterMsgFromTime: filterMsgFromTime, - isReverseOrder: false, - isIncludeCurrent: true, - pageLimit: 100, - }); - } + async queryMsgsWithFilterExWithSeqV2 (peer: Peer, msgSeq: string, MsgTime: string, SendersUid: string[]) { + return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { + chatInfo: peer, + filterMsgType: [], + // searchFields: 3, + filterSendersUid: SendersUid, + filterMsgToTime: MsgTime, + filterMsgFromTime: MsgTime, + isReverseOrder: false, + isIncludeCurrent: true, + pageLimit: 1, + }); + } - async queryFirstMsgBySender(peer: Peer, SendersUid: string[]) { - return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', { - chatInfo: peer, - filterMsgType: [], - filterSendersUid: SendersUid, - //searchFields: 3, - filterMsgToTime: '0', - filterMsgFromTime: '0', - isReverseOrder: true, - isIncludeCurrent: true, - pageLimit: 20000, - }); - } + async queryMsgsWithFilterExWithSeqV3 (peer: Peer, msgSeq: string, SendersUid: string[]) { + return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { + chatInfo: peer, + filterMsgType: [], + filterSendersUid: SendersUid, + filterMsgToTime: '0', + filterMsgFromTime: '0', + isReverseOrder: false, + // searchFields: 3, + isIncludeCurrent: true, + pageLimit: 1, + }); + } - async setMsgRead(peer: Peer) { - return this.context.session.getMsgService().setMsgRead(peer); - } + async queryFirstMsgBySeq (peer: Peer, msgSeq: string) { + return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { + chatInfo: peer, + filterMsgType: [], + filterSendersUid: [], + filterMsgToTime: '0', + // searchFields: 3, + filterMsgFromTime: '0', + isReverseOrder: true, + isIncludeCurrent: true, + pageLimit: 1, + }); + } - async getGroupFileList(GroupCode: string, params: GetFileListParam) { - const item: GroupFileInfoUpdateItem[] = []; - let index = params.startIndex; - while (true) { - params.startIndex = index; - const [, groupFileListResult] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelRichMediaService/getGroupFileList', - 'NodeIKernelMsgListener/onGroupFileInfoUpdate', - [ - GroupCode, - params, - ], - () => true, - () => true, // 应当通过 groupFileListResult 判断 - 1, - 5000, - ); - if (!groupFileListResult?.item?.length) break; - item.push(...groupFileListResult.item); - if (groupFileListResult.isEnd) break; - if (item.length === params.fileCount) break; - index = groupFileListResult.nextIndex; + // 客户端还在用别慌 + async getMsgsBySeqAndCount (peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean) { + return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, isReverseOrder); + } + + async getMsgExBySeq (peer: Peer, msgSeq: string) { + const DateNow = Math.floor(Date.now() / 1000); + const filterMsgFromTime = (DateNow - 300).toString(); + const filterMsgToTime = DateNow.toString(); + return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { + chatInfo: peer, // 此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa + filterMsgType: [], + filterSendersUid: [], + // searchFields: 3, + filterMsgToTime, + filterMsgFromTime, + isReverseOrder: false, + isIncludeCurrent: true, + pageLimit: 100, + }); + } + + async queryFirstMsgBySender (peer: Peer, SendersUid: string[]) { + return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', { + chatInfo: peer, + filterMsgType: [], + filterSendersUid: SendersUid, + // searchFields: 3, + filterMsgToTime: '0', + filterMsgFromTime: '0', + isReverseOrder: true, + isIncludeCurrent: true, + pageLimit: 20000, + }); + } + + async setMsgRead (peer: Peer) { + return this.context.session.getMsgService().setMsgRead(peer); + } + + async getGroupFileList (GroupCode: string, params: GetFileListParam) { + const item: GroupFileInfoUpdateItem[] = []; + let index = params.startIndex; + while (true) { + params.startIndex = index; + const [, groupFileListResult] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelRichMediaService/getGroupFileList', + 'NodeIKernelMsgListener/onGroupFileInfoUpdate', + [ + GroupCode, + params, + ], + () => true, + () => true, // 应当通过 groupFileListResult 判断 + 1, + 5000 + ); + if (!groupFileListResult?.item?.length) break; + item.push(...groupFileListResult.item); + if (groupFileListResult.isEnd) break; + if (item.length === params.fileCount) break; + index = groupFileListResult.nextIndex; + } + return item; + } + + async getMsgHistory (peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) { + // 消息时间从旧到新 + return this.context.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder); + } + + async recallMsg (peer: Peer, msgId: string) { + return await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelMsgService/recallMsg', + 'NodeIKernelMsgListener/onMsgInfoListUpdate', + [peer, [msgId]], + () => true, + (updatedList) => updatedList.find(m => m.msgId === msgId && m.recallTime !== '0') !== undefined, + 1, + 1000 + ); + } + + async PrepareTempChat (toUserUid: string, GroupCode: string, nickname: string) { + return this.context.session.getMsgService().prepareTempChat({ + chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, + peerUid: toUserUid, + peerNickname: nickname, + fromGroupCode: GroupCode, + sig: '', + selfPhone: '', + selfUid: this.core.selfInfo.uid, + gameSession: { + nickname: '', + gameAppId: '', + selfTinyId: '', + peerRoleId: '', + peerOpenId: '', + }, + }); + } + + async getTempChatInfo (chatType: ChatType, peerUid: string) { + return this.context.session.getMsgService().getTempChatInfo(chatType, peerUid); + } + + async sendMsg (peer: Peer, msgElements: SendMessageElement[], timeout = 10000) { + // 唉?!我有个想法 + if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') { + const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid); + if (member) { + await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick); + } + } + const msgId = await this.generateMsgUniqueId(peer.chatType); + peer.guildId = msgId; + const [, msgList] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelMsgService/sendMsg', + 'NodeIKernelMsgListener/onMsgInfoListUpdate', + [ + '0', + peer, + msgElements, + new Map(), + ], + (ret) => ret.result === 0, + msgRecords => { + for (const msgRecord of msgRecords) { + if (msgRecord.guildId === msgId && msgRecord.sendStatus === SendStatusType.KSEND_STATUS_SUCCESS) { + return true; + } } - return item; - } + return false; + }, + 1, + timeout + ); + return msgList.find(msgRecord => msgRecord.guildId === msgId); + } - async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) { - // 消息时间从旧到新 - return this.context.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder); - } + async generateMsgUniqueId (chatType: number) { + return this.context.session.getMsgService().generateMsgUniqueId(chatType, this.context.session.getMSFService().getServerTime()); + } - async recallMsg(peer: Peer, msgId: string) { - return await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelMsgService/recallMsg', - 'NodeIKernelMsgListener/onMsgInfoListUpdate', - [peer, [msgId]], - () => true, - (updatedList) => updatedList.find(m => m.msgId === msgId && m.recallTime !== '0') !== undefined, - 1, - 1000, - ); - } + async forwardMsg (srcPeer: Peer, destPeer: Peer, msgIds: string[]) { + return this.context.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map()); + } - async PrepareTempChat(toUserUid: string, GroupCode: string, nickname: string) { - return this.context.session.getMsgService().prepareTempChat({ - chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, - peerUid: toUserUid, - peerNickname: nickname, - fromGroupCode: GroupCode, - sig: '', - selfPhone: '', - selfUid: this.core.selfInfo.uid, - gameSession: { - nickname: '', - gameAppId: '', - selfTinyId: '', - peerRoleId: '', - peerOpenId: '', - }, - }); + async multiForwardMsg (srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise { + const msgInfos = msgIds.map(id => { + return { msgId: id, senderShowName: this.core.selfInfo.nick }; + }); + const [, msgList] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelMsgService/multiForwardMsgWithComment', + 'NodeIKernelMsgListener/onMsgInfoListUpdate', + [ + msgInfos, + srcPeer, + destPeer, + [], + new Map(), + ], + () => true, + (msgRecords) => msgRecords.some( + msgRecord => msgRecord.peerUid === destPeer.peerUid && + msgRecord.senderUid === this.core.selfInfo.uid + ) + ); + for (const msg of msgList) { + const arkElement = msg.elements.find(ele => ele.arkElement); + if (!arkElement) { + continue; + } + const forwardData: { app: string } = JSON.parse(arkElement.arkElement?.bytesData ?? ''); + if (forwardData.app != 'com.tencent.multimsg') { + continue; + } + if (msg.peerUid == destPeer.peerUid && msg.senderUid == this.core.selfInfo.uid) { + return msg; + } } + throw new Error('转发消息超时'); + } - async getTempChatInfo(chatType: ChatType, peerUid: string) { - return this.context.session.getMsgService().getTempChatInfo(chatType, peerUid); - } - - async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) { - //唉?!我有个想法 - if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') { - const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid); - if (member) { - await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick); - } - } - const msgId = await this.generateMsgUniqueId(peer.chatType); - peer.guildId = msgId; - const [, msgList] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelMsgService/sendMsg', - 'NodeIKernelMsgListener/onMsgInfoListUpdate', - [ - '0', - peer, - msgElements, - new Map(), - ], - (ret) => ret.result === 0, - msgRecords => { - for (const msgRecord of msgRecords) { - if (msgRecord.guildId === msgId && msgRecord.sendStatus === SendStatusType.KSEND_STATUS_SUCCESS) { - return true; - } - } - return false; - }, - 1, - timeout, - ); - return msgList.find(msgRecord => msgRecord.guildId === msgId); - } - - async generateMsgUniqueId(chatType: number) { - return this.context.session.getMsgService().generateMsgUniqueId(chatType, this.context.session.getMSFService().getServerTime()); - } - - async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { - return this.context.session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], new Map()); - } - - async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise { - const msgInfos = msgIds.map(id => { - return { msgId: id, senderShowName: this.core.selfInfo.nick }; - }); - const [, msgList] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelMsgService/multiForwardMsgWithComment', - 'NodeIKernelMsgListener/onMsgInfoListUpdate', - [ - msgInfos, - srcPeer, - destPeer, - [], - new Map(), - ], - () => true, - (msgRecords) => msgRecords.some( - msgRecord => msgRecord.peerUid === destPeer.peerUid - && msgRecord.senderUid === this.core.selfInfo.uid - ), - ); - for (const msg of msgList) { - const arkElement = msg.elements.find(ele => ele.arkElement); - if (!arkElement) { - continue; - } - const forwardData: { app: string } = JSON.parse(arkElement.arkElement?.bytesData ?? ''); - if (forwardData.app != 'com.tencent.multimsg') { - continue; - } - if (msg.peerUid == destPeer.peerUid && msg.senderUid == this.core.selfInfo.uid) { - return msg; - } - } - throw new Error('转发消息超时'); - } - - async markAllMsgAsRead() { - return this.context.session.getMsgService().setAllC2CAndGroupMsgRead(); - } + async markAllMsgAsRead () { + return this.context.session.getMsgService().setAllC2CAndGroupMsgRead(); + } } diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index 197213ef..6a0d66bd 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -6,73 +6,73 @@ import { PacketClientSession } from '@/core/packet/clientSession'; import { napCatVersion } from '@/common/version'; interface OffsetType { - [key: string]: { - recv: string; - send: string; - }; + [key: string]: { + recv: string; + send: string; + }; } const typedOffset: OffsetType = offset; export class NTQQPacketApi { - context: InstanceContext; - core: NapCatCore; - logger: LogWrapper; - qqVersion: string | undefined; - pkt!: PacketClientSession; - errStack: string[] = []; - packetStatus: boolean = false; + context: InstanceContext; + core: NapCatCore; + logger: LogWrapper; + qqVersion: string | undefined; + pkt!: PacketClientSession; + errStack: string[] = []; + packetStatus: boolean = false; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - this.logger = core.context.logger; - } + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + this.logger = core.context.logger; + } - async initApi() { - this.packetStatus = (await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVersion()) - .then((result) => { - return result; - }) - .catch((err) => { - this.logger.logError(err); - this.errStack.push(err); - return false; - })) && this.pkt?.available; - } + async initApi () { + this.packetStatus = (await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVersion()) + .then((result) => { + return result; + }) + .catch((err) => { + this.logger.logError(err); + this.errStack.push(err); + return false; + })) && this.pkt?.available; + } - get available(): boolean { - return this.pkt?.available ?? false; - } + get available (): boolean { + return this.pkt?.available ?? false; + } - get clientLogStack() { - return this.pkt?.clientLogStack + '\n' + this.errStack.join('\n'); - } + get clientLogStack () { + return this.pkt?.clientLogStack + '\n' + this.errStack.join('\n'); + } - async InitSendPacket(qqVer: string) { - this.qqVersion = qqVer; - const table = typedOffset[qqVer + '-' + os.arch()]; - if (!table) { - const err = `[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqVer}-${os.arch()}, + async InitSendPacket (qqVer: string) { + this.qqVersion = qqVer; + const table = typedOffset[qqVer + '-' + os.arch()]; + if (!table) { + const err = `[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqVer}-${os.arch()}, 请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本!`; - this.logger.logError(err); - this.errStack.push(err); - return false; - } - if (this.core.configLoader.configData.packetBackend === 'disable') { - const err = '[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!'; - this.logger.logError(err); - this.errStack.push(err); - return false; - } - this.pkt = new PacketClientSession(this.core); - await this.pkt.init(process.pid, table.recv, table.send); - try { - await this.pkt.operation.FetchRkey(1500); - } catch (error) { - this.logger.logError('测试Packet状态异常', error); - return false; - } - return true; + this.logger.logError(err); + this.errStack.push(err); + return false; } + if (this.core.configLoader.configData.packetBackend === 'disable') { + const err = '[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!'; + this.logger.logError(err); + this.errStack.push(err); + return false; + } + this.pkt = new PacketClientSession(this.core); + await this.pkt.init(process.pid, table.recv, table.send); + try { + await this.pkt.operation.FetchRkey(1500); + } catch (error) { + this.logger.logError('测试Packet状态异常', error); + return false; + } + return true; + } } diff --git a/src/core/apis/system.ts b/src/core/apis/system.ts index 53245a77..572d6b81 100644 --- a/src/core/apis/system.ts +++ b/src/core/apis/system.ts @@ -1,36 +1,36 @@ import { InstanceContext, NapCatCore } from '@/core'; export class NTQQSystemApi { - context: InstanceContext; - core: NapCatCore; + context: InstanceContext; + core: NapCatCore; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } - async hasOtherRunningQQProcess() { - return this.core.util.hasOtherRunningQQProcess(); - } + async hasOtherRunningQQProcess () { + return this.core.util.hasOtherRunningQQProcess(); + } - async ocrImage(filePath: string) { - return this.context.session.getNodeMiscService().wantWinScreenOCR(filePath); - } + async ocrImage (filePath: string) { + return this.context.session.getNodeMiscService().wantWinScreenOCR(filePath); + } - async translateEnWordToZn(words: string[]) { - return this.context.session.getRichMediaService().translateEnWordToZn(words); - } + async translateEnWordToZn (words: string[]) { + return this.context.session.getRichMediaService().translateEnWordToZn(words); + } - async getOnlineDev() { - this.context.session.getMsgService().getOnLineDev(); - } + async getOnlineDev () { + this.context.session.getMsgService().getOnLineDev(); + } - async getArkJsonCollection() { - return await this.core.eventWrapper.callNoListenerEvent('NodeIKernelCollectionService/collectionArkShare', '1717662698058'); - } + async getArkJsonCollection () { + return await this.core.eventWrapper.callNoListenerEvent('NodeIKernelCollectionService/collectionArkShare', '1717662698058'); + } - async bootMiniApp(appFile: string, params: string) { - await this.context.session.getNodeMiscService().setMiniAppVersion('2.16.4'); - return this.context.session.getNodeMiscService().startNewMiniApp(appFile, params); - } + async bootMiniApp (appFile: string, params: string) { + await this.context.session.getNodeMiscService().setMiniAppVersion('2.16.4'); + return this.context.session.getNodeMiscService().startNewMiniApp(appFile, params); + } } diff --git a/src/core/apis/user.ts b/src/core/apis/user.ts index 1bb2ef42..cf0ac2c4 100644 --- a/src/core/apis/user.ts +++ b/src/core/apis/user.ts @@ -5,243 +5,244 @@ import { solveAsyncProblem } from '@/common/helper'; import { Fallback, FallbackUtil } from '@/common/fall-back'; export class NTQQUserApi { - context: InstanceContext; - core: NapCatCore; + context: InstanceContext; + core: NapCatCore; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } - async getCoreAndBaseInfo(uids: string[]) { - return await this.core.eventWrapper.callNoListenerEvent( - 'NodeIKernelProfileService/getCoreAndBaseInfo', - 'nodeStore', - uids, - ); - } + async getCoreAndBaseInfo (uids: string[]) { + return await this.core.eventWrapper.callNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', + 'nodeStore', + uids + ); + } - // 默认获取自己的 type = 2 获取别人 type = 1 - async getProfileLike(uid: string, start: number, count: number, type: number = 2) { - return this.context.session.getProfileLikeService().getBuddyProfileLike({ - friendUids: [uid], - basic: 1, - vote: 1, - favorite: 0, - userProfile: 1, - type: type, - start: start, - limit: count, - }); - } - async setLongNick(longNick: string) { - return this.context.session.getProfileService().setLongNick(longNick); - } + // 默认获取自己的 type = 2 获取别人 type = 1 + async getProfileLike (uid: string, start: number, count: number, type: number = 2) { + return this.context.session.getProfileLikeService().getBuddyProfileLike({ + friendUids: [uid], + basic: 1, + vote: 1, + favorite: 0, + userProfile: 1, + type, + start, + limit: count, + }); + } - async setSelfOnlineStatus(status: number, extStatus: number, batteryStatus: number) { - return this.context.session.getMsgService().setStatus({ - status: status, - extStatus: extStatus, - batteryStatus: batteryStatus, - }); - } + async setLongNick (longNick: string) { + return this.context.session.getProfileService().setLongNick(longNick); + } - async setDiySelfOnlineStatus(faceId: string, wording: string, faceType: string) { - return this.context.session.getMsgService().setStatus({ - status: 10, - extStatus: 2000, - customStatus: { faceId: faceId, wording: wording, faceType: faceType }, - batteryStatus: 0 - }); - } + async setSelfOnlineStatus (status: number, extStatus: number, batteryStatus: number) { + return this.context.session.getMsgService().setStatus({ + status, + extStatus, + batteryStatus, + }); + } - async getBuddyRecommendContactArkJson(uin: string, sencenID = '') { - return this.context.session.getBuddyService().getBuddyRecommendContactArkJson(uin, sencenID); - } + async setDiySelfOnlineStatus (faceId: string, wording: string, faceType: string) { + return this.context.session.getMsgService().setStatus({ + status: 10, + extStatus: 2000, + customStatus: { faceId, wording, faceType }, + batteryStatus: 0, + }); + } - async like(uid: string, count = 1): Promise<{ result: number, errMsg: string, succCounts: number }> { - return this.context.session.getProfileLikeService().setBuddyProfileLike({ - friendUid: uid, - sourceId: 71, - doLikeCount: count, - doLikeTollCount: 0, - }); - } + async getBuddyRecommendContactArkJson (uin: string, sencenID = '') { + return this.context.session.getBuddyService().getBuddyRecommendContactArkJson(uin, sencenID); + } - async setQQAvatar(filePath: string) { - const ret = await this.context.session.getProfileService().setHeader(filePath); - return { result: ret?.result, errMsg: ret?.errMsg }; - } + async like (uid: string, count = 1): Promise<{ result: number, errMsg: string, succCounts: number }> { + return this.context.session.getProfileLikeService().setBuddyProfileLike({ + friendUid: uid, + sourceId: 71, + doLikeCount: count, + doLikeTollCount: 0, + }); + } - async setGroupAvatar(gc: string, filePath: string) { - return this.context.session.getGroupService().setHeader(gc, filePath); - } + async setQQAvatar (filePath: string) { + const ret = await this.context.session.getProfileService().setHeader(filePath); + return { result: ret?.result, errMsg: ret?.errMsg }; + } - async fetchUserDetailInfo(uid: string, mode: UserDetailSource = UserDetailSource.KDB) { - const [, profile] = await this.core.eventWrapper.callNormalEventV2( - 'NodeIKernelProfileService/fetchUserDetailInfo', - 'NodeIKernelProfileListener/onUserDetailInfoChanged', - [ - 'BuddyProfileStore', - [uid], - mode, - [ProfileBizType.KALL], - ], - () => true, - (profile) => profile.uid === uid, - ); - return profile; - } + async setGroupAvatar (gc: string, filePath: string) { + return this.context.session.getGroupService().setHeader(gc, filePath); + } - async getUserDetailInfo(uid: string, no_cache: boolean = false): Promise { - let profile = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, no_cache ? UserDetailSource.KSERVER : UserDetailSource.KDB), uid); - if (profile && profile.uin !== '0' && profile.commonExt) { - return { - ...profile.simpleInfo.status, - ...profile.simpleInfo.vasInfo, - ...profile.commonExt, - ...profile.simpleInfo.baseInfo, - ...profile.simpleInfo.coreInfo, - qqLevel: profile.commonExt?.qqLevel, - age: profile.simpleInfo.baseInfo.age, - pendantId: '', - nick: profile.simpleInfo.coreInfo.nick || '', - }; - } - this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.'); - profile = await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER); - if (profile && profile.uin === '0') { - profile.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0'; - } - return { - ...profile.simpleInfo.status, - ...profile.simpleInfo.vasInfo, - ...profile.commonExt, - ...profile.simpleInfo.baseInfo, - ...profile.simpleInfo.coreInfo, - qqLevel: profile.commonExt?.qqLevel, - age: profile.simpleInfo.baseInfo.age, - pendantId: '', - nick: profile.simpleInfo.coreInfo.nick || '', - }; - } + async fetchUserDetailInfo (uid: string, mode: UserDetailSource = UserDetailSource.KDB) { + const [, profile] = await this.core.eventWrapper.callNormalEventV2( + 'NodeIKernelProfileService/fetchUserDetailInfo', + 'NodeIKernelProfileListener/onUserDetailInfoChanged', + [ + 'BuddyProfileStore', + [uid], + mode, + [ProfileBizType.KALL], + ], + () => true, + (profile) => profile.uid === uid + ); + return profile; + } - async modifySelfProfile(param: ModifyProfileParams) { - return this.context.session.getProfileService().modifyDesktopMiniProfile(param); + async getUserDetailInfo (uid: string, no_cache: boolean = false): Promise { + let profile = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, no_cache ? UserDetailSource.KSERVER : UserDetailSource.KDB), uid); + if (profile && profile.uin !== '0' && profile.commonExt) { + return { + ...profile.simpleInfo.status, + ...profile.simpleInfo.vasInfo, + ...profile.commonExt, + ...profile.simpleInfo.baseInfo, + ...profile.simpleInfo.coreInfo, + qqLevel: profile.commonExt?.qqLevel, + age: profile.simpleInfo.baseInfo.age, + pendantId: '', + nick: profile.simpleInfo.coreInfo.nick || '', + }; } + this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.'); + profile = await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER); + if (profile && profile.uin === '0') { + profile.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0'; + } + return { + ...profile.simpleInfo.status, + ...profile.simpleInfo.vasInfo, + ...profile.commonExt, + ...profile.simpleInfo.baseInfo, + ...profile.simpleInfo.coreInfo, + qqLevel: profile.commonExt?.qqLevel, + age: profile.simpleInfo.baseInfo.age, + pendantId: '', + nick: profile.simpleInfo.coreInfo.nick || '', + }; + } - async getCookies(domain: string) { - const ClientKeyData = await this.forceFetchClientKey(); - const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + + async modifySelfProfile (param: ModifyProfileParams) { + return this.context.session.getProfileService().modifyDesktopMiniProfile(param); + } + + async getCookies (domain: string) { + const ClientKeyData = await this.forceFetchClientKey(); + const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27'; - const data = await RequestUtil.HttpsGetCookies(requestUrl); - if (!data['p_skey'] || data['p_skey'].length == 0) { - try { - const pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain); - if (pskey) data['p_skey'] = pskey; - } catch { - return data; - } - } + const data = await RequestUtil.HttpsGetCookies(requestUrl); + if (!data['p_skey'] || data['p_skey'].length == 0) { + try { + const pskey = (await this.getPSkey([domain])).domainPskeyMap.get(domain); + if (pskey) data['p_skey'] = pskey; + } catch { return data; + } + } + return data; + } + + async getPSkey (domainList: string[]) { + return await this.context.session.getTipOffService().getPskey(domainList, true); + } + + async getRobotUinRange (): Promise> { + const robotUinRanges = await this.context.session.getRobotService().getRobotUinRange({ + justFetchMsgConfig: '1', + type: 1, + version: 0, + aioKeywordVersion: 0, + }); + return robotUinRanges?.response?.robotUinRanges; + } + + // 需要异常处理 + + async getQzoneCookies () { + const ClientKeyData = await this.forceFetchClientKey(); + const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27'; + return await RequestUtil.HttpsGetCookies(requestUrl); + } + + // 需要异常处理 + + async getSKey (): Promise { + const ClientKeyData = await this.forceFetchClientKey(); + if (ClientKeyData.result !== 0) { + throw new Error('getClientKey Error'); + } + const clientKey = ClientKeyData.clientKey; + // const keyIndex = ClientKeyData.keyIndex; + const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + clientKey + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=19%27'; + const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl); + const skey = cookies['skey']; + if (!skey) { + throw new Error('SKey is Empty'); } - async getPSkey(domainList: string[]) { - return await this.context.session.getTipOffService().getPskey(domainList, true); + return skey; + } + + async getUidByUinV2 (uin: string) { + if (!uin) { + return ''; } - async getRobotUinRange(): Promise> { - const robotUinRanges = await this.context.session.getRobotService().getRobotUinRange({ - justFetchMsgConfig: '1', - type: 1, - version: 0, - aioKeywordVersion: 0, - }); - return robotUinRanges?.response?.robotUinRanges; - } - - //需要异常处理 - - async getQzoneCookies() { - const ClientKeyData = await this.forceFetchClientKey(); - const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + this.core.selfInfo.uin + '%2Finfocenter&keyindex=19%27'; - return await RequestUtil.HttpsGetCookies(requestUrl); - } - - //需要异常处理 - - async getSKey(): Promise { - const ClientKeyData = await this.forceFetchClientKey(); - if (ClientKeyData.result !== 0) { - throw new Error('getClientKey Error'); - } - const clientKey = ClientKeyData.clientKey; - // const keyIndex = ClientKeyData.keyIndex; - const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + this.core.selfInfo.uin + '&clientkey=' + clientKey + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=19%27'; - const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl); - const skey = cookies['skey']; - if (!skey) { - throw new Error('SKey is Empty'); - } - - return skey; - } - - async getUidByUinV2(uin: string) { - if (!uin) { - return ''; - } - - const fallback = + const fallback = new Fallback((uid) => FallbackUtil.boolchecker(uid, uid !== undefined && uid.indexOf('*') === -1 && uid !== '')) - .add(() => this.context.session.getUixConvertService().getUid([uin]).then((data) => data.uidInfo.get(uin))) - .add(() => this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [uin]).get(uin)) - .add(() => this.context.session.getGroupService().getUidByUins([uin]).then((data) => data.uids.get(uin))) - .add(() => this.getUserDetailInfoByUin(uin).then((data) => data.detail.uid)); + .add(() => this.context.session.getUixConvertService().getUid([uin]).then((data) => data.uidInfo.get(uin))) + .add(() => this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [uin]).get(uin)) + .add(() => this.context.session.getGroupService().getUidByUins([uin]).then((data) => data.uids.get(uin))) + .add(() => this.getUserDetailInfoByUin(uin).then((data) => data.detail.uid)); - const uid = await fallback.run().catch(() => ''); - return uid ?? ''; + const uid = await fallback.run().catch(() => ''); + return uid ?? ''; + } + + async getUinByUidV2 (uid: string) { + if (!uid) { + return '0'; } - async getUinByUidV2(uid: string) { - if (!uid) { - return '0'; - } + const fallback = new Fallback((uin) => FallbackUtil.boolchecker(uin, uin !== undefined && uin !== '0' && uin !== '')) + .add(() => this.context.session.getUixConvertService().getUin([uid]).then((data) => data.uinInfo.get(uid))) + .add(() => this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [uid]).get(uid)) + .add(() => this.context.session.getGroupService().getUinByUids([uid]).then((data) => data.uins.get(uid))) + .add(() => this.getUserDetailInfo(uid).then((data) => data.uin)); - const fallback = new Fallback((uin) => FallbackUtil.boolchecker(uin, uin !== undefined && uin !== '0' && uin !== '')) - .add(() => this.context.session.getUixConvertService().getUin([uid]).then((data) => data.uinInfo.get(uid))) - .add(() => this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [uid]).get(uid)) - .add(() => this.context.session.getGroupService().getUinByUids([uid]).then((data) => data.uins.get(uid))) - .add(() => this.getUserDetailInfo(uid).then((data) => data.uin)); + const uin = await fallback.run().catch(() => '0'); + return uin ?? '0'; + } - const uin = await fallback.run().catch(() => '0'); - return uin ?? '0'; - } + async getRecentContactListSnapShot (count: number) { + return await this.context.session.getRecentContactService().getRecentContactListSnapShot(count); + } - async getRecentContactListSnapShot(count: number) { - return await this.context.session.getRecentContactService().getRecentContactListSnapShot(count); - } + async getRecentContactListSyncLimit (count: number) { + return await this.context.session.getRecentContactService().getRecentContactListSyncLimit(count); + } - async getRecentContactListSyncLimit(count: number) { - return await this.context.session.getRecentContactService().getRecentContactListSyncLimit(count); - } + async getRecentContactListSync () { + return await this.context.session.getRecentContactService().getRecentContactListSync(); + } - async getRecentContactListSync() { - return await this.context.session.getRecentContactService().getRecentContactListSync(); - } + async getRecentContactList () { + return await this.context.session.getRecentContactService().getRecentContactList(); + } - async getRecentContactList() { - return await this.context.session.getRecentContactService().getRecentContactList(); - } + async getUserDetailInfoByUin (Uin: string) { + return await this.core.eventWrapper.callNoListenerEvent( + 'NodeIKernelProfileService/getUserDetailInfoByUin', + Uin + ); + } - async getUserDetailInfoByUin(Uin: string) { - return await this.core.eventWrapper.callNoListenerEvent( - 'NodeIKernelProfileService/getUserDetailInfoByUin', - Uin - ); - } - - async forceFetchClientKey() { - return await this.context.session.getTicketService().forceFetchClientKey(''); - } + async forceFetchClientKey () { + return await this.context.session.getTicketService().forceFetchClientKey(''); + } } diff --git a/src/core/apis/webapi.ts b/src/core/apis/webapi.ts index cede8d2e..8abae88a 100644 --- a/src/core/apis/webapi.ts +++ b/src/core/apis/webapi.ts @@ -1,79 +1,81 @@ import { RequestUtil } from '@/common/request'; import { - GroupEssenceMsgRet, - InstanceContext, - WebApiGroupMember, - WebApiGroupMemberRet, - WebApiGroupNoticeRet, - WebHonorType, + GroupEssenceMsgRet, + InstanceContext, + WebApiGroupMember, + WebApiGroupMemberRet, + WebApiGroupNoticeRet, + WebHonorType, NapCatCore, } from '@/core'; -import { NapCatCore } from '..'; + import { createReadStream, readFileSync, statSync } from 'node:fs'; import { createHash } from 'node:crypto'; import { basename } from 'node:path'; import { qunAlbumControl } from '../data/webapi'; import { createAlbumCommentRequest, createAlbumFeedPublish, createAlbumMediaFeed } from '../data/album'; export class NTQQWebApi { - context: InstanceContext; - core: NapCatCore; + context: InstanceContext; + core: NapCatCore; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } + constructor (context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } - async shareDigest(groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) { - const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${new URLSearchParams({ + async shareDigest (groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) { + const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); + const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${new URLSearchParams({ bkn: this.getBknFromCookie(cookieObject), group_code: groupCode, msg_seq: msgSeq, msg_random: msgRandom, target_group_code: targetGroupCode, }).toString()}`; - try { - return RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': this.cookieToString(cookieObject) }); - } catch { - return undefined; - } + try { + return RequestUtil.HttpGetText(url, 'GET', '', { Cookie: this.cookieToString(cookieObject) }); + } catch { + return undefined; } - async getGroupEssenceMsgAll(GroupCode: string) { - const ret: GroupEssenceMsgRet[] = []; - for (let i = 0; i < 20; i++) { - const data = await this.getGroupEssenceMsg(GroupCode, i, 50); - if (!data) break; - ret.push(data); - if (data.data.is_end) break; - } - return ret; + } + + async getGroupEssenceMsgAll (GroupCode: string) { + const ret: GroupEssenceMsgRet[] = []; + for (let i = 0; i < 20; i++) { + const data = await this.getGroupEssenceMsg(GroupCode, i, 50); + if (!data) break; + ret.push(data); + if (data.data.is_end) break; } - async getGroupEssenceMsg(GroupCode: string, page_start: number = 0, page_limit: number = 50) { - const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({ + return ret; + } + + async getGroupEssenceMsg (GroupCode: string, page_start: number = 0, page_limit: number = 50) { + const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); + const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({ bkn: this.getBknFromCookie(cookieObject), page_start: page_start.toString(), page_limit: page_limit.toString(), group_code: GroupCode, }).toString()}`; - try { - const ret = await RequestUtil.HttpGetJson( - url, - 'GET', - '', - { 'Cookie': this.cookieToString(cookieObject) } - ); - return ret.retcode === 0 ? ret : undefined; - } catch { - return undefined; - } + try { + const ret = await RequestUtil.HttpGetJson( + url, + 'GET', + '', + { Cookie: this.cookieToString(cookieObject) } + ); + return ret.retcode === 0 ? ret : undefined; + } catch { + return undefined; } + } - async getGroupMembers(GroupCode: string): Promise { - //logDebug('webapi 获取群成员', GroupCode); - const memberData: Array = new Array(); - const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - const retList: Promise[] = []; - const fastRet = await RequestUtil.HttpGetJson( + async getGroupMembers (GroupCode: string): Promise { + // logDebug('webapi 获取群成员', GroupCode); + const memberData: Array = new Array(); + const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); + const retList: Promise[] = []; + const fastRet = await RequestUtil.HttpGetJson( `https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({ st: '0', end: '40', @@ -83,22 +85,22 @@ export class NTQQWebApi { }).toString()}`, 'POST', '', - { 'Cookie': this.cookieToString(cookieObject) } - ); - if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) { - return []; - } else { - for (const key in fastRet.mems) { - if (fastRet.mems[key]) { - memberData.push(fastRet.mems[key]); - } - } + { Cookie: this.cookieToString(cookieObject) } + ); + if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) { + return []; + } else { + for (const key in fastRet.mems) { + if (fastRet.mems[key]) { + memberData.push(fastRet.mems[key]); } - //初始化获取PageNum - const PageNum = Math.ceil(fastRet.count / 40); - //遍历批量请求 - for (let i = 2; i <= PageNum; i++) { - const ret = RequestUtil.HttpGetJson( + } + } + // 初始化获取PageNum + const PageNum = Math.ceil(fastRet.count / 40); + // 遍历批量请求 + for (let i = 2; i <= PageNum; i++) { + const ret = RequestUtil.HttpGetJson( `https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({ st: ((i - 1) * 40).toString(), end: (i * 40).toString(), @@ -108,97 +110,97 @@ export class NTQQWebApi { }).toString()}`, 'POST', '', - { 'Cookie': this.cookieToString(cookieObject) } - ); - retList.push(ret); + { Cookie: this.cookieToString(cookieObject) } + ); + retList.push(ret); + } + // 批量等待 + for (let i = 1; i <= PageNum; i++) { + const ret = await (retList[i]); + if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) { + continue; + } + for (const key in ret.mems) { + if (ret.mems[key]) { + memberData.push(ret.mems[key]); } - //批量等待 - for (let i = 1; i <= PageNum; i++) { - const ret = await (retList[i]); - if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) { - continue; - } - for (const key in ret.mems) { - if (ret.mems[key]) { - memberData.push(ret.mems[key]); - } - } - } - return memberData; + } + } + return memberData; + } + + // public async addGroupDigest(groupCode: string, msgSeq: string) { + // const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`; + // const res = await this.request(url); + // return await res.json(); + // } + + // public async getGroupDigest(groupCode: string) { + // const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`; + // const res = await this.request(url); + // return await res.json(); + // } + + async setGroupNotice ( + GroupCode: string, + Content: string, + pinned: number = 0, + type: number = 1, + is_show_edit_card: number = 1, + tip_window_type: number = 1, + confirm_required: number = 1, + picId: string = '', + imgWidth: number = 540, + imgHeight: number = 300 + ) { + interface SetNoticeRetSuccess { + ec: number; + em: string; + id: number; + ltsm: number; + new_fid: string; + read_only: number; + role: number; + srv_code: number; } - // public async addGroupDigest(groupCode: string, msgSeq: string) { - // const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`; - // const res = await this.request(url); - // return await res.json(); - // } + const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - // public async getGroupDigest(groupCode: string) { - // const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`; - // const res = await this.request(url); - // return await res.json(); - // } - - async setGroupNotice( - GroupCode: string, - Content: string, - pinned: number = 0, - type: number = 1, - is_show_edit_card: number = 1, - tip_window_type: number = 1, - confirm_required: number = 1, - picId: string = '', - imgWidth: number = 540, - imgHeight: number = 300, - ) { - interface SetNoticeRetSuccess { - ec: number; - em: string; - id: number; - ltsm: number; - new_fid: string; - read_only: number; - role: number; - srv_code: number; - } - - const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - - try { - const settings = JSON.stringify({ - is_show_edit_card: is_show_edit_card, - tip_window_type: tip_window_type, - confirm_required: confirm_required - }); - const externalParam = { - pic: picId, - imgWidth: imgWidth.toString(), - imgHeight: imgHeight.toString(), - }; - const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson( + try { + const settings = JSON.stringify({ + is_show_edit_card, + tip_window_type, + confirm_required, + }); + const externalParam = { + pic: picId, + imgWidth: imgWidth.toString(), + imgHeight: imgHeight.toString(), + }; + const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson( `https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({ bkn: this.getBknFromCookie(cookieObject), qid: GroupCode, text: Content, pinned: pinned.toString(), type: type.toString(), - settings: settings, - ...(picId === '' ? {} : externalParam) + settings, + ...(picId === '' ? {} : externalParam), }).toString()}`, 'POST', '', - { 'Cookie': this.cookieToString(cookieObject) } - ); - return ret; - } catch { - return undefined; - } + { Cookie: this.cookieToString(cookieObject) } + ); + return ret; + } catch { + return undefined; } + } - async getGroupNotice(GroupCode: string): Promise { - const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - try { - const ret = await RequestUtil.HttpGetJson( + async getGroupNotice (GroupCode: string): Promise { + const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); + try { + const ret = await RequestUtil.HttpGetJson( `https://web.qun.qq.com/cgi-bin/announce/get_t_list?${new URLSearchParams({ bkn: this.getBknFromCookie(cookieObject), qid: GroupCode, @@ -212,305 +214,309 @@ export class NTQQWebApi { }).toString()}&n=20`, 'GET', '', - { 'Cookie': this.cookieToString(cookieObject) } - ); - return ret?.ec === 0 ? ret : undefined; - } catch { - return undefined; - } + { Cookie: this.cookieToString(cookieObject) } + ); + return ret?.ec === 0 ? ret : undefined; + } catch { + return undefined; } + } - private async getDataInternal(cookieObject: { [key: string]: string }, groupCode: string, type: number) { - let resJson; - try { - const res = await RequestUtil.HttpGetText( + private async getDataInternal (cookieObject: { [key: string]: string }, groupCode: string, type: number) { + let resJson; + try { + const res = await RequestUtil.HttpGetText( `https://qun.qq.com/interactive/honorlist?${new URLSearchParams({ gc: groupCode, type: type.toString(), }).toString()}`, 'GET', '', - { 'Cookie': this.cookieToString(cookieObject) } - ); - const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res); - if (match?.[1]) { - resJson = JSON.parse(match[1].trim()); - } - return type === 1 ? resJson?.talkativeList : resJson?.actorList; - } catch (e) { - this.context.logger.logDebug('获取当前群荣耀失败', e); - return undefined; - } + { Cookie: this.cookieToString(cookieObject) } + ); + const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res); + if (match?.[1]) { + resJson = JSON.parse(match[1].trim()); + } + return type === 1 ? resJson?.talkativeList : resJson?.actorList; + } catch (e) { + this.context.logger.logDebug('获取当前群荣耀失败', e); + return undefined; + } + } + + private async getHonorList (cookieObject: { [key: string]: string }, groupCode: string, type: number) { + const data = await this.getDataInternal(cookieObject, groupCode, type); + if (!data) { + this.context.logger.logError(`获取类型 ${type} 的荣誉信息失败`); + return []; + } + return data.map((item: { + uin: string, + name: string, + avatar: string, + desc: string, + }) => ({ + user_id: item?.uin, + nickname: item?.name, + avatar: item?.avatar, + description: item?.desc, + })); + } + + async getGroupHonorInfo (groupCode: string, getType: WebHonorType) { + const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); + const HonorInfo = { + group_id: Number(groupCode), + current_talkative: {}, + talkative_list: [], + performer_list: [], + legend_list: [], + emotion_list: [], + strong_newbie_list: [], + }; + + if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) { + const talkativeList = await this.getHonorList(cookieObject, groupCode, 1); + if (talkativeList.length > 0) { + HonorInfo.current_talkative = talkativeList[0]; + HonorInfo.talkative_list = talkativeList; + } } - private async getHonorList(cookieObject: { [key: string]: string }, groupCode: string, type: number) { - const data = await this.getDataInternal(cookieObject, groupCode, type); - if (!data) { - this.context.logger.logError(`获取类型 ${type} 的荣誉信息失败`); - return []; - } - return data.map((item: { - uin: string, - name: string, - avatar: string, - desc: string, - }) => ({ - user_id: item?.uin, - nickname: item?.name, - avatar: item?.avatar, - description: item?.desc, - })); + if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) { + HonorInfo.performer_list = await this.getHonorList(cookieObject, groupCode, 2); } - async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { - const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com'); - let HonorInfo = { - group_id: Number(groupCode), - current_talkative: {}, - talkative_list: [], - performer_list: [], - legend_list: [], - emotion_list: [], - strong_newbie_list: [], - }; - - if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) { - const talkativeList = await this.getHonorList(cookieObject, groupCode, 1); - if (talkativeList.length > 0) { - HonorInfo.current_talkative = talkativeList[0]; - HonorInfo.talkative_list = talkativeList; - } - } - - if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) { - HonorInfo.performer_list = await this.getHonorList(cookieObject, groupCode, 2); - } - - if (getType === WebHonorType.LEGEND || getType === WebHonorType.ALL) { - HonorInfo.legend_list = await this.getHonorList(cookieObject, groupCode, 3); - } - - if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { - HonorInfo.emotion_list = await this.getHonorList(cookieObject, groupCode, 6); - } - - // 冒尖小春笋好像已经被tx扬了 R.I.P. - if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { - HonorInfo.strong_newbie_list = []; - } - - return HonorInfo; + if (getType === WebHonorType.LEGEND || getType === WebHonorType.ALL) { + HonorInfo.legend_list = await this.getHonorList(cookieObject, groupCode, 3); } - private cookieToString(cookieObject: { [key: string]: string }) { - return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; '); + if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { + HonorInfo.emotion_list = await this.getHonorList(cookieObject, groupCode, 6); } - public getBknFromCookie(cookieObject: { [key: string]: string }) { - const sKey = cookieObject['skey'] as string; - - let hash = 5381; - for (let i = 0; i < sKey.length; i++) { - const code = sKey.charCodeAt(i); - hash = hash + (hash << 5) + code; - } - return (hash & 0x7FFFFFFF).toString(); - } - public getBknFromSKey(sKey: string) { - let hash = 5381; - for (let i = 0; i < sKey.length; i++) { - const code = sKey.charCodeAt(i); - hash = hash + (hash << 5) + code; - } - return (hash & 0x7FFFFFFF).toString(); - } - async getAlbumListByNTQQ(gc: string) { - return await this.context.session.getAlbumService().getAlbumList({ - qun_id: gc, - attach_info: '', - seq: 3331, - request_time_line: { - request_invoke_time: "0" - } - }) - } - async getAlbumList(gc: string) { - const skey = await this.core.apis.UserApi.getSKey() || ''; - const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || ''; - const bkn = this.getBknFromSKey(skey); - const uin = this.core.selfInfo.uin || '10001'; - const cookies = `p_uin=o${this.core.selfInfo.uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin} `; - const api = `https://h5.qzone.qq.com/proxy/domain/u.photo.qzone.qq.com/cgi-bin/upp/qun_list_album_v2?`; - const params = new URLSearchParams({ - random: '7570', - g_tk: bkn, - format: 'json', - inCharset: 'utf-8', - outCharset: 'utf-8', - qua: 'V1_IPH_SQ_6.2.0_0_HDBM_T', - cmd: 'qunGetAlbumList', - qunId: gc, - qunid: gc, - start: '0', - num: '1000', - uin: uin, - getMemberRole: '0' - }); - const response = await RequestUtil.HttpGetJson<{ data: { album: Array<{ id: string, title: string }> } }>(api + params.toString(), 'GET', '', { - 'Cookie': cookies - }); - return response.data.album; + // 冒尖小春笋好像已经被tx扬了 R.I.P. + if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { + HonorInfo.strong_newbie_list = []; } - async createQunAlbumSession(gc: string, sAlbumID: string, sAlbumName: string, path: string, skey: string, pskey: string, img_md5: string, uin: string) { - const img = readFileSync(path); - const img_size = img.length; - const img_name = basename(path); - const GTK = this.getBknFromSKey(skey); - const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`; - const body = qunAlbumControl({ - uin, - group_id: gc, - pskey, - pic_md5: img_md5, - img_size, - img_name, - sAlbumName: sAlbumName, - sAlbumID: sAlbumID - }); - const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileBatchControl/${img_md5}?g_tk=${GTK}`; - const post = await RequestUtil.HttpGetJson<{ data: { session: string }, ret: number, msg: string }>(api, 'POST', body, { - 'Cookie': cookie, - 'Content-Type': 'application/json' - }); - return post; + return HonorInfo; + } + + private cookieToString (cookieObject: { [key: string]: string }) { + return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; '); + } + + public getBknFromCookie (cookieObject: { [key: string]: string }) { + const sKey = cookieObject['skey'] as string; + + let hash = 5381; + for (let i = 0; i < sKey.length; i++) { + const code = sKey.charCodeAt(i); + hash = hash + (hash << 5) + code; } + return (hash & 0x7FFFFFFF).toString(); + } - async uploadQunAlbumSlice(path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) { - const img_size = statSync(path).size; - let seq = 0; - let offset = 0; - const GTK = this.getBknFromSKey(skey); - const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`; - - const stream = createReadStream(path, { highWaterMark: slice_size }); - - for await (const chunk of stream) { - const end = Math.min(offset + chunk.length, img_size); - const form = new FormData(); - form.append('uin', uin); - form.append('appid', 'qun'); - form.append('session', session); - form.append('offset', offset.toString()); - form.append('data', new Blob([chunk], { type: 'application/octet-stream' }), 'blob'); - form.append('checksum', ''); - form.append('check_type', '0'); - form.append('retry', '0'); - form.append('seq', seq.toString()); - form.append('end', end.toString()); - form.append('cmd', 'FileUpload'); - form.append('slice_size', slice_size.toString()); - form.append('biz_req.iUploadType', '0'); - - const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileUpload?seq=${seq}&retry=0&offset=${offset}&end=${end}&total=${img_size}&type=form&g_tk=${GTK}`; - const response = await fetch(api, { - method: 'POST', - headers: { - 'Cookie': cookie, - }, - body: form - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const post = await response.json() as { ret: number, msg: string }; if (post.ret !== 0) { - throw new Error(`分片 ${seq} 上传失败: ${post.msg}`); - } - offset += chunk.length; - seq++; - } - - return { success: true, message: '上传完成' }; + public getBknFromSKey (sKey: string) { + let hash = 5381; + for (let i = 0; i < sKey.length; i++) { + const code = sKey.charCodeAt(i); + hash = hash + (hash << 5) + code; } + return (hash & 0x7FFFFFFF).toString(); + } - async uploadImageToQunAlbum(gc: string, sAlbumID: string, sAlbumName: string, path: string) { - const skey = await this.core.apis.UserApi.getSKey() || ''; - const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || ''; - const img_md5 = createHash('md5').update(readFileSync(path)).digest('hex'); - const uin = this.core.selfInfo.uin || '10001'; - const session = (await this.createQunAlbumSession(gc, sAlbumID, sAlbumName, path, skey, pskey, img_md5, uin)).data.session; - if (!session) throw new Error('创建群相册会话失败'); - await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 16384); - } - async getAlbumMediaListByNTQQ(gc: string, albumId: string, attach_info: string = '') { - return (await this.context.session.getAlbumService().getMediaList({ - qun_id: gc, - attach_info: attach_info, - seq: 0, - request_time_line: { - request_invoke_time: "0" - }, - album_id: albumId, - lloc: '', - batch_id: '' - })).response; - } + async getAlbumListByNTQQ (gc: string) { + return await this.context.session.getAlbumService().getAlbumList({ + qun_id: gc, + attach_info: '', + seq: 3331, + request_time_line: { + request_invoke_time: '0', + }, + }); + } - async doAlbumMediaPlainCommentByNTQQ( - qunId: string, - albumId: string, - lloc: string, - content: string) { - const random_seq = Math.floor(Math.random() * 9000) + 1000; - const uin = this.core.selfInfo.uin || '10001'; - //16位number数字 - const client_key = Date.now() * 1000 - return await this.context.session.getAlbumService().doQunComment( - random_seq, { - map_info: [], - map_bytes_info: [], - map_user_account: [] + async getAlbumList (gc: string) { + const skey = await this.core.apis.UserApi.getSKey() || ''; + const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || ''; + const bkn = this.getBknFromSKey(skey); + const uin = this.core.selfInfo.uin || '10001'; + const cookies = `p_uin=o${this.core.selfInfo.uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin} `; + const api = 'https://h5.qzone.qq.com/proxy/domain/u.photo.qzone.qq.com/cgi-bin/upp/qun_list_album_v2?'; + const params = new URLSearchParams({ + random: '7570', + g_tk: bkn, + format: 'json', + inCharset: 'utf-8', + outCharset: 'utf-8', + qua: 'V1_IPH_SQ_6.2.0_0_HDBM_T', + cmd: 'qunGetAlbumList', + qunId: gc, + qunid: gc, + start: '0', + num: '1000', + uin, + getMemberRole: '0', + }); + const response = await RequestUtil.HttpGetJson<{ data: { album: Array<{ id: string, title: string }> } }>(api + params.toString(), 'GET', '', { + Cookie: cookies, + }); + return response.data.album; + } + + async createQunAlbumSession (gc: string, sAlbumID: string, sAlbumName: string, path: string, skey: string, pskey: string, img_md5: string, uin: string) { + const img = readFileSync(path); + const img_size = img.length; + const img_name = basename(path); + const GTK = this.getBknFromSKey(skey); + const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`; + const body = qunAlbumControl({ + uin, + group_id: gc, + pskey, + pic_md5: img_md5, + img_size, + img_name, + sAlbumName, + sAlbumID, + }); + const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileBatchControl/${img_md5}?g_tk=${GTK}`; + const post = await RequestUtil.HttpGetJson<{ data: { session: string }, ret: number, msg: string }>(api, 'POST', body, { + Cookie: cookie, + 'Content-Type': 'application/json', + }); + return post; + } + + async uploadQunAlbumSlice (path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) { + const img_size = statSync(path).size; + let seq = 0; + let offset = 0; + const GTK = this.getBknFromSKey(skey); + const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`; + + const stream = createReadStream(path, { highWaterMark: slice_size }); + + for await (const chunk of stream) { + const end = Math.min(offset + chunk.length, img_size); + const form = new FormData(); + form.append('uin', uin); + form.append('appid', 'qun'); + form.append('session', session); + form.append('offset', offset.toString()); + form.append('data', new Blob([chunk], { type: 'application/octet-stream' }), 'blob'); + form.append('checksum', ''); + form.append('check_type', '0'); + form.append('retry', '0'); + form.append('seq', seq.toString()); + form.append('end', end.toString()); + form.append('cmd', 'FileUpload'); + form.append('slice_size', slice_size.toString()); + form.append('biz_req.iUploadType', '0'); + + const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileUpload?seq=${seq}&retry=0&offset=${offset}&end=${end}&total=${img_size}&type=form&g_tk=${GTK}`; + const response = await fetch(api, { + method: 'POST', + headers: { + Cookie: cookie, }, - qunId, - 2, - createAlbumMediaFeed(uin, albumId, lloc), - createAlbumCommentRequest(uin, content, client_key) - ); + body: form, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const post = await response.json() as { ret: number, msg: string }; if (post.ret !== 0) { + throw new Error(`分片 ${seq} 上传失败: ${post.msg}`); + } + offset += chunk.length; + seq++; } - async deleteAlbumMediaByNTQQ( - qunId: string, - albumId: string, - lloc: string) { - const random_seq = Math.floor(Math.random() * 9000) + 1000; - return await this.context.session.getAlbumService().deleteMedias( - random_seq, - qunId, - albumId, - [lloc], - [] - ); - } + return { success: true, message: '上传完成' }; + } - async doAlbumMediaLikeByNTQQ( - qunId: string, - albumId: string, - lloc: string, - id: string) { - const random_seq = Math.floor(Math.random() * 9000) + 1000; - const uin = this.core.selfInfo.uin || '10001'; - return await this.context.session.getAlbumService().doQunLike( - random_seq, { - map_info: [], - map_bytes_info: [], - map_user_account: [] - }, { - id: id, - status: 1 - }, - createAlbumFeedPublish(qunId, uin, albumId, lloc) - ) - } -} \ No newline at end of file + async uploadImageToQunAlbum (gc: string, sAlbumID: string, sAlbumName: string, path: string) { + const skey = await this.core.apis.UserApi.getSKey() || ''; + const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || ''; + const img_md5 = createHash('md5').update(readFileSync(path)).digest('hex'); + const uin = this.core.selfInfo.uin || '10001'; + const session = (await this.createQunAlbumSession(gc, sAlbumID, sAlbumName, path, skey, pskey, img_md5, uin)).data.session; + if (!session) throw new Error('创建群相册会话失败'); + await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 16384); + } + + async getAlbumMediaListByNTQQ (gc: string, albumId: string, attach_info: string = '') { + return (await this.context.session.getAlbumService().getMediaList({ + qun_id: gc, + attach_info, + seq: 0, + request_time_line: { + request_invoke_time: '0', + }, + album_id: albumId, + lloc: '', + batch_id: '', + })).response; + } + + async doAlbumMediaPlainCommentByNTQQ ( + qunId: string, + albumId: string, + lloc: string, + content: string) { + const random_seq = Math.floor(Math.random() * 9000) + 1000; + const uin = this.core.selfInfo.uin || '10001'; + // 16位number数字 + const client_key = Date.now() * 1000; + return await this.context.session.getAlbumService().doQunComment( + random_seq, { + map_info: [], + map_bytes_info: [], + map_user_account: [], + }, + qunId, + 2, + createAlbumMediaFeed(uin, albumId, lloc), + createAlbumCommentRequest(uin, content, client_key) + ); + } + + async deleteAlbumMediaByNTQQ ( + qunId: string, + albumId: string, + lloc: string) { + const random_seq = Math.floor(Math.random() * 9000) + 1000; + return await this.context.session.getAlbumService().deleteMedias( + random_seq, + qunId, + albumId, + [lloc], + [] + ); + } + + async doAlbumMediaLikeByNTQQ ( + qunId: string, + albumId: string, + lloc: string, + id: string) { + const random_seq = Math.floor(Math.random() * 9000) + 1000; + const uin = this.core.selfInfo.uin || '10001'; + return await this.context.session.getAlbumService().doQunLike( + random_seq, { + map_info: [], + map_bytes_info: [], + map_user_account: [], + }, { + id, + status: 1, + }, + createAlbumFeedPublish(qunId, uin, albumId, lloc) + ); + } +} diff --git a/src/core/data/album.ts b/src/core/data/album.ts index 3568a193..1a1176ff 100644 --- a/src/core/data/album.ts +++ b/src/core/data/album.ts @@ -2,15 +2,15 @@ * 群相册列表请求参数接口 */ export interface AlbumListRequest { - qun_id: string; - attach_info: string; - seq: number; - request_time_line: { - request_invoke_time: string; - }; - album_id: string; - lloc: string; - batch_id: string; + qun_id: string; + attach_info: string; + seq: number; + request_time_line: { + request_invoke_time: string; + }; + album_id: string; + lloc: string; + batch_id: string; } /** @@ -20,45 +20,45 @@ export interface AlbumListRequest { * @param seq 请求序列号,默认值为0 * @returns 请求参数对象 */ -export function createAlbumListRequest( - qunId: string, - albumId: string, - seq: number = 0 +export function createAlbumListRequest ( + qunId: string, + albumId: string, + seq: number = 0 ): AlbumListRequest { - return { - qun_id: qunId, - attach_info: "", - seq: seq, - request_time_line: { - request_invoke_time: "0" - }, - album_id: albumId, - lloc: "", - batch_id: "" - }; + return { + qun_id: qunId, + attach_info: '', + seq, + request_time_line: { + request_invoke_time: '0', + }, + album_id: albumId, + lloc: '', + batch_id: '', + }; } /** * 相册媒体项请求接口 */ export interface AlbumMediaFeed { - cell_common: { - time: string; - }; - cell_user_info: { - user: { - uin: string; - }; - }; - cell_media: { - album_id: string; - batch_id: string; - media_items: Array<{ - image: { - lloc: string; - }; - }>; + cell_common: { + time: string; + }; + cell_user_info: { + user: { + uin: string; }; + }; + cell_media: { + album_id: string; + batch_id: string; + media_items: Array<{ + image: { + lloc: string; + }; + }>; + }; } /** @@ -68,58 +68,58 @@ export interface AlbumMediaFeed { * @param lloc * @returns 媒体请求参数对象 */ -export function createAlbumMediaFeed( - uin: string, - albumId: string, - lloc: string +export function createAlbumMediaFeed ( + uin: string, + albumId: string, + lloc: string ): AlbumMediaFeed { - return { - cell_common: { - time: "" + return { + cell_common: { + time: '', + }, + cell_user_info: { + user: { + uin, + }, + }, + cell_media: { + album_id: albumId, + batch_id: '', + media_items: [{ + image: { + lloc, }, - cell_user_info: { - user: { - uin: uin - } - }, - cell_media: { - album_id: albumId, - batch_id: "", - media_items: [{ - image: { - lloc: lloc - } - }] - } - }; + }], + }, + }; } /** * 相册评论内容接口 */ export interface AlbumCommentContent { - type: number; - content: string; - who: number; - uid: string; - name: string; - url: string; + type: number; + content: string; + who: number; + uid: string; + name: string; + url: string; } /** * 相册评论请求接口 */ export interface AlbumCommentReplyContent { - client_key: number; - content: AlbumCommentContent[]; - user: { - uin: string; - }; + client_key: number; + content: AlbumCommentContent[]; + user: { + uin: string; + }; } export enum RichMsgType { - KRICHMSGTYPEPLAINTEXT, - KRICHMSGTYPEAT, - KRICHMSGTYPEURL, - KRICHMSGTYPEMEDIA + KRICHMSGTYPEPLAINTEXT, + KRICHMSGTYPEAT, + KRICHMSGTYPEURL, + KRICHMSGTYPEMEDIA, } /** @@ -129,52 +129,52 @@ export enum RichMsgType { * @param client_key 客户端鉴权密钥 * @returns 评论请求参数对象 */ -export function createAlbumCommentRequest( - uin: string, - content: string, - client_key: number +export function createAlbumCommentRequest ( + uin: string, + content: string, + client_key: number ): AlbumCommentReplyContent { - return { - client_key: client_key, - //暂时只支持纯文本吧 - content: [{ - type: RichMsgType.KRICHMSGTYPEPLAINTEXT, - content: content, - who: 0, - uid: "", - name: "", - url: "" - }], - user: { - uin: uin - } - }; + return { + client_key, + // 暂时只支持纯文本吧 + content: [{ + type: RichMsgType.KRICHMSGTYPEPLAINTEXT, + content, + who: 0, + uid: '', + name: '', + url: '', + }], + user: { + uin, + }, + }; } export interface AlbumFeedLikePublish { - cell_common: { - time: number; - feed_id: string; - }; - cell_user_info: { - user: { - uin: string; - }; - }; - cell_media: { - album_id: string; - batch_id: number; - media_items: Array<{ - type: number; - image: { - lloc: string; - sloc: string; - }; - }>; - }; - cell_qun_info: { - qun_id: string; + cell_common: { + time: number; + feed_id: string; + }; + cell_user_info: { + user: { + uin: string; }; + }; + cell_media: { + album_id: string; + batch_id: number; + media_items: Array<{ + type: number; + image: { + lloc: string; + sloc: string; + }; + }>; + }; + cell_qun_info: { + qun_id: string; + }; } /** @@ -186,36 +186,36 @@ export interface AlbumFeedLikePublish { * @param sloc 信息(可选,默认与lloc相同) * @returns 动态发布请求参数对象 */ -export function createAlbumFeedPublish( - qunId: string, - uin: string, - albumId: string, - lloc: string, - sloc?: string +export function createAlbumFeedPublish ( + qunId: string, + uin: string, + albumId: string, + lloc: string, + sloc?: string ): AlbumFeedLikePublish { - return { - cell_common: { - time: Date.now(), - feed_id: "" + return { + cell_common: { + time: Date.now(), + feed_id: '', + }, + cell_user_info: { + user: { + uin, + }, + }, + cell_media: { + album_id: albumId, + batch_id: 0, + media_items: [{ + type: 0, + image: { + lloc, + sloc: sloc || lloc, }, - cell_user_info: { - user: { - uin: uin - } - }, - cell_media: { - album_id: albumId, - batch_id: 0, - media_items: [{ - type: 0, - image: { - lloc: lloc, - sloc: sloc || lloc - } - }] - }, - cell_qun_info: { - qun_id: qunId - } - }; -} \ No newline at end of file + }], + }, + cell_qun_info: { + qun_id: qunId, + }, + }; +} diff --git a/src/core/data/group.ts b/src/core/data/group.ts index 2b9636e7..991e511b 100644 --- a/src/core/data/group.ts +++ b/src/core/data/group.ts @@ -1,245 +1,248 @@ -import { GroupDetailInfoV2Param, GroupExtInfo, GroupExtFilter } from "../types"; +import { GroupDetailInfoV2Param, GroupExtInfo, GroupExtFilter } from '../types'; -export function createGroupDetailInfoV2Param(group_code: string): GroupDetailInfoV2Param { - return { - groupCode: group_code, - filter: +export function createGroupDetailInfoV2Param (group_code: string): GroupDetailInfoV2Param { + return { + groupCode: group_code, + filter: { - noCodeFingerOpenFlag: 0, - noFingerOpenFlag: 0, - groupName: 0, - classExt: 0, - classText: 0, - fingerMemo: 0, - richFingerMemo: 0, - tagRecord: 0, - groupGeoInfo: + noCodeFingerOpenFlag: 0, + noFingerOpenFlag: 0, + groupName: 0, + classExt: 0, + classText: 0, + fingerMemo: 0, + richFingerMemo: 0, + tagRecord: 0, + groupGeoInfo: { - ownerUid: 0, - setTime: 0, - cityId: 0, - longitude: 0, - latitude: 0, - geoContent: 0, - poiId: 0 + ownerUid: 0, + setTime: 0, + cityId: 0, + longitude: 0, + latitude: 0, + geoContent: 0, + poiId: 0, }, - groupExtAdminNum: 0, - flag: 0, - groupMemo: 0, - groupAioSkinUrl: 0, - groupBoardSkinUrl: 0, - groupCoverSkinUrl: 0, - groupGrade: 0, - activeMemberNum: 0, - certificationType: 0, - certificationText: 0, - groupNewGuideLines: + groupExtAdminNum: 0, + flag: 0, + groupMemo: 0, + groupAioSkinUrl: 0, + groupBoardSkinUrl: 0, + groupCoverSkinUrl: 0, + groupGrade: 0, + activeMemberNum: 0, + certificationType: 0, + certificationText: 0, + groupNewGuideLines: { - enabled: 0, - content: 0 + enabled: 0, + content: 0, }, - groupFace: 0, - addOption: 0, - shutUpTime: 0, - groupTypeFlag: 0, - appPrivilegeFlag: 0, - appPrivilegeMask: 0, - groupExtOnly: + groupFace: 0, + addOption: 0, + shutUpTime: 0, + groupTypeFlag: 0, + appPrivilegeFlag: 0, + appPrivilegeMask: 0, + groupExtOnly: { - tribeId: 0, - moneyForAddGroup: 0 - }, groupSecLevel: 0, - groupSecLevelInfo: 0, - subscriptionUin: 0, - subscriptionUid: "", - allowMemberInvite: 0, - groupQuestion: 0, - groupAnswer: 0, - groupFlagExt3: 0, - groupFlagExt3Mask: 0, - groupOpenAppid: 0, - rootId: 0, - msgLimitFrequency: 0, - hlGuildAppid: 0, - hlGuildSubType: 0, - hlGuildOrgId: 0, - groupFlagExt4: 0, - groupFlagExt4Mask: 0, - groupSchoolInfo: { - location: 0, - grade: 0, - school: 0 + tribeId: 0, + moneyForAddGroup: 0, }, - groupCardPrefix: + groupSecLevel: 0, + groupSecLevelInfo: 0, + subscriptionUin: 0, + subscriptionUid: '', + allowMemberInvite: 0, + groupQuestion: 0, + groupAnswer: 0, + groupFlagExt3: 0, + groupFlagExt3Mask: 0, + groupOpenAppid: 0, + rootId: 0, + msgLimitFrequency: 0, + hlGuildAppid: 0, + hlGuildSubType: 0, + hlGuildOrgId: 0, + groupFlagExt4: 0, + groupFlagExt4Mask: 0, + groupSchoolInfo: { + location: 0, + grade: 0, + school: 0, + }, + groupCardPrefix: { - introduction: 0, - rptPrefix: 0 - }, allianceId: 0, - groupFlagPro1: 0, - groupFlagPro1Mask: 0 + introduction: 0, + rptPrefix: 0, + }, + allianceId: 0, + groupFlagPro1: 0, + groupFlagPro1Mask: 0, }, - modifyInfo: { - noCodeFingerOpenFlag: 0, - noFingerOpenFlag: 0, - groupName: "", - classExt: 0, - classText: "", - fingerMemo: "", - richFingerMemo: "", - tagRecord: [], - groupGeoInfo: { - ownerUid: "", - SetTime: 0, - CityId: 0, - Longitude: "", - Latitude: "", - GeoContent: "", - poiId: "" - }, - groupExtAdminNum: 0, - flag: 0, - groupMemo: "", - groupAioSkinUrl: "", - groupBoardSkinUrl: "", - groupCoverSkinUrl: "", - groupGrade: 0, - activeMemberNum: 0, - certificationType: 0, - certificationText: "", - groupNewGuideLines: { - enabled: false, - content: "" - }, groupFace: 0, - addOption: 0, - shutUpTime: 0, - groupTypeFlag: 0, - appPrivilegeFlag: 0, - appPrivilegeMask: 0, - groupExtOnly: { - tribeId: 0, - moneyForAddGroup: 0 - }, - groupSecLevel: 0, - groupSecLevelInfo: 0, - subscriptionUin: "", - subscriptionUid: "", - allowMemberInvite: 0, - groupQuestion: "", - groupAnswer: "", - groupFlagExt3: 0, - groupFlagExt3Mask: 0, - groupOpenAppid: 0, - rootId: "", - msgLimitFrequency: 0, - hlGuildAppid: 0, - hlGuildSubType: 0, - hlGuildOrgId: 0, - groupFlagExt4: 0, - groupFlagExt4Mask: 0, - groupSchoolInfo: { - location: "", - grade: 0, - school: "" - }, - groupCardPrefix: + modifyInfo: { + noCodeFingerOpenFlag: 0, + noFingerOpenFlag: 0, + groupName: '', + classExt: 0, + classText: '', + fingerMemo: '', + richFingerMemo: '', + tagRecord: [], + groupGeoInfo: { + ownerUid: '', + SetTime: 0, + CityId: 0, + Longitude: '', + Latitude: '', + GeoContent: '', + poiId: '', + }, + groupExtAdminNum: 0, + flag: 0, + groupMemo: '', + groupAioSkinUrl: '', + groupBoardSkinUrl: '', + groupCoverSkinUrl: '', + groupGrade: 0, + activeMemberNum: 0, + certificationType: 0, + certificationText: '', + groupNewGuideLines: { + enabled: false, + content: '', + }, + groupFace: 0, + addOption: 0, + shutUpTime: 0, + groupTypeFlag: 0, + appPrivilegeFlag: 0, + appPrivilegeMask: 0, + groupExtOnly: { + tribeId: 0, + moneyForAddGroup: 0, + }, + groupSecLevel: 0, + groupSecLevelInfo: 0, + subscriptionUin: '', + subscriptionUid: '', + allowMemberInvite: 0, + groupQuestion: '', + groupAnswer: '', + groupFlagExt3: 0, + groupFlagExt3Mask: 0, + groupOpenAppid: 0, + rootId: '', + msgLimitFrequency: 0, + hlGuildAppid: 0, + hlGuildSubType: 0, + hlGuildOrgId: 0, + groupFlagExt4: 0, + groupFlagExt4Mask: 0, + groupSchoolInfo: { + location: '', + grade: 0, + school: '', + }, + groupCardPrefix: { - introduction: "", - rptPrefix: [] + introduction: '', + rptPrefix: [], }, - allianceId: "", - groupFlagPro1: 0, - groupFlagPro1Mask: 0 - } - } + allianceId: '', + groupFlagPro1: 0, + groupFlagPro1Mask: 0, + }, + }; } -export function createGroupExtInfo(group_code: string): GroupExtInfo { - return { - groupCode: group_code, - resultCode: 0, - extInfo: { - groupInfoExtSeq: 0, - reserve: 0, - luckyWordId: '', - lightCharNum: 0, - luckyWord: '', - starId: 0, - essentialMsgSwitch: 0, - todoSeq: 0, - blacklistExpireTime: 0, - isLimitGroupRtc: 0, - companyId: 0, - hasGroupCustomPortrait: 0, - bindGuildId: '', - groupOwnerId: { - memberUin: '', - memberUid: '', - memberQid: '', - }, - essentialMsgPrivilege: 0, - msgEventSeq: '', - inviteRobotSwitch: 0, - gangUpId: '', - qqMusicMedalSwitch: 0, - showPlayTogetherSwitch: 0, - groupFlagPro1: '', - groupBindGuildIds: { - guildIds: [], - }, - viewedMsgDisappearTime: '', - groupExtFlameData: { - switchState: 0, - state: 0, - dayNums: [], - version: 0, - updateTime: '', - isDisplayDayNum: false, - }, - groupBindGuildSwitch: 0, - groupAioBindGuildId: '', - groupExcludeGuildIds: { - guildIds: [], - }, - fullGroupExpansionSwitch: 0, - fullGroupExpansionSeq: '', - inviteRobotMemberSwitch: 0, - inviteRobotMemberExamine: 0, - groupSquareSwitch: 0, - } - } +export function createGroupExtInfo (group_code: string): GroupExtInfo { + return { + groupCode: group_code, + resultCode: 0, + extInfo: { + groupInfoExtSeq: 0, + reserve: 0, + luckyWordId: '', + lightCharNum: 0, + luckyWord: '', + starId: 0, + essentialMsgSwitch: 0, + todoSeq: 0, + blacklistExpireTime: 0, + isLimitGroupRtc: 0, + companyId: 0, + hasGroupCustomPortrait: 0, + bindGuildId: '', + groupOwnerId: { + memberUin: '', + memberUid: '', + memberQid: '', + }, + essentialMsgPrivilege: 0, + msgEventSeq: '', + inviteRobotSwitch: 0, + gangUpId: '', + qqMusicMedalSwitch: 0, + showPlayTogetherSwitch: 0, + groupFlagPro1: '', + groupBindGuildIds: { + guildIds: [], + }, + viewedMsgDisappearTime: '', + groupExtFlameData: { + switchState: 0, + state: 0, + dayNums: [], + version: 0, + updateTime: '', + isDisplayDayNum: false, + }, + groupBindGuildSwitch: 0, + groupAioBindGuildId: '', + groupExcludeGuildIds: { + guildIds: [], + }, + fullGroupExpansionSwitch: 0, + fullGroupExpansionSeq: '', + inviteRobotMemberSwitch: 0, + inviteRobotMemberExamine: 0, + groupSquareSwitch: 0, + }, + }; +} +export function createGroupExtFilter (): GroupExtFilter { + return { + groupInfoExtSeq: 0, + reserve: 0, + luckyWordId: 0, + lightCharNum: 0, + luckyWord: 0, + starId: 0, + essentialMsgSwitch: 0, + todoSeq: 0, + blacklistExpireTime: 0, + isLimitGroupRtc: 0, + companyId: 0, + hasGroupCustomPortrait: 0, + bindGuildId: 0, + groupOwnerId: 0, + essentialMsgPrivilege: 0, + msgEventSeq: 0, + inviteRobotSwitch: 0, + gangUpId: 0, + qqMusicMedalSwitch: 0, + showPlayTogetherSwitch: 0, + groupFlagPro1: 0, + groupBindGuildIds: 0, + viewedMsgDisappearTime: 0, + groupExtFlameData: 0, + groupBindGuildSwitch: 0, + groupAioBindGuildId: 0, + groupExcludeGuildIds: 0, + fullGroupExpansionSwitch: 0, + fullGroupExpansionSeq: 0, + inviteRobotMemberSwitch: 0, + inviteRobotMemberExamine: 0, + groupSquareSwitch: 0, + }; } -export function createGroupExtFilter(): GroupExtFilter { - return { - groupInfoExtSeq: 0, - reserve: 0, - luckyWordId: 0, - lightCharNum: 0, - luckyWord: 0, - starId: 0, - essentialMsgSwitch: 0, - todoSeq: 0, - blacklistExpireTime: 0, - isLimitGroupRtc: 0, - companyId: 0, - hasGroupCustomPortrait: 0, - bindGuildId: 0, - groupOwnerId: 0, - essentialMsgPrivilege: 0, - msgEventSeq: 0, - inviteRobotSwitch: 0, - gangUpId: 0, - qqMusicMedalSwitch: 0, - showPlayTogetherSwitch: 0, - groupFlagPro1: 0, - groupBindGuildIds: 0, - viewedMsgDisappearTime: 0, - groupExtFlameData: 0, - groupBindGuildSwitch: 0, - groupAioBindGuildId: 0, - groupExcludeGuildIds: 0, - fullGroupExpansionSwitch: 0, - fullGroupExpansionSeq: 0, - inviteRobotMemberSwitch: 0, - inviteRobotMemberExamine: 0, - groupSquareSwitch: 0, - } -}; \ No newline at end of file diff --git a/src/core/data/index.ts b/src/core/data/index.ts index 7cd52db8..8a78f593 100644 --- a/src/core/data/index.ts +++ b/src/core/data/index.ts @@ -1 +1 @@ -export * from "./group"; +export * from './group'; diff --git a/src/core/data/webapi.ts b/src/core/data/webapi.ts index 69fff601..0434e8a2 100644 --- a/src/core/data/webapi.ts +++ b/src/core/data/webapi.ts @@ -1,177 +1,176 @@ - export interface ControlReq { - appid?: string; - asy_upload?: number; - biz_req?: BizReq; - check_type?: number; - checksum?: string; - cmd?: string; - env?: Env; - file_len?: number; - model?: number; - session?: string; - token?: Token; - uin?: string; + appid?: string; + asy_upload?: number; + biz_req?: BizReq; + check_type?: number; + checksum?: string; + cmd?: string; + env?: Env; + file_len?: number; + model?: number; + session?: string; + token?: Token; + uin?: string; } export interface BizReq { - iAlbumTypeID: number; - iBatchID: number; - iBitmap: number; - iDistinctUse: number; - iNeedFeeds: number; - iPicHight: number; - iPicWidth: number; - iUploadTime: number; - iUploadType: number; - iUpPicType: number; - iWaterType: number; - mapExt: MapExt; - sAlbumID: string; - sAlbumName: string; - sPicDesc: string; - sPicPath: string; - sPicTitle: string; - stExtendInfo: StExtendInfo; + iAlbumTypeID: number; + iBatchID: number; + iBitmap: number; + iDistinctUse: number; + iNeedFeeds: number; + iPicHight: number; + iPicWidth: number; + iUploadTime: number; + iUploadType: number; + iUpPicType: number; + iWaterType: number; + mapExt: MapExt; + sAlbumID: string; + sAlbumName: string; + sPicDesc: string; + sPicPath: string; + sPicTitle: string; + stExtendInfo: StExtendInfo; } export interface MapExt { - appid: string; - userid: string; + appid: string; + userid: string; } export interface StExtendInfo { - mapParams: MapParams; + mapParams: MapParams; } export interface MapParams { - batch_num: string; - photo_num: string; - video_num: string; + batch_num: string; + photo_num: string; + video_num: string; } export interface Env { - deviceInfo: string; - refer: string; + deviceInfo: string; + refer: string; } export interface Token { - appid: number; - data: string; - type: number; + appid: number; + data: string; + type: number; } -export function qunAlbumControl({ - uin, - group_id, - pskey, - pic_md5, - img_size, - img_name, - sAlbumName, - sAlbumID, - photo_num = "1", - video_num = "0", - batch_num = "1" +export function qunAlbumControl ({ + uin, + group_id, + pskey, + pic_md5, + img_size, + img_name, + sAlbumName, + sAlbumID, + photo_num = '1', + video_num = '0', + batch_num = '1', }: { - uin: string, - group_id: string, - pskey: string, - pic_md5: string, - img_size: number, - img_name: string, - sAlbumName: string, - sAlbumID: string, - photo_num?: string, - video_num?: string, - batch_num?: string + uin: string, + group_id: string, + pskey: string, + pic_md5: string, + img_size: number, + img_name: string, + sAlbumName: string, + sAlbumID: string, + photo_num?: string, + video_num?: string, + batch_num?: string } ): { control_req: ControlReq[] -} { - const timestamp = Math.floor(Date.now() / 1000); + } { + const timestamp = Math.floor(Date.now() / 1000); - return { - control_req: [ - { - uin: uin, - token: { - type: 4, - data: pskey, - appid: 5 - }, - appid: "qun", - checksum: pic_md5, - check_type: 0, - file_len: img_size, - env: { - refer: "qzone", - deviceInfo: "h5" - }, - model: 0, - biz_req: { - sPicTitle: img_name, - sPicDesc: "", - sAlbumName: sAlbumName, - sAlbumID: sAlbumID, - iAlbumTypeID: 0, - iBitmap: 0, - iUploadType: 0, - iUpPicType: 0, - iBatchID: timestamp, - sPicPath: "", - iPicWidth: 0, - iPicHight: 0, - iWaterType: 0, - iDistinctUse: 0, - iNeedFeeds: 1, - iUploadTime: timestamp, - mapExt: { - appid: "qun", - userid: group_id - }, - stExtendInfo: { - mapParams: { - photo_num: photo_num, - video_num: video_num, - batch_num: batch_num - } - } - }, - session: "", - asy_upload: 0, - cmd: "FileUpload" - }] - } + return { + control_req: [ + { + uin, + token: { + type: 4, + data: pskey, + appid: 5, + }, + appid: 'qun', + checksum: pic_md5, + check_type: 0, + file_len: img_size, + env: { + refer: 'qzone', + deviceInfo: 'h5', + }, + model: 0, + biz_req: { + sPicTitle: img_name, + sPicDesc: '', + sAlbumName, + sAlbumID, + iAlbumTypeID: 0, + iBitmap: 0, + iUploadType: 0, + iUpPicType: 0, + iBatchID: timestamp, + sPicPath: '', + iPicWidth: 0, + iPicHight: 0, + iWaterType: 0, + iDistinctUse: 0, + iNeedFeeds: 1, + iUploadTime: timestamp, + mapExt: { + appid: 'qun', + userid: group_id, + }, + stExtendInfo: { + mapParams: { + photo_num, + video_num, + batch_num, + }, + }, + }, + session: '', + asy_upload: 0, + cmd: 'FileUpload', + }], + }; } -export function createStreamUpload( - { - uin, - session, - offset, - seq, - end, - slice_size, - data +export function createStreamUpload ( + { + uin, + session, + offset, + seq, + end, + slice_size, + data, - }: { uin: string, session: string, offset: number, seq: number, end: number, slice_size: number, data: string } + }: { uin: string, session: string, offset: number, seq: number, end: number, slice_size: number, data: string } ) { - return { - uin: uin, - appid: "qun", - session: session, - offset: offset,//分片起始位置 - data: data,//base64编码数据 - checksum: "", - check_type: 0, - retry: 0,//重试次数 - seq: seq,//分片序号 - end: end,//分片结束位置 文件总大小 - cmd: "FileUpload", - slice_size: slice_size,//分片大小16KB 16384 - biz_req: { - iUploadType: 3 - } - }; -} \ No newline at end of file + return { + uin, + appid: 'qun', + session, + offset, // 分片起始位置 + data, // base64编码数据 + checksum: '', + check_type: 0, + retry: 0, // 重试次数 + seq, // 分片序号 + end, // 分片结束位置 文件总大小 + cmd: 'FileUpload', + slice_size, // 分片大小16KB 16384 + biz_req: { + iUploadType: 3, + }, + }; +} diff --git a/src/core/helper/adaptDecoder.ts b/src/core/helper/adaptDecoder.ts index f6298592..8939f318 100644 --- a/src/core/helper/adaptDecoder.ts +++ b/src/core/helper/adaptDecoder.ts @@ -2,60 +2,60 @@ import { NapProtoMsg, ProtoField, ScalarType } from '@napneko/nap-proto-core'; const LikeDetail = { - txt: ProtoField(1, ScalarType.STRING), - uin: ProtoField(3, ScalarType.INT64), - nickname: ProtoField(5, ScalarType.STRING) + txt: ProtoField(1, ScalarType.STRING), + uin: ProtoField(3, ScalarType.INT64), + nickname: ProtoField(5, ScalarType.STRING), }; const LikeMsg = { - times: ProtoField(1, ScalarType.INT32), - time: ProtoField(2, ScalarType.INT32), - detail: ProtoField(3, () => LikeDetail) + times: ProtoField(1, ScalarType.INT32), + time: ProtoField(2, ScalarType.INT32), + detail: ProtoField(3, () => LikeDetail), }; const ProfileLikeSubTip = { - msg: ProtoField(14, () => LikeMsg) + msg: ProtoField(14, () => LikeMsg), }; const ProfileLikeTip = { - msgType: ProtoField(1, ScalarType.INT32), - subType: ProtoField(2, ScalarType.INT32), - content: ProtoField(203, () => ProfileLikeSubTip) + msgType: ProtoField(1, ScalarType.INT32), + subType: ProtoField(2, ScalarType.INT32), + content: ProtoField(203, () => ProfileLikeSubTip), }; const SysMessageHeader = { - PeerNumber: ProtoField(1, ScalarType.UINT32), - PeerString: ProtoField(2, ScalarType.STRING), - Uin: ProtoField(5, ScalarType.UINT32), - Uid: ProtoField(6, ScalarType.STRING, true) + PeerNumber: ProtoField(1, ScalarType.UINT32), + PeerString: ProtoField(2, ScalarType.STRING), + Uin: ProtoField(5, ScalarType.UINT32), + Uid: ProtoField(6, ScalarType.STRING, true), }; const SysMessageMsgSpec = { - msgType: ProtoField(1, ScalarType.UINT32), - subType: ProtoField(2, ScalarType.UINT32), - subSubType: ProtoField(3, ScalarType.UINT32), - msgSeq: ProtoField(5, ScalarType.UINT32), - time: ProtoField(6, ScalarType.UINT32), - msgId: ProtoField(12, ScalarType.UINT64), - other: ProtoField(13, ScalarType.UINT32) + msgType: ProtoField(1, ScalarType.UINT32), + subType: ProtoField(2, ScalarType.UINT32), + subSubType: ProtoField(3, ScalarType.UINT32), + msgSeq: ProtoField(5, ScalarType.UINT32), + time: ProtoField(6, ScalarType.UINT32), + msgId: ProtoField(12, ScalarType.UINT64), + other: ProtoField(13, ScalarType.UINT32), }; const SysMessageBodyWrapper = { - wrappedBody: ProtoField(2, ScalarType.BYTES) + wrappedBody: ProtoField(2, ScalarType.BYTES), }; const SysMessage = { - header: ProtoField(1, () => SysMessageHeader, false, true), - msgSpec: ProtoField(2, () => SysMessageMsgSpec, false, true), - bodyWrapper: ProtoField(3, () => SysMessageBodyWrapper) + header: ProtoField(1, () => SysMessageHeader, false, true), + msgSpec: ProtoField(2, () => SysMessageMsgSpec, false, true), + bodyWrapper: ProtoField(3, () => SysMessageBodyWrapper), }; -export function decodeProfileLikeTip(buffer: Uint8Array) { - const msg = new NapProtoMsg(ProfileLikeTip); - return msg.decode(buffer); +export function decodeProfileLikeTip (buffer: Uint8Array) { + const msg = new NapProtoMsg(ProfileLikeTip); + return msg.decode(buffer); } -export function decodeSysMessage(buffer: Uint8Array) { - const msg = new NapProtoMsg(SysMessage); - return msg.decode(buffer); +export function decodeSysMessage (buffer: Uint8Array) { + const msg = new NapProtoMsg(SysMessage); + return msg.decode(buffer); } diff --git a/src/core/helper/config.ts b/src/core/helper/config.ts index 0c2540c1..fc4da9f1 100644 --- a/src/core/helper/config.ts +++ b/src/core/helper/config.ts @@ -4,19 +4,19 @@ import { Type, Static } from '@sinclair/typebox'; import { AnySchema } from 'ajv'; export const NapcatConfigSchema = Type.Object({ - fileLog: Type.Boolean({ default: false }), - consoleLog: Type.Boolean({ default: true }), - fileLogLevel: Type.String({ default: 'debug' }), - consoleLogLevel: Type.String({ default: 'info' }), - packetBackend: Type.String({ default: 'auto' }), - packetServer: Type.String({ default: '' }), - o3HookMode: Type.Number({ default: 0 }), + fileLog: Type.Boolean({ default: false }), + consoleLog: Type.Boolean({ default: true }), + fileLogLevel: Type.String({ default: 'debug' }), + consoleLogLevel: Type.String({ default: 'info' }), + packetBackend: Type.String({ default: 'auto' }), + packetServer: Type.String({ default: '' }), + o3HookMode: Type.Number({ default: 0 }), }); export type NapcatConfig = Static; export class NapCatConfigLoader extends ConfigBase { - constructor(core: NapCatCore, configPath: string, schema: AnySchema) { - super('napcat', core, configPath, schema); - } + constructor (core: NapCatCore, configPath: string, schema: AnySchema) { + super('napcat', core, configPath, schema); + } } diff --git a/src/core/helper/msg.ts b/src/core/helper/msg.ts index c28858d8..8eec7678 100644 --- a/src/core/helper/msg.ts +++ b/src/core/helper/msg.ts @@ -1,14 +1,14 @@ import { fileTypeFromFile } from 'file-type'; import { PicType } from '../types'; -export async function getFileTypeForSendType(picPath: string): Promise { - const fileTypeResult = (await fileTypeFromFile(picPath))?.ext ?? 'jpg'; - const picTypeMap: { [key: string]: PicType } = { - //'webp': PicType.NEWPIC_WEBP, - 'gif': PicType.NEWPIC_GIF, - // 'png': PicType.NEWPIC_APNG, - // 'jpg': PicType.NEWPIC_JPEG, - // 'jpeg': PicType.NEWPIC_JPEG, - // 'bmp': PicType.NEWPIC_BMP, - }; - return picTypeMap[fileTypeResult] ?? PicType.NEWPIC_JPEG; -} \ No newline at end of file +export async function getFileTypeForSendType (picPath: string): Promise { + const fileTypeResult = (await fileTypeFromFile(picPath))?.ext ?? 'jpg'; + const picTypeMap: { [key: string]: PicType } = { + // 'webp': PicType.NEWPIC_WEBP, + gif: PicType.NEWPIC_GIF, + // 'png': PicType.NEWPIC_APNG, + // 'jpg': PicType.NEWPIC_JPEG, + // 'jpeg': PicType.NEWPIC_JPEG, + // 'bmp': PicType.NEWPIC_BMP, + }; + return picTypeMap[fileTypeResult] ?? PicType.NEWPIC_JPEG; +} diff --git a/src/core/helper/rkey.ts b/src/core/helper/rkey.ts index 47f748d4..202b3adc 100644 --- a/src/core/helper/rkey.ts +++ b/src/core/helper/rkey.ts @@ -2,128 +2,129 @@ import { LogWrapper } from '@/common/log'; import { RequestUtil } from '@/common/request'; interface ServerRkeyData { - group_rkey: string; - private_rkey: string; - expired_time: number; + group_rkey: string; + private_rkey: string; + expired_time: number; } interface OneBotApiRet { - status: string, - retcode: number, - data: ServerRkeyData, - message: string, - wording: string, + status: string, + retcode: number, + data: ServerRkeyData, + message: string, + wording: string, } interface UrlFailureInfo { - count: number; - lastTimestamp: number; + count: number; + lastTimestamp: number; } export class RkeyManager { - serverUrl: string[] = []; - logger: LogWrapper; - private rkeyData: ServerRkeyData = { - group_rkey: '', - private_rkey: '', - expired_time: 0, - }; - private urlFailures: Map = new Map(); - private readonly FAILURE_LIMIT: number = 4; - private readonly ONE_DAY: number = 24 * 60 * 60 * 1000; + serverUrl: string[] = []; + logger: LogWrapper; + private rkeyData: ServerRkeyData = { + group_rkey: '', + private_rkey: '', + expired_time: 0, + }; - constructor(serverUrl: string[], logger: LogWrapper) { - this.logger = logger; - this.serverUrl = serverUrl; + private urlFailures: Map = new Map(); + private readonly FAILURE_LIMIT: number = 4; + private readonly ONE_DAY: number = 24 * 60 * 60 * 1000; + + constructor (serverUrl: string[], logger: LogWrapper) { + this.logger = logger; + this.serverUrl = serverUrl; + } + + async getRkey () { + const availableUrls = this.getAvailableUrls(); + if (availableUrls.length === 0) { + this.logger.logError('[Rkey] 所有服务均已禁用, 图片使用FallBack机制'); + throw new Error('获取rkey失败:所有服务URL均已被禁用'); } - async getRkey() { - const availableUrls = this.getAvailableUrls(); - if (availableUrls.length === 0) { - this.logger.logError('[Rkey] 所有服务均已禁用, 图片使用FallBack机制'); - throw new Error('获取rkey失败:所有服务URL均已被禁用'); - } + if (this.isExpired()) { + try { + await this.refreshRkey(); + } catch (e) { + throw new Error(`${e}`); + } + } + return this.rkeyData; + } - if (this.isExpired()) { - try { - await this.refreshRkey(); - } catch (e) { - throw new Error(`${e}`); - } - } - return this.rkeyData; + private getAvailableUrls (): string[] { + return this.serverUrl.filter(url => !this.isUrlDisabled(url)); + } + + private isUrlDisabled (url: string): boolean { + const failureInfo = this.urlFailures.get(url); + if (!failureInfo) return false; + + const now = new Date().getTime(); + // 如果已经过了一天,重置失败计数 + if (now - failureInfo.lastTimestamp > this.ONE_DAY) { + failureInfo.count = 0; + this.urlFailures.set(url, failureInfo); + return false; } - private getAvailableUrls(): string[] { - return this.serverUrl.filter(url => !this.isUrlDisabled(url)); + return failureInfo.count >= this.FAILURE_LIMIT; + } + + private updateUrlFailure (url: string) { + const now = new Date().getTime(); + const failureInfo = this.urlFailures.get(url) || { count: 0, lastTimestamp: 0 }; + + // 如果已经过了一天,重置失败计数 + if (now - failureInfo.lastTimestamp > this.ONE_DAY) { + failureInfo.count = 1; + } else { + failureInfo.count++; } - private isUrlDisabled(url: string): boolean { - const failureInfo = this.urlFailures.get(url); - if (!failureInfo) return false; + failureInfo.lastTimestamp = now; + this.urlFailures.set(url, failureInfo); - const now = new Date().getTime(); - // 如果已经过了一天,重置失败计数 - if (now - failureInfo.lastTimestamp > this.ONE_DAY) { - failureInfo.count = 0; - this.urlFailures.set(url, failureInfo); - return false; - } + if (failureInfo.count >= this.FAILURE_LIMIT) { + this.logger.logError(`[Rkey] URL ${url} 已被禁用,失败次数达到 ${this.FAILURE_LIMIT} 次`); + } + } - return failureInfo.count >= this.FAILURE_LIMIT; + isExpired (): boolean { + const now = new Date().getTime() / 1000; + return now > this.rkeyData.expired_time; + } + + async refreshRkey () { + const availableUrls = this.getAvailableUrls(); + + if (availableUrls.length === 0) { + this.logger.logError('[Rkey] 所有服务均已禁用'); + throw new Error('获取rkey失败:所有服务URL均已被禁用'); } - private updateUrlFailure(url: string) { - const now = new Date().getTime(); - const failureInfo = this.urlFailures.get(url) || { count: 0, lastTimestamp: 0 }; - - // 如果已经过了一天,重置失败计数 - if (now - failureInfo.lastTimestamp > this.ONE_DAY) { - failureInfo.count = 1; - } else { - failureInfo.count++; + for (const url of availableUrls) { + try { + let temp = await RequestUtil.HttpGetJson(url, 'GET'); + if ('retcode' in temp) { + // 支持Onebot Ret风格 + temp = (temp as unknown as OneBotApiRet).data; } + this.rkeyData = { + group_rkey: temp.group_rkey.slice(6), + private_rkey: temp.private_rkey.slice(6), + expired_time: temp.expired_time, + }; + return; + } catch (e) { + this.logger.logError(`[Rkey] 异常服务 ${url} 异常 / `, e); + this.updateUrlFailure(url); - failureInfo.lastTimestamp = now; - this.urlFailures.set(url, failureInfo); - - if (failureInfo.count >= this.FAILURE_LIMIT) { - this.logger.logError(`[Rkey] URL ${url} 已被禁用,失败次数达到 ${this.FAILURE_LIMIT} 次`); + if (url === availableUrls[availableUrls.length - 1]) { + throw new Error(`获取rkey失败: ${e}`); } + } } - - isExpired(): boolean { - const now = new Date().getTime() / 1000; - return now > this.rkeyData.expired_time; - } - - async refreshRkey() { - const availableUrls = this.getAvailableUrls(); - - if (availableUrls.length === 0) { - this.logger.logError('[Rkey] 所有服务均已禁用'); - throw new Error('获取rkey失败:所有服务URL均已被禁用'); - } - - for (const url of availableUrls) { - try { - let temp = await RequestUtil.HttpGetJson(url, 'GET'); - if ('retcode' in temp) { - // 支持Onebot Ret风格 - temp = (temp as unknown as OneBotApiRet).data; - } - this.rkeyData = { - group_rkey: temp.group_rkey.slice(6), - private_rkey: temp.private_rkey.slice(6), - expired_time: temp.expired_time - }; - return; - } catch (e) { - this.logger.logError(`[Rkey] 异常服务 ${url} 异常 / `, e); - this.updateUrlFailure(url); - - if (url === availableUrls[availableUrls.length - 1]) { - throw new Error(`获取rkey失败: ${e}`); - } - } - } - } -} \ No newline at end of file + } +} diff --git a/src/core/helper/status.ts b/src/core/helper/status.ts index 7aca69a7..b7fd860a 100644 --- a/src/core/helper/status.ts +++ b/src/core/helper/status.ts @@ -2,137 +2,137 @@ import os from 'node:os'; import EventEmitter from 'node:events'; export interface SystemStatus { - cpu: { - model: string, - speed: string - usage: { - system: string - qq: string - }, - core: number + cpu: { + model: string, + speed: string + usage: { + system: string + qq: string }, - memory: { - total: string - usage: { - system: string - qq: string - } - }, - arch: string + core: number + }, + memory: { + total: string + usage: { + system: string + qq: string + } + }, + arch: string } export class StatusHelper { - private psCpuUsage = process.cpuUsage(); - private psCurrentTime = process.hrtime(); - private cpuTimes = os.cpus().map(cpu => cpu.times); + private psCpuUsage = process.cpuUsage(); + private psCurrentTime = process.hrtime(); + private cpuTimes = os.cpus().map(cpu => cpu.times); - private replaceNaN(value: number) { - return isNaN(value) ? 0 : value; - } + private replaceNaN (value: number) { + return isNaN(value) ? 0 : value; + } - private sysCpuInfo() { - const currentTimes = os.cpus().map(cpu => cpu.times); - const { total, active } = currentTimes.map((times, index) => { - const prevTimes = this.cpuTimes[index]; - const totalCurrent = times.user + times.nice + times.sys + times.idle + times.irq; - const totalPrev = (prevTimes?.user ?? 0) + (prevTimes?.nice ?? 0) + (prevTimes?.sys ?? 0) + (prevTimes?.idle ?? 0) + (prevTimes?.irq ?? 0); - const activeCurrent = totalCurrent - times.idle; - const activePrev = totalPrev - (prevTimes?.idle ?? 0); - return { - total: totalCurrent - totalPrev, - active: activeCurrent - activePrev - }; - }).reduce((acc, cur) => ({ - total: acc.total + cur.total, - active: acc.active + cur.active - }), { total: 0, active: 0 }); - this.cpuTimes = currentTimes; - return { - usage: this.replaceNaN(((active / total) * 100)).toFixed(2), - model: os.cpus()[0]?.model ?? 'none', - speed: os.cpus()[0]?.speed ?? 0, - core: os.cpus().length - }; - } + private sysCpuInfo () { + const currentTimes = os.cpus().map(cpu => cpu.times); + const { total, active } = currentTimes.map((times, index) => { + const prevTimes = this.cpuTimes[index]; + const totalCurrent = times.user + times.nice + times.sys + times.idle + times.irq; + const totalPrev = (prevTimes?.user ?? 0) + (prevTimes?.nice ?? 0) + (prevTimes?.sys ?? 0) + (prevTimes?.idle ?? 0) + (prevTimes?.irq ?? 0); + const activeCurrent = totalCurrent - times.idle; + const activePrev = totalPrev - (prevTimes?.idle ?? 0); + return { + total: totalCurrent - totalPrev, + active: activeCurrent - activePrev, + }; + }).reduce((acc, cur) => ({ + total: acc.total + cur.total, + active: acc.active + cur.active, + }), { total: 0, active: 0 }); + this.cpuTimes = currentTimes; + return { + usage: this.replaceNaN(((active / total) * 100)).toFixed(2), + model: os.cpus()[0]?.model ?? 'none', + speed: os.cpus()[0]?.speed ?? 0, + core: os.cpus().length, + }; + } - private sysMemoryUsage() { - const { total, free } = { total: os.totalmem(), free: os.freemem() }; - return ((total - free) / 1024 / 1024).toFixed(2); - } + private sysMemoryUsage () { + const { total, free } = { total: os.totalmem(), free: os.freemem() }; + return ((total - free) / 1024 / 1024).toFixed(2); + } - private qqUsage() { - const mem = process.memoryUsage(); - const numCpus = os.cpus().length; - const usageDiff = process.cpuUsage(this.psCpuUsage); - const endTime = process.hrtime(this.psCurrentTime); - this.psCpuUsage = process.cpuUsage(); - this.psCurrentTime = process.hrtime(); - const usageMS = (usageDiff.user + usageDiff.system) / 1e3; - const totalMS = endTime[0] * 1e3 + endTime[1] / 1e6; - const normPercent = (usageMS / totalMS / numCpus) * 100; - return { - cpu: this.replaceNaN(normPercent).toFixed(2), - memory: ((mem.heapTotal + mem.external + mem.arrayBuffers) / 1024 / 1024).toFixed(2) - }; - } + private qqUsage () { + const mem = process.memoryUsage(); + const numCpus = os.cpus().length; + const usageDiff = process.cpuUsage(this.psCpuUsage); + const endTime = process.hrtime(this.psCurrentTime); + this.psCpuUsage = process.cpuUsage(); + this.psCurrentTime = process.hrtime(); + const usageMS = (usageDiff.user + usageDiff.system) / 1e3; + const totalMS = endTime[0] * 1e3 + endTime[1] / 1e6; + const normPercent = (usageMS / totalMS / numCpus) * 100; + return { + cpu: this.replaceNaN(normPercent).toFixed(2), + memory: ((mem.heapTotal + mem.external + mem.arrayBuffers) / 1024 / 1024).toFixed(2), + }; + } - systemStatus(): SystemStatus { - const qqUsage = this.qqUsage(); - const sysCpuInfo = this.sysCpuInfo(); - return { - cpu: { - core: sysCpuInfo.core, - model: sysCpuInfo.model, - speed: (sysCpuInfo.speed / 1000).toFixed(2), - usage: { - system: sysCpuInfo.usage, - qq: qqUsage.cpu - }, - }, - memory: { - total: (os.totalmem() / 1024 / 1024).toFixed(2), - usage: { - system: this.sysMemoryUsage(), - qq: qqUsage.memory - } - }, - arch: `${os.platform()} ${os.arch()} ${os.release()}` - }; - } + systemStatus (): SystemStatus { + const qqUsage = this.qqUsage(); + const sysCpuInfo = this.sysCpuInfo(); + return { + cpu: { + core: sysCpuInfo.core, + model: sysCpuInfo.model, + speed: (sysCpuInfo.speed / 1000).toFixed(2), + usage: { + system: sysCpuInfo.usage, + qq: qqUsage.cpu, + }, + }, + memory: { + total: (os.totalmem() / 1024 / 1024).toFixed(2), + usage: { + system: this.sysMemoryUsage(), + qq: qqUsage.memory, + }, + }, + arch: `${os.platform()} ${os.arch()} ${os.release()}`, + }; + } } class StatusHelperSubscription extends EventEmitter { - private statusHelper: StatusHelper; - private interval: NodeJS.Timeout | null = null; + private statusHelper: StatusHelper; + private interval: NodeJS.Timeout | null = null; - constructor(time: number = 3000) { - super(); - this.statusHelper = new StatusHelper(); - this.on('newListener', (event: string) => { - if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) { - this.startInterval(time); - } - }); - this.on('removeListener', (event: string) => { - if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) { - this.stopInterval(); - } - }); - } + constructor (time: number = 3000) { + super(); + this.statusHelper = new StatusHelper(); + this.on('newListener', (event: string) => { + if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) { + this.startInterval(time); + } + }); + this.on('removeListener', (event: string) => { + if (event === 'statusUpdate' && this.listenerCount('statusUpdate') === 0) { + this.stopInterval(); + } + }); + } - private startInterval(time: number) { - this.interval ??= setInterval(() => { - const status = this.statusHelper.systemStatus(); - this.emit('statusUpdate', status); - }, time); - } + private startInterval (time: number) { + this.interval ??= setInterval(() => { + const status = this.statusHelper.systemStatus(); + this.emit('statusUpdate', status); + }, time); + } - private stopInterval() { - if (this.interval) { - clearInterval(this.interval); - this.interval = null; - } + private stopInterval () { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; } + } } export const statusHelperSubscription = new StatusHelperSubscription(); diff --git a/src/core/index.ts b/src/core/index.ts index fbe84535..89198090 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,20 +1,20 @@ import { - NTQQFileApi, - NTQQFriendApi, - NTQQGroupApi, - NTQQMsgApi, - NTQQSystemApi, - NTQQUserApi, - NTQQWebApi, + NTQQFileApi, + NTQQFriendApi, + NTQQGroupApi, + NTQQMsgApi, + NTQQSystemApi, + NTQQUserApi, + NTQQWebApi, } from '@/core/apis'; import { NTQQCollectionApi } from '@/core/apis/collection'; import { - NodeIQQNTWrapperSession, - NodeQQNTWrapperUtil, - PlatformType, - VendorType, - WrapperNodeApi, - WrapperSessionInitConfig, + NodeIQQNTWrapperSession, + NodeQQNTWrapperUtil, + PlatformType, + VendorType, + WrapperNodeApi, + WrapperSessionInitConfig, } from '@/core/wrapper'; import { LogLevel, LogWrapper } from '@/common/log'; import { NodeIKernelLoginService } from '@/core/services'; @@ -37,239 +37,241 @@ export * from './services'; export * from './listeners'; export enum NapCatCoreWorkingEnv { - Unknown = 0, - Shell = 1, - Framework = 2, + Unknown = 0, + Shell = 1, + Framework = 2, } -export function loadQQWrapper(QQVersion: string): WrapperNodeApi { - let appPath; - if (os.platform() === 'darwin') { - appPath = path.resolve(path.dirname(process.execPath), '../Resources/app'); - } else if (os.platform() === 'linux') { - appPath = path.resolve(path.dirname(process.execPath), './resources/app'); - } else { - appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`); - } - let wrapperNodePath = path.resolve(appPath, 'wrapper.node'); - if (!fs.existsSync(wrapperNodePath)) { - wrapperNodePath = path.join(appPath, './resources/app/wrapper.node'); - } - //老版本兼容 未来去掉 - if (!fs.existsSync(wrapperNodePath)) { - wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`); - } - const nativemodule: { exports: WrapperNodeApi } = { exports: {} as WrapperNodeApi }; - process.dlopen(nativemodule, wrapperNodePath); - return nativemodule.exports; +export function loadQQWrapper (QQVersion: string): WrapperNodeApi { + let appPath; + if (os.platform() === 'darwin') { + appPath = path.resolve(path.dirname(process.execPath), '../Resources/app'); + } else if (os.platform() === 'linux') { + appPath = path.resolve(path.dirname(process.execPath), './resources/app'); + } else { + appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`); + } + let wrapperNodePath = path.resolve(appPath, 'wrapper.node'); + if (!fs.existsSync(wrapperNodePath)) { + wrapperNodePath = path.join(appPath, './resources/app/wrapper.node'); + } + // 老版本兼容 未来去掉 + if (!fs.existsSync(wrapperNodePath)) { + wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`); + } + const nativemodule: { exports: WrapperNodeApi } = { exports: {} as WrapperNodeApi }; + process.dlopen(nativemodule, wrapperNodePath); + return nativemodule.exports; } -export function getMajorPath(QQVersion: string): string { - // major.node - let appPath; - if (os.platform() === 'darwin') { - appPath = path.resolve(path.dirname(process.execPath), '../Resources/app'); - } else if (os.platform() === 'linux') { - appPath = path.resolve(path.dirname(process.execPath), './resources/app'); - } else { - appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`); - } - let majorPath = path.resolve(appPath, 'major.node'); - if (!fs.existsSync(majorPath)) { - majorPath = path.join(appPath, './resources/app/major.node'); - } - //老版本兼容 未来去掉 - if (!fs.existsSync(majorPath)) { - majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`); - } - return majorPath; +export function getMajorPath (QQVersion: string): string { + // major.node + let appPath; + if (os.platform() === 'darwin') { + appPath = path.resolve(path.dirname(process.execPath), '../Resources/app'); + } else if (os.platform() === 'linux') { + appPath = path.resolve(path.dirname(process.execPath), './resources/app'); + } else { + appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`); + } + let majorPath = path.resolve(appPath, 'major.node'); + if (!fs.existsSync(majorPath)) { + majorPath = path.join(appPath, './resources/app/major.node'); + } + // 老版本兼容 未来去掉 + if (!fs.existsSync(majorPath)) { + majorPath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/major.node`); + } + return majorPath; } export class NapCatCore { - readonly context: InstanceContext; - readonly eventWrapper: NTEventWrapper; - NapCatDataPath: string = ''; - NapCatTempPath: string = ''; - apis: StableNTApiWrapper; - // runtime info, not readonly - selfInfo: SelfInfo; - util: NodeQQNTWrapperUtil; - configLoader: NapCatConfigLoader; + readonly context: InstanceContext; + readonly eventWrapper: NTEventWrapper; + NapCatDataPath: string = ''; + NapCatTempPath: string = ''; + apis: StableNTApiWrapper; + // runtime info, not readonly + selfInfo: SelfInfo; + util: NodeQQNTWrapperUtil; + configLoader: NapCatConfigLoader; - // 通过构造器递过去的 runtime info 应该尽量少 - constructor(context: InstanceContext, selfInfo: SelfInfo) { - this.selfInfo = selfInfo; - this.context = context; - this.util = this.context.wrapper.NodeQQNTWrapperUtil; - this.eventWrapper = new NTEventWrapper(context.session); - this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath,NapcatConfigSchema); - this.apis = { - FileApi: new NTQQFileApi(this.context, this), - SystemApi: new NTQQSystemApi(this.context, this), - CollectionApi: new NTQQCollectionApi(this.context, this), - PacketApi: new NTQQPacketApi(this.context, this), - WebApi: new NTQQWebApi(this.context, this), - FriendApi: new NTQQFriendApi(this.context, this), - MsgApi: new NTQQMsgApi(this.context, this), - UserApi: new NTQQUserApi(this.context, this), - GroupApi: new NTQQGroupApi(this.context, this), - }; + // 通过构造器递过去的 runtime info 应该尽量少 + constructor (context: InstanceContext, selfInfo: SelfInfo) { + this.selfInfo = selfInfo; + this.context = context; + this.util = this.context.wrapper.NodeQQNTWrapperUtil; + this.eventWrapper = new NTEventWrapper(context.session); + this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath, NapcatConfigSchema); + this.apis = { + FileApi: new NTQQFileApi(this.context, this), + SystemApi: new NTQQSystemApi(this.context, this), + CollectionApi: new NTQQCollectionApi(this.context, this), + PacketApi: new NTQQPacketApi(this.context, this), + WebApi: new NTQQWebApi(this.context, this), + FriendApi: new NTQQFriendApi(this.context, this), + MsgApi: new NTQQMsgApi(this.context, this), + UserApi: new NTQQUserApi(this.context, this), + GroupApi: new NTQQGroupApi(this.context, this), + }; + } + + async initCore () { + this.NapCatDataPath = path.join(this.dataPath, 'NapCat'); + fs.mkdirSync(this.NapCatDataPath, { recursive: true }); + this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp'); + // 创建临时目录 + if (!fs.existsSync(this.NapCatTempPath)) { + fs.mkdirSync(this.NapCatTempPath, { recursive: true }); } - async initCore() { - this.NapCatDataPath = path.join(this.dataPath, 'NapCat'); - fs.mkdirSync(this.NapCatDataPath, { recursive: true }); - this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp'); - // 创建临时目录 - if (!fs.existsSync(this.NapCatTempPath)) { - fs.mkdirSync(this.NapCatTempPath, { recursive: true }); - } - //遍历this.apis[i].initApi 如果存在该函数进行async 调用 - for (const apiKey in this.apis) { - const api = this.apis[apiKey as keyof StableNTApiWrapper]; - if ('initApi' in api && typeof api.initApi === 'function') { - await api.initApi(); - } - } - this.initNapCatCoreListeners().then().catch((e) => this.context.logger.logError(e)); - - this.context.logger.setFileLogEnabled( - this.configLoader.configData.fileLog, - ); - this.context.logger.setConsoleLogEnabled( - this.configLoader.configData.consoleLog, - ); - this.context.logger.setFileAndConsoleLogLevel( - this.configLoader.configData.fileLogLevel as LogLevel, - this.configLoader.configData.consoleLogLevel as LogLevel, - ); + // 遍历this.apis[i].initApi 如果存在该函数进行async 调用 + for (const apiKey in this.apis) { + const api = this.apis[apiKey as keyof StableNTApiWrapper]; + if ('initApi' in api && typeof api.initApi === 'function') { + await api.initApi(); + } } - get dataPath(): string { - let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig(); - if (!result) { - result = path.resolve(os.homedir(), './.config/QQ'); - fs.mkdirSync(result, { recursive: true }); - } - return result; + this.initNapCatCoreListeners().then().catch((e) => this.context.logger.logError(e)); + + this.context.logger.setFileLogEnabled( + this.configLoader.configData.fileLog + ); + this.context.logger.setConsoleLogEnabled( + this.configLoader.configData.consoleLog + ); + this.context.logger.setFileAndConsoleLogLevel( + this.configLoader.configData.fileLogLevel as LogLevel, + this.configLoader.configData.consoleLogLevel as LogLevel + ); + } + + get dataPath (): string { + let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig(); + if (!result) { + result = path.resolve(os.homedir(), './.config/QQ'); + fs.mkdirSync(result, { recursive: true }); } + return result; + } - // Renamed from 'InitDataListener' - async initNapCatCoreListeners() { - const msgListener = new NodeIKernelMsgListener(); + // Renamed from 'InitDataListener' + async initNapCatCoreListeners () { + const msgListener = new NodeIKernelMsgListener(); - msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => { - // 下线通知 - this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc); - this.selfInfo.online = false; - }; - msgListener.onRecvMsg = (msgs) => { - msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo)); - }; - msgListener.onAddSendMsg = (msg) => { - this.context.logger.logMessage(msg, this.selfInfo); - }; - this.context.session.getMsgService().addKernelMsgListener( - proxiedListenerOf(msgListener, this.context.logger), - ); + msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => { + // 下线通知 + this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc); + this.selfInfo.online = false; + }; + msgListener.onRecvMsg = (msgs) => { + msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo)); + }; + msgListener.onAddSendMsg = (msg) => { + this.context.logger.logMessage(msg, this.selfInfo); + }; + this.context.session.getMsgService().addKernelMsgListener( + proxiedListenerOf(msgListener, this.context.logger) + ); - const profileListener = new NodeIKernelProfileListener(); - profileListener.onProfileDetailInfoChanged = (profile) => { - if (profile.uid === this.selfInfo.uid) { - Object.assign(this.selfInfo, profile); - } - }; - profileListener.onSelfStatusChanged = (Info: SelfStatusInfo) => { - if (Info.status == 20) { - this.selfInfo.online = false; - this.context.logger.log('账号状态变更为离线'); - } else { - this.selfInfo.online = true; - } - }; - this.context.session.getProfileService().addKernelProfileListener( - proxiedListenerOf(profileListener, this.context.logger), - ); - } + const profileListener = new NodeIKernelProfileListener(); + profileListener.onProfileDetailInfoChanged = (profile) => { + if (profile.uid === this.selfInfo.uid) { + Object.assign(this.selfInfo, profile); + } + }; + profileListener.onSelfStatusChanged = (Info: SelfStatusInfo) => { + if (Info.status == 20) { + this.selfInfo.online = false; + this.context.logger.log('账号状态变更为离线'); + } else { + this.selfInfo.online = true; + } + }; + this.context.session.getProfileService().addKernelProfileListener( + proxiedListenerOf(profileListener, this.context.logger) + ); + } } -export async function genSessionConfig( - guid: string, - QQVersionAppid: string, - QQVersion: string, - selfUin: string, - selfUid: string, - account_path: string +export async function genSessionConfig ( + guid: string, + QQVersionAppid: string, + QQVersion: string, + selfUin: string, + selfUid: string, + account_path: string ): Promise { - const downloadPath = path.join(account_path, 'NapCat', 'temp'); - fs.mkdirSync(downloadPath, { recursive: true }); - const platformMapping: Partial> = { - win32: PlatformType.KWINDOWS, - darwin: PlatformType.KMAC, - linux: PlatformType.KLINUX, - }; - const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS; - return { - selfUin, - selfUid, - desktopPathConfig: { - account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 - }, - clientVer: QQVersion, - a2: '', - d2: '', - d2Key: '', - machineId: '', - platform: systemPlatform, // 3是Windows? - platVer: systemVersion, // 系统版本号, 应该可以固定 - appid: QQVersionAppid, - rdeliveryConfig: { - appKey: '', - systemId: 0, - appId: '', - logicEnvironment: '', - platform: systemPlatform, - language: '', - sdkVersion: '', - userId: '', - appVersion: '', - osVersion: '', - bundleId: '', - serverUrl: '', - fixedAfterHitKeys: [''], - }, - defaultFileDownloadPath: downloadPath, - deviceInfo: { - guid, - buildVer: QQVersion, - localId: 2052, - devName: hostname, - devType: systemName, - vendorName: '', - osVer: systemVersion, - vendorOsName: systemName, - setMute: false, - vendorType: VendorType.KNOSETONIOS, - }, - deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}', - }; + const downloadPath = path.join(account_path, 'NapCat', 'temp'); + fs.mkdirSync(downloadPath, { recursive: true }); + const platformMapping: Partial> = { + win32: PlatformType.KWINDOWS, + darwin: PlatformType.KMAC, + linux: PlatformType.KLINUX, + }; + const systemPlatform = platformMapping[os.platform()] ?? PlatformType.KWINDOWS; + return { + selfUin, + selfUid, + desktopPathConfig: { + account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 + }, + clientVer: QQVersion, + a2: '', + d2: '', + d2Key: '', + machineId: '', + platform: systemPlatform, // 3是Windows? + platVer: systemVersion, // 系统版本号, 应该可以固定 + appid: QQVersionAppid, + rdeliveryConfig: { + appKey: '', + systemId: 0, + appId: '', + logicEnvironment: '', + platform: systemPlatform, + language: '', + sdkVersion: '', + userId: '', + appVersion: '', + osVersion: '', + bundleId: '', + serverUrl: '', + fixedAfterHitKeys: [''], + }, + defaultFileDownloadPath: downloadPath, + deviceInfo: { + guid, + buildVer: QQVersion, + localId: 2052, + devName: hostname, + devType: systemName, + vendorName: '', + osVer: systemVersion, + vendorOsName: systemName, + setMute: false, + vendorType: VendorType.KNOSETONIOS, + }, + deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}', + }; } export interface InstanceContext { - readonly workingEnv: NapCatCoreWorkingEnv; - readonly wrapper: WrapperNodeApi; - readonly session: NodeIQQNTWrapperSession; - readonly logger: LogWrapper; - readonly loginService: NodeIKernelLoginService; - readonly basicInfoWrapper: QQBasicInfoWrapper; - readonly pathWrapper: NapCatPathWrapper; - readonly packetHandler: NativePacketHandler; + readonly workingEnv: NapCatCoreWorkingEnv; + readonly wrapper: WrapperNodeApi; + readonly session: NodeIQQNTWrapperSession; + readonly logger: LogWrapper; + readonly loginService: NodeIKernelLoginService; + readonly basicInfoWrapper: QQBasicInfoWrapper; + readonly pathWrapper: NapCatPathWrapper; + readonly packetHandler: NativePacketHandler; } export interface StableNTApiWrapper { - FileApi: NTQQFileApi, - SystemApi: NTQQSystemApi, - PacketApi: NTQQPacketApi, - CollectionApi: NTQQCollectionApi, - WebApi: NTQQWebApi, - FriendApi: NTQQFriendApi, - MsgApi: NTQQMsgApi, - UserApi: NTQQUserApi, - GroupApi: NTQQGroupApi + FileApi: NTQQFileApi, + SystemApi: NTQQSystemApi, + PacketApi: NTQQPacketApi, + CollectionApi: NTQQCollectionApi, + WebApi: NTQQWebApi, + FriendApi: NTQQFriendApi, + MsgApi: NTQQMsgApi, + UserApi: NTQQUserApi, + GroupApi: NTQQGroupApi } diff --git a/src/core/listeners/NodeIKernelBuddyListener.ts b/src/core/listeners/NodeIKernelBuddyListener.ts index c7f6303f..b4697be8 100644 --- a/src/core/listeners/NodeIKernelBuddyListener.ts +++ b/src/core/listeners/NodeIKernelBuddyListener.ts @@ -3,74 +3,74 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/types'; export type OnBuddyChangeParams = BuddyCategoryType[]; export class NodeIKernelBuddyListener { - onBuddyListChangedV2(_arg: unknown): any { - } + onBuddyListChangedV2 (_arg: unknown): any { + } - onAddBuddyNeedVerify(_arg: unknown): any { - } + onAddBuddyNeedVerify (_arg: unknown): any { + } - onAddMeSettingChanged(_arg: unknown): any { - } + onAddMeSettingChanged (_arg: unknown): any { + } - onAvatarUrlUpdated(_arg: unknown): any { - } + onAvatarUrlUpdated (_arg: unknown): any { + } - onBlockChanged(_arg: unknown): any { - } + onBlockChanged (_arg: unknown): any { + } - onBuddyDetailInfoChange(_arg: unknown): any { - } + onBuddyDetailInfoChange (_arg: unknown): any { + } - onBuddyInfoChange(_arg: unknown): any { - } + onBuddyInfoChange (_arg: unknown): any { + } - onBuddyListChange(_arg: OnBuddyChangeParams): any { - } + onBuddyListChange (_arg: OnBuddyChangeParams): any { + } - onBuddyRemarkUpdated(_arg: unknown): any { - } + onBuddyRemarkUpdated (_arg: unknown): any { + } - onBuddyReqChange(_arg: FriendRequestNotify): any { - } + onBuddyReqChange (_arg: FriendRequestNotify): any { + } - onBuddyReqUnreadCntChange(_arg: unknown): any { - } + onBuddyReqUnreadCntChange (_arg: unknown): any { + } - onCheckBuddySettingResult(_arg: unknown): any { - } + onCheckBuddySettingResult (_arg: unknown): any { + } - onDelBatchBuddyInfos(_arg: unknown): any { - } + onDelBatchBuddyInfos (_arg: unknown): any { + } - onDoubtBuddyReqChange(_arg: - { - reqId: string; - cookie: string; - doubtList: Array<{ - uid: string; - nick: string; - age: number, - sex: number; - commFriendNum: number; - reqTime: string; - msg: string; - source: string; - reason: string; - groupCode: string; - nameMore?: null; - }>; - }): void | Promise { - } + onDoubtBuddyReqChange (_arg: + { + reqId: string; + cookie: string; + doubtList: Array<{ + uid: string; + nick: string; + age: number, + sex: number; + commFriendNum: number; + reqTime: string; + msg: string; + source: string; + reason: string; + groupCode: string; + nameMore?: null; + }>; + }): void | Promise { + } - onDoubtBuddyReqUnreadNumChange(_num: number): void | Promise { - } + onDoubtBuddyReqUnreadNumChange (_num: number): void | Promise { + } - onNickUpdated(_arg: unknown): any { - } + onNickUpdated (_arg: unknown): any { + } - onSmartInfos(_arg: unknown): any { - } + onSmartInfos (_arg: unknown): any { + } - onSpacePermissionInfos(_arg: unknown): any { - } + onSpacePermissionInfos (_arg: unknown): any { + } } diff --git a/src/core/listeners/NodeIKernelFileAssistantListener.ts b/src/core/listeners/NodeIKernelFileAssistantListener.ts index 47256103..a1ef4c3d 100644 --- a/src/core/listeners/NodeIKernelFileAssistantListener.ts +++ b/src/core/listeners/NodeIKernelFileAssistantListener.ts @@ -1,60 +1,60 @@ export class NodeIKernelFileAssistantListener { - onFileStatusChanged(fileStatus: { - id: string, - fileStatus: number, - fileProgress: `${number}`, - fileSize: `${number}`, - fileSpeed: number, - thumbPath: string | null, - filePath: string | null, - }): any { - } + onFileStatusChanged (fileStatus: { + id: string, + fileStatus: number, + fileProgress: `${number}`, + fileSize: `${number}`, + fileSpeed: number, + thumbPath: string | null, + filePath: string | null, + }): any { + } - onSessionListChanged(...args: unknown[]): any { - } + onSessionListChanged (...args: unknown[]): any { + } - onSessionChanged(...args: unknown[]): any { - } + onSessionChanged (...args: unknown[]): any { + } - onFileListChanged(...args: unknown[]): any { - } + onFileListChanged (...args: unknown[]): any { + } - onFileSearch(searchResult: SearchResultWrapper): any { - } + onFileSearch (searchResult: SearchResultWrapper): any { + } } export type SearchResultWrapper = { - searchId: number, - resultId: number, - hasMore: boolean, - resultItems: SearchResultItem[], + searchId: number, + resultId: number, + hasMore: boolean, + resultItems: SearchResultItem[], }; export type SearchResultItem = { - id: string, - fileName: string, - fileNameHits: string[], - fileStatus: number, - fileSize: string, - isSend: boolean, - source: number, - fileTime: string, - expTime: string, - session: { - context: null, - uid: string, - nick: string, - remark: string, - memberCard: string, - groupCode: string, - groupName: string, - groupRemark: string, - count: number, - }, - thumbPath: string, - filePath: string, - msgId: string, - chatType: number, - peerUid: string, - fileType: number, + id: string, + fileName: string, + fileNameHits: string[], + fileStatus: number, + fileSize: string, + isSend: boolean, + source: number, + fileTime: string, + expTime: string, + session: { + context: null, + uid: string, + nick: string, + remark: string, + memberCard: string, + groupCode: string, + groupName: string, + groupRemark: string, + count: number, + }, + thumbPath: string, + filePath: string, + msgId: string, + chatType: number, + peerUid: string, + fileType: number, }; diff --git a/src/core/listeners/NodeIKernelGroupListener.ts b/src/core/listeners/NodeIKernelGroupListener.ts index 7873f335..75375b98 100644 --- a/src/core/listeners/NodeIKernelGroupListener.ts +++ b/src/core/listeners/NodeIKernelGroupListener.ts @@ -1,85 +1,85 @@ import { DataSource, Group, GroupDetailInfo, GroupListUpdateType, GroupMember, GroupNotify, ShutUpGroupMember } from '@/core/types'; export class NodeIKernelGroupListener { - onGroupListInited(listEmpty: boolean): any { } - // 发现于Win 9.9.9 23159 - onGroupMemberLevelInfoChange(...args: unknown[]): any { + onGroupListInited (listEmpty: boolean): any { } + // 发现于Win 9.9.9 23159 + onGroupMemberLevelInfoChange (...args: unknown[]): any { - } + } - onGetGroupBulletinListResult(...args: unknown[]): any { - } + onGetGroupBulletinListResult (...args: unknown[]): any { + } - onGroupAllInfoChange(...args: unknown[]): any { - } + onGroupAllInfoChange (...args: unknown[]): any { + } - onGroupBulletinChange(...args: unknown[]): any { - } + onGroupBulletinChange (...args: unknown[]): any { + } - onGroupBulletinRemindNotify(...args: unknown[]): any { - } + onGroupBulletinRemindNotify (...args: unknown[]): any { + } - onGroupArkInviteStateResult(...args: unknown[]): any { - } + onGroupArkInviteStateResult (...args: unknown[]): any { + } - onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): any { - } + onGroupBulletinRichMediaDownloadComplete (...args: unknown[]): any { + } - onGroupConfMemberChange(...args: unknown[]): any { - } + onGroupConfMemberChange (...args: unknown[]): any { + } - onGroupDetailInfoChange(detailInfo: GroupDetailInfo): any { - } + onGroupDetailInfoChange (detailInfo: GroupDetailInfo): any { + } - onGroupExtListUpdate(...args: unknown[]): any { - } + onGroupExtListUpdate (...args: unknown[]): any { + } - onGroupFirstBulletinNotify(...args: unknown[]): any { - } + onGroupFirstBulletinNotify (...args: unknown[]): any { + } - onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): any { - } + onGroupListUpdate (updateType: GroupListUpdateType, groupList: Group[]): any { + } - onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): any { - } + onGroupNotifiesUpdated (dboubt: boolean, notifies: GroupNotify[]): any { + } - onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): any { - } + onGroupBulletinRichMediaProgressUpdate (...args: unknown[]): any { + } - onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any { - } + onGroupNotifiesUnreadCountUpdated (...args: unknown[]): any { + } - onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): any { - } + onGroupSingleScreenNotifies (doubt: boolean, seq: string, notifies: GroupNotify[]): any { + } - onGroupsMsgMaskResult(...args: unknown[]): any { - } + onGroupsMsgMaskResult (...args: unknown[]): any { + } - onGroupStatisticInfoChange(...args: unknown[]): any { - } + onGroupStatisticInfoChange (...args: unknown[]): any { + } - onJoinGroupNotify(...args: unknown[]): any { - } + onJoinGroupNotify (...args: unknown[]): any { + } - onJoinGroupNoVerifyFlag(...args: unknown[]): any { - } + onJoinGroupNoVerifyFlag (...args: unknown[]): any { + } - onMemberInfoChange(groupCode: string, dateSource: DataSource, members: Map): any { - } + onMemberInfoChange (groupCode: string, dateSource: DataSource, members: Map): any { + } - onMemberListChange(arg: { - sceneId: string, - ids: string[], - infos: Map, // uid -> GroupMember - hasPrev: boolean, - hasNext: boolean, - hasRobot: boolean - }): any { - } + onMemberListChange (arg: { + sceneId: string, + ids: string[], + infos: Map, // uid -> GroupMember + hasPrev: boolean, + hasNext: boolean, + hasRobot: boolean + }): any { + } - onSearchMemberChange(...args: unknown[]): any { - } + onSearchMemberChange (...args: unknown[]): any { + } - onShutUpMemberListChanged(groupCode: string, members: Array): any { - } -} \ No newline at end of file + onShutUpMemberListChanged (groupCode: string, members: Array): any { + } +} diff --git a/src/core/listeners/NodeIKernelLoginListener.ts b/src/core/listeners/NodeIKernelLoginListener.ts index 51d05711..06bce45e 100644 --- a/src/core/listeners/NodeIKernelLoginListener.ts +++ b/src/core/listeners/NodeIKernelLoginListener.ts @@ -1,67 +1,67 @@ export class NodeIKernelLoginListener { - onLoginConnected(): Promise | void { - } + onLoginConnected (): Promise | void { + } - onLoginDisConnected(...args: any[]): any { - } + onLoginDisConnected (...args: any[]): any { + } - onLoginConnecting(...args: any[]): any { - } + onLoginConnecting (...args: any[]): any { + } - onQRCodeGetPicture(arg: { pngBase64QrcodeData: string, qrcodeUrl: string }): any { - // let base64Data: string = arg.pngBase64QrcodeData - // base64Data = base64Data.split("data:image/png;base64,")[1] - // let buffer = Buffer.from(base64Data, 'base64') - // console.log("onQRCodeGetPicture", arg); - } + onQRCodeGetPicture (arg: { pngBase64QrcodeData: string, qrcodeUrl: string }): any { + // let base64Data: string = arg.pngBase64QrcodeData + // base64Data = base64Data.split("data:image/png;base64,")[1] + // let buffer = Buffer.from(base64Data, 'base64') + // console.log("onQRCodeGetPicture", arg); + } - onQRCodeLoginPollingStarted(...args: any[]): any { - } + onQRCodeLoginPollingStarted (...args: any[]): any { + } - onQRCodeSessionUserScaned(...args: any[]): any { - } + onQRCodeSessionUserScaned (...args: any[]): any { + } - onQRCodeLoginSucceed(arg: QRCodeLoginSucceedResult): any { - } + onQRCodeLoginSucceed (arg: QRCodeLoginSucceedResult): any { + } - onQRCodeSessionFailed(...args: any[]): any { - } + onQRCodeSessionFailed (...args: any[]): any { + } - onLoginFailed(...args: any[]): any { - } + onLoginFailed (...args: any[]): any { + } - onLogoutSucceed(...args: any[]): any { - } + onLogoutSucceed (...args: any[]): any { + } - onLogoutFailed(...args: any[]): any { - } + onLogoutFailed (...args: any[]): any { + } - onUserLoggedIn(...args: any[]): any { - } + onUserLoggedIn (...args: any[]): any { + } - onQRCodeSessionQuickLoginFailed(...args: any[]): any { - } + onQRCodeSessionQuickLoginFailed (...args: any[]): any { + } - onPasswordLoginFailed(...args: any[]): any { - } + onPasswordLoginFailed (...args: any[]): any { + } - OnConfirmUnusualDeviceFailed(...args: any[]): any { - } + OnConfirmUnusualDeviceFailed (...args: any[]): any { + } - onQQLoginNumLimited(...args: any[]): any { - } + onQQLoginNumLimited (...args: any[]): any { + } - onLoginState(...args: any[]): any { - } + onLoginState (...args: any[]): any { + } } export interface QRCodeLoginSucceedResult { - account: string; - mainAccount: string; - uin: string; //拿UIN - uid: string; //拿UID - nickName: string; //一般是空的 拿不到 - gender: number; - age: number; - faceUrl: string;//一般是空的 拿不到 + account: string; + mainAccount: string; + uin: string; // 拿UIN + uid: string; // 拿UID + nickName: string; // 一般是空的 拿不到 + gender: number; + age: number; + faceUrl: string;// 一般是空的 拿不到 } diff --git a/src/core/listeners/NodeIKernelMsgListener.ts b/src/core/listeners/NodeIKernelMsgListener.ts index 6518a82a..af6320fc 100644 --- a/src/core/listeners/NodeIKernelMsgListener.ts +++ b/src/core/listeners/NodeIKernelMsgListener.ts @@ -2,38 +2,38 @@ import { ChatType, KickedOffLineInfo, RawMessage } from '@/core/types'; import { CommonFileInfo } from '@/core'; export interface OnRichMediaDownloadCompleteParams { - fileModelId: string, - msgElementId: string, - msgId: string, - fileId: string, - fileProgress: string, // '0' - fileSpeed: string, // '0' - fileErrCode: string, // '0' - fileErrMsg: string, - fileDownType: number, // 暂时未知 - thumbSize: number, - filePath: string, - totalSize: string, - trasferStatus: number, - step: number, - commonFileInfo?: CommonFileInfo, - fileSrvErrCode: string, - clientMsg: string, - businessId: number, - userTotalSpacePerDay: unknown, - userUsedSpacePerDay: unknown, - chatType: number, + fileModelId: string, + msgElementId: string, + msgId: string, + fileId: string, + fileProgress: string, // '0' + fileSpeed: string, // '0' + fileErrCode: string, // '0' + fileErrMsg: string, + fileDownType: number, // 暂时未知 + thumbSize: number, + filePath: string, + totalSize: string, + trasferStatus: number, + step: number, + commonFileInfo?: CommonFileInfo, + fileSrvErrCode: string, + clientMsg: string, + businessId: number, + userTotalSpacePerDay: unknown, + userUsedSpacePerDay: unknown, + chatType: number, } export interface GroupFileInfoUpdateParamType { - retCode: number; - retMsg: string; - clientWording: string; - isEnd: boolean; - item: Array; - allFileCount: number; - nextIndex: number; - reqId: number; + retCode: number; + retMsg: string; + clientWording: string; + isEnd: boolean; + item: Array; + allFileCount: number; + nextIndex: number; + reqId: number; } // { @@ -46,342 +46,341 @@ export interface GroupFileInfoUpdateParamType { // } export interface GroupFileInfoUpdateItem { - peerId: string; - type: number; - folderInfo?: { - folderId: string; - parentFolderId: string; - folderName: string; - createTime: number; - modifyTime: number; - createUin: string; - creatorName: string; - totalFileCount: number; - modifyUin: string; - modifyName: string; - usedSpace: string; - }, - fileInfo?: { - fileModelId: string; - fileId: string; - fileName: string; - fileSize: string; - busId: number; - uploadedSize: string; - uploadTime: number; - deadTime: number; - modifyTime: number; - downloadTimes: number; - sha: string; - sha3: string; - md5: string; - uploaderLocalPath: string; - uploaderName: string; - uploaderUin: string; - parentFolderId: string; - localPath: string; - transStatus: number; - transType: number; - elementId: string; - isFolder: boolean; - }, + peerId: string; + type: number; + folderInfo?: { + folderId: string; + parentFolderId: string; + folderName: string; + createTime: number; + modifyTime: number; + createUin: string; + creatorName: string; + totalFileCount: number; + modifyUin: string; + modifyName: string; + usedSpace: string; + }, + fileInfo?: { + fileModelId: string; + fileId: string; + fileName: string; + fileSize: string; + busId: number; + uploadedSize: string; + uploadTime: number; + deadTime: number; + modifyTime: number; + downloadTimes: number; + sha: string; + sha3: string; + md5: string; + uploaderLocalPath: string; + uploaderName: string; + uploaderUin: string; + parentFolderId: string; + localPath: string; + transStatus: number; + transType: number; + elementId: string; + isFolder: boolean; + }, } export interface TempOnRecvParams { - sessionType: number,//1 - chatType: ChatType,//100 - peerUid: string,//uid - groupCode: string,//gc - fromNick: string,//gc name - sig: string, + sessionType: number, // 1 + chatType: ChatType, // 100 + peerUid: string, // uid + groupCode: string, // gc + fromNick: string, // gc name + sig: string, } export class NodeIKernelMsgListener { - onAddSendMsg(_msgRecord: RawMessage): any { + onAddSendMsg (_msgRecord: RawMessage): any { - } + } - onBroadcastHelperDownloadComplete(_broadcastHelperTransNotifyInfo: unknown): any { + onBroadcastHelperDownloadComplete (_broadcastHelperTransNotifyInfo: unknown): any { - } + } - onBroadcastHelperProgressUpdate(_broadcastHelperTransNotifyInfo: unknown): any { + onBroadcastHelperProgressUpdate (_broadcastHelperTransNotifyInfo: unknown): any { - } + } - onChannelFreqLimitInfoUpdate(_contact: unknown, _z: unknown, _freqLimitInfo: unknown): any { + onChannelFreqLimitInfoUpdate (_contact: unknown, _z: unknown, _freqLimitInfo: unknown): any { - } + } - onContactUnreadCntUpdate(_hashMap: unknown): any { + onContactUnreadCntUpdate (_hashMap: unknown): any { - } + } - onCustomWithdrawConfigUpdate(_customWithdrawConfig: unknown): any { + onCustomWithdrawConfigUpdate (_customWithdrawConfig: unknown): any { - } + } - onDraftUpdate(_contact: unknown, _arrayList: unknown, _j2: unknown): any { + onDraftUpdate (_contact: unknown, _arrayList: unknown, _j2: unknown): any { - } + } - onEmojiDownloadComplete(_emojiNotifyInfo: unknown): any { + onEmojiDownloadComplete (_emojiNotifyInfo: unknown): any { - } + } - onEmojiResourceUpdate(_emojiResourceInfo: unknown): any { + onEmojiResourceUpdate (_emojiResourceInfo: unknown): any { - } + } - onFeedEventUpdate(_firstViewDirectMsgNotifyInfo: unknown): any { + onFeedEventUpdate (_firstViewDirectMsgNotifyInfo: unknown): any { - } + } - onFileMsgCome(_arrayList: unknown): any { + onFileMsgCome (_arrayList: unknown): any { - } + } - onFirstViewDirectMsgUpdate(_firstViewDirectMsgNotifyInfo: unknown): any { + onFirstViewDirectMsgUpdate (_firstViewDirectMsgNotifyInfo: unknown): any { - } + } - onFirstViewGroupGuildMapping(_arrayList: unknown): any { + onFirstViewGroupGuildMapping (_arrayList: unknown): any { - } + } - onGrabPasswordRedBag(_i2: unknown, _str: unknown, _i3: unknown, _recvdOrder: unknown, _msgRecord: unknown): any { + onGrabPasswordRedBag (_i2: unknown, _str: unknown, _i3: unknown, _recvdOrder: unknown, _msgRecord: unknown): any { - } + } - onGroupFileInfoAdd(_groupItem: unknown): any { + onGroupFileInfoAdd (_groupItem: unknown): any { - } + } - onGroupFileInfoUpdate(_groupFileListResult: GroupFileInfoUpdateParamType): any { + onGroupFileInfoUpdate (_groupFileListResult: GroupFileInfoUpdateParamType): any { - } + } - onGroupGuildUpdate(_groupGuildNotifyInfo: unknown): any { + onGroupGuildUpdate (_groupGuildNotifyInfo: unknown): any { - } + } + onGroupTransferInfoAdd (_groupItem: unknown): any { - onGroupTransferInfoAdd(_groupItem: unknown): any { + } - } + onGroupTransferInfoUpdate (_groupFileListResult: unknown): any { - onGroupTransferInfoUpdate(_groupFileListResult: unknown): any { + } - } + onGuildInteractiveUpdate (_guildInteractiveNotificationItem: unknown): any { - onGuildInteractiveUpdate(_guildInteractiveNotificationItem: unknown): any { + } - } + onGuildMsgAbFlagChanged (_guildMsgAbFlag: unknown): any { - onGuildMsgAbFlagChanged(_guildMsgAbFlag: unknown): any { + } - } + onGuildNotificationAbstractUpdate (_guildNotificationAbstractInfo: unknown): any { - onGuildNotificationAbstractUpdate(_guildNotificationAbstractInfo: unknown): any { + } - } + onHitCsRelatedEmojiResult (_downloadRelateEmojiResultInfo: unknown): any { - onHitCsRelatedEmojiResult(_downloadRelateEmojiResultInfo: unknown): any { + } - } + onHitEmojiKeywordResult (_hitRelatedEmojiWordsResult: unknown): any { - onHitEmojiKeywordResult(_hitRelatedEmojiWordsResult: unknown): any { + } - } + onHitRelatedEmojiResult (_relatedWordEmojiInfo: unknown): any { - onHitRelatedEmojiResult(_relatedWordEmojiInfo: unknown): any { + } - } + onImportOldDbProgressUpdate (_importOldDbMsgNotifyInfo: unknown): any { - onImportOldDbProgressUpdate(_importOldDbMsgNotifyInfo: unknown): any { + } - } + onInputStatusPush (_inputStatusInfo: { + chatType: number; + eventType: number; + fromUin: string; + interval: string; + showTime: string; + statusText: string; + timestamp: string; + toUin: string; + }): any { - onInputStatusPush(_inputStatusInfo: { - chatType: number; - eventType: number; - fromUin: string; - interval: string; - showTime: string; - statusText: string; - timestamp: string; - toUin: string; - }): any { + } - } + onKickedOffLine (_kickedInfo: KickedOffLineInfo): any { - onKickedOffLine(_kickedInfo: KickedOffLineInfo): any { + } - } + onLineDev (_arrayList: unknown): any { - onLineDev(_arrayList: unknown): any { + } - } + onLogLevelChanged (_j2: unknown): any { - onLogLevelChanged(_j2: unknown): any { + } - } + onMsgAbstractUpdate (_arrayList: unknown): any { - onMsgAbstractUpdate(_arrayList: unknown): any { + } - } + onMsgBoxChanged (_arrayList: unknown): any { - onMsgBoxChanged(_arrayList: unknown): any { + } - } + onMsgDelete (_contact: unknown, _arrayList: unknown): any { - onMsgDelete(_contact: unknown, _arrayList: unknown): any { + } - } + onMsgEventListUpdate (_hashMap: unknown): any { - onMsgEventListUpdate(_hashMap: unknown): any { + } - } + onMsgInfoListAdd (_arrayList: unknown): any { - onMsgInfoListAdd(_arrayList: unknown): any { + } - } + onMsgInfoListUpdate (_msgList: RawMessage[]): any { - onMsgInfoListUpdate(_msgList: RawMessage[]): any { + } - } + onMsgQRCodeStatusChanged (_i2: unknown): any { - onMsgQRCodeStatusChanged(_i2: unknown): any { + } - } + onMsgRecall (_chatType: ChatType, _uid: string, _msgSeq: string): any { - onMsgRecall(_chatType: ChatType, _uid: string, _msgSeq: string): any { + } - } + onMsgSecurityNotify (_msgRecord: unknown): any { - onMsgSecurityNotify(_msgRecord: unknown): any { + } - } + onMsgSettingUpdate (_msgSetting: unknown): any { - onMsgSettingUpdate(_msgSetting: unknown): any { + } - } + onNtFirstViewMsgSyncEnd (): any { - onNtFirstViewMsgSyncEnd(): any { + } - } + onNtMsgSyncEnd (): any { - onNtMsgSyncEnd(): any { + } - } + onNtMsgSyncStart (): any { - onNtMsgSyncStart(): any { + } - } + onReadFeedEventUpdate (_firstViewDirectMsgNotifyInfo: unknown): any { - onReadFeedEventUpdate(_firstViewDirectMsgNotifyInfo: unknown): any { + } - } + onRecvGroupGuildFlag (_i2: unknown): any { - onRecvGroupGuildFlag(_i2: unknown): any { + } - } + onRecvMsg (_arrayList: RawMessage[]): any { - onRecvMsg(_arrayList: RawMessage[]): any { + } - } + onRecvMsgSvrRspTransInfo (_j2: unknown, _contact: unknown, _i2: unknown, _i3: unknown, _str: unknown, _bArr: unknown): any { - onRecvMsgSvrRspTransInfo(_j2: unknown, _contact: unknown, _i2: unknown, _i3: unknown, _str: unknown, _bArr: unknown): any { + } - } + onRecvOnlineFileMsg (_arrayList: unknown): any { - onRecvOnlineFileMsg(_arrayList: unknown): any { + } - } + onRecvS2CMsg (_arrayList: unknown): any { - onRecvS2CMsg(_arrayList: unknown): any { + } - } + onRecvSysMsg (_arrayList: Array): any { - onRecvSysMsg(_arrayList: Array): any { + } - } + onRecvUDCFlag (_i2: unknown): any { - onRecvUDCFlag(_i2: unknown): any { + } - } + onRichMediaDownloadComplete (_fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): any { + } - onRichMediaDownloadComplete(_fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): any { - } + onRichMediaProgerssUpdate (_fileTransNotifyInfo: unknown): any { - onRichMediaProgerssUpdate(_fileTransNotifyInfo: unknown): any { + } - } + onRichMediaUploadComplete (_fileTransNotifyInfo: unknown): any { - onRichMediaUploadComplete(_fileTransNotifyInfo: unknown): any { + } - } + onSearchGroupFileInfoUpdate (_searchGroupFileResult: unknown): any { - onSearchGroupFileInfoUpdate(_searchGroupFileResult: unknown): any { + } - } + onSendMsgError (_j2: unknown, _contact: unknown, _i2: unknown, _str: unknown): any { - onSendMsgError(_j2: unknown, _contact: unknown, _i2: unknown, _str: unknown): any { + } - } + onSysMsgNotification (_i2: unknown, _j2: unknown, _j3: unknown, _arrayList: unknown): any { - onSysMsgNotification(_i2: unknown, _j2: unknown, _j3: unknown, _arrayList: unknown): any { + } - } + onTempChatInfoUpdate (_tempChatInfo: TempOnRecvParams): any { - onTempChatInfoUpdate(_tempChatInfo: TempOnRecvParams): any { + } - } + onUnreadCntAfterFirstView (_hashMap: unknown): any { - onUnreadCntAfterFirstView(_hashMap: unknown): any { + } - } + onUnreadCntUpdate (_hashMap: unknown): any { - onUnreadCntUpdate(_hashMap: unknown): any { + } - } + onUserChannelTabStatusChanged (_z: unknown): any { - onUserChannelTabStatusChanged(_z: unknown): any { + } - } + onUserOnlineStatusChanged (_z: unknown): any { - onUserOnlineStatusChanged(_z: unknown): any { + } - } + onUserTabStatusChanged (_arrayList: unknown): any { - onUserTabStatusChanged(_arrayList: unknown): any { + } - } + onlineStatusBigIconDownloadPush (_i2: unknown, _j2: unknown, _str: unknown): any { - onlineStatusBigIconDownloadPush(_i2: unknown, _j2: unknown, _str: unknown): any { + } - } + onlineStatusSmallIconDownloadPush (_i2: unknown, _j2: unknown, _str: unknown): any { - onlineStatusSmallIconDownloadPush(_i2: unknown, _j2: unknown, _str: unknown): any { + } - } + // 第一次发现于Linux + onUserSecQualityChanged (..._args: unknown[]): any { - // 第一次发现于Linux - onUserSecQualityChanged(..._args: unknown[]): any { + } - } + onMsgWithRichLinkInfoUpdate (..._args: unknown[]): any { - onMsgWithRichLinkInfoUpdate(..._args: unknown[]): any { + } - } + onRedTouchChanged (..._args: unknown[]): any { - onRedTouchChanged(..._args: unknown[]): any { + } - } + // 第一次发现于Win 9.9.9-23159 + onBroadcastHelperProgerssUpdate (..._args: unknown[]): any { - // 第一次发现于Win 9.9.9-23159 - onBroadcastHelperProgerssUpdate(..._args: unknown[]): any { - - } + } } diff --git a/src/core/listeners/NodeIKernelProfileListener.ts b/src/core/listeners/NodeIKernelProfileListener.ts index 5ba11be0..9964f9ed 100644 --- a/src/core/listeners/NodeIKernelProfileListener.ts +++ b/src/core/listeners/NodeIKernelProfileListener.ts @@ -1,71 +1,71 @@ import { User, UserDetailInfoListenerArg } from '@/core/types'; export class NodeIKernelProfileListener { - onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void { + onUserDetailInfoChanged (arg: UserDetailInfoListenerArg): void { - } + } - onProfileSimpleChanged(...args: unknown[]): any { + onProfileSimpleChanged (...args: unknown[]): any { - } + } - onProfileDetailInfoChanged(profile: User): any { + onProfileDetailInfoChanged (profile: User): any { - } + } - onStatusUpdate(...args: unknown[]): any { + onStatusUpdate (...args: unknown[]): any { - } + } - onSelfStatusChanged(...args: unknown[]): any { + onSelfStatusChanged (...args: unknown[]): any { - } + } - onStrangerRemarkChanged(...args: unknown[]): any { + onStrangerRemarkChanged (...args: unknown[]): any { - } + } - onMemberListChange(...args: unknown[]): any { + onMemberListChange (...args: unknown[]): any { - } + } - onMemberInfoChange(...args: unknown[]): any { + onMemberInfoChange (...args: unknown[]): any { - } + } - onGroupListUpdate(...args: unknown[]): any { + onGroupListUpdate (...args: unknown[]): any { - } + } - onGroupAllInfoChange(...args: unknown[]): any { + onGroupAllInfoChange (...args: unknown[]): any { - } + } - onGroupDetailInfoChange(...args: unknown[]): any { + onGroupDetailInfoChange (...args: unknown[]): any { - } + } - onGroupConfMemberChange(...args: unknown[]): any { + onGroupConfMemberChange (...args: unknown[]): any { - } + } - onGroupExtListUpdate(...args: unknown[]): any { + onGroupExtListUpdate (...args: unknown[]): any { - } + } - onGroupNotifiesUpdated(...args: unknown[]): any { + onGroupNotifiesUpdated (...args: unknown[]): any { - } + } - onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any { + onGroupNotifiesUnreadCountUpdated (...args: unknown[]): any { - } + } - onGroupMemberLevelInfoChange(...args: unknown[]): any { + onGroupMemberLevelInfoChange (...args: unknown[]): any { - } + } - onGroupBulletinChange(...args: unknown[]): any { + onGroupBulletinChange (...args: unknown[]): any { - } + } } diff --git a/src/core/listeners/NodeIKernelRecentContactListener.ts b/src/core/listeners/NodeIKernelRecentContactListener.ts index 9797cabe..bd6c8baf 100644 --- a/src/core/listeners/NodeIKernelRecentContactListener.ts +++ b/src/core/listeners/NodeIKernelRecentContactListener.ts @@ -1,25 +1,25 @@ export class NodeIKernelRecentContactListener { - onDeletedContactsNotify(...args: unknown[]): any { + onDeletedContactsNotify (...args: unknown[]): any { - } + } - onRecentContactNotification(msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number): any { + onRecentContactNotification (msgList: any, arg0: { msgListUnreadCnt: string }, arg1: number): any { - } + } - onMsgUnreadCountUpdate(...args: unknown[]): any { + onMsgUnreadCountUpdate (...args: unknown[]): any { - } + } - onGuildDisplayRecentContactListChanged(...args: unknown[]): any { + onGuildDisplayRecentContactListChanged (...args: unknown[]): any { - } + } - onRecentContactListChanged(...args: unknown[]): any { + onRecentContactListChanged (...args: unknown[]): any { - } + } - onRecentContactListChangedVer2(...args: unknown[]): any { + onRecentContactListChangedVer2 (...args: unknown[]): any { - } + } } diff --git a/src/core/listeners/NodeIKernelRobotListener.ts b/src/core/listeners/NodeIKernelRobotListener.ts index b8f817a4..2c128b66 100644 --- a/src/core/listeners/NodeIKernelRobotListener.ts +++ b/src/core/listeners/NodeIKernelRobotListener.ts @@ -1,13 +1,13 @@ export class NodeIKernelRobotListener { - onRobotFriendListChanged(...args: unknown[]): any { + onRobotFriendListChanged (...args: unknown[]): any { - } + } - onRobotListChanged(...args: unknown[]): any { + onRobotListChanged (...args: unknown[]): any { - } + } - onRobotProfileChanged(...args: unknown[]): any { + onRobotProfileChanged (...args: unknown[]): any { - } + } } diff --git a/src/core/listeners/NodeIKernelSearchListener.ts b/src/core/listeners/NodeIKernelSearchListener.ts index e0a34e9d..961cbcab 100644 --- a/src/core/listeners/NodeIKernelSearchListener.ts +++ b/src/core/listeners/NodeIKernelSearchListener.ts @@ -1,120 +1,120 @@ import { ChatType, RawMessage } from '@/core'; export interface SearchGroupInfo { - groupCode: string; - ownerUid: string; - groupFlag: number; - groupFlagExt: number; - maxMemberNum: number; - memberNum: number; - groupOption: number; - classExt: number; - groupName: string; - fingerMemo: string; - groupQuestion: string; - certType: number; - shutUpAllTimestamp: number; - shutUpMeTimestamp: number; - groupTypeFlag: number; - privilegeFlag: number; - groupSecLevel: number; - groupFlagExt3: number; - isConfGroup: number; - isModifyConfGroupFace: number; - isModifyConfGroupName: number; - noFigerOpenFlag: number; - noCodeFingerOpenFlag: number; - groupFlagExt4: number; - groupMemo: string; - cmdUinMsgSeq: number; - cmdUinJoinTime: number; - cmdUinUinFlag: number; - cmdUinMsgMask: number; - groupSecLevelInfo: number; - cmdUinPrivilege: number; - cmdUinFlagEx2: number; - appealDeadline: number; - remarkName: string; - isTop: boolean; - richFingerMemo: string; - groupAnswer: string; - joinGroupAuth: string; - isAllowModifyConfGroupName: number; + groupCode: string; + ownerUid: string; + groupFlag: number; + groupFlagExt: number; + maxMemberNum: number; + memberNum: number; + groupOption: number; + classExt: number; + groupName: string; + fingerMemo: string; + groupQuestion: string; + certType: number; + shutUpAllTimestamp: number; + shutUpMeTimestamp: number; + groupTypeFlag: number; + privilegeFlag: number; + groupSecLevel: number; + groupFlagExt3: number; + isConfGroup: number; + isModifyConfGroupFace: number; + isModifyConfGroupName: number; + noFigerOpenFlag: number; + noCodeFingerOpenFlag: number; + groupFlagExt4: number; + groupMemo: string; + cmdUinMsgSeq: number; + cmdUinJoinTime: number; + cmdUinUinFlag: number; + cmdUinMsgMask: number; + groupSecLevelInfo: number; + cmdUinPrivilege: number; + cmdUinFlagEx2: number; + appealDeadline: number; + remarkName: string; + isTop: boolean; + richFingerMemo: string; + groupAnswer: string; + joinGroupAuth: string; + isAllowModifyConfGroupName: number; } export interface GroupInfo { - groupCode: string; - searchGroupInfo: SearchGroupInfo; - privilege: number; + groupCode: string; + searchGroupInfo: SearchGroupInfo; + privilege: number; } export interface GroupSearchResult { - keyWord: string; - errorCode: number; - groupInfos: GroupInfo[]; - penetrate: string; - isEnd: boolean; - nextPos: number; + keyWord: string; + errorCode: number; + groupInfos: GroupInfo[]; + penetrate: string; + isEnd: boolean; + nextPos: number; } export interface NodeIKernelSearchListener { - onSearchGroupResult(params: GroupSearchResult): any; + onSearchGroupResult(params: GroupSearchResult): any; - onSearchFileKeywordsResult(params: { - searchId: string, - hasMore: boolean, - resultItems: { - chatType: ChatType, - buddyChatInfo: any[], - discussChatInfo: any[], - groupChatInfo: { - groupCode: string, - isConf: boolean, - hasModifyConfGroupFace: boolean, - hasModifyConfGroupName: boolean, - groupName: string, - remark: string - }[], - dataLineChatInfo: any[], - tmpChatInfo: any[], - msgId: string, - msgSeq: string, - msgTime: string, - senderUid: string, - senderNick: string, - senderRemark: string, - senderCard: string, - elemId: string, - elemType: number, - fileSize: string, - filePath: string, - fileName: string, - hits: { - start: number, - end: number - }[] - }[] - }): any; + onSearchFileKeywordsResult(params: { + searchId: string, + hasMore: boolean, + resultItems: { + chatType: ChatType, + buddyChatInfo: any[], + discussChatInfo: any[], + groupChatInfo: { + groupCode: string, + isConf: boolean, + hasModifyConfGroupFace: boolean, + hasModifyConfGroupName: boolean, + groupName: string, + remark: string + }[], + dataLineChatInfo: any[], + tmpChatInfo: any[], + msgId: string, + msgSeq: string, + msgTime: string, + senderUid: string, + senderNick: string, + senderRemark: string, + senderCard: string, + elemId: string, + elemType: number, + fileSize: string, + filePath: string, + fileName: string, + hits: { + start: number, + end: number + }[] + }[] + }): any; - onSearchMsgKeywordsResult(params: { - searchId: string, - hasMore: boolean, - resultItems: Array<{ - msgId: string, - msgSeq: string, - msgTime: string, - senderUid: string, - senderUin: string, - senderNick: string, - senderNickHits: unknown[], - senderRemark: string, - senderRemarkHits: unknown[], - senderCard: string, - senderCardHits: unknown[], - fieldType: number, - fieldText: string, - msgRecord: RawMessage; - hitsInfo: Array, - msgAbstract: unknown, - }> - }): void | Promise; + onSearchMsgKeywordsResult(params: { + searchId: string, + hasMore: boolean, + resultItems: Array<{ + msgId: string, + msgSeq: string, + msgTime: string, + senderUid: string, + senderUin: string, + senderNick: string, + senderNickHits: unknown[], + senderRemark: string, + senderRemarkHits: unknown[], + senderCard: string, + senderCardHits: unknown[], + fieldType: number, + fieldText: string, + msgRecord: RawMessage; + hitsInfo: Array, + msgAbstract: unknown, + }> + }): void | Promise; } diff --git a/src/core/listeners/NodeIKernelSessionListener.ts b/src/core/listeners/NodeIKernelSessionListener.ts index bf82cbaf..9573b514 100644 --- a/src/core/listeners/NodeIKernelSessionListener.ts +++ b/src/core/listeners/NodeIKernelSessionListener.ts @@ -1,25 +1,25 @@ export class NodeIKernelSessionListener { - onNTSessionCreate(args: unknown): any { + onNTSessionCreate (args: unknown): any { - } + } - onGProSessionCreate(args: unknown): any { + onGProSessionCreate (args: unknown): any { - } + } - onSessionInitComplete(args: unknown): any { + onSessionInitComplete (args: unknown): any { - } + } - onOpentelemetryInit(info: { is_init: boolean, is_report: boolean }): any { + onOpentelemetryInit (info: { is_init: boolean, is_report: boolean }): any { - } + } - onUserOnlineResult(args: unknown): any { + onUserOnlineResult (args: unknown): any { - } + } - onGetSelfTinyId(args: unknown): any { + onGetSelfTinyId (args: unknown): any { - } + } } diff --git a/src/core/listeners/NodeIKernelStorageCleanListener.ts b/src/core/listeners/NodeIKernelStorageCleanListener.ts index 37d23db2..495b3f33 100644 --- a/src/core/listeners/NodeIKernelStorageCleanListener.ts +++ b/src/core/listeners/NodeIKernelStorageCleanListener.ts @@ -1,21 +1,21 @@ export class NodeIKernelStorageCleanListener { - onCleanCacheProgressChanged(args: unknown): any { + onCleanCacheProgressChanged (args: unknown): any { - } + } - onScanCacheProgressChanged(args: unknown): any { + onScanCacheProgressChanged (args: unknown): any { - } + } - onCleanCacheStorageChanged(args: unknown): any { + onCleanCacheStorageChanged (args: unknown): any { - } + } - onFinishScan(args: unknown): any { + onFinishScan (args: unknown): any { - } + } - onChatCleanDone(args: unknown): any { + onChatCleanDone (args: unknown): any { - } + } } diff --git a/src/core/listeners/NodeIKernelTicketListener.ts b/src/core/listeners/NodeIKernelTicketListener.ts index 3763de5b..c70ead26 100644 --- a/src/core/listeners/NodeIKernelTicketListener.ts +++ b/src/core/listeners/NodeIKernelTicketListener.ts @@ -1,5 +1,5 @@ export class NodeIKernelTicketListener { - listener(): any { + listener (): any { - } + } } diff --git a/src/core/listeners/NodeIO3MiscListener.ts b/src/core/listeners/NodeIO3MiscListener.ts index bc7a6ebb..5b7f1d0f 100644 --- a/src/core/listeners/NodeIO3MiscListener.ts +++ b/src/core/listeners/NodeIO3MiscListener.ts @@ -1,5 +1,5 @@ export class NodeIO3MiscListener { - getOnAmgomDataPiece(...arg: unknown[]): any { + getOnAmgomDataPiece (...arg: unknown[]): any { - } -} \ No newline at end of file + } +} diff --git a/src/core/listeners/index.ts b/src/core/listeners/index.ts index a1eb0a7c..eaf6f720 100644 --- a/src/core/listeners/index.ts +++ b/src/core/listeners/index.ts @@ -1,3 +1,17 @@ +import type { + NodeIKernelBuddyListener, + NodeIKernelFileAssistantListener, + NodeIKernelGroupListener, + NodeIKernelLoginListener, + NodeIKernelMsgListener, + NodeIKernelProfileListener, + NodeIKernelRobotListener, + NodeIKernelSessionListener, + NodeIKernelStorageCleanListener, + NodeIKernelTicketListener, +} from '.'; +import { NodeIKernelSearchListener } from './NodeIKernelSearchListener'; + export * from './NodeIKernelSessionListener'; export * from './NodeIKernelLoginListener'; export * from './NodeIKernelMsgListener'; @@ -11,30 +25,16 @@ export * from './NodeIKernelStorageCleanListener'; export * from './NodeIKernelFileAssistantListener'; export * from './NodeIKernelSearchListener'; -import type { - NodeIKernelBuddyListener, - NodeIKernelFileAssistantListener, - NodeIKernelGroupListener, - NodeIKernelLoginListener, - NodeIKernelMsgListener, - NodeIKernelProfileListener, - NodeIKernelRobotListener, - NodeIKernelSessionListener, - NodeIKernelStorageCleanListener, - NodeIKernelTicketListener, -} from '.'; -import { NodeIKernelSearchListener } from './NodeIKernelSearchListener'; - export type ListenerNamingMapping = { - NodeIKernelSessionListener: NodeIKernelSessionListener; - NodeIKernelLoginListener: NodeIKernelLoginListener; - NodeIKernelMsgListener: NodeIKernelMsgListener; - NodeIKernelGroupListener: NodeIKernelGroupListener; - NodeIKernelBuddyListener: NodeIKernelBuddyListener; - NodeIKernelProfileListener: NodeIKernelProfileListener; - NodeIKernelRobotListener: NodeIKernelRobotListener; - NodeIKernelTicketListener: NodeIKernelTicketListener; - NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener; - NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener; - NodeIKernelSearchListener: NodeIKernelSearchListener; + NodeIKernelSessionListener: NodeIKernelSessionListener; + NodeIKernelLoginListener: NodeIKernelLoginListener; + NodeIKernelMsgListener: NodeIKernelMsgListener; + NodeIKernelGroupListener: NodeIKernelGroupListener; + NodeIKernelBuddyListener: NodeIKernelBuddyListener; + NodeIKernelProfileListener: NodeIKernelProfileListener; + NodeIKernelRobotListener: NodeIKernelRobotListener; + NodeIKernelTicketListener: NodeIKernelTicketListener; + NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener; + NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener; + NodeIKernelSearchListener: NodeIKernelSearchListener; }; diff --git a/src/core/packet/client/nativeClient.ts b/src/core/packet/client/nativeClient.ts index ecbb842b..cb86410e 100644 --- a/src/core/packet/client/nativeClient.ts +++ b/src/core/packet/client/nativeClient.ts @@ -7,101 +7,102 @@ import { NapCoreContext } from '@/core/packet/context/napCoreContext'; import { PacketLogger } from '@/core/packet/context/loggerContext'; import { OidbPacket, PacketBuf } from '@/core/packet/transformer/base'; export interface RecvPacket { - type: string, // 仅recv - data: RecvPacketData + type: string, // 仅recv + data: RecvPacketData } export interface RecvPacketData { - seq: number - cmd: string - data: Buffer + seq: number + cmd: string + data: Buffer } // 0 send 1 recv export interface NativePacketExportType { - initHook?: (send: string, recv: string) => boolean; + initHook?: (send: string, recv: string) => boolean; } export class NativePacketClient { - protected readonly napcore: NapCoreContext; - protected readonly logger: PacketLogger; - protected readonly cb = new Map Promise | any>(); // hash-type callback - logStack: LogStack; - available: boolean = false; - private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; - private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} }; + protected readonly napcore: NapCoreContext; + protected readonly logger: PacketLogger; + protected readonly cb = new Map Promise | any>(); // hash-type callback + logStack: LogStack; + available: boolean = false; + private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; + private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} }; - constructor(napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) { - this.napcore = napCore; - this.logger = logger; - this.logStack = logStack; + constructor (napCore: NapCoreContext, logger: PacketLogger, logStack: LogStack) { + this.napcore = napCore; + this.logger = logger; + this.logStack = logStack; + } + + check (): boolean { + const platform = process.platform + '.' + process.arch; + if (!this.supportedPlatforms.includes(platform)) { + this.logStack.pushLogWarn(`NativePacketClient: 不支持的平台: ${platform}`); + return false; } - - check(): boolean { - const platform = process.platform + '.' + process.arch; - if (!this.supportedPlatforms.includes(platform)) { - this.logStack.pushLogWarn(`NativePacketClient: 不支持的平台: ${platform}`); - return false; - } - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/napi2native/napi2native.' + platform + '.node'); - if (!fs.existsSync(moehoo_path)) { - this.logStack.pushLogWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); - return false; - } - return true; + const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/napi2native/napi2native.' + platform + '.node'); + if (!fs.existsSync(moehoo_path)) { + this.logStack.pushLogWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); + return false; } + return true; + } - async init(_pid: number, recv: string, send: string): Promise { - const platform = process.platform + '.' + process.arch; - const isNewQQ = this.napcore.basicInfo.requireMinNTQQBuild("40824"); - if (isNewQQ) { - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/napi2native/napi2native.' + platform + '.node'); - process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); - this.MoeHooExport?.exports.initHook?.(send, recv); - this.available = true; - } + async init (_pid: number, recv: string, send: string): Promise { + const platform = process.platform + '.' + process.arch; + const isNewQQ = this.napcore.basicInfo.requireMinNTQQBuild('40824'); + if (isNewQQ) { + const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/napi2native/napi2native.' + platform + '.node'); + process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); + this.MoeHooExport?.exports.initHook?.(send, recv); + this.available = true; } + } - async sendPacket( - cmd: string, - data: PacketBuf, - rsp = false, - timeout = 5000 - ): Promise { - if (!rsp) { - this.napcore - .sendSsoCmdReqByContend(cmd, data) - .catch(err => - this.logger.error( + async sendPacket ( + cmd: string, + data: PacketBuf, + rsp = false, + timeout = 5000 + ): Promise { + if (!rsp) { + this.napcore + .sendSsoCmdReqByContend(cmd, data) + .catch(err => + this.logger.error( `[PacketClient] sendPacket 无响应命令发送失败 cmd=${cmd} err=${err}` - ) - ); - return { seq: 0, cmd, data: Buffer.alloc(0) }; - } + ) + ); + return { seq: 0, cmd, data: Buffer.alloc(0) }; + } - const sendPromise = this.napcore - .sendSsoCmdReqByContend(cmd, data) - .then(ret => ({ - seq: 0, - cmd, - data: (ret as { rspbuffer: Buffer }).rspbuffer - })); + const sendPromise = this.napcore + .sendSsoCmdReqByContend(cmd, data) + .then(ret => ({ + seq: 0, + cmd, + data: (ret as { rspbuffer: Buffer }).rspbuffer, + })); - const timeoutPromise = new Promise((_, reject) => { - setTimeout( - () => - reject( - new Error( + const timeoutPromise = new Promise((_, reject) => { + setTimeout( + () => + reject( + new Error( `[PacketClient] sendPacket 超时 cmd=${cmd} timeout=${timeout}ms` - ) - ), - timeout - ); - }); + ) + ), + timeout + ); + }); - return Promise.race([sendPromise, timeoutPromise]); - } - async sendOidbPacket(pkt: OidbPacket, rsp = false, timeout = 5000): Promise { - return await this.sendPacket(pkt.cmd, pkt.data, rsp, timeout); - } + return Promise.race([sendPromise, timeoutPromise]); + } + + async sendOidbPacket (pkt: OidbPacket, rsp = false, timeout = 5000): Promise { + return await this.sendPacket(pkt.cmd, pkt.data, rsp, timeout); + } } diff --git a/src/core/packet/clientSession.ts b/src/core/packet/clientSession.ts index 4f2dc55a..edad34e3 100644 --- a/src/core/packet/clientSession.ts +++ b/src/core/packet/clientSession.ts @@ -2,30 +2,30 @@ import { PacketContext } from '@/core/packet/context/packetContext'; import { NapCatCore } from '@/core'; export class PacketClientSession { - private readonly context: PacketContext; + private readonly context: PacketContext; - constructor(core: NapCatCore) { - this.context = new PacketContext(core); - } + constructor (core: NapCatCore) { + this.context = new PacketContext(core); + } - init(pid: number, recv: string, send: string): Promise { - return this.context.client.init(pid, recv, send); - } + init (pid: number, recv: string, send: string): Promise { + return this.context.client.init(pid, recv, send); + } - get clientLogStack() { - return this.context.client.clientLogStack; - } + get clientLogStack () { + return this.context.client.clientLogStack; + } - get available() { - return this.context.client.available; - } + get available () { + return this.context.client.available; + } - get operation() { - return this.context.operation; - } + get operation () { + return this.context.operation; + } - // TODO: global message element adapter (? - get msgConverter() { - return this.context.msgConverter; - } + // TODO: global message element adapter (? + get msgConverter () { + return this.context.msgConverter; + } } diff --git a/src/core/packet/context/clientContext.ts b/src/core/packet/context/clientContext.ts index 69f3ae1b..21352b9b 100644 --- a/src/core/packet/context/clientContext.ts +++ b/src/core/packet/context/clientContext.ts @@ -4,77 +4,77 @@ import { PacketLogger } from '@/core/packet/context/loggerContext'; import { NapCoreContext } from '@/core/packet/context/napCoreContext'; export class LogStack { - private stack: string[] = []; - private readonly logger: PacketLogger; + private stack: string[] = []; + private readonly logger: PacketLogger; - constructor(logger: PacketLogger) { - this.logger = logger; - } + constructor (logger: PacketLogger) { + this.logger = logger; + } - push(msg: string) { - this.stack.push(msg); - } + push (msg: string) { + this.stack.push(msg); + } - pushLogInfo(msg: string) { - this.logger.info(msg); - this.stack.push(`${new Date().toISOString()} [INFO] ${msg}`); - } + pushLogInfo (msg: string) { + this.logger.info(msg); + this.stack.push(`${new Date().toISOString()} [INFO] ${msg}`); + } - pushLogWarn(msg: string) { - this.logger.warn(msg); - this.stack.push(`${new Date().toISOString()} [WARN] ${msg}`); - } + pushLogWarn (msg: string) { + this.logger.warn(msg); + this.stack.push(`${new Date().toISOString()} [WARN] ${msg}`); + } - pushLogError(msg: string) { - this.logger.error(msg); - this.stack.push(`${new Date().toISOString()} [ERROR] ${msg}`); - } + pushLogError (msg: string) { + this.logger.error(msg); + this.stack.push(`${new Date().toISOString()} [ERROR] ${msg}`); + } - clear() { - this.stack = []; - } + clear () { + this.stack = []; + } - content() { - return this.stack.join('\n'); - } + content () { + return this.stack.join('\n'); + } } export class PacketClientContext { - private readonly napCore: NapCoreContext; - private readonly logger: PacketLogger; - private readonly logStack: LogStack; - private readonly _client: NativePacketClient; + private readonly napCore: NapCoreContext; + private readonly logger: PacketLogger; + private readonly logStack: LogStack; + private readonly _client: NativePacketClient; - constructor(napCore: NapCoreContext, logger: PacketLogger) { - this.napCore = napCore; - this.logger = logger; - this.logStack = new LogStack(logger); - this._client = this.newClient(); - } + constructor (napCore: NapCoreContext, logger: PacketLogger) { + this.napCore = napCore; + this.logger = logger; + this.logStack = new LogStack(logger); + this._client = this.newClient(); + } - get available(): boolean { - return this._client.available; - } + get available (): boolean { + return this._client.available; + } - get clientLogStack(): string { - return this._client.logStack.content(); - } + get clientLogStack (): string { + return this._client.logStack.content(); + } - async init(pid: number, recv: string, send: string): Promise { - await this._client.init(pid, recv, send); - } + async init (pid: number, recv: string, send: string): Promise { + await this._client.init(pid, recv, send); + } - async sendOidbPacket(pkt: OidbPacket, rsp?: T, timeout?: number): Promise { - const raw = await this._client.sendOidbPacket(pkt, rsp, timeout); - return raw.data as T extends true ? Buffer : void; - } + async sendOidbPacket(pkt: OidbPacket, rsp?: T, timeout?: number): Promise { + const raw = await this._client.sendOidbPacket(pkt, rsp, timeout); + return raw.data as T extends true ? Buffer : void; + } - private newClient(): NativePacketClient { - this.logger.info('使用 NativePacketClient 作为后端'); - const client = new NativePacketClient(this.napCore, this.logger, this.logStack); - if (!client.check()) { - throw new Error('[Core] [Packet] NativePacketClient 不可用,NapCat.Packet将不会加载!'); - } - return client; + private newClient (): NativePacketClient { + this.logger.info('使用 NativePacketClient 作为后端'); + const client = new NativePacketClient(this.napCore, this.logger, this.logStack); + if (!client.check()) { + throw new Error('[Core] [Packet] NativePacketClient 不可用,NapCat.Packet将不会加载!'); } + return client; + } } diff --git a/src/core/packet/context/loggerContext.ts b/src/core/packet/context/loggerContext.ts index 3b12015c..9a5e7a60 100644 --- a/src/core/packet/context/loggerContext.ts +++ b/src/core/packet/context/loggerContext.ts @@ -1,36 +1,35 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { LogLevel, LogWrapper } from '@/common/log'; import { NapCoreContext } from '@/core/packet/context/napCoreContext'; // TODO: check bind? export class PacketLogger { - private readonly napLogger: LogWrapper; + private readonly napLogger: LogWrapper; - constructor(napcore: NapCoreContext) { - this.napLogger = napcore.logger; - } + constructor (napcore: NapCoreContext) { + this.napLogger = napcore.logger; + } - private _log(level: LogLevel, ...msg: any[]): void { - this.napLogger._log(level, '[Core] [Packet] ' + msg); - } + private _log (level: LogLevel, ...msg: any[]): void { + this.napLogger._log(level, '[Core] [Packet] ' + msg); + } - debug(...msg: any[]): void { - this._log(LogLevel.DEBUG, msg); - } + debug (...msg: any[]): void { + this._log(LogLevel.DEBUG, msg); + } - info(...msg: any[]): void { - this._log(LogLevel.INFO, msg); - } + info (...msg: any[]): void { + this._log(LogLevel.INFO, msg); + } - warn(...msg: any[]): void { - this._log(LogLevel.WARN, msg); - } + warn (...msg: any[]): void { + this._log(LogLevel.WARN, msg); + } - error(...msg: any[]): void { - this._log(LogLevel.ERROR, msg); - } + error (...msg: any[]): void { + this._log(LogLevel.ERROR, msg); + } - fatal(...msg: any[]): void { - this._log(LogLevel.FATAL, msg); - } + fatal (...msg: any[]): void { + this._log(LogLevel.FATAL, msg); + } } diff --git a/src/core/packet/context/napCoreContext.ts b/src/core/packet/context/napCoreContext.ts index 00e3f67e..43e4a9db 100644 --- a/src/core/packet/context/napCoreContext.ts +++ b/src/core/packet/context/napCoreContext.ts @@ -1,38 +1,38 @@ import { NapCatCore } from '@/core'; export interface NapCoreCompatBasicInfo { - readonly requireMinNTQQBuild: (buildVer: string) => boolean; - readonly uin: number; - readonly uid: string; - readonly uin2uid: (uin: number) => Promise; - readonly uid2uin: (uid: string) => Promise; - readonly sendSsoCmdReqByContend: (cmd: string, trace_id: string) => Promise; + readonly requireMinNTQQBuild: (buildVer: string) => boolean; + readonly uin: number; + readonly uid: string; + readonly uin2uid: (uin: number) => Promise; + readonly uid2uin: (uid: string) => Promise; + readonly sendSsoCmdReqByContend: (cmd: string, trace_id: string) => Promise; } export class NapCoreContext { - private readonly core: NapCatCore; + private readonly core: NapCatCore; - constructor(core: NapCatCore) { - this.core = core; - } + constructor (core: NapCatCore) { + this.core = core; + } - get logger() { - return this.core.context.logger; - } + get logger () { + return this.core.context.logger; + } - get basicInfo() { - return { - requireMinNTQQBuild: (buildVer: string) => this.core.context.basicInfoWrapper.requireMinNTQQBuild(buildVer), - uin: +this.core.selfInfo.uin, - uid: this.core.selfInfo.uid, - uin2uid: (uin: number) => this.core.apis.UserApi.getUidByUinV2(String(uin)).then(res => res ?? ''), - uid2uin: (uid: string) => this.core.apis.UserApi.getUinByUidV2(uid).then(res => +res), - } as NapCoreCompatBasicInfo; - } + get basicInfo () { + return { + requireMinNTQQBuild: (buildVer: string) => this.core.context.basicInfoWrapper.requireMinNTQQBuild(buildVer), + uin: +this.core.selfInfo.uin, + uid: this.core.selfInfo.uid, + uin2uid: (uin: number) => this.core.apis.UserApi.getUidByUinV2(String(uin)).then(res => res ?? ''), + uid2uin: (uid: string) => this.core.apis.UserApi.getUinByUidV2(uid).then(res => +res), + } as NapCoreCompatBasicInfo; + } - get config() { - return this.core.configLoader.configData; - } + get config () { + return this.core.configLoader.configData; + } - sendSsoCmdReqByContend = (cmd: string, data: Buffer) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, data); + sendSsoCmdReqByContend = (cmd: string, data: Buffer) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, data); } diff --git a/src/core/packet/context/operationContext.ts b/src/core/packet/context/operationContext.ts index 00c4143b..a919a408 100644 --- a/src/core/packet/context/operationContext.ts +++ b/src/core/packet/context/operationContext.ts @@ -3,11 +3,11 @@ import { PacketContext } from '@/core/packet/context/packetContext'; import * as trans from '@/core/packet/transformer'; import { PacketMsg } from '@/core/packet/message/message'; import { - PacketMsgFileElement, - PacketMsgPicElement, - PacketMsgPttElement, - PacketMsgReplyElement, - PacketMsgVideoElement, + PacketMsgFileElement, + PacketMsgPicElement, + PacketMsgPttElement, + PacketMsgReplyElement, + PacketMsgVideoElement, } from '@/core/packet/message/element'; import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/core'; import { MiniAppRawData, MiniAppReqParams } from '@/core/packet/entities/miniApp'; @@ -20,353 +20,354 @@ import { gunzipSync } from 'zlib'; import { PacketMsgConverter } from '@/core/packet/message/converter'; export class PacketOperationContext { - private readonly context: PacketContext; + private readonly context: PacketContext; - constructor(context: PacketContext) { - this.context = context; - } + constructor (context: PacketContext) { + this.context = context; + } - async sendPacket(pkt: OidbPacket, rsp?: T): Promise { - return await this.context.client.sendOidbPacket(pkt, rsp); - } + async sendPacket(pkt: OidbPacket, rsp?: T): Promise { + return await this.context.client.sendOidbPacket(pkt, rsp); + } - async SendPoke(is_group: boolean, peer: number, target?: number) { - const req = trans.SendPoke.build(is_group, peer, target ?? peer); - await this.context.client.sendOidbPacket(req); - } - async SetGroupTodo(groupUin: number, msgSeq: string) { - const req = trans.SetGroupTodo.build(groupUin, msgSeq); - await this.context.client.sendOidbPacket(req, true); - } - async FetchRkey(timeout: number = 10000) { - const req = trans.FetchRkey.build(); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.FetchRkey.parse(resp); - return res.data.rkeyList; - } + async SendPoke (is_group: boolean, peer: number, target?: number) { + const req = trans.SendPoke.build(is_group, peer, target ?? peer); + await this.context.client.sendOidbPacket(req); + } - async GroupSign(groupUin: number) { - const req = trans.GroupSign.build(this.context.napcore.basicInfo.uin, groupUin); - await this.context.client.sendOidbPacket(req); + async SetGroupTodo (groupUin: number, msgSeq: string) { + const req = trans.SetGroupTodo.build(groupUin, msgSeq); + await this.context.client.sendOidbPacket(req, true); + } + + async FetchRkey (timeout: number = 10000) { + const req = trans.FetchRkey.build(); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.FetchRkey.parse(resp); + return res.data.rkeyList; + } + + async GroupSign (groupUin: number) { + const req = trans.GroupSign.build(this.context.napcore.basicInfo.uin, groupUin); + await this.context.client.sendOidbPacket(req); + } + + async GetStrangerStatus (uin: number) { + let status = 0; + try { + const req = trans.GetStrangerInfo.build(uin); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.GetStrangerInfo.parse(resp); + const extBigInt = BigInt(res.data.status.value); + if (extBigInt <= 10n) { + return { status: Number(extBigInt) * 10, ext_status: 0 }; + } + status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn)); + return { status: 10, ext_status: status }; + } catch { + return undefined; } + } - async GetStrangerStatus(uin: number) { - let status = 0; - try { - const req = trans.GetStrangerInfo.build(uin); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.GetStrangerInfo.parse(resp); - const extBigInt = BigInt(res.data.status.value); - if (extBigInt <= 10n) { - return { status: Number(extBigInt) * 10, ext_status: 0 }; - } - status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn)); - return { status: 10, ext_status: status }; - } catch { - return undefined; - } + async SetGroupSpecialTitle (groupUin: number, uid: string, title: string) { + const req = trans.SetSpecialTitle.build(groupUin, uid, title); + await this.context.client.sendOidbPacket(req); + } + + async UploadResources (msg: PacketMsg[], groupUin: number = 0) { + const chatType = groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C; + const peerUid = groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid; + const reqList = msg.flatMap((m) => + m.msg + .map((e) => { + if (e instanceof PacketMsgPicElement) { + return this.context.highway.uploadImage({ chatType, peerUid }, e); + } else if (e instanceof PacketMsgVideoElement) { + return this.context.highway.uploadVideo({ chatType, peerUid }, e); + } else if (e instanceof PacketMsgPttElement) { + return this.context.highway.uploadPtt({ chatType, peerUid }, e); + } else if (e instanceof PacketMsgFileElement) { + return this.context.highway.uploadFile({ chatType, peerUid }, e); + } + return null; + }) + .filter(Boolean) + ); + const res = await Promise.allSettled(reqList); + this.context.logger.info(`上传资源${res.length}个,失败${res.filter((r) => r.status === 'rejected').length}个`); + res.forEach((result, index) => { + if (result.status === 'rejected') { + this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`); + } + }); + } + + async UploadImage (img: PacketMsgPicElement) { + await this.context.highway.uploadImage( + { + chatType: ChatType.KCHATTYPEC2C, + peerUid: this.context.napcore.basicInfo.uid, + }, + img + ); + const index = img.msgInfo?.msgInfoBody?.at(0)?.index; + if (!index) { + throw new Error('img.msgInfo?.msgInfoBody![0].index! is undefined'); } + return await this.GetImageUrl(this.context.napcore.basicInfo.uid, index); + } - async SetGroupSpecialTitle(groupUin: number, uid: string, title: string) { - const req = trans.SetSpecialTitle.build(groupUin, uid, title); - await this.context.client.sendOidbPacket(req); - } + async GetImageUrl (selfUid: string, node: NapProtoEncodeStructType) { + const req = trans.DownloadImage.build(selfUid, node); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.DownloadImage.parse(resp); + return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; + } - async UploadResources(msg: PacketMsg[], groupUin: number = 0) { - const chatType = groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C; - const peerUid = groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid; - const reqList = msg.flatMap((m) => - m.msg - .map((e) => { - if (e instanceof PacketMsgPicElement) { - return this.context.highway.uploadImage({ chatType, peerUid }, e); - } else if (e instanceof PacketMsgVideoElement) { - return this.context.highway.uploadVideo({ chatType, peerUid }, e); - } else if (e instanceof PacketMsgPttElement) { - return this.context.highway.uploadPtt({ chatType, peerUid }, e); - } else if (e instanceof PacketMsgFileElement) { - return this.context.highway.uploadFile({ chatType, peerUid }, e); - } - return null; - }) - .filter(Boolean) - ); - const res = await Promise.allSettled(reqList); - this.context.logger.info(`上传资源${res.length}个,失败${res.filter((r) => r.status === 'rejected').length}个`); - res.forEach((result, index) => { - if (result.status === 'rejected') { - this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`); - } - }); - } + async GetPttUrl (selfUid: string, node: NapProtoEncodeStructType, timeout?: number) { + const req = trans.DownloadPtt.build(selfUid, node); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadPtt.parse(resp); + return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; + } - async UploadImage(img: PacketMsgPicElement) { - await this.context.highway.uploadImage( - { - chatType: ChatType.KCHATTYPEC2C, - peerUid: this.context.napcore.basicInfo.uid, - }, - img - ); - const index = img.msgInfo?.msgInfoBody?.at(0)?.index; - if (!index) { - throw new Error('img.msgInfo?.msgInfoBody![0].index! is undefined'); - } - return await this.GetImageUrl(this.context.napcore.basicInfo.uid, index); - } + async GetVideoUrl (selfUid: string, node: NapProtoEncodeStructType, timeout?: number) { + const req = trans.DownloadVideo.build(selfUid, node); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadVideo.parse(resp); + return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; + } - async GetImageUrl(selfUid: string, node: NapProtoEncodeStructType) { - const req = trans.DownloadImage.build(selfUid, node); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.DownloadImage.parse(resp); - return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; - } + async GetGroupImageUrl (groupUin: number, node: NapProtoEncodeStructType, timeout?: number) { + const req = trans.DownloadGroupImage.build(groupUin, node); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadImage.parse(resp); + return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; + } - async GetPttUrl(selfUid: string, node: NapProtoEncodeStructType, timeout?: number) { - const req = trans.DownloadPtt.build(selfUid, node); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadPtt.parse(resp); - return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; - } + async GetGroupPttUrl (groupUin: number, node: NapProtoEncodeStructType, timeout?: number) { + const req = trans.DownloadGroupPtt.build(groupUin, node); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadImage.parse(resp); + return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; + } - async GetVideoUrl(selfUid: string, node: NapProtoEncodeStructType, timeout?: number) { - const req = trans.DownloadVideo.build(selfUid, node); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadVideo.parse(resp); - return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; - } + async GetGroupVideoUrl (groupUin: number, node: NapProtoEncodeStructType, timeout: number = 20000) { + const req = trans.DownloadGroupVideo.build(groupUin, node); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadImage.parse(resp); + return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; + } - async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType, timeout?: number) { - const req = trans.DownloadGroupImage.build(groupUin, node); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadImage.parse(resp); - return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; - } - - async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType, timeout?: number) { - const req = trans.DownloadGroupPtt.build(groupUin, node); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadImage.parse(resp); - return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; - } - - async GetGroupVideoUrl(groupUin: number, node: NapProtoEncodeStructType, timeout: number = 20000) { - const req = trans.DownloadGroupVideo.build(groupUin, node); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadImage.parse(resp); - return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`; - } - - - async ImageOCR(imgUrl: string) { - const req = trans.ImageOCR.build(imgUrl); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.ImageOCR.parse(resp); + async ImageOCR (imgUrl: string) { + const req = trans.ImageOCR.build(imgUrl); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.ImageOCR.parse(resp); + return { + texts: res.ocrRspBody.textDetections.map((item) => { return { - texts: res.ocrRspBody.textDetections.map((item) => { - return { - text: item.detectedText, - confidence: item.confidence, - coordinates: item.polygon.coordinates.map((c) => { - return { - x: c.x, - y: c.y, - }; - }), - }; - }), - language: res.ocrRspBody.language, - } as ImageOcrResult; - } - - private async SendPreprocess(msg: PacketMsg[], groupUin: number = 0) { - const ps = msg.map((m) => { - return m.msg.map(async (e) => { - if (e instanceof PacketMsgReplyElement && !e.targetElems) { - this.context.logger.debug(`Cannot find reply element's targetElems, prepare to fetch it...`); - if (!e.targetPeer?.peerUid) { - this.context.logger.error(`targetPeer is undefined!`); - } - let targetMsg: NapProtoEncodeStructType[] | undefined; - if (e.isGroupReply) { - targetMsg = await this.FetchGroupMessage(+(e.targetPeer?.peerUid ?? 0), e.targetMessageSeq, e.targetMessageSeq); - } else { - targetMsg = await this.FetchC2CMessage(await this.context.napcore.basicInfo.uin2uid(e.targetUin), e.targetMessageSeq, e.targetMessageSeq); - } - e.targetElems = targetMsg.at(0)?.body?.richText?.elems; - e.targetSourceMsg = targetMsg.at(0); - } - }); - }).flat(); - await Promise.all(ps) - await this.UploadResources(msg, groupUin); - } - - async FetchGroupMessage(groupUin: number, startSeq: number, endSeq: number): Promise[]> { - const req = trans.FetchGroupMessage.build(groupUin, startSeq, endSeq); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.FetchGroupMessage.parse(resp); - return res.body.messages - } - - async FetchC2CMessage(targetUid: string, startSeq: number, endSeq: number): Promise[]> { - const req = trans.FetchC2CMessage.build(targetUid, startSeq, endSeq); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.FetchC2CMessage.parse(resp); - return res.messages - } - - async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) { - await this.SendPreprocess(msg, groupUin); - const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.UploadForwardMsg.parse(resp); - return res.result.resId; - } - - async MoveGroupFile( - groupUin: number, - fileUUID: string, - currentParentDirectory: string, - targetParentDirectory: string - ) { - const req = trans.MoveGroupFile.build(groupUin, fileUUID, currentParentDirectory, targetParentDirectory); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.MoveGroupFile.parse(resp); - return res.move.retCode; - } - - async RenameGroupFile(groupUin: number, fileUUID: string, currentParentDirectory: string, newName: string) { - const req = trans.RenameGroupFile.build(groupUin, fileUUID, currentParentDirectory, newName); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.RenameGroupFile.parse(resp); - return res.rename.retCode; - } - - async GetGroupFileUrl(groupUin: number, fileUUID: string, timeout?: number) { - const req = trans.DownloadGroupFile.build(groupUin, fileUUID); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadGroupFile.parse(resp); - return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`; - } - - async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string, timeout?: number) { - const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5); - const resp = await this.context.client.sendOidbPacket(req, true, timeout); - const res = trans.DownloadPrivateFile.parse(resp); - return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`; - } - - async GetMiniAppAdaptShareInfo(param: MiniAppReqParams) { - const req = trans.GetMiniAppAdaptShareInfo.build(param); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.GetMiniAppAdaptShareInfo.parse(resp); - return JSON.parse(res.content.jsonContent) as MiniAppRawData; - } - - async FetchAiVoiceList(groupUin: number, chatType: AIVoiceChatType) { - const req = trans.FetchAiVoiceList.build(groupUin, chatType); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.FetchAiVoiceList.parse(resp); - if (!res.content) return null; - return res.content.map((item) => { + text: item.detectedText, + confidence: item.confidence, + coordinates: item.polygon.coordinates.map((c) => { return { - category: item.category, - voices: item.voices, + x: c.x, + y: c.y, }; - }); - } + }), + }; + }), + language: res.ocrRspBody.language, + } as ImageOcrResult; + } - async GetAiVoice( - groupUin: number, - voiceId: string, - text: string, - chatType: AIVoiceChatType - ): Promise> { - let reqTime = 0; - const reqMaxTime = 30; - const sessionId = crypto.randomBytes(4).readUInt32BE(0); - while (true) { - if (reqTime >= reqMaxTime) { - throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`); - } - reqTime++; - const req = trans.GetAiVoice.build(groupUin, voiceId, text, sessionId, chatType); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.GetAiVoice.parse(resp); - if (!res.msgInfo) continue; - return res.msgInfo; + private async SendPreprocess (msg: PacketMsg[], groupUin: number = 0) { + const ps = msg.map((m) => { + return m.msg.map(async (e) => { + if (e instanceof PacketMsgReplyElement && !e.targetElems) { + this.context.logger.debug('Cannot find reply element\'s targetElems, prepare to fetch it...'); + if (!e.targetPeer?.peerUid) { + this.context.logger.error('targetPeer is undefined!'); + } + let targetMsg: NapProtoEncodeStructType[] | undefined; + if (e.isGroupReply) { + targetMsg = await this.FetchGroupMessage(+(e.targetPeer?.peerUid ?? 0), e.targetMessageSeq, e.targetMessageSeq); + } else { + targetMsg = await this.FetchC2CMessage(await this.context.napcore.basicInfo.uin2uid(e.targetUin), e.targetMessageSeq, e.targetMessageSeq); + } + e.targetElems = targetMsg.at(0)?.body?.richText?.elems; + e.targetSourceMsg = targetMsg.at(0); } + }); + }).flat(); + await Promise.all(ps); + await this.UploadResources(msg, groupUin); + } + + async FetchGroupMessage (groupUin: number, startSeq: number, endSeq: number): Promise[]> { + const req = trans.FetchGroupMessage.build(groupUin, startSeq, endSeq); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.FetchGroupMessage.parse(resp); + return res.body.messages; + } + + async FetchC2CMessage (targetUid: string, startSeq: number, endSeq: number): Promise[]> { + const req = trans.FetchC2CMessage.build(targetUid, startSeq, endSeq); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.FetchC2CMessage.parse(resp); + return res.messages; + } + + async UploadForwardMsg (msg: PacketMsg[], groupUin: number = 0) { + await this.SendPreprocess(msg, groupUin); + const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.UploadForwardMsg.parse(resp); + return res.result.resId; + } + + async MoveGroupFile ( + groupUin: number, + fileUUID: string, + currentParentDirectory: string, + targetParentDirectory: string + ) { + const req = trans.MoveGroupFile.build(groupUin, fileUUID, currentParentDirectory, targetParentDirectory); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.MoveGroupFile.parse(resp); + return res.move.retCode; + } + + async RenameGroupFile (groupUin: number, fileUUID: string, currentParentDirectory: string, newName: string) { + const req = trans.RenameGroupFile.build(groupUin, fileUUID, currentParentDirectory, newName); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.RenameGroupFile.parse(resp); + return res.rename.retCode; + } + + async GetGroupFileUrl (groupUin: number, fileUUID: string, timeout?: number) { + const req = trans.DownloadGroupFile.build(groupUin, fileUUID); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadGroupFile.parse(resp); + return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`; + } + + async GetPrivateFileUrl (self_id: string, fileUUID: string, md5: string, timeout?: number) { + const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5); + const resp = await this.context.client.sendOidbPacket(req, true, timeout); + const res = trans.DownloadPrivateFile.parse(resp); + return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`; + } + + async GetMiniAppAdaptShareInfo (param: MiniAppReqParams) { + const req = trans.GetMiniAppAdaptShareInfo.build(param); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.GetMiniAppAdaptShareInfo.parse(resp); + return JSON.parse(res.content.jsonContent) as MiniAppRawData; + } + + async FetchAiVoiceList (groupUin: number, chatType: AIVoiceChatType) { + const req = trans.FetchAiVoiceList.build(groupUin, chatType); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.FetchAiVoiceList.parse(resp); + if (!res.content) return null; + return res.content.map((item) => { + return { + category: item.category, + voices: item.voices, + }; + }); + } + + async GetAiVoice ( + groupUin: number, + voiceId: string, + text: string, + chatType: AIVoiceChatType + ): Promise> { + let reqTime = 0; + const reqMaxTime = 30; + const sessionId = crypto.randomBytes(4).readUInt32BE(0); + while (true) { + if (reqTime >= reqMaxTime) { + throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`); + } + reqTime++; + const req = trans.GetAiVoice.build(groupUin, voiceId, text, sessionId, chatType); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.GetAiVoice.parse(resp); + if (!res.msgInfo) continue; + return res.msgInfo; } + } - async FetchForwardMsg(res_id: string): Promise { - const req = trans.DownloadForwardMsg.build(this.context.napcore.basicInfo.uid, res_id); - const resp = await this.context.client.sendOidbPacket(req, true); - const res = trans.DownloadForwardMsg.parse(resp); - const inflate = gunzipSync(res.result.payload); - const result = new NapProtoMsg(LongMsgResult).decode(inflate); + async FetchForwardMsg (res_id: string): Promise { + const req = trans.DownloadForwardMsg.build(this.context.napcore.basicInfo.uid, res_id); + const resp = await this.context.client.sendOidbPacket(req, true); + const res = trans.DownloadForwardMsg.parse(resp); + const inflate = gunzipSync(res.result.payload); + const result = new NapProtoMsg(LongMsgResult).decode(inflate); - const main = result.action.find((r) => r.actionCommand === 'MultiMsg'); - if (!main?.actionData.msgBody) { - throw new Error('msgBody is empty'); - } - this.context.logger.debug('rawChains ', inflate.toString('hex')); + const main = result.action.find((r) => r.actionCommand === 'MultiMsg'); + if (!main?.actionData.msgBody) { + throw new Error('msgBody is empty'); + } + this.context.logger.debug('rawChains ', inflate.toString('hex')); - const messagesPromises = main.actionData.msgBody.map(async (msg) => { - if (!msg?.body?.richText?.elems) { - throw new Error('msg.body.richText.elems is empty'); + const messagesPromises = main.actionData.msgBody.map(async (msg) => { + if (!msg?.body?.richText?.elems) { + throw new Error('msg.body.richText.elems is empty'); + } + const rawChains = new PacketMsgConverter().packetMsgToRaw(msg?.body?.richText?.elems); + const elements = await Promise.all( + rawChains.map(async ([element, rawElem]) => { + if (element.picElement && rawElem?.commonElem?.pbElem) { + const extra = new NapProtoMsg(MsgInfo).decode(rawElem.commonElem.pbElem); + const index = extra?.msgInfoBody[0]?.index; + if (msg?.responseHead.grp !== undefined) { + const groupUin = msg?.responseHead.grp?.groupUin ?? 0; + element.picElement = { + ...element.picElement, + originImageUrl: await this.GetGroupImageUrl(groupUin, index!), + }; + } else { + element.picElement = { + ...element.picElement, + originImageUrl: await this.GetImageUrl(this.context.napcore.basicInfo.uid, index!), + }; } - const rawChains = new PacketMsgConverter().packetMsgToRaw(msg?.body?.richText?.elems); - const elements = await Promise.all( - rawChains.map(async ([element, rawElem]) => { - if (element.picElement && rawElem?.commonElem?.pbElem) { - const extra = new NapProtoMsg(MsgInfo).decode(rawElem.commonElem.pbElem); - const index = extra?.msgInfoBody[0]?.index; - if (msg?.responseHead.grp !== undefined) { - const groupUin = msg?.responseHead.grp?.groupUin ?? 0; - element.picElement = { - ...element.picElement, - originImageUrl: await this.GetGroupImageUrl(groupUin, index!), - }; - } else { - element.picElement = { - ...element.picElement, - originImageUrl: await this.GetImageUrl(this.context.napcore.basicInfo.uid, index!), - }; - } - return element; - } - return element; - }) - ); - return { - chatType: ChatType.KCHATTYPEGROUP, - elements: elements, - guildId: '', - isOnlineMsg: false, - msgId: '7467703692092974645', // TODO: no necessary - msgRandom: '0', - msgSeq: String(msg.contentHead.sequence ?? 0), - msgTime: String(msg.contentHead.timeStamp ?? 0), - msgType: NTMsgType.KMSGTYPEMIX, - parentMsgIdList: [], - parentMsgPeer: { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: String(msg?.responseHead.grp?.groupUin ?? 0), - }, - peerName: '', - peerUid: '1094950020', - peerUin: '1094950020', - recallTime: '0', - records: [], - sendNickName: msg?.responseHead.grp?.memberName ?? '', - sendRemarkName: msg?.responseHead.grp?.memberName ?? '', - senderUid: '', - senderUin: '1094950020', - sourceType: MsgSourceType.K_DOWN_SOURCETYPE_UNKNOWN, - subMsgType: 1, - }; - }); - return await Promise.all(messagesPromises); - } + return element; + } + return element; + }) + ); + return { + chatType: ChatType.KCHATTYPEGROUP, + elements, + guildId: '', + isOnlineMsg: false, + msgId: '7467703692092974645', // TODO: no necessary + msgRandom: '0', + msgSeq: String(msg.contentHead.sequence ?? 0), + msgTime: String(msg.contentHead.timeStamp ?? 0), + msgType: NTMsgType.KMSGTYPEMIX, + parentMsgIdList: [], + parentMsgPeer: { + chatType: ChatType.KCHATTYPEGROUP, + peerUid: String(msg?.responseHead.grp?.groupUin ?? 0), + }, + peerName: '', + peerUid: '1094950020', + peerUin: '1094950020', + recallTime: '0', + records: [], + sendNickName: msg?.responseHead.grp?.memberName ?? '', + sendRemarkName: msg?.responseHead.grp?.memberName ?? '', + senderUid: '', + senderUin: '1094950020', + sourceType: MsgSourceType.K_DOWN_SOURCETYPE_UNKNOWN, + subMsgType: 1, + }; + }); + return await Promise.all(messagesPromises); + } } diff --git a/src/core/packet/context/packetContext.ts b/src/core/packet/context/packetContext.ts index 6a158166..613dbbaa 100644 --- a/src/core/packet/context/packetContext.ts +++ b/src/core/packet/context/packetContext.ts @@ -7,19 +7,19 @@ import { PacketOperationContext } from '@/core/packet/context/operationContext'; import { PacketMsgConverter } from '@/core/packet/message/converter'; export class PacketContext { - readonly msgConverter: PacketMsgConverter; - readonly napcore: NapCoreContext; - readonly logger: PacketLogger; - readonly client: PacketClientContext; - readonly highway: PacketHighwayContext; - readonly operation: PacketOperationContext; + readonly msgConverter: PacketMsgConverter; + readonly napcore: NapCoreContext; + readonly logger: PacketLogger; + readonly client: PacketClientContext; + readonly highway: PacketHighwayContext; + readonly operation: PacketOperationContext; - constructor(core: NapCatCore) { - this.msgConverter = new PacketMsgConverter(); - this.napcore = new NapCoreContext(core); - this.logger = new PacketLogger(this.napcore); - this.client = new PacketClientContext(this.napcore, this.logger); - this.highway = new PacketHighwayContext(this.napcore, this.logger, this.client); - this.operation = new PacketOperationContext(this); - } + constructor (core: NapCatCore) { + this.msgConverter = new PacketMsgConverter(); + this.napcore = new NapCoreContext(core); + this.logger = new PacketLogger(this.napcore); + this.client = new PacketClientContext(this.napcore, this.logger); + this.highway = new PacketHighwayContext(this.napcore, this.logger, this.client); + this.operation = new PacketOperationContext(this); + } } diff --git a/src/core/packet/entities/aiChat.ts b/src/core/packet/entities/aiChat.ts index 08f1dfda..f565bc0c 100644 --- a/src/core/packet/entities/aiChat.ts +++ b/src/core/packet/entities/aiChat.ts @@ -1,16 +1,16 @@ export enum AIVoiceChatType { - Unknown = 0, - Sound = 1, - Sing = 2 + Unknown = 0, + Sound = 1, + Sing = 2, } export interface AIVoiceItem { - voiceId: string; - voiceDisplayName: string; - voiceExampleUrl: string; + voiceId: string; + voiceDisplayName: string; + voiceExampleUrl: string; } export interface AIVoiceItemList { - category: string; - voices: AIVoiceItem[]; + category: string; + voices: AIVoiceItem[]; } diff --git a/src/core/packet/entities/miniApp.ts b/src/core/packet/entities/miniApp.ts index 67c58513..08782c09 100644 --- a/src/core/packet/entities/miniApp.ts +++ b/src/core/packet/entities/miniApp.ts @@ -1,80 +1,80 @@ export interface MiniAppReqCustomParams { - title: string; - desc: string; - picUrl: string; - jumpUrl: string; - webUrl?: string; + title: string; + desc: string; + picUrl: string; + jumpUrl: string; + webUrl?: string; } export interface MiniAppReqTemplateParams { - sdkId: string; - appId: string; - scene: number; - iconUrl: string; - templateType: number; - businessType: number; - verType: number; - shareType: number; - versionId: string; - withShareTicket: number; + sdkId: string; + appId: string; + scene: number; + iconUrl: string; + templateType: number; + businessType: number; + verType: number; + shareType: number; + versionId: string; + withShareTicket: number; } export interface MiniAppReqParams extends MiniAppReqCustomParams, MiniAppReqTemplateParams {} export interface MiniAppData { - ver: string; - prompt: string; - config: Config; - app: string; - view: string; - meta: MetaData; - miniappShareOrigin: number; - miniappOpenRefer: string; + ver: string; + prompt: string; + config: Config; + app: string; + view: string; + meta: MetaData; + miniappShareOrigin: number; + miniappOpenRefer: string; } export interface MiniAppRawData { - appName: string; - appView: string; - ver: string; - desc: string; - prompt: string; - metaData: MetaData; - config: Config; + appName: string; + appView: string; + ver: string; + desc: string; + prompt: string; + metaData: MetaData; + config: Config; } interface Config { - type: string; - width: number; - height: number; - forward: number; - autoSize: number; - ctime: number; - token: string; + type: string; + width: number; + height: number; + forward: number; + autoSize: number; + ctime: number; + token: string; } interface Host { - uin: number; - nick: string; + uin: number; + nick: string; } interface Detail { - appid: string; - appType: number; - title: string; - desc: string; - icon: string; - preview: string; - url: string; - scene: number; - host: Host; - shareTemplateId: string; - shareTemplateData: Record; - showLittleTail: string; - gamePoints: string; - gamePointsUrl: string; - shareOrigin: number; + appid: string; + appType: number; + title: string; + desc: string; + icon: string; + preview: string; + url: string; + scene: number; + host: Host; + shareTemplateId: string; + shareTemplateData: Record; + showLittleTail: string; + gamePoints: string; + gamePointsUrl: string; + shareOrigin: number; } interface MetaData { - detail_1: Detail; + detail_1: Detail; } diff --git a/src/core/packet/entities/ocrResult.ts b/src/core/packet/entities/ocrResult.ts index 31fdefa8..4f5eac7c 100644 --- a/src/core/packet/entities/ocrResult.ts +++ b/src/core/packet/entities/ocrResult.ts @@ -1,15 +1,15 @@ export interface ImageOcrResult { - texts: TextDetection[]; - language: string; + texts: TextDetection[]; + language: string; } export interface TextDetection { - text: string; - confidence: number; - coordinates: Coordinate[]; + text: string; + confidence: number; + coordinates: Coordinate[]; } export interface Coordinate { - x: number; - y: number; + x: number; + y: number; } diff --git a/src/core/packet/handler/client.ts b/src/core/packet/handler/client.ts index 8e19fc0a..ed917be7 100644 --- a/src/core/packet/handler/client.ts +++ b/src/core/packet/handler/client.ts @@ -5,210 +5,208 @@ import { constants } from 'node:os'; import { LogWrapper } from '@/common/log'; import offset from '@/core/external/packet.json'; interface OffsetType { - [key: string]: { - recv: string; - send: string; - }; + [key: string]: { + recv: string; + send: string; + }; } const typedOffset: OffsetType = offset; // 0 send 1 recv export interface NativePacketExportType { - initHook?: (send: string, recv: string, callback: (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean; + initHook?: (send: string, recv: string, callback: (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean; } export type PacketType = 0 | 1; // 0: send, 1: recv export type PacketCallback = (data: { type: PacketType, uin: string, cmd: string, seq: number, hex_data: string }) => void; interface ListenerEntry { - callback: PacketCallback; - once: boolean; + callback: PacketCallback; + once: boolean; } export class NativePacketHandler { - private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; - private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} }; - protected readonly logger: LogWrapper; + private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64', 'darwin.x64', 'darwin.arm64']; + private readonly MoeHooExport: { exports: NativePacketExportType } = { exports: {} }; + protected readonly logger: LogWrapper; - // 统一的监听器存储 - key: 'all' | 'type:0' | 'type:1' | 'cmd:xxx' | 'exact:type:cmd' - private readonly listeners: Map> = new Map(); + // 统一的监听器存储 - key: 'all' | 'type:0' | 'type:1' | 'cmd:xxx' | 'exact:type:cmd' + private readonly listeners: Map> = new Map(); + constructor ({ logger }: { logger: LogWrapper }) { + this.logger = logger; + } - constructor({ logger }: { logger: LogWrapper }) { - this.logger = logger; - } - - /** + /** * 添加监听器的通用方法 */ - private addListener(key: string, callback: PacketCallback, once: boolean = false): () => void { - if (!this.listeners.has(key)) { - this.listeners.set(key, new Set()); - } - const entry: ListenerEntry = { callback, once }; - this.listeners.get(key)!.add(entry); - return () => this.removeListener(key, callback); + private addListener (key: string, callback: PacketCallback, once: boolean = false): () => void { + if (!this.listeners.has(key)) { + this.listeners.set(key, new Set()); } + const entry: ListenerEntry = { callback, once }; + this.listeners.get(key)!.add(entry); + return () => this.removeListener(key, callback); + } - /** + /** * 移除监听器的通用方法 */ - private removeListener(key: string, callback: PacketCallback): boolean { - const entries = this.listeners.get(key); - if (!entries) return false; + private removeListener (key: string, callback: PacketCallback): boolean { + const entries = this.listeners.get(key); + if (!entries) return false; - for (const entry of entries) { - if (entry.callback === callback) { - return entries.delete(entry); - } - } - return false; + for (const entry of entries) { + if (entry.callback === callback) { + return entries.delete(entry); + } } + return false; + } - // ===== 永久监听器 ===== + // ===== 永久监听器 ===== - /** 监听所有数据包 */ - onAll(callback: PacketCallback): () => void { - return this.addListener('all', callback); - } + /** 监听所有数据包 */ + onAll (callback: PacketCallback): () => void { + return this.addListener('all', callback); + } - /** 监听特定类型的数据包 (0: send, 1: recv) */ - onType(type: PacketType, callback: PacketCallback): () => void { - return this.addListener(`type:${type}`, callback); - } + /** 监听特定类型的数据包 (0: send, 1: recv) */ + onType (type: PacketType, callback: PacketCallback): () => void { + return this.addListener(`type:${type}`, callback); + } - /** 监听所有发送的数据包 */ - onSend(callback: PacketCallback): () => void { - return this.onType(0, callback); - } + /** 监听所有发送的数据包 */ + onSend (callback: PacketCallback): () => void { + return this.onType(0, callback); + } - /** 监听所有接收的数据包 */ - onRecv(callback: PacketCallback): () => void { - return this.onType(1, callback); - } + /** 监听所有接收的数据包 */ + onRecv (callback: PacketCallback): () => void { + return this.onType(1, callback); + } - /** 监听特定cmd的数据包(不限type) */ - onCmd(cmd: string, callback: PacketCallback): () => void { - return this.addListener(`cmd:${cmd}`, callback); - } + /** 监听特定cmd的数据包(不限type) */ + onCmd (cmd: string, callback: PacketCallback): () => void { + return this.addListener(`cmd:${cmd}`, callback); + } - /** 监听特定type和cmd的数据包(精确匹配) */ - onExact(type: PacketType, cmd: string, callback: PacketCallback): () => void { - return this.addListener(`exact:${type}:${cmd}`, callback); - } + /** 监听特定type和cmd的数据包(精确匹配) */ + onExact (type: PacketType, cmd: string, callback: PacketCallback): () => void { + return this.addListener(`exact:${type}:${cmd}`, callback); + } - // ===== 一次性监听器 ===== + // ===== 一次性监听器 ===== - /** 一次性监听所有数据包 */ - onceAll(callback: PacketCallback): () => void { - return this.addListener('all', callback, true); - } + /** 一次性监听所有数据包 */ + onceAll (callback: PacketCallback): () => void { + return this.addListener('all', callback, true); + } - /** 一次性监听特定类型的数据包 */ - onceType(type: PacketType, callback: PacketCallback): () => void { - return this.addListener(`type:${type}`, callback, true); - } + /** 一次性监听特定类型的数据包 */ + onceType (type: PacketType, callback: PacketCallback): () => void { + return this.addListener(`type:${type}`, callback, true); + } - /** 一次性监听所有发送的数据包 */ - onceSend(callback: PacketCallback): () => void { - return this.onceType(0, callback); - } + /** 一次性监听所有发送的数据包 */ + onceSend (callback: PacketCallback): () => void { + return this.onceType(0, callback); + } - /** 一次性监听所有接收的数据包 */ - onceRecv(callback: PacketCallback): () => void { - return this.onceType(1, callback); - } + /** 一次性监听所有接收的数据包 */ + onceRecv (callback: PacketCallback): () => void { + return this.onceType(1, callback); + } - /** 一次性监听特定cmd的数据包 */ - onceCmd(cmd: string, callback: PacketCallback): () => void { - return this.addListener(`cmd:${cmd}`, callback, true); - } + /** 一次性监听特定cmd的数据包 */ + onceCmd (cmd: string, callback: PacketCallback): () => void { + return this.addListener(`cmd:${cmd}`, callback, true); + } - /** 一次性监听特定type和cmd的数据包 */ - onceExact(type: PacketType, cmd: string, callback: PacketCallback): () => void { - return this.addListener(`exact:${type}:${cmd}`, callback, true); - } + /** 一次性监听特定type和cmd的数据包 */ + onceExact (type: PacketType, cmd: string, callback: PacketCallback): () => void { + return this.addListener(`exact:${type}:${cmd}`, callback, true); + } - // ===== 移除监听器 ===== + // ===== 移除监听器 ===== - /** 移除特定的全局监听器 */ - off(key: string, callback: PacketCallback): boolean { - return this.removeListener(key, callback); - } + /** 移除特定的全局监听器 */ + off (key: string, callback: PacketCallback): boolean { + return this.removeListener(key, callback); + } - /** 移除特定key下的所有监听器 */ - offAll(key: string): void { - this.listeners.delete(key); - } + /** 移除特定key下的所有监听器 */ + offAll (key: string): void { + this.listeners.delete(key); + } - /** 移除所有监听器 */ - removeAllListeners(): void { - this.listeners.clear(); - } + /** 移除所有监听器 */ + removeAllListeners (): void { + this.listeners.clear(); + } - /** + /** * 触发监听器 - 按优先级触发: 精确匹配 > cmd匹配 > type匹配 > 全局 */ - private emitPacket(type: PacketType, uin: string, cmd: string, seq: number, hex_data: string): void { - const keys = [ + private emitPacket (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string): void { + const keys = [ `exact:${type}:${cmd}`, // 精确匹配 `cmd:${cmd}`, // cmd匹配 `type:${type}`, // type匹配 - 'all' // 全局 - ]; + 'all', // 全局 + ]; - for (const key of keys) { - const entries = this.listeners.get(key); - if (!entries) continue; + for (const key of keys) { + const entries = this.listeners.get(key); + if (!entries) continue; - const toRemove: ListenerEntry[] = []; - for (const entry of entries) { - try { - entry.callback({ type, uin, cmd, seq, hex_data }); - if (entry.once) { - toRemove.push(entry); - } - } catch (error) { - this.logger.logError('监听器回调执行出错:', error); - } - } - - // 移除一次性监听器 - for (const entry of toRemove) { - entries.delete(entry); - } - } - } - - async init(version: string): Promise { - const version_arch = version + '-' + process.arch; + const toRemove: ListenerEntry[] = []; + for (const entry of entries) { try { - const send = typedOffset[version_arch]?.send; - const recv = typedOffset[version_arch]?.recv; - if (!send || !recv) { - this.logger.logWarn(`NativePacketClient: 未找到对应版本的偏移数据: ${version_arch}`); - return false; - } - const platform = process.platform + '.' + process.arch; - if (!this.supportedPlatforms.includes(platform)) { - this.logger.logWarn(`NativePacketClient: 不支持的平台: ${platform}`); - return false; - } - const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/packet/MoeHoo.' + platform + '.node'); + entry.callback({ type, uin, cmd, seq, hex_data }); + if (entry.once) { + toRemove.push(entry); + } + } catch (error) { + this.logger.logError('监听器回调执行出错:', error); + } + } - process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); - if (!fs.existsSync(moehoo_path)) { - this.logger.logWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); - return false; - } - this.MoeHooExport.exports.initHook?.(send, recv, (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => { - this.emitPacket(type, uin, cmd, seq, hex_data); - }, true); - return true; - } - catch (error) { - this.logger.logError('NativePacketClient 初始化出错:', error); - return false; - } + // 移除一次性监听器 + for (const entry of toRemove) { + entries.delete(entry); + } } + } + + async init (version: string): Promise { + const version_arch = version + '-' + process.arch; + try { + const send = typedOffset[version_arch]?.send; + const recv = typedOffset[version_arch]?.recv; + if (!send || !recv) { + this.logger.logWarn(`NativePacketClient: 未找到对应版本的偏移数据: ${version_arch}`); + return false; + } + const platform = process.platform + '.' + process.arch; + if (!this.supportedPlatforms.includes(platform)) { + this.logger.logWarn(`NativePacketClient: 不支持的平台: ${platform}`); + return false; + } + const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './native/packet/MoeHoo.' + platform + '.node'); + + process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); + if (!fs.existsSync(moehoo_path)) { + this.logger.logWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); + return false; + } + this.MoeHooExport.exports.initHook?.(send, recv, (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => { + this.emitPacket(type, uin, cmd, seq, hex_data); + }, true); + return true; + } catch (error) { + this.logger.logError('NativePacketClient 初始化出错:', error); + return false; + } + } } diff --git a/src/core/packet/highway/client.ts b/src/core/packet/highway/client.ts index d5c55362..c387102c 100644 --- a/src/core/packet/highway/client.ts +++ b/src/core/packet/highway/client.ts @@ -6,69 +6,68 @@ import { PacketHighwaySig } from '@/core/packet/highway/highwayContext'; import { PacketLogger } from '@/core/packet/context/loggerContext'; export interface PacketHighwayTrans { - uin: string; - cmd: number; - command: string; - data: stream.Readable; - sum: Uint8Array; - size: number; - ticket: Uint8Array; - loginSig?: Uint8Array; - ext: Uint8Array; - encrypt: boolean; - timeout?: number; - server: string; - port: number; + uin: string; + cmd: number; + command: string; + data: stream.Readable; + sum: Uint8Array; + size: number; + ticket: Uint8Array; + loginSig?: Uint8Array; + ext: Uint8Array; + encrypt: boolean; + timeout?: number; + server: string; + port: number; } export class PacketHighwayClient { - sig: PacketHighwaySig; - server: string = 'htdata3.qq.com'; - port: number = 80; - logger: PacketLogger; + sig: PacketHighwaySig; + server: string = 'htdata3.qq.com'; + port: number = 80; + logger: PacketLogger; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(sig: PacketHighwaySig, logger: PacketLogger, _server: string = 'htdata3.qq.com', _port: number = 80) { - this.sig = sig; - this.logger = logger; - } + constructor (sig: PacketHighwaySig, logger: PacketLogger, _server: string = 'htdata3.qq.com', _port: number = 80) { + this.sig = sig; + this.logger = logger; + } - changeServer(server: string, port: number) { - this.server = server; - this.port = port; - } + changeServer (server: string, port: number) { + this.server = server; + this.port = port; + } - private buildDataUpTrans(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 1200): PacketHighwayTrans { - return { - uin: this.sig.uin, - cmd: cmd, - command: 'PicUp.DataUp', - data: data, - sum: md5, - size: fileSize, - ticket: this.sig.sigSession!, - ext: extendInfo, - encrypt: false, - timeout: timeout, - server: this.server, - port: this.port, - } as PacketHighwayTrans; - } + private buildDataUpTrans (cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array, timeout: number = 1200): PacketHighwayTrans { + return { + uin: this.sig.uin, + cmd, + command: 'PicUp.DataUp', + data, + sum: md5, + size: fileSize, + ticket: this.sig.sigSession!, + ext: extendInfo, + encrypt: false, + timeout, + server: this.server, + port: this.port, + } as PacketHighwayTrans; + } - async upload(cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array): Promise { - const trans = this.buildDataUpTrans(cmd, data, fileSize, md5, extendInfo); - try { - const tcpUploader = new HighwayTcpUploader(trans, this.logger); - await tcpUploader.upload(); - } catch (e) { - this.logger.error(`[Highway] upload failed: ${e}, fallback to http upload`); - try { - const httpUploader = new HighwayHttpUploader(trans, this.logger); - await httpUploader.upload(); - } catch (e) { - this.logger.error(`[Highway] http upload failed: ${e}`); - throw e; - } - } + async upload (cmd: number, data: ReadStream, fileSize: number, md5: Uint8Array, extendInfo: Uint8Array): Promise { + const trans = this.buildDataUpTrans(cmd, data, fileSize, md5, extendInfo); + try { + const tcpUploader = new HighwayTcpUploader(trans, this.logger); + await tcpUploader.upload(); + } catch (e) { + this.logger.error(`[Highway] upload failed: ${e}, fallback to http upload`); + try { + const httpUploader = new HighwayHttpUploader(trans, this.logger); + await httpUploader.upload(); + } catch (e) { + this.logger.error(`[Highway] http upload failed: ${e}`); + throw e; + } } + } } diff --git a/src/core/packet/highway/frame.ts b/src/core/packet/highway/frame.ts index 96806d2c..8e6c5f68 100644 --- a/src/core/packet/highway/frame.ts +++ b/src/core/packet/highway/frame.ts @@ -1,23 +1,23 @@ import assert from 'node:assert'; -export class Frame{ - static pack(head: Buffer, body: Buffer): Buffer { - const totalLength = 9 + head.length + body.length + 1; - const buffer = Buffer.allocUnsafe(totalLength); - buffer[0] = 0x28; - buffer.writeUInt32BE(head.length, 1); - buffer.writeUInt32BE(body.length, 5); - head.copy(buffer, 9); - body.copy(buffer, 9 + head.length); - buffer[totalLength - 1] = 0x29; - return buffer; - } +export class Frame { + static pack (head: Buffer, body: Buffer): Buffer { + const totalLength = 9 + head.length + body.length + 1; + const buffer = Buffer.allocUnsafe(totalLength); + buffer[0] = 0x28; + buffer.writeUInt32BE(head.length, 1); + buffer.writeUInt32BE(body.length, 5); + head.copy(buffer, 9); + body.copy(buffer, 9 + head.length); + buffer[totalLength - 1] = 0x29; + return buffer; + } - static unpack(frame: Buffer): [Buffer, Buffer] { - assert(frame[0] === 0x28 && frame[frame.length - 1] === 0x29, 'Invalid frame!'); - const headLen = frame.readUInt32BE(1); - const bodyLen = frame.readUInt32BE(5); - // assert(frame.length === 9 + headLen + bodyLen + 1, `Frame ${frame.toString('hex')} length does not match head and body lengths!`); - return [frame.subarray(9, 9 + headLen), frame.subarray(9 + headLen, 9 + headLen + bodyLen)]; - } + static unpack (frame: Buffer): [Buffer, Buffer] { + assert(frame[0] === 0x28 && frame[frame.length - 1] === 0x29, 'Invalid frame!'); + const headLen = frame.readUInt32BE(1); + const bodyLen = frame.readUInt32BE(5); + // assert(frame.length === 9 + headLen + bodyLen + 1, `Frame ${frame.toString('hex')} length does not match head and body lengths!`); + return [frame.subarray(9, 9 + headLen), frame.subarray(9 + headLen, 9 + headLen + bodyLen)]; + } } diff --git a/src/core/packet/highway/utils.ts b/src/core/packet/highway/utils.ts index 18cf7462..d84d9d8c 100644 --- a/src/core/packet/highway/utils.ts +++ b/src/core/packet/highway/utils.ts @@ -1,20 +1,19 @@ import { NapProtoEncodeStructType } from '@napneko/nap-proto-core'; import * as proto from '@/core/packet/transformer/proto'; - export const int32ip2str = (ip: number) => { - ip = ip & 0xffffffff; - return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.'); + ip = ip & 0xffffffff; + return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.'); }; export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType[]): NapProtoEncodeStructType[] => { - return ipv4s.map((ip) => { - return { - domain: { - isEnable: true, - ip: int32ip2str(ip.outIP ?? 0), - }, - port: ip.outPort! - } as NapProtoEncodeStructType; - }); + return ipv4s.map((ip) => { + return { + domain: { + isEnable: true, + ip: int32ip2str(ip.outIP ?? 0), + }, + port: ip.outPort!, + } as NapProtoEncodeStructType; + }); }; diff --git a/src/core/packet/transformer/action/FetchAiVoiceList.ts b/src/core/packet/transformer/action/FetchAiVoiceList.ts index feb46491..f7ad4fab 100644 --- a/src/core/packet/transformer/action/FetchAiVoiceList.ts +++ b/src/core/packet/transformer/action/FetchAiVoiceList.ts @@ -5,22 +5,22 @@ import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; import { AIVoiceChatType } from '@/core/packet/entities/aiChat'; class FetchAiVoiceList extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(groupUin: number, chatType: AIVoiceChatType): OidbPacket { - const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0).encode({ - groupUin: groupUin, - chatType: chatType - }); - return OidbBase.build(0x929D, 0, data); - } + build (groupUin: number, chatType: AIVoiceChatType): OidbPacket { + const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0).encode({ + groupUin, + chatType, + }); + return OidbBase.build(0x929D, 0, data); + } - parse(data: Buffer) { - const oidbBody = OidbBase.parse(data).body; - return new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0Resp).decode(oidbBody); - } + parse (data: Buffer) { + const oidbBody = OidbBase.parse(data).body; + return new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0Resp).decode(oidbBody); + } } export default new FetchAiVoiceList(); diff --git a/src/core/packet/transformer/action/GroupSign.ts b/src/core/packet/transformer/action/GroupSign.ts index 11858847..c880b301 100644 --- a/src/core/packet/transformer/action/GroupSign.ts +++ b/src/core/packet/transformer/action/GroupSign.ts @@ -4,26 +4,26 @@ import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base'; import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; class GroupSign extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(uin: number, groupCode: number): OidbPacket { - const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XEB7).encode( - { - body: { - uin: String(uin), - groupUin: String(groupCode), - version: '9.0.90' - } - } - ); - return OidbBase.build(0XEB7, 1, body, false, false); - } + build (uin: number, groupCode: number): OidbPacket { + const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XEB7).encode( + { + body: { + uin: String(uin), + groupUin: String(groupCode), + version: '9.0.90', + }, + } + ); + return OidbBase.build(0XEB7, 1, body, false, false); + } - parse(data: Buffer) { - return OidbBase.parse(data); - } + parse (data: Buffer) { + return OidbBase.parse(data); + } } export default new GroupSign(); diff --git a/src/core/packet/transformer/action/SendPoke.ts b/src/core/packet/transformer/action/SendPoke.ts index 757c6ec4..782aaba3 100644 --- a/src/core/packet/transformer/action/SendPoke.ts +++ b/src/core/packet/transformer/action/SendPoke.ts @@ -4,23 +4,23 @@ import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base'; import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; class SendPoke extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(is_group: boolean, peer: number, target: number): OidbPacket { - const payload = { - uin: target, - ext: 0, - ...(is_group ? { groupUin: peer } : { friendUin: peer }) - }; - const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode(payload); - return OidbBase.build(0xED3, 1, data); - } + build (is_group: boolean, peer: number, target: number): OidbPacket { + const payload = { + uin: target, + ext: 0, + ...(is_group ? { groupUin: peer } : { friendUin: peer }), + }; + const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode(payload); + return OidbBase.build(0xED3, 1, data); + } - parse(data: Buffer) { - return OidbBase.parse(data); - } + parse (data: Buffer) { + return OidbBase.parse(data); + } } export default new SendPoke(); diff --git a/src/core/packet/transformer/action/SetGroupTodo.ts b/src/core/packet/transformer/action/SetGroupTodo.ts index e535d69d..e22e5472 100644 --- a/src/core/packet/transformer/action/SetGroupTodo.ts +++ b/src/core/packet/transformer/action/SetGroupTodo.ts @@ -4,21 +4,21 @@ import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base'; import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; class SetGroupTodo extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(peer: number, msgSeq: string): OidbPacket { - const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XF90_1).encode({ - groupUin: peer, - msgSeq: BigInt(msgSeq) - }); - return OidbBase.build(0xF90, 1, data); - } + build (peer: number, msgSeq: string): OidbPacket { + const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XF90_1).encode({ + groupUin: peer, + msgSeq: BigInt(msgSeq), + }); + return OidbBase.build(0xF90, 1, data); + } - parse(data: Buffer) { - return OidbBase.parse(data); - } + parse (data: Buffer) { + return OidbBase.parse(data); + } } -export default new SetGroupTodo(); \ No newline at end of file +export default new SetGroupTodo(); diff --git a/src/core/packet/transformer/action/SetSpecialTitle.ts b/src/core/packet/transformer/action/SetSpecialTitle.ts index 3d75fb36..3728fb53 100644 --- a/src/core/packet/transformer/action/SetSpecialTitle.ts +++ b/src/core/packet/transformer/action/SetSpecialTitle.ts @@ -4,26 +4,26 @@ import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base'; import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; class SetSpecialTitle extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(groupCode: number, uid: string, title: string): OidbPacket { - const oidb_0x8FC_2 = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2).encode({ - groupUin: +groupCode, - body: { - targetUid: uid, - specialTitle: title, - expiredTime: -1, - uinName: title - } - }); - return OidbBase.build(0x8FC, 2, oidb_0x8FC_2, false, false); - } + build (groupCode: number, uid: string, title: string): OidbPacket { + const oidb_0x8FC_2 = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2).encode({ + groupUin: +groupCode, + body: { + targetUid: uid, + specialTitle: title, + expiredTime: -1, + uinName: title, + }, + }); + return OidbBase.build(0x8FC, 2, oidb_0x8FC_2, false, false); + } - parse(data: Buffer) { - return OidbBase.parse(data); - } + parse (data: Buffer) { + return OidbBase.parse(data); + } } export default new SetSpecialTitle(); diff --git a/src/core/packet/transformer/action/index.ts b/src/core/packet/transformer/action/index.ts index f96312f8..88f3f6bf 100644 --- a/src/core/packet/transformer/action/index.ts +++ b/src/core/packet/transformer/action/index.ts @@ -8,4 +8,4 @@ export { default as SetSpecialTitle } from './SetSpecialTitle'; export { default as ImageOCR } from './ImageOCR'; export { default as MoveGroupFile } from './MoveGroupFile'; export { default as RenameGroupFile } from './RenameGroupFile'; -export { default as SetGroupTodo } from './SetGroupTodo'; \ No newline at end of file +export { default as SetGroupTodo } from './SetGroupTodo'; diff --git a/src/core/packet/transformer/base.ts b/src/core/packet/transformer/base.ts index bbe08353..63ae0c14 100644 --- a/src/core/packet/transformer/base.ts +++ b/src/core/packet/transformer/base.ts @@ -1,25 +1,25 @@ import { NapProtoDecodeStructType } from '@napneko/nap-proto-core'; import { PacketMsgBuilder } from '@/core/packet/message/builder'; -export type PacketBuf = Buffer & { readonly hexNya: unique symbol }; +export type PacketBuf = Buffer & { readonly hexNya: unique symbol; }; export const PacketBufBuilder = (str: Uint8Array): PacketBuf => { - return Buffer.from(str) as PacketBuf; + return Buffer.from(str) as PacketBuf; }; export interface OidbPacket { - cmd: string; - data: PacketBuf + cmd: string; + data: PacketBuf; } export abstract class PacketTransformer { - protected msgBuilder: PacketMsgBuilder; + protected msgBuilder: PacketMsgBuilder; - protected constructor() { - this.msgBuilder = new PacketMsgBuilder(); - } + constructor () { + this.msgBuilder = new PacketMsgBuilder(); + } - abstract build(...args: any[]): OidbPacket | Promise; + abstract build (...args: any[]): OidbPacket | Promise; - abstract parse(data: Buffer): NapProtoDecodeStructType; + abstract parse (data: Buffer): NapProtoDecodeStructType; } diff --git a/src/core/packet/transformer/highway/DownloadGroupPtt.ts b/src/core/packet/transformer/highway/DownloadGroupPtt.ts index cc5028a3..72aa97d3 100644 --- a/src/core/packet/transformer/highway/DownloadGroupPtt.ts +++ b/src/core/packet/transformer/highway/DownloadGroupPtt.ts @@ -4,46 +4,46 @@ import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base'; import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; class DownloadGroupPtt extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(groupUin: number, node: NapProtoEncodeStructType): OidbPacket { - const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({ - reqHead: { - common: { - requestId: 4, - command: 200 - }, - scene: { - requestType: 1, - businessType: 3, - sceneType: 2, - group: { - groupUin: groupUin - } - }, - client: { - agentType: 2 - } - }, - download: { - node: node, - download: { - video: { - busiType: 0, - sceneType: 0, - } - } - } - }); - return OidbBase.build(0x126E, 200, body, true, false); - } + build (groupUin: number, node: NapProtoEncodeStructType): OidbPacket { + const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({ + reqHead: { + common: { + requestId: 4, + command: 200, + }, + scene: { + requestType: 1, + businessType: 3, + sceneType: 2, + group: { + groupUin, + }, + }, + client: { + agentType: 2, + }, + }, + download: { + node, + download: { + video: { + busiType: 0, + sceneType: 0, + }, + }, + }, + }); + return OidbBase.build(0x126E, 200, body, true, false); + } - parse(data: Buffer) { - const oidbBody = OidbBase.parse(data).body; - return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody); - } + parse (data: Buffer) { + const oidbBody = OidbBase.parse(data).body; + return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody); + } } export default new DownloadGroupPtt(); diff --git a/src/core/packet/transformer/highway/DownloadPtt.ts b/src/core/packet/transformer/highway/DownloadPtt.ts index 41ab6c7e..fbdc2c21 100644 --- a/src/core/packet/transformer/highway/DownloadPtt.ts +++ b/src/core/packet/transformer/highway/DownloadPtt.ts @@ -5,47 +5,47 @@ import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; import { IndexNode } from '@/core/packet/transformer/proto'; class DownloadPtt extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(selfUid: string, node: NapProtoEncodeStructType): OidbPacket { - const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({ - reqHead: { - common: { - requestId: 1, - command: 200 - }, - scene: { - requestType: 1, - businessType: 3, - sceneType: 1, - c2C: { - accountType: 2, - targetUid: selfUid - }, - }, - client: { - agentType: 2, - } - }, - download: { - node: node, - download: { - video: { - busiType: 0, - sceneType: 0 - } - } - } - }); - return OidbBase.build(0x126D, 200, body, true, false); - } + build (selfUid: string, node: NapProtoEncodeStructType): OidbPacket { + const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({ + reqHead: { + common: { + requestId: 1, + command: 200, + }, + scene: { + requestType: 1, + businessType: 3, + sceneType: 1, + c2C: { + accountType: 2, + targetUid: selfUid, + }, + }, + client: { + agentType: 2, + }, + }, + download: { + node, + download: { + video: { + busiType: 0, + sceneType: 0, + }, + }, + }, + }); + return OidbBase.build(0x126D, 200, body, true, false); + } - parse(data: Buffer) { - const oidbBody = OidbBase.parse(data).body; - return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody); - } + parse (data: Buffer) { + const oidbBody = OidbBase.parse(data).body; + return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody); + } } export default new DownloadPtt(); diff --git a/src/core/packet/transformer/highway/UploadGroupPtt.ts b/src/core/packet/transformer/highway/UploadGroupPtt.ts index 272c090b..76b0481b 100644 --- a/src/core/packet/transformer/highway/UploadGroupPtt.ts +++ b/src/core/packet/transformer/highway/UploadGroupPtt.ts @@ -6,79 +6,77 @@ import crypto from 'node:crypto'; import { PacketMsgPttElement } from '@/core/packet/message/element'; class UploadGroupPtt extends PacketTransformer { - constructor() { - super(); - } - - build(groupUin: number, ptt: PacketMsgPttElement): OidbPacket { - const data = new NapProtoMsg(proto.NTV2RichMediaReq).encode({ - reqHead: { - common: { - requestId: 1, - command: 100 - }, - scene: { - requestType: 2, - businessType: 3, - sceneType: 2, - group: { - groupUin: groupUin - } - }, - client: { - agentType: 2 - } + build (groupUin: number, ptt: PacketMsgPttElement): OidbPacket { + const data = new NapProtoMsg(proto.NTV2RichMediaReq).encode({ + reqHead: { + common: { + requestId: 1, + command: 100, + }, + scene: { + requestType: 2, + businessType: 3, + sceneType: 2, + group: { + groupUin, + }, + }, + client: { + agentType: 2, + }, + }, + upload: { + uploadInfo: [ + { + fileInfo: { + fileSize: ptt.fileSize, + fileHash: ptt.fileMd5, + fileSha1: ptt.fileSha1, + fileName: `${ptt.fileMd5}.amr`, + type: { + type: 3, + picFormat: 0, + videoFormat: 0, + voiceFormat: 1, + }, + height: 0, + width: 0, + time: ptt.fileDuration, + original: 0, }, - upload: { - uploadInfo: [ - { - fileInfo: { - fileSize: ptt.fileSize, - fileHash: ptt.fileMd5, - fileSha1: ptt.fileSha1, - fileName: `${ptt.fileMd5}.amr`, - type: { - type: 3, - picFormat: 0, - videoFormat: 0, - voiceFormat: 1 - }, - height: 0, - width: 0, - time: ptt.fileDuration, - original: 0 - }, - subFileType: 0 - } - ], - tryFastUploadCompleted: true, - srvSendMsg: false, - clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'), - compatQMsgSceneType: 2, - extBizInfo: { - pic: { - textSummary: 'Nya~', - }, - video: { - bytesPbReserve: Buffer.alloc(0), - }, - ptt: { - bytesPbReserve: Buffer.alloc(0), - bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]), - bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x07, 0xaa, 0x03, 0x04, 0x08, 0x08, 0x12, 0x00]), - } - }, - clientSeq: 0, - noNeedCompatMsg: false - } - }); - return OidbBase.build(0x126E, 100, data, true, false); - } + subFileType: 0, + }, + ], + tryFastUploadCompleted: true, + srvSendMsg: false, + clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'), + compatQMsgSceneType: 2, + extBizInfo: { + pic: { + textSummary: 'Nya~', + }, + video: { + bytesPbReserve: Buffer.alloc(0), + }, + ptt: { + bytesPbReserve: Buffer.alloc(0), + bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]), + bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x07, 0xaa, 0x03, 0x04, 0x08, 0x08, 0x12, 0x00]), + }, + }, + clientSeq: 0, + noNeedCompatMsg: false, + }, + }); + return OidbBase.build(0x126E, 100, data, true, false); + } - parse(data: Buffer) { - const oidbBody = OidbBase.parse(data).body; - return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody); - } + parse (data: Buffer) { + const oidbBody = OidbBase.parse(data).body; + return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody); + } } -export default new UploadGroupPtt(); +const instance = new UploadGroupPtt(); + +export default instance; diff --git a/src/core/packet/transformer/oidb/oidbBase.ts b/src/core/packet/transformer/oidb/oidbBase.ts index 4e9e092c..bc54b8bf 100644 --- a/src/core/packet/transformer/oidb/oidbBase.ts +++ b/src/core/packet/transformer/oidb/oidbBase.ts @@ -3,30 +3,30 @@ import { NapProtoMsg } from '@napneko/nap-proto-core'; import { OidbPacket, PacketBufBuilder, PacketTransformer } from '@/core/packet/transformer/base'; class OidbBase extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, _isLafter: boolean = false): OidbPacket { - const data = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).encode({ - command: cmd, - subCommand: subCmd, - body: body, - isReserved: isUid ? 1 : 0 - }); - return { - cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`, - data: PacketBufBuilder(data), - }; - } + build (cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, _isLafter: boolean = false): OidbPacket { + const data = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).encode({ + command: cmd, + subCommand: subCmd, + body, + isReserved: isUid ? 1 : 0, + }); + return { + cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`, + data: PacketBufBuilder(data), + }; + } - parse(data: Buffer) { - const res = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).decode(data); - if (res.errorCode !== 0) { - throw new Error(`OidbSvcTrpcTcpBase parse error: ${res.errorMsg} (code=${res.errorCode})`); - } - return res; + parse (data: Buffer) { + const res = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).decode(data); + if (res.errorCode !== 0) { + throw new Error(`OidbSvcTrpcTcpBase parse error: ${res.errorMsg} (code=${res.errorCode})`); } + return res; + } } export default new OidbBase(); diff --git a/src/core/packet/transformer/proto/action/action.ts b/src/core/packet/transformer/proto/action/action.ts index bd135726..de3f611f 100644 --- a/src/core/packet/transformer/proto/action/action.ts +++ b/src/core/packet/transformer/proto/action/action.ts @@ -3,112 +3,112 @@ import { ProtoField } from '@napneko/nap-proto-core'; import { ContentHead, MessageBody, MessageControl, RoutingHead } from '@/core/packet/transformer/proto'; export const FaceRoamRequest = { - comm: ProtoField(1, () => PlatInfo, true), - selfUin: ProtoField(2, ScalarType.UINT32), - subCmd: ProtoField(3, ScalarType.UINT32), - field6: ProtoField(6, ScalarType.UINT32), + comm: ProtoField(1, () => PlatInfo, true), + selfUin: ProtoField(2, ScalarType.UINT32), + subCmd: ProtoField(3, ScalarType.UINT32), + field6: ProtoField(6, ScalarType.UINT32), }; export const PlatInfo = { - imPlat: ProtoField(1, ScalarType.UINT32), - osVersion: ProtoField(2, ScalarType.STRING, true), - qVersion: ProtoField(3, ScalarType.STRING, true), + imPlat: ProtoField(1, ScalarType.UINT32), + osVersion: ProtoField(2, ScalarType.STRING, true), + qVersion: ProtoField(3, ScalarType.STRING, true), }; export const FaceRoamResponse = { - retCode: ProtoField(1, ScalarType.UINT32), - errMsg: ProtoField(2, ScalarType.STRING), - subCmd: ProtoField(3, ScalarType.UINT32), - userInfo: ProtoField(6, () => FaceRoamUserInfo), + retCode: ProtoField(1, ScalarType.UINT32), + errMsg: ProtoField(2, ScalarType.STRING), + subCmd: ProtoField(3, ScalarType.UINT32), + userInfo: ProtoField(6, () => FaceRoamUserInfo), }; export const FaceRoamUserInfo = { - fileName: ProtoField(1, ScalarType.STRING, false, true), - deleteFile: ProtoField(2, ScalarType.STRING, false, true), - bid: ProtoField(3, ScalarType.STRING), - maxRoamSize: ProtoField(4, ScalarType.UINT32), - emojiType: ProtoField(5, ScalarType.UINT32, false, true), + fileName: ProtoField(1, ScalarType.STRING, false, true), + deleteFile: ProtoField(2, ScalarType.STRING, false, true), + bid: ProtoField(3, ScalarType.STRING), + maxRoamSize: ProtoField(4, ScalarType.UINT32), + emojiType: ProtoField(5, ScalarType.UINT32, false, true), }; export const SendMessageRequest = { - state: ProtoField(1, ScalarType.INT32), - sizeCache: ProtoField(2, ScalarType.INT32), - unknownFields: ProtoField(3, ScalarType.BYTES), - routingHead: ProtoField(4, () => RoutingHead), - contentHead: ProtoField(5, () => ContentHead), - messageBody: ProtoField(6, () => MessageBody), - msgSeq: ProtoField(7, ScalarType.INT32), - msgRand: ProtoField(8, ScalarType.INT32), - syncCookie: ProtoField(9, ScalarType.BYTES), - msgVia: ProtoField(10, ScalarType.INT32), - dataStatist: ProtoField(11, ScalarType.INT32), - messageControl: ProtoField(12, () => MessageControl), - multiSendSeq: ProtoField(13, ScalarType.INT32), + state: ProtoField(1, ScalarType.INT32), + sizeCache: ProtoField(2, ScalarType.INT32), + unknownFields: ProtoField(3, ScalarType.BYTES), + routingHead: ProtoField(4, () => RoutingHead), + contentHead: ProtoField(5, () => ContentHead), + messageBody: ProtoField(6, () => MessageBody), + msgSeq: ProtoField(7, ScalarType.INT32), + msgRand: ProtoField(8, ScalarType.INT32), + syncCookie: ProtoField(9, ScalarType.BYTES), + msgVia: ProtoField(10, ScalarType.INT32), + dataStatist: ProtoField(11, ScalarType.INT32), + messageControl: ProtoField(12, () => MessageControl), + multiSendSeq: ProtoField(13, ScalarType.INT32), }; export const SendMessageResponse = { - result: ProtoField(1, ScalarType.INT32), - errMsg: ProtoField(2, ScalarType.STRING, true), - timestamp1: ProtoField(3, ScalarType.UINT32), - field10: ProtoField(10, ScalarType.UINT32), - groupSequence: ProtoField(11, ScalarType.UINT32, true), - timestamp2: ProtoField(12, ScalarType.UINT32), - privateSequence: ProtoField(14, ScalarType.UINT32), + result: ProtoField(1, ScalarType.INT32), + errMsg: ProtoField(2, ScalarType.STRING, true), + timestamp1: ProtoField(3, ScalarType.UINT32), + field10: ProtoField(10, ScalarType.UINT32), + groupSequence: ProtoField(11, ScalarType.UINT32, true), + timestamp2: ProtoField(12, ScalarType.UINT32), + privateSequence: ProtoField(14, ScalarType.UINT32), }; export const SetStatus = { - status: ProtoField(1, ScalarType.UINT32), - extStatus: ProtoField(2, ScalarType.UINT32), - batteryStatus: ProtoField(3, ScalarType.UINT32), - customExt: ProtoField(4, () => SetStatusCustomExt, true), + status: ProtoField(1, ScalarType.UINT32), + extStatus: ProtoField(2, ScalarType.UINT32), + batteryStatus: ProtoField(3, ScalarType.UINT32), + customExt: ProtoField(4, () => SetStatusCustomExt, true), }; export const SetStatusCustomExt = { - faceId: ProtoField(1, ScalarType.UINT32), - text: ProtoField(2, ScalarType.STRING, true), - field3: ProtoField(3, ScalarType.UINT32), + faceId: ProtoField(1, ScalarType.UINT32), + text: ProtoField(2, ScalarType.STRING, true), + field3: ProtoField(3, ScalarType.UINT32), }; export const SetStatusResponse = { - message: ProtoField(2, ScalarType.STRING), + message: ProtoField(2, ScalarType.STRING), }; export const HttpConn = { - field1: ProtoField(1, ScalarType.INT32), - field2: ProtoField(2, ScalarType.INT32), - field3: ProtoField(3, ScalarType.INT32), - field4: ProtoField(4, ScalarType.INT32), - tgt: ProtoField(5, ScalarType.STRING), - field6: ProtoField(6, ScalarType.INT32), - serviceTypes: ProtoField(7, ScalarType.INT32, false, true), - field9: ProtoField(9, ScalarType.INT32), - field10: ProtoField(10, ScalarType.INT32), - field11: ProtoField(11, ScalarType.INT32), - ver: ProtoField(15, ScalarType.STRING), + field1: ProtoField(1, ScalarType.INT32), + field2: ProtoField(2, ScalarType.INT32), + field3: ProtoField(3, ScalarType.INT32), + field4: ProtoField(4, ScalarType.INT32), + tgt: ProtoField(5, ScalarType.STRING), + field6: ProtoField(6, ScalarType.INT32), + serviceTypes: ProtoField(7, ScalarType.INT32, false, true), + field9: ProtoField(9, ScalarType.INT32), + field10: ProtoField(10, ScalarType.INT32), + field11: ProtoField(11, ScalarType.INT32), + ver: ProtoField(15, ScalarType.STRING), }; export const HttpConn0x6ff_501 = { - httpConn: ProtoField(0x501, () => HttpConn), + httpConn: ProtoField(0x501, () => HttpConn), }; export const HttpConn0x6ff_501Response = { - httpConn: ProtoField(0x501, () => HttpConnResponse), + httpConn: ProtoField(0x501, () => HttpConnResponse), }; export const HttpConnResponse = { - sigSession: ProtoField(1, ScalarType.BYTES), - sessionKey: ProtoField(2, ScalarType.BYTES), - serverInfos: ProtoField(3, () => ServerInfo, false, true), + sigSession: ProtoField(1, ScalarType.BYTES), + sessionKey: ProtoField(2, ScalarType.BYTES), + serverInfos: ProtoField(3, () => ServerInfo, false, true), }; export const ServerAddr = { - type: ProtoField(1, ScalarType.UINT32), - ip: ProtoField(2, ScalarType.FIXED32), - port: ProtoField(3, ScalarType.UINT32), - area: ProtoField(4, ScalarType.UINT32), + type: ProtoField(1, ScalarType.UINT32), + ip: ProtoField(2, ScalarType.FIXED32), + port: ProtoField(3, ScalarType.UINT32), + area: ProtoField(4, ScalarType.UINT32), }; export const ServerInfo = { - serviceType: ProtoField(1, ScalarType.UINT32), - serverAddrs: ProtoField(2, () => ServerAddr, false, true), + serviceType: ProtoField(1, ScalarType.UINT32), + serverAddrs: ProtoField(2, () => ServerAddr, false, true), }; diff --git a/src/core/packet/transformer/proto/action/miniAppAdaptShareInfo.ts b/src/core/packet/transformer/proto/action/miniAppAdaptShareInfo.ts index 08e3c2a1..19f18dc4 100644 --- a/src/core/packet/transformer/proto/action/miniAppAdaptShareInfo.ts +++ b/src/core/packet/transformer/proto/action/miniAppAdaptShareInfo.ts @@ -1,48 +1,48 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const MiniAppAdaptShareInfoReq = { - appId: ProtoField(2, ScalarType.STRING), - body: ProtoField(4, () => MiniAppAdaptShareInfoReqBody), + appId: ProtoField(2, ScalarType.STRING), + body: ProtoField(4, () => MiniAppAdaptShareInfoReqBody), }; export const MiniAppAdaptShareInfoReqBody = { - extInfo: ProtoField(1, () => ExtInfo), - appid: ProtoField(2, ScalarType.STRING), - title: ProtoField(3, ScalarType.STRING), - desc: ProtoField(4, ScalarType.STRING), - time: ProtoField(5, ScalarType.UINT64), - scene: ProtoField(6, ScalarType.UINT32), - templateType: ProtoField(7, ScalarType.UINT32), - businessType: ProtoField(8, ScalarType.UINT32), - picUrl: ProtoField(9, ScalarType.STRING), - vidUrl: ProtoField(10, ScalarType.STRING), - jumpUrl: ProtoField(11, ScalarType.STRING), - iconUrl: ProtoField(12, ScalarType.STRING), - verType: ProtoField(13, ScalarType.UINT32), - shareType: ProtoField(14, ScalarType.UINT32), - versionId: ProtoField(15, ScalarType.STRING), - withShareTicket: ProtoField(16, ScalarType.UINT32), - webURL: ProtoField(17, ScalarType.STRING), - appidRich: ProtoField(18, ScalarType.BYTES), - template: ProtoField(19, () => Template), - field20: ProtoField(20, ScalarType.STRING), + extInfo: ProtoField(1, () => ExtInfo), + appid: ProtoField(2, ScalarType.STRING), + title: ProtoField(3, ScalarType.STRING), + desc: ProtoField(4, ScalarType.STRING), + time: ProtoField(5, ScalarType.UINT64), + scene: ProtoField(6, ScalarType.UINT32), + templateType: ProtoField(7, ScalarType.UINT32), + businessType: ProtoField(8, ScalarType.UINT32), + picUrl: ProtoField(9, ScalarType.STRING), + vidUrl: ProtoField(10, ScalarType.STRING), + jumpUrl: ProtoField(11, ScalarType.STRING), + iconUrl: ProtoField(12, ScalarType.STRING), + verType: ProtoField(13, ScalarType.UINT32), + shareType: ProtoField(14, ScalarType.UINT32), + versionId: ProtoField(15, ScalarType.STRING), + withShareTicket: ProtoField(16, ScalarType.UINT32), + webURL: ProtoField(17, ScalarType.STRING), + appidRich: ProtoField(18, ScalarType.BYTES), + template: ProtoField(19, () => Template), + field20: ProtoField(20, ScalarType.STRING), }; export const ExtInfo = { - field2: ProtoField(2, ScalarType.BYTES), + field2: ProtoField(2, ScalarType.BYTES), }; export const Template = { - templateId: ProtoField(1, ScalarType.STRING), - templateData: ProtoField(2, ScalarType.STRING), + templateId: ProtoField(1, ScalarType.STRING), + templateData: ProtoField(2, ScalarType.STRING), }; export const MiniAppAdaptShareInfoResp = { - field2: ProtoField(2, ScalarType.UINT32), - field3: ProtoField(3, ScalarType.STRING), - content: ProtoField(4, () => MiniAppAdaptShareInfoRespContent), + field2: ProtoField(2, ScalarType.UINT32), + field3: ProtoField(3, ScalarType.STRING), + content: ProtoField(4, () => MiniAppAdaptShareInfoRespContent), }; export const MiniAppAdaptShareInfoRespContent = { - jsonContent: ProtoField(2, ScalarType.STRING), + jsonContent: ProtoField(2, ScalarType.STRING), }; diff --git a/src/core/packet/transformer/proto/highway/highway.ts b/src/core/packet/transformer/proto/highway/highway.ts index 8e15e4e3..b6b69368 100644 --- a/src/core/packet/transformer/proto/highway/highway.ts +++ b/src/core/packet/transformer/proto/highway/highway.ts @@ -2,153 +2,153 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; import { MsgInfoBody } from '@/core/packet/transformer/proto'; export const DataHighwayHead = { - version: ProtoField(1, ScalarType.UINT32), - uin: ProtoField(2, ScalarType.STRING, true), - command: ProtoField(3, ScalarType.STRING, true), - seq: ProtoField(4, ScalarType.UINT32, true), - retryTimes: ProtoField(5, ScalarType.UINT32, true), - appId: ProtoField(6, ScalarType.UINT32), - dataFlag: ProtoField(7, ScalarType.UINT32), - commandId: ProtoField(8, ScalarType.UINT32), - buildVer: ProtoField(9, ScalarType.BYTES, true), + version: ProtoField(1, ScalarType.UINT32), + uin: ProtoField(2, ScalarType.STRING, true), + command: ProtoField(3, ScalarType.STRING, true), + seq: ProtoField(4, ScalarType.UINT32, true), + retryTimes: ProtoField(5, ScalarType.UINT32, true), + appId: ProtoField(6, ScalarType.UINT32), + dataFlag: ProtoField(7, ScalarType.UINT32), + commandId: ProtoField(8, ScalarType.UINT32), + buildVer: ProtoField(9, ScalarType.BYTES, true), }; export const FileUploadExt = { - unknown1: ProtoField(1, ScalarType.INT32), - unknown2: ProtoField(2, ScalarType.INT32), - unknown3: ProtoField(3, ScalarType.INT32), - entry: ProtoField(100, () => FileUploadEntry), - unknown200: ProtoField(200, ScalarType.INT32), + unknown1: ProtoField(1, ScalarType.INT32), + unknown2: ProtoField(2, ScalarType.INT32), + unknown3: ProtoField(3, ScalarType.INT32), + entry: ProtoField(100, () => FileUploadEntry), + unknown200: ProtoField(200, ScalarType.INT32), }; export const FileUploadEntry = { - busiBuff: ProtoField(100, () => ExcitingBusiInfo), - fileEntry: ProtoField(200, () => ExcitingFileEntry), - clientInfo: ProtoField(300, () => ExcitingClientInfo), - fileNameInfo: ProtoField(400, () => ExcitingFileNameInfo), - host: ProtoField(500, () => ExcitingHostConfig), + busiBuff: ProtoField(100, () => ExcitingBusiInfo), + fileEntry: ProtoField(200, () => ExcitingFileEntry), + clientInfo: ProtoField(300, () => ExcitingClientInfo), + fileNameInfo: ProtoField(400, () => ExcitingFileNameInfo), + host: ProtoField(500, () => ExcitingHostConfig), }; export const ExcitingBusiInfo = { - busId: ProtoField(1, ScalarType.INT32), - senderUin: ProtoField(100, ScalarType.UINT64), - receiverUin: ProtoField(200, ScalarType.UINT64), - groupCode: ProtoField(400, ScalarType.UINT64), + busId: ProtoField(1, ScalarType.INT32), + senderUin: ProtoField(100, ScalarType.UINT64), + receiverUin: ProtoField(200, ScalarType.UINT64), + groupCode: ProtoField(400, ScalarType.UINT64), }; export const ExcitingFileEntry = { - fileSize: ProtoField(100, ScalarType.UINT64), - md5: ProtoField(200, ScalarType.BYTES), - checkKey: ProtoField(300, ScalarType.BYTES), - md5S2: ProtoField(400, ScalarType.BYTES), - fileId: ProtoField(600, ScalarType.STRING), - uploadKey: ProtoField(700, ScalarType.BYTES), + fileSize: ProtoField(100, ScalarType.UINT64), + md5: ProtoField(200, ScalarType.BYTES), + checkKey: ProtoField(300, ScalarType.BYTES), + md5S2: ProtoField(400, ScalarType.BYTES), + fileId: ProtoField(600, ScalarType.STRING), + uploadKey: ProtoField(700, ScalarType.BYTES), }; export const ExcitingClientInfo = { - clientType: ProtoField(100, ScalarType.INT32), - appId: ProtoField(200, ScalarType.STRING), - terminalType: ProtoField(300, ScalarType.INT32), - clientVer: ProtoField(400, ScalarType.STRING), - unknown: ProtoField(600, ScalarType.INT32), + clientType: ProtoField(100, ScalarType.INT32), + appId: ProtoField(200, ScalarType.STRING), + terminalType: ProtoField(300, ScalarType.INT32), + clientVer: ProtoField(400, ScalarType.STRING), + unknown: ProtoField(600, ScalarType.INT32), }; export const ExcitingFileNameInfo = { - fileName: ProtoField(100, ScalarType.STRING), + fileName: ProtoField(100, ScalarType.STRING), }; export const ExcitingHostConfig = { - hosts: ProtoField(200, () => ExcitingHostInfo, false, true), + hosts: ProtoField(200, () => ExcitingHostInfo, false, true), }; export const ExcitingHostInfo = { - url: ProtoField(1, () => ExcitingUrlInfo), - port: ProtoField(2, ScalarType.UINT32), + url: ProtoField(1, () => ExcitingUrlInfo), + port: ProtoField(2, ScalarType.UINT32), }; export const ExcitingUrlInfo = { - unknown: ProtoField(1, ScalarType.INT32), - host: ProtoField(2, ScalarType.STRING), + unknown: ProtoField(1, ScalarType.INT32), + host: ProtoField(2, ScalarType.STRING), }; export const LoginSigHead = { - uint32LoginSigType: ProtoField(1, ScalarType.UINT32), - bytesLoginSig: ProtoField(2, ScalarType.BYTES), - appId: ProtoField(3, ScalarType.UINT32), + uint32LoginSigType: ProtoField(1, ScalarType.UINT32), + bytesLoginSig: ProtoField(2, ScalarType.BYTES), + appId: ProtoField(3, ScalarType.UINT32), }; export const NTV2RichMediaHighwayExt = { - fileUuid: ProtoField(1, ScalarType.STRING), - uKey: ProtoField(2, ScalarType.STRING), - network: ProtoField(5, () => NTHighwayNetwork), - msgInfoBody: ProtoField(6, () => MsgInfoBody, false, true), - blockSize: ProtoField(10, ScalarType.UINT32), - hash: ProtoField(11, () => NTHighwayHash), + fileUuid: ProtoField(1, ScalarType.STRING), + uKey: ProtoField(2, ScalarType.STRING), + network: ProtoField(5, () => NTHighwayNetwork), + msgInfoBody: ProtoField(6, () => MsgInfoBody, false, true), + blockSize: ProtoField(10, ScalarType.UINT32), + hash: ProtoField(11, () => NTHighwayHash), }; export const NTHighwayHash = { - fileSha1: ProtoField(1, ScalarType.BYTES, false, true), + fileSha1: ProtoField(1, ScalarType.BYTES, false, true), }; export const NTHighwayNetwork = { - ipv4s: ProtoField(1, () => NTHighwayIPv4, false, true), + ipv4s: ProtoField(1, () => NTHighwayIPv4, false, true), }; export const NTHighwayIPv4 = { - domain: ProtoField(1, () => NTHighwayDomain), - port: ProtoField(2, ScalarType.UINT32), + domain: ProtoField(1, () => NTHighwayDomain), + port: ProtoField(2, ScalarType.UINT32), }; export const NTHighwayDomain = { - isEnable: ProtoField(1, ScalarType.BOOL), - ip: ProtoField(2, ScalarType.STRING), + isEnable: ProtoField(1, ScalarType.BOOL), + ip: ProtoField(2, ScalarType.STRING), }; export const ReqDataHighwayHead = { - msgBaseHead: ProtoField(1, () => DataHighwayHead, true), - msgSegHead: ProtoField(2, () => SegHead, true), - bytesReqExtendInfo: ProtoField(3, ScalarType.BYTES, true), - timestamp: ProtoField(4, ScalarType.UINT64), - msgLoginSigHead: ProtoField(5, () => LoginSigHead, true), + msgBaseHead: ProtoField(1, () => DataHighwayHead, true), + msgSegHead: ProtoField(2, () => SegHead, true), + bytesReqExtendInfo: ProtoField(3, ScalarType.BYTES, true), + timestamp: ProtoField(4, ScalarType.UINT64), + msgLoginSigHead: ProtoField(5, () => LoginSigHead, true), }; export const RespDataHighwayHead = { - msgBaseHead: ProtoField(1, () => DataHighwayHead, true), - msgSegHead: ProtoField(2, () => SegHead, true), - errorCode: ProtoField(3, ScalarType.UINT32), - allowRetry: ProtoField(4, ScalarType.UINT32), - cacheCost: ProtoField(5, ScalarType.UINT32), - htCost: ProtoField(6, ScalarType.UINT32), - bytesRspExtendInfo: ProtoField(7, ScalarType.BYTES, true), - timestamp: ProtoField(8, ScalarType.UINT64), - range: ProtoField(9, ScalarType.UINT64), - isReset: ProtoField(10, ScalarType.UINT32), + msgBaseHead: ProtoField(1, () => DataHighwayHead, true), + msgSegHead: ProtoField(2, () => SegHead, true), + errorCode: ProtoField(3, ScalarType.UINT32), + allowRetry: ProtoField(4, ScalarType.UINT32), + cacheCost: ProtoField(5, ScalarType.UINT32), + htCost: ProtoField(6, ScalarType.UINT32), + bytesRspExtendInfo: ProtoField(7, ScalarType.BYTES, true), + timestamp: ProtoField(8, ScalarType.UINT64), + range: ProtoField(9, ScalarType.UINT64), + isReset: ProtoField(10, ScalarType.UINT32), }; export const SegHead = { - serviceId: ProtoField(1, ScalarType.UINT32, true), - filesize: ProtoField(2, ScalarType.UINT64), - dataOffset: ProtoField(3, ScalarType.UINT64, true), - dataLength: ProtoField(4, ScalarType.UINT32), - retCode: ProtoField(5, ScalarType.UINT32, true), - serviceTicket: ProtoField(6, ScalarType.BYTES), - flag: ProtoField(7, ScalarType.UINT32, true), - md5: ProtoField(8, ScalarType.BYTES), - fileMd5: ProtoField(9, ScalarType.BYTES), - cacheAddr: ProtoField(10, ScalarType.UINT32, true), - queryTimes: ProtoField(11, ScalarType.UINT32), - updateCacheIp: ProtoField(12, ScalarType.UINT32), - cachePort: ProtoField(13, ScalarType.UINT32, true), + serviceId: ProtoField(1, ScalarType.UINT32, true), + filesize: ProtoField(2, ScalarType.UINT64), + dataOffset: ProtoField(3, ScalarType.UINT64, true), + dataLength: ProtoField(4, ScalarType.UINT32), + retCode: ProtoField(5, ScalarType.UINT32, true), + serviceTicket: ProtoField(6, ScalarType.BYTES), + flag: ProtoField(7, ScalarType.UINT32, true), + md5: ProtoField(8, ScalarType.BYTES), + fileMd5: ProtoField(9, ScalarType.BYTES), + cacheAddr: ProtoField(10, ScalarType.UINT32, true), + queryTimes: ProtoField(11, ScalarType.UINT32), + updateCacheIp: ProtoField(12, ScalarType.UINT32), + cachePort: ProtoField(13, ScalarType.UINT32, true), }; export const GroupAvatarExtra = { - type: ProtoField(1, ScalarType.UINT32), - groupUin: ProtoField(2, ScalarType.UINT32), - field3: ProtoField(3, () => GroupAvatarExtraField3), - field5: ProtoField(5, ScalarType.UINT32), - field6: ProtoField(6, ScalarType.UINT32), + type: ProtoField(1, ScalarType.UINT32), + groupUin: ProtoField(2, ScalarType.UINT32), + field3: ProtoField(3, () => GroupAvatarExtraField3), + field5: ProtoField(5, ScalarType.UINT32), + field6: ProtoField(6, ScalarType.UINT32), }; export const GroupAvatarExtraField3 = { - field1: ProtoField(1, ScalarType.UINT32), + field1: ProtoField(1, ScalarType.UINT32), }; diff --git a/src/core/packet/transformer/proto/misc/fileid.ts b/src/core/packet/transformer/proto/misc/fileid.ts index 71426f3f..2026b550 100644 --- a/src/core/packet/transformer/proto/misc/fileid.ts +++ b/src/core/packet/transformer/proto/misc/fileid.ts @@ -1,6 +1,6 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const FileId = { - appid: ProtoField(4, ScalarType.UINT32, true), - ttl: ProtoField(10, ScalarType.UINT32, true), + appid: ProtoField(4, ScalarType.UINT32, true), + ttl: ProtoField(10, ScalarType.UINT32, true), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0XE37_800.ts b/src/core/packet/transformer/proto/oidb/Oidb.0XE37_800.ts index 58f5904a..6ad9ec36 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0XE37_800.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0XE37_800.ts @@ -2,60 +2,60 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from '@/core/packet/transformer/proto'; export const OidbSvcTrpcTcp0XE37_800 = { - subCommand: ProtoField(1, ScalarType.UINT32), - field2: ProtoField(2, ScalarType.INT32), - body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800Body, true), - field101: ProtoField(101, ScalarType.INT32), - field102: ProtoField(102, ScalarType.INT32), - field200: ProtoField(200, ScalarType.INT32) + subCommand: ProtoField(1, ScalarType.UINT32), + field2: ProtoField(2, ScalarType.INT32), + body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800Body, true), + field101: ProtoField(101, ScalarType.INT32), + field102: ProtoField(102, ScalarType.INT32), + field200: ProtoField(200, ScalarType.INT32), }; export const OidbSvcTrpcTcp0XE37_800Body = { - senderUid: ProtoField(10, ScalarType.STRING, true), - receiverUid: ProtoField(20, ScalarType.STRING, true), - fileUuid: ProtoField(30, ScalarType.STRING, true), - fileHash: ProtoField(40, ScalarType.STRING, true) + senderUid: ProtoField(10, ScalarType.STRING, true), + receiverUid: ProtoField(20, ScalarType.STRING, true), + fileUuid: ProtoField(30, ScalarType.STRING, true), + fileHash: ProtoField(40, ScalarType.STRING, true), }; export const OidbSvcTrpcTcp0XE37Response = { - command: ProtoField(1, ScalarType.UINT32), - seq: ProtoField(2, ScalarType.INT32), - upload: ProtoField(19, () => ApplyUploadRespV3, true), - businessId: ProtoField(101, ScalarType.INT32), - clientType: ProtoField(102, ScalarType.INT32), - flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32) + command: ProtoField(1, ScalarType.UINT32), + seq: ProtoField(2, ScalarType.INT32), + upload: ProtoField(19, () => ApplyUploadRespV3, true), + businessId: ProtoField(101, ScalarType.INT32), + clientType: ProtoField(102, ScalarType.INT32), + flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32), }; export const ApplyUploadRespV3 = { - retCode: ProtoField(10, ScalarType.INT32), - retMsg: ProtoField(20, ScalarType.STRING, true), - totalSpace: ProtoField(30, ScalarType.INT64), - usedSpace: ProtoField(40, ScalarType.INT64), - uploadedSize: ProtoField(50, ScalarType.INT64), - uploadIp: ProtoField(60, ScalarType.STRING, true), - uploadDomain: ProtoField(70, ScalarType.STRING, true), - uploadPort: ProtoField(80, ScalarType.UINT32), - uuid: ProtoField(90, ScalarType.STRING, true), - uploadKey: ProtoField(100, ScalarType.BYTES, true), - boolFileExist: ProtoField(110, ScalarType.BOOL), - packSize: ProtoField(120, ScalarType.INT32), - uploadIpList: ProtoField(130, ScalarType.STRING, false, true), // repeated - uploadHttpsPort: ProtoField(140, ScalarType.INT32), - uploadHttpsDomain: ProtoField(150, ScalarType.STRING, true), - uploadDns: ProtoField(160, ScalarType.STRING, true), - uploadLanip: ProtoField(170, ScalarType.STRING, true), - fileAddon: ProtoField(200, ScalarType.STRING, true), - mediaPlatformUploadKey: ProtoField(220, ScalarType.BYTES, true) + retCode: ProtoField(10, ScalarType.INT32), + retMsg: ProtoField(20, ScalarType.STRING, true), + totalSpace: ProtoField(30, ScalarType.INT64), + usedSpace: ProtoField(40, ScalarType.INT64), + uploadedSize: ProtoField(50, ScalarType.INT64), + uploadIp: ProtoField(60, ScalarType.STRING, true), + uploadDomain: ProtoField(70, ScalarType.STRING, true), + uploadPort: ProtoField(80, ScalarType.UINT32), + uuid: ProtoField(90, ScalarType.STRING, true), + uploadKey: ProtoField(100, ScalarType.BYTES, true), + boolFileExist: ProtoField(110, ScalarType.BOOL), + packSize: ProtoField(120, ScalarType.INT32), + uploadIpList: ProtoField(130, ScalarType.STRING, false, true), // repeated + uploadHttpsPort: ProtoField(140, ScalarType.INT32), + uploadHttpsDomain: ProtoField(150, ScalarType.STRING, true), + uploadDns: ProtoField(160, ScalarType.STRING, true), + uploadLanip: ProtoField(170, ScalarType.STRING, true), + fileAddon: ProtoField(200, ScalarType.STRING, true), + mediaPlatformUploadKey: ProtoField(220, ScalarType.BYTES, true), }; export const OidbSvcTrpcTcp0XE37_800Response = { - command: ProtoField(1, ScalarType.UINT32, true), - subCommand: ProtoField(2, ScalarType.UINT32, true), - body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800ResponseBody, true), - field50: ProtoField(50, ScalarType.UINT32, true), + command: ProtoField(1, ScalarType.UINT32, true), + subCommand: ProtoField(2, ScalarType.UINT32, true), + body: ProtoField(10, () => OidbSvcTrpcTcp0XE37_800ResponseBody, true), + field50: ProtoField(50, ScalarType.UINT32, true), }; export const OidbSvcTrpcTcp0XE37_800ResponseBody = { - field10: ProtoField(10, ScalarType.UINT32, true), - field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), + field10: ProtoField(10, ScalarType.UINT32, true), + field30: ProtoField(30, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0XFE1_2.ts b/src/core/packet/transformer/proto/oidb/Oidb.0XFE1_2.ts index c4ec293a..c4904e3c 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0XFE1_2.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0XFE1_2.ts @@ -1,22 +1,22 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0XFE1_2 = { - uin: ProtoField(1, ScalarType.UINT32), - key: ProtoField(3, () => OidbSvcTrpcTcp0XFE1_2Key, false, true), + uin: ProtoField(1, ScalarType.UINT32), + key: ProtoField(3, () => OidbSvcTrpcTcp0XFE1_2Key, false, true), }; export const OidbSvcTrpcTcp0XFE1_2Key = { - key: ProtoField(1, ScalarType.UINT32) + key: ProtoField(1, ScalarType.UINT32), }; export const OidbSvcTrpcTcp0XFE1_2RSP_Status = { - key: ProtoField(1, ScalarType.UINT32), - value: ProtoField(2, ScalarType.UINT64) + key: ProtoField(1, ScalarType.UINT32), + value: ProtoField(2, ScalarType.UINT64), }; export const OidbSvcTrpcTcp0XFE1_2RSP_Data = { - status: ProtoField(2, () => OidbSvcTrpcTcp0XFE1_2RSP_Status) + status: ProtoField(2, () => OidbSvcTrpcTcp0XFE1_2RSP_Status), }; export const OidbSvcTrpcTcp0XFE1_2RSP = { - data: ProtoField(1, () => OidbSvcTrpcTcp0XFE1_2RSP_Data) + data: ProtoField(1, () => OidbSvcTrpcTcp0XFE1_2RSP_Data), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0x6D6.ts b/src/core/packet/transformer/proto/oidb/Oidb.0x6D6.ts index be61b443..40205a5d 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0x6D6.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0x6D6.ts @@ -1,99 +1,99 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0x6D6 = { - file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true), - download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6Download, true), - delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6Delete, true), - rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6Rename, true), - move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6Move, true), + file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true), + download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6Download, true), + delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6Delete, true), + rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6Rename, true), + move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6Move, true), }; export const OidbSvcTrpcTcp0x6D6Upload = { - groupUin: ProtoField(1, ScalarType.UINT32), - appId: ProtoField(2, ScalarType.UINT32), - busId: ProtoField(3, ScalarType.UINT32), - entrance: ProtoField(4, ScalarType.UINT32), - targetDirectory: ProtoField(5, ScalarType.STRING), - fileName: ProtoField(6, ScalarType.STRING), - localDirectory: ProtoField(7, ScalarType.STRING), - fileSize: ProtoField(8, ScalarType.UINT64), - fileSha1: ProtoField(9, ScalarType.BYTES), - fileSha3: ProtoField(10, ScalarType.BYTES), - fileMd5: ProtoField(11, ScalarType.BYTES), - field15: ProtoField(15, ScalarType.BOOL), + groupUin: ProtoField(1, ScalarType.UINT32), + appId: ProtoField(2, ScalarType.UINT32), + busId: ProtoField(3, ScalarType.UINT32), + entrance: ProtoField(4, ScalarType.UINT32), + targetDirectory: ProtoField(5, ScalarType.STRING), + fileName: ProtoField(6, ScalarType.STRING), + localDirectory: ProtoField(7, ScalarType.STRING), + fileSize: ProtoField(8, ScalarType.UINT64), + fileSha1: ProtoField(9, ScalarType.BYTES), + fileSha3: ProtoField(10, ScalarType.BYTES), + fileMd5: ProtoField(11, ScalarType.BYTES), + field15: ProtoField(15, ScalarType.BOOL), }; export const OidbSvcTrpcTcp0x6D6Download = { - groupUin: ProtoField(1, ScalarType.UINT32), - appId: ProtoField(2, ScalarType.UINT32), - busId: ProtoField(3, ScalarType.UINT32), - fileId: ProtoField(4, ScalarType.STRING), + groupUin: ProtoField(1, ScalarType.UINT32), + appId: ProtoField(2, ScalarType.UINT32), + busId: ProtoField(3, ScalarType.UINT32), + fileId: ProtoField(4, ScalarType.STRING), }; export const OidbSvcTrpcTcp0x6D6Delete = { - groupUin: ProtoField(1, ScalarType.UINT32), - busId: ProtoField(3, ScalarType.UINT32), - fileId: ProtoField(5, ScalarType.STRING), + groupUin: ProtoField(1, ScalarType.UINT32), + busId: ProtoField(3, ScalarType.UINT32), + fileId: ProtoField(5, ScalarType.STRING), }; export const OidbSvcTrpcTcp0x6D6Rename = { - groupUin: ProtoField(1, ScalarType.UINT32), - busId: ProtoField(3, ScalarType.UINT32), - fileId: ProtoField(4, ScalarType.STRING), - parentFolder: ProtoField(5, ScalarType.STRING), - newFileName: ProtoField(6, ScalarType.STRING), + groupUin: ProtoField(1, ScalarType.UINT32), + busId: ProtoField(3, ScalarType.UINT32), + fileId: ProtoField(4, ScalarType.STRING), + parentFolder: ProtoField(5, ScalarType.STRING), + newFileName: ProtoField(6, ScalarType.STRING), }; export const OidbSvcTrpcTcp0x6D6Move = { - groupUin: ProtoField(1, ScalarType.UINT32), - appId: ProtoField(2, ScalarType.UINT32), - busId: ProtoField(3, ScalarType.UINT32), - fileId: ProtoField(4, ScalarType.STRING), - parentDirectory: ProtoField(5, ScalarType.STRING), - targetDirectory: ProtoField(6, ScalarType.STRING), + groupUin: ProtoField(1, ScalarType.UINT32), + appId: ProtoField(2, ScalarType.UINT32), + busId: ProtoField(3, ScalarType.UINT32), + fileId: ProtoField(4, ScalarType.STRING), + parentDirectory: ProtoField(5, ScalarType.STRING), + targetDirectory: ProtoField(6, ScalarType.STRING), }; export const OidbSvcTrpcTcp0x6D6Response = { - upload: ProtoField(1, () => OidbSvcTrpcTcp0x6D6_0Response), - download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6_2Response), - delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6_3_4_5Response), - rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6_3_4_5Response), - move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6_3_4_5Response), + upload: ProtoField(1, () => OidbSvcTrpcTcp0x6D6_0Response), + download: ProtoField(3, () => OidbSvcTrpcTcp0x6D6_2Response), + delete: ProtoField(4, () => OidbSvcTrpcTcp0x6D6_3_4_5Response), + rename: ProtoField(5, () => OidbSvcTrpcTcp0x6D6_3_4_5Response), + move: ProtoField(6, () => OidbSvcTrpcTcp0x6D6_3_4_5Response), }; export const OidbSvcTrpcTcp0x6D6_0Response = { - retCode: ProtoField(1, ScalarType.INT32), - retMsg: ProtoField(2, ScalarType.STRING), - clientWording: ProtoField(3, ScalarType.STRING), - uploadIp: ProtoField(4, ScalarType.STRING), - serverDns: ProtoField(5, ScalarType.STRING), - busId: ProtoField(6, ScalarType.INT32), - fileId: ProtoField(7, ScalarType.STRING), - checkKey: ProtoField(8, ScalarType.BYTES), - fileKey: ProtoField(9, ScalarType.BYTES), - boolFileExist: ProtoField(10, ScalarType.BOOL), - uploadIpLanV4: ProtoField(12, ScalarType.STRING, false, true), - uploadIpLanV6: ProtoField(13, ScalarType.STRING, false, true), - uploadPort: ProtoField(14, ScalarType.UINT32), + retCode: ProtoField(1, ScalarType.INT32), + retMsg: ProtoField(2, ScalarType.STRING), + clientWording: ProtoField(3, ScalarType.STRING), + uploadIp: ProtoField(4, ScalarType.STRING), + serverDns: ProtoField(5, ScalarType.STRING), + busId: ProtoField(6, ScalarType.INT32), + fileId: ProtoField(7, ScalarType.STRING), + checkKey: ProtoField(8, ScalarType.BYTES), + fileKey: ProtoField(9, ScalarType.BYTES), + boolFileExist: ProtoField(10, ScalarType.BOOL), + uploadIpLanV4: ProtoField(12, ScalarType.STRING, false, true), + uploadIpLanV6: ProtoField(13, ScalarType.STRING, false, true), + uploadPort: ProtoField(14, ScalarType.UINT32), }; export const OidbSvcTrpcTcp0x6D6_2Response = { - retCode: ProtoField(1, ScalarType.INT32), - retMsg: ProtoField(2, ScalarType.STRING), - clientWording: ProtoField(3, ScalarType.STRING), - downloadIp: ProtoField(4, ScalarType.STRING), - downloadDns: ProtoField(5, ScalarType.STRING), - downloadUrl: ProtoField(6, ScalarType.BYTES), - fileSha1: ProtoField(7, ScalarType.BYTES), - fileSha3: ProtoField(8, ScalarType.BYTES), - fileMd5: ProtoField(9, ScalarType.BYTES), - cookieVal: ProtoField(10, ScalarType.BYTES), - saveFileName: ProtoField(11, ScalarType.STRING), - previewPort: ProtoField(12, ScalarType.UINT32), + retCode: ProtoField(1, ScalarType.INT32), + retMsg: ProtoField(2, ScalarType.STRING), + clientWording: ProtoField(3, ScalarType.STRING), + downloadIp: ProtoField(4, ScalarType.STRING), + downloadDns: ProtoField(5, ScalarType.STRING), + downloadUrl: ProtoField(6, ScalarType.BYTES), + fileSha1: ProtoField(7, ScalarType.BYTES), + fileSha3: ProtoField(8, ScalarType.BYTES), + fileMd5: ProtoField(9, ScalarType.BYTES), + cookieVal: ProtoField(10, ScalarType.BYTES), + saveFileName: ProtoField(11, ScalarType.STRING), + previewPort: ProtoField(12, ScalarType.UINT32), }; export const OidbSvcTrpcTcp0x6D6_3_4_5Response = { - retCode: ProtoField(1, ScalarType.INT32), - retMsg: ProtoField(2, ScalarType.STRING), - clientWording: ProtoField(3, ScalarType.STRING), + retCode: ProtoField(1, ScalarType.INT32), + retMsg: ProtoField(2, ScalarType.STRING), + clientWording: ProtoField(3, ScalarType.STRING), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts b/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts index 4f96409b..4edd3bae 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0x8FC_2.ts @@ -1,15 +1,14 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; - -//设置群头衔 OidbSvcTrpcTcp.0x8fc_2 +// 设置群头衔 OidbSvcTrpcTcp.0x8fc_2 export const OidbSvcTrpcTcp0X8FC_2_Body = { - targetUid: ProtoField(1, ScalarType.STRING), - specialTitle: ProtoField(5, ScalarType.STRING, true), - expiredTime: ProtoField(6, ScalarType.INT32), - uinName: ProtoField(7, ScalarType.STRING, true), - targetName: ProtoField(8, ScalarType.STRING), + targetUid: ProtoField(1, ScalarType.STRING), + specialTitle: ProtoField(5, ScalarType.STRING, true), + expiredTime: ProtoField(6, ScalarType.INT32), + uinName: ProtoField(7, ScalarType.STRING, true), + targetName: ProtoField(8, ScalarType.STRING), }; export const OidbSvcTrpcTcp0X8FC_2 = { - groupUin: ProtoField(1, ScalarType.UINT32), - body: ProtoField(3, () => OidbSvcTrpcTcp0X8FC_2_Body), + groupUin: ProtoField(1, ScalarType.UINT32), + body: ProtoField(3, () => OidbSvcTrpcTcp0X8FC_2_Body), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0x9067_202.ts b/src/core/packet/transformer/proto/oidb/Oidb.0x9067_202.ts index 8ebf3367..5f6dc331 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0x9067_202.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0x9067_202.ts @@ -1,26 +1,26 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; import { MultiMediaReqHead } from './common/Ntv2.RichMediaReq'; -//Req +// Req export const OidbSvcTrpcTcp0X9067_202 = { - ReqHead: ProtoField(1, () => MultiMediaReqHead), - DownloadRKeyReq: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202Key), + ReqHead: ProtoField(1, () => MultiMediaReqHead), + DownloadRKeyReq: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202Key), }; export const OidbSvcTrpcTcp0X9067_202Key = { - key: ProtoField(1, ScalarType.INT32, false, true), + key: ProtoField(1, ScalarType.INT32, false, true), }; -//Rsp +// Rsp export const OidbSvcTrpcTcp0X9067_202_RkeyList = { - rkey: ProtoField(1, ScalarType.STRING), - ttl: ProtoField(2, ScalarType.UINT64), - time: ProtoField(4, ScalarType.UINT32), - type: ProtoField(5, ScalarType.UINT32), + rkey: ProtoField(1, ScalarType.STRING), + ttl: ProtoField(2, ScalarType.UINT64), + time: ProtoField(4, ScalarType.UINT32), + type: ProtoField(5, ScalarType.UINT32), }; export const OidbSvcTrpcTcp0X9067_202_Data = { - rkeyList: ProtoField(1, () => OidbSvcTrpcTcp0X9067_202_RkeyList, false, true), + rkeyList: ProtoField(1, () => OidbSvcTrpcTcp0X9067_202_RkeyList, false, true), }; export const OidbSvcTrpcTcp0X9067_202_Rsp_Body = { - data: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202_Data), + data: ProtoField(4, () => OidbSvcTrpcTcp0X9067_202_Data), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0x929.ts b/src/core/packet/transformer/proto/oidb/Oidb.0x929.ts index 0f5837b4..87579eea 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0x929.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0x929.ts @@ -1,42 +1,41 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; import { MsgInfo } from '@/core/packet/transformer/proto'; - export const OidbSvcTrpcTcp0X929D_0 = { - groupUin: ProtoField(1, ScalarType.UINT32), - chatType: ProtoField(2, ScalarType.UINT32), + groupUin: ProtoField(1, ScalarType.UINT32), + chatType: ProtoField(2, ScalarType.UINT32), }; export const OidbSvcTrpcTcp0X929D_0Resp = { - content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true), + content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true), }; export const OidbSvcTrpcTcp0X929D_0RespContent = { - category: ProtoField(1, ScalarType.STRING), - voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true), + category: ProtoField(1, ScalarType.STRING), + voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true), }; export const OidbSvcTrpcTcp0X929D_0RespContentVoice = { - voiceId: ProtoField(1, ScalarType.STRING), - voiceDisplayName: ProtoField(2, ScalarType.STRING), - voiceExampleUrl: ProtoField(3, ScalarType.STRING), + voiceId: ProtoField(1, ScalarType.STRING), + voiceDisplayName: ProtoField(2, ScalarType.STRING), + voiceExampleUrl: ProtoField(3, ScalarType.STRING), }; export const OidbSvcTrpcTcp0X929B_0 = { - groupUin: ProtoField(1, ScalarType.UINT32), - voiceId: ProtoField(2, ScalarType.STRING), - text: ProtoField(3, ScalarType.STRING), - chatType: ProtoField(4, ScalarType.UINT32), - session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session), + groupUin: ProtoField(1, ScalarType.UINT32), + voiceId: ProtoField(2, ScalarType.STRING), + text: ProtoField(3, ScalarType.STRING), + chatType: ProtoField(4, ScalarType.UINT32), + session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session), }; export const OidbSvcTrpcTcp0X929B_0_Session = { - sessionId: ProtoField(1, ScalarType.UINT32), + sessionId: ProtoField(1, ScalarType.UINT32), }; export const OidbSvcTrpcTcp0X929B_0Resp = { - statusCode: ProtoField(1, ScalarType.UINT32), - field2: ProtoField(2, ScalarType.UINT32, true), - field3: ProtoField(3, ScalarType.UINT32), - msgInfo: ProtoField(4, () => MsgInfo, true), + statusCode: ProtoField(1, ScalarType.UINT32), + field2: ProtoField(2, ScalarType.UINT32, true), + field3: ProtoField(3, ScalarType.UINT32), + msgInfo: ProtoField(4, () => MsgInfo, true), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0xE07.ts b/src/core/packet/transformer/proto/oidb/Oidb.0xE07.ts index 4ec47095..274e479c 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0xE07.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0xE07.ts @@ -1,59 +1,59 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0xE07_0 = { - version: ProtoField(1, ScalarType.UINT32), - client: ProtoField(2, ScalarType.UINT32), - entrance: ProtoField(3, ScalarType.UINT32), - ocrReqBody: ProtoField(10, () => OcrReqBody, true), + version: ProtoField(1, ScalarType.UINT32), + client: ProtoField(2, ScalarType.UINT32), + entrance: ProtoField(3, ScalarType.UINT32), + ocrReqBody: ProtoField(10, () => OcrReqBody, true), }; export const OcrReqBody = { - imageUrl: ProtoField(1, ScalarType.STRING), - languageType: ProtoField(2, ScalarType.UINT32), - scene: ProtoField(3, ScalarType.UINT32), - originMd5: ProtoField(10, ScalarType.STRING), - afterCompressMd5: ProtoField(11, ScalarType.STRING), - afterCompressFileSize: ProtoField(12, ScalarType.STRING), - afterCompressWeight: ProtoField(13, ScalarType.STRING), - afterCompressHeight: ProtoField(14, ScalarType.STRING), - isCut: ProtoField(15, ScalarType.BOOL), + imageUrl: ProtoField(1, ScalarType.STRING), + languageType: ProtoField(2, ScalarType.UINT32), + scene: ProtoField(3, ScalarType.UINT32), + originMd5: ProtoField(10, ScalarType.STRING), + afterCompressMd5: ProtoField(11, ScalarType.STRING), + afterCompressFileSize: ProtoField(12, ScalarType.STRING), + afterCompressWeight: ProtoField(13, ScalarType.STRING), + afterCompressHeight: ProtoField(14, ScalarType.STRING), + isCut: ProtoField(15, ScalarType.BOOL), }; export const OidbSvcTrpcTcp0xE07_0_Response = { - retCode: ProtoField(1, ScalarType.INT32), - errMsg: ProtoField(2, ScalarType.STRING), - wording: ProtoField(3, ScalarType.STRING), - ocrRspBody: ProtoField(10, () => OcrRspBody), + retCode: ProtoField(1, ScalarType.INT32), + errMsg: ProtoField(2, ScalarType.STRING), + wording: ProtoField(3, ScalarType.STRING), + ocrRspBody: ProtoField(10, () => OcrRspBody), }; export const OcrRspBody = { - textDetections: ProtoField(1, () => TextDetection, false, true), - language: ProtoField(2, ScalarType.STRING), - requestId: ProtoField(3, ScalarType.STRING), - ocrLanguageList: ProtoField(101, ScalarType.STRING, false, true), - dstTranslateLanguageList: ProtoField(102, ScalarType.STRING, false, true), - languageList: ProtoField(103, () => Language, false, true), - afterCompressWeight: ProtoField(111, ScalarType.UINT32), - afterCompressHeight: ProtoField(112, ScalarType.UINT32), + textDetections: ProtoField(1, () => TextDetection, false, true), + language: ProtoField(2, ScalarType.STRING), + requestId: ProtoField(3, ScalarType.STRING), + ocrLanguageList: ProtoField(101, ScalarType.STRING, false, true), + dstTranslateLanguageList: ProtoField(102, ScalarType.STRING, false, true), + languageList: ProtoField(103, () => Language, false, true), + afterCompressWeight: ProtoField(111, ScalarType.UINT32), + afterCompressHeight: ProtoField(112, ScalarType.UINT32), }; export const TextDetection = { - detectedText: ProtoField(1, ScalarType.STRING), - confidence: ProtoField(2, ScalarType.UINT32), - polygon: ProtoField(3, () => Polygon), - advancedInfo: ProtoField(4, ScalarType.STRING), + detectedText: ProtoField(1, ScalarType.STRING), + confidence: ProtoField(2, ScalarType.UINT32), + polygon: ProtoField(3, () => Polygon), + advancedInfo: ProtoField(4, ScalarType.STRING), }; export const Polygon = { - coordinates: ProtoField(1, () => Coordinate, false, true), + coordinates: ProtoField(1, () => Coordinate, false, true), }; export const Coordinate = { - x: ProtoField(1, ScalarType.INT32), - y: ProtoField(2, ScalarType.INT32), + x: ProtoField(1, ScalarType.INT32), + y: ProtoField(2, ScalarType.INT32), }; export const Language = { - languageCode: ProtoField(1, ScalarType.STRING), - languageDesc: ProtoField(2, ScalarType.STRING), + languageCode: ProtoField(1, ScalarType.STRING), + languageDesc: ProtoField(2, ScalarType.STRING), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1200.ts b/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1200.ts index 70dbc934..b739aefe 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1200.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1200.ts @@ -1,60 +1,60 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0XE37_1200 = { - subCommand: ProtoField(1, ScalarType.UINT32, true), - field2: ProtoField(2, ScalarType.INT32, true), - body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200Body, true), - field101: ProtoField(101, ScalarType.INT32, true), - field102: ProtoField(102, ScalarType.INT32, true), - field200: ProtoField(200, ScalarType.INT32, true), - field99999: ProtoField(99999, ScalarType.BYTES, true), + subCommand: ProtoField(1, ScalarType.UINT32, true), + field2: ProtoField(2, ScalarType.INT32, true), + body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200Body, true), + field101: ProtoField(101, ScalarType.INT32, true), + field102: ProtoField(102, ScalarType.INT32, true), + field200: ProtoField(200, ScalarType.INT32, true), + field99999: ProtoField(99999, ScalarType.BYTES, true), }; export const OidbSvcTrpcTcp0XE37_1200Body = { - receiverUid: ProtoField(10, ScalarType.STRING, true), - fileUuid: ProtoField(20, ScalarType.STRING, true), - type: ProtoField(30, ScalarType.INT32, true), - fileHash: ProtoField(60, ScalarType.STRING, true), - t2: ProtoField(601, ScalarType.INT32, true), + receiverUid: ProtoField(10, ScalarType.STRING, true), + fileUuid: ProtoField(20, ScalarType.STRING, true), + type: ProtoField(30, ScalarType.INT32, true), + fileHash: ProtoField(60, ScalarType.STRING, true), + t2: ProtoField(601, ScalarType.INT32, true), }; export const OidbSvcTrpcTcp0XE37_1200Response = { - command: ProtoField(1, ScalarType.UINT32, true), - subCommand: ProtoField(2, ScalarType.UINT32, true), - body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200ResponseBody, true), - field50: ProtoField(50, ScalarType.UINT32, true), + command: ProtoField(1, ScalarType.UINT32, true), + subCommand: ProtoField(2, ScalarType.UINT32, true), + body: ProtoField(14, () => OidbSvcTrpcTcp0XE37_1200ResponseBody, true), + field50: ProtoField(50, ScalarType.UINT32, true), }; export const OidbSvcTrpcTcp0XE37_1200ResponseBody = { - field10: ProtoField(10, ScalarType.UINT32, true), - state: ProtoField(20, ScalarType.STRING, true), - result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true), - metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), + field10: ProtoField(10, ScalarType.UINT32, true), + state: ProtoField(20, ScalarType.STRING, true), + result: ProtoField(30, () => OidbSvcTrpcTcp0XE37_1200Result, true), + metadata: ProtoField(40, () => OidbSvcTrpcTcp0XE37_800_1200Metadata, true), }; export const OidbSvcTrpcTcp0XE37_1200Result = { - server: ProtoField(20, ScalarType.STRING, true), - port: ProtoField(40, ScalarType.UINT32, true), - url: ProtoField(50, ScalarType.STRING, true), - additionalServer: ProtoField(60, ScalarType.STRING, false, true), - ssoPort: ProtoField(80, ScalarType.UINT32, true), - ssoUrl: ProtoField(90, ScalarType.STRING, true), - extra: ProtoField(120, ScalarType.BYTES, true), + server: ProtoField(20, ScalarType.STRING, true), + port: ProtoField(40, ScalarType.UINT32, true), + url: ProtoField(50, ScalarType.STRING, true), + additionalServer: ProtoField(60, ScalarType.STRING, false, true), + ssoPort: ProtoField(80, ScalarType.UINT32, true), + ssoUrl: ProtoField(90, ScalarType.STRING, true), + extra: ProtoField(120, ScalarType.BYTES, true), }; export const OidbSvcTrpcTcp0XE37_800_1200Metadata = { - uin: ProtoField(1, ScalarType.UINT32, true), - field2: ProtoField(2, ScalarType.UINT32, true), - field3: ProtoField(3, ScalarType.UINT32, true), - size: ProtoField(4, ScalarType.UINT32, true), - timestamp: ProtoField(5, ScalarType.UINT32, true), - fileUuid: ProtoField(6, ScalarType.STRING, true), - fileName: ProtoField(7, ScalarType.STRING, true), - field100: ProtoField(100, ScalarType.BYTES, true), - field101: ProtoField(101, ScalarType.BYTES, true), - field110: ProtoField(110, ScalarType.UINT32, true), - timestamp1: ProtoField(130, ScalarType.UINT32, true), - fileHash: ProtoField(140, ScalarType.STRING, true), - field141: ProtoField(141, ScalarType.BYTES, true), - field142: ProtoField(142, ScalarType.BYTES, true), + uin: ProtoField(1, ScalarType.UINT32, true), + field2: ProtoField(2, ScalarType.UINT32, true), + field3: ProtoField(3, ScalarType.UINT32, true), + size: ProtoField(4, ScalarType.UINT32, true), + timestamp: ProtoField(5, ScalarType.UINT32, true), + fileUuid: ProtoField(6, ScalarType.STRING, true), + fileName: ProtoField(7, ScalarType.STRING, true), + field100: ProtoField(100, ScalarType.BYTES, true), + field101: ProtoField(101, ScalarType.BYTES, true), + field110: ProtoField(110, ScalarType.UINT32, true), + timestamp1: ProtoField(130, ScalarType.UINT32, true), + fileHash: ProtoField(140, ScalarType.STRING, true), + field141: ProtoField(141, ScalarType.BYTES, true), + field142: ProtoField(142, ScalarType.BYTES, true), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1700.ts b/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1700.ts index 8fbe5a34..9e82557f 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1700.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0xE37_1700.ts @@ -1,22 +1,22 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0XE37_1700 = { - command: ProtoField(1, ScalarType.UINT32, true), - seq: ProtoField(2, ScalarType.INT32, true), - upload: ProtoField(19, () => ApplyUploadReqV3, true), - businessId: ProtoField(101, ScalarType.INT32, true), - clientType: ProtoField(102, ScalarType.INT32, true), - flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true), + command: ProtoField(1, ScalarType.UINT32, true), + seq: ProtoField(2, ScalarType.INT32, true), + upload: ProtoField(19, () => ApplyUploadReqV3, true), + businessId: ProtoField(101, ScalarType.INT32, true), + clientType: ProtoField(102, ScalarType.INT32, true), + flagSupportMediaPlatform: ProtoField(200, ScalarType.INT32, true), }; export const ApplyUploadReqV3 = { - senderUid: ProtoField(10, ScalarType.STRING, true), - receiverUid: ProtoField(20, ScalarType.STRING, true), - fileSize: ProtoField(30, ScalarType.UINT32, true), - fileName: ProtoField(40, ScalarType.STRING, true), - md510MCheckSum: ProtoField(50, ScalarType.BYTES, true), - sha1CheckSum: ProtoField(60, ScalarType.BYTES, true), - localPath: ProtoField(70, ScalarType.STRING, true), - md5CheckSum: ProtoField(110, ScalarType.BYTES, true), - sha3CheckSum: ProtoField(120, ScalarType.BYTES, true), + senderUid: ProtoField(10, ScalarType.STRING, true), + receiverUid: ProtoField(20, ScalarType.STRING, true), + fileSize: ProtoField(30, ScalarType.UINT32, true), + fileName: ProtoField(40, ScalarType.STRING, true), + md510MCheckSum: ProtoField(50, ScalarType.BYTES, true), + sha1CheckSum: ProtoField(60, ScalarType.BYTES, true), + localPath: ProtoField(70, ScalarType.STRING, true), + md5CheckSum: ProtoField(110, ScalarType.BYTES, true), + sha3CheckSum: ProtoField(120, ScalarType.BYTES, true), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0xEB7.ts b/src/core/packet/transformer/proto/oidb/Oidb.0xEB7.ts index c7c7ed80..ed75e358 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0xEB7.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0xEB7.ts @@ -1,11 +1,11 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0XEB7_Body = { - uin: ProtoField(1, ScalarType.STRING), - groupUin: ProtoField(2, ScalarType.STRING), - version: ProtoField(3, ScalarType.STRING), + uin: ProtoField(1, ScalarType.STRING), + groupUin: ProtoField(2, ScalarType.STRING), + version: ProtoField(3, ScalarType.STRING), }; export const OidbSvcTrpcTcp0XEB7 = { - body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body), + body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0xED3_1.ts b/src/core/packet/transformer/proto/oidb/Oidb.0xED3_1.ts index fa071cba..b51d4c85 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0xED3_1.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0xED3_1.ts @@ -2,8 +2,8 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; // Send Poke export const OidbSvcTrpcTcp0XED3_1 = { - uin: ProtoField(1, ScalarType.UINT32), - groupUin: ProtoField(2, ScalarType.UINT32), - friendUin: ProtoField(5, ScalarType.UINT32), - ext: ProtoField(6, ScalarType.UINT32, true) + uin: ProtoField(1, ScalarType.UINT32), + groupUin: ProtoField(2, ScalarType.UINT32), + friendUin: ProtoField(5, ScalarType.UINT32), + ext: ProtoField(6, ScalarType.UINT32, true), }; diff --git a/src/core/packet/transformer/proto/oidb/Oidb.0xf90_1.ts b/src/core/packet/transformer/proto/oidb/Oidb.0xf90_1.ts index 98af201e..4516a742 100644 --- a/src/core/packet/transformer/proto/oidb/Oidb.0xf90_1.ts +++ b/src/core/packet/transformer/proto/oidb/Oidb.0xf90_1.ts @@ -1,6 +1,6 @@ -import { ProtoField, ScalarType } from "@napneko/nap-proto-core"; +import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcp0XF90_1 = { - groupUin: ProtoField(1, ScalarType.UINT32), - msgSeq: ProtoField(2, ScalarType.UINT64), + groupUin: ProtoField(1, ScalarType.UINT32), + msgSeq: ProtoField(2, ScalarType.UINT64), }; diff --git a/src/core/packet/transformer/proto/oidb/OidbBase.ts b/src/core/packet/transformer/proto/oidb/OidbBase.ts index 5557d755..157f7503 100644 --- a/src/core/packet/transformer/proto/oidb/OidbBase.ts +++ b/src/core/packet/transformer/proto/oidb/OidbBase.ts @@ -1,13 +1,13 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const OidbSvcTrpcTcpBase = { - command: ProtoField(1, ScalarType.UINT32), - subCommand: ProtoField(2, ScalarType.UINT32), - errorCode: ProtoField(3, ScalarType.UINT32), - body: ProtoField(4, ScalarType.BYTES), - errorMsg: ProtoField(5, ScalarType.STRING, true), - isReserved: ProtoField(12, ScalarType.UINT32) + command: ProtoField(1, ScalarType.UINT32), + subCommand: ProtoField(2, ScalarType.UINT32), + errorCode: ProtoField(3, ScalarType.UINT32), + body: ProtoField(4, ScalarType.BYTES), + errorMsg: ProtoField(5, ScalarType.STRING, true), + isReserved: ProtoField(12, ScalarType.UINT32), }; export const OidbSvcTrpcTcpBaseRsp = { - body: ProtoField(4, ScalarType.BYTES) + body: ProtoField(4, ScalarType.BYTES), }; diff --git a/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaReq.ts b/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaReq.ts index 66f60203..a243fa9f 100644 --- a/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaReq.ts +++ b/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaReq.ts @@ -1,93 +1,93 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; export const NTV2RichMediaReq = { - ReqHead: ProtoField(1, () => MultiMediaReqHead), - Upload: ProtoField(2, () => UploadReq), - Download: ProtoField(3, () => DownloadReq), - DownloadRKey: ProtoField(4, () => DownloadRKeyReq), - Delete: ProtoField(5, () => DeleteReq), - UploadCompleted: ProtoField(6, () => UploadCompletedReq), - MsgInfoAuth: ProtoField(7, () => MsgInfoAuthReq), - UploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalReq), - DownloadSafe: ProtoField(9, () => DownloadSafeReq), - Extension: ProtoField(99, ScalarType.BYTES, true), + ReqHead: ProtoField(1, () => MultiMediaReqHead), + Upload: ProtoField(2, () => UploadReq), + Download: ProtoField(3, () => DownloadReq), + DownloadRKey: ProtoField(4, () => DownloadRKeyReq), + Delete: ProtoField(5, () => DeleteReq), + UploadCompleted: ProtoField(6, () => UploadCompletedReq), + MsgInfoAuth: ProtoField(7, () => MsgInfoAuthReq), + UploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalReq), + DownloadSafe: ProtoField(9, () => DownloadSafeReq), + Extension: ProtoField(99, ScalarType.BYTES, true), }; export const MultiMediaReqHead = { - Common: ProtoField(1, () => CommonHead), - Scene: ProtoField(2, () => SceneInfo), - Client: ProtoField(3, () => ClientMeta), + Common: ProtoField(1, () => CommonHead), + Scene: ProtoField(2, () => SceneInfo), + Client: ProtoField(3, () => ClientMeta), }; export const CommonHead = { - RequestId: ProtoField(1, ScalarType.UINT32), - Command: ProtoField(2, ScalarType.UINT32), + RequestId: ProtoField(1, ScalarType.UINT32), + Command: ProtoField(2, ScalarType.UINT32), }; export const SceneInfo = { - RequestType: ProtoField(101, ScalarType.UINT32), - BusinessType: ProtoField(102, ScalarType.UINT32), - SceneType: ProtoField(200, ScalarType.UINT32), - C2C: ProtoField(201, () => C2CUserInfo, true), - Group: ProtoField(202, () => NTGroupInfo, true), + RequestType: ProtoField(101, ScalarType.UINT32), + BusinessType: ProtoField(102, ScalarType.UINT32), + SceneType: ProtoField(200, ScalarType.UINT32), + C2C: ProtoField(201, () => C2CUserInfo, true), + Group: ProtoField(202, () => NTGroupInfo, true), }; export const C2CUserInfo = { - AccountType: ProtoField(1, ScalarType.UINT32), - TargetUid: ProtoField(2, ScalarType.STRING), + AccountType: ProtoField(1, ScalarType.UINT32), + TargetUid: ProtoField(2, ScalarType.STRING), }; export const NTGroupInfo = { - GroupUin: ProtoField(1, ScalarType.UINT32), + GroupUin: ProtoField(1, ScalarType.UINT32), }; export const ClientMeta = { - AgentType: ProtoField(1, ScalarType.UINT32), + AgentType: ProtoField(1, ScalarType.UINT32), }; export const DownloadReq = { - Node: ProtoField(1, () => IndexNode), - Download: ProtoField(2, () => DownloadExt), + Node: ProtoField(1, () => IndexNode), + Download: ProtoField(2, () => DownloadExt), }; export const IndexNode = { - Info: ProtoField(1, () => FileInfo), - FileUuid: ProtoField(2, ScalarType.STRING), - StoreId: ProtoField(3, ScalarType.UINT32), - UploadTime: ProtoField(4, ScalarType.UINT32), - Ttl: ProtoField(5, ScalarType.UINT32), - SubType: ProtoField(6, ScalarType.UINT32), + Info: ProtoField(1, () => FileInfo), + FileUuid: ProtoField(2, ScalarType.STRING), + StoreId: ProtoField(3, ScalarType.UINT32), + UploadTime: ProtoField(4, ScalarType.UINT32), + Ttl: ProtoField(5, ScalarType.UINT32), + SubType: ProtoField(6, ScalarType.UINT32), }; export const FileInfo = { - FileSize: ProtoField(1, ScalarType.UINT32), - FileHash: ProtoField(2, ScalarType.STRING), - FileSha1: ProtoField(3, ScalarType.STRING), - FileName: ProtoField(4, ScalarType.STRING), - Type: ProtoField(5, () => FileType), - Width: ProtoField(6, ScalarType.UINT32), - Height: ProtoField(7, ScalarType.UINT32), - Time: ProtoField(8, ScalarType.UINT32), - Original: ProtoField(9, ScalarType.UINT32), + FileSize: ProtoField(1, ScalarType.UINT32), + FileHash: ProtoField(2, ScalarType.STRING), + FileSha1: ProtoField(3, ScalarType.STRING), + FileName: ProtoField(4, ScalarType.STRING), + Type: ProtoField(5, () => FileType), + Width: ProtoField(6, ScalarType.UINT32), + Height: ProtoField(7, ScalarType.UINT32), + Time: ProtoField(8, ScalarType.UINT32), + Original: ProtoField(9, ScalarType.UINT32), }; export const FileType = { - Type: ProtoField(1, ScalarType.UINT32), - PicFormat: ProtoField(2, ScalarType.UINT32), - VideoFormat: ProtoField(3, ScalarType.UINT32), - VoiceFormat: ProtoField(4, ScalarType.UINT32), + Type: ProtoField(1, ScalarType.UINT32), + PicFormat: ProtoField(2, ScalarType.UINT32), + VideoFormat: ProtoField(3, ScalarType.UINT32), + VoiceFormat: ProtoField(4, ScalarType.UINT32), }; export const DownloadExt = { - Pic: ProtoField(1, () => PicDownloadExt), - Video: ProtoField(2, () => VideoDownloadExt), - Ptt: ProtoField(3, () => PttDownloadExt), + Pic: ProtoField(1, () => PicDownloadExt), + Video: ProtoField(2, () => VideoDownloadExt), + Ptt: ProtoField(3, () => PttDownloadExt), }; export const VideoDownloadExt = { - BusiType: ProtoField(1, ScalarType.UINT32), - SceneType: ProtoField(2, ScalarType.UINT32), - SubBusiType: ProtoField(3, ScalarType.UINT32), + BusiType: ProtoField(1, ScalarType.UINT32), + SceneType: ProtoField(2, ScalarType.UINT32), + SubBusiType: ProtoField(3, ScalarType.UINT32), }; export const PicDownloadExt = {}; @@ -95,50 +95,50 @@ export const PicDownloadExt = {}; export const PttDownloadExt = {}; export const DownloadRKeyReq = { - Types: ProtoField(1, ScalarType.INT32, false, true), + Types: ProtoField(1, ScalarType.INT32, false, true), }; export const DeleteReq = { - Index: ProtoField(1, () => IndexNode, false, true), - NeedRecallMsg: ProtoField(2, ScalarType.BOOL), - MsgSeq: ProtoField(3, ScalarType.UINT64), - MsgRandom: ProtoField(4, ScalarType.UINT64), - MsgTime: ProtoField(5, ScalarType.UINT64), + Index: ProtoField(1, () => IndexNode, false, true), + NeedRecallMsg: ProtoField(2, ScalarType.BOOL), + MsgSeq: ProtoField(3, ScalarType.UINT64), + MsgRandom: ProtoField(4, ScalarType.UINT64), + MsgTime: ProtoField(5, ScalarType.UINT64), }; export const UploadCompletedReq = { - SrvSendMsg: ProtoField(1, ScalarType.BOOL), - ClientRandomId: ProtoField(2, ScalarType.UINT64), - MsgInfo: ProtoField(3, () => MsgInfo), - ClientSeq: ProtoField(4, ScalarType.UINT32), + SrvSendMsg: ProtoField(1, ScalarType.BOOL), + ClientRandomId: ProtoField(2, ScalarType.UINT64), + MsgInfo: ProtoField(3, () => MsgInfo), + ClientSeq: ProtoField(4, ScalarType.UINT32), }; export const MsgInfoAuthReq = { - Msg: ProtoField(1, ScalarType.BYTES), - AuthTime: ProtoField(2, ScalarType.UINT64), + Msg: ProtoField(1, ScalarType.BYTES), + AuthTime: ProtoField(2, ScalarType.UINT64), }; export const DownloadSafeReq = { - Index: ProtoField(1, () => IndexNode), + Index: ProtoField(1, () => IndexNode), }; export const UploadKeyRenewalReq = { - OldUKey: ProtoField(1, ScalarType.STRING), - SubType: ProtoField(2, ScalarType.UINT32), + OldUKey: ProtoField(1, ScalarType.STRING), + SubType: ProtoField(2, ScalarType.UINT32), }; export const MsgInfo = { - MsgInfoBody: ProtoField(1, () => MsgInfoBody, false, true), - ExtBizInfo: ProtoField(2, () => ExtBizInfo), + MsgInfoBody: ProtoField(1, () => MsgInfoBody, false, true), + ExtBizInfo: ProtoField(2, () => ExtBizInfo), }; export const MsgInfoBody = { - Index: ProtoField(1, () => IndexNode), - Picture: ProtoField(2, () => PictureInfo), - Video: ProtoField(3, () => VideoInfo), - Audio: ProtoField(4, () => AudioInfo), - FileExist: ProtoField(5, ScalarType.BOOL), - HashSum: ProtoField(6, ScalarType.BYTES), + Index: ProtoField(1, () => IndexNode), + Picture: ProtoField(2, () => PictureInfo), + Video: ProtoField(3, () => VideoInfo), + Audio: ProtoField(4, () => AudioInfo), + FileExist: ProtoField(5, ScalarType.BOOL), + HashSum: ProtoField(6, ScalarType.BYTES), }; export const VideoInfo = {}; @@ -146,92 +146,92 @@ export const VideoInfo = {}; export const AudioInfo = {}; export const PictureInfo = { - UrlPath: ProtoField(1, ScalarType.STRING), - Ext: ProtoField(2, () => PicUrlExtInfo), - Domain: ProtoField(3, ScalarType.STRING), + UrlPath: ProtoField(1, ScalarType.STRING), + Ext: ProtoField(2, () => PicUrlExtInfo), + Domain: ProtoField(3, ScalarType.STRING), }; export const PicUrlExtInfo = { - OriginalParameter: ProtoField(1, ScalarType.STRING), - BigParameter: ProtoField(2, ScalarType.STRING), - ThumbParameter: ProtoField(3, ScalarType.STRING), + OriginalParameter: ProtoField(1, ScalarType.STRING), + BigParameter: ProtoField(2, ScalarType.STRING), + ThumbParameter: ProtoField(3, ScalarType.STRING), }; export const VideoExtInfo = { - VideoCodecFormat: ProtoField(1, ScalarType.UINT32), + VideoCodecFormat: ProtoField(1, ScalarType.UINT32), }; export const ExtBizInfo = { - Pic: ProtoField(1, () => PicExtBizInfo), - Video: ProtoField(2, () => VideoExtBizInfo), - Ptt: ProtoField(3, () => PttExtBizInfo), - BusiType: ProtoField(10, ScalarType.UINT32), + Pic: ProtoField(1, () => PicExtBizInfo), + Video: ProtoField(2, () => VideoExtBizInfo), + Ptt: ProtoField(3, () => PttExtBizInfo), + BusiType: ProtoField(10, ScalarType.UINT32), }; export const PttExtBizInfo = { - SrcUin: ProtoField(1, ScalarType.UINT64), - PttScene: ProtoField(2, ScalarType.UINT32), - PttType: ProtoField(3, ScalarType.UINT32), - ChangeVoice: ProtoField(4, ScalarType.UINT32), - Waveform: ProtoField(5, ScalarType.BYTES), - AutoConvertText: ProtoField(6, ScalarType.UINT32), - BytesReserve: ProtoField(11, ScalarType.BYTES), - BytesPbReserve: ProtoField(12, ScalarType.BYTES), - BytesGeneralFlags: ProtoField(13, ScalarType.BYTES), + SrcUin: ProtoField(1, ScalarType.UINT64), + PttScene: ProtoField(2, ScalarType.UINT32), + PttType: ProtoField(3, ScalarType.UINT32), + ChangeVoice: ProtoField(4, ScalarType.UINT32), + Waveform: ProtoField(5, ScalarType.BYTES), + AutoConvertText: ProtoField(6, ScalarType.UINT32), + BytesReserve: ProtoField(11, ScalarType.BYTES), + BytesPbReserve: ProtoField(12, ScalarType.BYTES), + BytesGeneralFlags: ProtoField(13, ScalarType.BYTES), }; export const VideoExtBizInfo = { - FromScene: ProtoField(1, ScalarType.UINT32), - ToScene: ProtoField(2, ScalarType.UINT32), - BytesPbReserve: ProtoField(3, ScalarType.BYTES), + FromScene: ProtoField(1, ScalarType.UINT32), + ToScene: ProtoField(2, ScalarType.UINT32), + BytesPbReserve: ProtoField(3, ScalarType.BYTES), }; export const PicExtBizInfo = { - BizType: ProtoField(1, ScalarType.UINT32), - TextSummary: ProtoField(2, ScalarType.STRING), - BytesPbReserveC2c: ProtoField(11, () => BytesPbReserveC2c), - BytesPbReserveTroop: ProtoField(12, () => BytesPbReserveTroop), - FromScene: ProtoField(1001, ScalarType.UINT32), - ToScene: ProtoField(1002, ScalarType.UINT32), - OldFileId: ProtoField(1003, ScalarType.UINT32), + BizType: ProtoField(1, ScalarType.UINT32), + TextSummary: ProtoField(2, ScalarType.STRING), + BytesPbReserveC2c: ProtoField(11, () => BytesPbReserveC2c), + BytesPbReserveTroop: ProtoField(12, () => BytesPbReserveTroop), + FromScene: ProtoField(1001, ScalarType.UINT32), + ToScene: ProtoField(1002, ScalarType.UINT32), + OldFileId: ProtoField(1003, ScalarType.UINT32), }; export const UploadReq = { - UploadInfo: ProtoField(1, () => UploadInfo, false, true), - TryFastUploadCompleted: ProtoField(2, ScalarType.BOOL), - SrvSendMsg: ProtoField(3, ScalarType.BOOL), - ClientRandomId: ProtoField(4, ScalarType.UINT64), - CompatQMsgSceneType: ProtoField(5, ScalarType.UINT32), - ExtBizInfo: ProtoField(6, () => ExtBizInfo), - ClientSeq: ProtoField(7, ScalarType.UINT32), - NoNeedCompatMsg: ProtoField(8, ScalarType.BOOL), + UploadInfo: ProtoField(1, () => UploadInfo, false, true), + TryFastUploadCompleted: ProtoField(2, ScalarType.BOOL), + SrvSendMsg: ProtoField(3, ScalarType.BOOL), + ClientRandomId: ProtoField(4, ScalarType.UINT64), + CompatQMsgSceneType: ProtoField(5, ScalarType.UINT32), + ExtBizInfo: ProtoField(6, () => ExtBizInfo), + ClientSeq: ProtoField(7, ScalarType.UINT32), + NoNeedCompatMsg: ProtoField(8, ScalarType.BOOL), }; export const UploadInfo = { - FileInfo: ProtoField(1, () => FileInfo), - SubFileType: ProtoField(2, ScalarType.UINT32), + FileInfo: ProtoField(1, () => FileInfo), + SubFileType: ProtoField(2, ScalarType.UINT32), }; export const BytesPbReserveC2c = { - subType: ProtoField(1, ScalarType.UINT32), - field3: ProtoField(3, ScalarType.UINT32), - field4: ProtoField(4, ScalarType.UINT32), - field8: ProtoField(8, ScalarType.STRING), - field10: ProtoField(10, ScalarType.UINT32), - field12: ProtoField(12, ScalarType.STRING), - field18: ProtoField(18, ScalarType.STRING), - field19: ProtoField(19, ScalarType.STRING), - field20: ProtoField(20, ScalarType.BYTES), + subType: ProtoField(1, ScalarType.UINT32), + field3: ProtoField(3, ScalarType.UINT32), + field4: ProtoField(4, ScalarType.UINT32), + field8: ProtoField(8, ScalarType.STRING), + field10: ProtoField(10, ScalarType.UINT32), + field12: ProtoField(12, ScalarType.STRING), + field18: ProtoField(18, ScalarType.STRING), + field19: ProtoField(19, ScalarType.STRING), + field20: ProtoField(20, ScalarType.BYTES), }; export const BytesPbReserveTroop = { - subType: ProtoField(1, ScalarType.UINT32), - field3: ProtoField(3, ScalarType.UINT32), - field4: ProtoField(4, ScalarType.UINT32), - field9: ProtoField(9, ScalarType.STRING), - field10: ProtoField(10, ScalarType.UINT32), - field12: ProtoField(12, ScalarType.STRING), - field18: ProtoField(18, ScalarType.STRING), - field19: ProtoField(19, ScalarType.STRING), - field21: ProtoField(21, ScalarType.BYTES), + subType: ProtoField(1, ScalarType.UINT32), + field3: ProtoField(3, ScalarType.UINT32), + field4: ProtoField(4, ScalarType.UINT32), + field9: ProtoField(9, ScalarType.STRING), + field10: ProtoField(10, ScalarType.UINT32), + field12: ProtoField(12, ScalarType.STRING), + field18: ProtoField(18, ScalarType.STRING), + field19: ProtoField(19, ScalarType.STRING), + field21: ProtoField(21, ScalarType.BYTES), }; diff --git a/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaResp.ts b/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaResp.ts index d63e81a2..013636b9 100644 --- a/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaResp.ts +++ b/src/core/packet/transformer/proto/oidb/common/Ntv2.RichMediaResp.ts @@ -1,114 +1,113 @@ import { ProtoField, ScalarType } from '@napneko/nap-proto-core'; import { CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo } from '@/core/packet/transformer/proto'; - export const NTV2RichMediaResp = { - respHead: ProtoField(1, () => MultiMediaRespHead), - upload: ProtoField(2, () => UploadResp), - download: ProtoField(3, () => DownloadResp), - downloadRKey: ProtoField(4, () => DownloadRKeyResp), - delete: ProtoField(5, () => DeleteResp), - uploadCompleted: ProtoField(6, () => UploadCompletedResp), - msgInfoAuth: ProtoField(7, () => MsgInfoAuthResp), - uploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalResp), - downloadSafe: ProtoField(9, () => DownloadSafeResp), - extension: ProtoField(99, ScalarType.BYTES, true), + respHead: ProtoField(1, () => MultiMediaRespHead), + upload: ProtoField(2, () => UploadResp), + download: ProtoField(3, () => DownloadResp), + downloadRKey: ProtoField(4, () => DownloadRKeyResp), + delete: ProtoField(5, () => DeleteResp), + uploadCompleted: ProtoField(6, () => UploadCompletedResp), + msgInfoAuth: ProtoField(7, () => MsgInfoAuthResp), + uploadKeyRenewal: ProtoField(8, () => UploadKeyRenewalResp), + downloadSafe: ProtoField(9, () => DownloadSafeResp), + extension: ProtoField(99, ScalarType.BYTES, true), }; export const MultiMediaRespHead = { - common: ProtoField(1, () => CommonHead), - retCode: ProtoField(2, ScalarType.UINT32), - message: ProtoField(3, ScalarType.STRING), + common: ProtoField(1, () => CommonHead), + retCode: ProtoField(2, ScalarType.UINT32), + message: ProtoField(3, ScalarType.STRING), }; export const DownloadResp = { - rKeyParam: ProtoField(1, ScalarType.STRING), - rKeyTtlSecond: ProtoField(2, ScalarType.UINT32), - info: ProtoField(3, () => DownloadInfo), - rKeyCreateTime: ProtoField(4, ScalarType.UINT32), + rKeyParam: ProtoField(1, ScalarType.STRING), + rKeyTtlSecond: ProtoField(2, ScalarType.UINT32), + info: ProtoField(3, () => DownloadInfo), + rKeyCreateTime: ProtoField(4, ScalarType.UINT32), }; export const DownloadInfo = { - domain: ProtoField(1, ScalarType.STRING), - urlPath: ProtoField(2, ScalarType.STRING), - httpsPort: ProtoField(3, ScalarType.UINT32), - ipv4s: ProtoField(4, () => IPv4, false, true), - ipv6s: ProtoField(5, () => IPv6, false, true), - picUrlExtInfo: ProtoField(6, () => PicUrlExtInfo), - videoExtInfo: ProtoField(7, () => VideoExtInfo), + domain: ProtoField(1, ScalarType.STRING), + urlPath: ProtoField(2, ScalarType.STRING), + httpsPort: ProtoField(3, ScalarType.UINT32), + ipv4s: ProtoField(4, () => IPv4, false, true), + ipv6s: ProtoField(5, () => IPv6, false, true), + picUrlExtInfo: ProtoField(6, () => PicUrlExtInfo), + videoExtInfo: ProtoField(7, () => VideoExtInfo), }; export const IPv4 = { - outIP: ProtoField(1, ScalarType.UINT32), - outPort: ProtoField(2, ScalarType.UINT32), - inIP: ProtoField(3, ScalarType.UINT32), - inPort: ProtoField(4, ScalarType.UINT32), - ipType: ProtoField(5, ScalarType.UINT32), + outIP: ProtoField(1, ScalarType.UINT32), + outPort: ProtoField(2, ScalarType.UINT32), + inIP: ProtoField(3, ScalarType.UINT32), + inPort: ProtoField(4, ScalarType.UINT32), + ipType: ProtoField(5, ScalarType.UINT32), }; export const IPv6 = { - outIP: ProtoField(1, ScalarType.BYTES), - outPort: ProtoField(2, ScalarType.UINT32), - inIP: ProtoField(3, ScalarType.BYTES), - inPort: ProtoField(4, ScalarType.UINT32), - ipType: ProtoField(5, ScalarType.UINT32), + outIP: ProtoField(1, ScalarType.BYTES), + outPort: ProtoField(2, ScalarType.UINT32), + inIP: ProtoField(3, ScalarType.BYTES), + inPort: ProtoField(4, ScalarType.UINT32), + ipType: ProtoField(5, ScalarType.UINT32), }; export const UploadResp = { - uKey: ProtoField(1, ScalarType.STRING, true), - uKeyTtlSecond: ProtoField(2, ScalarType.UINT32), - ipv4s: ProtoField(3, () => IPv4, false, true), - ipv6s: ProtoField(4, () => IPv6, false, true), - msgSeq: ProtoField(5, ScalarType.UINT64), - msgInfo: ProtoField(6, () => MsgInfo), - ext: ProtoField(7, () => RichMediaStorageTransInfo, false, true), - compatQMsg: ProtoField(8, ScalarType.BYTES), - subFileInfos: ProtoField(10, () => SubFileInfo, false, true), + uKey: ProtoField(1, ScalarType.STRING, true), + uKeyTtlSecond: ProtoField(2, ScalarType.UINT32), + ipv4s: ProtoField(3, () => IPv4, false, true), + ipv6s: ProtoField(4, () => IPv6, false, true), + msgSeq: ProtoField(5, ScalarType.UINT64), + msgInfo: ProtoField(6, () => MsgInfo), + ext: ProtoField(7, () => RichMediaStorageTransInfo, false, true), + compatQMsg: ProtoField(8, ScalarType.BYTES), + subFileInfos: ProtoField(10, () => SubFileInfo, false, true), }; export const RichMediaStorageTransInfo = { - subType: ProtoField(1, ScalarType.UINT32), - extType: ProtoField(2, ScalarType.UINT32), - extValue: ProtoField(3, ScalarType.BYTES), + subType: ProtoField(1, ScalarType.UINT32), + extType: ProtoField(2, ScalarType.UINT32), + extValue: ProtoField(3, ScalarType.BYTES), }; export const SubFileInfo = { - subType: ProtoField(1, ScalarType.UINT32), - uKey: ProtoField(2, ScalarType.STRING), - uKeyTtlSecond: ProtoField(3, ScalarType.UINT32), - ipv4s: ProtoField(4, () => IPv4, false, true), - ipv6s: ProtoField(5, () => IPv6, false, true), + subType: ProtoField(1, ScalarType.UINT32), + uKey: ProtoField(2, ScalarType.STRING), + uKeyTtlSecond: ProtoField(3, ScalarType.UINT32), + ipv4s: ProtoField(4, () => IPv4, false, true), + ipv6s: ProtoField(5, () => IPv6, false, true), }; export const DownloadSafeResp = { }; export const UploadKeyRenewalResp = { - ukey: ProtoField(1, ScalarType.STRING), - ukeyTtlSec: ProtoField(2, ScalarType.UINT64), + ukey: ProtoField(1, ScalarType.STRING), + ukeyTtlSec: ProtoField(2, ScalarType.UINT64), }; export const MsgInfoAuthResp = { - authCode: ProtoField(1, ScalarType.UINT32), - msg: ProtoField(2, ScalarType.BYTES), - resultTime: ProtoField(3, ScalarType.UINT64), + authCode: ProtoField(1, ScalarType.UINT32), + msg: ProtoField(2, ScalarType.BYTES), + resultTime: ProtoField(3, ScalarType.UINT64), }; export const UploadCompletedResp = { - msgSeq: ProtoField(1, ScalarType.UINT64), + msgSeq: ProtoField(1, ScalarType.UINT64), }; export const DeleteResp = { }; export const DownloadRKeyResp = { - rKeys: ProtoField(1, () => RKeyInfo, false, true), + rKeys: ProtoField(1, () => RKeyInfo, false, true), }; export const RKeyInfo = { - rkey: ProtoField(1, ScalarType.STRING), - rkeyTtlSec: ProtoField(2, ScalarType.UINT64), - storeId: ProtoField(3, ScalarType.UINT32), - rkeyCreateTime: ProtoField(4, ScalarType.UINT32, true), - type: ProtoField(5, ScalarType.UINT32, true), + rkey: ProtoField(1, ScalarType.STRING), + rkeyTtlSec: ProtoField(2, ScalarType.UINT64), + storeId: ProtoField(3, ScalarType.UINT32), + rkeyCreateTime: ProtoField(4, ScalarType.UINT32, true), + type: ProtoField(5, ScalarType.UINT32, true), }; diff --git a/src/core/packet/transformer/system/FetchRkey.ts b/src/core/packet/transformer/system/FetchRkey.ts index b461e4fb..b5ef31d2 100644 --- a/src/core/packet/transformer/system/FetchRkey.ts +++ b/src/core/packet/transformer/system/FetchRkey.ts @@ -4,37 +4,37 @@ import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base'; import OidbBase from '@/core/packet/transformer/oidb/oidbBase'; class FetchRkey extends PacketTransformer { - constructor() { - super(); - } + constructor () { + super(); + } - build(): OidbPacket { - const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X9067_202).encode({ - reqHead: { - common: { - requestId: 1, - command: 202 - }, - scene: { - requestType: 2, - businessType: 1, - sceneType: 0 - }, - client: { - agentType: 2 - } - }, - downloadRKeyReq: { - key: [10, 20, 2] - }, - }); - return OidbBase.build(0x9067, 202, data); - } + build (): OidbPacket { + const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X9067_202).encode({ + reqHead: { + common: { + requestId: 1, + command: 202, + }, + scene: { + requestType: 2, + businessType: 1, + sceneType: 0, + }, + client: { + agentType: 2, + }, + }, + downloadRKeyReq: { + key: [10, 20, 2], + }, + }); + return OidbBase.build(0x9067, 202, data); + } - parse(data: Buffer) { - const oidbBody = OidbBase.parse(data).body; - return new NapProtoMsg(proto.OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(oidbBody); - } + parse (data: Buffer) { + const oidbBody = OidbBase.parse(data).body; + return new NapProtoMsg(proto.OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(oidbBody); + } } export default new FetchRkey(); diff --git a/src/core/packet/utils/crypto/hash.ts b/src/core/packet/utils/crypto/hash.ts index d80d3c49..4341e5dd 100644 --- a/src/core/packet/utils/crypto/hash.ts +++ b/src/core/packet/utils/crypto/hash.ts @@ -4,44 +4,44 @@ import * as stream from 'stream'; import * as fs from 'fs'; import { CalculateStreamBytesTransform } from '@/core/packet/utils/crypto/sha1StreamBytesTransform'; -function sha1Stream(readable: stream.Readable) { - return new Promise((resolve, reject) => { - readable.on('error', reject); - readable.pipe(crypto.createHash('sha1').on('error', reject).on('data', resolve)); - }); +function sha1Stream (readable: stream.Readable) { + return new Promise((resolve, reject) => { + readable.on('error', reject); + readable.pipe(crypto.createHash('sha1').on('error', reject).on('data', resolve)); + }); } -function md5Stream(readable: stream.Readable) { - return new Promise((resolve, reject) => { - readable.on('error', reject); - readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve)); - }); +function md5Stream (readable: stream.Readable) { + return new Promise((resolve, reject) => { + readable.on('error', reject); + readable.pipe(crypto.createHash('md5').on('error', reject).on('data', resolve)); + }); } -export function calculateSha1(filePath: string): Promise { +export function calculateSha1 (filePath: string): Promise { + const readable = fs.createReadStream(filePath); + return sha1Stream(readable); +} + +export function computeMd5AndLengthWithLimit (filePath: string, limit?: number): Promise { + const readStream = fs.createReadStream(filePath, limit ? { start: 0, end: limit - 1 } : {}); + return md5Stream(readStream); +} + +export function calculateSha1StreamBytes (filePath: string): Promise { + return new Promise((resolve, reject) => { const readable = fs.createReadStream(filePath); - return sha1Stream(readable); -} - -export function computeMd5AndLengthWithLimit(filePath: string, limit?: number): Promise { - const readStream = fs.createReadStream(filePath, limit ? { start: 0, end: limit - 1 } : {}); - return md5Stream(readStream); -} - -export function calculateSha1StreamBytes(filePath: string): Promise { - return new Promise((resolve, reject) => { - const readable = fs.createReadStream(filePath); - const calculateStreamBytes = new CalculateStreamBytesTransform(); - const byteArrayList: Buffer[] = []; - calculateStreamBytes.on('data', (chunk: Buffer) => { - byteArrayList.push(chunk); - }); - calculateStreamBytes.on('end', () => { - resolve(byteArrayList); - }); - calculateStreamBytes.on('error', (err: Error) => { - reject(err); - }); - readable.pipe(calculateStreamBytes); + const calculateStreamBytes = new CalculateStreamBytesTransform(); + const byteArrayList: Buffer[] = []; + calculateStreamBytes.on('data', (chunk: Buffer) => { + byteArrayList.push(chunk); }); + calculateStreamBytes.on('end', () => { + resolve(byteArrayList); + }); + calculateStreamBytes.on('error', (err: Error) => { + reject(err); + }); + readable.pipe(calculateStreamBytes); + }); } diff --git a/src/core/packet/utils/crypto/sha1Stream.test.ts b/src/core/packet/utils/crypto/sha1Stream.test.ts index b8c9ef7f..d43ba5fd 100644 --- a/src/core/packet/utils/crypto/sha1Stream.test.ts +++ b/src/core/packet/utils/crypto/sha1Stream.test.ts @@ -2,18 +2,18 @@ import crypto from 'crypto'; import assert from 'assert'; import { Sha1Stream } from './sha1Stream'; -function testSha1Stream() { - for (let i = 0; i < 100000; i++) { - const randomLength = Math.floor(Math.random() * 1024); - const randomData = crypto.randomBytes(randomLength); - const sha1Stream = new Sha1Stream(); - sha1Stream.update(randomData); - const hash = sha1Stream.final(); - const expectedDigest = crypto.createHash('sha1').update(randomData).digest(); - assert.strictEqual(hash.toString('hex'), expectedDigest.toString('hex')); - console.log(`Test ${i + 1}: Passed`); - } - console.log('All tests passed successfully.'); +function testSha1Stream () { + for (let i = 0; i < 100000; i++) { + const randomLength = Math.floor(Math.random() * 1024); + const randomData = crypto.randomBytes(randomLength); + const sha1Stream = new Sha1Stream(); + sha1Stream.update(randomData); + const hash = sha1Stream.final(); + const expectedDigest = crypto.createHash('sha1').update(randomData).digest(); + assert.strictEqual(hash.toString('hex'), expectedDigest.toString('hex')); + console.log(`Test ${i + 1}: Passed`); + } + console.log('All tests passed successfully.'); } testSha1Stream(); diff --git a/src/core/packet/utils/crypto/sha1Stream.ts b/src/core/packet/utils/crypto/sha1Stream.ts index e13cebdd..d22fb89d 100644 --- a/src/core/packet/utils/crypto/sha1Stream.ts +++ b/src/core/packet/utils/crypto/sha1Stream.ts @@ -1,126 +1,125 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck +// @ts-nocheck export class Sha1Stream { - readonly Sha1BlockSize = 64; - readonly Sha1DigestSize = 20; - private readonly _padding = Buffer.concat([Buffer.from([0x80]), Buffer.alloc(63)]); - private readonly _state = new Uint32Array(5); - private readonly _count = new Uint32Array(2); - private readonly _buffer = Buffer.allocUnsafe(this.Sha1BlockSize); - private readonly _w = new Uint32Array(80); + readonly Sha1BlockSize = 64; + readonly Sha1DigestSize = 20; + private readonly _padding = Buffer.concat([Buffer.from([0x80]), Buffer.alloc(63)]); + private readonly _state = new Uint32Array(5); + private readonly _count = new Uint32Array(2); + private readonly _buffer = Buffer.allocUnsafe(this.Sha1BlockSize); + private readonly _w = new Uint32Array(80); - constructor() { - this.reset(); + constructor () { + this.reset(); + } + + private reset (): void { + this._state[0] = 0x67452301; + this._state[1] = 0xEFCDAB89; + this._state[2] = 0x98BADCFE; + this._state[3] = 0x10325476; + this._state[4] = 0xC3D2E1F0; + this._count[0] = 0; + this._count[1] = 0; + this._buffer.fill(0); + } + + private rotateLeft (v: number, o: number): number { + return ((v << o) | (v >>> (32 - o))) >>> 0; + } + + private transform (chunk: Buffer, offset: number): void { + const w = this._w; + const view = new DataView(chunk.buffer, chunk.byteOffset + offset, 64); + + for (let i = 0; i < 16; i++) { + w[i] = view.getUint32(i * 4, false); } - private reset(): void { - this._state[0] = 0x67452301; - this._state[1] = 0xEFCDAB89; - this._state[2] = 0x98BADCFE; - this._state[3] = 0x10325476; - this._state[4] = 0xC3D2E1F0; - this._count[0] = 0; - this._count[1] = 0; - this._buffer.fill(0); + for (let i = 16; i < 80; i++) { + w[i] = this.rotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0; } - private rotateLeft(v: number, o: number): number { - return ((v << o) | (v >>> (32 - o))) >>> 0; + let a = this._state[0]; + let b = this._state[1]; + let c = this._state[2]; + let d = this._state[3]; + let e = this._state[4]; + + for (let i = 0; i < 80; i++) { + let temp; + if (i < 20) { + temp = ((b & c) | (~b & d)) + 0x5A827999; + } else if (i < 40) { + temp = (b ^ c ^ d) + 0x6ED9EBA1; + } else if (i < 60) { + temp = ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC; + } else { + temp = (b ^ c ^ d) + 0xCA62C1D6; + } + temp += ((this.rotateLeft(a, 5) + e + w[i]) >>> 0); + e = d; + d = c; + c = this.rotateLeft(b, 30) >>> 0; + b = a; + a = temp; } - private transform(chunk: Buffer, offset: number): void { - const w = this._w; - const view = new DataView(chunk.buffer, chunk.byteOffset + offset, 64); + this._state[0] = (this._state[0] + a) >>> 0; + this._state[1] = (this._state[1] + b) >>> 0; + this._state[2] = (this._state[2] + c) >>> 0; + this._state[3] = (this._state[3] + d) >>> 0; + this._state[4] = (this._state[4] + e) >>> 0; + } - for (let i = 0; i < 16; i++) { - w[i] = view.getUint32(i * 4, false); - } + public update (data: Buffer, len?: number): void { + let index = ((this._count[0] >>> 3) & 0x3F) >>> 0; + const dataLen = len ?? data.length; + this._count[0] = (this._count[0] + (dataLen << 3)) >>> 0; - for (let i = 16; i < 80; i++) { - w[i] = this.rotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0; - } + if (this._count[0] < (dataLen << 3)) this._count[1] = (this._count[1] + 1) >>> 0; - let a = this._state[0]; - let b = this._state[1]; - let c = this._state[2]; - let d = this._state[3]; - let e = this._state[4]; + this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0; - for (let i = 0; i < 80; i++) { - let temp; - if (i < 20) { - temp = ((b & c) | (~b & d)) + 0x5A827999; - } else if (i < 40) { - temp = (b ^ c ^ d) + 0x6ED9EBA1; - } else if (i < 60) { - temp = ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC; - } else { - temp = (b ^ c ^ d) + 0xCA62C1D6; - } - temp += ((this.rotateLeft(a, 5) + e + w[i]) >>> 0); - e = d; - d = c; - c = this.rotateLeft(b, 30) >>> 0; - b = a; - a = temp; - } + const partLen = (this.Sha1BlockSize - index) >>> 0; + let i = 0; - this._state[0] = (this._state[0] + a) >>> 0; - this._state[1] = (this._state[1] + b) >>> 0; - this._state[2] = (this._state[2] + c) >>> 0; - this._state[3] = (this._state[3] + d) >>> 0; - this._state[4] = (this._state[4] + e) >>> 0; + if (dataLen >= partLen) { + data.copy(this._buffer, index, 0, partLen); + this.transform(this._buffer, 0); + for (i = partLen; (i + this.Sha1BlockSize) <= dataLen; i = (i + this.Sha1BlockSize) >>> 0) { + this.transform(data, i); + } + index = 0; } - public update(data: Buffer, len?: number): void { - let index = ((this._count[0] >>> 3) & 0x3F) >>> 0; - const dataLen = len ?? data.length; - this._count[0] = (this._count[0] + (dataLen << 3)) >>> 0; + data.copy(this._buffer, index, i, dataLen); + } - if (this._count[0] < (dataLen << 3)) this._count[1] = (this._count[1] + 1) >>> 0; + public hash (bigEndian: boolean = true): Buffer { + const digest = Buffer.allocUnsafe(this.Sha1DigestSize); + if (bigEndian) { + for (let i = 0; i < 5; i++) digest.writeUInt32BE(this._state[i], i * 4); + } else { + for (let i = 0; i < 5; i++) digest.writeUInt32LE(this._state[i], i * 4); + } + return digest; + } - this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0; + public final (): Buffer { + const digest = Buffer.allocUnsafe(this.Sha1DigestSize); + const bits = Buffer.allocUnsafe(8); + bits.writeUInt32BE(this._count[1], 0); + bits.writeUInt32BE(this._count[0], 4); - const partLen = (this.Sha1BlockSize - index) >>> 0; - let i = 0; + const index = ((this._count[0] >>> 3) & 0x3F) >>> 0; + const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0; + this.update(this._padding, padLen); + this.update(bits); - if (dataLen >= partLen) { - data.copy(this._buffer, index, 0, partLen); - this.transform(this._buffer, 0); - for (i = partLen; (i + this.Sha1BlockSize) <= dataLen; i = (i + this.Sha1BlockSize) >>> 0) { - this.transform(data, i); - } - index = 0; - } - - data.copy(this._buffer, index, i, dataLen); + for (let i = 0; i < 5; i++) { + digest.writeUInt32BE(this._state[i], i * 4); } - public hash(bigEndian: boolean = true): Buffer { - const digest = Buffer.allocUnsafe(this.Sha1DigestSize); - if (bigEndian) { - for (let i = 0; i < 5; i++) digest.writeUInt32BE(this._state[i], i * 4); - } else { - for (let i = 0; i < 5; i++) digest.writeUInt32LE(this._state[i], i * 4); - } - return digest; - } - - public final(): Buffer { - const digest = Buffer.allocUnsafe(this.Sha1DigestSize); - const bits = Buffer.allocUnsafe(8); - bits.writeUInt32BE(this._count[1], 0); - bits.writeUInt32BE(this._count[0], 4); - - const index = ((this._count[0] >>> 3) & 0x3F) >>> 0; - const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0; - this.update(this._padding, padLen); - this.update(bits); - - for (let i = 0; i < 5; i++) { - digest.writeUInt32BE(this._state[i], i * 4); - } - - return digest; - } + return digest; + } } diff --git a/src/core/packet/utils/crypto/sha1StreamBytesTransform.ts b/src/core/packet/utils/crypto/sha1StreamBytesTransform.ts index 1e4478a9..a45159d7 100644 --- a/src/core/packet/utils/crypto/sha1StreamBytesTransform.ts +++ b/src/core/packet/utils/crypto/sha1StreamBytesTransform.ts @@ -2,53 +2,52 @@ import * as stream from 'node:stream'; import { Sha1Stream } from '@/core/packet/utils/crypto/sha1Stream'; export class CalculateStreamBytesTransform extends stream.Transform { - private readonly blockSize = 1024 * 1024; - private readonly sha1: Sha1Stream; - private buffer: Buffer; - private bytesRead: number; - private readonly byteArrayList: Buffer[]; + private readonly blockSize = 1024 * 1024; + private readonly sha1: Sha1Stream; + private buffer: Buffer; + private bytesRead: number; + private readonly byteArrayList: Buffer[]; - constructor() { - super(); - this.sha1 = new Sha1Stream(); - this.buffer = Buffer.alloc(0); - this.bytesRead = 0; - this.byteArrayList = []; - } + constructor () { + super(); + this.sha1 = new Sha1Stream(); + this.buffer = Buffer.alloc(0); + this.bytesRead = 0; + this.byteArrayList = []; + } - // eslint-disable-next-line no-undef - override _transform(chunk: Buffer, _: BufferEncoding, callback: stream.TransformCallback): void { - try { - this.buffer = Buffer.concat([this.buffer, chunk]); - let offset = 0; - while (this.buffer.length - offset >= this.sha1.Sha1BlockSize) { - const block = this.buffer.subarray(offset, offset + this.sha1.Sha1BlockSize); - this.sha1.update(block); - offset += this.sha1.Sha1BlockSize; - this.bytesRead += this.sha1.Sha1BlockSize; - if (this.bytesRead % this.blockSize === 0) { - const digest = this.sha1.hash(false); - this.byteArrayList.push(Buffer.from(digest)); - } - } - this.buffer = this.buffer.subarray(offset); - callback(null); - } catch (err) { - callback(err as Error); + override _transform (chunk: Buffer, _: BufferEncoding, callback: stream.TransformCallback): void { + try { + this.buffer = Buffer.concat([this.buffer, chunk]); + let offset = 0; + while (this.buffer.length - offset >= this.sha1.Sha1BlockSize) { + const block = this.buffer.subarray(offset, offset + this.sha1.Sha1BlockSize); + this.sha1.update(block); + offset += this.sha1.Sha1BlockSize; + this.bytesRead += this.sha1.Sha1BlockSize; + if (this.bytesRead % this.blockSize === 0) { + const digest = this.sha1.hash(false); + this.byteArrayList.push(Buffer.from(digest)); } + } + this.buffer = this.buffer.subarray(offset); + callback(null); + } catch (err) { + callback(err as Error); } + } - override _flush(callback: stream.TransformCallback): void { - try { - if (this.buffer.length > 0) this.sha1.update(this.buffer); - const finalDigest = this.sha1.final(); - this.byteArrayList.push(Buffer.from(finalDigest)); - for (const digest of this.byteArrayList) { - this.push(digest); - } - callback(null); - } catch (err) { - callback(err as Error); - } + override _flush (callback: stream.TransformCallback): void { + try { + if (this.buffer.length > 0) this.sha1.update(this.buffer); + const finalDigest = this.sha1.final(); + this.byteArrayList.push(Buffer.from(finalDigest)); + for (const digest of this.byteArrayList) { + this.push(digest); + } + callback(null); + } catch (err) { + callback(err as Error); } + } } diff --git a/src/core/packet/utils/crypto/tea.ts b/src/core/packet/utils/crypto/tea.ts index 4b23cc11..c916db67 100644 --- a/src/core/packet/utils/crypto/tea.ts +++ b/src/core/packet/utils/crypto/tea.ts @@ -1,87 +1,87 @@ // love from https://github.com/LagrangeDev/lagrangejs/blob/main/src/core/tea.ts & https://github.com/takayama-lily/oicq/blob/main/lib/core/tea.ts -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck + +// @ts-nocheck const BUF7 = Buffer.alloc(7); const deltas = [ - 0x9e3779b9, 0x3c6ef372, 0xdaa66d2b, 0x78dde6e4, 0x1715609d, 0xb54cda56, 0x5384540f, 0xf1bbcdc8, 0x8ff34781, - 0x2e2ac13a, 0xcc623af3, 0x6a99b4ac, 0x08d12e65, 0xa708a81e, 0x454021d7, 0xe3779b90, + 0x9e3779b9, 0x3c6ef372, 0xdaa66d2b, 0x78dde6e4, 0x1715609d, 0xb54cda56, 0x5384540f, 0xf1bbcdc8, 0x8ff34781, + 0x2e2ac13a, 0xcc623af3, 0x6a99b4ac, 0x08d12e65, 0xa708a81e, 0x454021d7, 0xe3779b90, ]; -function _toUInt32(num: number) { - return num >>> 0; +function _toUInt32 (num: number) { + return num >>> 0; } -function _encrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number): [number, number] { - for (let i = 0; i < 16; ++i) { - let aa = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1); - aa >>>= 0; - x = _toUInt32(x + aa); - let bb = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3); - bb >>>= 0; - y = _toUInt32(y + bb); - } - return [x, y]; +function _encrypt (x: number, y: number, k0: number, k1: number, k2: number, k3: number): [number, number] { + for (let i = 0; i < 16; ++i) { + let aa = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1); + aa >>>= 0; + x = _toUInt32(x + aa); + let bb = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3); + bb >>>= 0; + y = _toUInt32(y + bb); + } + return [x, y]; } -export function encrypt(data: Buffer, key: Buffer) { - let n = (6 - data.length) >>> 0; - n = (n % 8) + 2; - const v = Buffer.concat([Buffer.from([(n - 2) | 0xf8]), Buffer.allocUnsafe(n), data, BUF7]); - const k0 = key.readUInt32BE(0); - const k1 = key.readUInt32BE(4); - const k2 = key.readUInt32BE(8); - const k3 = key.readUInt32BE(12); - let r1 = 0, r2 = 0, t1 = 0, t2 = 0; - for (let i = 0; i < v.length; i += 8) { - const a1 = v.readUInt32BE(i); - const a2 = v.readUInt32BE(i + 4); - const b1 = a1 ^ r1; - const b2 = a2 ^ r2; - const [x, y] = _encrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3); - r1 = x ^ t1; - r2 = y ^ t2; - t1 = b1; - t2 = b2; - v.writeInt32BE(r1, i); - v.writeInt32BE(r2, i + 4); - } - return v; +export function encrypt (data: Buffer, key: Buffer) { + let n = (6 - data.length) >>> 0; + n = (n % 8) + 2; + const v = Buffer.concat([Buffer.from([(n - 2) | 0xf8]), Buffer.allocUnsafe(n), data, BUF7]); + const k0 = key.readUInt32BE(0); + const k1 = key.readUInt32BE(4); + const k2 = key.readUInt32BE(8); + const k3 = key.readUInt32BE(12); + let r1 = 0; let r2 = 0; let t1 = 0; let t2 = 0; + for (let i = 0; i < v.length; i += 8) { + const a1 = v.readUInt32BE(i); + const a2 = v.readUInt32BE(i + 4); + const b1 = a1 ^ r1; + const b2 = a2 ^ r2; + const [x, y] = _encrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3); + r1 = x ^ t1; + r2 = y ^ t2; + t1 = b1; + t2 = b2; + v.writeInt32BE(r1, i); + v.writeInt32BE(r2, i + 4); + } + return v; } -function _decrypt(x: number, y: number, k0: number, k1: number, k2: number, k3: number) { - for (let i = 15; i >= 0; --i) { - const aa = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3); - y = (y - aa) >>> 0; - const bb = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1); - x = (x - bb) >>> 0; - } - return [x, y]; +function _decrypt (x: number, y: number, k0: number, k1: number, k2: number, k3: number) { + for (let i = 15; i >= 0; --i) { + const aa = ((_toUInt32(((x << 4) >>> 0) + k2) ^ _toUInt32(x + deltas[i])) >>> 0) ^ _toUInt32(~~(x / 32) + k3); + y = (y - aa) >>> 0; + const bb = ((_toUInt32(((y << 4) >>> 0) + k0) ^ _toUInt32(y + deltas[i])) >>> 0) ^ _toUInt32(~~(y / 32) + k1); + x = (x - bb) >>> 0; + } + return [x, y]; } -export function decrypt(encrypted: Buffer, key: Buffer) { - if (encrypted.length % 8) throw ERROR_ENCRYPTED_LENGTH; - const k0 = key.readUInt32BE(0); - const k1 = key.readUInt32BE(4); - const k2 = key.readUInt32BE(8); - const k3 = key.readUInt32BE(12); - let r1 = 0, r2 = 0, t1 = 0, t2 = 0, x = 0, y = 0; - for (let i = 0; i < encrypted.length; i += 8) { - const a1 = encrypted.readUInt32BE(i); - const a2 = encrypted.readUInt32BE(i + 4); - const b1 = a1 ^ x; - const b2 = a2 ^ y; - [x, y] = _decrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3); - r1 = x ^ t1; - r2 = y ^ t2; - t1 = a1; - t2 = a2; - encrypted.writeInt32BE(r1, i); - encrypted.writeInt32BE(r2, i + 4); - } - if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL; - // if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL; - return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7); - // return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7); +export function decrypt (encrypted: Buffer, key: Buffer) { + if (encrypted.length % 8) throw ERROR_ENCRYPTED_LENGTH; + const k0 = key.readUInt32BE(0); + const k1 = key.readUInt32BE(4); + const k2 = key.readUInt32BE(8); + const k3 = key.readUInt32BE(12); + let r1 = 0; let r2 = 0; let t1 = 0; let t2 = 0; let x = 0; let y = 0; + for (let i = 0; i < encrypted.length; i += 8) { + const a1 = encrypted.readUInt32BE(i); + const a2 = encrypted.readUInt32BE(i + 4); + const b1 = a1 ^ x; + const b2 = a2 ^ y; + [x, y] = _decrypt(b1 >>> 0, b2 >>> 0, k0, k1, k2, k3); + r1 = x ^ t1; + r2 = y ^ t2; + t1 = a1; + t2 = a2; + encrypted.writeInt32BE(r1, i); + encrypted.writeInt32BE(r2, i + 4); + } + if (Buffer.compare(encrypted.subarray(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL; + // if (Buffer.compare(encrypted.slice(encrypted.length - 7), BUF7) !== 0) throw ERROR_ENCRYPTED_ILLEGAL; + return encrypted.subarray((encrypted[0] & 0x07) + 3, encrypted.length - 7); + // return encrypted.slice((encrypted[0] & 0x07) + 3, encrypted.length - 7); } const ERROR_ENCRYPTED_LENGTH = new Error('length of encrypted data must be a multiple of 8'); diff --git a/src/core/packet/utils/helper/miniAppHelper.ts b/src/core/packet/utils/helper/miniAppHelper.ts index 2f3acd06..d2320ebd 100644 --- a/src/core/packet/utils/helper/miniAppHelper.ts +++ b/src/core/packet/utils/helper/miniAppHelper.ts @@ -1,94 +1,94 @@ import { - MiniAppData, - MiniAppReqParams, - MiniAppRawData, - MiniAppReqCustomParams, - MiniAppReqTemplateParams + MiniAppData, + MiniAppReqParams, + MiniAppRawData, + MiniAppReqCustomParams, + MiniAppReqTemplateParams, } from '@/core/packet/entities/miniApp'; type MiniAppTemplateNameList = 'bili' | 'weibo'; export abstract class MiniAppInfo { - static readonly sdkId: string = 'V1_PC_MINISDK_99.99.99_1_APP_A'; - template: MiniAppReqTemplateParams; + static readonly sdkId: string = 'V1_PC_MINISDK_99.99.99_1_APP_A'; + template: MiniAppReqTemplateParams; - private static readonly appMap = new Map(); + private static readonly appMap = new Map(); - protected constructor(template: MiniAppReqTemplateParams) { - this.template = template; + protected constructor (template: MiniAppReqTemplateParams) { + this.template = template; + } + + static get (name: MiniAppTemplateNameList): MiniAppInfo | undefined { + return this.appMap.get(name); + } + + static readonly Bili = new class extends MiniAppInfo { + constructor () { + super({ + sdkId: MiniAppInfo.sdkId, + appId: '1109937557', + scene: 1, + templateType: 1, + businessType: 0, + verType: 3, + shareType: 0, + versionId: 'cfc5f7b05b44b5956502edaecf9d2240', + withShareTicket: 0, + iconUrl: 'https://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg', + }); + MiniAppInfo.appMap.set('bili', this); } + }(); - static get(name: MiniAppTemplateNameList): MiniAppInfo | undefined { - return this.appMap.get(name); + static readonly WeiBo = new class extends MiniAppInfo { + constructor () { + super({ + sdkId: MiniAppInfo.sdkId, + appId: '1109224783', + scene: 1, + templateType: 1, + businessType: 0, + verType: 3, + shareType: 0, + versionId: 'e482a3cc4e574d9b772e96ba6eec9ba2', + withShareTicket: 0, + iconUrl: 'https://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg', + }); + MiniAppInfo.appMap.set('weibo', this); } - - static readonly Bili = new class extends MiniAppInfo { - constructor() { - super({ - sdkId: MiniAppInfo.sdkId, - appId: '1109937557', - scene: 1, - templateType: 1, - businessType: 0, - verType: 3, - shareType: 0, - versionId: 'cfc5f7b05b44b5956502edaecf9d2240', - withShareTicket: 0, - iconUrl: 'https://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg' - }); - MiniAppInfo.appMap.set('bili', this); - } - }; - - static readonly WeiBo = new class extends MiniAppInfo { - constructor() { - super({ - sdkId: MiniAppInfo.sdkId, - appId: '1109224783', - scene: 1, - templateType: 1, - businessType: 0, - verType: 3, - shareType: 0, - versionId: 'e482a3cc4e574d9b772e96ba6eec9ba2', - withShareTicket: 0, - iconUrl: 'https://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg' - }); - MiniAppInfo.appMap.set('weibo', this); - } - }; + }(); } export class MiniAppInfoHelper { - static generateReq(custom: MiniAppReqCustomParams, template: MiniAppReqTemplateParams): MiniAppReqParams { - return { - ...custom, - ...template - }; - } + static generateReq (custom: MiniAppReqCustomParams, template: MiniAppReqTemplateParams): MiniAppReqParams { + return { + ...custom, + ...template, + }; + } - static RawToSend(rawData: MiniAppRawData): MiniAppData { - return { - ver: rawData.ver, - prompt: rawData.prompt, - config: rawData.config, - app: rawData.appName, - view: rawData.appView, - meta: rawData.metaData, - miniappShareOrigin: 3, - miniappOpenRefer: '10002', - }; - } + static RawToSend (rawData: MiniAppRawData): MiniAppData { + return { + ver: rawData.ver, + prompt: rawData.prompt, + config: rawData.config, + app: rawData.appName, + view: rawData.appView, + meta: rawData.metaData, + miniappShareOrigin: 3, + miniappOpenRefer: '10002', + }; + } - static SendToRaw(data: MiniAppData): MiniAppRawData { - return { - appName: data.app, - appView: data.view, - ver: data.ver, - desc: data.meta.detail_1.desc, - prompt: data.prompt, - metaData: data.meta, - config: data.config, - }; - } + static SendToRaw (data: MiniAppData): MiniAppRawData { + return { + appName: data.app, + appView: data.view, + ver: data.ver, + desc: data.meta.detail_1.desc, + prompt: data.prompt, + metaData: data.meta, + config: data.config, + }; + } } diff --git a/src/core/services/NodeIKernelAlbumService.ts b/src/core/services/NodeIKernelAlbumService.ts index 9cc086b2..f6699d67 100644 --- a/src/core/services/NodeIKernelAlbumService.ts +++ b/src/core/services/NodeIKernelAlbumService.ts @@ -1,101 +1,101 @@ -import { AlbumCommentReplyContent, AlbumFeedLikePublish, AlbumListRequest, AlbumMediaFeed } from "../data/album"; +import { AlbumCommentReplyContent, AlbumFeedLikePublish, AlbumListRequest, AlbumMediaFeed } from '../data/album'; export interface NodeIKernelAlbumService { - setAlbumServiceInfo(...args: unknown[]): unknown;// needs 3 arguments + setAlbumServiceInfo(...args: unknown[]): unknown;// needs 3 arguments - getMainPage(...args: unknown[]): unknown;// needs 2 arguments + getMainPage(...args: unknown[]): unknown;// needs 2 arguments - getAlbumList(params: { - qun_id: string, - attach_info: string, - seq: number, - request_time_line: { - request_invoke_time: string - } - }): Promise<{ - response: { - seq: number, - result: number, - errMs: string,//没错就是errMs不是errMsg - trace_id: string, - is_from_cache: boolean, - request_time_line: unknown, - album_list: Array<{ name: string, album_id: string }>, - attach_info: string, - has_more: boolean, - right: unknown, - banner: unknown - } - }> - getAlbumInfo(...args: unknown[]): unknown;// needs 1 arguments + getAlbumList(params: { + qun_id: string, + attach_info: string, + seq: number, + request_time_line: { + request_invoke_time: string + } + }): Promise<{ + response: { + seq: number, + result: number, + errMs: string, // 没错就是errMs不是errMsg + trace_id: string, + is_from_cache: boolean, + request_time_line: unknown, + album_list: Array<{ name: string, album_id: string }>, + attach_info: string, + has_more: boolean, + right: unknown, + banner: unknown + } + }> + getAlbumInfo(...args: unknown[]): unknown;// needs 1 arguments - deleteAlbum(...args: unknown[]): unknown;// needs 3 arguments + deleteAlbum(...args: unknown[]): unknown;// needs 3 arguments - addAlbum(...args: unknown[]): unknown;// needs 2 arguments + addAlbum(...args: unknown[]): unknown;// needs 2 arguments - deleteMedias(seq: number, group_code: string, album_id: string, media_ids: string[], ban_ids: unknown[]): Promise;// needs 4 arguments + deleteMedias(seq: number, group_code: string, album_id: string, media_ids: string[], ban_ids: unknown[]): Promise;// needs 4 arguments - modifyAlbum(...args: unknown[]): unknown;// needs 3 arguments + modifyAlbum(...args: unknown[]): unknown;// needs 3 arguments - getMediaList(param: AlbumListRequest): Promise<{ - response: { - seq: number, - result: number, - errMs: string,//没错就是errMs不是errMsg - trace_id: string, - request_time_line: unknown, - } - }>;// needs 1 arguments + getMediaList(param: AlbumListRequest): Promise<{ + response: { + seq: number, + result: number, + errMs: string, // 没错就是errMs不是errMsg + trace_id: string, + request_time_line: unknown, + } + }>;// needs 1 arguments - quoteToQzone(...args: unknown[]): unknown;// needs 1 arguments + quoteToQzone(...args: unknown[]): unknown;// needs 1 arguments - quoteToQunAlbum(...args: unknown[]): unknown;// needs 1 arguments + quoteToQunAlbum(...args: unknown[]): unknown;// needs 1 arguments - queryQuoteToQunAlbumStatus(...args: unknown[]): unknown;// needs 1 arguments + queryQuoteToQunAlbumStatus(...args: unknown[]): unknown;// needs 1 arguments - getQunFeeds(...args: unknown[]): unknown;//needs 1 arguments + getQunFeeds(...args: unknown[]): unknown;// needs 1 arguments - getQunFeedDetail(...args: unknown[]): unknown;// needs 1 arguments + getQunFeedDetail(...args: unknown[]): unknown;// needs 1 arguments - getQunNoticeList(...args: unknown[]): unknown;// needs 4 arguments + getQunNoticeList(...args: unknown[]): unknown;// needs 4 arguments - getQunComment(...args: unknown[]): unknown;// needs 1 arguments + getQunComment(...args: unknown[]): unknown;// needs 1 arguments - getQunLikes(...args: unknown[]): unknown;// needs 4 arguments + getQunLikes(...args: unknown[]): unknown;// needs 4 arguments - deleteQunFeed(...args: unknown[]): unknown;// needs 1 arguments - //seq random - //stCommonExt {"map_info":[],"map_bytes_info":[],"map_user_account":[]} - //qunId string - doQunComment(seq: number, ext: { - map_info: unknown[], - map_bytes_info: unknown[], - map_user_account: unknown[] + deleteQunFeed(...args: unknown[]): unknown;// needs 1 arguments + // seq random + // stCommonExt {"map_info":[],"map_bytes_info":[],"map_user_account":[]} + // qunId string + doQunComment(seq: number, ext: { + map_info: unknown[], + map_bytes_info: unknown[], + map_user_account: unknown[] + }, + qunId: string, + commentType: number, + feed: AlbumMediaFeed, + content: AlbumCommentReplyContent, + ): Promise;// needs 6 arguments + + doQunReply(...args: unknown[]): unknown;// needs 7 arguments + + doQunLike( + seq: number, + ext: { + map_info: unknown[], + map_bytes_info: unknown[], + map_user_account: unknown[] }, - qunId: string, - commentType: number, - feed: AlbumMediaFeed, - content: AlbumCommentReplyContent, - ): Promise;// needs 6 arguments + param: { + // {"id":"421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3e*KqaLVYdic!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560336594^||^1","status":1} + id: string, + status: number + }, + like: AlbumFeedLikePublish + ): Promise;// needs 5 arguments - doQunReply(...args: unknown[]): unknown;// needs 7 arguments - - doQunLike( - seq: number, - ext: { - map_info: unknown[], - map_bytes_info: unknown[], - map_user_account: unknown[] - }, - param: { - //{"id":"421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3e*KqaLVYdic!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560336594^||^1","status":1} - id: string, - status: number - }, - like: AlbumFeedLikePublish - ): Promise;// needs 5 arguments - - getRedPoints(...args: unknown[]): unknown;// needs 3 arguments + getRedPoints(...args: unknown[]): unknown;// needs 3 arguments } diff --git a/src/core/services/NodeIKernelAvatarService.ts b/src/core/services/NodeIKernelAvatarService.ts index 176c0aba..2375c16d 100644 --- a/src/core/services/NodeIKernelAvatarService.ts +++ b/src/core/services/NodeIKernelAvatarService.ts @@ -1,31 +1,31 @@ export interface NodeIKernelAvatarService { - addAvatarListener(listener: unknown): void; + addAvatarListener(listener: unknown): void; - removeAvatarListener(listenerId: number): void; + removeAvatarListener(listenerId: number): void; - getAvatarPath(arg1: unknown, arg2: unknown): unknown; + getAvatarPath(arg1: unknown, arg2: unknown): unknown; - forceDownloadAvatar(uid: string, useCache: number): Promise; + forceDownloadAvatar(uid: string, useCache: number): Promise; - getGroupAvatarPath(arg1: unknown, arg2: unknown): unknown; + getGroupAvatarPath(arg1: unknown, arg2: unknown): unknown; - getConfGroupAvatarPath(arg: unknown): unknown; + getConfGroupAvatarPath(arg: unknown): unknown; - forceDownloadGroupAvatar(arg1: unknown, arg2: unknown): unknown; + forceDownloadGroupAvatar(arg1: unknown, arg2: unknown): unknown; - getGroupPortraitPath(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + getGroupPortraitPath(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - forceDownloadGroupPortrait(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + forceDownloadGroupPortrait(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - getAvatarPaths(arg1: unknown, arg2: unknown): unknown; + getAvatarPaths(arg1: unknown, arg2: unknown): unknown; - getGroupAvatarPaths(arg1: unknown, arg2: unknown): unknown; + getGroupAvatarPaths(arg1: unknown, arg2: unknown): unknown; - getConfGroupAvatarPaths(arg: unknown): unknown; + getConfGroupAvatarPaths(arg: unknown): unknown; - getAvatarPathByUin(arg1: unknown, arg2: unknown): unknown; + getAvatarPathByUin(arg1: unknown, arg2: unknown): unknown; - forceDownloadAvatarByUin(arg1: unknown, arg2: unknown): unknown; + forceDownloadAvatarByUin(arg1: unknown, arg2: unknown): unknown; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelBaseEmojiService.ts b/src/core/services/NodeIKernelBaseEmojiService.ts index 2af69db6..de6c622f 100644 --- a/src/core/services/NodeIKernelBaseEmojiService.ts +++ b/src/core/services/NodeIKernelBaseEmojiService.ts @@ -2,36 +2,36 @@ import { DownloadBaseEmojiByIdReq, DownloadBaseEmojiByUrlReq, GetBaseEmojiPathRe import { GeneralCallResult } from './common'; export interface NodeIKernelBaseEmojiService { - removeKernelBaseEmojiListener(listenerId: number): void; + removeKernelBaseEmojiListener(listenerId: number): void; - addKernelBaseEmojiListener(listener: unknown): number; + addKernelBaseEmojiListener(listener: unknown): number; - isBaseEmojiPathExist(args: Array): unknown; + isBaseEmojiPathExist(args: Array): unknown; - fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): Promise, - downloadInfo: Array - }, - normalPanelResult: { - SysEmojiGroupList: Array, - downloadInfo: Array - }, - superPanelResult: { - SysEmojiGroupList: Array, - downloadInfo: Array - }, - redHeartPanelResult: { - SysEmojiGroupList: Array, - downloadInfo: Array - } - } - }>; + fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): Promise, + downloadInfo: Array + }, + normalPanelResult: { + SysEmojiGroupList: Array, + downloadInfo: Array + }, + superPanelResult: { + SysEmojiGroupList: Array, + downloadInfo: Array + }, + redHeartPanelResult: { + SysEmojiGroupList: Array, + downloadInfo: Array + } + } + }>; - getBaseEmojiPathByIds(getBaseEmojiPathReqs: Array): unknown; + getBaseEmojiPathByIds(getBaseEmojiPathReqs: Array): unknown; - downloadBaseEmojiByIdWithUrl(downloadBaseEmojiByUrlReq: DownloadBaseEmojiByUrlReq): unknown; + downloadBaseEmojiByIdWithUrl(downloadBaseEmojiByUrlReq: DownloadBaseEmojiByUrlReq): unknown; - downloadBaseEmojiById(downloadBaseEmojiByIdReq: DownloadBaseEmojiByIdReq): unknown; -} \ No newline at end of file + downloadBaseEmojiById(downloadBaseEmojiByIdReq: DownloadBaseEmojiByIdReq): unknown; +} diff --git a/src/core/services/NodeIKernelBuddyService.ts b/src/core/services/NodeIKernelBuddyService.ts index dd025c08..16303250 100644 --- a/src/core/services/NodeIKernelBuddyService.ts +++ b/src/core/services/NodeIKernelBuddyService.ts @@ -3,122 +3,122 @@ import { NodeIKernelBuddyListener } from '@/core/listeners'; import { BuddyListReqType } from '@/core/types/user'; export interface NodeIKernelBuddyService { - getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise - }> - }>; + getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise + }> + }>; - getBuddyListFromCache(reqType: BuddyListReqType): Promise//Uids + categoryId: number, // 9999为特别关心 + categorySortId: number, // 排序方式 + categroyName: string, // 分类名 + categroyMbCount: number, // 不懂 + onlineCount: number, // 在线数目 + buddyUids: Array// Uids }>>; - addKernelBuddyListener(listener: NodeIKernelBuddyListener): number; + addKernelBuddyListener(listener: NodeIKernelBuddyListener): number; - getAllBuddyCount(): number; + getAllBuddyCount(): number; - removeKernelBuddyListener(listenerId: number): void; + removeKernelBuddyListener(listenerId: number): void; - //getBuddyList(nocache: boolean): Promise; + // getBuddyList(nocache: boolean): Promise; - getBuddyNick(uid: number): string; + getBuddyNick(uid: number): string; - getBuddyRemark(uid: number): string; + getBuddyRemark(uid: number): string; - setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void; + setBuddyRemark(param: { uid: string, remark: string, signInfo?: unknown }): void; - getAvatarUrl(uid: number): string; + getAvatarUrl(uid: number): string; - isBuddy(uid: string): boolean; + isBuddy(uid: string): boolean; - getCategoryNameWithUid(uid: number): string; + getCategoryNameWithUid(uid: number): string; - getTargetBuddySetting(uid: number): unknown; + getTargetBuddySetting(uid: number): unknown; - getTargetBuddySettingByType(uid: number, type: number): unknown; + getTargetBuddySettingByType(uid: number, type: number): unknown; - getBuddyReqUnreadCnt(): number; + getBuddyReqUnreadCnt(): number; - getBuddyReq(): Promise; + getBuddyReq(): Promise; - delBuddyReq(uid: number): void; + delBuddyReq(uid: number): void; - clearBuddyReqUnreadCnt(): Promise; + clearBuddyReqUnreadCnt(): Promise; - reqToAddFriends(uid: number, msg: string): void; + reqToAddFriends(uid: number, msg: string): void; - setSpacePermission(uid: number, permission: number): void; + setSpacePermission(uid: number, permission: number): void; - approvalFriendRequest(arg: { - friendUid: string; - reqTime: string; - accept: boolean; - }): Promise; + approvalFriendRequest(arg: { + friendUid: string; + reqTime: string; + accept: boolean; + }): Promise; - delBuddy(param: { - friendUid: string; - tempBlock: boolean; - tempBothDel: boolean; - }): Promise; + delBuddy(param: { + friendUid: string; + tempBlock: boolean; + tempBothDel: boolean; + }): Promise; - delBatchBuddy(uids: number[]): void; + delBatchBuddy(uids: number[]): void; - getSmartInfos(uid: number): unknown; + getSmartInfos(uid: number): unknown; - setBuddyCategory(uid: number, category: number): void; + setBuddyCategory(uid: number, category: number): void; - setBatchBuddyCategory(uids: number[], category: number): void; + setBatchBuddyCategory(uids: number[], category: number): void; - addCategory(category: string): void; + addCategory(category: string): void; - delCategory(category: string): void; + delCategory(category: string): void; - renameCategory(oldCategory: string, newCategory: string): void; + renameCategory(oldCategory: string, newCategory: string): void; - resortCategory(categorys: string[]): void; + resortCategory(categorys: string[]): void; - pullCategory(uid: number, category: string): void; + pullCategory(uid: number, category: string): void; - setTop(uid: number, isTop: boolean): void; + setTop(uid: number, isTop: boolean): void; - SetSpecialCare(uid: number, isSpecialCare: boolean): void; + SetSpecialCare(uid: number, isSpecialCare: boolean): void; - setMsgNotify(uid: number, isNotify: boolean): void; + setMsgNotify(uid: number, isNotify: boolean): void; - hasBuddyList(): boolean; + hasBuddyList(): boolean; - setBlock(uid: number, isBlock: boolean): void; + setBlock(uid: number, isBlock: boolean): void; - isBlocked(uid: number): boolean; + isBlocked(uid: number): boolean; - modifyAddMeSetting(setting: unknown): void; + modifyAddMeSetting(setting: unknown): void; - getAddMeSetting(): unknown; + getAddMeSetting(): unknown; - getDoubtBuddyReq(reqId: string, num: number,uk:string): Promise; + getDoubtBuddyReq(reqId: string, num: number, uk:string): Promise; - getDoubtBuddyUnreadNum(): number; + getDoubtBuddyUnreadNum(): number; - approvalDoubtBuddyReq(uid: string, str1: string, str2: string): void; + approvalDoubtBuddyReq(uid: string, str1: string, str2: string): void; - delDoubtBuddyReq(uid: number): void; + delDoubtBuddyReq(uid: number): void; - delAllDoubtBuddyReq(): Promise; + delAllDoubtBuddyReq(): Promise; - reportDoubtBuddyReqUnread(): void; + reportDoubtBuddyReqUnread(): void; - getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise; + getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelCollectionService.ts b/src/core/services/NodeIKernelCollectionService.ts index 673c8ead..1b32757e 100644 --- a/src/core/services/NodeIKernelCollectionService.ts +++ b/src/core/services/NodeIKernelCollectionService.ts @@ -1,91 +1,91 @@ import { GeneralCallResult } from './common'; export interface NodeIKernelCollectionService { - addKernelCollectionListener(...args: unknown[]): void;//needs 1 arguments + addKernelCollectionListener(...args: unknown[]): void;// needs 1 arguments - removeKernelCollectionListener(listenerId: number): void; + removeKernelCollectionListener(listenerId: number): void; - getCollectionItemList(param: { - category: number, - groupId: number, - forceSync: boolean, - forceFromDb: boolean, - timeStamp: string, - count: number, - searchDown: boolean - }): Promise, - hasMore: boolean, - bottomTimeStamp: string - } + hasMore: boolean, + bottomTimeStamp: string + } } >; - getCollectionContent(...args: unknown[]): unknown;//needs 5 arguments + getCollectionContent(...args: unknown[]): unknown;// needs 5 arguments - getCollectionCustomGroupList(...args: unknown[]): unknown;//needs 0 arguments + getCollectionCustomGroupList(...args: unknown[]): unknown;// needs 0 arguments - getCollectionUserInfo(...args: unknown[]): unknown;//needs 0 arguments + getCollectionUserInfo(...args: unknown[]): unknown;// needs 0 arguments - searchCollectionItemList(...args: unknown[]): unknown;//needs 2 arguments + searchCollectionItemList(...args: unknown[]): unknown;// needs 2 arguments - addMsgToCollection(...args: unknown[]): unknown;//needs 2 arguments + addMsgToCollection(...args: unknown[]): unknown;// needs 2 arguments - collectionArkShare(...args: unknown[]): unknown;//needs 1 arguments + collectionArkShare(...args: unknown[]): unknown;// needs 1 arguments - collectionFileForward(...args: unknown[]): unknown;//needs 3 arguments + collectionFileForward(...args: unknown[]): unknown;// needs 3 arguments - downloadCollectionFile(...args: unknown[]): unknown;//needs 4 arguments + downloadCollectionFile(...args: unknown[]): unknown;// needs 4 arguments - downloadCollectionFileThumbPic(...args: unknown[]): unknown;//needs 4 arguments + downloadCollectionFileThumbPic(...args: unknown[]): unknown;// needs 4 arguments - downloadCollectionPic(...args: unknown[]): unknown;//needs 3 arguments + downloadCollectionPic(...args: unknown[]): unknown;// needs 3 arguments - cancelDownloadCollectionFile(...args: unknown[]): unknown;//needs 1 arguments + cancelDownloadCollectionFile(...args: unknown[]): unknown;// needs 1 arguments - deleteCollectionItemList(...args: unknown[]): unknown;//needs 1 arguments + deleteCollectionItemList(...args: unknown[]): unknown;// needs 1 arguments - editCollectionItem(...args: unknown[]): unknown;//needs 2 arguments + editCollectionItem(...args: unknown[]): unknown;// needs 2 arguments - getEditPicInfoByPath(...args: unknown[]): unknown;//needs 1 arguments + getEditPicInfoByPath(...args: unknown[]): unknown;// needs 1 arguments - collectionFastUpload(...args: unknown[]): unknown;//needs 1 arguments + collectionFastUpload(...args: unknown[]): unknown;// needs 1 arguments - editCollectionItemAfterFastUpload(...args: unknown[]): unknown;//needs 2 arguments + editCollectionItemAfterFastUpload(...args: unknown[]): unknown;// needs 2 arguments - createNewCollectionItem(...args: unknown[]): unknown;//needs 1 arguments + createNewCollectionItem(...args: unknown[]): unknown;// needs 1 arguments } diff --git a/src/core/services/NodeIKernelDbToolsService.ts b/src/core/services/NodeIKernelDbToolsService.ts index 8dd9bd4e..508bdc65 100644 --- a/src/core/services/NodeIKernelDbToolsService.ts +++ b/src/core/services/NodeIKernelDbToolsService.ts @@ -1,9 +1,9 @@ export interface NodeIKernelDbToolsService { - depositDatabase(...args: unknown[]): unknown; + depositDatabase(...args: unknown[]): unknown; - backupDatabase(...args: unknown[]): unknown; + backupDatabase(...args: unknown[]): unknown; + + retrieveDatabase(...args: unknown[]): unknown; - retrieveDatabase(...args: unknown[]): unknown; - } diff --git a/src/core/services/NodeIKernelECDHService.ts b/src/core/services/NodeIKernelECDHService.ts index bf8af076..7edb787e 100644 --- a/src/core/services/NodeIKernelECDHService.ts +++ b/src/core/services/NodeIKernelECDHService.ts @@ -1,3 +1,3 @@ export interface NodeIKernelECDHService { - sendOIDBECRequest: (data: Uint8Array) => Promise; + sendOIDBECRequest: (data: Uint8Array) => Promise; } diff --git a/src/core/services/NodeIKernelFileAssistantService.ts b/src/core/services/NodeIKernelFileAssistantService.ts index 8d647264..c1dedf89 100644 --- a/src/core/services/NodeIKernelFileAssistantService.ts +++ b/src/core/services/NodeIKernelFileAssistantService.ts @@ -1,37 +1,37 @@ import { NodeIKernelFileAssistantListener } from '@/core'; export interface NodeIKernelFileAssistantService { - addKernelFileAssistantListener(listener: NodeIKernelFileAssistantListener): unknown; + addKernelFileAssistantListener(listener: NodeIKernelFileAssistantListener): unknown; - removeKernelFileAssistantListener(arg1: unknown[]): unknown; + removeKernelFileAssistantListener(arg1: unknown[]): unknown; - getFileAssistantList(arg1: unknown[]): unknown; + getFileAssistantList(arg1: unknown[]): unknown; - getMoreFileAssistantList(arg1: unknown[]): unknown; + getMoreFileAssistantList(arg1: unknown[]): unknown; - getFileSessionList(): unknown; + getFileSessionList(): unknown; - searchFile(keywords: string[], params: { resultType: number, pageLimit: number }, resultId: number): number; + searchFile(keywords: string[], params: { resultType: number, pageLimit: number }, resultId: number): number; - resetSearchFileSortType(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + resetSearchFileSortType(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - searchMoreFile(arg1: unknown[]): unknown; + searchMoreFile(arg1: unknown[]): unknown; - cancelSearchFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + cancelSearchFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - downloadFile(fileIds: string[]): { result: number, errMsg: string }; + downloadFile(fileIds: string[]): { result: number, errMsg: string }; - forwardFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + forwardFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - cancelFileAction(arg1: unknown[]): unknown; + cancelFileAction(arg1: unknown[]): unknown; - retryFileAction(arg1: unknown[]): unknown; + retryFileAction(arg1: unknown[]): unknown; - deleteFile(arg1: unknown[]): unknown; + deleteFile(arg1: unknown[]): unknown; - saveAs(arg1: unknown, arg2: unknown): unknown; + saveAs(arg1: unknown, arg2: unknown): unknown; - saveAsWithRename(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + saveAsWithRename(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelGroupService.ts b/src/core/services/NodeIKernelGroupService.ts index 47be9c07..c60b762b 100644 --- a/src/core/services/NodeIKernelGroupService.ts +++ b/src/core/services/NodeIKernelGroupService.ts @@ -1,297 +1,296 @@ import { NodeIKernelGroupListener } from '@/core/listeners/NodeIKernelGroupListener'; import { - GroupExt0xEF0InfoFilter, - GroupExtParam, - GroupInfoSource, - GroupMember, - NTGroupMemberRole, - GroupNotifyMsgType, - NTGroupRequestOperateTypes, - KickMemberV2Req, - GroupDetailInfoV2Param, - GroupExtInfo, - GroupExtFilter, + GroupExt0xEF0InfoFilter, + GroupExtParam, + GroupInfoSource, + GroupMember, + NTGroupMemberRole, + GroupNotifyMsgType, + NTGroupRequestOperateTypes, + KickMemberV2Req, + GroupDetailInfoV2Param, + GroupExtInfo, + GroupExtFilter, } from '@/core/types'; import { GeneralCallResult } from '@/core/services/common'; export interface NodeIKernelGroupService { - modifyGroupExtInfoV2(groupExtInfo: GroupExtInfo, groupExtFilter: GroupExtFilter): Promise; - - // ---> - // 待启用 For Next Version 3.2.0 - // isTroopMember ? 0 : 111 - getGroupMemberMaxNum(groupCode: string, serviceType: number): Promise; - - getAllGroupPrivilegeFlag(troopUinList: string[], serviceType: number): Promise; - // <--- - getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean): - Promise } }>; - - kickMemberV2(param: KickMemberV2Req): Promise; - - quitGroupV2(param: { groupCode: string; needDeleteLocalMsg: boolean; }): Promise; - - getMemberCommonInfo(Req: { + result: { groupCode: string, - startUin: string, - identifyFlag: string, - uinList: string[], - memberCommonFilter: { - memberUin: number, - uinFlag: number, - uinFlagExt: number, - uinMobileFlag: number, - shutUpTime: number, - privilege: number, - }, - memberNum: number, - filterMethod: string, - onlineFlag: string, - realSpecialTitleFlag: number - }): Promise; - - getGroupMemberLevelInfo(groupCode: string): Promise; - - getGroupInfoForJoinGroup(groupCode: string, needPrivilegeFlag: boolean, serviceType: number): Promise; - - getGroupHonorList(req: { groupCodes: Array }): Promise; - - getUinByUids(uins: string[]): Promise<{ - errCode: number, - errMsg: string, - uins: Map + result: number + } }>; - getUidByUins(uins: string[]): Promise<{ - errCode: number, - errMsg: string, - uids: Map - }>; + // ---> + // 待启用 For Next Version 3.2.0 + // isTroopMember ? 0 : 111 + getGroupMemberMaxNum(groupCode: string, serviceType: number): Promise; - checkGroupMemberCache(arrayList: Array): Promise; + getAllGroupPrivilegeFlag(troopUinList: string[], serviceType: number): Promise; + // <--- + getGroupExt0xEF0Info(enableGroupCodes: string[], bannedGroupCodes: string[], filter: GroupExt0xEF0InfoFilter, forceFetch: boolean): + Promise } }>; - getGroupLatestEssenceList(groupCode: string): Promise; + kickMemberV2(param: KickMemberV2Req): Promise; - shareDigest(Req: { - appId: string, - appType: number, - msgStyle: number, - recvUin: string, - sendType: number, - clientInfo: { - platform: number - }, - richMsg: { - usingArk: boolean, - title: string, - summary: string, - url: string, - pictureUrl: string, - brief: string - } - }): Promise; + quitGroupV2(param: { groupCode: string; needDeleteLocalMsg: boolean; }): Promise; + getMemberCommonInfo(Req: { + groupCode: string, + startUin: string, + identifyFlag: string, + uinList: string[], + memberCommonFilter: { + memberUin: number, + uinFlag: number, + uinFlagExt: number, + uinMobileFlag: number, + shutUpTime: number, + privilege: number, + }, + memberNum: number, + filterMethod: string, + onlineFlag: string, + realSpecialTitleFlag: number + }): Promise; - isEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise; + getGroupMemberLevelInfo(groupCode: string): Promise; - queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<{ items: Array }>; + getGroupInfoForJoinGroup(groupCode: string, needPrivilegeFlag: boolean, serviceType: number): Promise; - fetchGroupEssenceList(req: { + getGroupHonorList(req: { groupCodes: Array }): Promise; + + getUinByUids(uins: string[]): Promise<{ + errCode: number, + errMsg: string, + uins: Map + }>; + + getUidByUins(uins: string[]): Promise<{ + errCode: number, + errMsg: string, + uids: Map + }>; + + checkGroupMemberCache(arrayList: Array): Promise; + + getGroupLatestEssenceList(groupCode: string): Promise; + + shareDigest(Req: { + appId: string, + appType: number, + msgStyle: number, + recvUin: string, + sendType: number, + clientInfo: { + platform: number + }, + richMsg: { + usingArk: boolean, + title: string, + summary: string, + url: string, + pictureUrl: string, + brief: string + } + }): Promise; + + isEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise; + + queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<{ items: Array }>; + + fetchGroupEssenceList(req: { + groupCode: string, + pageStart: number, + pageLimit: number + }, Arg: unknown): Promise; + + getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{ + errCode: number, + errMsg: string, + result: { + ids: Array<{ + uid: string, + index: number// 0 + }>, + infos: Map, + finish: true, + hasRobot: false + } + }>; + + setHeader(uid: string, path: string): Promise; + + addKernelGroupListener(listener: NodeIKernelGroupListener): number; + + removeKernelGroupListener(listenerId: number): void; + + createMemberListScene(groupCode: string, scene: string): string; + + destroyMemberListScene(SceneId: string): void; + + getNextMemberList(sceneId: string, groupMemberInfoListId: { index: number, uid: string } | undefined, num: number): Promise<{ + errCode: number, + errMsg: string, + result: { ids: string[], infos: Map, finish: boolean, hasRobot: boolean } + }>; + + getPrevMemberList(): unknown; + + monitorMemberList(): unknown; + + searchMember(sceneId: string, keywords: string[]): unknown; + + getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise; + + kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise; + + modifyMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void; + + modifyMemberCardName(groupCode: string, uid: string, cardName: string): void; + + getTransferableMemberInfo(groupCode: string): unknown;// 获取整个群的 + + transferGroup(uid: string): void; + + getGroupList(force: boolean): Promise; + + getGroupExtList(force: boolean): Promise; + + getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise; + + getMemberExtInfo(param: GroupExtParam): Promise;// req + + getGroupAllInfo(groupId: string, sourceId: number): Promise; + + getDiscussExistInfo(): unknown; + + getGroupConfMember(): unknown; + + getGroupMsgMask(): unknown; + + getGroupPortrait(): void; + + modifyGroupName(groupCode: string, groupName: string, isNormalMember: boolean): Promise; + + modifyGroupRemark(groupCode: string, remark: string): Promise; + + modifyGroupDetailInfo(groupCode: string, arg: unknown): void; + + // 第二个参数在大多数情况为0 设置群成员权限 例如上传群文件权限和群成员付费/加入邀请加入时为8 + modifyGroupDetailInfoV2(param: GroupDetailInfoV2Param, arg: number): Promise; + + setGroupMsgMask(groupCode: string, arg: unknown): void; + + changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void; + + inviteToGroup(arg: unknown): void; + + inviteMembersToGroup(args: unknown[]): void; + + inviteMembersToGroupWithMsg(args: unknown): void; + + createGroup(arg: unknown): void; + + createGroupWithMembers(arg: unknown): void; + + quitGroup(groupCode: string): void; + + destroyGroup(groupCode: string): void; + + getSingleScreenNotifies(doubt: boolean, startSeq: string, count: number): Promise; + + clearGroupNotifies(groupCode: string): void; + + getGroupNotifiesUnreadCount(doubt: boolean): Promise; + + clearGroupNotifiesUnreadCount(doubt: boolean): void; + + operateSysNotify( + doubt: boolean, + operateMsg: { + operateType: NTGroupRequestOperateTypes, + targetMsg: { + seq: string, + type: GroupNotifyMsgType, groupCode: string, - pageStart: number, - pageLimit: number - }, Arg: unknown): Promise; + postscript: string + } + }): Promise; - getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{ - errCode: number, - errMsg: string, - result: { - ids: Array<{ - uid: string, - index: number//0 - }>, - infos: Map, - finish: true, - hasRobot: false - } - }>; + setTop(groupCode: string, isTop: boolean): void; - setHeader(uid: string, path: string): Promise; + getGroupBulletin(groupCode: string): unknown; - addKernelGroupListener(listener: NodeIKernelGroupListener): number; + deleteGroupBulletin(groupCode: string, seq: string, noticeId: string): void; - removeKernelGroupListener(listenerId: number): void; + publishGroupBulletin(groupCode: string, pskey: string, data: unknown): Promise; - createMemberListScene(groupCode: string, scene: string): string; + publishInstructionForNewcomers(groupCode: string, arg: unknown): void; - destroyMemberListScene(SceneId: string): void; + uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise; - getNextMemberList(sceneId: string, groupMemberInfoListId: { index: number, uid: string } | undefined, num: number): Promise<{ - errCode: number, - errMsg: string, - result: { ids: string[], infos: Map, finish: boolean, hasRobot: boolean } - }>; + downloadGroupBulletinRichMedia(groupCode: string): unknown; - getPrevMemberList(): unknown; + getGroupBulletinList(groupCode: string): unknown; - monitorMemberList(): unknown; + getGroupStatisticInfo(groupCode: string): unknown; - searchMember(sceneId: string, keywords: string[]): unknown; + getGroupRemainAtTimes(groupCode: string): Promise & { + errCode: number, + atInfo: { + canAtAll: boolean + RemainAtAllCountForUin: number + RemainAtAllCountForGroup: number + atTimesMsg: string + canNotAtAllMsg: '' + } + }>; - getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise; + getJoinGroupNoVerifyFlag(groupCode: string): unknown; - kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise; + getGroupArkInviteState(groupCode: string): unknown; - modifyMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void; + reqToJoinGroup(groupCode: string, arg: unknown): void; - modifyMemberCardName(groupCode: string, uid: string, cardName: string): void; + setGroupShutUp(groupCode: string, shutUp: boolean): Promise; - getTransferableMemberInfo(groupCode: string): unknown;//获取整个群的 + getGroupShutUpMemberList(groupCode: string): Promise; - transferGroup(uid: string): void; + setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise; - getGroupList(force: boolean): Promise; + getGroupRecommendContactArkJson(groupCode: string): Promise; - getGroupExtList(force: boolean): Promise; + getJoinGroupLink(param: { + groupCode: string, + srcId: number, // 73 + needShortUrl: boolean, // true + additionalParam: string// '' + }): Promise; - getGroupDetailInfo(groupCode: string, groupInfoSource: GroupInfoSource): Promise; + modifyGroupExtInfo(groupCode: string, arg: unknown): void; - getMemberExtInfo(param: GroupExtParam): Promise;//req + addGroupEssence(param: { + groupCode: string + msgRandom: number, + msgSeq: number + }): Promise; - getGroupAllInfo(groupId: string, sourceId: number): Promise; + removeGroupEssence(param: { + groupCode: string + msgRandom: number, + msgSeq: number + }): Promise; - getDiscussExistInfo(): unknown; - - getGroupConfMember(): unknown; - - getGroupMsgMask(): unknown; - - getGroupPortrait(): void; - - modifyGroupName(groupCode: string, groupName: string, isNormalMember: boolean): Promise; - - modifyGroupRemark(groupCode: string, remark: string): Promise; - - modifyGroupDetailInfo(groupCode: string, arg: unknown): void; - - // 第二个参数在大多数情况为0 设置群成员权限 例如上传群文件权限和群成员付费/加入邀请加入时为8 - modifyGroupDetailInfoV2(param: GroupDetailInfoV2Param, arg: number): Promise; - - setGroupMsgMask(groupCode: string, arg: unknown): void; - - changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void; - - inviteToGroup(arg: unknown): void; - - inviteMembersToGroup(args: unknown[]): void; - - inviteMembersToGroupWithMsg(args: unknown): void; - - createGroup(arg: unknown): void; - - createGroupWithMembers(arg: unknown): void; - - quitGroup(groupCode: string): void; - - destroyGroup(groupCode: string): void; - - getSingleScreenNotifies(doubt: boolean, startSeq: string, count: number): Promise; - - clearGroupNotifies(groupCode: string): void; - - getGroupNotifiesUnreadCount(doubt: boolean): Promise; - - clearGroupNotifiesUnreadCount(doubt: boolean): void; - - operateSysNotify( - doubt: boolean, - operateMsg: { - operateType: NTGroupRequestOperateTypes, - targetMsg: { - seq: string, - type: GroupNotifyMsgType, - groupCode: string, - postscript: string - } - }): Promise; - - setTop(groupCode: string, isTop: boolean): void; - - getGroupBulletin(groupCode: string): unknown; - - deleteGroupBulletin(groupCode: string, seq: string, noticeId: string): void; - - publishGroupBulletin(groupCode: string, pskey: string, data: unknown): Promise; - - publishInstructionForNewcomers(groupCode: string, arg: unknown): void; - - uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise; - - downloadGroupBulletinRichMedia(groupCode: string): unknown; - - getGroupBulletinList(groupCode: string): unknown; - - getGroupStatisticInfo(groupCode: string): unknown; - - getGroupRemainAtTimes(groupCode: string): Promise & { - errCode: number, - atInfo: { - canAtAll: boolean - RemainAtAllCountForUin: number - RemainAtAllCountForGroup: number - atTimesMsg: string - canNotAtAllMsg: '' - } - }>; - - getJoinGroupNoVerifyFlag(groupCode: string): unknown; - - getGroupArkInviteState(groupCode: string): unknown; - - reqToJoinGroup(groupCode: string, arg: unknown): void; - - setGroupShutUp(groupCode: string, shutUp: boolean): Promise; - - getGroupShutUpMemberList(groupCode: string): Promise; - - setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise; - - getGroupRecommendContactArkJson(groupCode: string): Promise; - - getJoinGroupLink(param: { - groupCode: string, - srcId: number,//73 - needShortUrl: boolean,//true - additionalParam: string//'' - }): Promise; - - modifyGroupExtInfo(groupCode: string, arg: unknown): void; - - addGroupEssence(param: { - groupCode: string - msgRandom: number, - msgSeq: number - }): Promise; - - removeGroupEssence(param: { - groupCode: string - msgRandom: number, - msgSeq: number - }): Promise; - - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelLoginService.ts b/src/core/services/NodeIKernelLoginService.ts index c3f7602c..44082f48 100644 --- a/src/core/services/NodeIKernelLoginService.ts +++ b/src/core/services/NodeIKernelLoginService.ts @@ -2,90 +2,90 @@ import { NodeIKernelLoginListener } from '@/core/listeners/NodeIKernelLoginListe import { GeneralCallResult } from './common'; export interface LoginInitConfig { - machineId: ''; - appid: string; - platVer: string; - commonPath: string; - clientVer: string; - hostName: string; + machineId: ''; + appid: string; + platVer: string; + commonPath: string; + clientVer: string; + hostName: string; } export interface PasswordLoginRetType { - result: string, - loginErrorInfo: { - step: number; - errMsg: string; - proofWaterUrl: string; - newDevicePullQrCodeSig: string; - jumpUrl: string, - jumpWord: string; - tipsTitle: string; - tipsContent: string; - } + result: string, + loginErrorInfo: { + step: number; + errMsg: string; + proofWaterUrl: string; + newDevicePullQrCodeSig: string; + jumpUrl: string, + jumpWord: string; + tipsTitle: string; + tipsContent: string; + } } export interface PasswordLoginArgType { - uin: string; - passwordMd5: string;//passwMD5 - step: number;//猜测是需要二次认证 参数 一次为0 - newDeviceLoginSig: string; - proofWaterSig: string; - proofWaterRand: string; - proofWaterSid: string; + uin: string; + passwordMd5: string;// passwMD5 + step: number;// 猜测是需要二次认证 参数 一次为0 + newDeviceLoginSig: string; + proofWaterSig: string; + proofWaterRand: string; + proofWaterSid: string; } export interface LoginListItem { - uin: string; - uid: string; - nickName: string; - faceUrl: string; - facePath: string; - loginType: 1; // 1是二维码登录? - isQuickLogin: boolean; // 是否可以快速登录 - isAutoLogin: boolean; // 是否可以自动登录 + uin: string; + uid: string; + nickName: string; + faceUrl: string; + facePath: string; + loginType: 1; // 1是二维码登录? + isQuickLogin: boolean; // 是否可以快速登录 + isAutoLogin: boolean; // 是否可以自动登录 } export interface QuickLoginResult { - result: string; - loginErrorInfo: { - step: number, - errMsg: string, - proofWaterUrl: string, - newDevicePullQrCodeSig: string, - jumpUrl: string, - jumpWord: string, - tipsTitle: string, - tipsContent: string - }; + result: string; + loginErrorInfo: { + step: number, + errMsg: string, + proofWaterUrl: string, + newDevicePullQrCodeSig: string, + jumpUrl: string, + jumpWord: string, + tipsTitle: string, + tipsContent: string + }; } export interface NodeIKernelLoginService { - getMsfStatus: () => number; + getMsfStatus: () => number; - setLoginMiscData(arg0: string, value: string): unknown; - - getMachineGuid(): string; + setLoginMiscData(arg0: string, value: string): unknown; - get(): NodeIKernelLoginService; + getMachineGuid(): string; - connect(): boolean; + get(): NodeIKernelLoginService; - addKernelLoginListener(listener: NodeIKernelLoginListener): number; + connect(): boolean; - removeKernelLoginListener(listener: number): void; + addKernelLoginListener(listener: NodeIKernelLoginListener): number; - initConfig(config: LoginInitConfig): void; + removeKernelLoginListener(listener: number): void; - getLoginMiscData(data: string): Promise; + initConfig(config: LoginInitConfig): void; - getLoginList(): Promise<{ - result: number, // 0是ok - LocalLoginInfoList: LoginListItem[] - }>; + getLoginMiscData(data: string): Promise; - quickLoginWithUin(uin: string): Promise; + getLoginList(): Promise<{ + result: number, // 0是ok + LocalLoginInfoList: LoginListItem[] + }>; - passwordLogin(param: PasswordLoginArgType): Promise; + quickLoginWithUin(uin: string): Promise; - getQRCodePicture(): boolean; + passwordLogin(param: PasswordLoginArgType): Promise; + + getQRCodePicture(): boolean; } diff --git a/src/core/services/NodeIKernelMSFService.ts b/src/core/services/NodeIKernelMSFService.ts index 9a9db7dc..ac1f0424 100644 --- a/src/core/services/NodeIKernelMSFService.ts +++ b/src/core/services/NodeIKernelMSFService.ts @@ -1,43 +1,43 @@ import { GeneralCallResult } from './common'; enum ProxyType { - CLOSE = 0, - HTTP = 1, - SOCKET = 2 + CLOSE = 0, + HTTP = 1, + SOCKET = 2, } export interface NodeIKernelMSFService { - getServerTime(): string; - setNetworkProxy(param: { - userName: string, - userPwd: string, - address: string, - port: number, - proxyType: ProxyType, - domain: string, - isSocket: boolean - }): Promise; - getNetworkProxy(): Promise<{ - userName: string, - userPwd: string, - address: string, - port: number, - proxyType: ProxyType, - domain: string, - isSocket: boolean - }>; - //http - // userName: '', - // userPwd: '', - // address: '127.0.0.1', - // port: 5666, - // proxyType: 1, - // domain: '', - // isSocket: false - //socket - // userName: '', - // userPwd: '', - // address: '127.0.0.1', - // port: 5667, - // proxyType: 2, - // domain: '', - // isSocket: true -} \ No newline at end of file + getServerTime(): string; + setNetworkProxy(param: { + userName: string, + userPwd: string, + address: string, + port: number, + proxyType: ProxyType, + domain: string, + isSocket: boolean + }): Promise; + getNetworkProxy(): Promise<{ + userName: string, + userPwd: string, + address: string, + port: number, + proxyType: ProxyType, + domain: string, + isSocket: boolean + }>; + // http + // userName: '', + // userPwd: '', + // address: '127.0.0.1', + // port: 5666, + // proxyType: 1, + // domain: '', + // isSocket: false + // socket + // userName: '', + // userPwd: '', + // address: '127.0.0.1', + // port: 5667, + // proxyType: 2, + // domain: '', + // isSocket: true +} diff --git a/src/core/services/NodeIKernelMsgBackupService.ts b/src/core/services/NodeIKernelMsgBackupService.ts index c0364e20..064377aa 100644 --- a/src/core/services/NodeIKernelMsgBackupService.ts +++ b/src/core/services/NodeIKernelMsgBackupService.ts @@ -1,27 +1,27 @@ export interface NodeIKernelMsgBackupService { - addKernelMsgBackupListener(listener: unknown): number; + addKernelMsgBackupListener(listener: unknown): number; - removeKernelMsgBackupListener(listenerId: number): void; + removeKernelMsgBackupListener(listenerId: number): void; - getMsgBackupLocation(...args: unknown[]): unknown;// needs 0 arguments + getMsgBackupLocation(...args: unknown[]): unknown;// needs 0 arguments - setMsgBackupLocation(...args: unknown[]): unknown;// needs 1 arguments + setMsgBackupLocation(...args: unknown[]): unknown;// needs 1 arguments - requestMsgBackup(...args: unknown[]): unknown;// needs 0 arguments + requestMsgBackup(...args: unknown[]): unknown;// needs 0 arguments - requestMsgRestore(...args: unknown[]): unknown;// needs 1 arguments + requestMsgRestore(...args: unknown[]): unknown;// needs 1 arguments - requestMsgMigrate(...args: unknown[]): unknown;// needs 1 arguments + requestMsgMigrate(...args: unknown[]): unknown;// needs 1 arguments - getLocalStorageBackup(...args: unknown[]): unknown;// needs 0 arguments + getLocalStorageBackup(...args: unknown[]): unknown;// needs 0 arguments - deleteLocalBackup(...args: unknown[]): unknown;// needs 1 arguments + deleteLocalBackup(...args: unknown[]): unknown;// needs 1 arguments - clearCache(...args: unknown[]): unknown;// needs 0 arguments + clearCache(...args: unknown[]): unknown;// needs 0 arguments - start(...args: unknown[]): unknown;// needs 1 arguments + start(...args: unknown[]): unknown;// needs 1 arguments - stop(...args: unknown[]): unknown;// needs 1 arguments + stop(...args: unknown[]): unknown;// needs 1 arguments - pause(...args: unknown[]): unknown;// needs 2 arguments + pause(...args: unknown[]): unknown;// needs 2 arguments } diff --git a/src/core/services/NodeIKernelMsgService.ts b/src/core/services/NodeIKernelMsgService.ts index 4d3c6a7c..e65cfa83 100644 --- a/src/core/services/NodeIKernelMsgService.ts +++ b/src/core/services/NodeIKernelMsgService.ts @@ -4,703 +4,703 @@ import { GeneralCallResult } from '@/core/services/common'; import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '@/core/types/msg'; export interface NodeIKernelMsgService { - buildMultiForwardMsg(req: { srcMsgIds: Array, srcContact: Peer }): Promise; + buildMultiForwardMsg(req: { srcMsgIds: Array, srcContact: Peer }): Promise; - generateMsgUniqueId(chatType: number, time: string): string; + generateMsgUniqueId(chatType: number, time: string): string; - addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number; + addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number; - sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map): Promise; + sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map): Promise; - recallMsg(peer: Peer, msgIds: string[]): Promise; + recallMsg(peer: Peer, msgIds: string[]): Promise; - addKernelMsgImportToolListener(arg: unknown): unknown; + addKernelMsgImportToolListener(arg: unknown): unknown; - removeKernelMsgListener(args: unknown): unknown; + removeKernelMsgListener(args: unknown): unknown; - addKernelTempChatSigListener(...args: unknown[]): unknown; + addKernelTempChatSigListener(...args: unknown[]): unknown; - removeKernelTempChatSigListener(...args: unknown[]): unknown; + removeKernelTempChatSigListener(...args: unknown[]): unknown; - setAutoReplyTextList(AutoReplyText: Array, i2: number): unknown; + setAutoReplyTextList(AutoReplyText: Array, i2: number): unknown; - getAutoReplyTextList(...args: unknown[]): unknown; + getAutoReplyTextList(...args: unknown[]): unknown; - getOnLineDev(): void; + getOnLineDev(): void; - kickOffLine(DevInfo: unknown): unknown; + kickOffLine(DevInfo: unknown): unknown; - setStatus(args: { status: number, extStatus: number, batteryStatus: number, customStatus?: { faceId: string, wording: string, faceType: string } }): Promise; + setStatus(args: { status: number, extStatus: number, batteryStatus: number, customStatus?: { faceId: string, wording: string, faceType: string } }): Promise; - fetchStatusMgrInfo(): unknown; + fetchStatusMgrInfo(): unknown; - fetchStatusUnitedConfigInfo(): unknown; + fetchStatusUnitedConfigInfo(): unknown; - getOnlineStatusSmallIconBasePath(): unknown; + getOnlineStatusSmallIconBasePath(): unknown; - getOnlineStatusSmallIconFileNameByUrl(Url: string): unknown; + getOnlineStatusSmallIconFileNameByUrl(Url: string): unknown; - downloadOnlineStatusSmallIconByUrl(arg0: number, arg1: string): unknown; + downloadOnlineStatusSmallIconByUrl(arg0: number, arg1: string): unknown; - getOnlineStatusBigIconBasePath(): unknown; + getOnlineStatusBigIconBasePath(): unknown; - downloadOnlineStatusBigIconByUrl(arg0: number, arg1: string): unknown; + downloadOnlineStatusBigIconByUrl(arg0: number, arg1: string): unknown; - getOnlineStatusCommonPath(arg: string): unknown; + getOnlineStatusCommonPath(arg: string): unknown; - getOnlineStatusCommonFileNameByUrl(Url: string): unknown; + getOnlineStatusCommonFileNameByUrl(Url: string): unknown; - downloadOnlineStatusCommonByUrl(arg0: string, arg1: string): unknown; + downloadOnlineStatusCommonByUrl(arg0: string, arg1: string): unknown; - setToken(arg: unknown): unknown; + setToken(arg: unknown): unknown; - switchForeGround(): unknown; + switchForeGround(): unknown; - switchBackGround(arg: unknown): unknown; + switchBackGround(arg: unknown): unknown; - setTokenForMqq(token: string): unknown; + setTokenForMqq(token: string): unknown; - switchForeGroundForMqq(...args: unknown[]): unknown; + switchForeGroundForMqq(...args: unknown[]): unknown; - switchBackGroundForMqq(...args: unknown[]): unknown; + switchBackGroundForMqq(...args: unknown[]): unknown; - getMsgSetting(...args: unknown[]): unknown; + getMsgSetting(...args: unknown[]): unknown; - setMsgSetting(...args: unknown[]): unknown; + setMsgSetting(...args: unknown[]): unknown; - addSendMsg(...args: unknown[]): unknown; + addSendMsg(...args: unknown[]): unknown; - cancelSendMsg(...args: unknown[]): unknown; + cancelSendMsg(...args: unknown[]): unknown; - switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown; + switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown; - reqToOfflineSendMsg(...args: unknown[]): unknown; + reqToOfflineSendMsg(...args: unknown[]): unknown; - refuseReceiveOnlineFileMsg(peer: Peer, MsgId: string): unknown; + refuseReceiveOnlineFileMsg(peer: Peer, MsgId: string): unknown; - resendMsg(...args: unknown[]): unknown; + resendMsg(...args: unknown[]): unknown; - recallMsg(...args: unknown[]): unknown; + recallMsg(...args: unknown[]): unknown; - reeditRecallMsg(...args: unknown[]): unknown; + reeditRecallMsg(...args: unknown[]): unknown; - forwardMsg(...args: unknown[]): Promise; + forwardMsg(...args: unknown[]): Promise; - forwardMsgWithComment(...args: unknown[]): unknown; + forwardMsgWithComment(...args: unknown[]): unknown; - forwardSubMsgWithComment(...args: unknown[]): unknown; + forwardSubMsgWithComment(...args: unknown[]): unknown; - forwardRichMsgInVist(...args: unknown[]): unknown; + forwardRichMsgInVist(...args: unknown[]): unknown; - forwardFile(...args: unknown[]): unknown; + forwardFile(...args: unknown[]): unknown; - multiForwardMsg(...args: unknown[]): unknown; + multiForwardMsg(...args: unknown[]): unknown; - multiForwardMsgWithComment(...args: unknown[]): unknown; + multiForwardMsgWithComment(...args: unknown[]): unknown; - deleteRecallMsg(...args: unknown[]): unknown; + deleteRecallMsg(...args: unknown[]): unknown; - deleteRecallMsgForLocal(...args: unknown[]): unknown; + deleteRecallMsgForLocal(...args: unknown[]): unknown; - addLocalGrayTipMsg(...args: unknown[]): unknown; + addLocalGrayTipMsg(...args: unknown[]): unknown; - addLocalJsonGrayTipMsg(...args: unknown[]): unknown; + addLocalJsonGrayTipMsg(...args: unknown[]): unknown; - addLocalJsonGrayTipMsgExt(...args: unknown[]): unknown; + addLocalJsonGrayTipMsgExt(...args: unknown[]): unknown; - IsLocalJsonTipValid(...args: unknown[]): unknown; + IsLocalJsonTipValid(...args: unknown[]): unknown; - addLocalAVRecordMsg(...args: unknown[]): unknown; + addLocalAVRecordMsg(...args: unknown[]): unknown; - addLocalTofuRecordMsg(...args: unknown[]): unknown; + addLocalTofuRecordMsg(...args: unknown[]): unknown; - addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array | number, front: boolean): Promise; + addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array | number, front: boolean): Promise; - deleteMsg(Peer: Peer, msgIds: Array): Promise; + deleteMsg(Peer: Peer, msgIds: Array): Promise; - updateElementExtBufForUI(...args: unknown[]): unknown; + updateElementExtBufForUI(...args: unknown[]): unknown; - updateMsgRecordExtPbBufForUI(...args: unknown[]): unknown; + updateMsgRecordExtPbBufForUI(...args: unknown[]): unknown; - startMsgSync(...args: unknown[]): unknown; + startMsgSync(...args: unknown[]): unknown; - startGuildMsgSync(...args: unknown[]): unknown; + startGuildMsgSync(...args: unknown[]): unknown; - isGuildChannelSync(...args: unknown[]): unknown; + isGuildChannelSync(...args: unknown[]): unknown; - getMsgUniqueId(UniqueId: string): string; + getMsgUniqueId(UniqueId: string): string; - isMsgMatched(...args: unknown[]): unknown; + isMsgMatched(...args: unknown[]): unknown; - getOnlineFileMsgs(...args: unknown[]): unknown; + getOnlineFileMsgs(...args: unknown[]): unknown; - getAllOnlineFileMsgs(...args: unknown[]): unknown; + getAllOnlineFileMsgs(...args: unknown[]): unknown; - getLatestDbMsgs(peer: Peer, cnt: number): Promise; + getLatestDbMsgs(peer: Peer, cnt: number): Promise; - getLastMessageList(peer: Peer[]): Promise; + getLastMessageList(peer: Peer[]): Promise; - getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise; + getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise; - // getMsgService/getMsgs { chatType: 2, peerUid: '975206796', privilegeFlag: 336068800 } 0 20 true - getMsgs(peer: Peer & { privilegeFlag: number }, msgId: string, count: number, queryOrder: boolean): Promise; + // getMsgService/getMsgs { chatType: 2, peerUid: '975206796', privilegeFlag: 336068800 } 0 20 true + getMsgs(peer: Peer & { privilegeFlag: number }, msgId: string, count: number, queryOrder: boolean): Promise; - getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise; + getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise; - //@deprecated - getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise; + // @deprecated + getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise; - getMsgsWithStatus(params: { - peer: Peer - msgId: string - msgTime: unknown - cnt: unknown - queryOrder: boolean - isIncludeSelf: boolean - appid: unknown - }): Promise; + getMsgsWithStatus(params: { + peer: Peer + msgId: string + msgTime: unknown + cnt: unknown + queryOrder: boolean + isIncludeSelf: boolean + appid: unknown + }): Promise; - getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise; - //@deprecated - getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean): Promise; + getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise; + // @deprecated + getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, isReverseOrder: boolean): Promise; - getMsgsByMsgId(peer: Peer, ids: string[]): Promise; + getMsgsByMsgId(peer: Peer, ids: string[]): Promise; - getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise; + getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise; - getMsgsBySeqList(peer: Peer, seqList: string[]): Promise; + getMsgsBySeqList(peer: Peer, seqList: string[]): Promise; - getMsgsExt(msgReq: MsgReqType): Promise; + getMsgsExt(msgReq: MsgReqType): Promise; - getSingleMsg(Peer: Peer, msgSeq: string): Promise; + getSingleMsg(Peer: Peer, msgSeq: string): Promise; - // 下面的msgid全部不真实 - getSourceOfReplyMsg(peer: Peer, msgId: string, sourceSeq: string): Promise; + // 下面的msgid全部不真实 + getSourceOfReplyMsg(peer: Peer, msgId: string, sourceSeq: string): Promise; - //用法和聊天记录一样 - getSourceOfReplyMsgV2(peer: Peer, rootMsgId: string, replyMsgId: string): Promise; + // 用法和聊天记录一样 + getSourceOfReplyMsgV2(peer: Peer, rootMsgId: string, replyMsgId: string): Promise; - getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): Promise; + getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): Promise; - getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string, replyMsgId: string): Promise; + getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string, replyMsgId: string): Promise; - getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { - type: number, - subtype: Array - }): Promise; + getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { + type: number, + subtype: Array + }): Promise; - getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ - type: number, - subtype: Array - }>): Promise; + getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ + type: number, + subtype: Array + }>): Promise; - getMsgWithAbstractByFilterParam(...args: unknown[]): Promise; + getMsgWithAbstractByFilterParam(...args: unknown[]): Promise; - queryMsgsWithFilter(...args: unknown[]): Promise; + queryMsgsWithFilter(...args: unknown[]): Promise; - //queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise; + // queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise; - queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise; + queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise; - queryFileMsgsDesktop(msgId: string, msgTime: string, msgSeq: string, param: QueryMsgsParams): Promise; + queryFileMsgsDesktop(msgId: string, msgTime: string, msgSeq: string, param: QueryMsgsParams): Promise; - setMsgRichInfoFlag(...args: unknown[]): unknown; + setMsgRichInfoFlag(...args: unknown[]): unknown; - queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise; + queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise; - queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown; + queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown; - queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise; + queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise; - queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise; + queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise; - queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown; + queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown; - setFocusOnGuild(...args: unknown[]): unknown; + setFocusOnGuild(...args: unknown[]): unknown; - setFocusSession(...args: unknown[]): unknown; + setFocusSession(...args: unknown[]): unknown; - enableFilterUnreadInfoNotify(...args: unknown[]): unknown; + enableFilterUnreadInfoNotify(...args: unknown[]): unknown; - enableFilterMsgAbstractNotify(...args: unknown[]): unknown; + enableFilterMsgAbstractNotify(...args: unknown[]): unknown; - onScenesChangeForSilenceMode(...args: unknown[]): unknown; + onScenesChangeForSilenceMode(...args: unknown[]): unknown; - getContactUnreadCnt(...args: unknown[]): unknown; + getContactUnreadCnt(...args: unknown[]): unknown; - getUnreadCntInfo(...args: unknown[]): unknown; + getUnreadCntInfo(...args: unknown[]): unknown; - getGuildUnreadCntInfo(...args: unknown[]): unknown; + getGuildUnreadCntInfo(...args: unknown[]): unknown; - getGuildUnreadCntTabInfo(...args: unknown[]): unknown; + getGuildUnreadCntTabInfo(...args: unknown[]): unknown; - getAllGuildUnreadCntInfo(...args: unknown[]): unknown; + getAllGuildUnreadCntInfo(...args: unknown[]): unknown; - getAllJoinGuildCnt(...args: unknown[]): unknown; + getAllJoinGuildCnt(...args: unknown[]): unknown; - getAllDirectSessionUnreadCntInfo(...args: unknown[]): unknown; + getAllDirectSessionUnreadCntInfo(...args: unknown[]): unknown; - getCategoryUnreadCntInfo(...args: unknown[]): unknown; + getCategoryUnreadCntInfo(...args: unknown[]): unknown; - getGuildFeedsUnreadCntInfo(...args: unknown[]): unknown; + getGuildFeedsUnreadCntInfo(...args: unknown[]): unknown; - setUnVisibleChannelCntInfo(...args: unknown[]): unknown; + setUnVisibleChannelCntInfo(...args: unknown[]): unknown; - setUnVisibleChannelTypeCntInfo(...args: unknown[]): unknown; + setUnVisibleChannelTypeCntInfo(...args: unknown[]): unknown; - setVisibleGuildCntInfo(...args: unknown[]): unknown; + setVisibleGuildCntInfo(...args: unknown[]): unknown; - setMsgRead(peer: Peer): Promise; + setMsgRead(peer: Peer): Promise; - setAllC2CAndGroupMsgRead(): Promise; + setAllC2CAndGroupMsgRead(): Promise; - setGuildMsgRead(...args: unknown[]): unknown; + setGuildMsgRead(...args: unknown[]): unknown; - setAllGuildMsgRead(...args: unknown[]): unknown; + setAllGuildMsgRead(...args: unknown[]): unknown; - setMsgReadAndReport(...args: unknown[]): unknown; + setMsgReadAndReport(...args: unknown[]): unknown; - setSpecificMsgReadAndReport(...args: unknown[]): unknown; + setSpecificMsgReadAndReport(...args: unknown[]): unknown; - setLocalMsgRead(...args: unknown[]): unknown; + setLocalMsgRead(...args: unknown[]): unknown; - setGroupGuildMsgRead(...args: unknown[]): unknown; + setGroupGuildMsgRead(...args: unknown[]): unknown; - getGuildGroupTransData(...args: unknown[]): unknown; + getGuildGroupTransData(...args: unknown[]): unknown; - setGroupGuildBubbleRead(...args: unknown[]): unknown; + setGroupGuildBubbleRead(...args: unknown[]): unknown; - getGuildGroupBubble(...args: unknown[]): unknown; + getGuildGroupBubble(...args: unknown[]): unknown; - fetchGroupGuildUnread(...args: unknown[]): unknown; + fetchGroupGuildUnread(...args: unknown[]): unknown; - setGroupGuildFlag(...args: unknown[]): unknown; + setGroupGuildFlag(...args: unknown[]): unknown; - setGuildUDCFlag(...args: unknown[]): unknown; + setGuildUDCFlag(...args: unknown[]): unknown; - setGuildTabUserFlag(...args: unknown[]): unknown; + setGuildTabUserFlag(...args: unknown[]): unknown; - setBuildMode(flag: number/*0 1 3*/): unknown; + setBuildMode(flag: number/* 0 1 3 */): unknown; - setConfigurationServiceData(...args: unknown[]): unknown; + setConfigurationServiceData(...args: unknown[]): unknown; - setMarkUnreadFlag(...args: unknown[]): unknown; + setMarkUnreadFlag(...args: unknown[]): unknown; - getChannelEventFlow(...args: unknown[]): unknown; + getChannelEventFlow(...args: unknown[]): unknown; - getMsgEventFlow(...args: unknown[]): unknown; + getMsgEventFlow(...args: unknown[]): unknown; - getRichMediaFilePathForMobileQQSend(...args: unknown[]): unknown; + getRichMediaFilePathForMobileQQSend(...args: unknown[]): unknown; - getRichMediaFilePathForGuild(arg: { - md5HexStr: string, - fileName: string, - elementType: ElementType, - elementSubType: number, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: '' - }): string; + getRichMediaFilePathForGuild(arg: { + md5HexStr: string, + fileName: string, + elementType: ElementType, + elementSubType: number, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '' + }): string; - assembleMobileQQRichMediaFilePath(...args: unknown[]): unknown; + assembleMobileQQRichMediaFilePath(...args: unknown[]): unknown; - getFileThumbSavePathForSend(...args: unknown[]): unknown; + getFileThumbSavePathForSend(...args: unknown[]): unknown; - getFileThumbSavePath(...args: unknown[]): unknown; + getFileThumbSavePath(...args: unknown[]): unknown; - translatePtt2Text(msgId: string, peer: Peer, msgElement: MessageElement): unknown; + translatePtt2Text(msgId: string, peer: Peer, msgElement: MessageElement): unknown; - setPttPlayedState(...args: unknown[]): unknown; + setPttPlayedState(...args: unknown[]): unknown; - fetchFavEmojiList(str: string, num: number, backward: boolean, forceRefresh: boolean): Promise - }>; + fetchFavEmojiList(str: string, num: number, backward: boolean, forceRefresh: boolean): Promise + }>; - addFavEmoji(...args: unknown[]): unknown; + addFavEmoji(...args: unknown[]): unknown; - fetchMarketEmoticonList(...args: unknown[]): unknown; + fetchMarketEmoticonList(...args: unknown[]): unknown; - fetchMarketEmoticonShowImage(...args: unknown[]): unknown; + fetchMarketEmoticonShowImage(...args: unknown[]): unknown; - fetchMarketEmoticonAioImage(...args: unknown[]): unknown; + fetchMarketEmoticonAioImage(...args: unknown[]): unknown; - fetchMarketEmotionJsonFile(...args: unknown[]): unknown; + fetchMarketEmotionJsonFile(...args: unknown[]): unknown; - getMarketEmoticonPath(...args: unknown[]): unknown; + getMarketEmoticonPath(...args: unknown[]): unknown; - getMarketEmoticonPathBySync(...args: unknown[]): unknown; + getMarketEmoticonPathBySync(...args: unknown[]): unknown; - fetchMarketEmoticonFaceImages(...args: unknown[]): unknown; + fetchMarketEmoticonFaceImages(...args: unknown[]): unknown; - fetchMarketEmoticonAuthDetail(...args: unknown[]): unknown; + fetchMarketEmoticonAuthDetail(...args: unknown[]): unknown; - getFavMarketEmoticonInfo(...args: unknown[]): unknown; + getFavMarketEmoticonInfo(...args: unknown[]): unknown; - addRecentUsedFace(...args: unknown[]): unknown; + addRecentUsedFace(...args: unknown[]): unknown; - getRecentUsedFaceList(...args: unknown[]): unknown; + getRecentUsedFaceList(...args: unknown[]): unknown; - getMarketEmoticonEncryptKeys(...args: unknown[]): unknown; + getMarketEmoticonEncryptKeys(...args: unknown[]): unknown; - downloadEmojiPic(...args: unknown[]): unknown; + downloadEmojiPic(...args: unknown[]): unknown; - deleteFavEmoji(...args: unknown[]): unknown; + deleteFavEmoji(...args: unknown[]): unknown; - modifyFavEmojiDesc(...args: unknown[]): unknown; + modifyFavEmojiDesc(...args: unknown[]): unknown; - queryFavEmojiByDesc(...args: unknown[]): unknown; + queryFavEmojiByDesc(...args: unknown[]): unknown; - getHotPicInfoListSearchString(...args: unknown[]): unknown; + getHotPicInfoListSearchString(...args: unknown[]): unknown; - getHotPicSearchResult(...args: unknown[]): unknown; + getHotPicSearchResult(...args: unknown[]): unknown; - getHotPicHotWords(...args: unknown[]): unknown; + getHotPicHotWords(...args: unknown[]): unknown; - getHotPicJumpInfo(...args: unknown[]): unknown; + getHotPicJumpInfo(...args: unknown[]): unknown; - getEmojiResourcePath(...args: unknown[]): unknown; + getEmojiResourcePath(...args: unknown[]): unknown; - JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: { - latestMsgSeq: string, - manageEmojiId: number, - manageMsgSeq: string, - peerContact: Peer - }): Promise; + JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: { + latestMsgSeq: string, + manageEmojiId: number, + manageMsgSeq: string, + peerContact: Peer + }): Promise; - getMsgAbstracts(...args: unknown[]): unknown; + getMsgAbstracts(...args: unknown[]): unknown; - getMsgAbstract(...args: unknown[]): unknown; + getMsgAbstract(...args: unknown[]): unknown; - getMsgAbstractList(...args: unknown[]): unknown; + getMsgAbstractList(...args: unknown[]): unknown; - getMsgAbstractListBySeqRange(...args: unknown[]): unknown; + getMsgAbstractListBySeqRange(...args: unknown[]): unknown; - refreshMsgAbstracts(...args: unknown[]): unknown; + refreshMsgAbstracts(...args: unknown[]): unknown; - refreshMsgAbstractsByGuildIds(...args: unknown[]): unknown; + refreshMsgAbstractsByGuildIds(...args: unknown[]): unknown; - getRichMediaElement(...args: unknown[]): unknown; + getRichMediaElement(...args: unknown[]): unknown; - cancelGetRichMediaElement(...args: unknown[]): unknown; + cancelGetRichMediaElement(...args: unknown[]): unknown; - refuseGetRichMediaElement(...args: unknown[]): unknown; + refuseGetRichMediaElement(...args: unknown[]): unknown; - switchToOfflineGetRichMediaElement(...args: unknown[]): unknown; + switchToOfflineGetRichMediaElement(...args: unknown[]): unknown; - downloadRichMedia(args: { - fileModelId: string, - downSourceType: number, - triggerType: number, - msgId: string, - chatType: number, - peerUid: string, - elementId: string, - thumbSize: number, - downloadType: number, - filePath: string - } & { - downloadSourceType: number, //33800左右一下的老版本 新版34606已经完全上面格式 + downloadRichMedia(args: { + fileModelId: string, + downSourceType: number, + triggerType: number, + msgId: string, + chatType: number, + peerUid: string, + elementId: string, + thumbSize: number, + downloadType: number, + filePath: string + } & { + downloadSourceType: number, // 33800左右一下的老版本 新版34606已经完全上面格式 + }): unknown; + + getFirstUnreadMsgSeq(args: { + peerUid: string + guildId: string + }): Promise; + + getFirstUnreadCommonMsg(...args: unknown[]): unknown; + + getFirstUnreadAtmeMsg(...args: unknown[]): unknown; + + getFirstUnreadAtallMsg(...args: unknown[]): unknown; + + getNavigateInfo(...args: unknown[]): unknown; + + getChannelFreqLimitInfo(...args: unknown[]): unknown; + + getRecentUseEmojiList(...args: unknown[]): unknown; + + getRecentEmojiList(...args: unknown[]): unknown; + + setMsgEmojiLikes(...args: unknown[]): unknown; + + getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ + result: number, + errMsg: string, + emojiLikesList: + Array<{ + tinyId: string, + nickName: string, + headUrl: string + }>, + cookie: string, + isLastPage: boolean, + isFirstPage: boolean + }>; + + setMsgEmojiLikesForRole(...args: unknown[]): unknown; + + clickInlineKeyboardButton(params: { + guildId?: string, + peerId: string, + botAppid: string, + msgSeq: string, + buttonId: string, + callback_data: string, + dmFlag: number, + chatType: number // 1私聊 2群 + }): Promise; + + setCurOnScreenMsg(...args: unknown[]): unknown; + + setCurOnScreenMsgForMsgEvent(peer: Peer, msgRegList: Map): void; + + getMiscData(key: string): unknown; + + setMiscData(key: string, value: string): unknown; + + getBookmarkData(...args: unknown[]): unknown; + + setBookmarkData(...args: unknown[]): unknown; + + sendShowInputStatusReq(ChatType: number, EventType: number, toUid: string): Promise; + + queryCalendar(...args: unknown[]): unknown; + + queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown; + + queryRoamCalendar(...args: unknown[]): unknown; + + queryFirstRoamMsg(...args: unknown[]): unknown; + + fetchLongMsg(peer: Peer, msgId: string): unknown; + + fetchLongMsgWithCb(...args: unknown[]): unknown; + + setIsStopKernelFetchLongMsg(...args: unknown[]): unknown; + + insertGameResultAsMsgToDb(...args: unknown[]): unknown; + + getMultiMsg(...args: unknown[]): Promise; + + setDraft(...args: unknown[]): unknown; + + getDraft(...args: unknown[]): unknown; + + deleteDraft(...args: unknown[]): unknown; + + getRecentHiddenSesionList(...args: unknown[]): unknown; + + setRecentHiddenSession(...args: unknown[]): unknown; + + delRecentHiddenSession(...args: unknown[]): unknown; + + getCurHiddenSession(...args: unknown[]): unknown; + + setCurHiddenSession(...args: unknown[]): unknown; + + setReplyDraft(...args: unknown[]): unknown; + + getReplyDraft(...args: unknown[]): unknown; + + deleteReplyDraft(...args: unknown[]): unknown; + + getFirstUnreadAtMsg(peer: Peer): unknown; + + clearMsgRecords(...args: unknown[]): unknown; + + IsExistOldDb(...args: unknown[]): unknown; + + canImportOldDbMsg(...args: unknown[]): unknown; + + setPowerStatus(isPowerOn: boolean): unknown; + + canProcessDataMigration(...args: unknown[]): unknown; + + importOldDbMsg(...args: unknown[]): unknown; + + stopImportOldDbMsgAndroid(...args: unknown[]): unknown; + + isMqqDataImportFinished(...args: unknown[]): unknown; + + getMqqDataImportTableNames(...args: unknown[]): unknown; + + getCurChatImportStatusByUin(...args: unknown[]): unknown; + + getDataImportUserLevel(): unknown; + + getMsgQRCode(...args: unknown[]): unknown; + + getGuestMsgAbstracts(...args: unknown[]): unknown; + + getGuestMsgByRange(...args: unknown[]): unknown; + + getGuestMsgAbstractByRange(...args: unknown[]): unknown; + + registerSysMsgNotification(...args: unknown[]): unknown; + + unregisterSysMsgNotification(...args: unknown[]): unknown; + + enterOrExitAio(...args: unknown[]): unknown; + + prepareTempChat(args: unknown): unknown; + + sendSsoCmdReqByContend(cmd: string, param: unknown): Promise; + + getTempChatInfo(ChatType: number, Uid: string): Promise; + + setContactLocalTop(...args: unknown[]): unknown; + + switchAnonymousChat(...args: unknown[]): unknown; + + renameAnonyChatNick(...args: unknown[]): unknown; + + getAnonymousInfo(...args: unknown[]): unknown; + + updateAnonymousInfo(...args: unknown[]): unknown; + + sendSummonMsg(peer: Peer, MsgElement: unknown, MsgAttributeInfo: unknown): Promise;// 频道的东西 + + outputGuildUnreadInfo(...args: unknown[]): unknown; + + checkMsgWithUrl(...args: unknown[]): unknown; + + checkTabListStatus(...args: unknown[]): unknown; + + getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown; + + insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown; + + isHitEmojiKeyword(...args: unknown[]): unknown; + + getKeyWordRelatedEmoji(...args: unknown[]): unknown; + + recordEmoji(...args: unknown[]): unknown; + + fetchGetHitEmotionsByWord(args: unknown): Promise;// 表情推荐? + + deleteAllRoamMsgs(...args: unknown[]): unknown;// 漫游消息? + + packRedBag(...args: unknown[]): unknown; + + grabRedBag(...args: unknown[]): unknown; + + pullDetail(...args: unknown[]): unknown; + + selectPasswordRedBag(...args: unknown[]): unknown; + + pullRedBagPasswordList(...args: unknown[]): unknown; + + requestTianshuAdv(...args: unknown[]): unknown; + + tianshuReport(...args: unknown[]): unknown; + + tianshuMultiReport(...args: unknown[]): unknown; + + GetMsgSubType(a0: number, a1: number): unknown; + + setIKernelPublicAccountAdapter(...args: unknown[]): unknown; + + // tempChatGameSession有关 + createUidFromTinyId(fromTinyId: string, toTinyId: string): string; + + dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown; + + dataMigrationGetMsgList(...args: unknown[]): unknown; + + dataMigrationStopOperation(...args: unknown[]): unknown; + + dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{ + extensionData: string// "Hex" + extraData: string // "" + chatType: number + chatUin: string + msgType: number + msgTime: string + msgSeq: string + msgRandom: string + }>, DataMigrationResourceInfo: { + extraData: string + filePath: string + fileSize: string + msgRandom: string + msgSeq: string + msgSubType: number + msgType: number }): unknown; - getFirstUnreadMsgSeq(args: { - peerUid: string - guildId: string - }): Promise; + dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown; - getFirstUnreadCommonMsg(...args: unknown[]): unknown; + dataMigrationSetIOSPathPrefix(...args: unknown[]): unknown; - getFirstUnreadAtmeMsg(...args: unknown[]): unknown; + getServiceAssistantSwitch(...args: unknown[]): unknown; - getFirstUnreadAtallMsg(...args: unknown[]): unknown; + setServiceAssistantSwitch(...args: unknown[]): unknown; - getNavigateInfo(...args: unknown[]): unknown; + setSubscribeFolderUsingSmallRedPoint(...args: unknown[]): unknown; - getChannelFreqLimitInfo(...args: unknown[]): unknown; + clearGuildNoticeRedPoint(...args: unknown[]): unknown; - getRecentUseEmojiList(...args: unknown[]): unknown; + clearFeedNoticeRedPoint(...args: unknown[]): unknown; - getRecentEmojiList(...args: unknown[]): unknown; + clearFeedSquareRead(...args: unknown[]): unknown; - setMsgEmojiLikes(...args: unknown[]): unknown; + IsC2CStyleChatType(...args: unknown[]): unknown; - getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ - result: number, - errMsg: string, - emojiLikesList: - Array<{ - tinyId: string, - nickName: string, - headUrl: string - }>, - cookie: string, - isLastPage: boolean, - isFirstPage: boolean - }>; + IsTempChatType(uin: number): unknown;// 猜的 - setMsgEmojiLikesForRole(...args: unknown[]): unknown; + getGuildInteractiveNotification(...args: unknown[]): unknown; - clickInlineKeyboardButton(params: { - guildId?: string, - peerId: string, - botAppid: string, - msgSeq: string, - buttonId: string, - callback_data: string, - dmFlag: number, - chatType: number // 1私聊 2群 - }): Promise; + getGuildNotificationAbstract(...args: unknown[]): unknown; - setCurOnScreenMsg(...args: unknown[]): unknown; + setFocusOnBase(...args: unknown[]): unknown; - setCurOnScreenMsgForMsgEvent(peer: Peer, msgRegList: Map): void; + queryArkInfo(...args: unknown[]): unknown; - getMiscData(key: string): unknown; + queryUserSecQuality(...args: unknown[]): unknown; - setMiscData(key: string, value: string): unknown; + getGuildMsgAbFlag(...args: unknown[]): unknown; - getBookmarkData(...args: unknown[]): unknown; - - setBookmarkData(...args: unknown[]): unknown; - - sendShowInputStatusReq(ChatType: number, EventType: number, toUid: string): Promise; - - queryCalendar(...args: unknown[]): unknown; - - queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown; - - queryRoamCalendar(...args: unknown[]): unknown; - - queryFirstRoamMsg(...args: unknown[]): unknown; - - fetchLongMsg(peer: Peer, msgId: string): unknown; - - fetchLongMsgWithCb(...args: unknown[]): unknown; - - setIsStopKernelFetchLongMsg(...args: unknown[]): unknown; - - insertGameResultAsMsgToDb(...args: unknown[]): unknown; - - getMultiMsg(...args: unknown[]): Promise; - - setDraft(...args: unknown[]): unknown; - - getDraft(...args: unknown[]): unknown; - - deleteDraft(...args: unknown[]): unknown; - - getRecentHiddenSesionList(...args: unknown[]): unknown; - - setRecentHiddenSession(...args: unknown[]): unknown; - - delRecentHiddenSession(...args: unknown[]): unknown; - - getCurHiddenSession(...args: unknown[]): unknown; - - setCurHiddenSession(...args: unknown[]): unknown; - - setReplyDraft(...args: unknown[]): unknown; - - getReplyDraft(...args: unknown[]): unknown; - - deleteReplyDraft(...args: unknown[]): unknown; - - getFirstUnreadAtMsg(peer: Peer): unknown; - - clearMsgRecords(...args: unknown[]): unknown; - - IsExistOldDb(...args: unknown[]): unknown; - - canImportOldDbMsg(...args: unknown[]): unknown; - - setPowerStatus(isPowerOn: boolean): unknown; - - canProcessDataMigration(...args: unknown[]): unknown; - - importOldDbMsg(...args: unknown[]): unknown; - - stopImportOldDbMsgAndroid(...args: unknown[]): unknown; - - isMqqDataImportFinished(...args: unknown[]): unknown; - - getMqqDataImportTableNames(...args: unknown[]): unknown; - - getCurChatImportStatusByUin(...args: unknown[]): unknown; - - getDataImportUserLevel(): unknown; - - getMsgQRCode(...args: unknown[]): unknown; - - getGuestMsgAbstracts(...args: unknown[]): unknown; - - getGuestMsgByRange(...args: unknown[]): unknown; - - getGuestMsgAbstractByRange(...args: unknown[]): unknown; - - registerSysMsgNotification(...args: unknown[]): unknown; - - unregisterSysMsgNotification(...args: unknown[]): unknown; - - enterOrExitAio(...args: unknown[]): unknown; - - prepareTempChat(args: unknown): unknown; - - sendSsoCmdReqByContend(cmd: string, param: unknown): Promise; - - getTempChatInfo(ChatType: number, Uid: string): Promise; - - setContactLocalTop(...args: unknown[]): unknown; - - switchAnonymousChat(...args: unknown[]): unknown; - - renameAnonyChatNick(...args: unknown[]): unknown; - - getAnonymousInfo(...args: unknown[]): unknown; - - updateAnonymousInfo(...args: unknown[]): unknown; - - sendSummonMsg(peer: Peer, MsgElement: unknown, MsgAttributeInfo: unknown): Promise;//频道的东西 - - outputGuildUnreadInfo(...args: unknown[]): unknown; - - checkMsgWithUrl(...args: unknown[]): unknown; - - checkTabListStatus(...args: unknown[]): unknown; - - getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown; - - insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown; - - isHitEmojiKeyword(...args: unknown[]): unknown; - - getKeyWordRelatedEmoji(...args: unknown[]): unknown; - - recordEmoji(...args: unknown[]): unknown; - - fetchGetHitEmotionsByWord(args: unknown): Promise;//表情推荐? - - deleteAllRoamMsgs(...args: unknown[]): unknown;//漫游消息? - - packRedBag(...args: unknown[]): unknown; - - grabRedBag(...args: unknown[]): unknown; - - pullDetail(...args: unknown[]): unknown; - - selectPasswordRedBag(...args: unknown[]): unknown; - - pullRedBagPasswordList(...args: unknown[]): unknown; - - requestTianshuAdv(...args: unknown[]): unknown; - - tianshuReport(...args: unknown[]): unknown; - - tianshuMultiReport(...args: unknown[]): unknown; - - GetMsgSubType(a0: number, a1: number): unknown; - - setIKernelPublicAccountAdapter(...args: unknown[]): unknown; - - //tempChatGameSession有关 - createUidFromTinyId(fromTinyId: string, toTinyId: string): string; - - dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown; - - dataMigrationGetMsgList(...args: unknown[]): unknown; - - dataMigrationStopOperation(...args: unknown[]): unknown; - - dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{ - extensionData: string//"Hex" - extraData: string //"" - chatType: number - chatUin: string - msgType: number - msgTime: string - msgSeq: string - msgRandom: string - }>, DataMigrationResourceInfo: { - extraData: string - filePath: string - fileSize: string - msgRandom: string - msgSeq: string - msgSubType: number - msgType: number - }): unknown; - - dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown; - - dataMigrationSetIOSPathPrefix(...args: unknown[]): unknown; - - getServiceAssistantSwitch(...args: unknown[]): unknown; - - setServiceAssistantSwitch(...args: unknown[]): unknown; - - setSubscribeFolderUsingSmallRedPoint(...args: unknown[]): unknown; - - clearGuildNoticeRedPoint(...args: unknown[]): unknown; - - clearFeedNoticeRedPoint(...args: unknown[]): unknown; - - clearFeedSquareRead(...args: unknown[]): unknown; - - IsC2CStyleChatType(...args: unknown[]): unknown; - - IsTempChatType(uin: number): unknown;//猜的 - - getGuildInteractiveNotification(...args: unknown[]): unknown; - - getGuildNotificationAbstract(...args: unknown[]): unknown; - - setFocusOnBase(...args: unknown[]): unknown; - - queryArkInfo(...args: unknown[]): unknown; - - queryUserSecQuality(...args: unknown[]): unknown; - - getGuildMsgAbFlag(...args: unknown[]): unknown; - - getGroupMsgStorageTime(): unknown; + getGroupMsgStorageTime(): unknown; } diff --git a/src/core/services/NodeIKernelNodeMiscService.ts b/src/core/services/NodeIKernelNodeMiscService.ts index 13cb3ae2..d6d4f77d 100644 --- a/src/core/services/NodeIKernelNodeMiscService.ts +++ b/src/core/services/NodeIKernelNodeMiscService.ts @@ -1,15 +1,15 @@ import { GeneralCallResult } from './common'; export interface NodeIKernelNodeMiscService { - writeVersionToRegistry(version: string): void; + writeVersionToRegistry(version: string): void; - getMiniAppPath(): unknown; + getMiniAppPath(): unknown; - setMiniAppVersion(version: string): unknown; + setMiniAppVersion(version: string): unknown; - wantWinScreenOCR(imagepath: string): Promise; + wantWinScreenOCR(imagepath: string): Promise; - SendMiniAppMsg(arg1: string, arg2: string, arg3: string): unknown; + SendMiniAppMsg(arg1: string, arg2: string, arg3: string): unknown; - startNewMiniApp(appfile: string, params: string): unknown; + startNewMiniApp(appfile: string, params: string): unknown; } diff --git a/src/core/services/NodeIKernelOnlineStatusService.ts b/src/core/services/NodeIKernelOnlineStatusService.ts index 39124b78..9a6b04f2 100644 --- a/src/core/services/NodeIKernelOnlineStatusService.ts +++ b/src/core/services/NodeIKernelOnlineStatusService.ts @@ -1,36 +1,36 @@ export interface NodeIKernelOnlineStatusService { - addKernelOnlineStatusListener(listener: unknown): number; + addKernelOnlineStatusListener(listener: unknown): number; - removeKernelOnlineStatusListener(listenerId: number): void; + removeKernelOnlineStatusListener(listenerId: number): void; - getShouldShowAIOStatusAnimation(arg: unknown): unknown; + getShouldShowAIOStatusAnimation(arg: unknown): unknown; - setReadLikeList(arg: unknown): unknown; + setReadLikeList(arg: unknown): unknown; - getLikeList(arg: unknown): unknown; + getLikeList(arg: unknown): unknown; - setLikeStatus(arg: unknown): unknown; + setLikeStatus(arg: unknown): unknown; - getAggregationPageEntrance(): unknown; + getAggregationPageEntrance(): unknown; - didClickAggregationPageEntrance(): unknown; + didClickAggregationPageEntrance(): unknown; - getAggregationGroupModels(): unknown; + getAggregationGroupModels(): unknown; - // { - // "businessType": 1, - // "uins": [ - // "1627126029", - // "66600000", - // "71702575" - // ] - // } + // { + // "businessType": 1, + // "uins": [ + // "1627126029", + // "66600000", + // "71702575" + // ] + // } - checkLikeStatus(param: { - businessType: number, - uins: string[] - }): Promise; + checkLikeStatus(param: { + businessType: number, + uins: string[] + }): Promise; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelProfileLikeService.ts b/src/core/services/NodeIKernelProfileLikeService.ts index 8e692255..8535faef 100644 --- a/src/core/services/NodeIKernelProfileLikeService.ts +++ b/src/core/services/NodeIKernelProfileLikeService.ts @@ -1,37 +1,37 @@ import { BuddyProfileLikeReq, GeneralCallResult, NTVoteInfo } from '@/core'; export interface NodeIKernelProfileLikeService { - addKernelProfileLikeListener(listener: unknown): number; + addKernelProfileLikeListener(listener: unknown): number; - removeKernelProfileLikeListener(listenerId: unknown): void; + removeKernelProfileLikeListener(listenerId: unknown): void; - setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number }; + setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number }; - getBuddyProfileLike(req: BuddyProfileLikeReq): Promise,//哪些人点我 - total_count: number, - last_time: number, - today_count: number - }, - voteInfo: { - total_count: number, - new_count: number, - new_nearby_count: number, - last_visit_time: number, - userInfos: Array,//点过哪些人 - } - }>, - friendMaxVotes: number, - start: number + getBuddyProfileLike(req: BuddyProfileLikeReq): Promise, // 哪些人点我 + total_count: number, + last_time: number, + today_count: number + }, + voteInfo: { + total_count: number, + new_count: number, + new_nearby_count: number, + last_visit_time: number, + userInfos: Array, // 点过哪些人 } - }>; + }>, + friendMaxVotes: number, + start: number + } + }>; - getProfileLikeScidResourceInfo(...args: unknown[]): void; + getProfileLikeScidResourceInfo(...args: unknown[]): void; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelProfileService.ts b/src/core/services/NodeIKernelProfileService.ts index 8b07773a..e1c8cb97 100644 --- a/src/core/services/NodeIKernelProfileService.ts +++ b/src/core/services/NodeIKernelProfileService.ts @@ -3,80 +3,80 @@ import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType import { GeneralCallResult } from '@/core/services/common'; export interface NodeIKernelProfileService { - getOtherFlag(callfrom: string, uids: string[]): Promise>; + getOtherFlag(callfrom: string, uids: string[]): Promise>; - getVasInfo(callfrom: string, uids: string[]): Promise>; + getVasInfo(callfrom: string, uids: string[]): Promise>; - getRelationFlag(callfrom: string, uids: string[]): Promise>; + getRelationFlag(callfrom: string, uids: string[]): Promise>; - getUidByUin(callfrom: string, uin: Array): Map; + getUidByUin(callfrom: string, uin: Array): Map; - getUinByUid(callfrom: string, uid: Array): Map; + getUinByUid(callfrom: string, uid: Array): Map; - getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise>; + getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise>; - fetchUserDetailInfo(trace: string, uids: string[], source: UserDetailSource, bizType: ProfileBizType[]): Promise detail - detail: Map, + source: UserDetailSource, + // uid -> detail + detail: Map, } >; - addKernelProfileListener(listener: NodeIKernelProfileListener): number; + addKernelProfileListener(listener: NodeIKernelProfileListener): number; - removeKernelProfileListener(listenerId: number): void; + removeKernelProfileListener(listenerId: number): void; - prepareRegionConfig(...args: unknown[]): unknown; + prepareRegionConfig(...args: unknown[]): unknown; - getLocalStrangerRemark(): Promise; + getLocalStrangerRemark(): Promise; - enumCountryOptions(): Array; + enumCountryOptions(): Array; - enumProvinceOptions(country: string): Array; + enumProvinceOptions(country: string): Array; - enumCityOptions(country: string, province: string): unknown; + enumCityOptions(country: string, province: string): unknown; - enumAreaOptions(...args: unknown[]): unknown; + enumAreaOptions(...args: unknown[]): unknown; - modifySelfProfile(...args: unknown[]): Promise; + modifySelfProfile(...args: unknown[]): Promise; - modifyDesktopMiniProfile(param: ModifyProfileParams): Promise; + modifyDesktopMiniProfile(param: ModifyProfileParams): Promise; - setNickName(nickName: string): Promise; + setNickName(nickName: string): Promise; - setLongNick(longNick: string): Promise; + setLongNick(longNick: string): Promise; - setBirthday(...args: unknown[]): Promise; + setBirthday(...args: unknown[]): Promise; - setGander(...args: unknown[]): Promise; + setGander(...args: unknown[]): Promise; - setHeader(arg: string): Promise; + setHeader(arg: string): Promise; - setRecommendImgFlag(...args: unknown[]): Promise; + setRecommendImgFlag(...args: unknown[]): Promise; - getUserSimpleInfo(force: boolean, uids: string[]): Promise; + getUserSimpleInfo(force: boolean, uids: string[]): Promise; - getUserDetailInfo(uid: string): Promise; + getUserDetailInfo(uid: string): Promise; - getUserDetailInfoWithBizInfo(uid: string, Biz: BizKey[]): Promise; + getUserDetailInfoWithBizInfo(uid: string, Biz: BizKey[]): Promise; - getUserDetailInfoByUin(uin: string): Promise; + getUserDetailInfoByUin(uin: string): Promise; - getZplanAvatarInfos(args: string[]): Promise; + getZplanAvatarInfos(args: string[]): Promise; - getStatus(uid: string): Promise; + getStatus(uid: string): Promise; - startStatusPolling(isForceReset: boolean): Promise; + startStatusPolling(isForceReset: boolean): Promise; - getSelfStatus(): Promise; + getSelfStatus(): Promise; - setdisableEmojiShortCuts(...args: unknown[]): unknown; + setdisableEmojiShortCuts(...args: unknown[]): unknown; - getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise; + getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise; - // UserRemarkServiceImpl::getStrangerRemarkByUid [] - getCoreInfo(sceneId: string, arg: unknown[]): unknown; + // UserRemarkServiceImpl::getStrangerRemarkByUid [] + getCoreInfo(sceneId: string, arg: unknown[]): unknown; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelRecentContactService.ts b/src/core/services/NodeIKernelRecentContactService.ts index 6c793d81..65238c3e 100644 --- a/src/core/services/NodeIKernelRecentContactService.ts +++ b/src/core/services/NodeIKernelRecentContactService.ts @@ -4,76 +4,80 @@ import { GeneralCallResult } from '@/core/services/common'; import { FSABRecentContactParams } from '@/core/types/contact'; export interface NodeIKernelRecentContactService { - setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments + setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments - setContactListTop(...args: unknown[]): unknown; // 2 arguments + setContactListTop(...args: unknown[]): unknown; // 2 arguments - updateRecentContactExtBufForUI(...args: unknown[]): unknown; // 2 arguments + updateRecentContactExtBufForUI(...args: unknown[]): unknown; // 2 arguments - upsertRecentContactManually(...args: unknown[]): unknown; // 1 arguments + upsertRecentContactManually(...args: unknown[]): unknown; // 1 arguments - enterOrExitMsgList(...args: unknown[]): unknown; // 1 arguments + enterOrExitMsgList(...args: unknown[]): unknown; // 1 arguments - getRecentContactListSnapShot(count: number): Promise, - changedList: Array<{ - remark: unknown; - peerName: unknown; - sendMemberName: unknown; - sendNickName: unknown; - peerUid: string; peerUin: string, msgTime: string, chatType: ChatType, msgId: string - }> - } - }>; // 1 arguments + getRecentContactListSnapShot(count: number): Promise, + changedList: Array<{ + remark: unknown; + peerName: unknown; + sendMemberName: unknown; + sendNickName: unknown; + peerUid: string; + peerUin: string, + msgTime: string, + chatType: ChatType, + msgId: string + }> + } + }>; // 1 arguments - clearMsgUnreadCount(...args: unknown[]): unknown; // 1 arguments + clearMsgUnreadCount(...args: unknown[]): unknown; // 1 arguments - getRecentContactListSyncLimit(count: number): unknown; + getRecentContactListSyncLimit(count: number): unknown; - jumpToSpecifyRecentContact(...args: unknown[]): unknown; // 1 arguments + jumpToSpecifyRecentContact(...args: unknown[]): unknown; // 1 arguments - fetchAndSubscribeABatchOfRecentContact(params: FSABRecentContactParams): unknown; // 1 arguments + fetchAndSubscribeABatchOfRecentContact(params: FSABRecentContactParams): unknown; // 1 arguments - addRecentContact(peer: Peer): unknown; + addRecentContact(peer: Peer): unknown; - deleteRecentContacts(peer: Peer): unknown; // 猜测 + deleteRecentContacts(peer: Peer): unknown; // 猜测 - getContacts(peers: Peer[]): Promise; + getContacts(peers: Peer[]): Promise; - setThirdPartyBusinessInfos(...args: unknown[]): unknown; // 1 arguments + setThirdPartyBusinessInfos(...args: unknown[]): unknown; // 1 arguments - updateGameMsgConfigs(...args: unknown[]): unknown; // 1 arguments + updateGameMsgConfigs(...args: unknown[]): unknown; // 1 arguments - removeKernelRecentContactListener(listenerid: number): unknown; // 1 arguments + removeKernelRecentContactListener(listenerid: number): unknown; // 1 arguments - addKernelRecentContactListener(listener: NodeIKernelRecentContactListener): void; + addKernelRecentContactListener(listener: NodeIKernelRecentContactListener): void; - clearRecentContactsByChatType(...args: unknown[]): unknown; // 1 arguments + clearRecentContactsByChatType(...args: unknown[]): unknown; // 1 arguments - upInsertModule(...args: unknown[]): unknown; // 1 arguments + upInsertModule(...args: unknown[]): unknown; // 1 arguments - jumpToSpecifyRecentContactVer2(...args: unknown[]): unknown; // 1 arguments + jumpToSpecifyRecentContactVer2(...args: unknown[]): unknown; // 1 arguments - deleteRecentContactsVer2(...args: unknown[]): unknown; // 1 arguments + deleteRecentContactsVer2(...args: unknown[]): unknown; // 1 arguments - getRecentContactList(): Promise; + getRecentContactList(): Promise; - getMsgUnreadCount(): unknown; + getMsgUnreadCount(): unknown; - clearRecentContacts(): unknown; + clearRecentContacts(): unknown; - getServiceAssistantRecentContactInfos(): unknown; + getServiceAssistantRecentContactInfos(): unknown; - getRecentContactInfos(): unknown; + getRecentContactInfos(): unknown; - getUnreadDetailsInfos(): unknown; + getUnreadDetailsInfos(): unknown; - cleanAllModule(): unknown; + cleanAllModule(): unknown; - setAllGameMsgRead(): unknown; + setAllGameMsgRead(): unknown; - getRecentContactListSync(): unknown; + getRecentContactListSync(): unknown; } diff --git a/src/core/services/NodeIKernelRichMediaService.ts b/src/core/services/NodeIKernelRichMediaService.ts index 85c267f3..f0d404d3 100644 --- a/src/core/services/NodeIKernelRichMediaService.ts +++ b/src/core/services/NodeIKernelRichMediaService.ts @@ -2,283 +2,283 @@ import { GetFileListParam, MessageElement, Peer } from '@/core/types'; import { GeneralCallResult } from './common'; export enum UrlFileDownloadType { - KUNKNOWN, - KURLFILEDOWNLOADPRIVILEGEICON, - KURLFILEDOWNLOADPHOTOWALL, - KURLFILEDOWNLOADQZONE, - KURLFILEDOWNLOADCOMMON, - KURLFILEDOWNLOADINSTALLAPP + KUNKNOWN, + KURLFILEDOWNLOADPRIVILEGEICON, + KURLFILEDOWNLOADPHOTOWALL, + KURLFILEDOWNLOADQZONE, + KURLFILEDOWNLOADCOMMON, + KURLFILEDOWNLOADINSTALLAPP, } export enum RMBizTypeEnum { - KUNKNOWN, - KC2CFILE, - KGROUPFILE, - KC2CPIC, - KGROUPPIC, - KDISCPIC, - KC2CVIDEO, - KGROUPVIDEO, - KC2CPTT, - KGROUPPTT, - KFEEDCOMMENTPIC, - KGUILDFILE, - KGUILDPIC, - KGUILDPTT, - KGUILDVIDEO + KUNKNOWN, + KC2CFILE, + KGROUPFILE, + KC2CPIC, + KGROUPPIC, + KDISCPIC, + KC2CVIDEO, + KGROUPVIDEO, + KC2CPTT, + KGROUPPTT, + KFEEDCOMMENTPIC, + KGUILDFILE, + KGUILDPIC, + KGUILDPTT, + KGUILDVIDEO, } export interface CommonFileInfo { - bizType: number; - chatType: number; - elemId: string; - favId: string; - fileModelId: string; - fileName: string; - fileSize: string; - md5: string; - md510m: string; - msgId: string; - msgTime: string; - parent: string; - peerUid: string; - picThumbPath: Array; - sha: string; - sha3: string; - subId: string; - uuid: string; + bizType: number; + chatType: number; + elemId: string; + favId: string; + fileModelId: string; + fileName: string; + fileSize: string; + md5: string; + md510m: string; + msgId: string; + msgTime: string; + parent: string; + peerUid: string; + picThumbPath: Array; + sha: string; + sha3: string; + subId: string; + uuid: string; } export interface NodeIKernelRichMediaService { - //getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb); - // public enum VideoCodecFormatType { - // KCODECFORMATH264, - // KCODECFORMATH265, - // KCODECFORMATH266, - // KCODECFORMATAV1 - // } - // public enum VideoRequestWay { - // KUNKNOW, - // KHAND, - // KAUTO - // } - getVideoPlayUrl(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, VideoRequestWay: number): Promise; + // getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb); + // public enum VideoCodecFormatType { + // KCODECFORMATH264, + // KCODECFORMATH265, + // KCODECFORMATH266, + // KCODECFORMATAV1 + // } + // public enum VideoRequestWay { + // KUNKNOW, + // KHAND, + // KAUTO + // } + getVideoPlayUrl(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, VideoRequestWay: number): Promise; - //exParams (RMReqExParams) - // this.downSourceType = i2; - // this.triggerType = i3; - //peer, msgId, elemId, videoCodecFormat, exParams - // 1 0 频道在用 - // 1 1 - // 0 2 + // exParams (RMReqExParams) + // this.downSourceType = i2; + // this.triggerType = i3; + // peer, msgId, elemId, videoCodecFormat, exParams + // 1 0 频道在用 + // 1 1 + // 0 2 - // public static final int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007; - // public static final int KDOWNSOURCETYPEAIOINNER = 1; - // public static final int KDOWNSOURCETYPEBIGSCREEN = 2; - // public static final int KDOWNSOURCETYPEHISTORY = 3; - // public static final int KDOWNSOURCETYPEUNKNOWN = 0; + // public static final int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007; + // public static final int KDOWNSOURCETYPEAIOINNER = 1; + // public static final int KDOWNSOURCETYPEBIGSCREEN = 2; + // public static final int KDOWNSOURCETYPEHISTORY = 3; + // public static final int KDOWNSOURCETYPEUNKNOWN = 0; - // public static final int KTRIGGERTYPEAUTO = 1; - // public static final int KTRIGGERTYPEMANUAL = 0; + // public static final int KTRIGGERTYPEAUTO = 1; + // public static final int KTRIGGERTYPEMANUAL = 0; - getVideoPlayUrlV2(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, exParams: { - downSourceType: number, - triggerType: number - }): Promise, - videoCodecFormat: number - } - }>; + getVideoPlayUrlV2(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, exParams: { + downSourceType: number, + triggerType: number + }): Promise, + videoCodecFormat: number + } + }>; - getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown; + getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown; - getVideoPlayUrlInVisit(arg: { - downloadType: number, - thumbSize: number, - msgId: string, - msgRandom: string, - msgSeq: string, - msgTime: string, - chatType: number, - senderUid: string, - peerUid: string, - guildId: string, - ele: MessageElement, - useHttps: boolean + getVideoPlayUrlInVisit(arg: { + downloadType: number, + thumbSize: number, + msgId: string, + msgRandom: string, + msgSeq: string, + msgTime: string, + chatType: number, + senderUid: string, + peerUid: string, + guildId: string, + ele: MessageElement, + useHttps: boolean + }): Promise; + + isFileExpired(arg: number): unknown; + + deleteGroupFolder(GroupCode: string, FolderId: string): Promise; + + // 参数与getVideoPlayUrlInVisit一样 + downloadRichMediaInVisit(arg: { + downloadType: number, + thumbSize: number, + msgId: string, + msgRandom: string, + msgSeq: string, + msgTime: string, + chatType: number, + senderUid: string, + peerUid: string, + guildId: string, + ele: MessageElement, + useHttps: boolean + }): unknown; + + downloadFileForModelId(peer: Peer, ModelId: string[], unknown: string): Promise; + + // 第三个参数 Array + // this.fileId = ""; + // this.fileName = ""; + // this.fileId = str; + // this.fileName = str2; + // this.fileSize = j2; + // this.fileModelId = j3; + + downloadFileForFileUuid(peer: Peer, uuid: string, arg3: { + fileId: string, + fileName: string, + fileSize: string, + fileModelId: string + }[]): Promise; + + downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array): unknown; + + downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown; + + createGroupFolder(GroupCode: string, FolderName: string): Promise } + }>; + + downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown; + + createGroupFolder(arg1: unknown, arg2: unknown): unknown; + + downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + + renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + + deleteGroupFolder(arg1: unknown, arg2: unknown): unknown; + + deleteTransferInfo(arg1: unknown, arg2: unknown): unknown; + + cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + + cancelUrlDownload(arg: unknown): unknown; + + updateOnlineVideoElemStatus(arg: unknown): unknown; + + getGroupSpace(arg: unknown): unknown; + + getGroupFileList(groupCode: string, params: GetFileListParam): Promise; + + getGroupFileInfo(arg1: unknown, arg2: unknown): unknown; + + getGroupTransferList(arg1: unknown, arg2: unknown): unknown; + + renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown; + + moveGroupFile(groupCode: string, busId: Array, fileList: Array, currentParentDirectory: string, targetParentDirectory: string): Promise, + failFileIdList: Array + } + }>; + + transGroupFile(groupCode: string, fileId: string): Promise; + + searchGroupFile( + keywords: Array, + param: { + groupIds: Array, + fileType: number, + context: string, + count: number, + sortType: number, + groupNames: Array }): Promise; - isFileExpired(arg: number): unknown; + searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown; - deleteGroupFolder(GroupCode: string, FolderId: string): Promise; - - //参数与getVideoPlayUrlInVisit一样 - downloadRichMediaInVisit(arg: { - downloadType: number, - thumbSize: number, - msgId: string, - msgRandom: string, - msgSeq: string, - msgTime: string, - chatType: number, - senderUid: string, - peerUid: string, - guildId: string, - ele: MessageElement, - useHttps: boolean - }): unknown; - - downloadFileForModelId(peer: Peer, ModelId: string[], unknown: string): Promise; - - //第三个参数 Array - // this.fileId = ""; - // this.fileName = ""; - // this.fileId = str; - // this.fileName = str2; - // this.fileSize = j2; - // this.fileModelId = j3; - - downloadFileForFileUuid(peer: Peer, uuid: string, arg3: { - fileId: string, - fileName: string, - fileSize: string, - fileModelId: string - }[]): Promise; - - downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array): unknown; - - downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown; - - createGroupFolder(GroupCode: string, FolderName: string): Promise } - }>; - - downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown; - - createGroupFolder(arg1: unknown, arg2: unknown): unknown; - - downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - - renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - - deleteGroupFolder(arg1: unknown, arg2: unknown): unknown; - - deleteTransferInfo(arg1: unknown, arg2: unknown): unknown; - - cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - - cancelUrlDownload(arg: unknown): unknown; - - updateOnlineVideoElemStatus(arg: unknown): unknown; - - getGroupSpace(arg: unknown): unknown; - - getGroupFileList(groupCode: string, params: GetFileListParam): Promise; - - getGroupFileInfo(arg1: unknown, arg2: unknown): unknown; - - getGroupTransferList(arg1: unknown, arg2: unknown): unknown; - - renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown; - - moveGroupFile(groupCode: string, busId: Array, fileList: Array, currentParentDirectory: string, targetParentDirectory: string): Promise, - failFileIdList: Array - } - }>; - - transGroupFile(groupCode: string, fileId: string): Promise; - - searchGroupFile( - keywords: Array, - param: { - groupIds: Array, - fileType: number, - context: string, - count: number, - sortType: number, - groupNames: Array - }): Promise; - - searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown; - - deleteGroupFile(GroupCode: string, params: Array, Files: Array): Promise - failFileIdList: Array - } - }>; - - translateEnWordToZn(words: string[]): Promise; - - getScreenOCR(path: string): Promise; - - batchGetGroupFileCount(Gids: Array): Promise, - groupFileCounts: Array - }>; - - queryPicDownloadSize(arg: unknown): unknown; - - searchGroupFile(arg1: unknown, arg2: unknown): unknown; - - searchMoreGroupFile(arg: unknown): unknown; - - cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - - onlyDownloadFile(peer: Peer, arg2: unknown, arg3: Array<{ - fileId: string, - fileName: string, - fileSize: string, - fileModelId: string + deleteGroupFile(GroupCode: string, params: Array, Files: Array): Promise + failFileIdList: Array } + }>; + + translateEnWordToZn(words: string[]): Promise; + + getScreenOCR(path: string): Promise; + + batchGetGroupFileCount(Gids: Array): Promise, + groupFileCounts: Array + }>; + + queryPicDownloadSize(arg: unknown): unknown; + + searchGroupFile(arg1: unknown, arg2: unknown): unknown; + + searchMoreGroupFile(arg: unknown): unknown; + + cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + + onlyDownloadFile(peer: Peer, arg2: unknown, arg3: Array<{ + fileId: string, + fileName: string, + fileSize: string, + fileModelId: string + } >): unknown; - onlyUploadFile(arg1: unknown, arg2: unknown): unknown; + onlyUploadFile(arg1: unknown, arg2: unknown): unknown; - isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown; + isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown; - uploadRMFileWithoutMsg(arg: { - bizType: RMBizTypeEnum, - filePath: string, - peerUid: string, - transferId: string - useNTV2: string - }): Promise; + uploadRMFileWithoutMsg(arg: { + bizType: RMBizTypeEnum, + filePath: string, + peerUid: string, + transferId: string + useNTV2: string + }): Promise; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelRobotService.ts b/src/core/services/NodeIKernelRobotService.ts index f8ee2216..c2ae880f 100644 --- a/src/core/services/NodeIKernelRobotService.ts +++ b/src/core/services/NodeIKernelRobotService.ts @@ -2,47 +2,47 @@ import { NodeIKernelRobotListener } from '@/core/listeners'; import { GeneralCallResult, Peer } from '..'; export interface NodeIKernelRobotService { - fetchGroupRobotStoreDiscovery(arg: unknown): unknown; + fetchGroupRobotStoreDiscovery(arg: unknown): unknown; - sendGroupRobotStoreSearch(arg: unknown): unknown; + sendGroupRobotStoreSearch(arg: unknown): unknown; - fetchGroupRobotStoreCategoryList(arg: unknown): unknown; + fetchGroupRobotStoreCategoryList(arg: unknown): unknown; - FetchSubscribeMsgTemplate(arg: unknown): unknown; + FetchSubscribeMsgTemplate(arg: unknown): unknown; - FetchSubcribeMsgTemplateStatus(arg: unknown): unknown; + FetchSubcribeMsgTemplateStatus(arg: unknown): unknown; - SubscribeMsgTemplateSet(arg1: unknown, arg2: unknown): unknown; + SubscribeMsgTemplateSet(arg1: unknown, arg2: unknown): unknown; - fetchRecentUsedRobots(arg: unknown): unknown; + fetchRecentUsedRobots(arg: unknown): unknown; - fetchShareArkInfo(arg: unknown): unknown; + fetchShareArkInfo(arg: unknown): unknown; - addKernelRobotListener(Listener: NodeIKernelRobotListener): number; + addKernelRobotListener(Listener: NodeIKernelRobotListener): number; - removeKernelRobotListener(ListenerId: number): unknown; + removeKernelRobotListener(ListenerId: number): unknown; - getAllRobotFriendsFromCache(): Promise; + getAllRobotFriendsFromCache(): Promise; - fetchAllRobots(arg1: unknown, arg2: unknown): unknown; + fetchAllRobots(arg1: unknown, arg2: unknown): unknown; - removeAllRecommendCache(): unknown; + removeAllRecommendCache(): unknown; - setRobotPickTts(arg1: unknown, arg2: unknown): unknown; + setRobotPickTts(arg1: unknown, arg2: unknown): unknown; - getRobotUinRange(data: unknown): Promise<{ response: { robotUinRanges: Array } }>; + getRobotUinRange(data: unknown): Promise<{ response: { robotUinRanges: Array } }>; - getRobotFunctions(peer: Peer, params: { - uins: Array, - num: 0, - client_info: { platform: 4, version: '', build_num: 9999 }, - tinyids: [], - page: 0, - full_fetch: false, - scene: 4, - filter: 1, - bkn: '' - }): Promise, next_page: number } }>; + getRobotFunctions(peer: Peer, params: { + uins: Array, + num: 0, + client_info: { platform: 4, version: '', build_num: 9999 }, + tinyids: [], + page: 0, + full_fetch: false, + scene: 4, + filter: 1, + bkn: '' + }): Promise, next_page: number } }>; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelSearchService.ts b/src/core/services/NodeIKernelSearchService.ts index 7eda4f8a..d615f615 100644 --- a/src/core/services/NodeIKernelSearchService.ts +++ b/src/core/services/NodeIKernelSearchService.ts @@ -3,134 +3,134 @@ import { GeneralCallResult } from './common'; export interface NodeIKernelSearchService { - addKernelSearchListener(listener: unknown): number; + addKernelSearchListener(listener: unknown): number; - removeKernelSearchListener(listenerId: number): void; + removeKernelSearchListener(listenerId: number): void; - searchStranger(unknown: string, searchStranger: unknown, searchParams: unknown): Promise; + searchStranger(unknown: string, searchStranger: unknown, searchParams: unknown): Promise; - searchGroup(param: { - keyWords: string, - groupNum: number, - exactSearch: boolean, - penetrate: string - }): Promise;// needs 1 arguments + searchGroup(param: { + keyWords: string, + groupNum: number, + exactSearch: boolean, + penetrate: string + }): Promise;// needs 1 arguments - searchLocalInfo(keywords: string, type: number/*4*/): unknown; + searchLocalInfo(keywords: string, type: number/* 4 */): unknown; - cancelSearchLocalInfo(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchLocalInfo(...args: unknown[]): unknown;// needs 3 arguments - searchBuddyChatInfo(...args: unknown[]): unknown;// needs 2 arguments + searchBuddyChatInfo(...args: unknown[]): unknown;// needs 2 arguments - searchMoreBuddyChatInfo(...args: unknown[]): unknown;// needs 1 arguments + searchMoreBuddyChatInfo(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchBuddyChatInfo(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchBuddyChatInfo(...args: unknown[]): unknown;// needs 3 arguments - searchContact(...args: unknown[]): unknown;// needs 2 arguments + searchContact(...args: unknown[]): unknown;// needs 2 arguments - searchMoreContact(...args: unknown[]): unknown;// needs 1 arguments + searchMoreContact(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchContact(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchContact(...args: unknown[]): unknown;// needs 3 arguments - searchGroupChatInfo(...args: unknown[]): unknown;// needs 3 arguments + searchGroupChatInfo(...args: unknown[]): unknown;// needs 3 arguments - resetSearchGroupChatInfoSortType(...args: unknown[]): unknown;// needs 3 arguments + resetSearchGroupChatInfoSortType(...args: unknown[]): unknown;// needs 3 arguments - resetSearchGroupChatInfoFilterMembers(...args: unknown[]): unknown;// needs 3 arguments + resetSearchGroupChatInfoFilterMembers(...args: unknown[]): unknown;// needs 3 arguments - searchMoreGroupChatInfo(...args: unknown[]): unknown;// needs 1 arguments + searchMoreGroupChatInfo(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchGroupChatInfo(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchGroupChatInfo(...args: unknown[]): unknown;// needs 3 arguments - searchChatsWithKeywords(...args: unknown[]): unknown;// needs 3 arguments + searchChatsWithKeywords(...args: unknown[]): unknown;// needs 3 arguments - searchMoreChatsWithKeywords(...args: unknown[]): unknown;// needs 1 arguments + searchMoreChatsWithKeywords(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchChatsWithKeywords(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchChatsWithKeywords(...args: unknown[]): unknown;// needs 3 arguments - searchChatMsgs(...args: unknown[]): unknown;// needs 2 arguments + searchChatMsgs(...args: unknown[]): unknown;// needs 2 arguments - searchMoreChatMsgs(...args: unknown[]): unknown;// needs 1 arguments + searchMoreChatMsgs(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchChatMsgs(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchChatMsgs(...args: unknown[]): unknown;// needs 3 arguments - searchMsgWithKeywords(keyWords: string[], param: Peer & { searchFields: number, pageLimit: number }): Promise; + searchMsgWithKeywords(keyWords: string[], param: Peer & { searchFields: number, pageLimit: number }): Promise; - searchMoreMsgWithKeywords(...args: unknown[]): unknown;// needs 1 arguments + searchMoreMsgWithKeywords(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchMsgWithKeywords(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchMsgWithKeywords(...args: unknown[]): unknown;// needs 3 arguments - searchFileWithKeywords(keywords: string[], source: number): Promise;// needs 2 arguments + searchFileWithKeywords(keywords: string[], source: number): Promise;// needs 2 arguments - searchMoreFileWithKeywords(...args: unknown[]): unknown;// needs 1 arguments + searchMoreFileWithKeywords(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchFileWithKeywords(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchFileWithKeywords(...args: unknown[]): unknown;// needs 3 arguments - searchAtMeChats(...args: unknown[]): unknown;// needs 3 arguments + searchAtMeChats(...args: unknown[]): unknown;// needs 3 arguments - searchMoreAtMeChats(...args: unknown[]): unknown;// needs 1 arguments + searchMoreAtMeChats(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchAtMeChats(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchAtMeChats(...args: unknown[]): unknown;// needs 3 arguments - searchChatAtMeMsgs(...args: unknown[]): unknown;// needs 1 arguments + searchChatAtMeMsgs(...args: unknown[]): unknown;// needs 1 arguments - searchMoreChatAtMeMsgs(...args: unknown[]): unknown;// needs 1 arguments + searchMoreChatAtMeMsgs(...args: unknown[]): unknown;// needs 1 arguments - cancelSearchChatAtMeMsgs(...args: unknown[]): unknown;// needs 3 arguments + cancelSearchChatAtMeMsgs(...args: unknown[]): unknown;// needs 3 arguments - addSearchHistory(param: { - type: number,//4 - contactList: [], - id: number,//-1 - groupInfos: [], - msgs: [], - fileInfos: [ - { - chatType: ChatType, - buddyChatInfo: Array<{ category_name: string, peerUid: string, peerUin: string, remark: string }>, - discussChatInfo: [], - groupChatInfo: Array< + addSearchHistory(param: { + type: number, // 4 + contactList: [], + id: number, // -1 + groupInfos: [], + msgs: [], + fileInfos: [ + { + chatType: ChatType, + buddyChatInfo: Array<{ category_name: string, peerUid: string, peerUin: string, remark: string }>, + discussChatInfo: [], + groupChatInfo: Array< { - groupCode: string, - isConf: boolean, - hasModifyConfGroupFace: boolean, - hasModifyConfGroupName: boolean, - groupName: string, - remark: string + groupCode: string, + isConf: boolean, + hasModifyConfGroupFace: boolean, + hasModifyConfGroupName: boolean, + groupName: string, + remark: string }>, - dataLineChatInfo: [], - tmpChatInfo: [], - msgId: string, - msgSeq: string, - msgTime: string, - senderUid: string, - senderNick: string, - senderRemark: string, - senderCard: string, - elemId: string, - elemType: string,//3 - fileSize: string, - filePath: string, - fileName: string, - hits: Array< + dataLineChatInfo: [], + tmpChatInfo: [], + msgId: string, + msgSeq: string, + msgTime: string, + senderUid: string, + senderNick: string, + senderRemark: string, + senderCard: string, + elemId: string, + elemType: string, // 3 + fileSize: string, + filePath: string, + fileName: string, + hits: Array< { - start: 12, - end: 14 + start: 12, + end: 14 } > - } - ] + } + ] - }): Promise<{ - result: number, - errMsg: string, - id?: number - }>; + }): Promise<{ + result: number, + errMsg: string, + id?: number + }>; - removeSearchHistory(...args: unknown[]): unknown;// needs 1 arguments + removeSearchHistory(...args: unknown[]): unknown;// needs 1 arguments - searchCache(...args: unknown[]): unknown;// needs 3 arguments + searchCache(...args: unknown[]): unknown;// needs 3 arguments - clearSearchCache(...args: unknown[]): unknown;// needs 1 arguments + clearSearchCache(...args: unknown[]): unknown;// needs 1 arguments } diff --git a/src/core/services/NodeIKernelStorageCleanService.ts b/src/core/services/NodeIKernelStorageCleanService.ts index 28a9d546..b47e8bce 100644 --- a/src/core/services/NodeIKernelStorageCleanService.ts +++ b/src/core/services/NodeIKernelStorageCleanService.ts @@ -3,39 +3,39 @@ import { GeneralCallResult } from './common'; export interface NodeIKernelStorageCleanService { - addKernelStorageCleanListener(listener: NodeIKernelStorageCleanListener): number; + addKernelStorageCleanListener(listener: NodeIKernelStorageCleanListener): number; - removeKernelStorageCleanListener(listenerId: number): void; + removeKernelStorageCleanListener(listenerId: number): void; - addCacheScanedPaths(arg: unknown): unknown; + addCacheScanedPaths(arg: unknown): unknown; - addFilesScanedPaths(arg: unknown): unknown; + addFilesScanedPaths(arg: unknown): unknown; - scanCache(): Promise; + scanCache(): Promise; - 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(arg: unknown): unknown; - setSilentScan(arg: unknown): unknown; + setSilentScan(arg: unknown): 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; } diff --git a/src/core/services/NodeIKernelTianShuService.ts b/src/core/services/NodeIKernelTianShuService.ts index 05548a2f..70259356 100644 --- a/src/core/services/NodeIKernelTianShuService.ts +++ b/src/core/services/NodeIKernelTianShuService.ts @@ -1,8 +1,8 @@ export interface NodeIKernelTianShuService { - addKernelTianShuListener(listener:unknown): number; + addKernelTianShuListener(listener:unknown): number; - removeKernelTianShuListener(listenerId:number): void; + removeKernelTianShuListener(listenerId:number): void; - reportTianShuNumeralRed(...args: unknown[]): unknown;// needs 1 arguments + reportTianShuNumeralRed(...args: unknown[]): unknown;// needs 1 arguments } diff --git a/src/core/services/NodeIKernelTicketService.ts b/src/core/services/NodeIKernelTicketService.ts index 57f802ba..135c9d8e 100644 --- a/src/core/services/NodeIKernelTicketService.ts +++ b/src/core/services/NodeIKernelTicketService.ts @@ -2,11 +2,11 @@ import { ForceFetchClientKeyRetType } from './common'; export interface NodeIKernelTicketService { - addKernelTicketListener(listener: unknown): number; + addKernelTicketListener(listener: unknown): number; - removeKernelTicketListener(listenerId: number): void; + removeKernelTicketListener(listenerId: number): void; - forceFetchClientKey(arg: string): Promise; + forceFetchClientKey(arg: string): Promise; - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelTipOffService.ts b/src/core/services/NodeIKernelTipOffService.ts index ae4617cd..e7a4fc89 100644 --- a/src/core/services/NodeIKernelTipOffService.ts +++ b/src/core/services/NodeIKernelTipOffService.ts @@ -2,21 +2,21 @@ import { GeneralCallResult } from './common'; export interface NodeIKernelTipOffService { - addKernelTipOffListener(listener: unknown): number; + addKernelTipOffListener(listener: unknown): number; - removeKernelTipOffListener(listenerId: unknown): void; + removeKernelTipOffListener(listenerId: unknown): void; - tipOffSendJsData(args: unknown[]): Promise;//2 + tipOffSendJsData(args: unknown[]): Promise;// 2 - getPskey(domainList: string[], nocache: boolean): Promise - }>; + getPskey(domainList: string[], nocache: boolean): Promise + }>; - tipOffSendJsData(args: unknown[]): Promise;//2 + tipOffSendJsData(args: unknown[]): Promise;// 2 - tipOffMsgs(args: unknown[]): Promise;//1 + tipOffMsgs(args: unknown[]): Promise;// 1 - encodeUinAesInfo(args: unknown[]): Promise;//2 + encodeUinAesInfo(args: unknown[]): Promise;// 2 - isNull(): boolean; + isNull(): boolean; } diff --git a/src/core/services/NodeIKernelUixConvertService.ts b/src/core/services/NodeIKernelUixConvertService.ts index b203c481..45ab9952 100644 --- a/src/core/services/NodeIKernelUixConvertService.ts +++ b/src/core/services/NodeIKernelUixConvertService.ts @@ -1,5 +1,5 @@ export interface NodeIKernelUixConvertService { - getUin(uid: string[]): Promise<{ uinInfo: Map }>; + getUin(uid: string[]): Promise<{ uinInfo: Map }>; - getUid(uin: string[]): Promise<{ uidInfo: Map }>; -} \ No newline at end of file + getUid(uin: string[]): Promise<{ uidInfo: Map }>; +} diff --git a/src/core/services/NodeIKernelUnitedConfigService.ts b/src/core/services/NodeIKernelUnitedConfigService.ts index 7604891d..d94d2ca0 100644 --- a/src/core/services/NodeIKernelUnitedConfigService.ts +++ b/src/core/services/NodeIKernelUnitedConfigService.ts @@ -1,17 +1,17 @@ export interface NodeIKernelUnitedConfigService { - addKernelUnitedConfigListener(listener:unknown): number; + addKernelUnitedConfigListener(listener:unknown): number; - removeKernelUnitedConfigListener(listenerId:number): void; + removeKernelUnitedConfigListener(listenerId:number): void; - fetchUnitedSwitchConfig(...args: unknown[]): unknown;// needs 1 arguments + fetchUnitedSwitchConfig(...args: unknown[]): unknown;// needs 1 arguments - isUnitedConfigSwitchOn(...args: unknown[]): unknown;// needs 1 arguments + isUnitedConfigSwitchOn(...args: unknown[]): unknown;// needs 1 arguments - registerUnitedConfigPushGroupList(...args: unknown[]): unknown;// needs 1 arguments + registerUnitedConfigPushGroupList(...args: unknown[]): unknown;// needs 1 arguments - fetchUnitedCommendConfig(ids: `${string}`[]): void + fetchUnitedCommendConfig(ids: `${string}`[]): void - loadUnitedConfig(id: string): Promise + loadUnitedConfig(id: string): Promise } diff --git a/src/core/services/NodeIO3MiscService.ts b/src/core/services/NodeIO3MiscService.ts index e90f892f..2369eb50 100644 --- a/src/core/services/NodeIO3MiscService.ts +++ b/src/core/services/NodeIO3MiscService.ts @@ -1,11 +1,11 @@ import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener'; export interface NodeIO3MiscService { - get(): NodeIO3MiscService; + get(): NodeIO3MiscService; - addO3MiscListener(listeners: NodeIO3MiscListener): number; + addO3MiscListener(listeners: NodeIO3MiscListener): number; - setAmgomDataPiece(appid: string, dataPiece: Uint8Array): void; + setAmgomDataPiece(appid: string, dataPiece: Uint8Array): void; - reportAmgomWeather(type: string, uk2: string, arg: Array): void; + reportAmgomWeather(type: string, uk2: string, arg: Array): void; } diff --git a/src/core/services/NodeIYellowFaceService.ts b/src/core/services/NodeIYellowFaceService.ts index 973cc91a..79a93e4a 100644 --- a/src/core/services/NodeIYellowFaceService.ts +++ b/src/core/services/NodeIYellowFaceService.ts @@ -1,5 +1,5 @@ export interface NodeIYellowFaceService { - download(resourceConfigJson: string, resourceDir: string, cacheDir: string, force: boolean): void; + download(resourceConfigJson: string, resourceDir: string, cacheDir: string, force: boolean): void; - setHistory(fullMd5: string): void; + setHistory(fullMd5: string): void; } diff --git a/src/core/services/NodeIkernelTestPerformanceService.ts b/src/core/services/NodeIkernelTestPerformanceService.ts index 4d978b26..bbd711ae 100644 --- a/src/core/services/NodeIkernelTestPerformanceService.ts +++ b/src/core/services/NodeIkernelTestPerformanceService.ts @@ -2,14 +2,14 @@ import { MessageElement, Peer } from '@/core/types'; export interface NodeIkernelTestPerformanceService { - insertMsg(MsgParam: { - peer: Peer - msgTime: string - msgId: string - msgSeq: string - batchNums: number - timesPerBatch: number - numPerTime: number - }, msg: Array): Promise; + insertMsg(MsgParam: { + peer: Peer + msgTime: string + msgId: string + msgSeq: string + batchNums: number + timesPerBatch: number + numPerTime: number + }, msg: Array): Promise; } diff --git a/src/core/services/common.ts b/src/core/services/common.ts index 1843db4e..64ec0cb2 100644 --- a/src/core/services/common.ts +++ b/src/core/services/common.ts @@ -1,15 +1,15 @@ export enum GeneralCallResultStatus { - OK = 0 + OK = 0, } export interface GeneralCallResult { - result: GeneralCallResultStatus, - errMsg: string + result: GeneralCallResultStatus, + errMsg: string } export interface ForceFetchClientKeyRetType extends GeneralCallResult { - url: string; - keyIndex: string; - clientKey: string; - expireTime: string; + url: string; + keyIndex: string; + clientKey: string; + expireTime: string; } diff --git a/src/core/services/index.ts b/src/core/services/index.ts index a36de20f..5e607438 100644 --- a/src/core/services/index.ts +++ b/src/core/services/index.ts @@ -1,3 +1,23 @@ +import type { + NodeIKernelAvatarService, + NodeIKernelBuddyService, + NodeIKernelCollectionService, + NodeIKernelDbToolsService, + NodeIKernelFileAssistantService, + NodeIKernelGroupService, + NodeIKernelLoginService, + NodeIKernelMsgService, + NodeIKernelOnlineStatusService, + NodeIKernelProfileLikeService, + NodeIKernelProfileService, + NodeIKernelRichMediaService, + NodeIKernelRobotService, + NodeIKernelSearchService, + NodeIKernelStorageCleanService, + NodeIKernelTicketService, + NodeIKernelTipOffService, +} from '.'; + export * from './common'; export * from './NodeIKernelAvatarService'; export * from './NodeIKernelBuddyService'; @@ -17,42 +37,22 @@ export * from './NodeIKernelTipOffService'; export * from './NodeIKernelSearchService'; export * from './NodeIKernelCollectionService'; -import type { - NodeIKernelAvatarService, - NodeIKernelBuddyService, - NodeIKernelCollectionService, - NodeIKernelDbToolsService, - NodeIKernelFileAssistantService, - NodeIKernelGroupService, - NodeIKernelLoginService, - NodeIKernelMsgService, - NodeIKernelOnlineStatusService, - NodeIKernelProfileLikeService, - NodeIKernelProfileService, - NodeIKernelRichMediaService, - NodeIKernelRobotService, - NodeIKernelSearchService, - NodeIKernelStorageCleanService, - NodeIKernelTicketService, - NodeIKernelTipOffService, -} from '.'; - export type ServiceNamingMapping = { - NodeIKernelAvatarService: NodeIKernelAvatarService; - NodeIKernelBuddyService: NodeIKernelBuddyService; - NodeIKernelFileAssistantService: NodeIKernelFileAssistantService; - NodeIKernelGroupService: NodeIKernelGroupService; - NodeIKernelLoginService: NodeIKernelLoginService; - NodeIKernelMsgService: NodeIKernelMsgService; - NodeIKernelOnlineStatusService: NodeIKernelOnlineStatusService; - NodeIKernelProfileLikeService: NodeIKernelProfileLikeService; - NodeIKernelProfileService: NodeIKernelProfileService; - NodeIKernelTicketService: NodeIKernelTicketService; - NodeIKernelStorageCleanService: NodeIKernelStorageCleanService; - NodeIKernelRobotService: NodeIKernelRobotService; - NodeIKernelRichMediaService: NodeIKernelRichMediaService; - NodeIKernelDbToolsService: NodeIKernelDbToolsService; - NodeIKernelTipOffService: NodeIKernelTipOffService; - NodeIKernelSearchService: NodeIKernelSearchService, - NodeIKernelCollectionService: NodeIKernelCollectionService; + NodeIKernelAvatarService: NodeIKernelAvatarService; + NodeIKernelBuddyService: NodeIKernelBuddyService; + NodeIKernelFileAssistantService: NodeIKernelFileAssistantService; + NodeIKernelGroupService: NodeIKernelGroupService; + NodeIKernelLoginService: NodeIKernelLoginService; + NodeIKernelMsgService: NodeIKernelMsgService; + NodeIKernelOnlineStatusService: NodeIKernelOnlineStatusService; + NodeIKernelProfileLikeService: NodeIKernelProfileLikeService; + NodeIKernelProfileService: NodeIKernelProfileService; + NodeIKernelTicketService: NodeIKernelTicketService; + NodeIKernelStorageCleanService: NodeIKernelStorageCleanService; + NodeIKernelRobotService: NodeIKernelRobotService; + NodeIKernelRichMediaService: NodeIKernelRichMediaService; + NodeIKernelDbToolsService: NodeIKernelDbToolsService; + NodeIKernelTipOffService: NodeIKernelTipOffService; + NodeIKernelSearchService: NodeIKernelSearchService, + NodeIKernelCollectionService: NodeIKernelCollectionService; }; diff --git a/src/core/types/adapter.ts b/src/core/types/adapter.ts index 5013be32..7c25eefe 100644 --- a/src/core/types/adapter.ts +++ b/src/core/types/adapter.ts @@ -1,11 +1,11 @@ export enum MsfStatusType { - KUNKNOWN = 0, - KDISCONNECTED = 1, - KCONNECTED = 2 + KUNKNOWN = 0, + KDISCONNECTED = 1, + KCONNECTED = 2, } export enum MsfChangeReasonType { - KUNKNOWN = 0, - KUSERLOGININ = 1, - KUSERLOGINOUT = 2, - KAUTO = 3 -} \ No newline at end of file + KUNKNOWN = 0, + KUSERLOGININ = 1, + KUSERLOGINOUT = 2, + KAUTO = 3, +} diff --git a/src/core/types/cache.ts b/src/core/types/cache.ts index 27d8433a..85c6fd43 100644 --- a/src/core/types/cache.ts +++ b/src/core/types/cache.ts @@ -4,65 +4,65 @@ import { ChatType } from './msg'; * 聊天缓存列表 */ export interface ChatCacheList { - pageCount: number; // 页数 - infos: ChatCacheListItem[]; // 聊天缓存项列表 + pageCount: number; // 页数 + infos: ChatCacheListItem[]; // 聊天缓存项列表 } /** * 聊天缓存列表项 */ export interface ChatCacheListItem { - chatType: ChatType; // 聊天类型 - basicChatCacheInfo: ChatCacheListItemBasic; // 基本聊天缓存信息 - guildChatCacheInfo: unknown[]; // 公会聊天缓存信息 + chatType: ChatType; // 聊天类型 + basicChatCacheInfo: ChatCacheListItemBasic; // 基本聊天缓存信息 + guildChatCacheInfo: unknown[]; // 公会聊天缓存信息 } /** * 基本聊天缓存信息 */ export interface ChatCacheListItemBasic { - chatSize: string; // 聊天大小 - chatTime: string; // 聊天时间 - uid: string; // 用户ID - uin: string; // 用户号码 - remarkName: string; // 备注名 - nickName: string; // 昵称 - chatType?: ChatType; // 聊天类型(可选) - isChecked?: boolean; // 是否已检查(可选) + chatSize: string; // 聊天大小 + chatTime: string; // 聊天时间 + uid: string; // 用户ID + uin: string; // 用户号码 + remarkName: string; // 备注名 + nickName: string; // 昵称 + chatType?: ChatType; // 聊天类型(可选) + isChecked?: boolean; // 是否已检查(可选) } /** * 缓存文件类型枚举 */ export enum CacheFileType { - IMAGE = 0, // 图片 - VIDEO = 1, // 视频 - AUDIO = 2, // 音频 - DOCUMENT = 3, // 文档 - OTHER = 4, // 其他 + IMAGE = 0, // 图片 + VIDEO = 1, // 视频 + AUDIO = 2, // 音频 + DOCUMENT = 3, // 文档 + OTHER = 4, // 其他 } /** * 缓存文件列表 */ export interface CacheFileList { - infos: CacheFileListItem[]; // 缓存文件项列表 + infos: CacheFileListItem[]; // 缓存文件项列表 } /** * 缓存文件列表项 */ export interface CacheFileListItem { - fileSize: string; // 文件大小 - fileTime: string; // 文件时间 - fileKey: string; // 文件键 - elementId: string; // 元素ID - elementIdStr: string; // 元素ID字符串 - fileType: CacheFileType; // 文件类型 - path: string; // 路径 - fileName: string; // 文件名 - senderId: string; // 发送者ID - previewPath: string; // 预览路径 - senderName: string; // 发送者名称 - isChecked?: boolean; // 是否已检查(可选) -} \ No newline at end of file + fileSize: string; // 文件大小 + fileTime: string; // 文件时间 + fileKey: string; // 文件键 + elementId: string; // 元素ID + elementIdStr: string; // 元素ID字符串 + fileType: CacheFileType; // 文件类型 + path: string; // 路径 + fileName: string; // 文件名 + senderId: string; // 发送者ID + previewPath: string; // 预览路径 + senderName: string; // 发送者名称 + isChecked?: boolean; // 是否已检查(可选) +} diff --git a/src/core/types/contact.ts b/src/core/types/contact.ts index 7ab61df8..340f556f 100644 --- a/src/core/types/contact.ts +++ b/src/core/types/contact.ts @@ -1,11 +1,11 @@ export interface FSABRecentContactParams { - anchorPointContact: { - contactId: string; - sortField: string; - pos: number; - }; - relativeMoveCount: number; - listType: number; - count: number; - fetchOld: boolean; + anchorPointContact: { + contactId: string; + sortField: string; + pos: number; + }; + relativeMoveCount: number; + listType: number; + count: number; + fetchOld: boolean; } diff --git a/src/core/types/element.ts b/src/core/types/element.ts index c0542d80..a40a877c 100644 --- a/src/core/types/element.ts +++ b/src/core/types/element.ts @@ -1,320 +1,319 @@ import { - ElementType, - MessageElement, - NTGrayTipElementSubTypeV2, - PicSubType, - PicType, - TipAioOpGrayTipElement, - TipGroupElement, - NTVideoType, - FaceType, - Peer + ElementType, + MessageElement, + NTGrayTipElementSubTypeV2, + PicSubType, + PicType, + TipAioOpGrayTipElement, + TipGroupElement, + NTVideoType, + FaceType, + Peer, } from './msg'; type ElementFullBase = Omit; export interface SendElementBase { - elementType: ET; - elementId: string; - extBufForUI?: string; + elementType: ET; + elementId: string; + extBufForUI?: string; } type ElementBase< K extends keyof ElementFullBase, S extends Partial<{ [P in K]: keyof NonNullable | Array> }> = object > = { - [P in K]: - S[P] extends Array - ? Pick, U & keyof NonNullable> - : S[P] extends keyof NonNullable - ? Pick, S[P]> - : NonNullable; - }; + [P in K]: + S[P] extends Array + ? Pick, U & keyof NonNullable> + : S[P] extends keyof NonNullable + ? Pick, S[P]> + : NonNullable; +}; export interface TextElement { - content: string; - atType: number; - atUid: string; - atTinyId: string; - atNtUid: string; + content: string; + atType: number; + atUid: string; + atTinyId: string; + atNtUid: string; } export interface FaceElement { - pokeType?: number; - faceIndex: number; - faceType: FaceType; - faceText?: string; - packId?: string; - stickerId?: string; - sourceType?: number; - stickerType?: number; - resultId?: string; - surpriseId?: string; - randomType?: number; - chainCount?: number; + pokeType?: number; + faceIndex: number; + faceType: FaceType; + faceText?: string; + packId?: string; + stickerId?: string; + sourceType?: number; + stickerType?: number; + resultId?: string; + surpriseId?: string; + randomType?: number; + chainCount?: number; } export interface GrayTipRovokeElement { - operatorRole: string; - operatorUid: string; - operatorNick: string; - operatorRemark: string; - isSelfOperate: boolean; // 是否是自己撤回的 - operatorMemRemark?: string; - wording: string; // 自定义的撤回提示语 + operatorRole: string; + operatorUid: string; + operatorNick: string; + operatorRemark: string; + isSelfOperate: boolean; // 是否是自己撤回的 + operatorMemRemark?: string; + wording: string; // 自定义的撤回提示语 } export interface GrayTipElement { - subElementType: NTGrayTipElementSubTypeV2; - revokeElement: GrayTipRovokeElement; - aioOpGrayTipElement: TipAioOpGrayTipElement; - groupElement: TipGroupElement; - xmlElement: { - busiId: string; - content: string; - templId: string; - }; - jsonGrayTipElement: { - busiId?: number; - jsonStr: string; - }; + subElementType: NTGrayTipElementSubTypeV2; + revokeElement: GrayTipRovokeElement; + aioOpGrayTipElement: TipAioOpGrayTipElement; + groupElement: TipGroupElement; + xmlElement: { + busiId: string; + content: string; + templId: string; + }; + jsonGrayTipElement: { + busiId?: number; + jsonStr: string; + }; } - export interface ArkElement { - bytesData: string; - linkInfo: null; - subElementType: null; + bytesData: string; + linkInfo: null; + subElementType: null; } export interface MarketFaceElement { - emojiPackageId: number; - faceName: string; - emojiId: string; - key: string; + emojiPackageId: number; + faceName: string; + emojiId: string; + key: string; } export interface VideoElement { - filePath: string; - fileName: string; - videoMd5?: string; - thumbMd5?: string; - fileTime?: number; // second - thumbSize?: number; // byte - fileFormat?: NTVideoType; // 2表示mp4 参考下面条目 - fileSize?: string; // byte - thumbWidth?: number; - thumbHeight?: number; - busiType?: 0; // - subBusiType?: 0; // 未知 - thumbPath?: Map; - transferStatus?: 0; // 未知 - progress?: 0; // 下载进度? - invalidState?: 0; // 未知 - fileUuid?: string; // 可以用于下载链接? - fileSubId?: string; - fileBizId?: null; - originVideoMd5?: string; - import_rich_media_context?: null; - sourceVideoCodecFormat?: number; + filePath: string; + fileName: string; + videoMd5?: string; + thumbMd5?: string; + fileTime?: number; // second + thumbSize?: number; // byte + fileFormat?: NTVideoType; // 2表示mp4 参考下面条目 + fileSize?: string; // byte + thumbWidth?: number; + thumbHeight?: number; + busiType?: 0; // + subBusiType?: 0; // 未知 + thumbPath?: Map; + transferStatus?: 0; // 未知 + progress?: 0; // 下载进度? + invalidState?: 0; // 未知 + fileUuid?: string; // 可以用于下载链接? + fileSubId?: string; + fileBizId?: null; + originVideoMd5?: string; + import_rich_media_context?: null; + sourceVideoCodecFormat?: number; } export interface PicElement { - md5HexStr?: string; - filePath?: string; - fileSize: number | string;//number - picWidth: number; - picHeight: number; - fileName: string; - sourcePath: string; - original: boolean; - picType: PicType; - picSubType?: PicSubType; - fileUuid: string; - fileSubId: string; - thumbFileSize: number; - summary: string; - thumbPath: Map; - originImageMd5?: string; - originImageUrl?: string; + md5HexStr?: string; + filePath?: string; + fileSize: number | string;// number + picWidth: number; + picHeight: number; + fileName: string; + sourcePath: string; + original: boolean; + picType: PicType; + picSubType?: PicSubType; + fileUuid: string; + fileSubId: string; + thumbFileSize: number; + summary: string; + thumbPath: Map; + originImageMd5?: string; + originImageUrl?: string; } export interface InlineKeyboardButton { - id: string; - label: string; - visitedLabel: string; - unsupportTips: string; - data: string; - specifyRoleIds: string[]; - specifyTinyids: string[]; - style: number; - type: number; - clickLimit: number; - atBotShowChannelList: boolean; - permissionType: number; + id: string; + label: string; + visitedLabel: string; + unsupportTips: string; + data: string; + specifyRoleIds: string[]; + specifyTinyids: string[]; + style: number; + type: number; + clickLimit: number; + atBotShowChannelList: boolean; + permissionType: number; } // 非element interface InlineKeyboardRow { - buttons: InlineKeyboardButton[]; + buttons: InlineKeyboardButton[]; } // 非element interface TofuElementContent { - color: string; - tittle: string; + color: string; + tittle: string; } export interface ActionBarElement { - rows: InlineKeyboardRow[]; - botAppid: string; + rows: InlineKeyboardRow[]; + botAppid: string; } export interface RecommendedMsgElement { - rows: InlineKeyboardRow[]; - botAppid: string; + rows: InlineKeyboardRow[]; + botAppid: string; } export interface TofuRecordElement { - type: number; - busiid: string; - busiuuid: string; - descriptionContent: string; - contentlist: TofuElementContent[], - background: string; - icon: string; - uinlist: string[], - uidlist: string[], - busiExtra: string; - updateTime: string; - dependedmsgid: string; - msgtime: string; - onscreennotify: boolean; + type: number; + busiid: string; + busiuuid: string; + descriptionContent: string; + contentlist: TofuElementContent[], + background: string; + icon: string; + uinlist: string[], + uidlist: string[], + busiExtra: string; + updateTime: string; + dependedmsgid: string; + msgtime: string; + onscreennotify: boolean; } export interface FileElement { - fileMd5?: string; - fileName: string; - filePath: string; - fileSize: string; - picHeight?: number; - picWidth?: number; - folderId?: string; - picThumbPath?: Map; - file10MMd5?: string; - fileSha?: string; - fileSha3?: string; - fileUuid?: string; - fileSubId?: string; - thumbFileSize?: number; - fileBizId?: number; + fileMd5?: string; + fileName: string; + filePath: string; + fileSize: string; + picHeight?: number; + picWidth?: number; + folderId?: string; + picThumbPath?: Map; + file10MMd5?: string; + fileSha?: string; + fileSha3?: string; + fileUuid?: string; + fileSubId?: string; + thumbFileSize?: number; + fileBizId?: number; } export interface ShareLocationElement { - text: string; - ext: string; + text: string; + ext: string; } export interface StructLongMsgElement { - xmlContent: string; - resId: string; + xmlContent: string; + resId: string; } export interface ReplyElement { - sourceMsgIdInRecords?: string; - replayMsgSeq: string; - replayMsgId: string; - senderUin: string; - senderUidStr?: string; - replyMsgTime?: string; - replyMsgClientSeq?: string; - // HACK: Attributes that were not originally available, - // but were added due to NTQQ and NapCat's internal implementation, are used to supplement NapCat - _replyMsgPeer?: Peer; + sourceMsgIdInRecords?: string; + replayMsgSeq: string; + replayMsgId: string; + senderUin: string; + senderUidStr?: string; + replyMsgTime?: string; + replyMsgClientSeq?: string; + // HACK: Attributes that were not originally available, + // but were added due to NTQQ and NapCat's internal implementation, are used to supplement NapCat + _replyMsgPeer?: Peer; } export interface CalendarElement { - summary: string; - msg: string; - expireTimeMs: string; - schemaType: number; - schema: string; + summary: string; + msg: string; + expireTimeMs: string; + schemaType: number; + schema: string; } export interface GiphyElement { - id: string; - isClip: boolean; - width: number; - height: number; + id: string; + isClip: boolean; + width: number; + height: number; } export interface AvRecordElement { - type: number; - time: string; - text: string; - mainType: number; - hasRead: boolean; - extraType: number; + type: number; + time: string; + text: string; + mainType: number; + hasRead: boolean; + extraType: number; } // 非element interface YoloUserInfo { - uid: string; - result: number; - rank: number; - bizId: string; + uid: string; + result: number; + rank: number; + bizId: string; } export interface YoloGameResultElement { - UserInfo: YoloUserInfo[]; + UserInfo: YoloUserInfo[]; } export interface FaceBubbleElement { - faceCount: number; - faceSummary: string; - faceFlag: number; - content: string; - oldVersionStr: string; - faceType: FaceType; - others: string; - yellowFaceInfo: { - index: number; - buf: string; - compatibleText: string; - text: string; - }; + faceCount: number; + faceSummary: string; + faceFlag: number; + content: string; + oldVersionStr: string; + faceType: FaceType; + others: string; + yellowFaceInfo: { + index: number; + buf: string; + compatibleText: string; + text: string; + }; } export interface TaskTopMsgElement { - msgTitle: string; - msgSummary: string; - iconUrl: string; - topMsgType: number; + msgTitle: string; + msgSummary: string; + iconUrl: string; + topMsgType: number; } export interface PttElement { - canConvert2Text: boolean; - duration: number; - fileBizId: null; - fileId: number; - fileName: string; - filePath: string; - fileSize: string; - fileSubId: string; - fileUuid: string; // FileId - formatType: number; // Todo 已定义 但是未替换 - invalidState: number; - md5HexStr: string; - playState: number; - progress: number; //进度 - text: string; - transferStatus: number; - translateStatus: number; - voiceChangeType: number; - voiceType: number; - waveAmplitudes: number[]; - autoConvertText: number; - storeID: number; - otherBusinessInfo: { - aiVoiceType: number; - }; + canConvert2Text: boolean; + duration: number; + fileBizId: null; + fileId: number; + fileName: string; + filePath: string; + fileSize: string; + fileSubId: string; + fileUuid: string; // FileId + formatType: number; // Todo 已定义 但是未替换 + invalidState: number; + md5HexStr: string; + playState: number; + progress: number; // 进度 + text: string; + transferStatus: number; + translateStatus: number; + voiceChangeType: number; + voiceType: number; + waveAmplitudes: number[]; + autoConvertText: number; + storeID: number; + otherBusinessInfo: { + aiVoiceType: number; + }; } export type SendRecommendedMsgElement = SendElementBase & ElementBase<'recommendedMsgElement'>; @@ -352,8 +351,8 @@ export type SendStructLongMsgElement = SendElementBase & ElementBase<'picElement'>; export type SendPttElement = SendElementBase & ElementBase<'pttElement', { - pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType', - 'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText', 'storeID', 'otherBusinessInfo'] + pttElement: ['fileName', 'filePath', 'md5HexStr', 'fileSize', 'duration', 'formatType', 'voiceType', + 'voiceChangeType', 'canConvert2Text', 'waveAmplitudes', 'fileSubId', 'playState', 'autoConvertText', 'storeID', 'otherBusinessInfo'] }>; export type SendFileElement = SendElementBase & ElementBase<'fileElement'>; diff --git a/src/core/types/emoji.ts b/src/core/types/emoji.ts index 1ed4a001..653588b4 100644 --- a/src/core/types/emoji.ts +++ b/src/core/types/emoji.ts @@ -1,54 +1,54 @@ export enum PullMomentType { - REINSTALL = 0, - RESTART_FIRST_AIO = 1, - LOGIN_APP = 2, - SINGEL_PULL_NOTIFY = 3, - TRIGGER_SPECIFIC_EMOJI_RANDOM_RESULT = 4 + REINSTALL = 0, + RESTART_FIRST_AIO = 1, + LOGIN_APP = 2, + SINGEL_PULL_NOTIFY = 3, + TRIGGER_SPECIFIC_EMOJI_RANDOM_RESULT = 4, } export interface PullSysEmojisReq { - fetchAdvaceSource: boolean; - fetchBaseSource: boolean; - pullMoment: PullMomentType; - pullType: number; - refresh: boolean; - thresholdValue: number; + fetchAdvaceSource: boolean; + fetchBaseSource: boolean; + pullMoment: PullMomentType; + pullType: number; + refresh: boolean; + thresholdValue: number; } export enum BaseEmojiType { - NORMAL_EMOJI = 0, - SUPER_EMOJI = 1, - RANDOM_SUPER_EMOJI = 2, - CHAIN_SUPER_EMOJI = 3, - EMOJI_EMOJI = 4 + NORMAL_EMOJI = 0, + SUPER_EMOJI = 1, + RANDOM_SUPER_EMOJI = 2, + CHAIN_SUPER_EMOJI = 3, + EMOJI_EMOJI = 4, } export interface GetBaseEmojiPathReq { - emojiId: string; - type: BaseEmojiType; + emojiId: string; + type: BaseEmojiType; } export enum EmojiPanelCategory { - OTHER_PANEL = 0, - NORMAL_PANEL = 1, - SUPER_PANEL = 2, - RED_HEART_PANEL = 3 + OTHER_PANEL = 0, + NORMAL_PANEL = 1, + SUPER_PANEL = 2, + RED_HEART_PANEL = 3, } export interface DownloadBaseEmojiInfo { - baseResDownloadUrl: string; - advancedResDownloadUrl: string; + baseResDownloadUrl: string; + advancedResDownloadUrl: string; } export interface DownloadBaseEmojiByUrlReq { - emojiId: string; - groupName: string; - panelCategory: EmojiPanelCategory; - downloadInfo: DownloadBaseEmojiInfo; + emojiId: string; + groupName: string; + panelCategory: EmojiPanelCategory; + downloadInfo: DownloadBaseEmojiInfo; } export interface DownloadBaseEmojiByIdReq { - emojiId: string; - groupName: string; - panelCategory: EmojiPanelCategory; - qzoneCode: string; + emojiId: string; + groupName: string; + panelCategory: EmojiPanelCategory; + qzoneCode: string; } diff --git a/src/core/types/file.ts b/src/core/types/file.ts index 02d21ff6..1a08d609 100644 --- a/src/core/types/file.ts +++ b/src/core/types/file.ts @@ -1,5 +1,5 @@ export interface rkeyDataType { - private_rkey: string; - group_rkey: string; - online_rkey: boolean; -}; \ No newline at end of file + private_rkey: string; + group_rkey: string; + online_rkey: boolean; +} diff --git a/src/core/types/graytip.ts b/src/core/types/graytip.ts index 4814919f..49462ecc 100644 --- a/src/core/types/graytip.ts +++ b/src/core/types/graytip.ts @@ -1,74 +1,74 @@ export enum JsonGrayBusiId { - AIO_AV_C2C_NOTICE = 2021, - AIO_AV_GROUP_NOTICE = 2022, - AIO_C2C_DONT_DISTURB = 2100, - AIO_CRM_FLAGS_TIPS = 2050, - AIO_GROUP_ESSENCE_MSG_TIP = 2401, - AIO_NUDGE_CUSTOM_GUIDE = 2041, - AIO_PUSH_GUIDE_GRAY_TIPS = 2701, - AIO_RECALL_MSGCUSTOM_WORDINGGUIDE = 2000, - AIO_ROBOT_SAFETY_TIP = 2201, - AIO_ZPLAN_EMOTICON_GUIDE = 2301, - AIO_ZPLAN_SCENE_LINKAGE = 2302, - AIO_ZPLAN_SEND_MEME = 2300, - DISBAND_DISCUSSION_GRAY_TIP_ID = 2603, - FILE_SENDING_SIZE_4GB_LIMIT = 3003, - GROUP_AIO_CONFIGURABLE_GRAY_TIPS = 2407, - GROUP_AIO_HOME_SCHOOL_WELCOME_GRAY_TIP_ID = 2404, - GROUP_AIO_MSG_FREQUENCY_GRAY_TIP_ID = 2406, - GROUP_AIO_SHUTUP_GRAY_TIP_ID = 2402, - GROUP_AIO_TEMPORARY_GRAY_TIP_ID = 2405, - GROUP_AIO_UNREAD_MSG_AI_SUMMARY = 2408, - GROUP_AIO_UPLOAD_PERMISSIONS_GRAY_TIP_ID = 2403, - LITE_ACTION = 86, - ONLINE_FILE_CANCEL_RECV_ON_RECVING = 4, - ONLINE_FILE_GO_OFFLINE = 11, - ONLINE_FILE_GO_OFFLINE_ALL = 12, - ONLINE_FILE_RECV_BY_MOBILE = 13, - ONLINE_FILE_RECV_ERROR = 10, - ONLINE_FILE_REFUSE_ALL_RECV = 7, - ONLINE_FILE_REFUSE_ALL_RECV_ON_RECVING = 8, - ONLINE_FILE_REFUSE_RECV = 3, - ONLINE_FILE_SEND_ERROR = 9, - ONLINE_FILE_STOP_ALL_SEND = 5, - ONLINE_FILE_STOP_ALL_SEND_ON_SENDING = 6, - ONLINE_FILE_STOP_SEND = 1, - ONLINE_FILE_STOP_SEND_ON_SENDING = 2, - ONLINE_GROUP_HOME_WORK = 51, - PTT_AUTO_CHANGE_GUIDE = 2060, - QCIRCLE_SHOW_FULE_TIPS = 2601, - QWALLET_GRAY_TIP_ID = 2602, - RED_BAG = 81, - RELATION_C2C_GROUP_AIO_SETUP_GROUP_AND_REMARK = 1005, - RELATION_C2C_LOVER_BONUS = 1003, - RELATION_C2C_MEMBER_ADD = 1017, - RELATION_C2C_REACTIVE_DEGRADE_MSG = 1019, - RELATION_C2C_REACTIVE_UPGRADE_MSG = 1018, - RELATION_C2C_SAY_HELLO = 1004, - RELATION_CHAIN_BLACKED = 1000, - RELATION_CHAIN_MATCH_FRIEND = 1007, - RELATION_CREATE_GROUP_GRAY_TIP_ID = 1009, - RELATION_EMOJIEGG_SHOW = 1001, - RELATION_EMOJIEGG_WILL_DEGRADE = 1002, - RELATION_FRIEND_CLONE_INFO = 1006, - RELATION_GROUP_BATCH_ADD_FRIEND = 1020, - RELATION_GROUP_MEMBER_ADD = 1022, - RELATION_GROUP_MEMBER_ADD_WITH_MODIFY_NAME = 1015, - RELATION_GROUP_MEMBER_ADD_WITH_WELCOME = 1016, - RELATION_GROUP_MEMBER_RECOMMEND = 1021, - RELATION_GROUP_SHUT_UP = 1014, - RELATION_LIMIT_TMP_CONVERSATION_SET = 1011, - RELATION_NEARBY_GOTO_VERIFY = 1008, - RELATION_ONEWAY_FRIEND_GRAY_TIP_ID = 1012, - RELATION_ONEWAY_FRIEND_NEW_GRAY_TIP_ID = 1013, - RELATION_YQT = 1010, - TROOP_ADD_FRIEND_ACTIVE = 19264, - TROOP_ADD_FRIEND_HOT_CHAT = 19265, - TROOP_ADD_FRIEND_NEW_MEMBER = 19267, - TROOP_ADD_FRIEND_REPLY_OR_AT = 19266, - TROOP_BREAK_ICE = 10405, - TROOP_FLAME_IGNITED = 19273, - UI_RESERVE_100000_110000 = 100000, - VAS_FILE_UPLOAD_OVER_1G = 3002, - VAS_FILE_UPLOAD_OVER_LIMIT = 3001, + AIO_AV_C2C_NOTICE = 2021, + AIO_AV_GROUP_NOTICE = 2022, + AIO_C2C_DONT_DISTURB = 2100, + AIO_CRM_FLAGS_TIPS = 2050, + AIO_GROUP_ESSENCE_MSG_TIP = 2401, + AIO_NUDGE_CUSTOM_GUIDE = 2041, + AIO_PUSH_GUIDE_GRAY_TIPS = 2701, + AIO_RECALL_MSGCUSTOM_WORDINGGUIDE = 2000, + AIO_ROBOT_SAFETY_TIP = 2201, + AIO_ZPLAN_EMOTICON_GUIDE = 2301, + AIO_ZPLAN_SCENE_LINKAGE = 2302, + AIO_ZPLAN_SEND_MEME = 2300, + DISBAND_DISCUSSION_GRAY_TIP_ID = 2603, + FILE_SENDING_SIZE_4GB_LIMIT = 3003, + GROUP_AIO_CONFIGURABLE_GRAY_TIPS = 2407, + GROUP_AIO_HOME_SCHOOL_WELCOME_GRAY_TIP_ID = 2404, + GROUP_AIO_MSG_FREQUENCY_GRAY_TIP_ID = 2406, + GROUP_AIO_SHUTUP_GRAY_TIP_ID = 2402, + GROUP_AIO_TEMPORARY_GRAY_TIP_ID = 2405, + GROUP_AIO_UNREAD_MSG_AI_SUMMARY = 2408, + GROUP_AIO_UPLOAD_PERMISSIONS_GRAY_TIP_ID = 2403, + LITE_ACTION = 86, + ONLINE_FILE_CANCEL_RECV_ON_RECVING = 4, + ONLINE_FILE_GO_OFFLINE = 11, + ONLINE_FILE_GO_OFFLINE_ALL = 12, + ONLINE_FILE_RECV_BY_MOBILE = 13, + ONLINE_FILE_RECV_ERROR = 10, + ONLINE_FILE_REFUSE_ALL_RECV = 7, + ONLINE_FILE_REFUSE_ALL_RECV_ON_RECVING = 8, + ONLINE_FILE_REFUSE_RECV = 3, + ONLINE_FILE_SEND_ERROR = 9, + ONLINE_FILE_STOP_ALL_SEND = 5, + ONLINE_FILE_STOP_ALL_SEND_ON_SENDING = 6, + ONLINE_FILE_STOP_SEND = 1, + ONLINE_FILE_STOP_SEND_ON_SENDING = 2, + ONLINE_GROUP_HOME_WORK = 51, + PTT_AUTO_CHANGE_GUIDE = 2060, + QCIRCLE_SHOW_FULE_TIPS = 2601, + QWALLET_GRAY_TIP_ID = 2602, + RED_BAG = 81, + RELATION_C2C_GROUP_AIO_SETUP_GROUP_AND_REMARK = 1005, + RELATION_C2C_LOVER_BONUS = 1003, + RELATION_C2C_MEMBER_ADD = 1017, + RELATION_C2C_REACTIVE_DEGRADE_MSG = 1019, + RELATION_C2C_REACTIVE_UPGRADE_MSG = 1018, + RELATION_C2C_SAY_HELLO = 1004, + RELATION_CHAIN_BLACKED = 1000, + RELATION_CHAIN_MATCH_FRIEND = 1007, + RELATION_CREATE_GROUP_GRAY_TIP_ID = 1009, + RELATION_EMOJIEGG_SHOW = 1001, + RELATION_EMOJIEGG_WILL_DEGRADE = 1002, + RELATION_FRIEND_CLONE_INFO = 1006, + RELATION_GROUP_BATCH_ADD_FRIEND = 1020, + RELATION_GROUP_MEMBER_ADD = 1022, + RELATION_GROUP_MEMBER_ADD_WITH_MODIFY_NAME = 1015, + RELATION_GROUP_MEMBER_ADD_WITH_WELCOME = 1016, + RELATION_GROUP_MEMBER_RECOMMEND = 1021, + RELATION_GROUP_SHUT_UP = 1014, + RELATION_LIMIT_TMP_CONVERSATION_SET = 1011, + RELATION_NEARBY_GOTO_VERIFY = 1008, + RELATION_ONEWAY_FRIEND_GRAY_TIP_ID = 1012, + RELATION_ONEWAY_FRIEND_NEW_GRAY_TIP_ID = 1013, + RELATION_YQT = 1010, + TROOP_ADD_FRIEND_ACTIVE = 19264, + TROOP_ADD_FRIEND_HOT_CHAT = 19265, + TROOP_ADD_FRIEND_NEW_MEMBER = 19267, + TROOP_ADD_FRIEND_REPLY_OR_AT = 19266, + TROOP_BREAK_ICE = 10405, + TROOP_FLAME_IGNITED = 19273, + UI_RESERVE_100000_110000 = 100000, + VAS_FILE_UPLOAD_OVER_1G = 3002, + VAS_FILE_UPLOAD_OVER_LIMIT = 3001, } diff --git a/src/core/types/group.ts b/src/core/types/group.ts index 3418d24f..3d229b75 100644 --- a/src/core/types/group.ts +++ b/src/core/types/group.ts @@ -1,45 +1,352 @@ import { QQLevel, NTSex } from './user'; export interface GroupExtInfo { - groupCode: string; - resultCode: number; - extInfo: EXTInfo; + groupCode: string; + resultCode: number; + extInfo: EXTInfo; } export interface GroupExtFilter { - groupInfoExtSeq: number; - reserve: number; - luckyWordId: number; - lightCharNum: number; - luckyWord: number; - starId: number; - essentialMsgSwitch: number; - todoSeq: number; - blacklistExpireTime: number; - isLimitGroupRtc: number; - companyId: number; - hasGroupCustomPortrait: number; - bindGuildId: number; - groupOwnerId: number; - essentialMsgPrivilege: number; - msgEventSeq: number; - inviteRobotSwitch: number; - gangUpId: number; - qqMusicMedalSwitch: number; - showPlayTogetherSwitch: number; - groupFlagPro1: number; - groupBindGuildIds: number; - viewedMsgDisappearTime: number; - groupExtFlameData: number; - groupBindGuildSwitch: number; - groupAioBindGuildId: number; - groupExcludeGuildIds: number; - fullGroupExpansionSwitch: number; - fullGroupExpansionSeq: number; - inviteRobotMemberSwitch: number; - inviteRobotMemberExamine: number; - groupSquareSwitch: number; -}; + groupInfoExtSeq: number; + reserve: number; + luckyWordId: number; + lightCharNum: number; + luckyWord: number; + starId: number; + essentialMsgSwitch: number; + todoSeq: number; + blacklistExpireTime: number; + isLimitGroupRtc: number; + companyId: number; + hasGroupCustomPortrait: number; + bindGuildId: number; + groupOwnerId: number; + essentialMsgPrivilege: number; + msgEventSeq: number; + inviteRobotSwitch: number; + gangUpId: number; + qqMusicMedalSwitch: number; + showPlayTogetherSwitch: number; + groupFlagPro1: number; + groupBindGuildIds: number; + viewedMsgDisappearTime: number; + groupExtFlameData: number; + groupBindGuildSwitch: number; + groupAioBindGuildId: number; + groupExcludeGuildIds: number; + fullGroupExpansionSwitch: number; + fullGroupExpansionSeq: number; + inviteRobotMemberSwitch: number; + inviteRobotMemberExamine: number; + groupSquareSwitch: number; +} export interface EXTInfo { + groupInfoExtSeq: number; + reserve: number; + luckyWordId: string; + lightCharNum: number; + luckyWord: string; + starId: number; + essentialMsgSwitch: number; + todoSeq: number; + blacklistExpireTime: number; + isLimitGroupRtc: number; + companyId: number; + hasGroupCustomPortrait: number; + bindGuildId: string; + groupOwnerId: GroupOwnerID; + essentialMsgPrivilege: number; + msgEventSeq: string; + inviteRobotSwitch: number; + gangUpId: string; + qqMusicMedalSwitch: number; + showPlayTogetherSwitch: number; + groupFlagPro1: string; + groupBindGuildIds: GroupGuildIDS; + viewedMsgDisappearTime: string; + groupExtFlameData: GroupEXTFlameData; + groupBindGuildSwitch: number; + groupAioBindGuildId: string; + groupExcludeGuildIds: GroupGuildIDS; + fullGroupExpansionSwitch: number; + fullGroupExpansionSeq: string; + inviteRobotMemberSwitch: number; + inviteRobotMemberExamine: number; + groupSquareSwitch: number; +} + +export interface GroupGuildIDS { + guildIds: any[]; +} + +export interface GroupEXTFlameData { + switchState: number; + state: number; + dayNums: any[]; + version: number; + updateTime: string; + isDisplayDayNum: boolean; +} + +export interface GroupOwnerID { + memberUin: string; + memberUid: string; + memberQid: string; +} + +export interface KickMemberInfo { + optFlag: number; + optOperate: number; + optMemberUid: string; + optBytesMsg: string; +} + +export interface GroupDetailInfoV2Param { + groupCode: string; + filter: Filter; + modifyInfo: ModifyInfo; +} + +export interface Filter { + noCodeFingerOpenFlag: number; + noFingerOpenFlag: number; + groupName: number; + classExt: number; + classText: number; + fingerMemo: number; + richFingerMemo: number; + tagRecord: number; + groupGeoInfo: FilterGroupGeoInfo; + groupExtAdminNum: number; + flag: number; + groupMemo: number; + groupAioSkinUrl: number; + groupBoardSkinUrl: number; + groupCoverSkinUrl: number; + groupGrade: number; + activeMemberNum: number; + certificationType: number; + certificationText: number; + groupNewGuideLines: FilterGroupNewGuideLines; + groupFace: number; + addOption: number; + shutUpTime: number; + groupTypeFlag: number; + appPrivilegeFlag: number; + appPrivilegeMask: number; + groupExtOnly: GroupEXTOnly; + groupSecLevel: number; + groupSecLevelInfo: number; + subscriptionUin: number; + subscriptionUid: string; + allowMemberInvite: number; + groupQuestion: number; + groupAnswer: number; + groupFlagExt3: number; + groupFlagExt3Mask: number; + groupOpenAppid: number; + rootId: number; + msgLimitFrequency: number; + hlGuildAppid: number; + hlGuildSubType: number; + hlGuildOrgId: number; + groupFlagExt4: number; + groupFlagExt4Mask: number; + groupSchoolInfo: FilterGroupSchoolInfo; + groupCardPrefix: FilterGroupCardPrefix; + allianceId: number; + groupFlagPro1: number; + groupFlagPro1Mask: number; +} + +export interface FilterGroupCardPrefix { + introduction: number; + rptPrefix: number; +} + +export interface GroupEXTOnly { + tribeId: number; + moneyForAddGroup: number; +} + +export interface FilterGroupGeoInfo { + ownerUid: number; + setTime: number; + cityId: number; + longitude: number; + latitude: number; + geoContent: number; + poiId: number; +} + +export interface FilterGroupNewGuideLines { + enabled: number; + content: number; +} + +export interface FilterGroupSchoolInfo { + location: number; + grade: number; + school: number; +} + +export interface ModifyInfo { + noCodeFingerOpenFlag: number; + noFingerOpenFlag: number; + groupName: string; + classExt: number; + classText: string; + fingerMemo: string; + richFingerMemo: string; + tagRecord: any[]; + groupGeoInfo: ModifyInfoGroupGeoInfo; + groupExtAdminNum: number; + flag: number; + groupMemo: string; + groupAioSkinUrl: string; + groupBoardSkinUrl: string; + groupCoverSkinUrl: string; + groupGrade: number; + activeMemberNum: number; + certificationType: number; + certificationText: string; + groupNewGuideLines: ModifyInfoGroupNewGuideLines; + groupFace: number; + addOption: number;// 0 空设置 1 任何人都可以进入 2 需要管理员批准 3 不允许任何人入群 4 问题进入答案 5 问题管理员批准 + shutUpTime: number; + groupTypeFlag: number; + appPrivilegeFlag: number; + // 需要管理员审核 + // 0000 0000 0000 0000 0000 0000 0000 + // 无需审核入群 + // 0000 0001 0000 0000 0000 0000 0000 + // 成员数100内无审核 + // 0100 0000 0000 0000 0000 0000 0000 + // 禁用 群成员邀请好友 + // 0100 0000 0000 0000 0000 0000 0000 + + appPrivilegeMask: number; + // 0110 0001 0000 0000 0000 0000 0000 + // 101711872 + groupExtOnly: GroupEXTOnly; + groupSecLevel: number; + groupSecLevelInfo: number; + subscriptionUin: string; + subscriptionUid: string; + allowMemberInvite: number; + groupQuestion: string; + groupAnswer: string; + groupFlagExt3: number; + groupFlagExt3Mask: number; + groupOpenAppid: number; + rootId: string; + msgLimitFrequency: number; + hlGuildAppid: number; + hlGuildSubType: number; + hlGuildOrgId: number; + groupFlagExt4: number; + groupFlagExt4Mask: number; + groupSchoolInfo: ModifyInfoGroupSchoolInfo; + groupCardPrefix: ModifyInfoGroupCardPrefix; + allianceId: string; + groupFlagPro1: number; + groupFlagPro1Mask: number; +} + +export interface ModifyInfoGroupCardPrefix { + introduction: string; + rptPrefix: any[]; +} + +export interface ModifyInfoGroupGeoInfo { + ownerUid: string; + SetTime: number; + CityId: number; + Longitude: string; + Latitude: string; + GeoContent: string; + poiId: string; +} + +export interface ModifyInfoGroupNewGuideLines { + enabled: boolean; + content: string; +} + +export interface ModifyInfoGroupSchoolInfo { + location: string; + grade: number; + school: string; +} + +// 获取群详细信息的来源类型 +export enum GroupInfoSource { + KUNSPECIFIED, + KBIGDATACARD, + KDATACARD, + KNOTICE, + KAIO, + KRECENTCONTACT, + KMOREPANEL, +} +export interface GroupDetailInfo { + groupCode: string; + groupUin: string; + ownerUid: string; + ownerUin: string; + groupFlag: number; + groupFlagExt: number; + maxMemberNum: number; + memberNum: number; + groupOption: number; + classExt: number; + groupName: string; + fingerMemo: string; + groupQuestion: string; + certType: number; + richFingerMemo: string; + tagRecord: unknown[]; + shutUpAllTimestamp: number; + shutUpMeTimestamp: number; + groupTypeFlag: number; + privilegeFlag: number; + groupSecLevel: number; + groupFlagExt3: number; + isConfGroup: number; + isModifyConfGroupFace: number; + isModifyConfGroupName: number; + groupFlagExt4: number; + groupMemo: string; + cmdUinMsgSeq: number; + cmdUinJoinTime: number; + cmdUinUinFlag: number; + cmdUinMsgMask: number; + groupSecLevelInfo: number; + cmdUinPrivilege: number; + cmdUinFlagEx2: number; + appealDeadline: number; + remarkName: string; + isTop: boolean; + groupFace: number; + groupGeoInfo: { + ownerUid: string; + SetTime: number; + CityId: number; + Longitude: string; + Latitude: string; + GeoContent: string; + poiId: string; + }; + certificationText: string; + cmdUinRingtoneId: number; + longGroupName: string; + autoAgreeJoinGroupUserNumForConfGroup: number; + autoAgreeJoinGroupUserNumForNormalGroup: number; + cmdUinFlagExt3Grocery: number; + groupCardPrefix: { + introduction: string; + rptPrefix: unknown[]; + }; + groupExt: { groupInfoExtSeq: number; reserve: number; luckyWordId: string; @@ -53,7 +360,11 @@ export interface EXTInfo { companyId: number; hasGroupCustomPortrait: number; bindGuildId: string; - groupOwnerId: GroupOwnerID; + groupOwnerId: { + memberUin: string; + memberUid: string; + memberQid: string; + }; essentialMsgPrivilege: number; msgEventSeq: string; inviteRobotSwitch: number; @@ -61,509 +372,197 @@ export interface EXTInfo { qqMusicMedalSwitch: number; showPlayTogetherSwitch: number; groupFlagPro1: string; - groupBindGuildIds: GroupGuildIDS; + groupBindGuildIds: { + guildIds: unknown[]; + }; viewedMsgDisappearTime: string; - groupExtFlameData: GroupEXTFlameData; + groupExtFlameData: { + switchState: number; + state: number; + dayNums: unknown[]; + version: number; + updateTime: string; + isDisplayDayNum: boolean; + }; groupBindGuildSwitch: number; groupAioBindGuildId: string; - groupExcludeGuildIds: GroupGuildIDS; + groupExcludeGuildIds: { + guildIds: unknown[]; + }; fullGroupExpansionSwitch: number; fullGroupExpansionSeq: string; inviteRobotMemberSwitch: number; inviteRobotMemberExamine: number; groupSquareSwitch: number; -} - -export interface GroupGuildIDS { - guildIds: any[]; -} - -export interface GroupEXTFlameData { - switchState: number; - state: number; - dayNums: any[]; - version: number; - updateTime: string; - isDisplayDayNum: boolean; -} - -export interface GroupOwnerID { - memberUin: string; - memberUid: string; - memberQid: string; -} - -export interface KickMemberInfo { - optFlag: number; - optOperate: number; - optMemberUid: string; - optBytesMsg: string; -} - - -export interface GroupDetailInfoV2Param { - groupCode: string; - filter: Filter; - modifyInfo: ModifyInfo; -} - -export interface Filter { - noCodeFingerOpenFlag: number; - noFingerOpenFlag: number; - groupName: number; - classExt: number; - classText: number; - fingerMemo: number; - richFingerMemo: number; - tagRecord: number; - groupGeoInfo: FilterGroupGeoInfo; - groupExtAdminNum: number; - flag: number; - groupMemo: number; - groupAioSkinUrl: number; - groupBoardSkinUrl: number; - groupCoverSkinUrl: number; - groupGrade: number; - activeMemberNum: number; - certificationType: number; - certificationText: number; - groupNewGuideLines: FilterGroupNewGuideLines; - groupFace: number; - addOption: number; - shutUpTime: number; - groupTypeFlag: number; - appPrivilegeFlag: number; - appPrivilegeMask: number; - groupExtOnly: GroupEXTOnly; - groupSecLevel: number; - groupSecLevelInfo: number; - subscriptionUin: number; - subscriptionUid: string; - allowMemberInvite: number; - groupQuestion: number; - groupAnswer: number; - groupFlagExt3: number; - groupFlagExt3Mask: number; - groupOpenAppid: number; - rootId: number; - msgLimitFrequency: number; - hlGuildAppid: number; - hlGuildSubType: number; - hlGuildOrgId: number; - groupFlagExt4: number; - groupFlagExt4Mask: number; - groupSchoolInfo: FilterGroupSchoolInfo; - groupCardPrefix: FilterGroupCardPrefix; - allianceId: number; - groupFlagPro1: number; - groupFlagPro1Mask: number; -} - -export interface FilterGroupCardPrefix { - introduction: number; - rptPrefix: number; -} - -export interface GroupEXTOnly { - tribeId: number; - moneyForAddGroup: number; -} - -export interface FilterGroupGeoInfo { - ownerUid: number; - setTime: number; - cityId: number; - longitude: number; - latitude: number; - geoContent: number; - poiId: number; -} - -export interface FilterGroupNewGuideLines { - enabled: number; - content: number; -} - -export interface FilterGroupSchoolInfo { - location: number; - grade: number; - school: number; -} - -export interface ModifyInfo { - noCodeFingerOpenFlag: number; - noFingerOpenFlag: number; - groupName: string; - classExt: number; - classText: string; - fingerMemo: string; - richFingerMemo: string; - tagRecord: any[]; - groupGeoInfo: ModifyInfoGroupGeoInfo; - groupExtAdminNum: number; - flag: number; - groupMemo: string; - groupAioSkinUrl: string; - groupBoardSkinUrl: string; - groupCoverSkinUrl: string; - groupGrade: number; - activeMemberNum: number; - certificationType: number; - certificationText: string; - groupNewGuideLines: ModifyInfoGroupNewGuideLines; - groupFace: number; - addOption: number;// 0 空设置 1 任何人都可以进入 2 需要管理员批准 3 不允许任何人入群 4 问题进入答案 5 问题管理员批准 - shutUpTime: number; - groupTypeFlag: number; - appPrivilegeFlag: number; - // 需要管理员审核 - // 0000 0000 0000 0000 0000 0000 0000 - // 无需审核入群 - // 0000 0001 0000 0000 0000 0000 0000 - // 成员数100内无审核 - // 0100 0000 0000 0000 0000 0000 0000 - // 禁用 群成员邀请好友 - // 0100 0000 0000 0000 0000 0000 0000 - - appPrivilegeMask: number; - // 0110 0001 0000 0000 0000 0000 0000 - // 101711872 - groupExtOnly: GroupEXTOnly; - groupSecLevel: number; - groupSecLevelInfo: number; - subscriptionUin: string; - subscriptionUid: string; - allowMemberInvite: number; - groupQuestion: string; - groupAnswer: string; - groupFlagExt3: number; - groupFlagExt3Mask: number; - groupOpenAppid: number; - rootId: string; - msgLimitFrequency: number; - hlGuildAppid: number; - hlGuildSubType: number; - hlGuildOrgId: number; - groupFlagExt4: number; - groupFlagExt4Mask: number; - groupSchoolInfo: ModifyInfoGroupSchoolInfo; - groupCardPrefix: ModifyInfoGroupCardPrefix; - allianceId: string; - groupFlagPro1: number; - groupFlagPro1Mask: number; -} - -export interface ModifyInfoGroupCardPrefix { - introduction: string; - rptPrefix: any[]; -} - -export interface ModifyInfoGroupGeoInfo { - ownerUid: string; - SetTime: number; - CityId: number; - Longitude: string; - Latitude: string; - GeoContent: string; - poiId: string; -} - -export interface ModifyInfoGroupNewGuideLines { - enabled: boolean; - content: string; -} - -export interface ModifyInfoGroupSchoolInfo { + }; + msgLimitFrequency: number; + hlGuildAppid: number; + hlGuildSubType: number; + isAllowRecallMsg: number; + confUin: string; + confMaxMsgSeq: number; + confToGroupTime: number; + groupSchoolInfo: { location: string; grade: number; school: string; -} - -// 获取群详细信息的来源类型 -export enum GroupInfoSource { - KUNSPECIFIED, - KBIGDATACARD, - KDATACARD, - KNOTICE, - KAIO, - KRECENTCONTACT, - KMOREPANEL -} -export interface GroupDetailInfo { - groupCode: string; - groupUin: string; - ownerUid: string; - ownerUin: string; - groupFlag: number; - groupFlagExt: number; - maxMemberNum: number; - memberNum: number; - groupOption: number; - classExt: number; - groupName: string; - fingerMemo: string; - groupQuestion: string; - certType: number; - richFingerMemo: string; - tagRecord: unknown[]; - shutUpAllTimestamp: number; - shutUpMeTimestamp: number; - groupTypeFlag: number; - privilegeFlag: number; - groupSecLevel: number; - groupFlagExt3: number; - isConfGroup: number; - isModifyConfGroupFace: number; - isModifyConfGroupName: number; - groupFlagExt4: number; - groupMemo: string; - cmdUinMsgSeq: number; - cmdUinJoinTime: number; - cmdUinUinFlag: number; - cmdUinMsgMask: number; - groupSecLevelInfo: number; - cmdUinPrivilege: number; - cmdUinFlagEx2: number; - appealDeadline: number; - remarkName: string; - isTop: boolean; - groupFace: number; - groupGeoInfo: { - ownerUid: string; - SetTime: number; - CityId: number; - Longitude: string; - Latitude: string; - GeoContent: string; - poiId: string; - }; - certificationText: string; - cmdUinRingtoneId: number; - longGroupName: string; - autoAgreeJoinGroupUserNumForConfGroup: number; - autoAgreeJoinGroupUserNumForNormalGroup: number; - cmdUinFlagExt3Grocery: number; - groupCardPrefix: { - introduction: string; - rptPrefix: unknown[]; - }; - groupExt: { - groupInfoExtSeq: number; - reserve: number; - luckyWordId: string; - lightCharNum: number; - luckyWord: string; - starId: number; - essentialMsgSwitch: number; - todoSeq: number; - blacklistExpireTime: number; - isLimitGroupRtc: number; - companyId: number; - hasGroupCustomPortrait: number; - bindGuildId: string; - groupOwnerId: { - memberUin: string; - memberUid: string; - memberQid: string; - }; - essentialMsgPrivilege: number; - msgEventSeq: string; - inviteRobotSwitch: number; - gangUpId: string; - qqMusicMedalSwitch: number; - showPlayTogetherSwitch: number; - groupFlagPro1: string; - groupBindGuildIds: { - guildIds: unknown[]; - }; - viewedMsgDisappearTime: string; - groupExtFlameData: { - switchState: number; - state: number; - dayNums: unknown[]; - version: number; - updateTime: string; - isDisplayDayNum: boolean; - }; - groupBindGuildSwitch: number; - groupAioBindGuildId: string; - groupExcludeGuildIds: { - guildIds: unknown[]; - }; - fullGroupExpansionSwitch: number; - fullGroupExpansionSeq: string; - inviteRobotMemberSwitch: number; - inviteRobotMemberExamine: number; - groupSquareSwitch: number; - }; - msgLimitFrequency: number; - hlGuildAppid: number; - hlGuildSubType: number; - isAllowRecallMsg: number; - confUin: string; - confMaxMsgSeq: number; - confToGroupTime: number; - groupSchoolInfo: { - location: string; - grade: number; - school: string; - }; - activeMemberNum: number; - groupGrade: number; - groupCreateTime: number; - subscriptionUin: string; - subscriptionUid: string; - noFingerOpenFlag: number; - noCodeFingerOpenFlag: number; - isGroupFreeze: number; - allianceId: string; - groupExtOnly: { - tribeId: number; - moneyForAddGroup: number; - }; - isAllowConfGroupMemberModifyGroupName: number; - isAllowConfGroupMemberNick: number; - isAllowConfGroupMemberAtAll: number; - groupClassText: string; - groupFreezeReason: number; - headPortraitSeq: number; - groupHeadPortrait: { - portraitCnt: number; - portraitInfo: unknown[]; - defaultId: number; - verifyingPortraitCnt: number; - verifyingPortraitInfo: unknown[]; - }; - cmdUinJoinMsgSeq: number; - cmdUinJoinRealMsgSeq: number; - groupAnswer: string; - groupAdminMaxNum: number; - inviteNoAuthNumLimit: string; - hlGuildOrgId: number; - isAllowHlGuildBinary: number; - localExitGroupReason: number; + }; + activeMemberNum: number; + groupGrade: number; + groupCreateTime: number; + subscriptionUin: string; + subscriptionUid: string; + noFingerOpenFlag: number; + noCodeFingerOpenFlag: number; + isGroupFreeze: number; + allianceId: string; + groupExtOnly: { + tribeId: number; + moneyForAddGroup: number; + }; + isAllowConfGroupMemberModifyGroupName: number; + isAllowConfGroupMemberNick: number; + isAllowConfGroupMemberAtAll: number; + groupClassText: string; + groupFreezeReason: number; + headPortraitSeq: number; + groupHeadPortrait: { + portraitCnt: number; + portraitInfo: unknown[]; + defaultId: number; + verifyingPortraitCnt: number; + verifyingPortraitInfo: unknown[]; + }; + cmdUinJoinMsgSeq: number; + cmdUinJoinRealMsgSeq: number; + groupAnswer: string; + groupAdminMaxNum: number; + inviteNoAuthNumLimit: string; + hlGuildOrgId: number; + isAllowHlGuildBinary: number; + localExitGroupReason: number; } export interface GroupExt0xEF0InfoFilter { - bindGuildId: number; - blacklistExpireTime: number; - companyId: number; - essentialMsgPrivilege: number; - essentialMsgSwitch: number; - fullGroupExpansionSeq: number; - fullGroupExpansionSwitch: number; - gangUpId: number; - groupAioBindGuildId: number; - groupBindGuildIds: number; - groupBindGuildSwitch: number; - groupExcludeGuildIds: number; - groupExtFlameData: number; - groupFlagPro1: number; - groupInfoExtSeq: number; - groupOwnerId: number; - groupSquareSwitch: number; - hasGroupCustomPortrait: number; - inviteRobotMemberExamine: number; - inviteRobotMemberSwitch: number; - inviteRobotSwitch: number; - isLimitGroupRtc: number; - lightCharNum: number; - luckyWord: number; - luckyWordId: number; - msgEventSeq: number; - qqMusicMedalSwitch: number; - reserve: number; - showPlayTogetherSwitch: number; - starId: number; - todoSeq: number; - viewedMsgDisappearTime: number; + bindGuildId: number; + blacklistExpireTime: number; + companyId: number; + essentialMsgPrivilege: number; + essentialMsgSwitch: number; + fullGroupExpansionSeq: number; + fullGroupExpansionSwitch: number; + gangUpId: number; + groupAioBindGuildId: number; + groupBindGuildIds: number; + groupBindGuildSwitch: number; + groupExcludeGuildIds: number; + groupExtFlameData: number; + groupFlagPro1: number; + groupInfoExtSeq: number; + groupOwnerId: number; + groupSquareSwitch: number; + hasGroupCustomPortrait: number; + inviteRobotMemberExamine: number; + inviteRobotMemberSwitch: number; + inviteRobotSwitch: number; + isLimitGroupRtc: number; + lightCharNum: number; + luckyWord: number; + luckyWordId: number; + msgEventSeq: number; + qqMusicMedalSwitch: number; + reserve: number; + showPlayTogetherSwitch: number; + starId: number; + todoSeq: number; + viewedMsgDisappearTime: number; } export interface KickMemberV2Req { - groupCode: string; - kickFlag: number; - kickList: Array; - kickListUids: Array; - kickMsg: string; + groupCode: string; + kickFlag: number; + kickList: Array; + kickListUids: Array; + kickMsg: string; } // 数据来源类型 export enum DataSource { - LOCAL = 0, - REMOTE = 1 + LOCAL = 0, + REMOTE = 1, } // 群列表更新类型 export enum GroupListUpdateType { - REFRESHALL = 0, - GETALL = 1, - MODIFIED = 2, - REMOVE = 3 + REFRESHALL = 0, + GETALL = 1, + MODIFIED = 2, + REMOVE = 3, } export interface GroupMemberCache { - group: { - data: GroupMember[]; - isExpired: boolean; - }; + group: { + data: GroupMember[]; isExpired: boolean; + }; + isExpired: boolean; } export interface Group { - groupCode: string; - createTime?: string; - maxMember: number; - memberCount: number; - groupName: string; - groupStatus: number; - memberRole: number; - isTop: boolean; - toppedTimestamp: string; - privilegeFlag: number; - isConf: boolean; - hasModifyConfGroupFace: boolean; - hasModifyConfGroupName: boolean; - remarkName: string; - hasMemo: boolean; - groupShutupExpireTime: string; - personShutupExpireTime: string; - discussToGroupUin: string; - discussToGroupMaxMsgSeq: number; - discussToGroupTime: number; - groupFlagExt: number; - authGroupType: number; - groupCreditLevel: number; - groupFlagExt3: number; - groupOwnerId: { - memberUin: string; - memberUid: string; - }; + groupCode: string; + createTime?: string; + maxMember: number; + memberCount: number; + groupName: string; + groupStatus: number; + memberRole: number; + isTop: boolean; + toppedTimestamp: string; + privilegeFlag: number; + isConf: boolean; + hasModifyConfGroupFace: boolean; + hasModifyConfGroupName: boolean; + remarkName: string; + hasMemo: boolean; + groupShutupExpireTime: string; + personShutupExpireTime: string; + discussToGroupUin: string; + discussToGroupMaxMsgSeq: number; + discussToGroupTime: number; + groupFlagExt: number; + authGroupType: number; + groupCreditLevel: number; + groupFlagExt3: number; + groupOwnerId: { + memberUin: string; + memberUid: string; + }; } export enum NTGroupMemberRole { - KUNSPECIFIED = 0, - KSTRANGER = 1, - KMEMBER = 2, - KADMIN = 3, - KOWNER = 4 + KUNSPECIFIED = 0, + KSTRANGER = 1, + KMEMBER = 2, + KADMIN = 3, + KOWNER = 4, } export interface GroupMember { - memberRealLevel: number | undefined; - memberSpecialTitle?: string; - avatarPath: string; - cardName: string; - cardType: number; - isDelete: boolean; - nick: string; - qid: string; - remark: string; - role: NTGroupMemberRole; - shutUpTime: number; // 禁言时间(S) - uid: string; - uin: string; - isRobot: boolean; - sex?: NTSex; - age?: number; - qqLevel?: QQLevel; - isChangeRole: boolean; - joinTime: string; - lastSpeakTime: string; -} \ No newline at end of file + memberRealLevel: number | undefined; + memberSpecialTitle?: string; + avatarPath: string; + cardName: string; + cardType: number; + isDelete: boolean; + nick: string; + qid: string; + remark: string; + role: NTGroupMemberRole; + shutUpTime: number; // 禁言时间(S) + uid: string; + uin: string; + isRobot: boolean; + sex?: NTSex; + age?: number; + qqLevel?: QQLevel; + isChangeRole: boolean; + joinTime: string; + lastSpeakTime: string; +} diff --git a/src/core/types/index.ts b/src/core/types/index.ts index c24df15a..d4adf5e0 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -10,4 +10,4 @@ export * from './element'; export * from './constant'; export * from './graytip'; export * from './emoji'; -export * from './service'; \ No newline at end of file +export * from './service'; diff --git a/src/core/types/msg.ts b/src/core/types/msg.ts index e949bbd9..d9772a23 100644 --- a/src/core/types/msg.ts +++ b/src/core/types/msg.ts @@ -9,387 +9,387 @@ import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBub * 表示对等方的信息 */ export interface Peer { - chatType: ChatType; // 聊天类型 - peerUid: string; // 对等方的唯一标识符 - guildId?: string; // 可选的频道ID + chatType: ChatType; // 聊天类型 + peerUid: string; // 对等方的唯一标识符 + guildId?: string; // 可选的频道ID } /** * 表示被踢下线的信息 */ export interface KickedOffLineInfo { - appId: number; // 应用ID - instanceId: number; // 实例ID - sameDevice: boolean; // 是否为同一设备 - tipsDesc: string; // 提示描述 - tipsTitle: string; // 提示标题 - kickedType: number; // 被踢类型 - securityKickedType: number; // 安全踢出类型 + appId: number; // 应用ID + instanceId: number; // 实例ID + sameDevice: boolean; // 是否为同一设备 + tipsDesc: string; // 提示描述 + tipsTitle: string; // 提示标题 + kickedType: number; // 被踢类型 + securityKickedType: number; // 安全踢出类型 } /** * 获取文件列表的参数 */ export interface GetFileListParam { - sortType: number; - fileCount: number; - startIndex: number; - sortOrder: number; - showOnlinedocFolder: number; - folderId?: string; + sortType: number; + fileCount: number; + startIndex: number; + sortOrder: number; + showOnlinedocFolder: number; + folderId?: string; } /** * 消息元素类型枚举 */ export enum ElementType { - UNKNOWN = 0, - TEXT = 1, - PIC = 2, - FILE = 3, - PTT = 4, - VIDEO = 5, - FACE = 6, - REPLY = 7, - GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等 - WALLET = 9, - ARK = 10, - MFACE = 11, - LIVEGIFT = 12, - STRUCTLONGMSG = 13, - MARKDOWN = 14, - GIPHY = 15, - MULTIFORWARD = 16, - INLINEKEYBOARD = 17, - INTEXTGIFT = 18, - CALENDAR = 19, - YOLOGAMERESULT = 20, - AVRECORD = 21, - FEED = 22, - TOFURECORD = 23, - ACEBUBBLE = 24, - ACTIVITY = 25, - TOFU = 26, - FACEBUBBLE = 27, - SHARELOCATION = 28, - TASKTOPMSG = 29, - RECOMMENDEDMSG = 43, - ACTIONBAR = 44 + UNKNOWN = 0, + TEXT = 1, + PIC = 2, + FILE = 3, + PTT = 4, + VIDEO = 5, + FACE = 6, + REPLY = 7, + GreyTip = 8, // “小灰条”,包括拍一拍 (Poke)、撤回提示等 + WALLET = 9, + ARK = 10, + MFACE = 11, + LIVEGIFT = 12, + STRUCTLONGMSG = 13, + MARKDOWN = 14, + GIPHY = 15, + MULTIFORWARD = 16, + INLINEKEYBOARD = 17, + INTEXTGIFT = 18, + CALENDAR = 19, + YOLOGAMERESULT = 20, + AVRECORD = 21, + FEED = 22, + TOFURECORD = 23, + ACEBUBBLE = 24, + ACTIVITY = 25, + TOFU = 26, + FACEBUBBLE = 27, + SHARELOCATION = 28, + TASKTOPMSG = 29, + RECOMMENDEDMSG = 43, + ACTIONBAR = 44, } /** * 消息类型枚举 */ export enum NTMsgType { - KMSGTYPEARKSTRUCT = 11, - KMSGTYPEFACEBUBBLE = 24, - KMSGTYPEFILE = 3, - KMSGTYPEGIFT = 14, - KMSGTYPEGIPHY = 13, - KMSGTYPEGRAYTIPS = 5, - KMSGTYPEMIX = 2, - KMSGTYPEMULTIMSGFORWARD = 8, - KMSGTYPENULL = 1, - KMSGTYPEONLINEFILE = 21, - KMSGTYPEONLINEFOLDER = 27, - KMSGTYPEPROLOGUE = 29, - KMSGTYPEPTT = 6, - KMSGTYPEREPLY = 9, - KMSGTYPESHARELOCATION = 25, - KMSGTYPESTRUCT = 4, - KMSGTYPESTRUCTLONGMSG = 12, - KMSGTYPETEXTGIFT = 15, - KMSGTYPEUNKNOWN = 0, - KMSGTYPEVIDEO = 7, - KMSGTYPEWALLET = 10 + KMSGTYPEARKSTRUCT = 11, + KMSGTYPEFACEBUBBLE = 24, + KMSGTYPEFILE = 3, + KMSGTYPEGIFT = 14, + KMSGTYPEGIPHY = 13, + KMSGTYPEGRAYTIPS = 5, + KMSGTYPEMIX = 2, + KMSGTYPEMULTIMSGFORWARD = 8, + KMSGTYPENULL = 1, + KMSGTYPEONLINEFILE = 21, + KMSGTYPEONLINEFOLDER = 27, + KMSGTYPEPROLOGUE = 29, + KMSGTYPEPTT = 6, + KMSGTYPEREPLY = 9, + KMSGTYPESHARELOCATION = 25, + KMSGTYPESTRUCT = 4, + KMSGTYPESTRUCTLONGMSG = 12, + KMSGTYPETEXTGIFT = 15, + KMSGTYPEUNKNOWN = 0, + KMSGTYPEVIDEO = 7, + KMSGTYPEWALLET = 10, } /** * 图片类型枚举 */ export enum PicType { - NEWPIC_APNG = 2001, - NEWPIC_BMP = 1005, - NEWPIC_GIF = 2000, - NEWPIC_JPEG = 1000, - NEWPIC_PNG = 1001, - NEWPIC_PROGERSSIV_JPEG = 1003, - NEWPIC_SHARPP = 1004, - NEWPIC_WEBP = 1002 + NEWPIC_APNG = 2001, + NEWPIC_BMP = 1005, + NEWPIC_GIF = 2000, + NEWPIC_JPEG = 1000, + NEWPIC_PNG = 1001, + NEWPIC_PROGERSSIV_JPEG = 1003, + NEWPIC_SHARPP = 1004, + NEWPIC_WEBP = 1002, } /** * 图片子类型枚举 */ export enum PicSubType { - KNORMAL = 0, - KCUSTOM = 1, - KHOT = 2, - KDIPPERCHART = 3, - KSMART = 4, - KSPACE = 5, - KUNKNOW = 6, - KRELATED = 7 + KNORMAL = 0, + KCUSTOM = 1, + KHOT = 2, + KDIPPERCHART = 3, + KSMART = 4, + KSPACE = 5, + KUNKNOW = 6, + KRELATED = 7, } /** * 消息AT类型枚举 */ export enum NTMsgAtType { - ATTYPEALL = 1, - ATTYPECATEGORY = 512, - ATTYPECHANNEL = 16, - ATTYPEME = 4, - ATTYPEONE = 2, - ATTYPEONLINE = 64, - ATTYPEROLE = 8, - ATTYPESUMMON = 32, - ATTYPESUMMONONLINE = 128, - ATTYPESUMMONROLE = 256, - ATTYPEUNKNOWN = 0 + ATTYPEALL = 1, + ATTYPECATEGORY = 512, + ATTYPECHANNEL = 16, + ATTYPEME = 4, + ATTYPEONE = 2, + ATTYPEONLINE = 64, + ATTYPEROLE = 8, + ATTYPESUMMON = 32, + ATTYPESUMMONONLINE = 128, + ATTYPESUMMONROLE = 256, + ATTYPEUNKNOWN = 0, } /** * 消息元素接口 */ export interface MessageElement { - elementType: ElementType, - elementId: string, - extBufForUI?: string, //"0x", - textElement?: TextElement; - faceElement?: FaceElement, - marketFaceElement?: MarketFaceElement, - replyElement?: ReplyElement, - picElement?: PicElement, - pttElement?: PttElement, - videoElement?: VideoElement, - grayTipElement?: GrayTipElement, - arkElement?: ArkElement, - fileElement?: FileElement, - liveGiftElement?: null, - markdownElement?: MarkdownElement, - structLongMsgElement?: StructLongMsgElement, - multiForwardMsgElement?: MultiForwardMsgElement, - giphyElement?: GiphyElement, - walletElement?: null, - inlineKeyboardElement?: InlineKeyboardElement, - textGiftElement?: null,//???? - calendarElement?: CalendarElement, - yoloGameResultElement?: YoloGameResultElement, - avRecordElement?: AvRecordElement, - structMsgElement?: null, - faceBubbleElement?: FaceBubbleElement, - shareLocationElement?: ShareLocationElement, - tofuRecordElement?: TofuRecordElement, - taskTopMsgElement?: TaskTopMsgElement, - recommendedMsgElement?: RecommendedMsgElement, - actionBarElement?: ActionBarElement + elementType: ElementType, + elementId: string, + extBufForUI?: string, // "0x", + textElement?: TextElement; + faceElement?: FaceElement, + marketFaceElement?: MarketFaceElement, + replyElement?: ReplyElement, + picElement?: PicElement, + pttElement?: PttElement, + videoElement?: VideoElement, + grayTipElement?: GrayTipElement, + arkElement?: ArkElement, + fileElement?: FileElement, + liveGiftElement?: null, + markdownElement?: MarkdownElement, + structLongMsgElement?: StructLongMsgElement, + multiForwardMsgElement?: MultiForwardMsgElement, + giphyElement?: GiphyElement, + walletElement?: null, + inlineKeyboardElement?: InlineKeyboardElement, + textGiftElement?: null, // ???? + calendarElement?: CalendarElement, + yoloGameResultElement?: YoloGameResultElement, + avRecordElement?: AvRecordElement, + structMsgElement?: null, + faceBubbleElement?: FaceBubbleElement, + shareLocationElement?: ShareLocationElement, + tofuRecordElement?: TofuRecordElement, + taskTopMsgElement?: TaskTopMsgElement, + recommendedMsgElement?: RecommendedMsgElement, + actionBarElement?: ActionBarElement } /** * 消息来源类型枚举 */ export enum MsgSourceType { - K_DOWN_SOURCETYPE_AIOINNER = 1, - K_DOWN_SOURCETYPE_BIGSCREEN = 2, - K_DOWN_SOURCETYPE_HISTORY = 3, - K_DOWN_SOURCETYPE_UNKNOWN = 0 + K_DOWN_SOURCETYPE_AIOINNER = 1, + K_DOWN_SOURCETYPE_BIGSCREEN = 2, + K_DOWN_SOURCETYPE_HISTORY = 3, + K_DOWN_SOURCETYPE_UNKNOWN = 0, } /** * 聊天类型枚举 */ export enum ChatType { - KCHATTYPEADELIE = 42, - KCHATTYPEBUDDYNOTIFY = 5, - KCHATTYPEC2C = 1, - KCHATTYPECIRCLE = 113, - KCHATTYPEDATALINE = 8, - KCHATTYPEDATALINEMQQ = 134, - KCHATTYPEDISC = 3, - KCHATTYPEFAV = 41, - KCHATTYPEGAMEMESSAGE = 105, - KCHATTYPEGAMEMESSAGEFOLDER = 116, - KCHATTYPEGROUP = 2, - KCHATTYPEGROUPBLESS = 133, - KCHATTYPEGROUPGUILD = 9, - KCHATTYPEGROUPHELPER = 7, - KCHATTYPEGROUPNOTIFY = 6, - KCHATTYPEGUILD = 4, - KCHATTYPEGUILDMETA = 16, - KCHATTYPEMATCHFRIEND = 104, - KCHATTYPEMATCHFRIENDFOLDER = 109, - KCHATTYPENEARBY = 106, - KCHATTYPENEARBYASSISTANT = 107, - KCHATTYPENEARBYFOLDER = 110, - KCHATTYPENEARBYHELLOFOLDER = 112, - KCHATTYPENEARBYINTERACT = 108, - KCHATTYPEQQNOTIFY = 132, - KCHATTYPERELATEACCOUNT = 131, - KCHATTYPESERVICEASSISTANT = 118, - KCHATTYPESERVICEASSISTANTSUB = 201, - KCHATTYPESQUAREPUBLIC = 115, - KCHATTYPESUBSCRIBEFOLDER = 30, - KCHATTYPETEMPADDRESSBOOK = 111, - KCHATTYPETEMPBUSSINESSCRM = 102, - KCHATTYPETEMPC2CFROMGROUP = 100, - KCHATTYPETEMPC2CFROMUNKNOWN = 99, - KCHATTYPETEMPFRIENDVERIFY = 101, - KCHATTYPETEMPNEARBYPRO = 119, - KCHATTYPETEMPPUBLICACCOUNT = 103, - KCHATTYPETEMPWPA = 117, - KCHATTYPEUNKNOWN = 0, - KCHATTYPEWEIYUN = 40, + KCHATTYPEADELIE = 42, + KCHATTYPEBUDDYNOTIFY = 5, + KCHATTYPEC2C = 1, + KCHATTYPECIRCLE = 113, + KCHATTYPEDATALINE = 8, + KCHATTYPEDATALINEMQQ = 134, + KCHATTYPEDISC = 3, + KCHATTYPEFAV = 41, + KCHATTYPEGAMEMESSAGE = 105, + KCHATTYPEGAMEMESSAGEFOLDER = 116, + KCHATTYPEGROUP = 2, + KCHATTYPEGROUPBLESS = 133, + KCHATTYPEGROUPGUILD = 9, + KCHATTYPEGROUPHELPER = 7, + KCHATTYPEGROUPNOTIFY = 6, + KCHATTYPEGUILD = 4, + KCHATTYPEGUILDMETA = 16, + KCHATTYPEMATCHFRIEND = 104, + KCHATTYPEMATCHFRIENDFOLDER = 109, + KCHATTYPENEARBY = 106, + KCHATTYPENEARBYASSISTANT = 107, + KCHATTYPENEARBYFOLDER = 110, + KCHATTYPENEARBYHELLOFOLDER = 112, + KCHATTYPENEARBYINTERACT = 108, + KCHATTYPEQQNOTIFY = 132, + KCHATTYPERELATEACCOUNT = 131, + KCHATTYPESERVICEASSISTANT = 118, + KCHATTYPESERVICEASSISTANTSUB = 201, + KCHATTYPESQUAREPUBLIC = 115, + KCHATTYPESUBSCRIBEFOLDER = 30, + KCHATTYPETEMPADDRESSBOOK = 111, + KCHATTYPETEMPBUSSINESSCRM = 102, + KCHATTYPETEMPC2CFROMGROUP = 100, + KCHATTYPETEMPC2CFROMUNKNOWN = 99, + KCHATTYPETEMPFRIENDVERIFY = 101, + KCHATTYPETEMPNEARBYPRO = 119, + KCHATTYPETEMPPUBLICACCOUNT = 103, + KCHATTYPETEMPWPA = 117, + KCHATTYPEUNKNOWN = 0, + KCHATTYPEWEIYUN = 40, } /** * 灰色提示元素子类型枚举 */ export enum NTGrayTipElementSubTypeV2 { - GRAYTIP_ELEMENT_SUBTYPE_AIOOP = 15, - GRAYTIP_ELEMENT_SUBTYPE_BLOCK = 14, - GRAYTIP_ELEMENT_SUBTYPE_BUDDY = 5, - GRAYTIP_ELEMENT_SUBTYPE_BUDDYNOTIFY = 9, - GRAYTIP_ELEMENT_SUBTYPE_EMOJIREPLY = 3, - GRAYTIP_ELEMENT_SUBTYPE_ESSENCE = 7, - GRAYTIP_ELEMENT_SUBTYPE_FEED = 6, - GRAYTIP_ELEMENT_SUBTYPE_FEEDCHANNELMSG = 11, - GRAYTIP_ELEMENT_SUBTYPE_FILE = 10, - GRAYTIP_ELEMENT_SUBTYPE_GROUP = 4, - GRAYTIP_ELEMENT_SUBTYPE_GROUPNOTIFY = 8, - GRAYTIP_ELEMENT_SUBTYPE_JSON = 17, - GRAYTIP_ELEMENT_SUBTYPE_LOCALMSG = 13, - GRAYTIP_ELEMENT_SUBTYPE_PROCLAMATION = 2, - GRAYTIP_ELEMENT_SUBTYPE_REVOKE = 1, - GRAYTIP_ELEMENT_SUBTYPE_UNKNOWN = 0, - GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16, - GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12, + GRAYTIP_ELEMENT_SUBTYPE_AIOOP = 15, + GRAYTIP_ELEMENT_SUBTYPE_BLOCK = 14, + GRAYTIP_ELEMENT_SUBTYPE_BUDDY = 5, + GRAYTIP_ELEMENT_SUBTYPE_BUDDYNOTIFY = 9, + GRAYTIP_ELEMENT_SUBTYPE_EMOJIREPLY = 3, + GRAYTIP_ELEMENT_SUBTYPE_ESSENCE = 7, + GRAYTIP_ELEMENT_SUBTYPE_FEED = 6, + GRAYTIP_ELEMENT_SUBTYPE_FEEDCHANNELMSG = 11, + GRAYTIP_ELEMENT_SUBTYPE_FILE = 10, + GRAYTIP_ELEMENT_SUBTYPE_GROUP = 4, + GRAYTIP_ELEMENT_SUBTYPE_GROUPNOTIFY = 8, + GRAYTIP_ELEMENT_SUBTYPE_JSON = 17, + GRAYTIP_ELEMENT_SUBTYPE_LOCALMSG = 13, + GRAYTIP_ELEMENT_SUBTYPE_PROCLAMATION = 2, + GRAYTIP_ELEMENT_SUBTYPE_REVOKE = 1, + GRAYTIP_ELEMENT_SUBTYPE_UNKNOWN = 0, + GRAYTIP_ELEMENT_SUBTYPE_WALLET = 16, + GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12, } /** * Poke 类型枚举 */ export enum PokeType { - POKE_TYPE_APPROVE = 3, - POKE_TYPE_GIVING_HEART = 2, - POKE_TYPE_GREAT_MOVE = 6, - POKE_TYPE_HEART_BREAK = 4, - POKE_TYPE_HI_TOGETHER = 5, - POKE_TYPE_POKE = 1, - POKE_TYPE_POKE_OLD = 0, - POKE_TYPE_VAS_POKE = 126, + POKE_TYPE_APPROVE = 3, + POKE_TYPE_GIVING_HEART = 2, + POKE_TYPE_GREAT_MOVE = 6, + POKE_TYPE_HEART_BREAK = 4, + POKE_TYPE_HI_TOGETHER = 5, + POKE_TYPE_POKE = 1, + POKE_TYPE_POKE_OLD = 0, + POKE_TYPE_VAS_POKE = 126, } /** * 表情索引枚举 */ export enum FaceIndex { - DICE = 358, - RPS = 359 + DICE = 358, + RPS = 359, } /** * 视频类型枚举 */ export enum NTVideoType { - VIDEO_FORMAT_AFS = 7, - VIDEO_FORMAT_AVI = 1, - VIDEO_FORMAT_MKV = 4, - VIDEO_FORMAT_MOD = 9, - VIDEO_FORMAT_MOV = 8, - VIDEO_FORMAT_MP4 = 2, - VIDEO_FORMAT_MTS = 11, - VIDEO_FORMAT_RM = 6, - VIDEO_FORMAT_RMVB = 5, - VIDEO_FORMAT_TS = 10, - VIDEO_FORMAT_WMV = 3, + VIDEO_FORMAT_AFS = 7, + VIDEO_FORMAT_AVI = 1, + VIDEO_FORMAT_MKV = 4, + VIDEO_FORMAT_MOD = 9, + VIDEO_FORMAT_MOV = 8, + VIDEO_FORMAT_MP4 = 2, + VIDEO_FORMAT_MTS = 11, + VIDEO_FORMAT_RM = 6, + VIDEO_FORMAT_RMVB = 5, + VIDEO_FORMAT_TS = 10, + VIDEO_FORMAT_WMV = 3, } /** * Markdown元素接口 */ export interface MarkdownElement { - content: string; + content: string; } /** * 内联键盘按钮接口 */ export interface InlineKeyboardElementRowButton { - id: string; - label: string; - visitedLabel: string; - style: 1; // 未知 - type: 2; // 未知 - clickLimit: 0; // 未知 - unsupportTips: string; - data: string; - atBotShowChannelList: boolean; - permissionType: number; - specifyRoleIds: []; - specifyTinyids: []; - isReply: false; - anchor: 0; - enter: false; - subscribeDataTemplateIds: []; + id: string; + label: string; + visitedLabel: string; + style: 1; // 未知 + type: 2; // 未知 + clickLimit: 0; // 未知 + unsupportTips: string; + data: string; + atBotShowChannelList: boolean; + permissionType: number; + specifyRoleIds: []; + specifyTinyids: []; + isReply: false; + anchor: 0; + enter: false; + subscribeDataTemplateIds: []; } /** * 内联键盘元素接口 */ export interface InlineKeyboardElement { - rows: [{ - buttons: InlineKeyboardElementRowButton[] - }], - botAppid: string; + rows: [{ + buttons: InlineKeyboardElementRowButton[] + }], + botAppid: string; } /** * Aio操作灰色提示元素接口 */ export interface TipAioOpGrayTipElement { - operateType: number; - peerUid: string; - fromGrpCodeOfTmpChat: string; + operateType: number; + peerUid: string; + fromGrpCodeOfTmpChat: string; } /** * 群提示元素类型枚举 */ export enum TipGroupElementType { - KUNKNOWN = 0, - KMEMBERADD = 1, - KDISBANDED = 2, - KQUITTE = 3, - KCREATED = 4, - KGROUPNAMEMODIFIED = 5, - KBLOCK = 6, - KUNBLOCK = 7, - KSHUTUP = 8, - KBERECYCLED = 9, - KDISBANDORBERECYCLED = 10 + KUNKNOWN = 0, + KMEMBERADD = 1, + KDISBANDED = 2, + KQUITTE = 3, + KCREATED = 4, + KGROUPNAMEMODIFIED = 5, + KBLOCK = 6, + KUNBLOCK = 7, + KSHUTUP = 8, + KBERECYCLED = 9, + KDISBANDORBERECYCLED = 10, } /** * 群加入ShowType */ export enum MemberAddShowType { - K_OTHER_ADD = 0, - K_OTHER_ADD_BY_OTHER_QRCODE = 2, - K_OTHER_ADD_BY_YOUR_QRCODE = 3, - K_OTHER_INVITE_OTHER = 5, - K_OTHER_INVITE_YOU = 6, - K_YOU_ADD = 1, - K_YOU_ADD_BY_OTHER_QRCODE = 4, - K_YOU_ALREADY_MEMBER = 8, - K_YOU_INVITE_OTHER = 7, + K_OTHER_ADD = 0, + K_OTHER_ADD_BY_OTHER_QRCODE = 2, + K_OTHER_ADD_BY_YOUR_QRCODE = 3, + K_OTHER_INVITE_OTHER = 5, + K_OTHER_INVITE_YOU = 6, + K_YOU_ADD = 1, + K_YOU_ADD_BY_OTHER_QRCODE = 4, + K_YOU_ALREADY_MEMBER = 8, + K_YOU_INVITE_OTHER = 7, } /** * 群提示元素成员角色枚举 */ export enum NTGroupGrayElementRole { - KOTHER = 0, - KMEMBER = 1, - KADMIN = 2 + KOTHER = 0, + KMEMBER = 1, + KADMIN = 2, } /** @@ -397,174 +397,174 @@ export enum NTGroupGrayElementRole { * */ export interface NTGroupGrayMember { - serialVersionUID: string; - uid: string; - name: string; + serialVersionUID: string; + uid: string; + name: string; } /** * 群灰色提示邀请者和被邀请者接口 * * */ export interface NTGroupGrayInviterAndInvite { - invited: NTGroupGrayMember; - inviter: NTGroupGrayMember; - serialVersionUID: string; + invited: NTGroupGrayMember; + inviter: NTGroupGrayMember; + serialVersionUID: string; } /** * 群提示元素接口 */ export interface TipGroupElement { - type: TipGroupElementType; - role: NTGroupGrayElementRole; - groupName: string; - memberUid: string; - memberNick: string; - memberRemark: string; - adminUid: string; - adminNick: string; - adminRemark: string; - createGroup: null; - memberAdd?: { - showType: MemberAddShowType; - otherAdd: NTGroupGrayMember; - otherAddByOtherQRCode: NTGroupGrayInviterAndInvite; - otherAddByYourQRCode: NTGroupGrayMember; - youAddByOtherQRCode: NTGroupGrayMember; - otherInviteOther: NTGroupGrayInviterAndInvite; - otherInviteYou: NTGroupGrayMember; - youInviteOther: NTGroupGrayMember; - }; - shutUp?: { - curTime: string; - duration: string; // 禁言时间,秒 - admin: { - uid: string; - card: string; - name: string; - role: NTGroupMemberRole - }; - member: { - uid: string - card: string; - name: string; - role: NTGroupMemberRole - } + type: TipGroupElementType; + role: NTGroupGrayElementRole; + groupName: string; + memberUid: string; + memberNick: string; + memberRemark: string; + adminUid: string; + adminNick: string; + adminRemark: string; + createGroup: null; + memberAdd?: { + showType: MemberAddShowType; + otherAdd: NTGroupGrayMember; + otherAddByOtherQRCode: NTGroupGrayInviterAndInvite; + otherAddByYourQRCode: NTGroupGrayMember; + youAddByOtherQRCode: NTGroupGrayMember; + otherInviteOther: NTGroupGrayInviterAndInvite; + otherInviteYou: NTGroupGrayMember; + youInviteOther: NTGroupGrayMember; + }; + shutUp?: { + curTime: string; + duration: string; // 禁言时间,秒 + admin: { + uid: string; + card: string; + name: string; + role: NTGroupMemberRole }; + member: { + uid: string + card: string; + name: string; + role: NTGroupMemberRole + } + }; } /** * 多条转发消息元素接口 */ export interface MultiForwardMsgElement { - xmlContent: string; // xml格式的消息内容 - resId: string; - fileName: string; + xmlContent: string; // xml格式的消息内容 + resId: string; + fileName: string; } /** * 发送状态类型枚举 */ export enum SendStatusType { - KSEND_STATUS_FAILED = 0, - KSEND_STATUS_SENDING = 1, - KSEND_STATUS_SUCCESS = 2, - KSEND_STATUS_SUCCESS_NOSEQ = 3 + KSEND_STATUS_FAILED = 0, + KSEND_STATUS_SENDING = 1, + KSEND_STATUS_SUCCESS = 2, + KSEND_STATUS_SUCCESS_NOSEQ = 3, } /** * 原始消息接口 */ export interface RawMessage { - parentMsgPeer: Peer; // 父消息的Peer - parentMsgIdList: string[];// 父消息 ID 列表 - id?: number;// 扩展字段,与 Ob11 msg ID 有关 - guildId: string;// 频道ID - msgRandom: string;// 消息ID相关 - msgId: string;// 雪花ID - msgTime: string;// 消息时间戳 - msgSeq: string;// 消息序列号 - msgType: NTMsgType;// 消息类型 - subMsgType: number;// 子消息类型 - senderUid: string;// 发送者 UID - senderUin: string;// 发送者 QQ 号 - peerUid: string;// 群号 / 用户 UID - peerUin: string;// 群号 / 用户 QQ 号 - remark?: string;// 备注 - peerName: string;// Peer名称 - sendNickName: string;// 发送者昵称 - sendRemarkName: string;// 发送者好友备注 - sendMemberName?: string;// 发送者群名片(如果是群消息) - chatType: ChatType;// 会话类型 - sendStatus?: SendStatusType;// 消息状态 - recallTime: string;// 撤回时间,"0" 是没有撤回 - records: RawMessage[];// 消息记录 - elements: MessageElement[];// 消息元素 - sourceType: MsgSourceType;// 消息来源类型 - isOnlineMsg: boolean;// 是否为在线消息 - clientSeq?: string; + parentMsgPeer: Peer; // 父消息的Peer + parentMsgIdList: string[];// 父消息 ID 列表 + id?: number;// 扩展字段,与 Ob11 msg ID 有关 + guildId: string;// 频道ID + msgRandom: string;// 消息ID相关 + msgId: string;// 雪花ID + msgTime: string;// 消息时间戳 + msgSeq: string;// 消息序列号 + msgType: NTMsgType;// 消息类型 + subMsgType: number;// 子消息类型 + senderUid: string;// 发送者 UID + senderUin: string;// 发送者 QQ 号 + peerUid: string;// 群号 / 用户 UID + peerUin: string;// 群号 / 用户 QQ 号 + remark?: string;// 备注 + peerName: string;// Peer名称 + sendNickName: string;// 发送者昵称 + sendRemarkName: string;// 发送者好友备注 + sendMemberName?: string;// 发送者群名片(如果是群消息) + chatType: ChatType;// 会话类型 + sendStatus?: SendStatusType;// 消息状态 + recallTime: string;// 撤回时间,"0" 是没有撤回 + records: RawMessage[];// 消息记录 + elements: MessageElement[];// 消息元素 + sourceType: MsgSourceType;// 消息来源类型 + isOnlineMsg: boolean;// 是否为在线消息 + clientSeq?: string; } /** * 查询消息参数接口 */ export interface QueryMsgsParams { - chatInfo: Peer & { privilegeFlag?: number }; - //searchFields: number; - filterMsgType: Array<{ type: NTMsgType, subType: Array }>; - filterSendersUid: string[]; - filterMsgFromTime: string; - filterMsgToTime: string; - pageLimit: number; - isReverseOrder: boolean; - isIncludeCurrent: boolean; + chatInfo: Peer & { privilegeFlag?: number }; + // searchFields: number; + filterMsgType: Array<{ type: NTMsgType, subType: Array }>; + filterSendersUid: string[]; + filterMsgFromTime: string; + filterMsgToTime: string; + pageLimit: number; + isReverseOrder: boolean; + isIncludeCurrent: boolean; } /** * 临时聊天信息API接口 */ export interface TmpChatInfoApi { - errMsg: string; - result: number; - tmpChatInfo?: TmpChatInfo; + errMsg: string; + result: number; + tmpChatInfo?: TmpChatInfo; } /** * 临时聊天信息接口 */ export interface TmpChatInfo { - chatType: number; - fromNick: string; - groupCode: string; - peerUid: string; - sessionType: number; - sig: string; + chatType: number; + fromNick: string; + groupCode: string; + peerUid: string; + sessionType: number; + sig: string; } /** * 消息请求类型接口 */ export interface MsgReqType { - peer: Peer, - byType: number, - msgId: string, - msgSeq: string, - msgTime: string, - clientSeq: string, - cnt: number, - queryOrder: boolean, - includeSelf: boolean, - includeDeleteMsg: boolean, - extraCnt: number + peer: Peer, + byType: number, + msgId: string, + msgSeq: string, + msgTime: string, + clientSeq: string, + cnt: number, + queryOrder: boolean, + includeSelf: boolean, + includeDeleteMsg: boolean, + extraCnt: number } /** * 表情类型枚举 */ export enum FaceType { - Unknown = 0, - OldFace = 1, // 老表情 - Normal = 2, // 常规表情 - AniSticke = 3, // 动画贴纸 - Lottie = 4,// 新格式表情 - Poke = 5 // 可变Poke + Unknown = 0, + OldFace = 1, // 老表情 + Normal = 2, // 常规表情 + AniSticke = 3, // 动画贴纸 + Lottie = 4, // 新格式表情 + Poke = 5, // 可变Poke } diff --git a/src/core/types/notify.ts b/src/core/types/notify.ts index 45fe4c9b..d798af8c 100644 --- a/src/core/types/notify.ts +++ b/src/core/types/notify.ts @@ -1,198 +1,198 @@ export enum GroupNotifyMsgType { - UN_SPECIFIED = 0, - INVITED_BY_MEMBER = 1, - REFUSE_INVITED = 2, - REFUSED_BY_ADMINI_STRATOR = 3, - AGREED_TOJOIN_DIRECT = 4,// 有人接受了邀请入群 - INVITED_NEED_ADMINI_STRATOR_PASS = 5, - AGREED_TO_JOIN_BY_ADMINI_STRATOR = 6, - REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS = 7, - SET_ADMIN = 8, - KICK_MEMBER_NOTIFY_ADMIN = 9, - KICK_MEMBER_NOTIFY_KICKED = 10, - MEMBER_LEAVE_NOTIFY_ADMIN = 11,// 主动退出 - CANCEL_ADMIN_NOTIFY_CANCELED = 12, - CANCEL_ADMIN_NOTIFY_ADMIN = 13,// 其他人取消管理员 - TRANSFER_GROUP_NOTIFY_OLDOWNER = 14, - TRANSFER_GROUP_NOTIFY_ADMIN = 15 + UN_SPECIFIED = 0, + INVITED_BY_MEMBER = 1, + REFUSE_INVITED = 2, + REFUSED_BY_ADMINI_STRATOR = 3, + AGREED_TOJOIN_DIRECT = 4, // 有人接受了邀请入群 + INVITED_NEED_ADMINI_STRATOR_PASS = 5, + AGREED_TO_JOIN_BY_ADMINI_STRATOR = 6, + REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS = 7, + SET_ADMIN = 8, + KICK_MEMBER_NOTIFY_ADMIN = 9, + KICK_MEMBER_NOTIFY_KICKED = 10, + MEMBER_LEAVE_NOTIFY_ADMIN = 11, // 主动退出 + CANCEL_ADMIN_NOTIFY_CANCELED = 12, + CANCEL_ADMIN_NOTIFY_ADMIN = 13, // 其他人取消管理员 + TRANSFER_GROUP_NOTIFY_OLDOWNER = 14, + TRANSFER_GROUP_NOTIFY_ADMIN = 15, } export interface GroupNotifies { - doubt: boolean; - nextStartSeq: string; - notifies: GroupNotify[]; + doubt: boolean; + nextStartSeq: string; + notifies: GroupNotify[]; } export enum GroupNotifyMsgStatus { - KINIT = 0,//初始化 - KUNHANDLE = 1,//未处理 - KAGREED = 2,//同意 - KREFUSED = 3,//拒绝 - KIGNORED = 4//忽略 + KINIT = 0, // 初始化 + KUNHANDLE = 1, // 未处理 + KAGREED = 2, // 同意 + KREFUSED = 3, // 拒绝 + KIGNORED = 4, // 忽略 } export enum GroupInviteStatus { - INIT = 0, - WAIT_TO_APPROVE = 1, - JOINED = 2, - REFUSED_BY_ADMINI_STRATOR = 3 + INIT = 0, + WAIT_TO_APPROVE = 1, + JOINED = 2, + REFUSED_BY_ADMINI_STRATOR = 3, } export enum GroupInviteType { - BYBUDDY = 0, - BYGROUPMEMBER = 1, - BYDISCUSSMEMBER = 2 + BYBUDDY = 0, + BYGROUPMEMBER = 1, + BYDISCUSSMEMBER = 2, } export interface ShutUpGroupHonor { - [key: string]: number; + [key: string]: number; } export interface ShutUpGroupMember { - uid: string; - qid: string; - uin: string; - nick: string; - remark: string; - cardType: number; - cardName: string; - role: number; - avatarPath: string; - shutUpTime: number; - isDelete: boolean; - isSpecialConcerned: boolean; - isSpecialShield: boolean; - isRobot: boolean; - groupHonor: ShutUpGroupHonor; - memberRealLevel: number; - memberLevel: number; - globalGroupLevel: number; - globalGroupPoint: number; - memberTitleId: number; - memberSpecialTitle: string; - specialTitleExpireTime: string; - userShowFlag: number; - userShowFlagNew: number; - richFlag: number; - mssVipType: number; - bigClubLevel: number; - bigClubFlag: number; - autoRemark: string; - creditLevel: number; - joinTime: number; - lastSpeakTime: number; - memberFlag: number; - memberFlagExt: number; - memberMobileFlag: number; - memberFlagExt2: number; - isSpecialShielded: boolean; - cardNameId: number; + uid: string; + qid: string; + uin: string; + nick: string; + remark: string; + cardType: number; + cardName: string; + role: number; + avatarPath: string; + shutUpTime: number; + isDelete: boolean; + isSpecialConcerned: boolean; + isSpecialShield: boolean; + isRobot: boolean; + groupHonor: ShutUpGroupHonor; + memberRealLevel: number; + memberLevel: number; + globalGroupLevel: number; + globalGroupPoint: number; + memberTitleId: number; + memberSpecialTitle: string; + specialTitleExpireTime: string; + userShowFlag: number; + userShowFlagNew: number; + richFlag: number; + mssVipType: number; + bigClubLevel: number; + bigClubFlag: number; + autoRemark: string; + creditLevel: number; + joinTime: number; + lastSpeakTime: number; + memberFlag: number; + memberFlagExt: number; + memberMobileFlag: number; + memberFlagExt2: number; + isSpecialShielded: boolean; + cardNameId: number; } export interface GroupNotify { - seq: string; // 通知序列号 - type: GroupNotifyMsgType; - status: GroupNotifyMsgStatus; - group: { groupCode: string; groupName: string }; - user1: { uid: string; nickName: string }; // 被设置管理员的人 - user2: { uid: string; nickName: string }; // 操作者 - actionUser: { uid: string; nickName: string }; //未知 - actionTime: string; - invitationExt: { - srcType: GroupInviteType; // 邀请来源 - groupCode: string; - waitStatus: GroupInviteStatus - }; - postscript: string; // 加群用户填写的验证信息 - repeatSeqs: []; - warningTips: string; + seq: string; // 通知序列号 + type: GroupNotifyMsgType; + status: GroupNotifyMsgStatus; + group: { groupCode: string; groupName: string }; + user1: { uid: string; nickName: string }; // 被设置管理员的人 + user2: { uid: string; nickName: string }; // 操作者 + actionUser: { uid: string; nickName: string }; // 未知 + actionTime: string; + invitationExt: { + srcType: GroupInviteType; // 邀请来源 + groupCode: string; + waitStatus: GroupInviteStatus + }; + postscript: string; // 加群用户填写的验证信息 + repeatSeqs: []; + warningTips: string; } export enum NTGroupRequestOperateTypes { - KUNSPECIFIED = 0, - KAGREE = 1, - KREFUSE = 2, - KIGNORE = 3, - KDELETE = 4 + KUNSPECIFIED = 0, + KAGREE = 1, + KREFUSE = 2, + KIGNORE = 3, + KDELETE = 4, } export enum BuddyReqType { - KMEINITIATOR = 0, - KPEERINITIATOR = 1, - KMEAGREED = 2, - KMEAGREEDANDADDED = 3, - KPEERAGREED = 4, - KPEERAGREEDANDADDED = 5, - KPEERREFUSED = 6, - KMEREFUSED = 7, - KMEIGNORED = 8, - KMEAGREEANYONE = 9, - KMESETQUESTION = 10, - KMEAGREEANDADDFAILED = 11, - KMSGINFO = 12, - KMEINITIATORWAITPEERCONFIRM = 13 + KMEINITIATOR = 0, + KPEERINITIATOR = 1, + KMEAGREED = 2, + KMEAGREEDANDADDED = 3, + KPEERAGREED = 4, + KPEERAGREEDANDADDED = 5, + KPEERREFUSED = 6, + KMEREFUSED = 7, + KMEIGNORED = 8, + KMEAGREEANYONE = 9, + KMESETQUESTION = 10, + KMEAGREEANDADDFAILED = 11, + KMSGINFO = 12, + KMEINITIATORWAITPEERCONFIRM = 13, } // 其中 ? 代表新版本参数 export interface FriendRequest { - isInitiator?: boolean; - isDecide: boolean; - friendUid: string; - reqType: BuddyReqType, - reqTime: string; // 时间戳 秒 - flag?: number; // 0 - preGroupingId?: number; // 0 - commFriendNum?: number; // 共同好友数 - extWords: string; // 申请人填写的验证消息 - isUnread: boolean; - isDoubt?: boolean; // 是否是可疑的好友请求 - nameMore?: string; - friendNick: string; - sourceId: number; - groupCode: string; - isBuddy?: boolean; - isAgreed?: boolean; - relation?: number; + isInitiator?: boolean; + isDecide: boolean; + friendUid: string; + reqType: BuddyReqType, + reqTime: string; // 时间戳 秒 + flag?: number; // 0 + preGroupingId?: number; // 0 + commFriendNum?: number; // 共同好友数 + extWords: string; // 申请人填写的验证消息 + isUnread: boolean; + isDoubt?: boolean; // 是否是可疑的好友请求 + nameMore?: string; + friendNick: string; + sourceId: number; + groupCode: string; + isBuddy?: boolean; + isAgreed?: boolean; + relation?: number; } export interface FriendRequestNotify { - unreadNums: number; - buddyReqs: FriendRequest[]; + unreadNums: number; + buddyReqs: FriendRequest[]; } export enum MemberExtSourceType { - DEFAULTTYPE = 0, - TITLETYPE = 1, - NEWGROUPTYPE = 2, + DEFAULTTYPE = 0, + TITLETYPE = 1, + NEWGROUPTYPE = 2, } export interface GroupExtParam { - groupCode: string; - seq: string; - beginUin: string; - dataTime: string; - uinList: Array; - uinNum: string; - groupType: string; - richCardNameVer: string; - sourceType: MemberExtSourceType; - memberExtFilter: { - memberLevelInfoUin: number - memberLevelInfoPoint: number - memberLevelInfoActiveDay: number - memberLevelInfoLevel: number - memberLevelInfoName: number - levelName: number - dataTime: number - userShowFlag: number - sysShowFlag: number - timeToUpdate: number - nickName: number - specialTitle: number - levelNameNew: number - userShowFlagNew: number - msgNeedField: number - cmdUinFlagExt3Grocery: number - memberIcon: number - memberInfoSeq: number - }; + groupCode: string; + seq: string; + beginUin: string; + dataTime: string; + uinList: Array; + uinNum: string; + groupType: string; + richCardNameVer: string; + sourceType: MemberExtSourceType; + memberExtFilter: { + memberLevelInfoUin: number + memberLevelInfoPoint: number + memberLevelInfoActiveDay: number + memberLevelInfoLevel: number + memberLevelInfoName: number + levelName: number + dataTime: number + userShowFlag: number + sysShowFlag: number + timeToUpdate: number + nickName: number + specialTitle: number + levelNameNew: number + userShowFlagNew: number + msgNeedField: number + cmdUinFlagExt3Grocery: number + memberIcon: number + memberInfoSeq: number + }; } diff --git a/src/core/types/service.ts b/src/core/types/service.ts index a53173d5..32a0c9dc 100644 --- a/src/core/types/service.ts +++ b/src/core/types/service.ts @@ -1,35 +1,35 @@ export enum LoginErrorCode { - KLOGINERRORACCOUNTNOTUIN = 140022018, - KLOGINERRORACCOUNTORPASSWORDERROR = 140022013, - KLOGINERRORBLACKACCOUNT = 150022021, - KLOGINERRORDEFAULT = 140022000, - KLOGINERROREXPIRETICKET = 140022014, - KLOGINERRORFROZEN = 140022005, - KLOGINERRORILLAGETICKET = 140022016, - KLOGINERRORINVAILDCOOKIE = 140022012, - KLOGINERRORINVALIDPARAMETER = 140022001, - KLOGINERRORKICKEDTICKET = 140022015, - KLOGINERRORMUTIPLEPASSWORDINCORRECT = 150022029, - KLOGINERRORNEEDUPDATE = 140022004, - KLOGINERRORNEEDVERIFYREALNAME = 140022019, - KLOGINERRORNEWDEVICE = 140022010, - KLOGINERRORNICEACCOUNTEXPIRED = 150022020, - KLOGINERRORNICEACCOUNTPARENTCHILDEXPIRED = 150022025, - KLOGINERRORPASSWORD = 2, - KLOGINERRORPROOFWATER = 140022008, - KLOGINERRORPROTECT = 140022006, - KLOGINERRORREFUSEPASSOWRDLOGIN = 140022009, - KLOGINERRORREMINDCANAELLATEDSTATUS = 150022028, - KLOGINERRORSCAN = 1, - KLOGINERRORSCCESS = 0, - KLOGINERRORSECBEAT = 140022017, - KLOGINERRORSMSINVALID = 150022026, - KLOGINERRORSTRICK = 140022007, - KLOGINERRORSYSTEMFAILED = 140022002, - KLOGINERRORTGTGTEXCHAGEA1FORBID = 150022027, - KLOGINERRORTIMEOUTRETRY = 140022003, - KLOGINERRORTOOMANYTIMESTODAY = 150022023, - KLOGINERRORTOOOFTEN = 150022022, - KLOGINERRORUNREGISTERED = 150022024, - KLOGINERRORUNUSUALDEVICE = 140022011, -} \ No newline at end of file + KLOGINERRORACCOUNTNOTUIN = 140022018, + KLOGINERRORACCOUNTORPASSWORDERROR = 140022013, + KLOGINERRORBLACKACCOUNT = 150022021, + KLOGINERRORDEFAULT = 140022000, + KLOGINERROREXPIRETICKET = 140022014, + KLOGINERRORFROZEN = 140022005, + KLOGINERRORILLAGETICKET = 140022016, + KLOGINERRORINVAILDCOOKIE = 140022012, + KLOGINERRORINVALIDPARAMETER = 140022001, + KLOGINERRORKICKEDTICKET = 140022015, + KLOGINERRORMUTIPLEPASSWORDINCORRECT = 150022029, + KLOGINERRORNEEDUPDATE = 140022004, + KLOGINERRORNEEDVERIFYREALNAME = 140022019, + KLOGINERRORNEWDEVICE = 140022010, + KLOGINERRORNICEACCOUNTEXPIRED = 150022020, + KLOGINERRORNICEACCOUNTPARENTCHILDEXPIRED = 150022025, + KLOGINERRORPASSWORD = 2, + KLOGINERRORPROOFWATER = 140022008, + KLOGINERRORPROTECT = 140022006, + KLOGINERRORREFUSEPASSOWRDLOGIN = 140022009, + KLOGINERRORREMINDCANAELLATEDSTATUS = 150022028, + KLOGINERRORSCAN = 1, + KLOGINERRORSCCESS = 0, + KLOGINERRORSECBEAT = 140022017, + KLOGINERRORSMSINVALID = 150022026, + KLOGINERRORSTRICK = 140022007, + KLOGINERRORSYSTEMFAILED = 140022002, + KLOGINERRORTGTGTEXCHAGEA1FORBID = 150022027, + KLOGINERRORTIMEOUTRETRY = 140022003, + KLOGINERRORTOOMANYTIMESTODAY = 150022023, + KLOGINERRORTOOOFTEN = 150022022, + KLOGINERRORUNREGISTERED = 150022024, + KLOGINERRORUNUSUALDEVICE = 140022011, +} diff --git a/src/core/types/sign.ts b/src/core/types/sign.ts index 8347ecbf..146701a8 100644 --- a/src/core/types/sign.ts +++ b/src/core/types/sign.ts @@ -1,25 +1,25 @@ export interface IdMusicSignPostData { - type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo', - id: string | number, + type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo', + id: string | number, } export interface CustomMusicSignPostData { - type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom', - id: undefined, - url: string, - audio?: string, - title?: string, - image: string, - singer?: string + type: 'qq' | '163' | 'kugou' | 'migu' | 'kuwo' | 'custom', + id: undefined, + url: string, + audio?: string, + title?: string, + image: string, + singer?: string } export interface MiniAppLuaJsonType { - prompt: string, - title: string, - preview: string, - jumpUrl: string, - tag: string, - tagIcon: string, - source: string, - sourcelogo: string, + prompt: string, + title: string, + preview: string, + jumpUrl: string, + tag: string, + tagIcon: string, + source: string, + sourcelogo: string, } diff --git a/src/core/types/system.ts b/src/core/types/system.ts index 9466c4c2..e6234cf5 100644 --- a/src/core/types/system.ts +++ b/src/core/types/system.ts @@ -1,18 +1,18 @@ import { SelfInfo } from './user'; export interface LineDevice { - instanceId: number; - clientType: number; - devUid: string; + instanceId: number; + clientType: number; + devUid: string; } export interface OBLineDevice { - app_id: string; - device_name: string; - device_kind: string; + app_id: string; + device_name: string; + device_kind: string; } export interface CoreCache { - selfInfo: SelfInfo, - DeviceList: OBLineDevice[] + selfInfo: SelfInfo, + DeviceList: OBLineDevice[] } diff --git a/src/core/types/user.ts b/src/core/types/user.ts index 921c709a..fa76b731 100644 --- a/src/core/types/user.ts +++ b/src/core/types/user.ts @@ -1,222 +1,221 @@ // 性别枚举 export enum NTSex { - GENDER_UNKOWN = 0, - GENDER_MALE = 1, - GENDER_FEMALE = 2, - GENDER_PRIVACY = 255, + GENDER_UNKOWN = 0, + GENDER_MALE = 1, + GENDER_FEMALE = 2, + GENDER_PRIVACY = 255, } export interface NTVoteInfo { - age: number; - bAvailableCnt: number; - bTodayVotedCnt: number; - count: number; - customId: number; - gender: number; - giftCount: number; - isFriend: boolean; - isSvip: boolean; - isvip: boolean; - lastCharged: number; - latestTime: number; - nick: string; - src: number; - uid: string; - uin: number; + age: number; + bAvailableCnt: number; + bTodayVotedCnt: number; + count: number; + customId: number; + gender: number; + giftCount: number; + isFriend: boolean; + isSvip: boolean; + isvip: boolean; + lastCharged: number; + latestTime: number; + nick: string; + src: number; + uid: string; + uin: number; } - // 好友分类类型 export interface BuddyCategoryType { - categoryId: number; - categoryName: string; - categoryMbCount: number; - buddyList: User[]; + categoryId: number; + categoryName: string; + categoryMbCount: number; + buddyList: User[]; } // 核心信息 export interface CoreInfo { - uid: string; - uin: string; - nick?: string; - remark: string; + uid: string; + uin: string; + nick?: string; + remark: string; } // 基本信息 export interface BaseInfo { - qid: string; - longNick: string; - birthday_year: number; - birthday_month: number; - birthday_day: number; - age: number; - sex: number; - eMail: string; - phoneNum: string; - categoryId: number; - richTime: number; - richBuffer: string; + qid: string; + longNick: string; + birthday_year: number; + birthday_month: number; + birthday_day: number; + age: number; + sex: number; + eMail: string; + phoneNum: string; + categoryId: number; + richTime: number; + richBuffer: string; } // 音乐信息 interface MusicInfo { - buf: string; + buf: string; } // 视频业务信息 interface VideoBizInfo { - cid: string; - tvUrl: string; - synchType: string; + cid: string; + tvUrl: string; + synchType: string; } // 视频信息 interface VideoInfo { - name: string; + name: string; } // 扩展在线业务信息 interface ExtOnlineBusinessInfo { - buf: string; - customStatus: unknown; - videoBizInfo: VideoBizInfo; - videoInfo: VideoInfo; + buf: string; + customStatus: unknown; + videoBizInfo: VideoBizInfo; + videoInfo: VideoInfo; } // 扩展缓冲区 interface ExtBuffer { - buf: string; + buf: string; } // 用户状态 interface UserStatus { - uid: string; - uin: string; - status: number; - extStatus: number; - batteryStatus: number; - termType: number; - netType: number; - iconType: number; - customStatus: unknown; - setTime: string; - specialFlag: number; - abiFlag: number; - eNetworkType: number; - showName: string; - termDesc: string; - musicInfo: MusicInfo; - extOnlineBusinessInfo: ExtOnlineBusinessInfo; - extBuffer: ExtBuffer; + uid: string; + uin: string; + status: number; + extStatus: number; + batteryStatus: number; + termType: number; + netType: number; + iconType: number; + customStatus: unknown; + setTime: string; + specialFlag: number; + abiFlag: number; + eNetworkType: number; + showName: string; + termDesc: string; + musicInfo: MusicInfo; + extOnlineBusinessInfo: ExtOnlineBusinessInfo; + extBuffer: ExtBuffer; } // 特权图标 interface PrivilegeIcon { - jumpUrl: string; - openIconList: unknown[]; - closeIconList: unknown[]; + jumpUrl: string; + openIconList: unknown[]; + closeIconList: unknown[]; } // 增值服务信息 interface VasInfo { - vipFlag: boolean; - yearVipFlag: boolean; - svipFlag: boolean; - vipLevel: number; - bigClub: boolean; - bigClubLevel: number; - nameplateVipType: number; - grayNameplateFlag: number; - superVipTemplateId: number; - diyFontId: number; - pendantId: number; - pendantDiyId: number; - faceId: number; - vipFont: number; - vipFontType: number; - magicFont: number; - fontEffect: number; - newLoverDiamondFlag: number; - extendNameplateId: number; - diyNameplateIDs: unknown[]; - vipStartFlag: number; - vipDataFlag: number; - gameNameplateId: string; - gameLastLoginTime: string; - gameRank: number; - gameIconShowFlag: boolean; - gameCardId: string; - vipNameColorId: string; - privilegeIcon: PrivilegeIcon; + vipFlag: boolean; + yearVipFlag: boolean; + svipFlag: boolean; + vipLevel: number; + bigClub: boolean; + bigClubLevel: number; + nameplateVipType: number; + grayNameplateFlag: number; + superVipTemplateId: number; + diyFontId: number; + pendantId: number; + pendantDiyId: number; + faceId: number; + vipFont: number; + vipFontType: number; + magicFont: number; + fontEffect: number; + newLoverDiamondFlag: number; + extendNameplateId: number; + diyNameplateIDs: unknown[]; + vipStartFlag: number; + vipDataFlag: number; + gameNameplateId: string; + gameLastLoginTime: string; + gameRank: number; + gameIconShowFlag: boolean; + gameCardId: string; + vipNameColorId: string; + privilegeIcon: PrivilegeIcon; } // 关系标志 interface RelationFlags { - topTime: string; - isBlock: boolean; - isMsgDisturb: boolean; - isSpecialCareOpen: boolean; - isSpecialCareZone: boolean; - ringId: string; - isBlocked: boolean; - recommendImgFlag: number; - disableEmojiShortCuts: number; - qidianMasterFlag: number; - qidianCrewFlag: number; - qidianCrewFlag2: number; - isHideQQLevel: number; - isHidePrivilegeIcon: number; + topTime: string; + isBlock: boolean; + isMsgDisturb: boolean; + isSpecialCareOpen: boolean; + isSpecialCareZone: boolean; + ringId: string; + isBlocked: boolean; + recommendImgFlag: number; + disableEmojiShortCuts: number; + qidianMasterFlag: number; + qidianCrewFlag: number; + qidianCrewFlag2: number; + isHideQQLevel: number; + isHidePrivilegeIcon: number; } // 通用扩展信息 interface CommonExt { - constellation: number; - shengXiao: number; - kBloodType: number; - homeTown: string; - makeFriendCareer: number; - pos: string; - college: string; - country: string; - province: string; - city: string; - postCode: string; - address: string; - regTime: number; - interest: string; - labels: string[]; - qqLevel: QQLevel; + constellation: number; + shengXiao: number; + kBloodType: number; + homeTown: string; + makeFriendCareer: number; + pos: string; + college: string; + country: string; + province: string; + city: string; + postCode: string; + address: string; + regTime: number; + interest: string; + labels: string[]; + qqLevel: QQLevel; } // 好友列表请求类型枚举 export enum BuddyListReqType { - KNOMAL, - KLETTER + KNOMAL, + KLETTER, } // 图片信息 interface Pic { - picId: string; - picTime: number; - picUrlMap: Record; + picId: string; + picTime: number; + picUrlMap: Record; } // 照片墙 interface PhotoWall { - picList: Pic[]; + picList: Pic[]; } // 简单信息 export interface SimpleInfo { - qqLevel?: QQLevel;//临时添加 - uid?: string; - uin?: string; - coreInfo: CoreInfo; - baseInfo: BaseInfo; - status: UserStatus | null; - vasInfo: VasInfo | null; - relationFlags: RelationFlags | null; - otherFlags: unknown; - intimate: unknown; + qqLevel?: QQLevel;// 临时添加 + uid?: string; + uin?: string; + coreInfo: CoreInfo; + baseInfo: BaseInfo; + status: UserStatus | null; + vasInfo: VasInfo | null; + relationFlags: RelationFlags | null; + otherFlags: unknown; + intimate: unknown; } // 好友类型 @@ -224,117 +223,117 @@ export type FriendV2 = SimpleInfo; // 自身状态信息 export interface SelfStatusInfo { - uid: string; - status: number; - extStatus: number; - termType: number; - netType: number; - iconType: number; - customStatus: unknown; - setTime: string; + uid: string; + status: number; + extStatus: number; + termType: number; + netType: number; + iconType: number; + customStatus: unknown; + setTime: string; } // 用户详细信息监听参数 export interface UserDetailInfoListenerArg { - uid: string; - uin: string; - simpleInfo: SimpleInfo; - commonExt: CommonExt; - photoWall: PhotoWall; + uid: string; + uin: string; + simpleInfo: SimpleInfo; + commonExt: CommonExt; + photoWall: PhotoWall; } // 修改个人资料参数 export interface ModifyProfileParams { - nick: string; - longNick: string; - sex: NTSex; - birthday: { birthday_year: string, birthday_month: string, birthday_day: string }; - location: unknown; + nick: string; + longNick: string; + sex: NTSex; + birthday: { birthday_year: string, birthday_month: string, birthday_day: string }; + location: unknown; } // 好友资料点赞请求 export interface BuddyProfileLikeReq { - friendUids: string[]; - basic: number; - vote: number; - favorite: number; - userProfile: number; - type: number; - start: number; - limit?: number; + friendUids: string[]; + basic: number; + vote: number; + favorite: number; + userProfile: number; + type: number; + start: number; + limit?: number; } // QQ等级信息 export interface QQLevel { - crownNum: number; - sunNum: number; - moonNum: number; - starNum: number; + crownNum: number; + sunNum: number; + moonNum: number; + starNum: number; } // 用户信息 export interface User { - uid: string; - uin: string; - nick: string; - avatarUrl?: string; - longNick?: string; - remark?: string; - sex?: NTSex; - age?: number; - qqLevel?: QQLevel; - qid?: string; - birthday_year?: number; - birthday_month?: number; - birthday_day?: number; - topTime?: string; - constellation?: number; - shengXiao?: number; - kBloodType?: number; - homeTown?: string; - makeFriendCareer?: number; - pos?: string; - eMail?: string; - phoneNum?: string; - college?: string; - country?: string; - province?: string; - city?: string; - postCode?: string; - address?: string; - isBlock?: boolean; - isSpecialCareOpen?: boolean; - isSpecialCareZone?: boolean; - ringId?: string; - regTime?: number; - interest?: string; - labels?: string[]; - isHideQQLevel?: number; - privilegeIcon?: { - jumpUrl: string; - openIconList: unknown[]; - closeIconList: unknown[]; - }; - photoWall?: { - picList: unknown[]; - }; - vipFlag?: boolean; - yearVipFlag?: boolean; - svipFlag?: boolean; - vipLevel?: number; - status?: number; - qidianMasterFlag?: number; - qidianCrewFlag?: number; - qidianCrewFlag2?: number; - extStatus?: number; - recommendImgFlag?: number; - disableEmojiShortCuts?: number; - pendantId?: string; + uid: string; + uin: string; + nick: string; + avatarUrl?: string; + longNick?: string; + remark?: string; + sex?: NTSex; + age?: number; + qqLevel?: QQLevel; + qid?: string; + birthday_year?: number; + birthday_month?: number; + birthday_day?: number; + topTime?: string; + constellation?: number; + shengXiao?: number; + kBloodType?: number; + homeTown?: string; + makeFriendCareer?: number; + pos?: string; + eMail?: string; + phoneNum?: string; + college?: string; + country?: string; + province?: string; + city?: string; + postCode?: string; + address?: string; + isBlock?: boolean; + isSpecialCareOpen?: boolean; + isSpecialCareZone?: boolean; + ringId?: string; + regTime?: number; + interest?: string; + labels?: string[]; + isHideQQLevel?: number; + privilegeIcon?: { + jumpUrl: string; + openIconList: unknown[]; + closeIconList: unknown[]; + }; + photoWall?: { + picList: unknown[]; + }; + vipFlag?: boolean; + yearVipFlag?: boolean; + svipFlag?: boolean; + vipLevel?: number; + status?: number; + qidianMasterFlag?: number; + qidianCrewFlag?: number; + qidianCrewFlag2?: number; + extStatus?: number; + recommendImgFlag?: number; + disableEmojiShortCuts?: number; + pendantId?: string; } // 自身信息 export interface SelfInfo extends User { - online?: boolean; + online?: boolean; } // 好友类型 @@ -342,34 +341,34 @@ export type Friend = User; // 业务键枚举 export enum BizKey { - KPRIVILEGEICON = 0, - KPHOTOWALL = 1 + KPRIVILEGEICON = 0, + KPHOTOWALL = 1, } // 根据UIN获取用户详细信息 export interface UserDetailInfoByUin { - result: number; - errMsg: string; - detail: { - uid: string; - uin: string; - simpleInfo: SimpleInfo; - commonExt: CommonExt; - photoWall: null; - }; + result: number; + errMsg: string; + detail: { + uid: string; + uin: string; + simpleInfo: SimpleInfo; + commonExt: CommonExt; + photoWall: null; + }; } // 用户详细信息来源枚举 export enum UserDetailSource { - KDB, - KSERVER + KDB, + KSERVER, } // 个人资料业务类型枚举 export enum ProfileBizType { - KALL = 0, - KBASEEXTEND = 1, - KVAS = 2, - KQZONE = 3, - KOTHER = 4 -} \ No newline at end of file + KALL = 0, + KBASEEXTEND = 1, + KVAS = 2, + KQZONE = 3, + KOTHER = 4, +} diff --git a/src/core/types/webapi.ts b/src/core/types/webapi.ts index c9771cba..b96aaac0 100644 --- a/src/core/types/webapi.ts +++ b/src/core/types/webapi.ts @@ -1,131 +1,131 @@ export enum WebHonorType { - ALL = 'all', - /** + ALL = 'all', + /** * 群聊之火 */ - TALKATIVE = 'talkative', - /** + TALKATIVE = 'talkative', + /** * 群聊炽焰 */ - PERFORMER = 'performer', - /** + PERFORMER = 'performer', + /** * 龙王 */ - LEGEND = 'legend', - /** + LEGEND = 'legend', + /** * 冒尖小春笋(R.I.P) */ - STRONG_NEWBIE = 'strong_newbie', - /** + STRONG_NEWBIE = 'strong_newbie', + /** * 快乐源泉 */ - EMOTION = 'emotion' + EMOTION = 'emotion', } export interface WebApiGroupMember { - uin: number; - role: number; - g: number; - join_time: number; - last_speak_time: number; - lv: { - point: number - level: number - }; - card: string; - tags: string; - flag: number; - nick: string; - qage: number; - rm: number; + uin: number; + role: number; + g: number; + join_time: number; + last_speak_time: number; + lv: { + point: number + level: number + }; + card: string; + tags: string; + flag: number; + nick: string; + qage: number; + rm: number; } export interface WebApiGroupMemberRet { - ec: number; - errcode: number; - em: string; - cache: number; - adm_num: number; - levelname: string; - mems: WebApiGroupMember[]; - count: number; - svr_time: number; - max_count: number; - search_count: number; - extmode: number; + ec: number; + errcode: number; + em: string; + cache: number; + adm_num: number; + levelname: string; + mems: WebApiGroupMember[]; + count: number; + svr_time: number; + max_count: number; + search_count: number; + extmode: number; } export interface WebApiGroupNoticeFeed { - u: number;//发送者 - fid: string;//fid,notice_id - pubt: number;//时间 - msg: { - text: string - text_face: string - title: string, - pics?: { - id: string, - w: string, - h: string - }[] - }; - type: number; - fn: number; - cn: number; - vn: number; - settings: { - is_show_edit_card: number - remind_ts: number - tip_window_type: number - confirm_required: number - }; - read_num: number; - is_read: number; - is_all_confirm: number; + u: number;// 发送者 + fid: string;// fid,notice_id + pubt: number;// 时间 + msg: { + text: string + text_face: string + title: string, + pics?: { + id: string, + w: string, + h: string + }[] + }; + type: number; + fn: number; + cn: number; + vn: number; + settings: { + is_show_edit_card: number + remind_ts: number + tip_window_type: number + confirm_required: number + }; + read_num: number; + is_read: number; + is_all_confirm: number; } export interface WebApiGroupNoticeRet { - ec: number - em: string - ltsm: number - srv_code: number - read_only: number - role: number - feeds: WebApiGroupNoticeFeed[] - group: { - group_id: number - class_ext: number - } - sta: number, - gln: number - tst: number, - ui: unknown - server_time: number - svrt: number - ad: number + ec: number + em: string + ltsm: number + srv_code: number + read_only: number + role: number + feeds: WebApiGroupNoticeFeed[] + group: { + group_id: number + class_ext: number + } + sta: number, + gln: number + tst: number, + ui: unknown + server_time: number + svrt: number + ad: number } export interface GroupEssenceMsg { - group_code: string; - msg_seq: number; - msg_random: number; - sender_uin: string; - sender_nick: string; - sender_time: number; - add_digest_uin: string; - add_digest_nick: string; - add_digest_time: number; - msg_content: { msg_type: number, text?: string, image_url?: string }[]; - can_be_removed: true; + group_code: string; + msg_seq: number; + msg_random: number; + sender_uin: string; + sender_nick: string; + sender_time: number; + add_digest_uin: string; + add_digest_nick: string; + add_digest_time: number; + msg_content: { msg_type: number, text?: string, image_url?: string }[]; + can_be_removed: true; } export interface GroupEssenceMsgRet { - retcode: number; - retmsg: string; - data: { - msg_list: GroupEssenceMsg[] - is_end: boolean - group_role: number - config_page_url: string - }; + retcode: number; + retmsg: string; + data: { + msg_list: GroupEssenceMsg[] + is_end: boolean + group_role: number + config_page_url: string + }; } diff --git a/src/core/wrapper.ts b/src/core/wrapper.ts index 0bc5b15e..d22bb327 100644 --- a/src/core/wrapper.ts +++ b/src/core/wrapper.ts @@ -1,18 +1,18 @@ import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from './adapters'; import { - NodeIKernelAvatarService, - NodeIKernelBuddyService, - NodeIKernelGroupService, - NodeIKernelLoginService, - NodeIKernelMsgService, - NodeIKernelProfileLikeService, - NodeIKernelProfileService, - NodeIKernelRichMediaService, - NodeIKernelRobotService, - NodeIKernelSessionListener, - NodeIKernelStorageCleanService, - NodeIKernelTicketService, - NodeIKernelTipOffService, + NodeIKernelAvatarService, + NodeIKernelBuddyService, + NodeIKernelGroupService, + NodeIKernelLoginService, + NodeIKernelMsgService, + NodeIKernelProfileLikeService, + NodeIKernelProfileService, + NodeIKernelRichMediaService, + NodeIKernelRobotService, + NodeIKernelSessionListener, + NodeIKernelStorageCleanService, + NodeIKernelTicketService, + NodeIKernelTipOffService, } from '@/core/index'; import { NodeIKernelNodeMiscService } from './services/NodeIKernelNodeMiscService'; import { NodeIKernelUixConvertService } from './services/NodeIKernelUixConvertService'; @@ -29,331 +29,331 @@ import { NodeIKernelECDHService } from './services/NodeIKernelECDHService'; import { NodeIO3MiscService } from './services/NodeIO3MiscService'; export interface NodeQQNTWrapperUtil { - get(): NodeQQNTWrapperUtil; + get(): NodeQQNTWrapperUtil; - getNTUserDataInfoConfig(): string; + getNTUserDataInfoConfig(): string; - emptyWorkingSet(n: number): void; + emptyWorkingSet(n: number): void; - getSsoCmdOfOidbReq(arg1: number, arg2: number): unknown; + getSsoCmdOfOidbReq(arg1: number, arg2: number): unknown; - getSsoBufferOfOidbReq(...args: unknown[]): unknown; //有点看不懂参数定义 待补充 好像是三个参数 + getSsoBufferOfOidbReq(...args: unknown[]): unknown; // 有点看不懂参数定义 待补充 好像是三个参数 - getOidbRspInfo(arg: string): unknown; //可能是错的 + getOidbRspInfo(arg: string): unknown; // 可能是错的 - getFileSize(path: string): Promise; //直接的猜测 + getFileSize(path: string): Promise; // 直接的猜测 - genFileMd5Buf(arg: string): unknown; //可能是错的 + genFileMd5Buf(arg: string): unknown; // 可能是错的 - genFileMd5Hex(path: string): unknown; //直接的猜测 + genFileMd5Hex(path: string): unknown; // 直接的猜测 - genFileShaBuf(path: string): unknown; //直接的猜测 + genFileShaBuf(path: string): unknown; // 直接的猜测 - genFileCumulateSha1(path: string): unknown; //直接的猜测 + genFileCumulateSha1(path: string): unknown; // 直接的猜测 - genFileShaHex(path: string): unknown; //直接的猜测 + genFileShaHex(path: string): unknown; // 直接的猜测 - fileIsExist(path: string): unknown; + fileIsExist(path: string): unknown; - startTrace(path: string): unknown; //可能是错的 + startTrace(path: string): unknown; // 可能是错的 - copyFile(src: string, dst: string): unknown; + copyFile(src: string, dst: string): unknown; - genFileShaAndMd5Hex(path: string, unknown: number): unknown; //可能是错的 + genFileShaAndMd5Hex(path: string, unknown: number): unknown; // 可能是错的 - setTraceInfo(unknown: unknown): unknown; + setTraceInfo(unknown: unknown): unknown; - encodeOffLine(unknown: unknown): unknown; + encodeOffLine(unknown: unknown): unknown; - decodeOffLine(arg: string): unknown; //可能是错的 传递hex + decodeOffLine(arg: string): unknown; // 可能是错的 传递hex - DecoderRecentInfo(arg: string): unknown; //可能是错的 传递hex + DecoderRecentInfo(arg: string): unknown; // 可能是错的 传递hex - getPinyin(arg0: string, arg1: boolean): unknown; + getPinyin(arg0: string, arg1: boolean): unknown; - matchInPinyin(arg0: unknown[], arg1: string): unknown; //参数特复杂 arg0是个复杂数据类型 + matchInPinyin(arg0: unknown[], arg1: string): unknown; // 参数特复杂 arg0是个复杂数据类型 - makeDirByPath(arg0: string): unknown; + makeDirByPath(arg0: string): unknown; - emptyWorkingSet(arg0: number): unknown; //参数是UINT32 + emptyWorkingSet(arg0: number): unknown; // 参数是UINT32 - runProcess(arg0: string, arg1: boolean): unknown; + runProcess(arg0: string, arg1: boolean): unknown; - runProcessArgs(arg0: string, arg1: { [key: string]: string }, arg2: boolean): unknown; + runProcessArgs(arg0: string, arg1: { [key: string]: string }, arg2: boolean): unknown; - calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown; + calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown; - fullWordToHalfWord(word: string): unknown; + fullWordToHalfWord(word: string): unknown; - getNTUserDataInfoConfig(): unknown; + getNTUserDataInfoConfig(): unknown; - pathIsReadableAndWriteable(path: string): unknown; //直接的猜测 + pathIsReadableAndWriteable(path: string): unknown; // 直接的猜测 - resetUserDataSavePathToDocument(): unknown; + resetUserDataSavePathToDocument(): unknown; - getSoBuildInfo(): unknown; //例如 0[0]_d491dc01e0a_0 + getSoBuildInfo(): unknown; // 例如 0[0]_d491dc01e0a_0 - registerCountInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown; + registerCountInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown; - registerValueInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown; + registerValueInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown; - registerValueInstrumentsWithBoundary( - arg0: string, - arg1: unknown, - arg2: unknown, - arg3: number, - arg4: number, - ): unknown; + registerValueInstrumentsWithBoundary( + arg0: string, + arg1: unknown, + arg2: unknown, + arg3: number, + arg4: number, + ): unknown; - reportCountIndicators( - arg0: string, - arg1: Map, - arg2: string, - arg3: number, - arg4: boolean, - ): unknown; + reportCountIndicators( + arg0: string, + arg1: Map, + arg2: string, + arg3: number, + arg4: boolean, + ): unknown; - reportValueIndicators( - arg0: string, - arg1: Map, - arg2: string, - arg3: boolean, - arg4: number, - ): unknown; + reportValueIndicators( + arg0: string, + arg1: Map, + arg2: string, + arg3: boolean, + arg4: number, + ): unknown; - checkNewUserDataSaveDirAvailable(arg0: string): unknown; + checkNewUserDataSaveDirAvailable(arg0: string): unknown; - copyUserData(arg0: string, arg1: string): Promise; + copyUserData(arg0: string, arg1: string): Promise; - setUserDataSaveDirectory(arg0: string): Promise; + setUserDataSaveDirectory(arg0: string): Promise; - hasOtherRunningQQProcess(): boolean; + hasOtherRunningQQProcess(): boolean; - quitAllRunningQQProcess(arg: boolean): unknown; + quitAllRunningQQProcess(arg: boolean): unknown; - checkNvidiaConfig(): unknown; + checkNvidiaConfig(): unknown; - repairNvidiaConfig(): unknown; + repairNvidiaConfig(): unknown; - getNvidiaDriverVersion(): unknown; + getNvidiaDriverVersion(): unknown; - isNull(): unknown; + isNull(): unknown; } export interface NodeIQQNTStartupSessionWrapper { - create(): NodeIQQNTStartupSessionWrapper; - stop(): void; - start(): void; - createWithModuleList(uk: unknown): unknown; - getSessionIdList(): unknown; + create(): NodeIQQNTStartupSessionWrapper; + stop(): void; + start(): void; + createWithModuleList(uk: unknown): unknown; + getSessionIdList(): unknown; } export interface NodeIQQNTWrapperSession { - getNTWrapperSession(str: string): NodeIQQNTWrapperSession; + getNTWrapperSession(str: string): NodeIQQNTWrapperSession; - get(): NodeIQQNTWrapperSession; + get(): NodeIQQNTWrapperSession; - new(): NodeIQQNTWrapperSession; + new(): NodeIQQNTWrapperSession; - create(): NodeIQQNTWrapperSession; + create(): NodeIQQNTWrapperSession; - init( - wrapperSessionInitConfig: WrapperSessionInitConfig, - nodeIDependsAdapter: NodeIDependsAdapter, - nodeIDispatcherAdapter: NodeIDispatcherAdapter, - nodeIKernelSessionListener: NodeIKernelSessionListener, - ): void; + init( + wrapperSessionInitConfig: WrapperSessionInitConfig, + nodeIDependsAdapter: NodeIDependsAdapter, + nodeIDispatcherAdapter: NodeIDispatcherAdapter, + nodeIKernelSessionListener: NodeIKernelSessionListener, + ): void; - startNT(session: number): void; + startNT(session: number): void; - startNT(): void; + startNT(): void; - getBdhUploadService(): unknown; + getBdhUploadService(): unknown; - getECDHService(): NodeIKernelECDHService; + getECDHService(): NodeIKernelECDHService; - getMsgService(): NodeIKernelMsgService; + getMsgService(): NodeIKernelMsgService; - getProfileService(): NodeIKernelProfileService; + getProfileService(): NodeIKernelProfileService; - getProfileLikeService(): NodeIKernelProfileLikeService; + getProfileLikeService(): NodeIKernelProfileLikeService; - getGroupService(): NodeIKernelGroupService; + getGroupService(): NodeIKernelGroupService; - getStorageCleanService(): NodeIKernelStorageCleanService; + getStorageCleanService(): NodeIKernelStorageCleanService; - getBuddyService(): NodeIKernelBuddyService; + getBuddyService(): NodeIKernelBuddyService; - getRobotService(): NodeIKernelRobotService; + getRobotService(): NodeIKernelRobotService; - getTicketService(): NodeIKernelTicketService; + getTicketService(): NodeIKernelTicketService; - getTipOffService(): NodeIKernelTipOffService; + getTipOffService(): NodeIKernelTipOffService; - getNodeMiscService(): NodeIKernelNodeMiscService; + getNodeMiscService(): NodeIKernelNodeMiscService; - getRichMediaService(): NodeIKernelRichMediaService; + getRichMediaService(): NodeIKernelRichMediaService; - getMsgBackupService(): NodeIKernelMsgBackupService; + getMsgBackupService(): NodeIKernelMsgBackupService; - getAlbumService(): NodeIKernelAlbumService; + getAlbumService(): NodeIKernelAlbumService; - getTianShuService(): NodeIKernelTianShuService; + getTianShuService(): NodeIKernelTianShuService; - getUnitedConfigService(): NodeIKernelUnitedConfigService; + getUnitedConfigService(): NodeIKernelUnitedConfigService; - getSearchService(): NodeIKernelSearchService; + getSearchService(): NodeIKernelSearchService; - getDirectSessionService(): unknown; + getDirectSessionService(): unknown; - getRDeliveryService(): unknown; + getRDeliveryService(): unknown; - getAvatarService(): NodeIKernelAvatarService; + getAvatarService(): NodeIKernelAvatarService; - getFeedChannelService(): unknown; + getFeedChannelService(): unknown; - getYellowFaceService(): unknown; + getYellowFaceService(): unknown; - getCollectionService(): NodeIKernelCollectionService; + getCollectionService(): NodeIKernelCollectionService; - getSettingService(): unknown; + getSettingService(): unknown; - getQiDianService(): unknown; + getQiDianService(): unknown; - getFileAssistantService(): unknown; + getFileAssistantService(): unknown; - getGuildService(): unknown; + getGuildService(): unknown; - getSkinService(): unknown; + getSkinService(): unknown; - getTestPerformanceService(): NodeIkernelTestPerformanceService; + getTestPerformanceService(): NodeIkernelTestPerformanceService; - getQQPlayService(): unknown; + getQQPlayService(): unknown; - getDbToolsService(): unknown; + getDbToolsService(): unknown; - getUixConvertService(): NodeIKernelUixConvertService; + getUixConvertService(): NodeIKernelUixConvertService; - getOnlineStatusService(): unknown; + getOnlineStatusService(): unknown; - getRemotingService(): unknown; + getRemotingService(): unknown; - getGroupTabService(): unknown; + getGroupTabService(): unknown; - getGroupSchoolService(): unknown; + getGroupSchoolService(): unknown; - getLiteBusinessService(): unknown; + getLiteBusinessService(): unknown; - getGuildMsgService(): unknown; + getGuildMsgService(): unknown; - getLockService(): unknown; + getLockService(): unknown; - getMSFService(): NodeIKernelMSFService; + getMSFService(): NodeIKernelMSFService; - getGuildHotUpdateService(): unknown; + getGuildHotUpdateService(): unknown; - getAVSDKService(): unknown; + getAVSDKService(): unknown; - getRecentContactService(): NodeIKernelRecentContactService; + getRecentContactService(): NodeIKernelRecentContactService; - getConfigMgrService(): unknown; + getConfigMgrService(): unknown; } export interface EnginInitDesktopConfig { - base_path_prefix: string; - platform_type: PlatformType; - app_type: 4; - app_version: string; - os_version: string; - use_xlog: boolean; - qua: string; - global_path_config: { - desktopGlobalPath: string; - }; - thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2 }; + base_path_prefix: string; + platform_type: PlatformType; + app_type: 4; + app_version: string; + os_version: string; + use_xlog: boolean; + qua: string; + global_path_config: { + desktopGlobalPath: string; + }; + thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2 }; } export interface NodeIQQNTWrapperEngine { - get(): NodeIQQNTWrapperEngine; + get(): NodeIQQNTWrapperEngine; - initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void; + initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void; } export interface WrapperNodeApi { - NodeIO3MiscService: NodeIO3MiscService; - NodeQQNTWrapperUtil: NodeQQNTWrapperUtil; - NodeIQQNTWrapperSession: NodeIQQNTWrapperSession; - NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapper - NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine; - NodeIKernelLoginService: NodeIKernelLoginService; + NodeIO3MiscService: NodeIO3MiscService; + NodeQQNTWrapperUtil: NodeQQNTWrapperUtil; + NodeIQQNTWrapperSession: NodeIQQNTWrapperSession; + NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapper + NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine; + NodeIKernelLoginService: NodeIKernelLoginService; } export enum PlatformType { - KUNKNOWN, - KANDROID, - KIOS, - KWINDOWS, - KMAC, - KLINUX + KUNKNOWN, + KANDROID, + KIOS, + KWINDOWS, + KMAC, + KLINUX, } export enum DeviceType { - KUNKNOWN, - KPHONE, - KPAD, - KCOMPUTER, + KUNKNOWN, + KPHONE, + KPAD, + KCOMPUTER, } -//推送类型 +// 推送类型 export enum VendorType { - KNOSETONIOS = 0, - KSUPPORTGOOGLEPUSH = 99, - KSUPPORTHMS = 3, - KSUPPORTOPPOPUSH = 4, - KSUPPORTTPNS = 2, - KSUPPORTVIVOPUSH = 5, - KUNSUPPORTANDROIDPUSH = 1, + KNOSETONIOS = 0, + KSUPPORTGOOGLEPUSH = 99, + KSUPPORTHMS = 3, + KSUPPORTOPPOPUSH = 4, + KSUPPORTTPNS = 2, + KSUPPORTVIVOPUSH = 5, + KUNSUPPORTANDROIDPUSH = 1, } export interface WrapperSessionInitConfig { - selfUin: string; - selfUid: string; - desktopPathConfig: { - account_path: string; // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 - }; - clientVer: string; // 9.9.8-22355 - a2: string; - d2: string; - d2Key: string; - machineId: string; - platform: PlatformType; // 3是Windows? - platVer: string; // 系统版本号, 应该可以固定 - appid: string; - rdeliveryConfig: { - appKey: string; - systemId: number; - appId: string; - logicEnvironment: string; - platform: PlatformType; - language: string; - sdkVersion: string; - userId: string; - appVersion: string; - osVersion: string; - bundleId: string; - serverUrl: string; - fixedAfterHitKeys: string[]; - }; - defaultFileDownloadPath: string; // 这个可以通过环境变量获取? - deviceInfo: { - guid: string; - buildVer: string; - localId: number; - devName: string; - devType: string; - vendorName: string; - osVer: string; - vendorOsName: string; - setMute: boolean; - vendorType: VendorType; - }; - deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'; + selfUin: string; + selfUid: string; + desktopPathConfig: { + account_path: string; // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 + }; + clientVer: string; // 9.9.8-22355 + a2: string; + d2: string; + d2Key: string; + machineId: string; + platform: PlatformType; // 3是Windows? + platVer: string; // 系统版本号, 应该可以固定 + appid: string; + rdeliveryConfig: { + appKey: string; + systemId: number; + appId: string; + logicEnvironment: string; + platform: PlatformType; + language: string; + sdkVersion: string; + userId: string; + appVersion: string; + osVersion: string; + bundleId: string; + serverUrl: string; + fixedAfterHitKeys: string[]; + }; + defaultFileDownloadPath: string; // 这个可以通过环境变量获取? + deviceInfo: { + guid: string; + buildVer: string; + localId: number; + devName: string; + devType: string; + vendorName: string; + osVer: string; + vendorOsName: string; + setMute: boolean; + vendorType: VendorType; + }; + deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'; } diff --git a/src/example-plugin/index.ts b/src/example-plugin/index.ts index 1bd308ec..a17d36c7 100644 --- a/src/example-plugin/index.ts +++ b/src/example-plugin/index.ts @@ -1,12 +1,12 @@ -import { EventType } from "@/onebot/event/OneBotEvent"; -import type { PluginModule } from "@/onebot/network/plugin-manger"; +import { EventType } from '@/onebot/event/OneBotEvent'; +import type { PluginModule } from '@/onebot/network/plugin-manger'; -const plugin_init: PluginModule["plugin_init"] = async (_core, _obContext, _actions, _instance) => { - console.log(`[Plugin: example] 插件已初始化`); -} -const plugin_onmessage: PluginModule["plugin_onmessage"] = async (adapter, _core, _obCtx, event, actions, instance) => { - if (event.post_type === EventType.MESSAGE && event.raw_message.includes('ping')) { - await actions.get('send_group_msg')?.handle({ group_id: String(event.group_id), message: 'pong' }, adapter, instance.config); - } -} -export { plugin_init, plugin_onmessage }; \ No newline at end of file +const plugin_init: PluginModule['plugin_init'] = async (_core, _obContext, _actions, _instance) => { + console.log('[Plugin: example] 插件已初始化'); +}; +const plugin_onmessage: PluginModule['plugin_onmessage'] = async (adapter, _core, _obCtx, event, actions, instance) => { + if (event.post_type === EventType.MESSAGE && event.raw_message.includes('ping')) { + await actions.get('send_group_msg')?.handle({ group_id: String(event.group_id), message: 'pong' }, adapter, instance.config); + } +}; +export { plugin_init, plugin_onmessage }; diff --git a/src/example-plugin/vite.config.ts b/src/example-plugin/vite.config.ts index a70d61d6..c735cb61 100644 --- a/src/example-plugin/vite.config.ts +++ b/src/example-plugin/vite.config.ts @@ -6,25 +6,25 @@ import { builtinModules } from 'module'; const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat(); export default defineConfig({ - resolve: { - conditions: ['node', 'default'], - alias: { - '@/core': resolve(__dirname, '../core'), - '@': resolve(__dirname, '../'), - }, + resolve: { + conditions: ['node', 'default'], + alias: { + '@/core': resolve(__dirname, '../core'), + '@': resolve(__dirname, '../'), }, - build: { - sourcemap: false, - target: 'esnext', - minify: false, - lib: { - entry: 'index.ts', - formats: ['es'], - fileName: () => 'index.mjs', - }, - rollupOptions: { - external: [...nodeModules], - }, + }, + build: { + sourcemap: false, + target: 'esnext', + minify: false, + lib: { + entry: 'index.ts', + formats: ['es'], + fileName: () => 'index.mjs', }, - plugins: [nodeResolve()], + rollupOptions: { + external: [...nodeModules], + }, + }, + plugins: [nodeResolve()], }); diff --git a/src/framework/napcat.ts b/src/framework/napcat.ts index e83ce425..a9a1b5a1 100644 --- a/src/framework/napcat.ts +++ b/src/framework/napcat.ts @@ -12,99 +12,99 @@ import { NapCatOneBot11Adapter } from '@/onebot'; import { FFmpegService } from '@/common/ffmpeg'; import { NativePacketHandler } from '@/core/packet/handler/client'; -//Framework ES入口文件 -export async function getWebUiUrl() { - const WebUiConfigData = (await WebUiConfig.GetWebUIConfig()); - return 'http://127.0.0.1:' + webUiRuntimePort + '/webui/?token=' + encodeURIComponent(WebUiConfigData.token); +// Framework ES入口文件 +export async function getWebUiUrl () { + const WebUiConfigData = (await WebUiConfig.GetWebUIConfig()); + return 'http://127.0.0.1:' + webUiRuntimePort + '/webui/?token=' + encodeURIComponent(WebUiConfigData.token); } -export async function NCoreInitFramework( - session: NodeIQQNTWrapperSession, - loginService: NodeIKernelLoginService, - registerInitCallback: (callback: () => void) => void, +export async function NCoreInitFramework ( + session: NodeIQQNTWrapperSession, + loginService: NodeIKernelLoginService, + registerInitCallback: (callback: () => void) => void ) { - //在进入本层前是否登录未进行判断 - console.log('NapCat Framework App Loading...'); + // 在进入本层前是否登录未进行判断 + console.log('NapCat Framework App Loading...'); - process.on('uncaughtException', (err) => { - console.log('[NapCat] [Error] Unhandled Exception:', err.message); - }); + process.on('uncaughtException', (err) => { + console.log('[NapCat] [Error] Unhandled Exception:', err.message); + }); - process.on('unhandledRejection', (reason) => { - console.log('[NapCat] [Error] unhandledRejection:', reason); - }); + process.on('unhandledRejection', (reason) => { + console.log('[NapCat] [Error] unhandledRejection:', reason); + }); - const pathWrapper = new NapCatPathWrapper(); - const logger = new LogWrapper(pathWrapper.logsPath); - const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); - const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion()); - const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 - // nativePacketHandler.onAll((packet) => { - // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); - // }); - await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion()); - // 在 init 之后注册监听器 + const pathWrapper = new NapCatPathWrapper(); + const logger = new LogWrapper(pathWrapper.logsPath); + const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); + const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion()); + const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 + // nativePacketHandler.onAll((packet) => { + // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); + // }); + await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion()); + // 在 init 之后注册监听器 - // 初始化 FFmpeg 服务 - await FFmpegService.init(pathWrapper.binaryPath, logger); - //直到登录成功后,执行下一步 - // const selfInfo = { - // uid: 'u_FUSS0_x06S_9Tf4na_WpUg', - // uin: '3684714082', - // nick: '', - // online: true - // } - const selfInfo = await new Promise((resolveSelfInfo) => { - const loginListener = new NodeIKernelLoginListener(); - loginListener.onQRCodeLoginSucceed = async (loginResult) => { - await new Promise(resolvePendingInit => { - registerInitCallback(() => resolvePendingInit()); - }); - resolveSelfInfo({ - uid: loginResult.uid, - uin: loginResult.uin, - nick: '', // 获取不到 - online: true, - }); - }; - loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger)); - }); + // 初始化 FFmpeg 服务 + await FFmpegService.init(pathWrapper.binaryPath, logger); + // 直到登录成功后,执行下一步 + // const selfInfo = { + // uid: 'u_FUSS0_x06S_9Tf4na_WpUg', + // uin: '3684714082', + // nick: '', + // online: true + // } + const selfInfo = await new Promise((resolveSelfInfo) => { + const loginListener = new NodeIKernelLoginListener(); + loginListener.onQRCodeLoginSucceed = async (loginResult) => { + await new Promise(resolvePendingInit => { + registerInitCallback(() => resolvePendingInit()); + }); + resolveSelfInfo({ + uid: loginResult.uid, + uin: loginResult.uin, + nick: '', // 获取不到 + online: true, + }); + }; + loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger)); + }); // 过早进入会导致addKernelMsgListener等Listener添加失败 // await sleep(2500); // 初始化 NapCatFramework - const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler); - await loaderObject.core.initCore(); + const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler); + await loaderObject.core.initCore(); - //启动WebUi - InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e)); - //初始化LLNC的Onebot实现 - await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot(); + // 启动WebUi + InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e)); + // 初始化LLNC的Onebot实现 + await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot(); } export class NapCatFramework { - public core: NapCatCore; - context: InstanceContext; + public core: NapCatCore; + context: InstanceContext; - constructor( - wrapper: WrapperNodeApi, - session: NodeIQQNTWrapperSession, - logger: LogWrapper, - loginService: NodeIKernelLoginService, - selfInfo: SelfInfo, - basicInfoWrapper: QQBasicInfoWrapper, - pathWrapper: NapCatPathWrapper, - packetHandler: NativePacketHandler, - ) { - this.context = { - packetHandler, - workingEnv: NapCatCoreWorkingEnv.Framework, - wrapper, - session, - logger, - loginService, - basicInfoWrapper, - pathWrapper, - }; - this.core = new NapCatCore(this.context, selfInfo); - } + constructor ( + wrapper: WrapperNodeApi, + session: NodeIQQNTWrapperSession, + logger: LogWrapper, + loginService: NodeIKernelLoginService, + selfInfo: SelfInfo, + basicInfoWrapper: QQBasicInfoWrapper, + pathWrapper: NapCatPathWrapper, + packetHandler: NativePacketHandler + ) { + this.context = { + packetHandler, + workingEnv: NapCatCoreWorkingEnv.Framework, + wrapper, + session, + logger, + loginService, + basicInfoWrapper, + pathWrapper, + }; + this.core = new NapCatCore(this.context, selfInfo); + } } diff --git a/src/framework/renderer.js b/src/framework/renderer.js index dfcb9da3..710d3c3e 100644 --- a/src/framework/renderer.js +++ b/src/framework/renderer.js @@ -1,6 +1,6 @@ export const onSettingWindowCreated = async (view) => { - let webui = await window.napcat.getWebUiUrl(); - view.innerHTML = ` + const webui = await window.napcat.getWebUiUrl(); + view.innerHTML = ` @@ -19,22 +19,22 @@ export const onSettingWindowCreated = async (view) => { `; - view.querySelector('.nc_openwebui').addEventListener('click', () => { - window.napcat.openInnerUrl(webui); - }); - view.querySelector('.nc_openwebui_ex').addEventListener('click', () => { - window.napcat.openExternalUrl(webui); - }); + view.querySelector('.nc_openwebui').addEventListener('click', () => { + window.napcat.openInnerUrl(webui); + }); + view.querySelector('.nc_openwebui_ex').addEventListener('click', () => { + window.napcat.openExternalUrl(webui); + }); - view.querySelector('.nc_webui').innerText = webui; + view.querySelector('.nc_webui').innerText = webui; - // 添加点击复制功能 - view.querySelector('.nc_webui').addEventListener('click', async () => { - try { - await navigator.clipboard.writeText(webui); - alert('WebUi URL 已复制到剪贴板'); - } catch (err) { - console.error('复制到剪贴板失败: ', err); - } - }); -}; \ No newline at end of file + // 添加点击复制功能 + view.querySelector('.nc_webui').addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(webui); + alert('WebUi URL 已复制到剪贴板'); + } catch (err) { + console.error('复制到剪贴板失败: ', err); + } + }); +}; diff --git a/src/image-size/index.ts b/src/image-size/index.ts index c9b93fab..8626a7a7 100644 --- a/src/image-size/index.ts +++ b/src/image-size/index.ts @@ -2,425 +2,423 @@ import * as fs from 'fs'; import { ReadStream } from 'fs'; export interface ImageSize { - width: number; - height: number; + width: number; + height: number; } export enum ImageType { - JPEG = 'jpeg', - PNG = 'png', - BMP = 'bmp', - GIF = 'gif', - WEBP = 'webp', - UNKNOWN = 'unknown', + JPEG = 'jpeg', + PNG = 'png', + BMP = 'bmp', + GIF = 'gif', + WEBP = 'webp', + UNKNOWN = 'unknown', } interface ImageParser { - readonly type: ImageType; - canParse(buffer: Buffer): boolean; - parseSize(stream: ReadStream): Promise; + readonly type: ImageType; + canParse(buffer: Buffer): boolean; + parseSize(stream: ReadStream): Promise; } // 魔术匹配 -function matchMagic(buffer: Buffer, magic: number[], offset = 0): boolean { - if (buffer.length < offset + magic.length) { - return false; - } +function matchMagic (buffer: Buffer, magic: number[], offset = 0): boolean { + if (buffer.length < offset + magic.length) { + return false; + } - for (let i = 0; i < magic.length; i++) { - if (buffer[offset + i] !== magic[i]) { - return false; - } + for (let i = 0; i < magic.length; i++) { + if (buffer[offset + i] !== magic[i]) { + return false; } - return true; + } + return true; } // PNG解析器 class PngParser implements ImageParser { - readonly type = ImageType.PNG; - // PNG 魔术头:89 50 4E 47 0D 0A 1A 0A - private readonly PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; + readonly type = ImageType.PNG; + // PNG 魔术头:89 50 4E 47 0D 0A 1A 0A + private readonly PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; - canParse(buffer: Buffer): boolean { - return matchMagic(buffer, this.PNG_SIGNATURE); - } + canParse (buffer: Buffer): boolean { + return matchMagic(buffer, this.PNG_SIGNATURE); + } - async parseSize(stream: ReadStream): Promise { - return new Promise((resolve, reject) => { - stream.once('error', reject); - stream.once('readable', () => { - const buf = stream.read(24) as Buffer; - if (!buf || buf.length < 24) { - return resolve(undefined); - } - if (this.canParse(buf)) { - const width = buf.readUInt32BE(16); - const height = buf.readUInt32BE(20); - resolve({ width, height }); - } else { - resolve(undefined); - } - }); - }); - } + async parseSize (stream: ReadStream): Promise { + return new Promise((resolve, reject) => { + stream.once('error', reject); + stream.once('readable', () => { + const buf = stream.read(24) as Buffer; + if (!buf || buf.length < 24) { + return resolve(undefined); + } + if (this.canParse(buf)) { + const width = buf.readUInt32BE(16); + const height = buf.readUInt32BE(20); + resolve({ width, height }); + } else { + resolve(undefined); + } + }); + }); + } } // JPEG解析器 class JpegParser implements ImageParser { - readonly type = ImageType.JPEG; - // JPEG 魔术头:FF D8 - private readonly JPEG_SIGNATURE = [0xFF, 0xD8]; + readonly type = ImageType.JPEG; + // JPEG 魔术头:FF D8 + private readonly JPEG_SIGNATURE = [0xFF, 0xD8]; - // JPEG标记常量 - private readonly SOF_MARKERS = { - SOF0: 0xC0, // 基线DCT - SOF1: 0xC1, // 扩展顺序DCT - SOF2: 0xC2, // 渐进式DCT - SOF3: 0xC3, // 无损 - } as const; + // JPEG标记常量 + private readonly SOF_MARKERS = { + SOF0: 0xC0, // 基线DCT + SOF1: 0xC1, // 扩展顺序DCT + SOF2: 0xC2, // 渐进式DCT + SOF3: 0xC3, // 无损 + } as const; - // 非SOF标记 - private readonly NON_SOF_MARKERS: number[] = [ - 0xC4, // DHT - 0xC8, // JPEG扩展 - 0xCC, // DAC - ] as const; + // 非SOF标记 + private readonly NON_SOF_MARKERS: number[] = [ + 0xC4, // DHT + 0xC8, // JPEG扩展 + 0xCC, // DAC + ] as const; - canParse(buffer: Buffer): boolean { - return matchMagic(buffer, this.JPEG_SIGNATURE); - } + canParse (buffer: Buffer): boolean { + return matchMagic(buffer, this.JPEG_SIGNATURE); + } - isSOFMarker(marker: number): boolean { - return ( - marker === this.SOF_MARKERS.SOF0 || + isSOFMarker (marker: number): boolean { + return ( + marker === this.SOF_MARKERS.SOF0 || marker === this.SOF_MARKERS.SOF1 || marker === this.SOF_MARKERS.SOF2 || marker === this.SOF_MARKERS.SOF3 - ); - } + ); + } - isNonSOFMarker(marker: number): boolean { - return this.NON_SOF_MARKERS.includes(marker); - } + isNonSOFMarker (marker: number): boolean { + return this.NON_SOF_MARKERS.includes(marker); + } - async parseSize(stream: ReadStream): Promise { - return new Promise((resolve, reject) => { - const BUFFER_SIZE = 1024; // 读取块大小,可以根据需要调整 - let buffer = Buffer.alloc(0); - let offset = 0; - let found = false; + async parseSize (stream: ReadStream): Promise { + return new Promise((resolve, reject) => { + const BUFFER_SIZE = 1024; // 读取块大小,可以根据需要调整 + let buffer = Buffer.alloc(0); + let offset = 0; + let found = false; - // 处理错误 - stream.on('error', (err) => { + // 处理错误 + stream.on('error', (err) => { + stream.destroy(); + reject(err); + }); + + // 处理数据块 + stream.on('data', (chunk: Buffer | string) => { + // 追加新数据到缓冲区 + const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + buffer = Buffer.concat([buffer.subarray(offset), chunkBuffer]); + offset = 0; + + // 保持缓冲区在合理大小内,只保留最后的部分用于跨块匹配 + const bufferSize = buffer.length; + const MIN_REQUIRED_BYTES = 10; // SOF段最低字节数 + + // 从JPEG头部后开始扫描 + while (offset < bufferSize - MIN_REQUIRED_BYTES) { + // 寻找FF标记 + if (buffer[offset] === 0xFF && buffer[offset + 1]! >= 0xC0 && buffer[offset + 1]! <= 0xCF) { + const marker = buffer[offset + 1]; + if (!marker) { + break; + } + // 跳过非SOF标记 + if (this.isNonSOFMarker(marker)) { + offset += 2; + continue; + } + + // 处理SOF标记 (包含尺寸信息) + if (this.isSOFMarker(marker)) { + // 确保缓冲区中有足够数据读取尺寸 + if (offset + 9 < bufferSize) { + // 解析尺寸: FF XX YY YY PP HH HH WW WW ... + // XX = 标记, YY YY = 段长度, PP = 精度, HH HH = 高, WW WW = 宽 + const height = buffer.readUInt16BE(offset + 5); + const width = buffer.readUInt16BE(offset + 7); + + found = true; stream.destroy(); - reject(err); - }); + resolve({ width, height }); + return; + } else { + // 如果缓冲区内数据不够,保留当前位置等待更多数据 + break; + } + } + } - // 处理数据块 - stream.on('data', (chunk: Buffer | string) => { - // 追加新数据到缓冲区 - const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - buffer = Buffer.concat([buffer.subarray(offset), chunkBuffer]); - offset = 0; + offset++; + } - // 保持缓冲区在合理大小内,只保留最后的部分用于跨块匹配 - const bufferSize = buffer.length; - const MIN_REQUIRED_BYTES = 10; // SOF段最低字节数 + // 缓冲区管理: 如果处理了许多数据但没找到标记, + // 保留最后N字节用于跨块匹配,丢弃之前的数据 + if (offset > BUFFER_SIZE) { + const KEEP_BYTES = 20; // 保留足够数据以处理跨块边界的情况 + if (offset > KEEP_BYTES) { + buffer = buffer.subarray(offset - KEEP_BYTES); + offset = KEEP_BYTES; + } + } + }); - // 从JPEG头部后开始扫描 - while (offset < bufferSize - MIN_REQUIRED_BYTES) { - // 寻找FF标记 - if (buffer[offset] === 0xFF && buffer[offset + 1]! >= 0xC0 && buffer[offset + 1]! <= 0xCF) { - const marker = buffer[offset + 1]; - if (!marker) { - break; - } - // 跳过非SOF标记 - if (this.isNonSOFMarker(marker)) { - offset += 2; - continue; - } - - // 处理SOF标记 (包含尺寸信息) - if (this.isSOFMarker(marker)) { - // 确保缓冲区中有足够数据读取尺寸 - if (offset + 9 < bufferSize) { - // 解析尺寸: FF XX YY YY PP HH HH WW WW ... - // XX = 标记, YY YY = 段长度, PP = 精度, HH HH = 高, WW WW = 宽 - const height = buffer.readUInt16BE(offset + 5); - const width = buffer.readUInt16BE(offset + 7); - - found = true; - stream.destroy(); - resolve({ width, height }); - return; - } else { - // 如果缓冲区内数据不够,保留当前位置等待更多数据 - break; - } - } - } - - offset++; - } - - // 缓冲区管理: 如果处理了许多数据但没找到标记, - // 保留最后N字节用于跨块匹配,丢弃之前的数据 - if (offset > BUFFER_SIZE) { - const KEEP_BYTES = 20; // 保留足够数据以处理跨块边界的情况 - if (offset > KEEP_BYTES) { - buffer = buffer.subarray(offset - KEEP_BYTES); - offset = KEEP_BYTES; - } - } - }); - - // 处理流结束 - stream.on('end', () => { - if (!found) { - resolve(undefined); - } - }); - }); - } + // 处理流结束 + stream.on('end', () => { + if (!found) { + resolve(undefined); + } + }); + }); + } } // BMP解析器 class BmpParser implements ImageParser { - readonly type = ImageType.BMP; - // BMP 魔术头:42 4D (BM) - private readonly BMP_SIGNATURE = [0x42, 0x4D]; + readonly type = ImageType.BMP; + // BMP 魔术头:42 4D (BM) + private readonly BMP_SIGNATURE = [0x42, 0x4D]; - canParse(buffer: Buffer): boolean { - return matchMagic(buffer, this.BMP_SIGNATURE); - } + canParse (buffer: Buffer): boolean { + return matchMagic(buffer, this.BMP_SIGNATURE); + } - async parseSize(stream: ReadStream): Promise { - return new Promise((resolve, reject) => { - stream.once('error', reject); - stream.once('readable', () => { - const buf = stream.read(26) as Buffer; - if (!buf || buf.length < 26) { - return resolve(undefined); - } - if (this.canParse(buf)) { - const width = buf.readUInt32LE(18); - const height = buf.readUInt32LE(22); - resolve({ width, height }); - } else { - resolve(undefined); - } - }); - }); - } + async parseSize (stream: ReadStream): Promise { + return new Promise((resolve, reject) => { + stream.once('error', reject); + stream.once('readable', () => { + const buf = stream.read(26) as Buffer; + if (!buf || buf.length < 26) { + return resolve(undefined); + } + if (this.canParse(buf)) { + const width = buf.readUInt32LE(18); + const height = buf.readUInt32LE(22); + resolve({ width, height }); + } else { + resolve(undefined); + } + }); + }); + } } // GIF解析器 class GifParser implements ImageParser { - readonly type = ImageType.GIF; - // GIF87a 魔术头:47 49 46 38 37 61 - private readonly GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]; - // GIF89a 魔术头:47 49 46 38 39 61 - private readonly GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]; + readonly type = ImageType.GIF; + // GIF87a 魔术头:47 49 46 38 37 61 + private readonly GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]; + // GIF89a 魔术头:47 49 46 38 39 61 + private readonly GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]; - canParse(buffer: Buffer): boolean { - return ( - matchMagic(buffer, this.GIF87A_SIGNATURE) || + canParse (buffer: Buffer): boolean { + return ( + matchMagic(buffer, this.GIF87A_SIGNATURE) || matchMagic(buffer, this.GIF89A_SIGNATURE) - ); - } + ); + } - async parseSize(stream: ReadStream): Promise { - return new Promise((resolve, reject) => { - stream.once('error', reject); - stream.once('readable', () => { - const buf = stream.read(10) as Buffer; - if (!buf || buf.length < 10) { - return resolve(undefined); - } - if (this.canParse(buf)) { - const width = buf.readUInt16LE(6); - const height = buf.readUInt16LE(8); - resolve({ width, height }); - } else { - resolve(undefined); - } - }); - }); - } + async parseSize (stream: ReadStream): Promise { + return new Promise((resolve, reject) => { + stream.once('error', reject); + stream.once('readable', () => { + const buf = stream.read(10) as Buffer; + if (!buf || buf.length < 10) { + return resolve(undefined); + } + if (this.canParse(buf)) { + const width = buf.readUInt16LE(6); + const height = buf.readUInt16LE(8); + resolve({ width, height }); + } else { + resolve(undefined); + } + }); + }); + } } // WEBP解析器 - 完整支持VP8, VP8L, VP8X格式 class WebpParser implements ImageParser { - readonly type = ImageType.WEBP; - // WEBP RIFF 头:52 49 46 46 (RIFF) - private readonly RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46]; - // WEBP 魔术头:57 45 42 50 (WEBP) - private readonly WEBP_SIGNATURE = [0x57, 0x45, 0x42, 0x50]; + readonly type = ImageType.WEBP; + // WEBP RIFF 头:52 49 46 46 (RIFF) + private readonly RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46]; + // WEBP 魔术头:57 45 42 50 (WEBP) + private readonly WEBP_SIGNATURE = [0x57, 0x45, 0x42, 0x50]; - // WEBP 块头 - private readonly CHUNK_VP8 = [0x56, 0x50, 0x38, 0x20]; // "VP8 " - private readonly CHUNK_VP8L = [0x56, 0x50, 0x38, 0x4C]; // "VP8L" - private readonly CHUNK_VP8X = [0x56, 0x50, 0x38, 0x58]; // "VP8X" + // WEBP 块头 + private readonly CHUNK_VP8 = [0x56, 0x50, 0x38, 0x20]; // "VP8 " + private readonly CHUNK_VP8L = [0x56, 0x50, 0x38, 0x4C]; // "VP8L" + private readonly CHUNK_VP8X = [0x56, 0x50, 0x38, 0x58]; // "VP8X" - canParse(buffer: Buffer): boolean { - return ( - buffer.length >= 12 && + canParse (buffer: Buffer): boolean { + return ( + buffer.length >= 12 && matchMagic(buffer, this.RIFF_SIGNATURE, 0) && matchMagic(buffer, this.WEBP_SIGNATURE, 8) - ); - } + ); + } - isChunkType(buffer: Buffer, offset: number, chunkType: number[]): boolean { - return buffer.length >= offset + 4 && matchMagic(buffer, chunkType, offset); - } + isChunkType (buffer: Buffer, offset: number, chunkType: number[]): boolean { + return buffer.length >= offset + 4 && matchMagic(buffer, chunkType, offset); + } - async parseSize(stream: ReadStream): Promise { - return new Promise((resolve, reject) => { - // 需要读取足够的字节来检测所有三种格式 - const MAX_HEADER_SIZE = 32; - let totalBytes = 0; - let buffer = Buffer.alloc(0); + async parseSize (stream: ReadStream): Promise { + return new Promise((resolve, reject) => { + // 需要读取足够的字节来检测所有三种格式 + const MAX_HEADER_SIZE = 32; + let totalBytes = 0; + let buffer = Buffer.alloc(0); - stream.on('error', reject); + stream.on('error', reject); - stream.on('data', (chunk: Buffer | string) => { - const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - buffer = Buffer.concat([buffer, chunkBuffer]); - totalBytes += chunk.length; + stream.on('data', (chunk: Buffer | string) => { + const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + buffer = Buffer.concat([buffer, chunkBuffer]); + totalBytes += chunk.length; - // 检查是否有足够的字节进行格式检测 - if (totalBytes >= MAX_HEADER_SIZE) { - stream.destroy(); + // 检查是否有足够的字节进行格式检测 + if (totalBytes >= MAX_HEADER_SIZE) { + stream.destroy(); - // 检查基本的WEBP签名 - if (!this.canParse(buffer)) { - return resolve(undefined); - } + // 检查基本的WEBP签名 + if (!this.canParse(buffer)) { + return resolve(undefined); + } - // 检查chunk头部,位于字节12-15 - if (this.isChunkType(buffer, 12, this.CHUNK_VP8)) { - // VP8格式 - 标准WebP - // 宽度和高度在帧头中 - const width = buffer.readUInt16LE(26) & 0x3FFF; - const height = buffer.readUInt16LE(28) & 0x3FFF; - return resolve({ width, height }); + // 检查chunk头部,位于字节12-15 + if (this.isChunkType(buffer, 12, this.CHUNK_VP8)) { + // VP8格式 - 标准WebP + // 宽度和高度在帧头中 + const width = buffer.readUInt16LE(26) & 0x3FFF; + const height = buffer.readUInt16LE(28) & 0x3FFF; + return resolve({ width, height }); + } else if (this.isChunkType(buffer, 12, this.CHUNK_VP8L)) { + // VP8L格式 - 无损WebP + // 1字节标记后是14位宽度和14位高度 + const bits = buffer.readUInt32LE(21); + const width = 1 + (bits & 0x3FFF); + const height = 1 + ((bits >> 14) & 0x3FFF); + return resolve({ width, height }); + } else if (this.isChunkType(buffer, 12, this.CHUNK_VP8X)) { + // VP8X格式 - 扩展WebP + // 24位宽度和高度(减去1) + if (!buffer[24] || !buffer[25] || !buffer[26] || !buffer[27] || !buffer[28] || !buffer[29]) { + return resolve(undefined); + } + const width = 1 + ((buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) & 0xFFFFFF); + const height = 1 + ((buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) & 0xFFFFFF); + return resolve({ width, height }); + } else { + // 未知的WebP子格式 + return resolve(undefined); + } + } + }); - } else if (this.isChunkType(buffer, 12, this.CHUNK_VP8L)) { - // VP8L格式 - 无损WebP - // 1字节标记后是14位宽度和14位高度 - const bits = buffer.readUInt32LE(21); - const width = 1 + (bits & 0x3FFF); - const height = 1 + ((bits >> 14) & 0x3FFF); - return resolve({ width, height }); - - } else if (this.isChunkType(buffer, 12, this.CHUNK_VP8X)) { - // VP8X格式 - 扩展WebP - // 24位宽度和高度(减去1) - if (!buffer[24] || !buffer[25] || !buffer[26] || !buffer[27] || !buffer[28] || !buffer[29]) { - return resolve(undefined); - } - const width = 1 + ((buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) & 0xFFFFFF); - const height = 1 + ((buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) & 0xFFFFFF); - return resolve({ width, height }); - } else { - // 未知的WebP子格式 - return resolve(undefined); - } - } - }); - - stream.on('end', () => { - // 如果没有读到足够的字节 - if (totalBytes < MAX_HEADER_SIZE) { - resolve(undefined); - } - }); - }); - } + stream.on('end', () => { + // 如果没有读到足够的字节 + if (totalBytes < MAX_HEADER_SIZE) { + resolve(undefined); + } + }); + }); + } } const parsers: ReadonlyArray = [ - new PngParser(), - new JpegParser(), - new BmpParser(), - new GifParser(), - new WebpParser(), + new PngParser(), + new JpegParser(), + new BmpParser(), + new GifParser(), + new WebpParser(), ]; -export async function detectImageType(filePath: string): Promise { - return new Promise((resolve, reject) => { - const stream = fs.createReadStream(filePath, { - highWaterMark: 64, // 优化读取buffer大小 - start: 0, - end: 63 - }); - - let buffer: Buffer | null = null; - - stream.once('error', (err) => { - stream.destroy(); - reject(err); - }); - - stream.once('readable', () => { - buffer = stream.read(64) as Buffer; - stream.destroy(); - - if (!buffer) { - return resolve(ImageType.UNKNOWN); - } - - for (const parser of parsers) { - if (parser.canParse(buffer)) { - return resolve(parser.type); - } - } - - resolve(ImageType.UNKNOWN); - }); - - stream.once('end', () => { - if (!buffer) { - resolve(ImageType.UNKNOWN); - } - }); +export async function detectImageType (filePath: string): Promise { + return new Promise((resolve, reject) => { + const stream = fs.createReadStream(filePath, { + highWaterMark: 64, // 优化读取buffer大小 + start: 0, + end: 63, }); + + let buffer: Buffer | null = null; + + stream.once('error', (err) => { + stream.destroy(); + reject(err); + }); + + stream.once('readable', () => { + buffer = stream.read(64) as Buffer; + stream.destroy(); + + if (!buffer) { + return resolve(ImageType.UNKNOWN); + } + + for (const parser of parsers) { + if (parser.canParse(buffer)) { + return resolve(parser.type); + } + } + + resolve(ImageType.UNKNOWN); + }); + + stream.once('end', () => { + if (!buffer) { + resolve(ImageType.UNKNOWN); + } + }); + }); } -export async function imageSizeFromFile(filePath: string): Promise { +export async function imageSizeFromFile (filePath: string): Promise { + try { + // 先检测类型 + const type = await detectImageType(filePath); + const parser = parsers.find(p => p.type === type); + if (!parser) { + return undefined; + } + + // 用流式方式解析尺寸 + const stream = fs.createReadStream(filePath); try { - // 先检测类型 - const type = await detectImageType(filePath); - const parser = parsers.find(p => p.type === type); - if (!parser) { - return undefined; - } - - // 用流式方式解析尺寸 - const stream = fs.createReadStream(filePath); - try { - return await parser.parseSize(stream); - } catch (err) { - console.error(`解析图片尺寸出错: ${err}`); - return undefined; - } finally { - if (!stream.destroyed) { - stream.destroy(); - } - } + return await parser.parseSize(stream); } catch (err) { - console.error(`检测图片类型出错: ${err}`); - return undefined; + console.error(`解析图片尺寸出错: ${err}`); + return undefined; + } finally { + if (!stream.destroyed) { + stream.destroy(); + } } + } catch (err) { + console.error(`检测图片类型出错: ${err}`); + return undefined; + } } -export async function imageSizeFallBack( - filePath: string, - fallback: ImageSize = { - width: 1024, - height: 1024, - } +export async function imageSizeFallBack ( + filePath: string, + fallback: ImageSize = { + width: 1024, + height: 1024, + } ): Promise { - return await imageSizeFromFile(filePath) ?? fallback; -} \ No newline at end of file + return await imageSizeFromFile(filePath) ?? fallback; +} diff --git a/src/index.ts b/src/index.ts index e6828d58..0aa07339 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -import '@/universal/napcat'; \ No newline at end of file +import '@/universal/napcat'; diff --git a/src/onebot/action/OneBotAction.ts b/src/onebot/action/OneBotAction.ts index d6fc9b36..5fdc388a 100644 --- a/src/onebot/action/OneBotAction.ts +++ b/src/onebot/action/OneBotAction.ts @@ -7,88 +7,88 @@ import { TSchema } from '@sinclair/typebox'; import { StreamPacket, StreamPacketBasic, StreamStatus } from './stream/StreamBasic'; export class OB11Response { - private static createResponse(data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return { - return { - status, - retcode, - data, - message, - wording: message, - echo, - stream: useStream ? 'stream-action' : 'normal-action' - }; - } + private static createResponse(data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return { + return { + status, + retcode, + data, + message, + wording: message, + echo, + stream: useStream ? 'stream-action' : 'normal-action', + }; + } - static res(data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return { - return this.createResponse(data, status, retcode, message, echo, useStream); - } + static res(data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return { + return this.createResponse(data, status, retcode, message, echo, useStream); + } - static ok(data: T, echo: unknown = null, useStream: boolean = false): OB11Return { - return this.createResponse(data, 'ok', 0, '', echo, useStream); - } + static ok(data: T, echo: unknown = null, useStream: boolean = false): OB11Return { + return this.createResponse(data, 'ok', 0, '', echo, useStream); + } - static error(err: string, retcode: number, echo: unknown = null, useStream: boolean = false): OB11Return { - return this.createResponse(useStream ? { type: StreamStatus.Error, data_type: 'error' } : null, 'failed', retcode, err, echo, useStream); - } + static error (err: string, retcode: number, echo: unknown = null, useStream: boolean = false): OB11Return { + return this.createResponse(useStream ? { type: StreamStatus.Error, data_type: 'error' } : null, 'failed', retcode, err, echo, useStream); + } } export abstract class OneBotRequestToolkit { - abstract send(packet: StreamPacket): Promise; + abstract send(packet: StreamPacket): Promise; } export abstract class OneBotAction { - actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown; - core: NapCatCore; - private validate?: ValidateFunction = undefined; - payloadSchema?: TSchema = undefined; - obContext: NapCatOneBot11Adapter; - useStream: boolean = false; + actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown; + core: NapCatCore; + private validate?: ValidateFunction = undefined; + payloadSchema?: TSchema = undefined; + obContext: NapCatOneBot11Adapter; + useStream: boolean = false; - constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - this.obContext = obContext; - this.core = core; + constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + this.obContext = obContext; + this.core = core; + } + + protected async check (payload: PayloadType): Promise { + if (this.payloadSchema) { + this.validate = new Ajv({ allowUnionTypes: true, useDefaults: true, coerceTypes: true }).compile(this.payloadSchema); } - - protected async check(payload: PayloadType): Promise { - if (this.payloadSchema) { - this.validate = new Ajv({ allowUnionTypes: true, useDefaults: true, coerceTypes: true }).compile(this.payloadSchema); - } - if (this.validate && !this.validate(payload)) { - const errors = this.validate.errors as ErrorObject[]; - const errorMessages = errors.map(e => `Key: ${e.instancePath.split('/').slice(1).join('.')}, Message: ${e.message}`); - return { - valid: false, - message: errorMessages.join('\n') ?? '未知错误', - }; - } - return { valid: true }; + if (this.validate && !this.validate(payload)) { + const errors = this.validate.errors as ErrorObject[]; + const errorMessages = errors.map(e => `Key: ${e.instancePath.split('/').slice(1).join('.')}, Message: ${e.message}`); + return { + valid: false, + message: errorMessages.join('\n') ?? '未知错误', + }; } + return { valid: true }; + } - public async handle(payload: PayloadType, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit = { send: async () => { } }, echo?: string): Promise> { - const result = await this.check(payload); - if (!result.valid) { - return OB11Response.error(result.message, 400); - } - try { - const resData = await this._handle(payload, adaptername, config, req); - return OB11Response.ok(resData, echo, this.useStream); - } catch (e: unknown) { - this.core.context.logger.logError('发生错误', e); - return OB11Response.error((e as Error).message.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200, echo, this.useStream); - } + public async handle (payload: PayloadType, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit = { send: async () => { } }, echo?: string): Promise> { + const result = await this.check(payload); + if (!result.valid) { + return OB11Response.error(result.message, 400); } - - public async websocketHandle(payload: PayloadType, echo: unknown, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit = { send: async () => { } }): Promise> { - const result = await this.check(payload); - if (!result.valid) { - return OB11Response.error(result.message, 1400, echo, this.useStream); - } - try { - const resData = await this._handle(payload, adaptername, config, req); - return OB11Response.ok(resData, echo, this.useStream); - } catch (e: unknown) { - this.core.context.logger.logError('发生错误', e); - return OB11Response.error(((e as Error).message.toString() || (e as Error).stack?.toString()) ?? 'Error', 1200, echo, this.useStream); - } + try { + const resData = await this._handle(payload, adaptername, config, req); + return OB11Response.ok(resData, echo, this.useStream); + } catch (e: unknown) { + this.core.context.logger.logError('发生错误', e); + return OB11Response.error((e as Error).message.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200, echo, this.useStream); } + } - abstract _handle(payload: PayloadType, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise; + public async websocketHandle (payload: PayloadType, echo: unknown, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit = { send: async () => { } }): Promise> { + const result = await this.check(payload); + if (!result.valid) { + return OB11Response.error(result.message, 1400, echo, this.useStream); + } + try { + const resData = await this._handle(payload, adaptername, config, req); + return OB11Response.ok(resData, echo, this.useStream); + } catch (e: unknown) { + this.core.context.logger.logError('发生错误', e); + return OB11Response.error(((e as Error).message.toString() || (e as Error).stack?.toString()) ?? 'Error', 1200, echo, this.useStream); + } + } + + abstract _handle (payload: PayloadType, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise; } diff --git a/src/onebot/action/extends/BotExit.ts b/src/onebot/action/extends/BotExit.ts index 60d98023..08e3f098 100644 --- a/src/onebot/action/extends/BotExit.ts +++ b/src/onebot/action/extends/BotExit.ts @@ -2,9 +2,9 @@ import { ActionName } from '@/onebot/action/router'; import { OneBotAction } from '../OneBotAction'; export class BotExit extends OneBotAction { - override actionName = ActionName.Exit; + override actionName = ActionName.Exit; - async _handle() { - process.exit(0); - } + async _handle () { + process.exit(0); + } } diff --git a/src/onebot/action/extends/ClickInlineKeyboardButton.ts b/src/onebot/action/extends/ClickInlineKeyboardButton.ts index f27acfb2..4566558b 100644 --- a/src/onebot/action/extends/ClickInlineKeyboardButton.ts +++ b/src/onebot/action/extends/ClickInlineKeyboardButton.ts @@ -3,28 +3,28 @@ import { OneBotAction } from '../OneBotAction'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - bot_appid: Type.String(), - button_id: Type.String({ default: '' }), - callback_data: Type.String({ default: '' }), - msg_seq: Type.String({ default: '10086' }), + group_id: Type.Union([Type.Number(), Type.String()]), + bot_appid: Type.String(), + button_id: Type.String({ default: '' }), + callback_data: Type.String({ default: '' }), + msg_seq: Type.String({ default: '10086' }), }); type Payload = Static; export class ClickInlineKeyboardButton extends OneBotAction { - override actionName = ActionName.ClickInlineKeyboardButton; - override payloadSchema = SchemaData; + override actionName = ActionName.ClickInlineKeyboardButton; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.MsgApi.clickInlineKeyboardButton({ - buttonId: payload.button_id, - peerId: payload.group_id.toString(), - botAppid: payload.bot_appid, - msgSeq: payload.msg_seq, - callback_data: payload.callback_data, - dmFlag: 0, - chatType: 2 - }) - } + async _handle (payload: Payload) { + return await this.core.apis.MsgApi.clickInlineKeyboardButton({ + buttonId: payload.button_id, + peerId: payload.group_id.toString(), + botAppid: payload.bot_appid, + msgSeq: payload.msg_seq, + callback_data: payload.callback_data, + dmFlag: 0, + chatType: 2, + }); + } } diff --git a/src/onebot/action/extends/CreateCollection.ts b/src/onebot/action/extends/CreateCollection.ts index 3f9c841f..a3daebfb 100644 --- a/src/onebot/action/extends/CreateCollection.ts +++ b/src/onebot/action/extends/CreateCollection.ts @@ -3,22 +3,22 @@ import { ActionName } from '@/onebot/action/router'; import { Type, Static } from '@sinclair/typebox'; const SchemaData = Type.Object({ - rawData: Type.String(), - brief: Type.String(), + rawData: Type.String(), + brief: Type.String(), }); type Payload = Static; export class CreateCollection extends OneBotAction { - override actionName = ActionName.CreateCollection; - override payloadSchema = SchemaData; + override actionName = ActionName.CreateCollection; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.CollectionApi.createCollection( - this.core.selfInfo.uin, - this.core.selfInfo.uid, - this.core.selfInfo.nick, - payload.brief, payload.rawData, - ); - } -} \ No newline at end of file + async _handle (payload: Payload) { + return await this.core.apis.CollectionApi.createCollection( + this.core.selfInfo.uin, + this.core.selfInfo.uid, + this.core.selfInfo.nick, + payload.brief, payload.rawData + ); + } +} diff --git a/src/onebot/action/extends/DelGroupAlbumMedia.ts b/src/onebot/action/extends/DelGroupAlbumMedia.ts index 44a0ef80..45068e93 100644 --- a/src/onebot/action/extends/DelGroupAlbumMedia.ts +++ b/src/onebot/action/extends/DelGroupAlbumMedia.ts @@ -3,22 +3,22 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - album_id: Type.String(), - lloc: Type.String() + group_id: Type.String(), + album_id: Type.String(), + lloc: Type.String(), }); type Payload = Static; export class DelGroupAlbumMedia extends OneBotAction { - override actionName = ActionName.DelGroupAlbumMedia; - override payloadSchema = SchemaData; + override actionName = ActionName.DelGroupAlbumMedia; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.WebApi.deleteAlbumMediaByNTQQ( - payload.group_id, - payload.album_id, - payload.lloc - ); - } + async _handle (payload: Payload) { + return await this.core.apis.WebApi.deleteAlbumMediaByNTQQ( + payload.group_id, + payload.album_id, + payload.lloc + ); + } } diff --git a/src/onebot/action/extends/DoGroupAlbumComment.ts b/src/onebot/action/extends/DoGroupAlbumComment.ts index 9153345e..2ba2cdb8 100644 --- a/src/onebot/action/extends/DoGroupAlbumComment.ts +++ b/src/onebot/action/extends/DoGroupAlbumComment.ts @@ -3,24 +3,24 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - album_id: Type.String(), - lloc: Type.String(), - content: Type.String(), + group_id: Type.String(), + album_id: Type.String(), + lloc: Type.String(), + content: Type.String(), }); type Payload = Static; export class DoGroupAlbumComment extends OneBotAction { - override actionName = ActionName.DoGroupAlbumComment; - override payloadSchema = SchemaData; + override actionName = ActionName.DoGroupAlbumComment; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.WebApi.doAlbumMediaPlainCommentByNTQQ( - payload.group_id, - payload.album_id, - payload.lloc, - payload.content - ); - } + async _handle (payload: Payload) { + return await this.core.apis.WebApi.doAlbumMediaPlainCommentByNTQQ( + payload.group_id, + payload.album_id, + payload.lloc, + payload.content + ); + } } diff --git a/src/onebot/action/extends/FetchCustomFace.ts b/src/onebot/action/extends/FetchCustomFace.ts index f2850097..e2d87911 100644 --- a/src/onebot/action/extends/FetchCustomFace.ts +++ b/src/onebot/action/extends/FetchCustomFace.ts @@ -3,17 +3,17 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; const SchemaData = Type.Object({ - count: Type.Union([Type.Number(), Type.String()], { default: 48 }), + count: Type.Union([Type.Number(), Type.String()], { default: 48 }), }); type Payload = Static; export class FetchCustomFace extends OneBotAction { - override actionName = ActionName.FetchCustomFace; - override payloadSchema = SchemaData; + override actionName = ActionName.FetchCustomFace; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count); - return ret.emojiInfoList.map(e => e.url); - } -} \ No newline at end of file + async _handle (payload: Payload) { + const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count); + return ret.emojiInfoList.map(e => e.url); + } +} diff --git a/src/onebot/action/extends/FetchEmojiLike.ts b/src/onebot/action/extends/FetchEmojiLike.ts index 39bab1a7..549e1c23 100644 --- a/src/onebot/action/extends/FetchEmojiLike.ts +++ b/src/onebot/action/extends/FetchEmojiLike.ts @@ -5,25 +5,25 @@ import { MessageUnique } from '@/common/message-unique'; import { type NTQQMsgApi } from '@/core/apis'; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), - emojiId: Type.Union([Type.Number(), Type.String()]), - emojiType: Type.Union([Type.Number(), Type.String()]), - count: Type.Union([Type.Number(), Type.String()], { default: 20 }), + message_id: Type.Union([Type.Number(), Type.String()]), + emojiId: Type.Union([Type.Number(), Type.String()]), + emojiType: Type.Union([Type.Number(), Type.String()]), + count: Type.Union([Type.Number(), Type.String()], { default: 20 }), }); type Payload = Static; export class FetchEmojiLike extends OneBotAction>> { - override actionName = ActionName.FetchEmojiLike; - override payloadSchema = SchemaData; + override actionName = ActionName.FetchEmojiLike; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); - if (!msgIdPeer) throw new Error('消息不存在'); - const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0]; - if (!msg) throw new Error('消息不存在'); - return await this.core.apis.MsgApi.getMsgEmojiLikesList( - msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), +payload.count - ); - } + async _handle (payload: Payload) { + const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); + if (!msgIdPeer) throw new Error('消息不存在'); + const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0]; + if (!msg) throw new Error('消息不存在'); + return await this.core.apis.MsgApi.getMsgEmojiLikesList( + msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), +payload.count + ); + } } diff --git a/src/onebot/action/extends/GetAiCharacters.ts b/src/onebot/action/extends/GetAiCharacters.ts index 4ddc26e4..0b19fa93 100644 --- a/src/onebot/action/extends/GetAiCharacters.ts +++ b/src/onebot/action/extends/GetAiCharacters.ts @@ -4,34 +4,34 @@ import { AIVoiceChatType } from '@/core/packet/entities/aiChat'; import { Type, Static } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - chat_type: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }), + group_id: Type.Union([Type.Number(), Type.String()]), + chat_type: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }), }); type Payload = Static; interface GetAiCharactersResponse { - type: string; - characters: { - character_id: string; - character_name: string; - preview_url: string; - }[]; + type: string; + characters: { + character_id: string; + character_name: string; + preview_url: string; + }[]; } export class GetAiCharacters extends GetPacketStatusDepends { - override actionName = ActionName.GetAiCharacters; - override payloadSchema = SchemaData; + override actionName = ActionName.GetAiCharacters; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +payload.chat_type as AIVoiceChatType); - return rawList?.map((item) => ({ - type: item.category, - characters: item.voices.map((voice) => ({ - character_id: voice.voiceId, - character_name: voice.voiceDisplayName, - preview_url: voice.voiceExampleUrl, - })), - })) ?? []; - } + async _handle (payload: Payload) { + const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +payload.chat_type as AIVoiceChatType); + return rawList?.map((item) => ({ + type: item.category, + characters: item.voices.map((voice) => ({ + character_id: voice.voiceId, + character_name: voice.voiceDisplayName, + preview_url: voice.voiceExampleUrl, + })), + })) ?? []; + } } diff --git a/src/onebot/action/extends/GetClientkey.ts b/src/onebot/action/extends/GetClientkey.ts index dfd774da..34e49463 100644 --- a/src/onebot/action/extends/GetClientkey.ts +++ b/src/onebot/action/extends/GetClientkey.ts @@ -2,13 +2,13 @@ import { ActionName } from '@/onebot/action/router'; import { OneBotAction } from '../OneBotAction'; interface GetClientkeyResponse { - clientkey?: string; + clientkey?: string; } export class GetClientkey extends OneBotAction { - override actionName = ActionName.GetClientkey; + override actionName = ActionName.GetClientkey; - async _handle() { - return { clientkey: (await this.core.apis.UserApi.forceFetchClientKey()).clientKey }; - } + async _handle () { + return { clientkey: (await this.core.apis.UserApi.forceFetchClientKey()).clientKey }; + } } diff --git a/src/onebot/action/extends/GetCollectionList.ts b/src/onebot/action/extends/GetCollectionList.ts index 2de4305f..da9feee0 100644 --- a/src/onebot/action/extends/GetCollectionList.ts +++ b/src/onebot/action/extends/GetCollectionList.ts @@ -4,17 +4,17 @@ import { ActionName } from '@/onebot/action/router'; import { Type, Static } from '@sinclair/typebox'; const SchemaData = Type.Object({ - category: Type.Union([Type.Number(), Type.String()]), - count: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }), + category: Type.Union([Type.Number(), Type.String()]), + count: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }), }); type Payload = Static; export class GetCollectionList extends OneBotAction>> { - override actionName = ActionName.GetCollectionList; - override payloadSchema = SchemaData; + override actionName = ActionName.GetCollectionList; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count); - } + async _handle (payload: Payload) { + return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count); + } } diff --git a/src/onebot/action/extends/GetFriendWithCategory.ts b/src/onebot/action/extends/GetFriendWithCategory.ts index 4251b475..2eb3688a 100644 --- a/src/onebot/action/extends/GetFriendWithCategory.ts +++ b/src/onebot/action/extends/GetFriendWithCategory.ts @@ -3,12 +3,12 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; export class GetFriendWithCategory extends OneBotAction { - override actionName = ActionName.GetFriendsWithCategory; + override actionName = ActionName.GetFriendsWithCategory; - async _handle() { - return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({ - ...category, - buddyList: OB11Construct.friends(category.buddyList), - })); - } + async _handle () { + return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({ + ...category, + buddyList: OB11Construct.friends(category.buddyList), + })); + } } diff --git a/src/onebot/action/extends/GetGroupAddRequest.ts b/src/onebot/action/extends/GetGroupAddRequest.ts index 164bc1fd..48681b8a 100644 --- a/src/onebot/action/extends/GetGroupAddRequest.ts +++ b/src/onebot/action/extends/GetGroupAddRequest.ts @@ -4,34 +4,34 @@ import { ActionName } from '@/onebot/action/router'; import { Notify } from '@/onebot/types'; export default class GetGroupAddRequest extends OneBotAction { - override actionName = ActionName.GetGroupIgnoreAddRequest; + override actionName = ActionName.GetGroupIgnoreAddRequest; - async _handle(): Promise { - const NTQQUserApi = this.core.apis.UserApi; - const NTQQGroupApi = this.core.apis.GroupApi; - const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10); - const retData: Notify[] = []; + async _handle (): Promise { + const NTQQUserApi = this.core.apis.UserApi; + const NTQQGroupApi = this.core.apis.GroupApi; + const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10); + const retData: Notify[] = []; - const notifyPromises = ignoredNotifies - .filter(notify => notify.type === 7) - .map(async SSNotify => { - const invitorUin = SSNotify.user1?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user1.uid) : 0; - const actorUin = SSNotify.user2?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user2.uid) : 0; - retData.push({ - request_id: +SSNotify.seq, - invitor_uin: invitorUin, - invitor_nick: SSNotify.user1?.nickName, - group_id: +SSNotify.group?.groupCode, - message: SSNotify?.postscript, - group_name: SSNotify.group?.groupName, - checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE, - actor: actorUin, - requester_nick: SSNotify.user1?.nickName, - }); - }); + const notifyPromises = ignoredNotifies + .filter(notify => notify.type === 7) + .map(async SSNotify => { + const invitorUin = SSNotify.user1?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user1.uid) : 0; + const actorUin = SSNotify.user2?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user2.uid) : 0; + retData.push({ + request_id: +SSNotify.seq, + invitor_uin: invitorUin, + invitor_nick: SSNotify.user1?.nickName, + group_id: +SSNotify.group?.groupCode, + message: SSNotify?.postscript, + group_name: SSNotify.group?.groupName, + checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE, + actor: actorUin, + requester_nick: SSNotify.user1?.nickName, + }); + }); - await Promise.all(notifyPromises); + await Promise.all(notifyPromises); - return retData; - } -} \ No newline at end of file + return retData; + } +} diff --git a/src/onebot/action/extends/GetGroupAlbumMediaList.ts b/src/onebot/action/extends/GetGroupAlbumMediaList.ts index be14af05..fc4edb2a 100644 --- a/src/onebot/action/extends/GetGroupAlbumMediaList.ts +++ b/src/onebot/action/extends/GetGroupAlbumMediaList.ts @@ -3,22 +3,22 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - album_id: Type.String(), - attach_info: Type.String({ default: "" }), + group_id: Type.String(), + album_id: Type.String(), + attach_info: Type.String({ default: '' }), }); type Payload = Static; export class GetGroupAlbumMediaList extends OneBotAction { - override actionName = ActionName.GetGroupAlbumMediaList; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupAlbumMediaList; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.WebApi.getAlbumMediaListByNTQQ( - payload.group_id, - payload.album_id, - payload.attach_info - ); - } + async _handle (payload: Payload) { + return await this.core.apis.WebApi.getAlbumMediaListByNTQQ( + payload.group_id, + payload.album_id, + payload.attach_info + ); + } } diff --git a/src/onebot/action/extends/GetGroupInfoEx.ts b/src/onebot/action/extends/GetGroupInfoEx.ts index c566a105..706f3dbe 100644 --- a/src/onebot/action/extends/GetGroupInfoEx.ts +++ b/src/onebot/action/extends/GetGroupInfoEx.ts @@ -2,16 +2,16 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { Type, Static } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class GetGroupInfoEx extends OneBotAction { - override actionName = ActionName.GetGroupInfoEx; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupInfoEx; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return (await this.core.apis.GroupApi.getGroupExtFE0Info([payload.group_id.toString()])).result.groupExtInfos.get(payload.group_id.toString()); - } + async _handle (payload: Payload) { + return (await this.core.apis.GroupApi.getGroupExtFE0Info([payload.group_id.toString()])).result.groupExtInfos.get(payload.group_id.toString()); + } } diff --git a/src/onebot/action/extends/GetMiniAppArk.ts b/src/onebot/action/extends/GetMiniAppArk.ts index 6ab90445..d95eb39c 100644 --- a/src/onebot/action/extends/GetMiniAppArk.ts +++ b/src/onebot/action/extends/GetMiniAppArk.ts @@ -5,74 +5,74 @@ import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Union([ - Type.Object({ - type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')]), - title: Type.String(), - desc: Type.String(), - picUrl: Type.String(), - jumpUrl: Type.String(), - webUrl: Type.Optional(Type.String()), - rawArkData: Type.Optional(Type.Union([Type.String()])) - }), - Type.Object({ - title: Type.String(), - desc: Type.String(), - picUrl: Type.String(), - jumpUrl: Type.String(), - iconUrl: Type.String(), - webUrl: Type.Optional(Type.String()), - appId: Type.String(), - scene: Type.Union([Type.Number(), Type.String()]), - templateType: Type.Union([Type.Number(), Type.String()]), - businessType: Type.Union([Type.Number(), Type.String()]), - verType: Type.Union([Type.Number(), Type.String()]), - shareType: Type.Union([Type.Number(), Type.String()]), - versionId: Type.String(), - sdkId: Type.String(), - withShareTicket: Type.Union([Type.Number(), Type.String()]), - rawArkData: Type.Optional(Type.Union([Type.String()])) - }) + Type.Object({ + type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')]), + title: Type.String(), + desc: Type.String(), + picUrl: Type.String(), + jumpUrl: Type.String(), + webUrl: Type.Optional(Type.String()), + rawArkData: Type.Optional(Type.Union([Type.String()])), + }), + Type.Object({ + title: Type.String(), + desc: Type.String(), + picUrl: Type.String(), + jumpUrl: Type.String(), + iconUrl: Type.String(), + webUrl: Type.Optional(Type.String()), + appId: Type.String(), + scene: Type.Union([Type.Number(), Type.String()]), + templateType: Type.Union([Type.Number(), Type.String()]), + businessType: Type.Union([Type.Number(), Type.String()]), + verType: Type.Union([Type.Number(), Type.String()]), + shareType: Type.Union([Type.Number(), Type.String()]), + versionId: Type.String(), + sdkId: Type.String(), + withShareTicket: Type.Union([Type.Number(), Type.String()]), + rawArkData: Type.Optional(Type.Union([Type.String()])), + }), ]); type Payload = Static; export class GetMiniAppArk extends GetPacketStatusDepends { - override actionName = ActionName.GetMiniAppArk; - override payloadSchema = SchemaData; + override actionName = ActionName.GetMiniAppArk; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - let reqParam: MiniAppReqParams; - const customParams = { - title: payload.title, - desc: payload.desc, - picUrl: payload.picUrl, - jumpUrl: payload.jumpUrl, - webUrl: payload.webUrl, - } as MiniAppReqCustomParams; - if ('type' in payload) { - reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template); - } else { - const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload; - reqParam = MiniAppInfoHelper.generateReq( - customParams, - { - sdkId: payload.sdkId ?? MiniAppInfo.sdkId, - appId: appId, - scene: +scene, - iconUrl: iconUrl, - templateType: +templateType, - businessType: +businessType, - verType: +verType, - shareType: +shareType, - versionId: versionId, - withShareTicket: +withShareTicket, - } - ); + async _handle (payload: Payload) { + let reqParam: MiniAppReqParams; + const customParams = { + title: payload.title, + desc: payload.desc, + picUrl: payload.picUrl, + jumpUrl: payload.jumpUrl, + webUrl: payload.webUrl, + } as MiniAppReqCustomParams; + if ('type' in payload) { + reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template); + } else { + const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload; + reqParam = MiniAppInfoHelper.generateReq( + customParams, + { + sdkId: payload.sdkId ?? MiniAppInfo.sdkId, + appId, + scene: +scene, + iconUrl, + templateType: +templateType, + businessType: +businessType, + verType: +verType, + shareType: +shareType, + versionId, + withShareTicket: +withShareTicket, } - const arkData = await this.core.apis.PacketApi.pkt.operation.GetMiniAppAdaptShareInfo(reqParam); - return { - data: payload.rawArkData === 'true' ? arkData : MiniAppInfoHelper.RawToSend(arkData) - }; + ); } + const arkData = await this.core.apis.PacketApi.pkt.operation.GetMiniAppAdaptShareInfo(reqParam); + return { + data: payload.rawArkData === 'true' ? arkData : MiniAppInfoHelper.RawToSend(arkData), + }; + } } diff --git a/src/onebot/action/extends/GetProfileLike.ts b/src/onebot/action/extends/GetProfileLike.ts index bd6f39c7..e9606837 100644 --- a/src/onebot/action/extends/GetProfileLike.ts +++ b/src/onebot/action/extends/GetProfileLike.ts @@ -4,47 +4,47 @@ import { ActionName } from '@/onebot/action/router'; import { Type, Static } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), - start: Type.Union([Type.Number(), Type.String()], { default: 0 }), - count: Type.Union([Type.Number(), Type.String()], { default: 10 }) + user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + start: Type.Union([Type.Number(), Type.String()], { default: 0 }), + count: Type.Union([Type.Number(), Type.String()], { default: 10 }), }); type Payload = Static; export class GetProfileLike extends OneBotAction; - total_count: number; - last_time: number; - today_count: number; - }; - voteInfo: { - total_count: number; - new_count: number; - new_nearby_count: number; - last_visit_time: number; - userInfos: Array; - }; + uid: string; + time: string; + favoriteInfo: { + userInfos: Array; + total_count: number; + last_time: number; + today_count: number; + }; + voteInfo: { + total_count: number; + new_count: number; + new_nearby_count: number; + last_visit_time: number; + userInfos: Array; + }; }> { - override actionName = ActionName.GetProfileLike; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const isSelf = this.core.selfInfo.uin === payload.user_id || !payload.user_id; - const userUid = isSelf || !payload.user_id ? this.core.selfInfo.uid : await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - const type = isSelf ? 2 : 1; - const ret = await this.core.apis.UserApi.getProfileLike(userUid ?? this.core.selfInfo.uid, +payload.start, +payload.count, type); - const data = ret.info.userLikeInfos[0]; - if (!data) { - throw new Error('get info error'); - } - for (const item of data.voteInfo.userInfos) { - item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? ''); - } - for (const item of data.favoriteInfo.userInfos) { - item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? ''); - } - return data; + override actionName = ActionName.GetProfileLike; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const isSelf = this.core.selfInfo.uin === payload.user_id || !payload.user_id; + const userUid = isSelf || !payload.user_id ? this.core.selfInfo.uid : await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + const type = isSelf ? 2 : 1; + const ret = await this.core.apis.UserApi.getProfileLike(userUid ?? this.core.selfInfo.uid, +payload.start, +payload.count, type); + const data = ret.info.userLikeInfos[0]; + if (!data) { + throw new Error('get info error'); } -} \ No newline at end of file + for (const item of data.voteInfo.userInfos) { + item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? ''); + } + for (const item of data.favoriteInfo.userInfos) { + item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? ''); + } + return data; + } +} diff --git a/src/onebot/action/extends/GetQunAlbumList.ts b/src/onebot/action/extends/GetQunAlbumList.ts index 1a08e8f4..aa90c5fb 100644 --- a/src/onebot/action/extends/GetQunAlbumList.ts +++ b/src/onebot/action/extends/GetQunAlbumList.ts @@ -1,19 +1,18 @@ - import { NTQQWebApi } from '@/core/apis'; import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String() + group_id: Type.String(), }); type Payload = Static; -export class GetQunAlbumList extends OneBotAction>['response']['album_list']> { - override actionName = ActionName.GetQunAlbumList; - override payloadSchema = SchemaData; +export class GetQunAlbumList extends OneBotAction>['response']['album_list']> { + override actionName = ActionName.GetQunAlbumList; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return (await this.core.apis.WebApi.getAlbumListByNTQQ(payload.group_id)).response.album_list; - } + async _handle (payload: Payload) { + return (await this.core.apis.WebApi.getAlbumListByNTQQ(payload.group_id)).response.album_list; + } } diff --git a/src/onebot/action/extends/GetRkey.ts b/src/onebot/action/extends/GetRkey.ts index 9b5d08bf..15fcc1de 100644 --- a/src/onebot/action/extends/GetRkey.ts +++ b/src/onebot/action/extends/GetRkey.ts @@ -2,9 +2,9 @@ import { ActionName } from '@/onebot/action/router'; import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; export class GetRkey extends GetPacketStatusDepends> { - override actionName = ActionName.GetRkey; + override actionName = ActionName.GetRkey; - async _handle() { - return await this.core.apis.PacketApi.pkt.operation.FetchRkey(); - } + async _handle () { + return await this.core.apis.PacketApi.pkt.operation.FetchRkey(); + } } diff --git a/src/onebot/action/extends/GetRobotUinRange.ts b/src/onebot/action/extends/GetRobotUinRange.ts index e3c9c8a4..766959d4 100644 --- a/src/onebot/action/extends/GetRobotUinRange.ts +++ b/src/onebot/action/extends/GetRobotUinRange.ts @@ -2,9 +2,9 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; export class GetRobotUinRange extends OneBotAction> { - override actionName = ActionName.GetRobotUinRange; + override actionName = ActionName.GetRobotUinRange; - async _handle() { - return await this.core.apis.UserApi.getRobotUinRange(); - } + async _handle () { + return await this.core.apis.UserApi.getRobotUinRange(); + } } diff --git a/src/onebot/action/extends/GetUnidirectionalFriendList.ts b/src/onebot/action/extends/GetUnidirectionalFriendList.ts index 7963568b..8ca9f490 100644 --- a/src/onebot/action/extends/GetUnidirectionalFriendList.ts +++ b/src/onebot/action/extends/GetUnidirectionalFriendList.ts @@ -4,53 +4,53 @@ import { ActionName } from '@/onebot/action/router'; import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf'; interface Friend { - uin: number; - uid: string; - nick_name: string; - age: number; - source: string; + uin: number; + uid: string; + nick_name: string; + age: number; + source: string; } interface Block { - str_uid: string; - bytes_source: string; - uint32_sex: number; - uint32_age: number; - bytes_nick: string; - uint64_uin: number; + str_uid: string; + bytes_source: string; + uint32_sex: number; + uint32_age: number; + bytes_nick: string; + uint64_uin: number; } export class GetUnidirectionalFriendList extends OneBotAction { - override actionName = ActionName.GetUnidirectionalFriendList; + override actionName = ActionName.GetUnidirectionalFriendList; - async pack_data(data: string): Promise { - return ProtoBuf(class extends ProtoBufBase { - type = PBUint32(2, false, 0); - data = PBString(3, false, data); - }).encode(); - } + async pack_data (data: string): Promise { + return ProtoBuf(class extends ProtoBufBase { + type = PBUint32(2, false, 0); + data = PBString(3, false, data); + }).encode(); + } - async _handle(): Promise { - const self_id = this.core.selfInfo.uin; - const req_json = { - uint64_uin: self_id, - uint64_top: 0, - uint32_req_num: 99, - bytes_cookies: "" - }; - const packed_data = await this.pack_data(JSON.stringify(req_json)); - const data = Buffer.from(packed_data); - const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as PacketBuf }; - const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true); - const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data); - const block_list: Block[] = JSON.parse(block_json.data).rpt_block_list; + async _handle (): Promise { + const self_id = this.core.selfInfo.uin; + const req_json = { + uint64_uin: self_id, + uint64_top: 0, + uint32_req_num: 99, + bytes_cookies: '', + }; + const packed_data = await this.pack_data(JSON.stringify(req_json)); + const data = Buffer.from(packed_data); + const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as PacketBuf }; + const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true); + const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data); + const block_list: Block[] = JSON.parse(block_json.data).rpt_block_list; - return block_list.map((block) => ({ - uin: block.uint64_uin, - uid: block.str_uid, - nick_name: Buffer.from(block.bytes_nick, 'base64').toString(), - age: block.uint32_age, - source: Buffer.from(block.bytes_source, 'base64').toString() - })); - } -} \ No newline at end of file + return block_list.map((block) => ({ + uin: block.uint64_uin, + uid: block.str_uid, + nick_name: Buffer.from(block.bytes_nick, 'base64').toString(), + age: block.uint32_age, + source: Buffer.from(block.bytes_source, 'base64').toString(), + })); + } +} diff --git a/src/onebot/action/extends/GetUserStatus.ts b/src/onebot/action/extends/GetUserStatus.ts index 3987c81a..0c9cbead 100644 --- a/src/onebot/action/extends/GetUserStatus.ts +++ b/src/onebot/action/extends/GetUserStatus.ts @@ -3,16 +3,16 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class GetUserStatus extends GetPacketStatusDepends { - override actionName = ActionName.GetUserStatus; - override payloadSchema = SchemaData; + override actionName = ActionName.GetUserStatus; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id); - } + async _handle (payload: Payload) { + return await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id); + } } diff --git a/src/onebot/action/extends/MoveGroupFile.ts b/src/onebot/action/extends/MoveGroupFile.ts index 110551bc..0a392a07 100644 --- a/src/onebot/action/extends/MoveGroupFile.ts +++ b/src/onebot/action/extends/MoveGroupFile.ts @@ -4,30 +4,30 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file_id: Type.String(), - current_parent_directory: Type.String(), - target_parent_directory: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + file_id: Type.String(), + current_parent_directory: Type.String(), + target_parent_directory: Type.String(), }); type Payload = Static; interface MoveGroupFileResponse { - ok: boolean; + ok: boolean; } export class MoveGroupFile extends GetPacketStatusDepends { - override actionName = ActionName.MoveGroupFile; - override payloadSchema = SchemaData; + override actionName = ActionName.MoveGroupFile; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); - if (contextMsgFile?.fileUUID) { - await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.target_parent_directory); - return { - ok: true, - }; - } - throw new Error('real fileUUID not found!'); + async _handle (payload: Payload) { + const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); + if (contextMsgFile?.fileUUID) { + await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.target_parent_directory); + return { + ok: true, + }; } + throw new Error('real fileUUID not found!'); + } } diff --git a/src/onebot/action/extends/OCRImage.ts b/src/onebot/action/extends/OCRImage.ts index 3b45c64e..06dc0553 100644 --- a/src/onebot/action/extends/OCRImage.ts +++ b/src/onebot/action/extends/OCRImage.ts @@ -6,39 +6,39 @@ import { Static, Type } from '@sinclair/typebox'; import { GeneralCallResultStatus } from '@/core'; const SchemaData = Type.Object({ - image: Type.String(), + image: Type.String(), }); type Payload = Static; class OCRImageBase extends OneBotAction { - override payloadSchema = SchemaData; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const { path, success } = await uriToLocalFile(this.core.NapCatTempPath, payload.image); - if (!success) { - throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`); - } - if (path) { - try { - await checkFileExist(path, 5000); // 避免崩溃 - const ret = await this.core.apis.SystemApi.ocrImage(path); - if (!ret) { - throw new Error(`OCR ${payload.image}失败`); - } - return ret.result; - } finally { - fs.unlink(path, () => { }); - } - } - throw new Error(`OCR ${payload.image}失败, 文件可能不存在`); + async _handle (payload: Payload) { + const { path, success } = await uriToLocalFile(this.core.NapCatTempPath, payload.image); + if (!success) { + throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`); } + if (path) { + try { + await checkFileExist(path, 5000); // 避免崩溃 + const ret = await this.core.apis.SystemApi.ocrImage(path); + if (!ret) { + throw new Error(`OCR ${payload.image}失败`); + } + return ret.result; + } finally { + fs.unlink(path, () => { }); + } + } + throw new Error(`OCR ${payload.image}失败, 文件可能不存在`); + } } export class OCRImage extends OCRImageBase { - override actionName = ActionName.OCRImage; + override actionName = ActionName.OCRImage; } export class IOCRImage extends OCRImageBase { - override actionName = ActionName.IOCRImage; -} \ No newline at end of file + override actionName = ActionName.IOCRImage; +} diff --git a/src/onebot/action/extends/RenameGroupFile.ts b/src/onebot/action/extends/RenameGroupFile.ts index a567ea55..8dfde819 100644 --- a/src/onebot/action/extends/RenameGroupFile.ts +++ b/src/onebot/action/extends/RenameGroupFile.ts @@ -4,30 +4,30 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file_id: Type.String(), - current_parent_directory: Type.String(), - new_name: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + file_id: Type.String(), + current_parent_directory: Type.String(), + new_name: Type.String(), }); type Payload = Static; interface RenameGroupFileResponse { - ok: boolean; + ok: boolean; } export class RenameGroupFile extends GetPacketStatusDepends { - override actionName = ActionName.RenameGroupFile; - override payloadSchema = SchemaData; + override actionName = ActionName.RenameGroupFile; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); - if (contextMsgFile?.fileUUID) { - await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.new_name); - return { - ok: true, - }; - } - throw new Error('real fileUUID not found!'); + async _handle (payload: Payload) { + const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); + if (contextMsgFile?.fileUUID) { + await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.new_name); + return { + ok: true, + }; } + throw new Error('real fileUUID not found!'); + } } diff --git a/src/onebot/action/extends/SendPacket.ts b/src/onebot/action/extends/SendPacket.ts index 2191c23a..5d51fe37 100644 --- a/src/onebot/action/extends/SendPacket.ts +++ b/src/onebot/action/extends/SendPacket.ts @@ -4,19 +4,19 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - cmd: Type.String(), - data: Type.String(), - rsp: Type.Union([Type.String(), Type.Boolean()], { default: true }), + cmd: Type.String(), + data: Type.String(), + rsp: Type.Union([Type.String(), Type.Boolean()], { default: true }), }); type Payload = Static; export class SendPacket extends GetPacketStatusDepends { - override payloadSchema = SchemaData; - override actionName = ActionName.SendPacket; - async _handle(payload: Payload) { - const rsp = typeof payload.rsp === 'boolean' ? payload.rsp : payload.rsp === 'true'; - const data = await this.core.apis.PacketApi.pkt.operation.sendPacket({ cmd: payload.cmd, data: Buffer.from(payload.data, 'hex') as PacketBuf }, rsp); - return typeof data === 'object' ? data.toString('hex') : undefined; - } + override payloadSchema = SchemaData; + override actionName = ActionName.SendPacket; + async _handle (payload: Payload) { + const rsp = typeof payload.rsp === 'boolean' ? payload.rsp : payload.rsp === 'true'; + const data = await this.core.apis.PacketApi.pkt.operation.sendPacket({ cmd: payload.cmd, data: Buffer.from(payload.data, 'hex') as PacketBuf }, rsp); + return typeof data === 'object' ? data.toString('hex') : undefined; + } } diff --git a/src/onebot/action/extends/SetDiyOnlineStatus.ts b/src/onebot/action/extends/SetDiyOnlineStatus.ts index 82e903ca..43ba8269 100644 --- a/src/onebot/action/extends/SetDiyOnlineStatus.ts +++ b/src/onebot/action/extends/SetDiyOnlineStatus.ts @@ -3,26 +3,26 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - face_id: Type.Union([Type.Number(), Type.String()]),// 参考 face_config.json 的 QSid - face_type: Type.Union([Type.Number(), Type.String()], { default: '1' }), - wording: Type.String({ default: ' ' }), + face_id: Type.Union([Type.Number(), Type.String()]), // 参考 face_config.json 的 QSid + face_type: Type.Union([Type.Number(), Type.String()], { default: '1' }), + wording: Type.String({ default: ' ' }), }); type Payload = Static; export class SetDiyOnlineStatus extends OneBotAction { - override actionName = ActionName.SetDiyOnlineStatus; - override payloadSchema = SchemaData; + override actionName = ActionName.SetDiyOnlineStatus; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const ret = await this.core.apis.UserApi.setDiySelfOnlineStatus( - payload.face_id.toString(), - payload.wording, - payload.face_type.toString(), - ); - if (ret.result !== 0) { - throw new Error('设置在线状态失败'); - } - return ret.errMsg; + async _handle (payload: Payload) { + const ret = await this.core.apis.UserApi.setDiySelfOnlineStatus( + payload.face_id.toString(), + payload.wording, + payload.face_type.toString() + ); + if (ret.result !== 0) { + throw new Error('设置在线状态失败'); } + return ret.errMsg; + } } diff --git a/src/onebot/action/extends/SetGroupAddOption.ts b/src/onebot/action/extends/SetGroupAddOption.ts index 7d270351..079416ec 100644 --- a/src/onebot/action/extends/SetGroupAddOption.ts +++ b/src/onebot/action/extends/SetGroupAddOption.ts @@ -3,26 +3,26 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - add_type: Type.Number(), - group_question: Type.Optional(Type.String()), - group_answer: Type.Optional(Type.String()), + group_id: Type.String(), + add_type: Type.Number(), + group_question: Type.Optional(Type.String()), + group_answer: Type.Optional(Type.String()), }); type Payload = Static; export default class SetGroupAddOption extends OneBotAction { - override actionName = ActionName.SetGroupAddOption; - override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - let ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, { - addOption: payload.add_type, - groupQuestion: payload.group_question, - groupAnswer: payload.group_answer, - }); - if (ret.result != 0) { - throw new Error(`设置群添加选项失败, ${ret.result}:${ret.errMsg}`); - } - return null; + override actionName = ActionName.SetGroupAddOption; + override payloadSchema = SchemaData; + async _handle (payload: Payload): Promise { + const ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, { + addOption: payload.add_type, + groupQuestion: payload.group_question, + groupAnswer: payload.group_answer, + }); + if (ret.result != 0) { + throw new Error(`设置群添加选项失败, ${ret.result}:${ret.errMsg}`); } + return null; + } } diff --git a/src/onebot/action/extends/SetGroupAlbumMediaLike.ts b/src/onebot/action/extends/SetGroupAlbumMediaLike.ts index f5c12aff..62763422 100644 --- a/src/onebot/action/extends/SetGroupAlbumMediaLike.ts +++ b/src/onebot/action/extends/SetGroupAlbumMediaLike.ts @@ -3,25 +3,25 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - album_id: Type.String(), - lloc: Type.String(), - id: Type.String(),//421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1 - set: Type.Boolean({default: true})//true=点赞 false=取消点赞 未实现 + group_id: Type.String(), + album_id: Type.String(), + lloc: Type.String(), + id: Type.String(), // 421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1 + set: Type.Boolean({ default: true }), // true=点赞 false=取消点赞 未实现 }); type Payload = Static; export class SetGroupAlbumMediaLike extends OneBotAction { - override actionName = ActionName.SetGroupAlbumMediaLike; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupAlbumMediaLike; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ( - payload.group_id, - payload.album_id, - payload.lloc, - payload.id - ); - } + async _handle (payload: Payload) { + return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ( + payload.group_id, + payload.album_id, + payload.lloc, + payload.id + ); + } } diff --git a/src/onebot/action/extends/SetGroupKickMembers.ts b/src/onebot/action/extends/SetGroupKickMembers.ts index 359a8cda..8ceaa15b 100644 --- a/src/onebot/action/extends/SetGroupKickMembers.ts +++ b/src/onebot/action/extends/SetGroupKickMembers.ts @@ -3,21 +3,21 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - user_id: Type.Array(Type.String()), - reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + group_id: Type.String(), + user_id: Type.Array(Type.String()), + reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export default class SetGroupKickMembers extends OneBotAction { - override actionName = ActionName.SetGroupKickMembers; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupKickMembers; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const rejectReq = payload.reject_add_request?.toString() == 'true'; - const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin))); - await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq); - return null; - } -} \ No newline at end of file + async _handle (payload: Payload): Promise { + const rejectReq = payload.reject_add_request?.toString() == 'true'; + const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin))); + await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq); + return null; + } +} diff --git a/src/onebot/action/extends/SetGroupRemark.ts b/src/onebot/action/extends/SetGroupRemark.ts index a8dbf5a9..56f18a9a 100644 --- a/src/onebot/action/extends/SetGroupRemark.ts +++ b/src/onebot/action/extends/SetGroupRemark.ts @@ -3,20 +3,20 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - remark: Type.String(), + group_id: Type.String(), + remark: Type.String(), }); type Payload = Static; export default class SetGroupRemark extends OneBotAction { - override actionName = ActionName.SetGroupRemark; - override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - let ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark); - if (ret.result != 0) { - throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`); - } - return null; + override actionName = ActionName.SetGroupRemark; + override payloadSchema = SchemaData; + async _handle (payload: Payload): Promise { + const ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark); + if (ret.result != 0) { + throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`); } + return null; + } } diff --git a/src/onebot/action/extends/SetGroupRobotAddOption.ts b/src/onebot/action/extends/SetGroupRobotAddOption.ts index c91abcc6..a81e61bc 100644 --- a/src/onebot/action/extends/SetGroupRobotAddOption.ts +++ b/src/onebot/action/extends/SetGroupRobotAddOption.ts @@ -3,25 +3,25 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - robot_member_switch: Type.Optional(Type.Number()), - robot_member_examine: Type.Optional(Type.Number()), + group_id: Type.String(), + robot_member_switch: Type.Optional(Type.Number()), + robot_member_examine: Type.Optional(Type.Number()), }); type Payload = Static; export default class SetGroupRobotAddOption extends OneBotAction { - override actionName = ActionName.SetGroupRobotAddOption; - override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - let ret = await this.core.apis.GroupApi.setGroupRobotAddOption( - payload.group_id, - payload.robot_member_switch, - payload.robot_member_examine, - ); - if (ret.result != 0) { - throw new Error(`设置群机器人添加选项失败, ${ret.result}:${ret.errMsg}`); - } - return null; + override actionName = ActionName.SetGroupRobotAddOption; + override payloadSchema = SchemaData; + async _handle (payload: Payload): Promise { + const ret = await this.core.apis.GroupApi.setGroupRobotAddOption( + payload.group_id, + payload.robot_member_switch, + payload.robot_member_examine + ); + if (ret.result != 0) { + throw new Error(`设置群机器人添加选项失败, ${ret.result}:${ret.errMsg}`); } + return null; + } } diff --git a/src/onebot/action/extends/SetGroupSearch.ts b/src/onebot/action/extends/SetGroupSearch.ts index e5b20c7b..5b861235 100644 --- a/src/onebot/action/extends/SetGroupSearch.ts +++ b/src/onebot/action/extends/SetGroupSearch.ts @@ -3,24 +3,24 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.String(), - no_code_finger_open: Type.Optional(Type.Number()), - no_finger_open: Type.Optional(Type.Number()), + group_id: Type.String(), + no_code_finger_open: Type.Optional(Type.Number()), + no_finger_open: Type.Optional(Type.Number()), }); type Payload = Static; export default class SetGroupSearch extends OneBotAction { - override actionName = ActionName.SetGroupSearch; - override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - let ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, { - noCodeFingerOpenFlag: payload.no_code_finger_open, - noFingerOpenFlag: payload.no_finger_open, - }); - if (ret.result != 0) { - throw new Error(`设置群搜索失败, ${ret.result}:${ret.errMsg}`); - } - return null; + override actionName = ActionName.SetGroupSearch; + override payloadSchema = SchemaData; + async _handle (payload: Payload): Promise { + const ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, { + noCodeFingerOpenFlag: payload.no_code_finger_open, + noFingerOpenFlag: payload.no_finger_open, + }); + if (ret.result != 0) { + throw new Error(`设置群搜索失败, ${ret.result}:${ret.errMsg}`); } + return null; + } } diff --git a/src/onebot/action/extends/SetGroupSign.ts b/src/onebot/action/extends/SetGroupSign.ts index 716aca22..0c36b99f 100644 --- a/src/onebot/action/extends/SetGroupSign.ts +++ b/src/onebot/action/extends/SetGroupSign.ts @@ -3,23 +3,23 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; class SetGroupSignBase extends GetPacketStatusDepends { - override payloadSchema = SchemaData; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id); - } + async _handle (payload: Payload) { + return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id); + } } export class SetGroupSign extends SetGroupSignBase { - override actionName = ActionName.SetGroupSign; + override actionName = ActionName.SetGroupSign; } export class SendGroupSign extends SetGroupSignBase { - override actionName = ActionName.SendGroupSign; + override actionName = ActionName.SendGroupSign; } diff --git a/src/onebot/action/extends/SetInputStatus.ts b/src/onebot/action/extends/SetInputStatus.ts index 448c00ff..4d5bad5f 100644 --- a/src/onebot/action/extends/SetInputStatus.ts +++ b/src/onebot/action/extends/SetInputStatus.ts @@ -4,22 +4,22 @@ import { ChatType } from '@/core'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.Union([Type.Number(), Type.String()]), - event_type: Type.Number(), + user_id: Type.Union([Type.Number(), Type.String()]), + event_type: Type.Number(), }); type Payload = Static; export class SetInputStatus extends OneBotAction { - override actionName = ActionName.SetInputStatus; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!uid) throw new Error('uid is empty'); - const peer = { - chatType: ChatType.KCHATTYPEC2C, - peerUid: uid, - }; - return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, payload.event_type); - } + override actionName = ActionName.SetInputStatus; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!uid) throw new Error('uid is empty'); + const peer = { + chatType: ChatType.KCHATTYPEC2C, + peerUid: uid, + }; + return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, payload.event_type); + } } diff --git a/src/onebot/action/extends/SetLongNick.ts b/src/onebot/action/extends/SetLongNick.ts index 5169bada..56c57010 100644 --- a/src/onebot/action/extends/SetLongNick.ts +++ b/src/onebot/action/extends/SetLongNick.ts @@ -3,16 +3,16 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - longNick: Type.String(), + longNick: Type.String(), }); type Payload = Static; export class SetLongNick extends OneBotAction { - override actionName = ActionName.SetLongNick; - override payloadSchema = SchemaData; + override actionName = ActionName.SetLongNick; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.UserApi.setLongNick(payload.longNick); - } + async _handle (payload: Payload) { + return await this.core.apis.UserApi.setLongNick(payload.longNick); + } } diff --git a/src/onebot/action/extends/SetOnlineStatus.ts b/src/onebot/action/extends/SetOnlineStatus.ts index 6e1782e9..3f16be4b 100644 --- a/src/onebot/action/extends/SetOnlineStatus.ts +++ b/src/onebot/action/extends/SetOnlineStatus.ts @@ -3,26 +3,26 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - status: Type.Union([Type.Number(), Type.String()]), - ext_status: Type.Union([Type.Number(), Type.String()]), - battery_status: Type.Union([Type.Number(), Type.String()]), + status: Type.Union([Type.Number(), Type.String()]), + ext_status: Type.Union([Type.Number(), Type.String()]), + battery_status: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class SetOnlineStatus extends OneBotAction { - override actionName = ActionName.SetOnlineStatus; - override payloadSchema = SchemaData; + override actionName = ActionName.SetOnlineStatus; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const ret = await this.core.apis.UserApi.setSelfOnlineStatus( - +payload.status, - +payload.ext_status, - +payload.battery_status, - ); - if (ret.result !== 0) { - throw new Error('设置在线状态失败'); - } - return null; + async _handle (payload: Payload) { + const ret = await this.core.apis.UserApi.setSelfOnlineStatus( + +payload.status, + +payload.ext_status, + +payload.battery_status + ); + if (ret.result !== 0) { + throw new Error('设置在线状态失败'); } + return null; + } } diff --git a/src/onebot/action/extends/SetQQAvatar.ts b/src/onebot/action/extends/SetQQAvatar.ts index c4e019aa..8ab8dea4 100644 --- a/src/onebot/action/extends/SetQQAvatar.ts +++ b/src/onebot/action/extends/SetQQAvatar.ts @@ -5,36 +5,36 @@ import { checkFileExist, uriToLocalFile } from '@/common/file'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - file: Type.String(), + file: Type.String(), }); type Payload = Static; export default class SetAvatar extends OneBotAction { - override actionName = ActionName.SetQQAvatar; - override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.file)); - if (!success) { - throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`); - } - if (path) { - await checkFileExist(path, 5000);// 避免崩溃 - const ret = await this.core.apis.UserApi.setQQAvatar(path); - fs.unlink(path).catch(() => { }); - if (!ret) { - throw new Error(`头像${payload.file}设置失败,api无返回`); - } - // log(`头像设置返回:${JSON.stringify(ret)}`) - if (ret.result as number == 1004022) { - throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`); - } else if (ret.result != 0) { - throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`); - } - } else { - fs.unlink(path).catch(() => { }); - throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`); - } - return null; + override actionName = ActionName.SetQQAvatar; + override payloadSchema = SchemaData; + async _handle (payload: Payload): Promise { + const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.file)); + if (!success) { + throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`); } + if (path) { + await checkFileExist(path, 5000);// 避免崩溃 + const ret = await this.core.apis.UserApi.setQQAvatar(path); + fs.unlink(path).catch(() => { }); + if (!ret) { + throw new Error(`头像${payload.file}设置失败,api无返回`); + } + // log(`头像设置返回:${JSON.stringify(ret)}`) + if (ret.result as number == 1004022) { + throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`); + } else if (ret.result != 0) { + throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`); + } + } else { + fs.unlink(path).catch(() => { }); + throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`); + } + return null; + } } diff --git a/src/onebot/action/extends/SetSpecialTitle.ts b/src/onebot/action/extends/SetSpecialTitle.ts index 7d68ff36..a2794ff2 100644 --- a/src/onebot/action/extends/SetSpecialTitle.ts +++ b/src/onebot/action/extends/SetSpecialTitle.ts @@ -3,20 +3,20 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - user_id: Type.Union([Type.Number(), Type.String()]), - special_title: Type.String({ default: '' }), + group_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), + special_title: Type.String({ default: '' }), }); type Payload = Static; export class SetSpecialTitle extends GetPacketStatusDepends { - override actionName = ActionName.SetSpecialTitle; - override payloadSchema = SchemaData; + override actionName = ActionName.SetSpecialTitle; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!uid) throw new Error('User not found'); - await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title); - } + async _handle (payload: Payload) { + const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!uid) throw new Error('User not found'); + await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title); + } } diff --git a/src/onebot/action/extends/ShareContact.ts b/src/onebot/action/extends/ShareContact.ts index 22c8654b..af654aa2 100644 --- a/src/onebot/action/extends/ShareContact.ts +++ b/src/onebot/action/extends/ShareContact.ts @@ -4,41 +4,41 @@ import { ActionName } from '@/onebot/action/router'; 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: '' }), + user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + phoneNumber: Type.String({ default: '' }), }); type Payload = Static; export class SharePeer extends OneBotAction { - override actionName = ActionName.SharePeer; - override payloadSchema = SchemaData; + 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); - } - throw new Error('group_id or user_id is required'); + 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); } + throw new Error('group_id or user_id is required'); + } } const SchemaDataGroupEx = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type PayloadGroupEx = Static; export class ShareGroupEx extends OneBotAction { - override actionName = ActionName.ShareGroupEx; - override payloadSchema = SchemaDataGroupEx; + override actionName = ActionName.ShareGroupEx; + override payloadSchema = SchemaDataGroupEx; - async _handle(payload: PayloadGroupEx) { - return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString()); - } + async _handle (payload: PayloadGroupEx) { + return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString()); + } } diff --git a/src/onebot/action/extends/TransGroupFile.ts b/src/onebot/action/extends/TransGroupFile.ts index 35b3275a..5676af12 100644 --- a/src/onebot/action/extends/TransGroupFile.ts +++ b/src/onebot/action/extends/TransGroupFile.ts @@ -4,31 +4,31 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file_id: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + file_id: Type.String(), }); type Payload = Static; interface TransGroupFileResponse { - ok: boolean; + ok: boolean; } export class TransGroupFile extends GetPacketStatusDepends { - override actionName = ActionName.TransGroupFile; - override payloadSchema = SchemaData; + override actionName = ActionName.TransGroupFile; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); - if (contextMsgFile?.fileUUID) { - const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), contextMsgFile.fileUUID); - if (result.transGroupFileResult.result.retCode === 0) { - return { - ok: true - }; - } - throw new Error(result.transGroupFileResult.result.retMsg); - } - throw new Error('real fileUUID not found!'); + async _handle (payload: Payload) { + const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); + if (contextMsgFile?.fileUUID) { + const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), contextMsgFile.fileUUID); + if (result.transGroupFileResult.result.retCode === 0) { + return { + ok: true, + }; + } + throw new Error(result.transGroupFileResult.result.retMsg); } + throw new Error('real fileUUID not found!'); + } } diff --git a/src/onebot/action/extends/TranslateEnWordToZn.ts b/src/onebot/action/extends/TranslateEnWordToZn.ts index 2edb8b83..e9ea0361 100644 --- a/src/onebot/action/extends/TranslateEnWordToZn.ts +++ b/src/onebot/action/extends/TranslateEnWordToZn.ts @@ -3,20 +3,20 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - words: Type.Array(Type.String()), + words: Type.Array(Type.String()), }); type Payload = Static; export class TranslateEnWordToZn extends OneBotAction | null> { - override actionName = ActionName.TranslateEnWordToZn; - override payloadSchema = SchemaData; + override actionName = ActionName.TranslateEnWordToZn; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const ret = await this.core.apis.SystemApi.translateEnWordToZn(payload.words); - if (ret.result !== 0) { - throw new Error('翻译失败'); - } - return ret.words; + async _handle (payload: Payload) { + const ret = await this.core.apis.SystemApi.translateEnWordToZn(payload.words); + if (ret.result !== 0) { + throw new Error('翻译失败'); } + return ret.words; + } } diff --git a/src/onebot/action/extends/UploadImageToQunAlbum.ts b/src/onebot/action/extends/UploadImageToQunAlbum.ts index fe1490eb..de3b548e 100644 --- a/src/onebot/action/extends/UploadImageToQunAlbum.ts +++ b/src/onebot/action/extends/UploadImageToQunAlbum.ts @@ -6,26 +6,26 @@ import { existsSync } from 'node:fs'; import { unlink } from 'node:fs/promises'; const SchemaData = Type.Object({ - group_id: Type.String(), - album_id: Type.String(), - album_name: Type.String(), - file: Type.String() + group_id: Type.String(), + album_id: Type.String(), + album_name: Type.String(), + file: Type.String(), }); type Payload = Static; export class UploadImageToQunAlbum extends OneBotAction { - override actionName = ActionName.UploadImageToQunAlbum; - override payloadSchema = SchemaData; + override actionName = ActionName.UploadImageToQunAlbum; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, payload.file); - try { - return await this.core.apis.WebApi.uploadImageToQunAlbum(payload.group_id, payload.album_id, payload.album_name, downloadResult.path); - } finally { - if (downloadResult.path && existsSync(downloadResult.path)) { - await unlink(downloadResult.path); - } - } + async _handle (payload: Payload) { + const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, payload.file); + try { + return await this.core.apis.WebApi.uploadImageToQunAlbum(payload.group_id, payload.album_id, payload.album_name, downloadResult.path); + } finally { + if (downloadResult.path && existsSync(downloadResult.path)) { + await unlink(downloadResult.path); + } } + } } diff --git a/src/onebot/action/file/GetFile.ts b/src/onebot/action/file/GetFile.ts index ceb904ee..6851652e 100644 --- a/src/onebot/action/file/GetFile.ts +++ b/src/onebot/action/file/GetFile.ts @@ -6,112 +6,111 @@ import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types'; import { Static, Type } from '@sinclair/typebox'; export interface GetFileResponse { - file?: string; // path - url?: string; - file_size?: string; - file_name?: string; - base64?: string; + file?: string; // path + url?: string; + file_size?: string; + file_name?: string; + base64?: string; } const GetFileBase_PayloadSchema = Type.Object({ - file: Type.Optional(Type.String()), - file_id: Type.Optional(Type.String()) + file: Type.Optional(Type.String()), + file_id: Type.Optional(Type.String()), }); - export type GetFilePayload = Static; export class GetFileBase extends OneBotAction { - override payloadSchema = GetFileBase_PayloadSchema; + override payloadSchema = GetFileBase_PayloadSchema; - async _handle(payload: GetFilePayload): Promise { - payload.file ||= payload.file_id || ''; - //接收消息标记模式 - const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file); - if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { - const { peer, msgId, elementId } = contextMsgFile; - const downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); - const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList - .find(msg => msg.msgId === msgId); - const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); - const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; - if (!mixElementInner) throw new Error('element not found'); - const fileSize = mixElementInner.fileSize?.toString() ?? ''; - const fileName = mixElementInner.fileName ?? ''; - let url = ''; - if (mixElement?.picElement && rawMessage) { - const tempData = + async _handle (payload: GetFilePayload): Promise { + payload.file ||= payload.file_id || ''; + // 接收消息标记模式 + const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file); + if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { + const { peer, msgId, elementId } = contextMsgFile; + const downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); + const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList + .find(msg => msg.msgId === msgId); + const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); + const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; + if (!mixElementInner) throw new Error('element not found'); + const fileSize = mixElementInner.fileSize?.toString() ?? ''; + const fileName = mixElementInner.fileName ?? ''; + let url = ''; + if (mixElement?.picElement && rawMessage) { + const tempData = await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: true }) as OB11MessageImage | undefined; - url = tempData?.data.url ?? ''; - } - if (mixElement?.videoElement && rawMessage) { - const tempData = + url = tempData?.data.url ?? ''; + } + if (mixElement?.videoElement && rawMessage) { + const tempData = await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: true }) as OB11MessageVideo | undefined; - url = tempData?.data.url ?? ''; - } - const res: GetFileResponse = { - file: downloadPath, - url: url !== '' ? url : downloadPath, - file_size: fileSize, - file_name: fileName, - }; + url = tempData?.data.url ?? ''; + } + const res: GetFileResponse = { + file: downloadPath, + url: url !== '' ? url : downloadPath, + file_size: fileSize, + file_name: fileName, + }; - if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) { - try { - res.base64 = await fs.readFile(downloadPath, 'base64'); - } catch (e) { - throw new Error('文件下载失败. ' + e); - } - } - return res; + if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) { + try { + res.base64 = await fs.readFile(downloadPath, 'base64'); + } catch (e) { + throw new Error('文件下载失败. ' + e); } - - //群文件模式 - const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file); - if (contextModelIdFile && contextModelIdFile.modelId) { - const { peer, modelId } = contextModelIdFile; - const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); - const res: GetFileResponse = { - file: downloadPath, - url: downloadPath, - file_size: '', - file_name: '', - }; - - if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) { - try { - res.base64 = await fs.readFile(downloadPath, 'base64'); - } catch (e) { - throw new Error('文件下载失败. ' + e); - } - } - return res; - } - - //搜索名字模式 - const searchResult = (await this.core.apis.FileApi.searchForFile([payload.file])); - if (searchResult) { - const downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, parseInt(searchResult.fileSize)); - const res: GetFileResponse = { - file: downloadPath, - url: downloadPath, - file_size: searchResult.fileSize.toString(), - file_name: searchResult.fileName, - }; - if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) { - try { - res.base64 = await fs.readFile(downloadPath, 'base64'); - } catch (e) { - throw new Error('文件下载失败. ' + e); - } - } - return res; - } - - throw new Error('file not found'); + } + return res; } + + // 群文件模式 + const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file); + if (contextModelIdFile && contextModelIdFile.modelId) { + const { peer, modelId } = contextModelIdFile; + const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); + const res: GetFileResponse = { + file: downloadPath, + url: downloadPath, + file_size: '', + file_name: '', + }; + + if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) { + try { + res.base64 = await fs.readFile(downloadPath, 'base64'); + } catch (e) { + throw new Error('文件下载失败. ' + e); + } + } + return res; + } + + // 搜索名字模式 + const searchResult = (await this.core.apis.FileApi.searchForFile([payload.file])); + if (searchResult) { + const downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, parseInt(searchResult.fileSize)); + const res: GetFileResponse = { + file: downloadPath, + url: downloadPath, + file_size: searchResult.fileSize.toString(), + file_name: searchResult.fileName, + }; + if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) { + try { + res.base64 = await fs.readFile(downloadPath, 'base64'); + } catch (e) { + throw new Error('文件下载失败. ' + e); + } + } + return res; + } + + throw new Error('file not found'); + } } export default class GetFile extends GetFileBase { - override actionName = ActionName.GetFile; + override actionName = ActionName.GetFile; } diff --git a/src/onebot/action/file/GetGroupFileUrl.ts b/src/onebot/action/file/GetGroupFileUrl.ts index 0ae7d314..ef51e3f1 100644 --- a/src/onebot/action/file/GetGroupFileUrl.ts +++ b/src/onebot/action/file/GetGroupFileUrl.ts @@ -4,27 +4,27 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file_id: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + file_id: Type.String(), }); type Payload = Static; interface GetGroupFileUrlResponse { - url?: string; + url?: string; } export class GetGroupFileUrl extends GetPacketStatusDepends { - override actionName = ActionName.GOCQHTTP_GetGroupFileUrl; - override payloadSchema = SchemaData; + override actionName = ActionName.GOCQHTTP_GetGroupFileUrl; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); - if (contextMsgFile?.fileUUID) { - return { - url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, contextMsgFile.fileUUID) - }; - } - throw new Error('real fileUUID not found!'); + async _handle (payload: Payload) { + const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id); + if (contextMsgFile?.fileUUID) { + return { + url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, contextMsgFile.fileUUID), + }; } + throw new Error('real fileUUID not found!'); + } } diff --git a/src/onebot/action/file/GetImage.ts b/src/onebot/action/file/GetImage.ts index e021138c..31cf8c09 100644 --- a/src/onebot/action/file/GetImage.ts +++ b/src/onebot/action/file/GetImage.ts @@ -1,7 +1,6 @@ import { GetFileBase } from './GetFile'; import { ActionName } from '@/onebot/action/router'; - export default class GetImage extends GetFileBase { - override actionName = ActionName.GetImage; -} \ No newline at end of file + override actionName = ActionName.GetImage; +} diff --git a/src/onebot/action/file/GetPrivateFileUrl.ts b/src/onebot/action/file/GetPrivateFileUrl.ts index f592cc08..418f2854 100644 --- a/src/onebot/action/file/GetPrivateFileUrl.ts +++ b/src/onebot/action/file/GetPrivateFileUrl.ts @@ -4,33 +4,32 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - file_id: Type.String(), + file_id: Type.String(), }); type Payload = Static; interface GetPrivateFileUrlResponse { - url?: string; + url?: string; } export class GetPrivateFileUrl extends GetPacketStatusDepends { - override actionName = ActionName.NapCat_GetPrivateFileUrl; - override payloadSchema = SchemaData; + override actionName = ActionName.NapCat_GetPrivateFileUrl; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id); + async _handle (payload: Payload) { + const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id); - if (contextMsgFile?.fileUUID && contextMsgFile.msgId) { - let msg = await this.core.apis.MsgApi.getMsgsByMsgId(contextMsgFile.peer, [contextMsgFile.msgId]); - let self_id = this.core.selfInfo.uid; - let file_hash = msg.msgList[0]?.elements.map(ele => ele.fileElement?.file10MMd5)[0]; - if (file_hash) { - return { - url: await this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(self_id, contextMsgFile.fileUUID, file_hash) - }; - } - - } - throw new Error('real fileUUID not found!'); + if (contextMsgFile?.fileUUID && contextMsgFile.msgId) { + const msg = await this.core.apis.MsgApi.getMsgsByMsgId(contextMsgFile.peer, [contextMsgFile.msgId]); + const self_id = this.core.selfInfo.uid; + const file_hash = msg.msgList[0]?.elements.map(ele => ele.fileElement?.file10MMd5)[0]; + if (file_hash) { + return { + url: await this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(self_id, contextMsgFile.fileUUID, file_hash), + }; + } } + throw new Error('real fileUUID not found!'); + } } diff --git a/src/onebot/action/file/GetRecord.ts b/src/onebot/action/file/GetRecord.ts index 4a740362..7950b1cf 100644 --- a/src/onebot/action/file/GetRecord.ts +++ b/src/onebot/action/file/GetRecord.ts @@ -7,50 +7,50 @@ import { FFmpegService } from '@/common/ffmpeg'; const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']; type Payload = { - out_format: string -} & GetFilePayload + out_format: string +} & GetFilePayload; export default class GetRecord extends GetFileBase { - override actionName = ActionName.GetRecord; + override actionName = ActionName.GetRecord; - override async _handle(payload: Payload): Promise { - const res = await super._handle(payload); - if (payload.out_format && typeof payload.out_format === 'string') { - const inputFile = res.file; - if (!inputFile) throw new Error('file not found'); - if (!out_format.includes(payload.out_format)) { - throw new Error('转换失败 out_format 字段可能格式不正确'); - } - const pcmFile = `${inputFile}.pcm`; - const outputFile = `${inputFile}.${payload.out_format}`; - try { - await fs.access(inputFile); - try { - await fs.access(outputFile); - } catch { - await this.decodeFile(inputFile, pcmFile); - await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format); - } - const base64Data = await fs.readFile(outputFile, { encoding: 'base64' }); - res.file = outputFile; - res.url = outputFile; - res.base64 = base64Data; - } catch (error) { - console.error('Error processing file:', error); - throw error; // 重新抛出错误以便调用者可以处理 - } - } - return res; - } - - private async decodeFile(inputFile: string, outputFile: string): Promise { + override async _handle (payload: Payload): Promise { + const res = await super._handle(payload); + if (payload.out_format && typeof payload.out_format === 'string') { + const inputFile = res.file; + if (!inputFile) throw new Error('file not found'); + if (!out_format.includes(payload.out_format)) { + throw new Error('转换失败 out_format 字段可能格式不正确'); + } + const pcmFile = `${inputFile}.pcm`; + const outputFile = `${inputFile}.${payload.out_format}`; + try { + await fs.access(inputFile); try { - const inputData = await fs.readFile(inputFile); - const decodedData = await decode(inputData, 24000); - await fs.writeFile(outputFile, Buffer.from(decodedData.data)); - } catch (error) { - console.error('Error decoding file:', error); - throw error; // 重新抛出错误以便调用者可以处理 + await fs.access(outputFile); + } catch { + await this.decodeFile(inputFile, pcmFile); + await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format); } + const base64Data = await fs.readFile(outputFile, { encoding: 'base64' }); + res.file = outputFile; + res.url = outputFile; + res.base64 = base64Data; + } catch (error) { + console.error('Error processing file:', error); + throw error; // 重新抛出错误以便调用者可以处理 + } } + return res; + } + + private async decodeFile (inputFile: string, outputFile: string): Promise { + try { + const inputData = await fs.readFile(inputFile); + const decodedData = await decode(inputData, 24000); + await fs.writeFile(outputFile, Buffer.from(decodedData.data)); + } catch (error) { + console.error('Error decoding file:', error); + throw error; // 重新抛出错误以便调用者可以处理 + } + } } diff --git a/src/onebot/action/go-cqhttp/CreateGroupFileFolder.ts b/src/onebot/action/go-cqhttp/CreateGroupFileFolder.ts index a3538bd9..e8e8e0d0 100644 --- a/src/onebot/action/go-cqhttp/CreateGroupFileFolder.ts +++ b/src/onebot/action/go-cqhttp/CreateGroupFileFolder.ts @@ -3,23 +3,23 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - // 兼容gocq 与name二选一 - folder_name: Type.Optional(Type.String()), - // 兼容gocq 与folder_name二选一 - name: Type.Optional(Type.String()), + group_id: Type.Union([Type.Number(), Type.String()]), + // 兼容gocq 与name二选一 + folder_name: Type.Optional(Type.String()), + // 兼容gocq 与folder_name二选一 + name: Type.Optional(Type.String()), }); type Payload = Static; -interface ResponseType{ - result:unknown; - groupItem:unknown; +interface ResponseType { + result: unknown; + groupItem: unknown; } -export class CreateGroupFileFolder extends OneBotAction { - override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const folderName = payload.folder_name || payload.name; - return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), folderName!)).resultWithGroupItem; - } +export class CreateGroupFileFolder extends OneBotAction { + override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const folderName = payload.folder_name || payload.name; + return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), folderName!)).resultWithGroupItem; + } } diff --git a/src/onebot/action/go-cqhttp/DeleteGroupFile.ts b/src/onebot/action/go-cqhttp/DeleteGroupFile.ts index 7db532a3..4207f013 100644 --- a/src/onebot/action/go-cqhttp/DeleteGroupFile.ts +++ b/src/onebot/action/go-cqhttp/DeleteGroupFile.ts @@ -1,4 +1,3 @@ - import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { FileNapCatOneBotUUID } from '@/common/file-uuid'; @@ -6,18 +5,18 @@ import { Static, Type } from '@sinclair/typebox'; import { NTQQGroupApi } from '@/core/apis'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file_id: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + file_id: Type.String(), }); type Payload = Static; export class DeleteGroupFile extends OneBotAction>> { - override actionName = ActionName.GOCQHTTP_DeleteGroupFile; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id); - if (!data || !data.fileId) throw new Error('Invalid file_id'); - return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]); - } + override actionName = ActionName.GOCQHTTP_DeleteGroupFile; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id); + if (!data || !data.fileId) throw new Error('Invalid file_id'); + return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]); + } } diff --git a/src/onebot/action/go-cqhttp/DeleteGroupFileFolder.ts b/src/onebot/action/go-cqhttp/DeleteGroupFileFolder.ts index 818c10a0..97ee7ef2 100644 --- a/src/onebot/action/go-cqhttp/DeleteGroupFileFolder.ts +++ b/src/onebot/action/go-cqhttp/DeleteGroupFileFolder.ts @@ -4,18 +4,18 @@ import { Static, Type } from '@sinclair/typebox'; import { NTQQGroupApi } from '@/core/apis'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - folder_id: Type.Optional(Type.String()), - folder: Type.Optional(Type.String()), + group_id: Type.Union([Type.Number(), Type.String()]), + folder_id: Type.Optional(Type.String()), + folder: Type.Optional(Type.String()), }); type Payload = Static; export class DeleteGroupFileFolder extends OneBotAction>['groupFileCommonResult']> { - override actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return (await this.core.apis.GroupApi.delGroupFileFolder( - payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult; - } + override actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + return (await this.core.apis.GroupApi.delGroupFileFolder( + payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult; + } } diff --git a/src/onebot/action/go-cqhttp/DownloadFile.ts b/src/onebot/action/go-cqhttp/DownloadFile.ts index 32fabfb5..7684ea0f 100644 --- a/src/onebot/action/go-cqhttp/DownloadFile.ts +++ b/src/onebot/action/go-cqhttp/DownloadFile.ts @@ -7,73 +7,72 @@ import { randomUUID } from 'crypto'; import { Static, Type } from '@sinclair/typebox'; interface FileResponse { - file: string; + file: string; } const SchemaData = Type.Object({ - url: Type.Optional(Type.String()), - base64: Type.Optional(Type.String()), - name: Type.Optional(Type.String()), - headers: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])), + url: Type.Optional(Type.String()), + base64: Type.Optional(Type.String()), + name: Type.Optional(Type.String()), + headers: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])), }); type Payload = Static; export default class GoCQHTTPDownloadFile extends OneBotAction { - override actionName = ActionName.GoCQHTTP_DownloadFile; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_DownloadFile; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const isRandomName = !payload.name; - const name = payload.name || randomUUID(); - let result: Awaited>; + async _handle (payload: Payload): Promise { + const isRandomName = !payload.name; + const name = payload.name || randomUUID(); + let result: Awaited>; - if (payload.base64) { - result = await uriToLocalFile(this.core.NapCatTempPath, `base64://${payload.base64}`, name); - } else if (payload.url) { - const headers = this.getHeaders(payload.headers); - result = await uriToLocalFile(this.core.NapCatTempPath, payload.url, name, headers); - } else { - throw new Error('不存在任何文件, 无法下载'); - } - if (!result.success) { - throw new Error(result.errMsg); - } - const filePath = result.path; - if (fs.existsSync(filePath)) { - - if (isRandomName) { - // 默认实现要名称未填写时文件名为文件 md5 - const md5 = await calculateFileMD5(filePath); - const newPath = joinPath(this.core.NapCatTempPath, md5); - fs.renameSync(filePath, newPath); - return { file: newPath }; - } - return { file: filePath }; - } else { - throw new Error('文件写入失败, 检查权限'); - } + if (payload.base64) { + result = await uriToLocalFile(this.core.NapCatTempPath, `base64://${payload.base64}`, name); + } else if (payload.url) { + const headers = this.getHeaders(payload.headers); + result = await uriToLocalFile(this.core.NapCatTempPath, payload.url, name, headers); + } else { + throw new Error('不存在任何文件, 无法下载'); } - - getHeaders(headersIn?: string | string[]): Record { - const headers: Record = {}; - if (typeof headersIn == 'string') { - headersIn = headersIn.split('[\\r\\n]'); - } - if (Array.isArray(headersIn)) { - for (const headerItem of headersIn) { - const spilt = headerItem.indexOf('='); - if (spilt < 0) { - headers[headerItem] = ''; - } else { - const key = headerItem.substring(0, spilt); - headers[key] = headerItem.substring(spilt + 1); - } - } - } - if (!headers['Content-Type']) { - headers['Content-Type'] = 'application/octet-stream'; - } - return headers; + if (!result.success) { + throw new Error(result.errMsg); } + const filePath = result.path; + if (fs.existsSync(filePath)) { + if (isRandomName) { + // 默认实现要名称未填写时文件名为文件 md5 + const md5 = await calculateFileMD5(filePath); + const newPath = joinPath(this.core.NapCatTempPath, md5); + fs.renameSync(filePath, newPath); + return { file: newPath }; + } + return { file: filePath }; + } else { + throw new Error('文件写入失败, 检查权限'); + } + } + + getHeaders (headersIn?: string | string[]): Record { + const headers: Record = {}; + if (typeof headersIn === 'string') { + headersIn = headersIn.split('[\\r\\n]'); + } + if (Array.isArray(headersIn)) { + for (const headerItem of headersIn) { + const spilt = headerItem.indexOf('='); + if (spilt < 0) { + headers[headerItem] = ''; + } else { + const key = headerItem.substring(0, spilt); + headers[key] = headerItem.substring(spilt + 1); + } + } + } + if (!headers['Content-Type']) { + headers['Content-Type'] = 'application/octet-stream'; + } + return headers; + } } diff --git a/src/onebot/action/go-cqhttp/GetForwardMsg.ts b/src/onebot/action/go-cqhttp/GetForwardMsg.ts index 11aa6794..2e5ddd29 100644 --- a/src/onebot/action/go-cqhttp/GetForwardMsg.ts +++ b/src/onebot/action/go-cqhttp/GetForwardMsg.ts @@ -7,138 +7,138 @@ import { ChatType, ElementType, MsgSourceType, NTMsgType, RawMessage } from '@/c import { isNumeric } from '@/common/helper'; const SchemaData = Type.Object({ - message_id: Type.Optional(Type.String()), - id: Type.Optional(Type.String()), + message_id: Type.Optional(Type.String()), + id: Type.Optional(Type.String()), }); type Payload = Static; export class GoCQHTTPGetForwardMsgAction extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetForwardMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetForwardMsg; + override payloadSchema = SchemaData; - private createTemplateNode(message: OB11Message): OB11MessageNode { + private createTemplateNode (message: OB11Message): OB11MessageNode { + return { + type: OB11MessageDataType.node, + data: { + user_id: message.user_id, + nickname: message.sender.nickname, + message: [], + content: [], + }, + }; + } + + async parseForward (messages: OB11Message[]): Promise { + const retMsg: OB11MessageNode[] = []; + + for (const message of messages) { + const templateNode = this.createTemplateNode(message); + + for (const msgdata of message.message) { + if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) { + const newNode = this.createTemplateNode(message); + newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content ?? []); + + templateNode.data.message.push(newNode); + } else { + templateNode.data.message.push(msgdata as OB11MessageData); + } + } + retMsg.push(templateNode); + } + + return retMsg; + } + + async _handle (payload: Payload) { + // 1. 检查消息ID是否存在 + const msgId = payload.message_id || payload.id; + if (!msgId) { + throw new Error('message_id is required'); + } + + // 2. 定义辅助函数 - 创建伪转发消息对象 + const createFakeForwardMsg = (resId: string): RawMessage => { + return { + chatType: ChatType.KCHATTYPEGROUP, + elements: [{ + elementType: ElementType.MULTIFORWARD, + elementId: '', + multiForwardMsgElement: { + resId, + fileName: '', + xmlContent: '', + }, + }], + guildId: '', + isOnlineMsg: false, + msgId: '', // TODO: no necessary + msgRandom: '0', + msgSeq: '', + msgTime: '', + msgType: NTMsgType.KMSGTYPEMIX, + parentMsgIdList: [], + parentMsgPeer: { + chatType: ChatType.KCHATTYPEGROUP, + peerUid: '', + }, + peerName: '', + peerUid: '284840486', + peerUin: '284840486', + recallTime: '0', + records: [], + sendNickName: '', + sendRemarkName: '', + senderUid: '', + senderUin: '1094950020', + sourceType: MsgSourceType.K_DOWN_SOURCETYPE_UNKNOWN, + subMsgType: 1, + } as RawMessage; + }; + + // 3. 定义协议回退逻辑函数 + const protocolFallbackLogic = async (resId: string) => { + const ob = (await this.obContext.apis.MsgApi.parseMessageV2(createFakeForwardMsg(resId)))?.arrayMsg; + if (ob) { return { - type: OB11MessageDataType.node, - data: { - user_id: message.user_id, - nickname: message.sender.nickname, - message: [], - content: [] - } + messages: (ob?.message?.[0] as OB11MessageForward)?.data?.content, }; + } + throw new Error('protocolFallbackLogic: 找不到相关的聊天记录'); + }; + // 4. 尝试通过正常渠道获取消息 + // 如果是数字ID,优先使用getMsgsByMsgId获取 + if (!isNumeric(msgId)) { + const ret = await protocolFallbackLogic(msgId); + if (ret.messages) { + return ret; + } + throw new Error('ResId无效: 找不到相关的聊天记录'); } + const rootMsgId = MessageUnique.getShortIdByMsgId(msgId.toString()); + const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId ?? +msgId); - async parseForward(messages: OB11Message[]): Promise { - const retMsg: OB11MessageNode[] = []; + if (rootMsg) { + // 5. 获取消息内容 + const data = await this.core.apis.MsgApi.getMsgsByMsgId(rootMsg.Peer, [rootMsg.MsgId]); - for (const message of messages) { - const templateNode = this.createTemplateNode(message); - - for (const msgdata of message.message) { - if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) { - const newNode = this.createTemplateNode(message); - newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content ?? []); - - templateNode.data.message.push(newNode); - } else { - templateNode.data.message.push(msgdata as OB11MessageData); - } - } - retMsg.push(templateNode); + if (data && data.result === 0 && data.msgList.length > 0) { + const singleMsg = data.msgList[0]; + if (!singleMsg) { + throw new Error('消息不存在或已过期'); } + // 6. 解析消息内容 + const resMsg = (await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array', true)); - return retMsg; - } - - async _handle(payload: Payload) { - // 1. 检查消息ID是否存在 - const msgId = payload.message_id || payload.id; - if (!msgId) { - throw new Error('message_id is required'); - } - - // 2. 定义辅助函数 - 创建伪转发消息对象 - const createFakeForwardMsg = (resId: string): RawMessage => { - return { - chatType: ChatType.KCHATTYPEGROUP, - elements: [{ - elementType: ElementType.MULTIFORWARD, - elementId: '', - multiForwardMsgElement: { - resId: resId, - fileName: '', - xmlContent: '', - } - }], - guildId: '', - isOnlineMsg: false, - msgId: '', // TODO: no necessary - msgRandom: '0', - msgSeq: '', - msgTime: '', - msgType: NTMsgType.KMSGTYPEMIX, - parentMsgIdList: [], - parentMsgPeer: { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: '', - }, - peerName: '', - peerUid: '284840486', - peerUin: '284840486', - recallTime: '0', - records: [], - sendNickName: '', - sendRemarkName: '', - senderUid: '', - senderUin: '1094950020', - sourceType: MsgSourceType.K_DOWN_SOURCETYPE_UNKNOWN, - subMsgType: 1, - } as RawMessage; - }; - - // 3. 定义协议回退逻辑函数 - const protocolFallbackLogic = async (resId: string) => { - const ob = (await this.obContext.apis.MsgApi.parseMessageV2(createFakeForwardMsg(resId)))?.arrayMsg; - if (ob) { - return { - messages: (ob?.message?.[0] as OB11MessageForward)?.data?.content - }; - } - throw new Error('protocolFallbackLogic: 找不到相关的聊天记录'); - }; - // 4. 尝试通过正常渠道获取消息 - // 如果是数字ID,优先使用getMsgsByMsgId获取 - if (!isNumeric(msgId)) { - let ret = await protocolFallbackLogic(msgId); - if (ret.messages) { - return ret; - } - throw new Error('ResId无效: 找不到相关的聊天记录'); - } - const rootMsgId = MessageUnique.getShortIdByMsgId(msgId.toString()); - const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId ?? +msgId); - - if (rootMsg) { - // 5. 获取消息内容 - const data = await this.core.apis.MsgApi.getMsgsByMsgId(rootMsg.Peer, [rootMsg.MsgId]); - - if (data && data.result === 0 && data.msgList.length > 0) { - const singleMsg = data.msgList[0]; - if (!singleMsg) { - throw new Error('消息不存在或已过期'); - } - // 6. 解析消息内容 - const resMsg = (await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array', true)); - - const forwardContent = (resMsg?.message?.[0] as OB11MessageForward)?.data?.content; - if (forwardContent) { - return { messages: forwardContent }; - } - } - } - // 说明消息已过期或者为内层消息 NapCat 一次返回不处理内层消息 - throw new Error('消息已过期或者为内层消息,无法获取转发消息'); + const forwardContent = (resMsg?.message?.[0] as OB11MessageForward)?.data?.content; + if (forwardContent) { + return { messages: forwardContent }; + } + } } + // 说明消息已过期或者为内层消息 NapCat 一次返回不处理内层消息 + throw new Error('消息已过期或者为内层消息,无法获取转发消息'); + } } diff --git a/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts b/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts index 71e0bce7..d904839d 100644 --- a/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts +++ b/src/onebot/action/go-cqhttp/GetFriendMsgHistory.ts @@ -8,44 +8,44 @@ import { Static, Type } from '@sinclair/typebox'; import { NetworkAdapterConfig } from '@/onebot/config/config'; interface Response { - messages: OB11Message[]; + messages: OB11Message[]; } const SchemaData = Type.Object({ - user_id: Type.String(), - message_seq: Type.Optional(Type.String()), - count: Type.Number({ default: 20 }), - reverseOrder: Type.Boolean({ default: false }), - disable_get_url: Type.Boolean({ default: false }), - parse_mult_msg: Type.Boolean({ default: true }), - quick_reply: Type.Boolean({ default: false }), + user_id: Type.String(), + message_seq: Type.Optional(Type.String()), + count: Type.Number({ default: 20 }), + reverseOrder: Type.Boolean({ default: false }), + disable_get_url: Type.Boolean({ default: false }), + parse_mult_msg: Type.Boolean({ default: true }), + quick_reply: Type.Boolean({ default: false }), }); - type Payload = Static; export default class GetFriendMsgHistory extends OneBotAction { - override actionName = ActionName.GetFriendMsgHistory; - override payloadSchema = SchemaData; + override actionName = ActionName.GetFriendMsgHistory; + override payloadSchema = SchemaData; - async _handle(payload: Payload, _adapter: string, config: NetworkAdapterConfig): Promise { - //处理参数 - const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!uid) throw new Error(`记录${payload.user_id}不存在`); - const friend = await this.core.apis.FriendApi.isBuddy(uid); - const peer = { chatType: friend ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: uid }; - 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.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList; - if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`); - //转换序号 - await Promise.all(msgList.map(async msg => { - msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId); - })); - //烘焙消息 - const ob11MsgList = (await Promise.all( - msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat, payload.parse_mult_msg, payload.disable_get_url))) - ).filter(msg => msg !== undefined); - return { 'messages': ob11MsgList }; - } + async _handle (payload: Payload, _adapter: string, config: NetworkAdapterConfig): Promise { + // 处理参数 + const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!uid) throw new Error(`记录${payload.user_id}不存在`); + const friend = await this.core.apis.FriendApi.isBuddy(uid); + const peer = { chatType: friend ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: uid }; + 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.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList; + if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`); + // 转换序号 + await Promise.all(msgList.map(async msg => { + msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId); + })); + // 烘焙消息 + const ob11MsgList = (await Promise.all( + msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat, payload.parse_mult_msg, payload.disable_get_url))) + ).filter(msg => msg !== undefined); + return { messages: ob11MsgList }; + } } diff --git a/src/onebot/action/go-cqhttp/GetGroupAtAllRemain.ts b/src/onebot/action/go-cqhttp/GetGroupAtAllRemain.ts index 5d4939d2..618bef07 100644 --- a/src/onebot/action/go-cqhttp/GetGroupAtAllRemain.ts +++ b/src/onebot/action/go-cqhttp/GetGroupAtAllRemain.ts @@ -3,26 +3,26 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]) + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; interface ResponseType { - can_at_all: boolean; - remain_at_all_count_for_group: number; - remain_at_all_count_for_uin: number; + can_at_all: boolean; + remain_at_all_count_for_group: number; + remain_at_all_count_for_uin: number; } export class GoCQHTTPGetGroupAtAllRemain extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString()); - const data = { - can_at_all: ret.atInfo.canAtAll, - remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup, - remain_at_all_count_for_uin: ret.atInfo.RemainAtAllCountForUin - }; - return data; - } + async _handle (payload: Payload) { + const ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString()); + const data = { + can_at_all: ret.atInfo.canAtAll, + remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup, + remain_at_all_count_for_uin: ret.atInfo.RemainAtAllCountForUin, + }; + return data; + } } diff --git a/src/onebot/action/go-cqhttp/GetGroupFileSystemInfo.ts b/src/onebot/action/go-cqhttp/GetGroupFileSystemInfo.ts index 9f740732..217c4f6c 100644 --- a/src/onebot/action/go-cqhttp/GetGroupFileSystemInfo.ts +++ b/src/onebot/action/go-cqhttp/GetGroupFileSystemInfo.ts @@ -3,30 +3,30 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]) + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class GetGroupFileSystemInfo extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetGroupFileSystemInfo; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetGroupFileSystemInfo; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const groupFileCount = (await this.core.apis.GroupApi.getGroupFileCount([payload.group_id.toString()])).groupFileCounts[0]; - if (!groupFileCount) { - throw new Error('Group not found'); - } - return { - file_count: groupFileCount, - limit_count: 10000, - used_space: 0, - total_space: 10 * 1024 * 1024 * 1024, - }; + async _handle (payload: Payload) { + const groupFileCount = (await this.core.apis.GroupApi.getGroupFileCount([payload.group_id.toString()])).groupFileCounts[0]; + if (!groupFileCount) { + throw new Error('Group not found'); } + return { + file_count: groupFileCount, + limit_count: 10000, + used_space: 0, + total_space: 10 * 1024 * 1024 * 1024, + }; + } } diff --git a/src/onebot/action/go-cqhttp/GetGroupFilesByFolder.ts b/src/onebot/action/go-cqhttp/GetGroupFilesByFolder.ts index dc5813b8..a7d8cf01 100644 --- a/src/onebot/action/go-cqhttp/GetGroupFilesByFolder.ts +++ b/src/onebot/action/go-cqhttp/GetGroupFilesByFolder.ts @@ -1,38 +1,36 @@ - import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { OB11Construct } from '@/onebot/helper/data'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - folder_id: Type.Optional(Type.String()), - folder: Type.Optional(Type.String()), - file_count: Type.Union([Type.Number(), Type.String()], { default: 50 }), + group_id: Type.Union([Type.Number(), Type.String()]), + folder_id: Type.Optional(Type.String()), + folder: Type.Optional(Type.String()), + file_count: Type.Union([Type.Number(), Type.String()], { default: 50 }), }); type Payload = Static; export class GetGroupFilesByFolder extends OneBotAction[], - folders: never[], + files: ReturnType[], + folders: never[], }> { - override actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - - const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), { - sortType: 1, - fileCount: +payload.file_count, - startIndex: 0, - sortOrder: 2, - showOnlinedocFolder: 0, - folderId: payload.folder ?? payload.folder_id ?? '', - }).catch(() => []); - return { - files: ret.filter(item => item.fileInfo) - .map(item => OB11Construct.file(item.peerId, item.fileInfo!)), - folders: [] as [], - }; - } + override actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), { + sortType: 1, + fileCount: +payload.file_count, + startIndex: 0, + sortOrder: 2, + showOnlinedocFolder: 0, + folderId: payload.folder ?? payload.folder_id ?? '', + }).catch(() => []); + return { + files: ret.filter(item => item.fileInfo) + .map(item => OB11Construct.file(item.peerId, item.fileInfo!)), + folders: [] as [], + }; + } } diff --git a/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts b/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts index 9ead4af2..3d20849c 100644 --- a/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts +++ b/src/onebot/action/go-cqhttp/GetGroupHonorInfo.ts @@ -4,20 +4,20 @@ import { WebHonorType } from '@/core/types'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - type: Type.Optional(Type.Enum(WebHonorType)) + group_id: Type.Union([Type.Number(), Type.String()]), + type: Type.Optional(Type.Enum(WebHonorType)), }); type Payload = Static; export class GetGroupHonorInfo extends OneBotAction> { - override actionName = ActionName.GetGroupHonorInfo; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupHonorInfo; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - if (!payload.type) { - payload.type = WebHonorType.ALL; - } - return await this.core.apis.WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type); + async _handle (payload: Payload) { + if (!payload.type) { + payload.type = WebHonorType.ALL; } + return await this.core.apis.WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type); + } } diff --git a/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts b/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts index 75391b4d..6cd83d40 100644 --- a/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts +++ b/src/onebot/action/go-cqhttp/GetGroupMsgHistory.ts @@ -7,43 +7,42 @@ import { Static, Type } from '@sinclair/typebox'; import { NetworkAdapterConfig } from '@/onebot/config/config'; interface Response { - messages: OB11Message[]; + messages: OB11Message[]; } const SchemaData = Type.Object({ - group_id: Type.String(), - message_seq: Type.Optional(Type.String()), - count: Type.Number({ default: 20 }), - reverseOrder: Type.Boolean({ default: false }), - disable_get_url: Type.Boolean({ default: false }), - parse_mult_msg: Type.Boolean({ default: true }), - quick_reply: Type.Boolean({ default: false }), + group_id: Type.String(), + message_seq: Type.Optional(Type.String()), + count: Type.Number({ default: 20 }), + reverseOrder: Type.Boolean({ default: false }), + disable_get_url: Type.Boolean({ default: false }), + parse_mult_msg: Type.Boolean({ default: true }), + quick_reply: Type.Boolean({ default: false }), }); - type Payload = Static; - export default class GoCQHTTPGetGroupMsgHistory extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetGroupMsgHistory; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetGroupMsgHistory; + override payloadSchema = SchemaData; - async _handle(payload: Payload, _adapter: string, config: NetworkAdapterConfig): Promise { - const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() }; - 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.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList; - if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`); - //转换序号 - await Promise.all(msgList.map(async msg => { - msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId); - })); - //烘焙消息 - const ob11MsgList = (await Promise.all( - msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat, payload.parse_mult_msg, payload.disable_get_url, payload.quick_reply))) - ).filter(msg => msg !== undefined); - return { 'messages': ob11MsgList }; - } + async _handle (payload: Payload, _adapter: string, config: NetworkAdapterConfig): Promise { + const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() }; + 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.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList; + if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`); + // 转换序号 + await Promise.all(msgList.map(async msg => { + msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId); + })); + // 烘焙消息 + const ob11MsgList = (await Promise.all( + msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat, payload.parse_mult_msg, payload.disable_get_url, payload.quick_reply))) + ).filter(msg => msg !== undefined); + return { messages: ob11MsgList }; + } } diff --git a/src/onebot/action/go-cqhttp/GetGroupRootFiles.ts b/src/onebot/action/go-cqhttp/GetGroupRootFiles.ts index a4c1c916..91882dd7 100644 --- a/src/onebot/action/go-cqhttp/GetGroupRootFiles.ts +++ b/src/onebot/action/go-cqhttp/GetGroupRootFiles.ts @@ -1,4 +1,3 @@ - import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { OB11GroupFile, OB11GroupFileFolder } from '@/onebot'; @@ -6,32 +5,32 @@ import { OB11Construct } from '@/onebot/helper/data'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file_count: Type.Union([Type.Number(), Type.String()], { default: 50 }), + group_id: Type.Union([Type.Number(), Type.String()]), + file_count: Type.Union([Type.Number(), Type.String()], { default: 50 }), }); type Payload = Static; export class GetGroupRootFiles extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetGroupRootFiles; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), { - sortType: 1, - fileCount: +payload.file_count, - startIndex: 0, - sortOrder: 2, - showOnlinedocFolder: 0, - }); + override actionName = ActionName.GoCQHTTP_GetGroupRootFiles; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const ret = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), { + sortType: 1, + fileCount: +payload.file_count, + startIndex: 0, + sortOrder: 2, + showOnlinedocFolder: 0, + }); - return { - files: ret.filter(item => item.fileInfo) - .map(item => OB11Construct.file(item.peerId, item.fileInfo!)), - folders: ret.filter(item => item.folderInfo) - .map(item => OB11Construct.folder(item.peerId, item.folderInfo!)), - }; - } + return { + files: ret.filter(item => item.fileInfo) + .map(item => OB11Construct.file(item.peerId, item.fileInfo!)), + folders: ret.filter(item => item.folderInfo) + .map(item => OB11Construct.folder(item.peerId, item.folderInfo!)), + }; + } } diff --git a/src/onebot/action/go-cqhttp/GetOnlineClient.ts b/src/onebot/action/go-cqhttp/GetOnlineClient.ts index 1b5c0dd1..ab045fcc 100644 --- a/src/onebot/action/go-cqhttp/GetOnlineClient.ts +++ b/src/onebot/action/go-cqhttp/GetOnlineClient.ts @@ -3,12 +3,12 @@ import { ActionName } from '@/onebot/action/router'; import { sleep } from '@/common/helper'; export class GetOnlineClient extends OneBotAction> { - override actionName = ActionName.GetOnlineClient; - async _handle() { - //注册监听 - this.core.apis.SystemApi.getOnlineDev(); - await sleep(500); + override actionName = ActionName.GetOnlineClient; + async _handle () { + // 注册监听 + this.core.apis.SystemApi.getOnlineDev(); + await sleep(500); - return []; - } + return []; + } } diff --git a/src/onebot/action/go-cqhttp/GetStrangerInfo.ts b/src/onebot/action/go-cqhttp/GetStrangerInfo.ts index 796f67f3..3035f8e3 100644 --- a/src/onebot/action/go-cqhttp/GetStrangerInfo.ts +++ b/src/onebot/action/go-cqhttp/GetStrangerInfo.ts @@ -6,43 +6,43 @@ import { calcQQLevel } from '@/common/helper'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.Union([Type.Number(), Type.String()]), - no_cache: Type.Union([Type.Boolean(), Type.String()], { default: false }), + user_id: Type.Union([Type.Number(), Type.String()]), + no_cache: Type.Union([Type.Boolean(), Type.String()], { default: false }), }); type Payload = Static; export default class GoCQHTTPGetStrangerInfo extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetStrangerInfo; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const user_id = payload.user_id.toString(); - const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache; - const extendData = await this.core.apis.UserApi.getUserDetailInfoByUin(user_id); - let uid = (await this.core.apis.UserApi.getUidByUinV2(user_id)); - if (!uid) uid = extendData.detail.uid; - const info = (await this.core.apis.UserApi.getUserDetailInfo(uid, isNocache)); - return { - ...extendData.detail.simpleInfo.coreInfo, - ...extendData.detail.commonExt ?? {}, - ...extendData.detail.simpleInfo.baseInfo, - ...extendData.detail.simpleInfo.relationFlags ?? {}, - ...extendData.detail.simpleInfo.status ?? {}, - user_id: parseInt(extendData.detail.uin) ?? 0, - uid: info.uid ?? uid, - nickname: extendData.detail.simpleInfo.coreInfo.nick ?? '', - age: extendData.detail.simpleInfo.baseInfo.age ?? info.age, - qid: extendData.detail.simpleInfo.baseInfo.qid, - qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel), - sex: OB11Construct.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown, - long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick, - reg_time: extendData.detail.commonExt?.regTime ?? info.regTime, - is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag, - is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag, - vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel, - remark: extendData.detail.simpleInfo.coreInfo.remark ?? info.remark, - status: extendData.detail.simpleInfo.status?.status ?? info.status, - login_days: 0,//失效 - }; - } + override actionName = ActionName.GoCQHTTP_GetStrangerInfo; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + const user_id = payload.user_id.toString(); + const isNocache = typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache; + const extendData = await this.core.apis.UserApi.getUserDetailInfoByUin(user_id); + let uid = (await this.core.apis.UserApi.getUidByUinV2(user_id)); + if (!uid) uid = extendData.detail.uid; + const info = (await this.core.apis.UserApi.getUserDetailInfo(uid, isNocache)); + return { + ...extendData.detail.simpleInfo.coreInfo, + ...extendData.detail.commonExt ?? {}, + ...extendData.detail.simpleInfo.baseInfo, + ...extendData.detail.simpleInfo.relationFlags ?? {}, + ...extendData.detail.simpleInfo.status ?? {}, + user_id: parseInt(extendData.detail.uin) ?? 0, + uid: info.uid ?? uid, + nickname: extendData.detail.simpleInfo.coreInfo.nick ?? '', + age: extendData.detail.simpleInfo.baseInfo.age ?? info.age, + qid: extendData.detail.simpleInfo.baseInfo.qid, + qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel), + sex: OB11Construct.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown, + long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick, + reg_time: extendData.detail.commonExt?.regTime ?? info.regTime, + is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag, + is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag, + vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel, + remark: extendData.detail.simpleInfo.coreInfo.remark ?? info.remark, + status: extendData.detail.simpleInfo.status?.status ?? info.status, + login_days: 0, // 失效 + }; + } } diff --git a/src/onebot/action/go-cqhttp/GoCQHTTPCheckUrlSafely.ts b/src/onebot/action/go-cqhttp/GoCQHTTPCheckUrlSafely.ts index 29a9b502..49f6b94a 100644 --- a/src/onebot/action/go-cqhttp/GoCQHTTPCheckUrlSafely.ts +++ b/src/onebot/action/go-cqhttp/GoCQHTTPCheckUrlSafely.ts @@ -3,16 +3,16 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - url: Type.String(), + url: Type.String(), }); type Payload = Static; export class GoCQHTTPCheckUrlSafely extends OneBotAction { - override actionName = ActionName.GoCQHTTP_CheckUrlSafely; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_CheckUrlSafely; + override payloadSchema = SchemaData; - async _handle() { - return { level: 1 }; - } + async _handle () { + return { level: 1 }; + } } diff --git a/src/onebot/action/go-cqhttp/GoCQHTTPDeleteFriend.ts b/src/onebot/action/go-cqhttp/GoCQHTTPDeleteFriend.ts index adc02cf9..ab31ab13 100644 --- a/src/onebot/action/go-cqhttp/GoCQHTTPDeleteFriend.ts +++ b/src/onebot/action/go-cqhttp/GoCQHTTPDeleteFriend.ts @@ -3,35 +3,35 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - friend_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), - user_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), - temp_block: Type.Optional(Type.Boolean()), - temp_both_del: Type.Optional(Type.Boolean()), + friend_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), + user_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), + temp_block: Type.Optional(Type.Boolean()), + temp_both_del: Type.Optional(Type.Boolean()), }); type Payload = Static; export class GoCQHTTPDeleteFriend extends OneBotAction { - override actionName = ActionName.GoCQHTTP_DeleteFriend; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_DeleteFriend; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const uin = payload.friend_id ?? payload.user_id ?? ''; - const uid = await this.core.apis.UserApi.getUidByUinV2(uin.toString()); + async _handle (payload: Payload) { + const uin = payload.friend_id ?? payload.user_id ?? ''; + const uid = await this.core.apis.UserApi.getUidByUinV2(uin.toString()); - if (!uid) { - return { - valid: false, - message: '好友不存在', - }; - } - const isBuddy = await this.core.apis.FriendApi.isBuddy(uid); - if (!isBuddy) { - return { - valid: false, - message: '不是好友', - }; - } - return await this.core.apis.FriendApi.delBuudy(uid, payload.temp_block, payload.temp_both_del); + if (!uid) { + return { + valid: false, + message: '好友不存在', + }; } + const isBuddy = await this.core.apis.FriendApi.isBuddy(uid); + if (!isBuddy) { + return { + valid: false, + message: '不是好友', + }; + } + return await this.core.apis.FriendApi.delBuudy(uid, payload.temp_block, payload.temp_both_del); + } } diff --git a/src/onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts b/src/onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts index af8ca93d..e220e933 100644 --- a/src/onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts +++ b/src/onebot/action/go-cqhttp/GoCQHTTPGetModelShow.ts @@ -3,29 +3,29 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - model: Type.Optional(Type.String()), + model: Type.Optional(Type.String()), }); type Payload = Static; export class GoCQHTTPGetModelShow extends OneBotAction> { - override actionName = ActionName.GoCQHTTP_GetModelShow; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetModelShow; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - if (!payload.model) { - payload.model = 'napcat'; - } - return [{ - variants: { - model_show: 'napcat', - need_pay: false - } - }]; + async _handle (payload: Payload) { + if (!payload.model) { + payload.model = 'napcat'; } + return [{ + variants: { + model_show: 'napcat', + need_pay: false, + }, + }]; + } } diff --git a/src/onebot/action/go-cqhttp/GoCQHTTPSetModelShow.ts b/src/onebot/action/go-cqhttp/GoCQHTTPSetModelShow.ts index 3ac4a9be..4086d922 100644 --- a/src/onebot/action/go-cqhttp/GoCQHTTPSetModelShow.ts +++ b/src/onebot/action/go-cqhttp/GoCQHTTPSetModelShow.ts @@ -1,10 +1,10 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; -//兼容性代码 +// 兼容性代码 export class GoCQHTTPSetModelShow extends OneBotAction { - override actionName = ActionName.GoCQHTTP_SetModelShow; + override actionName = ActionName.GoCQHTTP_SetModelShow; - async _handle() { - return; - } + async _handle () { + + } } diff --git a/src/onebot/action/go-cqhttp/QuickAction.ts b/src/onebot/action/go-cqhttp/QuickAction.ts index e19e4eeb..daf92693 100644 --- a/src/onebot/action/go-cqhttp/QuickAction.ts +++ b/src/onebot/action/go-cqhttp/QuickAction.ts @@ -3,17 +3,17 @@ import { ActionName } from '@/onebot/action/router'; import { QuickAction, QuickActionEvent } from '@/onebot/types'; interface Payload { - context: QuickActionEvent, - operation: QuickAction + context: QuickActionEvent, + operation: QuickAction } export class GoCQHTTPHandleQuickAction extends OneBotAction { - override actionName = ActionName.GoCQHTTP_HandleQuickAction; + override actionName = ActionName.GoCQHTTP_HandleQuickAction; - async _handle(payload: Payload): Promise { - this.obContext.apis.QuickActionApi - .handleQuickOperation(payload.context, payload.operation) - .catch(e => this.core.context.logger.logError(e)); - return null; - } + async _handle (payload: Payload): Promise { + this.obContext.apis.QuickActionApi + .handleQuickOperation(payload.context, payload.operation) + .catch(e => this.core.context.logger.logError(e)); + return null; + } } diff --git a/src/onebot/action/go-cqhttp/SendForwardMsg.ts b/src/onebot/action/go-cqhttp/SendForwardMsg.ts index c96392ec..8150abbe 100644 --- a/src/onebot/action/go-cqhttp/SendForwardMsg.ts +++ b/src/onebot/action/go-cqhttp/SendForwardMsg.ts @@ -4,29 +4,29 @@ import { ActionName } from '@/onebot/action/router'; // 未验证 export class GoCQHTTPSendForwardMsgBase extends SendMsgBase { - protected override async check(payload: OB11PostSendMsg) { - if (payload.messages) payload.message = normalize(payload.messages); - return super.check(payload); - } + protected override async check (payload: OB11PostSendMsg) { + if (payload.messages) payload.message = normalize(payload.messages); + return super.check(payload); + } } export class GoCQHTTPSendForwardMsg extends GoCQHTTPSendForwardMsgBase { - override actionName = ActionName.GoCQHTTP_SendForwardMsg; + override actionName = ActionName.GoCQHTTP_SendForwardMsg; - protected override async check(payload: OB11PostSendMsg) { - if (payload.messages) payload.message = normalize(payload.messages); - return super.check(payload); - } + protected override async check (payload: OB11PostSendMsg) { + if (payload.messages) payload.message = normalize(payload.messages); + return super.check(payload); + } } export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsgBase { - override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg; - override async _handle(payload: OB11PostSendMsg): Promise { - return this.base_handle(payload, ContextMode.Private); - } + override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg; + override async _handle (payload: OB11PostSendMsg): Promise { + return this.base_handle(payload, ContextMode.Private); + } } export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsgBase { - override actionName = ActionName.GoCQHTTP_SendGroupForwardMsg; - override async _handle(payload: OB11PostSendMsg): Promise { - return this.base_handle(payload, ContextMode.Group); - } + override actionName = ActionName.GoCQHTTP_SendGroupForwardMsg; + override async _handle (payload: OB11PostSendMsg): Promise { + return this.base_handle(payload, ContextMode.Group); + } } diff --git a/src/onebot/action/go-cqhttp/SendGroupNotice.ts b/src/onebot/action/go-cqhttp/SendGroupNotice.ts index 158886d9..6bceb8d5 100644 --- a/src/onebot/action/go-cqhttp/SendGroupNotice.ts +++ b/src/onebot/action/go-cqhttp/SendGroupNotice.ts @@ -5,61 +5,60 @@ import { unlink } from 'node:fs/promises'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - content: Type.String(), - image: Type.Optional(Type.String()), - pinned: Type.Union([Type.Number(), Type.String()], { default: 0 }), - type: Type.Union([Type.Number(), Type.String()], { default: 1 }), - confirm_required: Type.Union([Type.Number(), Type.String()], { default: 1 }), - is_show_edit_card: Type.Union([Type.Number(), Type.String()], { default: 0 }), - tip_window_type: Type.Union([Type.Number(), Type.String()], { default: 0 }) + group_id: Type.Union([Type.Number(), Type.String()]), + content: Type.String(), + image: Type.Optional(Type.String()), + pinned: Type.Union([Type.Number(), Type.String()], { default: 0 }), + type: Type.Union([Type.Number(), Type.String()], { default: 1 }), + confirm_required: Type.Union([Type.Number(), Type.String()], { default: 1 }), + is_show_edit_card: Type.Union([Type.Number(), Type.String()], { default: 0 }), + tip_window_type: Type.Union([Type.Number(), Type.String()], { default: 0 }), }); type Payload = Static; export class SendGroupNotice extends OneBotAction { - override actionName = ActionName.GoCQHTTP_SendGroupNotice; - override payloadSchema = SchemaData; - async _handle(payload: Payload) { + override actionName = ActionName.GoCQHTTP_SendGroupNotice; + override payloadSchema = SchemaData; + async _handle (payload: Payload) { + let UploadImage: { id: string, width: number, height: number } | undefined; + if (payload.image) { + // 公告图逻辑 + const { + path, + success, + } = (await uriToLocalFile(this.core.NapCatTempPath, payload.image)); + if (!success) { + throw new Error(`群公告${payload.image}设置失败,image字段可能格式不正确`); + } + if (!path) { + throw new Error(`群公告${payload.image}设置失败,获取资源失败`); + } + await checkFileExist(path, 5000); + const ImageUploadResult = await this.core.apis.GroupApi.uploadGroupBulletinPic(payload.group_id.toString(), path); + if (ImageUploadResult.errCode != 0) { + throw new Error(`群公告${payload.image}设置失败,图片上传失败`); + } - let UploadImage: { id: string, width: number, height: number } | undefined = undefined; - if (payload.image) { - //公告图逻辑 - const { - path, - success, - } = (await uriToLocalFile(this.core.NapCatTempPath, payload.image)); - if (!success) { - throw new Error(`群公告${payload.image}设置失败,image字段可能格式不正确`); - } - if (!path) { - throw new Error(`群公告${payload.image}设置失败,获取资源失败`); - } - await checkFileExist(path, 5000); - const ImageUploadResult = await this.core.apis.GroupApi.uploadGroupBulletinPic(payload.group_id.toString(), path); - if (ImageUploadResult.errCode != 0) { - throw new Error(`群公告${payload.image}设置失败,图片上传失败`); - } + unlink(path).catch(() => { }); - unlink(path).catch(() => { }); - - UploadImage = ImageUploadResult.picInfo; - } - const publishGroupBulletinResult = await this.core.apis.WebApi.setGroupNotice( - payload.group_id.toString(), - payload.content, - +payload.pinned, - +payload.type, - +payload.is_show_edit_card, - +payload.tip_window_type, - +payload.confirm_required, - UploadImage?.id, - UploadImage?.width, - UploadImage?.height - ); - if (!publishGroupBulletinResult || publishGroupBulletinResult.ec != 0) { - throw new Error(`设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`); - } - return null; + UploadImage = ImageUploadResult.picInfo; } + const publishGroupBulletinResult = await this.core.apis.WebApi.setGroupNotice( + payload.group_id.toString(), + payload.content, + +payload.pinned, + +payload.type, + +payload.is_show_edit_card, + +payload.tip_window_type, + +payload.confirm_required, + UploadImage?.id, + UploadImage?.width, + UploadImage?.height + ); + if (!publishGroupBulletinResult || publishGroupBulletinResult.ec != 0) { + throw new Error(`设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`); + } + return null; + } } diff --git a/src/onebot/action/go-cqhttp/SetGroupPortrait.ts b/src/onebot/action/go-cqhttp/SetGroupPortrait.ts index bfe0997f..50af15d3 100644 --- a/src/onebot/action/go-cqhttp/SetGroupPortrait.ts +++ b/src/onebot/action/go-cqhttp/SetGroupPortrait.ts @@ -5,37 +5,37 @@ import { Static, Type } from '@sinclair/typebox'; import fs from 'node:fs/promises'; import { GeneralCallResult } from '@/core'; const SchemaData = Type.Object({ - file: Type.String(), - group_id: Type.Union([Type.Number(), Type.String()]) + file: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export default class SetGroupPortrait extends OneBotAction { - override actionName = ActionName.SetGroupPortrait; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupPortrait; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.file)); - if (!success) { - throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`); - } - if (path) { - await checkFileExistV2(path, 5000); // 文件不存在QQ会崩溃,需要提前判断 - const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path); - fs.unlink(path).catch(() => { }); - if (!ret) { - throw new Error(`头像${payload.file}设置失败,api无返回`); - } - if (ret.result as number == 1004022) { - throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式或权限不足`); - } else if (ret.result != 0) { - throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`); - } - return ret; - } else { - fs.unlink(path).catch(() => { }); - throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`); - } + async _handle (payload: Payload): Promise { + const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.file)); + if (!success) { + throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`); } + if (path) { + await checkFileExistV2(path, 5000); // 文件不存在QQ会崩溃,需要提前判断 + const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path); + fs.unlink(path).catch(() => { }); + if (!ret) { + throw new Error(`头像${payload.file}设置失败,api无返回`); + } + if (ret.result as number == 1004022) { + throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式或权限不足`); + } else if (ret.result != 0) { + throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`); + } + return ret; + } else { + fs.unlink(path).catch(() => { }); + throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`); + } + } } diff --git a/src/onebot/action/go-cqhttp/SetQQProfile.ts b/src/onebot/action/go-cqhttp/SetQQProfile.ts index 976d87b5..c1b84e17 100644 --- a/src/onebot/action/go-cqhttp/SetQQProfile.ts +++ b/src/onebot/action/go-cqhttp/SetQQProfile.ts @@ -4,29 +4,29 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - nickname: Type.String(), - personal_note: Type.Optional(Type.String()), - sex: Type.Optional(Type.Union([Type.Number(), Type.String()])), // 传Sex值?建议传0 + nickname: Type.String(), + personal_note: Type.Optional(Type.String()), + sex: Type.Optional(Type.Union([Type.Number(), Type.String()])), // 传Sex值?建议传0 }); type Payload = Static; export class SetQQProfile extends OneBotAction> | null> { - override actionName = ActionName.SetQQProfile; - override payloadSchema = SchemaData; + override actionName = ActionName.SetQQProfile; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const self = this.core.selfInfo; - const OldProfile = await this.core.apis.UserApi.getUserDetailInfo(self.uid); - return await this.core.apis.UserApi.modifySelfProfile({ - nick: payload.nickname, - longNick: (payload?.personal_note ?? OldProfile?.longNick) || '', - sex: parseInt(payload?.sex ? payload?.sex.toString() : OldProfile?.sex!.toString()), - birthday: { - birthday_year: OldProfile?.birthday_year!.toString(), - birthday_month: OldProfile?.birthday_month!.toString(), - birthday_day: OldProfile?.birthday_day!.toString(), - }, - location: undefined, - }); - } + async _handle (payload: Payload) { + const self = this.core.selfInfo; + const OldProfile = await this.core.apis.UserApi.getUserDetailInfo(self.uid); + return await this.core.apis.UserApi.modifySelfProfile({ + nick: payload.nickname, + longNick: (payload?.personal_note ?? OldProfile?.longNick) || '', + sex: parseInt(payload?.sex ? payload?.sex.toString() : OldProfile?.sex!.toString()), + birthday: { + birthday_year: OldProfile?.birthday_year!.toString(), + birthday_month: OldProfile?.birthday_month!.toString(), + birthday_day: OldProfile?.birthday_day!.toString(), + }, + location: undefined, + }); + } } diff --git a/src/onebot/action/go-cqhttp/UploadGroupFile.ts b/src/onebot/action/go-cqhttp/UploadGroupFile.ts index 072251a7..ab92a232 100644 --- a/src/onebot/action/go-cqhttp/UploadGroupFile.ts +++ b/src/onebot/action/go-cqhttp/UploadGroupFile.ts @@ -7,47 +7,47 @@ import { SendMessageContext } from '@/onebot/api'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - file: Type.String(), - name: Type.String(), - folder: Type.Optional(Type.String()), - folder_id: Type.Optional(Type.String()),//临时扩展 + group_id: Type.Union([Type.Number(), Type.String()]), + file: Type.String(), + name: Type.String(), + folder: Type.Optional(Type.String()), + folder_id: Type.Optional(Type.String()), // 临时扩展 }); type Payload = Static; interface UploadGroupFileResponse { - file_id: string | null; + file_id: string | null; } export default class GoCQHTTPUploadGroupFile extends OneBotAction { - override actionName = ActionName.GoCQHTTP_UploadGroupFile; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_UploadGroupFile; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - let file = payload.file; - if (fs.existsSync(file)) { - file = `file://${file}`; - } - const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, file); - const peer: Peer = { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: payload.group_id.toString(), - }; - if (!downloadResult.success) { - throw new Error(downloadResult.errMsg); - } - const msgContext: SendMessageContext = { - peer: peer, - deleteAfterSentFiles: [] - }; - const sendFileEle = await this.core.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); - - const fileElement = returnMsg.elements.find(ele => ele.elementType === ElementType.FILE); - return { - file_id: fileElement?.fileElement?.fileUuid || null - }; + async _handle (payload: Payload): Promise { + let file = payload.file; + if (fs.existsSync(file)) { + file = `file://${file}`; } + const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, file); + const peer: Peer = { + chatType: ChatType.KCHATTYPEGROUP, + peerUid: payload.group_id.toString(), + }; + if (!downloadResult.success) { + throw new Error(downloadResult.errMsg); + } + const msgContext: SendMessageContext = { + peer, + deleteAfterSentFiles: [], + }; + const sendFileEle = await this.core.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); + + const fileElement = returnMsg.elements.find(ele => ele.elementType === ElementType.FILE); + return { + file_id: fileElement?.fileElement?.fileUuid || null, + }; + } } diff --git a/src/onebot/action/go-cqhttp/UploadPrivateFile.ts b/src/onebot/action/go-cqhttp/UploadPrivateFile.ts index b88cab49..fe94d503 100644 --- a/src/onebot/action/go-cqhttp/UploadPrivateFile.ts +++ b/src/onebot/action/go-cqhttp/UploadPrivateFile.ts @@ -8,56 +8,56 @@ import { ContextMode, createContext } from '@/onebot/action/msg/SendMsg'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.Union([Type.Number(), Type.String()]), - file: Type.String(), - name: Type.String(), + user_id: Type.Union([Type.Number(), Type.String()]), + file: Type.String(), + name: Type.String(), }); type Payload = Static; interface UploadPrivateFileResponse { - file_id: string | null; + file_id: string | null; } export default class GoCQHTTPUploadPrivateFile extends OneBotAction { - override actionName = ActionName.GOCQHTTP_UploadPrivateFile; - override payloadSchema = SchemaData; + override actionName = ActionName.GOCQHTTP_UploadPrivateFile; + override payloadSchema = SchemaData; - async getPeer(payload: Payload): Promise { - if (payload.user_id) { - const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!peerUid) { - throw new Error(`私聊${payload.user_id}不存在`); - } - const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid); - return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid }; - } - throw new Error('缺少参数 user_id'); + async getPeer (payload: Payload): Promise { + if (payload.user_id) { + const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!peerUid) { + throw new Error(`私聊${payload.user_id}不存在`); + } + const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid); + return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid }; + } + throw new Error('缺少参数 user_id'); + } + + async _handle (payload: Payload): Promise { + let file = payload.file; + if (fs.existsSync(file)) { + file = `file://${file}`; + } + const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, file); + if (!downloadResult.success) { + throw new Error(downloadResult.errMsg); } - async _handle(payload: Payload): Promise { - let file = payload.file; - if (fs.existsSync(file)) { - file = `file://${file}`; - } - const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, file); - if (!downloadResult.success) { - throw new Error(downloadResult.errMsg); - } + const msgContext: SendMessageContext = { + peer: await createContext(this.core, { + user_id: payload.user_id.toString(), + }, ContextMode.Private), + deleteAfterSentFiles: [], + }; + const sendFileEle: SendFileElement = await this.core.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); - const msgContext: SendMessageContext = { - peer: await createContext(this.core, { - user_id: payload.user_id.toString() - }, ContextMode.Private), - deleteAfterSentFiles: [] - }; - const sendFileEle: SendFileElement = await this.core.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); - - const fileElement = returnMsg.elements.find(ele => ele.elementType === ElementType.FILE); - return { - file_id: fileElement?.fileElement?.fileUuid || null - }; - } + const fileElement = returnMsg.elements.find(ele => ele.elementType === ElementType.FILE); + return { + file_id: fileElement?.fileElement?.fileUuid || null, + }; + } } diff --git a/src/onebot/action/group/DelEssenceMsg.ts b/src/onebot/action/group/DelEssenceMsg.ts index 1fdfbc67..9cb268bc 100644 --- a/src/onebot/action/group/DelEssenceMsg.ts +++ b/src/onebot/action/group/DelEssenceMsg.ts @@ -4,42 +4,42 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - message_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), - msg_seq: Type.Optional(Type.String()), - msg_random: Type.Optional(Type.String()), - group_id: Type.Optional(Type.String()), + message_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + msg_seq: Type.Optional(Type.String()), + msg_random: Type.Optional(Type.String()), + group_id: Type.Optional(Type.String()), }); type Payload = Static; export default class DelEssenceMsg extends OneBotAction { - override actionName = ActionName.DelEssenceMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.DelEssenceMsg; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - // 如果直接提供了 msg_seq, msg_random, group_id,优先使用 - if (payload.msg_seq && payload.msg_random && payload.group_id) { - return await this.core.apis.GroupApi.removeGroupEssenceBySeq( - payload.group_id, - payload.msg_random, - payload.msg_seq, - ); - } - - // 如果没有 message_id,则必须提供 msg_seq, msg_random, group_id - if (!payload.message_id) { - throw new Error('必须提供 message_id 或者同时提供 msg_seq, msg_random, group_id'); - } - - const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); - if (!msg) { - const data = this.core.apis.GroupApi.essenceLRU.getValue(+payload.message_id); - if(!data) throw new Error('消息不存在'); - const { msg_seq, msg_random, group_id } = JSON.parse(data) as { msg_seq: string, msg_random: string, group_id: string }; - return await this.core.apis.GroupApi.removeGroupEssenceBySeq(group_id, msg_seq, msg_random); - } - return await this.core.apis.GroupApi.removeGroupEssence( - msg.Peer.peerUid, - msg.MsgId, - ); + async _handle (payload: Payload): Promise { + // 如果直接提供了 msg_seq, msg_random, group_id,优先使用 + if (payload.msg_seq && payload.msg_random && payload.group_id) { + return await this.core.apis.GroupApi.removeGroupEssenceBySeq( + payload.group_id, + payload.msg_random, + payload.msg_seq + ); } + + // 如果没有 message_id,则必须提供 msg_seq, msg_random, group_id + if (!payload.message_id) { + throw new Error('必须提供 message_id 或者同时提供 msg_seq, msg_random, group_id'); + } + + const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); + if (!msg) { + const data = this.core.apis.GroupApi.essenceLRU.getValue(+payload.message_id); + if (!data) throw new Error('消息不存在'); + const { msg_seq, msg_random, group_id } = JSON.parse(data) as { msg_seq: string, msg_random: string, group_id: string }; + return await this.core.apis.GroupApi.removeGroupEssenceBySeq(group_id, msg_seq, msg_random); + } + return await this.core.apis.GroupApi.removeGroupEssence( + msg.Peer.peerUid, + msg.MsgId + ); + } } diff --git a/src/onebot/action/group/DelGroupNotice.ts b/src/onebot/action/group/DelGroupNotice.ts index 04f9cbea..f8efacb1 100644 --- a/src/onebot/action/group/DelGroupNotice.ts +++ b/src/onebot/action/group/DelGroupNotice.ts @@ -3,19 +3,19 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - notice_id: Type.String() + group_id: Type.Union([Type.Number(), Type.String()]), + notice_id: Type.String(), }); type Payload = Static; export class DelGroupNotice extends OneBotAction { - override actionName = ActionName.DelGroupNotice; - override payloadSchema = SchemaData; + override actionName = ActionName.DelGroupNotice; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const group = payload.group_id.toString(); - const noticeId = payload.notice_id; - return await this.core.apis.GroupApi.deleteGroupBulletin(group, noticeId); - } + async _handle (payload: Payload) { + const group = payload.group_id.toString(); + const noticeId = payload.notice_id; + return await this.core.apis.GroupApi.deleteGroupBulletin(group, noticeId); + } } diff --git a/src/onebot/action/group/GetAiRecord.ts b/src/onebot/action/group/GetAiRecord.ts index 0eeac7ab..74c22eb5 100644 --- a/src/onebot/action/group/GetAiRecord.ts +++ b/src/onebot/action/group/GetAiRecord.ts @@ -4,22 +4,22 @@ import { AIVoiceChatType } from '@/core/packet/entities/aiChat'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - character: Type.String(), - group_id: Type.Union([Type.Number(), Type.String()]), - text: Type.String(), + character: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + text: Type.String(), }); type Payload = Static; export class GetAiRecord extends GetPacketStatusDepends { - override actionName = ActionName.GetAiRecord; - override payloadSchema = SchemaData; + override actionName = ActionName.GetAiRecord; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound); - if (!rawRsp.msgInfoBody[0]) { - throw new Error('No voice data'); - } - return await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index); + async _handle (payload: Payload) { + const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound); + if (!rawRsp.msgInfoBody[0]) { + throw new Error('No voice data'); } + return await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index); + } } diff --git a/src/onebot/action/group/GetGroupDetailInfo.ts b/src/onebot/action/group/GetGroupDetailInfo.ts index 14074b6f..0b50cb48 100644 --- a/src/onebot/action/group/GetGroupDetailInfo.ts +++ b/src/onebot/action/group/GetGroupDetailInfo.ts @@ -3,25 +3,25 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class GetGroupDetailInfo extends OneBotAction { - override actionName = ActionName.GetGroupDetailInfo; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupDetailInfo; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString()); - return { - ...data, - group_all_shut: data.shutUpAllTimestamp > 0 ? -1 : 0, - group_remark: '', - group_id: +payload.group_id, - group_name: data.groupName, - member_count: data.memberNum, - max_member_count: data.maxMemberNum, - }; - } -} \ No newline at end of file + async _handle (payload: Payload) { + const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString()); + return { + ...data, + group_all_shut: data.shutUpAllTimestamp > 0 ? -1 : 0, + group_remark: '', + group_id: +payload.group_id, + group_name: data.groupName, + member_count: data.memberNum, + max_member_count: data.maxMemberNum, + }; + } +} diff --git a/src/onebot/action/group/GetGroupEssence.ts b/src/onebot/action/group/GetGroupEssence.ts index 8859599e..9fd525e1 100644 --- a/src/onebot/action/group/GetGroupEssence.ts +++ b/src/onebot/action/group/GetGroupEssence.ts @@ -7,93 +7,93 @@ import { Static, Type } from '@sinclair/typebox'; import { NetworkAdapterConfig } from '@/onebot/config/config'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class GetGroupEssence extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetEssenceMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetEssenceMsg; + override payloadSchema = SchemaData; - private async msgSeqToMsgId(peer: Peer, msgSeq: string, msgRandom: string) { - const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, msgSeq, 1, true, true)).msgList.find((msg) => msg.msgSeq === msgSeq && msg.msgRandom === msgRandom); - if (!replyMsgList) { - return undefined; - } + private async msgSeqToMsgId (peer: Peer, msgSeq: string, msgRandom: string) { + const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, msgSeq, 1, true, true)).msgList.find((msg) => msg.msgSeq === msgSeq && msg.msgRandom === msgRandom); + if (!replyMsgList) { + return undefined; + } + return { + id: MessageUnique.createUniqueMsgId(peer, replyMsgList.msgId), + msg: replyMsgList, + }; + } + + async _handle (payload: Payload, _adapter: string, config: NetworkAdapterConfig) { + const msglist = (await this.core.apis.WebApi.getGroupEssenceMsgAll(payload.group_id.toString())) + .flatMap((e) => e?.data?.msg_list) + // 在群精华回空的时候会出现[null]的情况~ https://github.com/NapNeko/NapCatQQ/issues/1334 + .filter(Boolean); + if (!msglist) { + throw new Error('获取失败'); + } + return await Promise.all(msglist.map(async (msg) => { + const msgOriginData = await this.msgSeqToMsgId({ + chatType: ChatType.KCHATTYPEGROUP, + peerUid: payload.group_id.toString(), + }, msg.msg_seq.toString(), msg.msg_random.toString()); + if (msgOriginData) { + const { id: message_id, msg: rawMessage } = msgOriginData; return { - id: MessageUnique.createUniqueMsgId(peer, replyMsgList.msgId), - msg: replyMsgList + msg_seq: msg.msg_seq, + msg_random: msg.msg_random, + sender_id: +msg.sender_uin, + sender_nick: msg.sender_nick, + operator_id: +msg.add_digest_uin, + operator_nick: msg.add_digest_nick, + message_id, + operator_time: msg.add_digest_time, + content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, config.messagePostFormat))?.message, }; - } - - async _handle(payload: Payload, _adapter: string, config: NetworkAdapterConfig) { - const msglist = (await this.core.apis.WebApi.getGroupEssenceMsgAll(payload.group_id.toString())) - .flatMap((e) => e?.data?.msg_list) - // 在群精华回空的时候会出现[null]的情况~ https://github.com/NapNeko/NapCatQQ/issues/1334 - .filter(Boolean); - if (!msglist) { - throw new Error('获取失败'); - } - return await Promise.all(msglist.map(async (msg) => { - const msgOriginData = await this.msgSeqToMsgId({ - chatType: ChatType.KCHATTYPEGROUP, - peerUid: payload.group_id.toString(), - }, msg.msg_seq.toString(), msg.msg_random.toString()); - if (msgOriginData) { - const { id: message_id, msg: rawMessage } = msgOriginData; - return { - msg_seq: msg.msg_seq, - msg_random: msg.msg_random, - sender_id: +msg.sender_uin, - sender_nick: msg.sender_nick, - operator_id: +msg.add_digest_uin, - operator_nick: msg.add_digest_nick, - message_id: message_id, - operator_time: msg.add_digest_time, - content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, config.messagePostFormat))?.message - }; - } - const msgTempData = JSON.stringify({ - msg_seq: msg.msg_seq.toString(), - msg_random: msg.msg_random.toString(), - group_id: payload.group_id.toString(), - }); - const hash = crypto.createHash('md5').update(msgTempData).digest(); - //设置第一个bit为0 保证shortId为正数 - if(hash[0]){ - hash[0] &= 0x7f; - } - const shortId = hash.readInt32BE(0); - this.core.apis.GroupApi.essenceLRU.set(shortId, msgTempData); + } + const msgTempData = JSON.stringify({ + msg_seq: msg.msg_seq.toString(), + msg_random: msg.msg_random.toString(), + group_id: payload.group_id.toString(), + }); + const hash = crypto.createHash('md5').update(msgTempData).digest(); + // 设置第一个bit为0 保证shortId为正数 + if (hash[0]) { + hash[0] &= 0x7f; + } + const shortId = hash.readInt32BE(0); + this.core.apis.GroupApi.essenceLRU.set(shortId, msgTempData); + return { + msg_seq: msg.msg_seq, + msg_random: msg.msg_random, + sender_id: +msg.sender_uin, + sender_nick: msg.sender_nick, + operator_id: +msg.add_digest_uin, + operator_nick: msg.add_digest_nick, + message_id: shortId, + operator_time: msg.add_digest_time, + content: msg.msg_content.map((msg) => { + if (msg.msg_type === 1) { return { - msg_seq: msg.msg_seq, - msg_random: msg.msg_random, - sender_id: +msg.sender_uin, - sender_nick: msg.sender_nick, - operator_id: +msg.add_digest_uin, - operator_nick: msg.add_digest_nick, - message_id: shortId, - operator_time: msg.add_digest_time, - content: msg.msg_content.map((msg) => { - if (msg.msg_type === 1) { - return { - type: 'text', - data: { - text: msg?.text - } - }; - } else if (msg.msg_type === 3) { - return { - type: 'image', - data: { - url: msg?.image_url, - } - }; - } - return undefined; - }).filter(e => e !== undefined), + type: 'text', + data: { + text: msg?.text, + }, }; - })); - } + } else if (msg.msg_type === 3) { + return { + type: 'image', + data: { + url: msg?.image_url, + }, + }; + } + return undefined; + }).filter(e => e !== undefined), + }; + })); + } } diff --git a/src/onebot/action/group/GetGroupIgnoredNotifies.ts b/src/onebot/action/group/GetGroupIgnoredNotifies.ts index a37600a8..2b9cb94c 100644 --- a/src/onebot/action/group/GetGroupIgnoredNotifies.ts +++ b/src/onebot/action/group/GetGroupIgnoredNotifies.ts @@ -4,42 +4,42 @@ import { ActionName } from '@/onebot/action/router'; import { Notify } from '@/onebot/types'; interface RetData { - invited_requests: Notify[]; - InvitedRequest: Notify[]; - join_requests: Notify[]; + invited_requests: Notify[]; + InvitedRequest: Notify[]; + join_requests: Notify[]; } export class GetGroupIgnoredNotifies extends OneBotAction { - override actionName = ActionName.GetGroupIgnoredNotifies; + override actionName = ActionName.GetGroupIgnoredNotifies; - async _handle(): Promise { - const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50); - const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] }; + async _handle (): Promise { + const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50); + const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] }; - const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => { - const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0; - const actorUin = SSNotify.user2?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2.uid) : 0; - const commonData = { - request_id: +SSNotify.seq, - invitor_uin: invitorUin, - invitor_nick: SSNotify.user1?.nickName, - group_id: +SSNotify.group?.groupCode, - message: SSNotify?.postscript, - group_name: SSNotify.group?.groupName, - checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE, - actor: actorUin, - requester_nick: SSNotify.user1?.nickName, - }; + const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => { + const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0; + const actorUin = SSNotify.user2?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2.uid) : 0; + const commonData = { + request_id: +SSNotify.seq, + invitor_uin: invitorUin, + invitor_nick: SSNotify.user1?.nickName, + group_id: +SSNotify.group?.groupCode, + message: SSNotify?.postscript, + group_name: SSNotify.group?.groupName, + checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE, + actor: actorUin, + requester_nick: SSNotify.user1?.nickName, + }; - if (SSNotify.type === 1) { - retData.InvitedRequest.push(commonData); - } else if (SSNotify.type === 7) { - retData.join_requests.push(commonData); - } - }); + if (SSNotify.type === 1) { + retData.InvitedRequest.push(commonData); + } else if (SSNotify.type === 7) { + retData.join_requests.push(commonData); + } + }); - await Promise.all(notifyPromises); - retData.invited_requests = retData.InvitedRequest; - return retData; - } -} \ No newline at end of file + await Promise.all(notifyPromises); + retData.invited_requests = retData.InvitedRequest; + return retData; + } +} diff --git a/src/onebot/action/group/GetGroupInfo.ts b/src/onebot/action/group/GetGroupInfo.ts index 106a2930..6131cc6e 100644 --- a/src/onebot/action/group/GetGroupInfo.ts +++ b/src/onebot/action/group/GetGroupInfo.ts @@ -5,34 +5,34 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; class GetGroupInfo extends OneBotAction { - override actionName = ActionName.GetGroupInfo; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupInfo; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const group = (await this.core.apis.GroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString()); - if (!group) { - const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString()); - if (data.ownerUid && data.ownerUin === '0') { - data.ownerUin = await this.core.apis.UserApi.getUinByUidV2(data.ownerUid); - } - return { - ...data, - group_all_shut: data.shutUpAllTimestamp > 0 ? -1 : 0, - group_remark: '', - group_id: +payload.group_id, - group_name: data.groupName, - member_count: data.memberNum, - max_member_count: data.maxMemberNum, - }; - } - return OB11Construct.group(group); + async _handle (payload: Payload) { + const group = (await this.core.apis.GroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString()); + if (!group) { + const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString()); + if (data.ownerUid && data.ownerUin === '0') { + data.ownerUin = await this.core.apis.UserApi.getUinByUidV2(data.ownerUid); + } + return { + ...data, + group_all_shut: data.shutUpAllTimestamp > 0 ? -1 : 0, + group_remark: '', + group_id: +payload.group_id, + group_name: data.groupName, + member_count: data.memberNum, + max_member_count: data.maxMemberNum, + }; } + return OB11Construct.group(group); + } } export default GetGroupInfo; diff --git a/src/onebot/action/group/GetGroupList.ts b/src/onebot/action/group/GetGroupList.ts index 251b79aa..c4e8ba9c 100644 --- a/src/onebot/action/group/GetGroupList.ts +++ b/src/onebot/action/group/GetGroupList.ts @@ -5,20 +5,20 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; class GetGroupList extends OneBotAction { - override actionName = ActionName.GetGroupList; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupList; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return OB11Construct.groups( - await this.core.apis.GroupApi.getGroups( - typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache)); - } + async _handle (payload: Payload) { + return OB11Construct.groups( + await this.core.apis.GroupApi.getGroups( + typeof payload.no_cache === 'string' ? payload.no_cache === 'true' : !!payload.no_cache)); + } } export default GetGroupList; diff --git a/src/onebot/action/group/GetGroupMemberInfo.ts b/src/onebot/action/group/GetGroupMemberInfo.ts index 03938f27..f9cce558 100644 --- a/src/onebot/action/group/GetGroupMemberInfo.ts +++ b/src/onebot/action/group/GetGroupMemberInfo.ts @@ -5,52 +5,52 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - user_id: Type.Union([Type.Number(), Type.String()]), - no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + group_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), + no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; class GetGroupMemberInfo extends OneBotAction { - override actionName = ActionName.GetGroupMemberInfo; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupMemberInfo; + override payloadSchema = SchemaData; - private parseBoolean(value: boolean | string): boolean { - return typeof value === 'string' ? value === 'true' : value; + private parseBoolean (value: boolean | string): boolean { + return typeof value === 'string' ? value === 'true' : value; + } + + private async getUid (userId: string | number): Promise { + const uid = await this.core.apis.UserApi.getUidByUinV2(userId.toString()); + if (!uid) throw new Error(`Uin2Uid Error: 用户ID ${userId} 不存在`); + return uid; + } + + private async getGroupMemberInfo (payload: Payload, uid: string, isNocache: boolean) { + const groupMemberCache = this.core.apis.GroupApi.groupMemberCache.get(payload.group_id.toString()); + const groupMember = groupMemberCache?.get(uid); + + const [member, info] = await Promise.all([ + this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, isNocache), + this.core.apis.UserApi.getUserDetailInfo(uid, isNocache), + ]); + + if (!member || !groupMember) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`); + + return info ? { ...groupMember, ...member, ...info } : member; + } + + async _handle (payload: Payload) { + const isNocache = this.parseBoolean(payload.no_cache ?? true); + const uid = await this.getUid(payload.user_id); + const member = await this.getGroupMemberInfo(payload, uid, isNocache); + + if (!member) { + this.core.context.logger.logDebug('获取群成员详细信息失败, 只能返回基础信息'); } - private async getUid(userId: string | number): Promise { - const uid = await this.core.apis.UserApi.getUidByUinV2(userId.toString()); - if (!uid) throw new Error(`Uin2Uid Error: 用户ID ${userId} 不存在`); - return uid; - } - - private async getGroupMemberInfo(payload: Payload, uid: string, isNocache: boolean) { - const groupMemberCache = this.core.apis.GroupApi.groupMemberCache.get(payload.group_id.toString()); - const groupMember = groupMemberCache?.get(uid); - - const [member, info] = await Promise.all([ - this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, isNocache), - this.core.apis.UserApi.getUserDetailInfo(uid, isNocache), - ]); - - if (!member || !groupMember) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`); - - return info ? { ...groupMember, ...member, ...info } : member; - } - - async _handle(payload: Payload) { - const isNocache = this.parseBoolean(payload.no_cache ?? true); - const uid = await this.getUid(payload.user_id); - const member = await this.getGroupMemberInfo(payload, uid, isNocache); - - if (!member) { - this.core.context.logger.logDebug('获取群成员详细信息失败, 只能返回基础信息'); - } - - return OB11Construct.groupMember(payload.group_id.toString(), member); - } + return OB11Construct.groupMember(payload.group_id.toString(), member); + } } -export default GetGroupMemberInfo; \ No newline at end of file +export default GetGroupMemberInfo; diff --git a/src/onebot/action/group/GetGroupMemberList.ts b/src/onebot/action/group/GetGroupMemberList.ts index 41e028bf..9ed73e4d 100644 --- a/src/onebot/action/group/GetGroupMemberList.ts +++ b/src/onebot/action/group/GetGroupMemberList.ts @@ -6,44 +6,44 @@ import { Static, Type } from '@sinclair/typebox'; import { GroupMember } from '@/core'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])) + group_id: Type.Union([Type.Number(), Type.String()]), + no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export class GetGroupMemberList extends OneBotAction { - override actionName = ActionName.GetGroupMemberList; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupMemberList; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const groupIdStr = payload.group_id.toString(); - const noCache = this.parseBoolean(payload.no_cache ?? false); - const groupMembers = await this.getGroupMembers(groupIdStr, noCache); - const _groupMembers = await Promise.all( - Array.from(groupMembers.values()).map(item => - OB11Construct.groupMember(groupIdStr, item) - ) - ); - return Array.from(new Map(_groupMembers.map(member => [member.user_id, member])).values()); + async _handle (payload: Payload) { + const groupIdStr = payload.group_id.toString(); + const noCache = this.parseBoolean(payload.no_cache ?? false); + const groupMembers = await this.getGroupMembers(groupIdStr, noCache); + const _groupMembers = await Promise.all( + Array.from(groupMembers.values()).map(item => + OB11Construct.groupMember(groupIdStr, item) + ) + ); + return Array.from(new Map(_groupMembers.map(member => [member.user_id, member])).values()); + } + + private parseBoolean (value: boolean | string): boolean { + return typeof value === 'string' ? value === 'true' : value; + } + + private async getGroupMembers (groupIdStr: string, noCache: boolean): Promise> { + const memberCache = this.core.apis.GroupApi.groupMemberCache; + let groupMembers = memberCache.get(groupIdStr); + + if (noCache || !groupMembers) { + const data = this.core.apis.GroupApi.refreshGroupMemberCache(groupIdStr, true).then().catch(); + groupMembers = memberCache.get(groupIdStr) || (await data); + if (!groupMembers) { + throw new Error(`Failed to get group member list for group ${groupIdStr}`); + } } - private parseBoolean(value: boolean | string): boolean { - return typeof value === 'string' ? value === 'true' : value; - } - - private async getGroupMembers(groupIdStr: string, noCache: boolean): Promise> { - const memberCache = this.core.apis.GroupApi.groupMemberCache; - let groupMembers = memberCache.get(groupIdStr); - - if (noCache || !groupMembers) { - const data = this.core.apis.GroupApi.refreshGroupMemberCache(groupIdStr, true).then().catch(); - groupMembers = memberCache.get(groupIdStr) || (await data); - if (!groupMembers) { - throw new Error(`Failed to get group member list for group ${groupIdStr}`); - } - } - - return groupMembers; - } -} \ No newline at end of file + return groupMembers; + } +} diff --git a/src/onebot/action/group/GetGroupNotice.ts b/src/onebot/action/group/GetGroupNotice.ts index a98470bb..702cbbcb 100644 --- a/src/onebot/action/group/GetGroupNotice.ts +++ b/src/onebot/action/group/GetGroupNotice.ts @@ -3,27 +3,27 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; interface GroupNotice { - sender_id: number; - publish_time: number; - notice_id: string; - message: { - text: string - // 保持一段时间兼容性 防止以往版本出现问题 后续版本可考虑移除 - image: Array<{ - height: string - width: string - id: string - }>, - images: Array<{ - height: string - width: string - id: string - }> - }; + sender_id: number; + publish_time: number; + notice_id: string; + message: { + text: string + // 保持一段时间兼容性 防止以往版本出现问题 后续版本可考虑移除 + image: Array<{ + height: string + width: string + id: string + }>, + images: Array<{ + height: string + width: string + id: string + }> + }; } const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; @@ -31,38 +31,38 @@ type Payload = Static; type ApiGroupNotice = GroupNotice & WebApiGroupNoticeFeed; export class GetGroupNotice extends OneBotAction { - override actionName = ActionName.GoCQHTTP_GetGroupNotice; - override payloadSchema = SchemaData; + override actionName = ActionName.GoCQHTTP_GetGroupNotice; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const group = payload.group_id.toString(); - const ret = await this.core.apis.WebApi.getGroupNotice(group); - if (!ret) { - throw new Error('获取公告失败'); - } - const retNotices: GroupNotice[] = new Array(); - for (const key in ret.feeds) { - if (!ret.feeds[key]) { - continue; - } - const retApiNotice: WebApiGroupNoticeFeed = ret.feeds[key]; - const image = retApiNotice.msg.pics?.map((pic) => { - return { id: pic.id, height: pic.h, width: pic.w }; - }) || []; - - const retNotice: GroupNotice = { - notice_id: retApiNotice.fid, - sender_id: retApiNotice.u, - publish_time: retApiNotice.pubt, - message: { - text: retApiNotice.msg.text, - image, - images: image, - }, - }; - retNotices.push(retNotice); - } - - return retNotices; + async _handle (payload: Payload) { + const group = payload.group_id.toString(); + const ret = await this.core.apis.WebApi.getGroupNotice(group); + if (!ret) { + throw new Error('获取公告失败'); } + const retNotices: GroupNotice[] = new Array(); + for (const key in ret.feeds) { + if (!ret.feeds[key]) { + continue; + } + const retApiNotice: WebApiGroupNoticeFeed = ret.feeds[key]; + const image = retApiNotice.msg.pics?.map((pic) => { + return { id: pic.id, height: pic.h, width: pic.w }; + }) || []; + + const retNotice: GroupNotice = { + notice_id: retApiNotice.fid, + sender_id: retApiNotice.u, + publish_time: retApiNotice.pubt, + message: { + text: retApiNotice.msg.text, + image, + images: image, + }, + }; + retNotices.push(retNotice); + } + + return retNotices; + } } diff --git a/src/onebot/action/group/GetGroupShutList.ts b/src/onebot/action/group/GetGroupShutList.ts index 05ab27de..046cf9c1 100644 --- a/src/onebot/action/group/GetGroupShutList.ts +++ b/src/onebot/action/group/GetGroupShutList.ts @@ -4,17 +4,16 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export class GetGroupShutList extends OneBotAction { - override actionName = ActionName.GetGroupShutList; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupShutList; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.GroupApi.getGroupShutUpMemberList(payload.group_id.toString()); - } + async _handle (payload: Payload) { + return await this.core.apis.GroupApi.getGroupShutUpMemberList(payload.group_id.toString()); + } } - diff --git a/src/onebot/action/group/SendGroupAiRecord.ts b/src/onebot/action/group/SendGroupAiRecord.ts index 7cfd5f85..7afb22e1 100644 --- a/src/onebot/action/group/SendGroupAiRecord.ts +++ b/src/onebot/action/group/SendGroupAiRecord.ts @@ -4,24 +4,23 @@ import { AIVoiceChatType } from '@/core/packet/entities/aiChat'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - character: Type.String(), - group_id: Type.Union([Type.Number(), Type.String()]), - text: Type.String(), + character: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + text: Type.String(), }); type Payload = Static; - export class SendGroupAiRecord extends GetPacketStatusDepends { - override actionName = ActionName.SendGroupAiRecord; - override payloadSchema = SchemaData; + override actionName = ActionName.SendGroupAiRecord; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound); - return { - message_id: 0 // can't get message_id from GetAiVoice - }; - } + async _handle (payload: Payload) { + await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound); + return { + message_id: 0, // can't get message_id from GetAiVoice + }; + } } diff --git a/src/onebot/action/group/SendGroupMsg.ts b/src/onebot/action/group/SendGroupMsg.ts index 31604c21..f68a48ec 100644 --- a/src/onebot/action/group/SendGroupMsg.ts +++ b/src/onebot/action/group/SendGroupMsg.ts @@ -4,16 +4,17 @@ import { OB11PostSendMsg } from '@/onebot/types'; // 未检测参数 class SendGroupMsg extends SendMsgBase { - override actionName = ActionName.SendGroupMsg; + override actionName = ActionName.SendGroupMsg; - protected override async check(payload: OB11PostSendMsg): Promise { - delete payload.user_id; - payload.message_type = 'group'; - return super.check(payload); - } - override async _handle(payload: OB11PostSendMsg): Promise { - return this.base_handle(payload, ContextMode.Group); - } + protected override async check (payload: OB11PostSendMsg): Promise { + delete payload.user_id; + payload.message_type = 'group'; + return super.check(payload); + } + + override async _handle (payload: OB11PostSendMsg): Promise { + return this.base_handle(payload, ContextMode.Group); + } } export default SendGroupMsg; diff --git a/src/onebot/action/group/SetEssenceMsg.ts b/src/onebot/action/group/SetEssenceMsg.ts index 6578d641..91389db5 100644 --- a/src/onebot/action/group/SetEssenceMsg.ts +++ b/src/onebot/action/group/SetEssenceMsg.ts @@ -4,23 +4,23 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), + message_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export default class SetEssenceMsg extends OneBotAction { - override actionName = ActionName.SetEssenceMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.SetEssenceMsg; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); - if (!msg) { - throw new Error('msg not found'); - } - return await this.core.apis.GroupApi.addGroupEssence( - msg.Peer.peerUid, - msg.MsgId, - ); + async _handle (payload: Payload) { + const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); + if (!msg) { + throw new Error('msg not found'); } + return await this.core.apis.GroupApi.addGroupEssence( + msg.Peer.peerUid, + msg.MsgId + ); + } } diff --git a/src/onebot/action/group/SetGroupAddRequest.ts b/src/onebot/action/group/SetGroupAddRequest.ts index bbf16aeb..c080b1da 100644 --- a/src/onebot/action/group/SetGroupAddRequest.ts +++ b/src/onebot/action/group/SetGroupAddRequest.ts @@ -4,49 +4,51 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - flag: Type.Union([Type.String(), Type.Number()]), - approve: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), - reason: Type.Optional(Type.Union([Type.String({ default: ' ' }), Type.Null()])), - count: Type.Optional(Type.Number({ default: 100 })), + flag: Type.Union([Type.String(), Type.Number()]), + approve: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + reason: Type.Optional(Type.Union([Type.String({ default: ' ' }), Type.Null()])), + count: Type.Optional(Type.Number({ default: 100 })), }); type Payload = Static; export default class SetGroupAddRequest extends OneBotAction { - override actionName = ActionName.SetGroupAddRequest; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupAddRequest; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const flag = payload.flag.toString(); - const approve = payload.approve?.toString() !== 'false'; - const reason = payload.reason ?? ' '; - const count = payload.count; - const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(flag); - const { doubt, notify } = invite_notify ? { - doubt: false, - notify: invite_notify, - } : await this.findNotify(flag, count); - if (!notify) { - throw new Error('No such request'); - } - await this.core.apis.GroupApi.handleGroupRequest( - doubt, - notify, - approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE, - reason, - ); - return null; + async _handle (payload: Payload): Promise { + const flag = payload.flag.toString(); + const approve = payload.approve?.toString() !== 'false'; + const reason = payload.reason ?? ' '; + const count = payload.count; + const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(flag); + const { doubt, notify } = invite_notify + ? { + doubt: false, + notify: invite_notify, + } + : await this.findNotify(flag, count); + if (!notify) { + throw new Error('No such request'); } + await this.core.apis.GroupApi.handleGroupRequest( + doubt, + notify, + approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE, + reason + ); + return null; + } - private async findNotify(flag: string, count: number = 100): Promise<{ - doubt: boolean, - notify: GroupNotify | undefined - }> { - let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, count)).find(e => e.seq == flag); - if (!notify) { - notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, count)).find(e => e.seq == flag); - return { doubt: true, notify }; - } - return { doubt: false, notify }; + private async findNotify (flag: string, count: number = 100): Promise<{ + doubt: boolean, + notify: GroupNotify | undefined + }> { + let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, count)).find(e => e.seq == flag); + if (!notify) { + notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, count)).find(e => e.seq == flag); + return { doubt: true, notify }; } + return { doubt: false, notify }; + } } diff --git a/src/onebot/action/group/SetGroupAdmin.ts b/src/onebot/action/group/SetGroupAdmin.ts index ee70edfb..a313aa4b 100644 --- a/src/onebot/action/group/SetGroupAdmin.ts +++ b/src/onebot/action/group/SetGroupAdmin.ts @@ -4,22 +4,22 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - user_id: Type.Union([Type.Number(), Type.String()]), - enable: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + group_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), + enable: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export default class SetGroupAdmin extends OneBotAction { - override actionName = ActionName.SetGroupAdmin; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupAdmin; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const enable = typeof payload.enable === 'string' ? payload.enable === 'true' : !!payload.enable; - const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!uid) throw new Error('get Uid Error'); - await this.core.apis.GroupApi.setMemberRole(payload.group_id.toString(), uid, enable ? NTGroupMemberRole.KADMIN : NTGroupMemberRole.KMEMBER); - return null; - } + async _handle (payload: Payload): Promise { + const enable = typeof payload.enable === 'string' ? payload.enable === 'true' : !!payload.enable; + const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!uid) throw new Error('get Uid Error'); + await this.core.apis.GroupApi.setMemberRole(payload.group_id.toString(), uid, enable ? NTGroupMemberRole.KADMIN : NTGroupMemberRole.KMEMBER); + return null; + } } diff --git a/src/onebot/action/group/SetGroupBan.ts b/src/onebot/action/group/SetGroupBan.ts index 3f30aa1f..4bc4241a 100644 --- a/src/onebot/action/group/SetGroupBan.ts +++ b/src/onebot/action/group/SetGroupBan.ts @@ -3,25 +3,25 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - user_id: Type.Union([Type.Number(), Type.String()]), - duration: Type.Union([Type.Number(), Type.String()], { default: 0 }), + group_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), + duration: Type.Union([Type.Number(), Type.String()], { default: 0 }), }); type Payload = Static; export default class SetGroupBan extends OneBotAction { - override actionName = ActionName.SetGroupBan; - override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!uid) throw new Error('uid error'); - let member_role = (await this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, true))?.role; - if (member_role === 4) throw new Error('cannot ban owner'); - // 例如无管理员权限时 result为 120101005 errMsg为 'ERR_NOT_GROUP_ADMIN' - let ret = await this.core.apis.GroupApi.banMember(payload.group_id.toString(), - [{ uid: uid, timeStamp: +payload.duration }]); - if (ret.result !== 0) throw new Error(ret.errMsg); - return null; - } + override actionName = ActionName.SetGroupBan; + override payloadSchema = SchemaData; + async _handle (payload: Payload): Promise { + const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!uid) throw new Error('uid error'); + const member_role = (await this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, true))?.role; + if (member_role === 4) throw new Error('cannot ban owner'); + // 例如无管理员权限时 result为 120101005 errMsg为 'ERR_NOT_GROUP_ADMIN' + const ret = await this.core.apis.GroupApi.banMember(payload.group_id.toString(), + [{ uid, timeStamp: +payload.duration }]); + if (ret.result !== 0) throw new Error(ret.errMsg); + return null; + } } diff --git a/src/onebot/action/group/SetGroupCard.ts b/src/onebot/action/group/SetGroupCard.ts index 2e825c68..43adda1d 100644 --- a/src/onebot/action/group/SetGroupCard.ts +++ b/src/onebot/action/group/SetGroupCard.ts @@ -3,20 +3,20 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - user_id: Type.Union([Type.Number(), Type.String()]), - card: Type.Optional(Type.String()) + group_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), + card: Type.Optional(Type.String()), }); type Payload = Static; export default class SetGroupCard extends OneBotAction { - override actionName = ActionName.SetGroupCard; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupCard; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const member = await this.core.apis.GroupApi.getGroupMember(payload.group_id.toString(), payload.user_id.toString()); - if (member) await this.core.apis.GroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || ''); - return null; - } + async _handle (payload: Payload): Promise { + const member = await this.core.apis.GroupApi.getGroupMember(payload.group_id.toString(), payload.user_id.toString()); + if (member) await this.core.apis.GroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || ''); + return null; + } } diff --git a/src/onebot/action/group/SetGroupKick.ts b/src/onebot/action/group/SetGroupKick.ts index 7b57d48c..f2f10f8c 100644 --- a/src/onebot/action/group/SetGroupKick.ts +++ b/src/onebot/action/group/SetGroupKick.ts @@ -3,22 +3,22 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - user_id: Type.Union([Type.Number(), Type.String()]), - reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + group_id: Type.Union([Type.Number(), Type.String()]), + user_id: Type.Union([Type.Number(), Type.String()]), + reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export default class SetGroupKick extends OneBotAction { - override actionName = ActionName.SetGroupKick; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupKick; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const rejectReq = payload.reject_add_request?.toString() == 'true'; - const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!uid) throw new Error('get Uid Error'); - await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), [uid], rejectReq); - return null; - } + async _handle (payload: Payload): Promise { + const rejectReq = payload.reject_add_request?.toString() == 'true'; + const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!uid) throw new Error('get Uid Error'); + await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), [uid], rejectReq); + return null; + } } diff --git a/src/onebot/action/group/SetGroupLeave.ts b/src/onebot/action/group/SetGroupLeave.ts index 4d9a4083..91b1698d 100644 --- a/src/onebot/action/group/SetGroupLeave.ts +++ b/src/onebot/action/group/SetGroupLeave.ts @@ -3,17 +3,17 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - is_dismiss: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + group_id: Type.Union([Type.Number(), Type.String()]), + is_dismiss: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export default class SetGroupLeave extends OneBotAction { - override actionName = ActionName.SetGroupLeave; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupLeave; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - await this.core.apis.GroupApi.quitGroup(payload.group_id.toString()); - } + async _handle (payload: Payload): Promise { + await this.core.apis.GroupApi.quitGroup(payload.group_id.toString()); + } } diff --git a/src/onebot/action/group/SetGroupName.ts b/src/onebot/action/group/SetGroupName.ts index cde09908..4958ebfb 100644 --- a/src/onebot/action/group/SetGroupName.ts +++ b/src/onebot/action/group/SetGroupName.ts @@ -1,24 +1,23 @@ - import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - group_name: Type.String(), + group_id: Type.Union([Type.Number(), Type.String()]), + group_name: Type.String(), }); type Payload = Static; export default class SetGroupName extends OneBotAction { - override actionName = ActionName.SetGroupName; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupName; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const ret = await this.core.apis.GroupApi.setGroupName(payload.group_id.toString(), payload.group_name); - if (ret.result !== 0) { - throw new Error(`设置群名称失败 ErrCode: ${ret.result} ErrMsg: ${ret.errMsg}`); - } - return null; + async _handle (payload: Payload): Promise { + const ret = await this.core.apis.GroupApi.setGroupName(payload.group_id.toString(), payload.group_name); + if (ret.result !== 0) { + throw new Error(`设置群名称失败 ErrCode: ${ret.result} ErrMsg: ${ret.errMsg}`); } + return null; + } } diff --git a/src/onebot/action/group/SetGroupWholeBan.ts b/src/onebot/action/group/SetGroupWholeBan.ts index 3ff633ca..4f313e32 100644 --- a/src/onebot/action/group/SetGroupWholeBan.ts +++ b/src/onebot/action/group/SetGroupWholeBan.ts @@ -3,22 +3,22 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Union([Type.Number(), Type.String()]), - enable: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + group_id: Type.Union([Type.Number(), Type.String()]), + enable: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export default class SetGroupWholeBan extends OneBotAction { - override actionName = ActionName.SetGroupWholeBan; - override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupWholeBan; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const enable = payload.enable?.toString() !== 'false'; - let res = await this.core.apis.GroupApi.banGroup(payload.group_id.toString(), enable); - if (res.result !== 0) { - throw new Error(`SetGroupWholeBan failed: ${res.errMsg} ${res.result}`); - } - return null; + async _handle (payload: Payload): Promise { + const enable = payload.enable?.toString() !== 'false'; + const res = await this.core.apis.GroupApi.banGroup(payload.group_id.toString(), enable); + if (res.result !== 0) { + throw new Error(`SetGroupWholeBan failed: ${res.errMsg} ${res.result}`); } + return null; + } } diff --git a/src/onebot/action/guild/GetGuildList.ts b/src/onebot/action/guild/GetGuildList.ts index a125c67e..92b14dd2 100644 --- a/src/onebot/action/guild/GetGuildList.ts +++ b/src/onebot/action/guild/GetGuildList.ts @@ -2,9 +2,9 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; export class GetGuildList extends OneBotAction { - override actionName = ActionName.GetGuildList; + override actionName = ActionName.GetGuildList; - async _handle(): Promise { - return; - } + async _handle (): Promise { + + } } diff --git a/src/onebot/action/guild/GetGuildProfile.ts b/src/onebot/action/guild/GetGuildProfile.ts index 2dc40c29..e1916eed 100644 --- a/src/onebot/action/guild/GetGuildProfile.ts +++ b/src/onebot/action/guild/GetGuildProfile.ts @@ -2,9 +2,9 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; export class GetGuildProfile extends OneBotAction { - override actionName = ActionName.GetGuildProfile; + override actionName = ActionName.GetGuildProfile; - async _handle(): Promise { - return; - } + async _handle (): Promise { + + } } diff --git a/src/onebot/action/index.ts b/src/onebot/action/index.ts index b77ada21..c79e2431 100644 --- a/src/onebot/action/index.ts +++ b/src/onebot/action/index.ts @@ -13,9 +13,9 @@ import CanSendRecord from './system/CanSendRecord'; import CanSendImage from './system/CanSendImage'; import GetStatus from './system/GetStatus'; import { - GoCQHTTPSendForwardMsg, - GoCQHTTPSendGroupForwardMsg, - GoCQHTTPSendPrivateForwardMsg, + GoCQHTTPSendForwardMsg, + GoCQHTTPSendGroupForwardMsg, + GoCQHTTPSendPrivateForwardMsg, } from './go-cqhttp/SendForwardMsg'; import GoCQHTTPGetStrangerInfo from './go-cqhttp/GetStrangerInfo'; import SendLike from './user/SendLike'; @@ -137,182 +137,181 @@ import { DownloadFileImageStream } from './stream/DownloadFileImageStream'; import { TestDownloadStream } from './stream/TestStreamDownload'; import { UploadFileStream } from './stream/UploadFileStream'; -export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - - const actionHandlers = [ - new CleanStreamTempFile(obContext, core), - new DownloadFileStream(obContext, core), - new DownloadFileRecordStream(obContext, core), - new DownloadFileImageStream(obContext, core), - new TestDownloadStream(obContext, core), - new UploadFileStream(obContext, core), - new DelGroupAlbumMedia(obContext, core), - new SetGroupAlbumMediaLike(obContext, core), - new DoGroupAlbumComment(obContext, core), - new GetGroupAlbumMediaList(obContext, core), - new GetQunAlbumList(obContext, core), - new UploadImageToQunAlbum(obContext, core), - new SetGroupTodo(obContext, core), - new GetGroupDetailInfo(obContext, core), - new SetGroupKickMembers(obContext, core), - new SetGroupAddOption(obContext, core), - new SetGroupRobotAddOption(obContext, core), - new SetGroupSearch(obContext, core), - new SetDoubtFriendsAddRequest(obContext, core), - new GetDoubtFriendsAddRequest(obContext, core), - new SetFriendRemark(obContext, core), - new GetRkeyEx(obContext, core), - new GetRkeyServer(obContext, core), - new SetGroupRemark(obContext, core), - new GetGroupInfoEx(obContext, core), - new FetchEmojiLike(obContext, core), - new GetFile(obContext, core), - new SetQQProfile(obContext, core), - new ShareGroupEx(obContext, core), - new SharePeer(obContext, core), - new CreateCollection(obContext, core), - new SetLongNick(obContext, core), - new ForwardFriendSingleMsg(obContext, core), - new ForwardGroupSingleMsg(obContext, core), - new MarkGroupMsgAsRead(obContext, core), - new MarkPrivateMsgAsRead(obContext, core), - new SetQQAvatar(obContext, core), - new TranslateEnWordToZn(obContext, core), - new GetGroupRootFiles(obContext, core), - new SetGroupSign(obContext, core), - new SendGroupSign(obContext, core), - new GetClientkey(obContext, core), - new MoveGroupFile(obContext, core), - new RenameGroupFile(obContext, core), - new TransGroupFile(obContext, core), - // onebot11 - new SendLike(obContext, core), - new GetMsg(obContext, core), - new GetLoginInfo(obContext, core), - new GetFriendList(obContext, core), - new GetGroupList(obContext, core), - new GetGroupInfo(obContext, core), - new GetGroupMemberList(obContext, core), - new GetGroupMemberInfo(obContext, core), - new SendGroupMsg(obContext, core), - new SendPrivateMsg(obContext, core), - new SendMsg(obContext, core), - new DeleteMsg(obContext, core), - new SetGroupAddRequest(obContext, core), - new SetFriendAddRequest(obContext, core), - new SetGroupLeave(obContext, core), - new GetVersionInfo(obContext, core), - new CanSendRecord(obContext, core), - new CanSendImage(obContext, core), - new GetStatus(obContext, core), - new SetGroupWholeBan(obContext, core), - new SetGroupBan(obContext, core), - new SetGroupKick(obContext, core), - new SetGroupAdmin(obContext, core), - new SetGroupName(obContext, core), - new SetGroupCard(obContext, core), - new GetImage(obContext, core), - new GetRecord(obContext, core), - new SetMsgEmojiLike(obContext, core), - new GetCookies(obContext, core), - new SetOnlineStatus(obContext, core), - new GetRobotUinRange(obContext, core), - new GetFriendWithCategory(obContext, core), - //以下为go-cqhttp api - new GoCQHTTPDeleteFriend(obContext, core), - new GoCQHTTPCheckUrlSafely(obContext, core), - new GetOnlineClient(obContext, core), - new OCRImage(obContext, core), - new IOCRImage(obContext, core), - new GetGroupHonorInfo(obContext, core), - new SendGroupNotice(obContext, core), - new GetGroupNotice(obContext, core), - new GetGroupEssence(obContext, core), - new GoCQHTTPGetGroupAtAllRemain(obContext, core), - new GoCQHTTPSendForwardMsg(obContext, core), - new GoCQHTTPSendGroupForwardMsg(obContext, core), - new GoCQHTTPSendPrivateForwardMsg(obContext, core), - new GoCQHTTPGetStrangerInfo(obContext, core), - new GoCQHTTPDownloadFile(obContext, core), - new GetGuildList(obContext, core), - new GoCQHTTPMarkMsgAsRead(obContext, core), - new GoCQHTTPUploadGroupFile(obContext, core), - new GoCQHTTPGetGroupMsgHistory(obContext, core), - new GoCQHTTPGetForwardMsgAction(obContext, core), - new GetFriendMsgHistory(obContext, core), - new GoCQHTTPHandleQuickAction(obContext, core), - new GetGroupIgnoredNotifies(obContext, core), - new DelEssenceMsg(obContext, core), - new SetEssenceMsg(obContext, core), - new GetRecentContact(obContext, core), - new MarkAllMsgAsRead(obContext, core), - new GetProfileLike(obContext, core), - new SetGroupPortrait(obContext, core), - new FetchCustomFace(obContext, core), - new GoCQHTTPUploadPrivateFile(obContext, core), - new GetGuildProfile(obContext, core), - new GoCQHTTPGetModelShow(obContext, core), - new GoCQHTTPSetModelShow(obContext, core), - new GoCQHTTPCheckUrlSafely(obContext, core), - new SetInputStatus(obContext, core), - new GetCSRF(obContext, core), - new GetCredentials(obContext, core), - new DelGroupNotice(obContext, core), - new DeleteGroupFile(obContext, core), - new CreateGroupFileFolder(obContext, core), - new DeleteGroupFileFolder(obContext, core), - new GetGroupFileSystemInfo(obContext, core), - new GetGroupFilesByFolder(obContext, core), - new GetPacketStatus(obContext, core), - new GroupPoke(obContext, core), - new FriendPoke(obContext, core), - new GetUserStatus(obContext, core), - new GetRkey(obContext, core), - new SetSpecialTitle(obContext, core), - new SetDiyOnlineStatus(obContext, core), - // new UploadForwardMsg(obContext, core), - new GetGroupShutList(obContext, core), - new GetGroupFileUrl(obContext, core), - new GetMiniAppArk(obContext, core), - new GetAiRecord(obContext, core), - new SendGroupAiRecord(obContext, core), - new GetAiCharacters(obContext, core), - new SendPacket(obContext, core), - new SendPoke(obContext, core), - new GetGroupSystemMsg(obContext, core), - new BotExit(obContext, core), - new ClickInlineKeyboardButton(obContext, core), - new GetPrivateFileUrl(obContext, core), - new GetUnidirectionalFriendList(obContext, core), - new CleanCache(obContext, core), - new GetGroupAddRequest(obContext, core), - new GetCollectionList(obContext, core), - ]; +export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + const actionHandlers = [ + new CleanStreamTempFile(obContext, core), + new DownloadFileStream(obContext, core), + new DownloadFileRecordStream(obContext, core), + new DownloadFileImageStream(obContext, core), + new TestDownloadStream(obContext, core), + new UploadFileStream(obContext, core), + new DelGroupAlbumMedia(obContext, core), + new SetGroupAlbumMediaLike(obContext, core), + new DoGroupAlbumComment(obContext, core), + new GetGroupAlbumMediaList(obContext, core), + new GetQunAlbumList(obContext, core), + new UploadImageToQunAlbum(obContext, core), + new SetGroupTodo(obContext, core), + new GetGroupDetailInfo(obContext, core), + new SetGroupKickMembers(obContext, core), + new SetGroupAddOption(obContext, core), + new SetGroupRobotAddOption(obContext, core), + new SetGroupSearch(obContext, core), + new SetDoubtFriendsAddRequest(obContext, core), + new GetDoubtFriendsAddRequest(obContext, core), + new SetFriendRemark(obContext, core), + new GetRkeyEx(obContext, core), + new GetRkeyServer(obContext, core), + new SetGroupRemark(obContext, core), + new GetGroupInfoEx(obContext, core), + new FetchEmojiLike(obContext, core), + new GetFile(obContext, core), + new SetQQProfile(obContext, core), + new ShareGroupEx(obContext, core), + new SharePeer(obContext, core), + new CreateCollection(obContext, core), + new SetLongNick(obContext, core), + new ForwardFriendSingleMsg(obContext, core), + new ForwardGroupSingleMsg(obContext, core), + new MarkGroupMsgAsRead(obContext, core), + new MarkPrivateMsgAsRead(obContext, core), + new SetQQAvatar(obContext, core), + new TranslateEnWordToZn(obContext, core), + new GetGroupRootFiles(obContext, core), + new SetGroupSign(obContext, core), + new SendGroupSign(obContext, core), + new GetClientkey(obContext, core), + new MoveGroupFile(obContext, core), + new RenameGroupFile(obContext, core), + new TransGroupFile(obContext, core), + // onebot11 + new SendLike(obContext, core), + new GetMsg(obContext, core), + new GetLoginInfo(obContext, core), + new GetFriendList(obContext, core), + new GetGroupList(obContext, core), + new GetGroupInfo(obContext, core), + new GetGroupMemberList(obContext, core), + new GetGroupMemberInfo(obContext, core), + new SendGroupMsg(obContext, core), + new SendPrivateMsg(obContext, core), + new SendMsg(obContext, core), + new DeleteMsg(obContext, core), + new SetGroupAddRequest(obContext, core), + new SetFriendAddRequest(obContext, core), + new SetGroupLeave(obContext, core), + new GetVersionInfo(obContext, core), + new CanSendRecord(obContext, core), + new CanSendImage(obContext, core), + new GetStatus(obContext, core), + new SetGroupWholeBan(obContext, core), + new SetGroupBan(obContext, core), + new SetGroupKick(obContext, core), + new SetGroupAdmin(obContext, core), + new SetGroupName(obContext, core), + new SetGroupCard(obContext, core), + new GetImage(obContext, core), + new GetRecord(obContext, core), + new SetMsgEmojiLike(obContext, core), + new GetCookies(obContext, core), + new SetOnlineStatus(obContext, core), + new GetRobotUinRange(obContext, core), + new GetFriendWithCategory(obContext, core), + // 以下为go-cqhttp api + new GoCQHTTPDeleteFriend(obContext, core), + new GoCQHTTPCheckUrlSafely(obContext, core), + new GetOnlineClient(obContext, core), + new OCRImage(obContext, core), + new IOCRImage(obContext, core), + new GetGroupHonorInfo(obContext, core), + new SendGroupNotice(obContext, core), + new GetGroupNotice(obContext, core), + new GetGroupEssence(obContext, core), + new GoCQHTTPGetGroupAtAllRemain(obContext, core), + new GoCQHTTPSendForwardMsg(obContext, core), + new GoCQHTTPSendGroupForwardMsg(obContext, core), + new GoCQHTTPSendPrivateForwardMsg(obContext, core), + new GoCQHTTPGetStrangerInfo(obContext, core), + new GoCQHTTPDownloadFile(obContext, core), + new GetGuildList(obContext, core), + new GoCQHTTPMarkMsgAsRead(obContext, core), + new GoCQHTTPUploadGroupFile(obContext, core), + new GoCQHTTPGetGroupMsgHistory(obContext, core), + new GoCQHTTPGetForwardMsgAction(obContext, core), + new GetFriendMsgHistory(obContext, core), + new GoCQHTTPHandleQuickAction(obContext, core), + new GetGroupIgnoredNotifies(obContext, core), + new DelEssenceMsg(obContext, core), + new SetEssenceMsg(obContext, core), + new GetRecentContact(obContext, core), + new MarkAllMsgAsRead(obContext, core), + new GetProfileLike(obContext, core), + new SetGroupPortrait(obContext, core), + new FetchCustomFace(obContext, core), + new GoCQHTTPUploadPrivateFile(obContext, core), + new GetGuildProfile(obContext, core), + new GoCQHTTPGetModelShow(obContext, core), + new GoCQHTTPSetModelShow(obContext, core), + new GoCQHTTPCheckUrlSafely(obContext, core), + new SetInputStatus(obContext, core), + new GetCSRF(obContext, core), + new GetCredentials(obContext, core), + new DelGroupNotice(obContext, core), + new DeleteGroupFile(obContext, core), + new CreateGroupFileFolder(obContext, core), + new DeleteGroupFileFolder(obContext, core), + new GetGroupFileSystemInfo(obContext, core), + new GetGroupFilesByFolder(obContext, core), + new GetPacketStatus(obContext, core), + new GroupPoke(obContext, core), + new FriendPoke(obContext, core), + new GetUserStatus(obContext, core), + new GetRkey(obContext, core), + new SetSpecialTitle(obContext, core), + new SetDiyOnlineStatus(obContext, core), + // new UploadForwardMsg(obContext, core), + new GetGroupShutList(obContext, core), + new GetGroupFileUrl(obContext, core), + new GetMiniAppArk(obContext, core), + new GetAiRecord(obContext, core), + new SendGroupAiRecord(obContext, core), + new GetAiCharacters(obContext, core), + new SendPacket(obContext, core), + new SendPoke(obContext, core), + new GetGroupSystemMsg(obContext, core), + new BotExit(obContext, core), + new ClickInlineKeyboardButton(obContext, core), + new GetPrivateFileUrl(obContext, core), + new GetUnidirectionalFriendList(obContext, core), + new CleanCache(obContext, core), + new GetGroupAddRequest(obContext, core), + new GetCollectionList(obContext, core), + ]; type HandlerUnion = typeof actionHandlers[number]; type MapType = { - [H in HandlerUnion as H['actionName']]: H; + [H in HandlerUnion as H['actionName']]: H; } & { - [H in HandlerUnion as `${H['actionName']}_async`]: H; + [H in HandlerUnion as `${H['actionName']}_async`]: H; } & { - [H in HandlerUnion as `${H['actionName']}_rate_limited`]: H; + [H in HandlerUnion as `${H['actionName']}_rate_limited`]: H; }; const _map = new Map(); actionHandlers.forEach(h => { - _map.set(h.actionName as keyof MapType, h); - _map.set(`${h.actionName}_async` as keyof MapType, h); - _map.set(`${h.actionName}_rate_limited` as keyof MapType, h); + _map.set(h.actionName as keyof MapType, h); + _map.set(`${h.actionName}_async` as keyof MapType, h); + _map.set(`${h.actionName}_rate_limited` as keyof MapType, h); }); // function get(key: K): MapType[K]; // function get(key: K): null; // function get(key: K): HandlerUnion | null | MapType[K] - function get(key: K): MapType[K] | undefined { - return _map.get(key as keyof MapType) as MapType[K] | undefined; + function get (key: K): MapType[K] | undefined { + return _map.get(key as keyof MapType) as MapType[K] | undefined; } return { get }; } -export type ActionMap = ReturnType +export type ActionMap = ReturnType; diff --git a/src/onebot/action/msg/DeleteMsg.ts b/src/onebot/action/msg/DeleteMsg.ts index 4b8acdb8..840898ec 100644 --- a/src/onebot/action/msg/DeleteMsg.ts +++ b/src/onebot/action/msg/DeleteMsg.ts @@ -4,26 +4,26 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), + message_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; class DeleteMsg extends OneBotAction { - override actionName = ActionName.DeleteMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.DeleteMsg; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const msg = MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id)); - if (msg) { - this.obContext.recallEventCache.set(msg.MsgId, setTimeout(() => { - this.obContext.recallEventCache.delete(msg.MsgId); - }, 5000)); - await this.core.apis.MsgApi.recallMsg(msg.Peer, msg.MsgId); - } else { - throw new Error('Recall failed'); - } + async _handle (payload: Payload) { + const msg = MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id)); + if (msg) { + this.obContext.recallEventCache.set(msg.MsgId, setTimeout(() => { + this.obContext.recallEventCache.delete(msg.MsgId); + }, 5000)); + await this.core.apis.MsgApi.recallMsg(msg.Peer, msg.MsgId); + } else { + throw new Error('Recall failed'); } + } } export default DeleteMsg; diff --git a/src/onebot/action/msg/ForwardSingleMsg.ts b/src/onebot/action/msg/ForwardSingleMsg.ts index 7156159c..6fd5596d 100644 --- a/src/onebot/action/msg/ForwardSingleMsg.ts +++ b/src/onebot/action/msg/ForwardSingleMsg.ts @@ -5,48 +5,48 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), - group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), - user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + message_id: Type.Union([Type.Number(), Type.String()]), + group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), }); type Payload = Static; class ForwardSingleMsg extends OneBotAction { - protected async getTargetPeer(payload: Payload): Promise { - if (payload.user_id) { - const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!peerUid) { - throw new Error(`无法找到私聊对象${payload.user_id}`); - } - return { chatType: ChatType.KCHATTYPEC2C, peerUid }; - } - return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id!.toString() }; + protected async getTargetPeer (payload: Payload): Promise { + if (payload.user_id) { + const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!peerUid) { + throw new Error(`无法找到私聊对象${payload.user_id}`); + } + return { chatType: ChatType.KCHATTYPEC2C, peerUid }; } + return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id!.toString() }; + } - async _handle(payload: Payload): Promise { - const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); - if (!msg) { - throw new Error(`无法找到消息${payload.message_id}`); - } - const peer = await this.getTargetPeer(payload); - const ret = await this.core.apis.MsgApi.forwardMsg(msg.Peer, - peer, - [msg.MsgId], - ); - if (ret.result !== 0) { - throw new Error(`转发消息失败 ${ret.errMsg}`); - } - return null; + async _handle (payload: Payload): Promise { + const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); + if (!msg) { + throw new Error(`无法找到消息${payload.message_id}`); } + const peer = await this.getTargetPeer(payload); + const ret = await this.core.apis.MsgApi.forwardMsg(msg.Peer, + peer, + [msg.MsgId] + ); + if (ret.result !== 0) { + throw new Error(`转发消息失败 ${ret.errMsg}`); + } + return null; + } } export class ForwardFriendSingleMsg extends ForwardSingleMsg { - override payloadSchema = SchemaData; - override actionName = ActionName.ForwardFriendSingleMsg; + override payloadSchema = SchemaData; + override actionName = ActionName.ForwardFriendSingleMsg; } export class ForwardGroupSingleMsg extends ForwardSingleMsg { - override payloadSchema = SchemaData; - override actionName = ActionName.ForwardGroupSingleMsg; + override payloadSchema = SchemaData; + override actionName = ActionName.ForwardGroupSingleMsg; } diff --git a/src/onebot/action/msg/GetMsg.ts b/src/onebot/action/msg/GetMsg.ts index 91526377..d34746a9 100644 --- a/src/onebot/action/msg/GetMsg.ts +++ b/src/onebot/action/msg/GetMsg.ts @@ -6,47 +6,47 @@ import { RawMessage } from '@/core'; import { Static, Type } from '@sinclair/typebox'; import { NetworkAdapterConfig } from '@/onebot/config/config'; -export type ReturnDataType = OB11Message +export type ReturnDataType = OB11Message; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), + message_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; class GetMsg extends OneBotAction { - override actionName = ActionName.GetMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.GetMsg; + override payloadSchema = SchemaData; - async _handle(payload: Payload, _adapter: string, config: NetworkAdapterConfig) { - if (!payload.message_id) { - throw Error('参数message_id不能为空'); - } - const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString()); - const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId ?? +payload.message_id); - if (!msgIdWithPeer) { - throw new Error('消息不存在'); - } - const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType }; - //const orimsg = this.obContext.recallMsgCache.get(msgIdWithPeer.MsgId); - let msg: RawMessage|undefined; - // if (orimsg) { - // msg = orimsg; - // } else { - msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0]; - //} - if (!msg) throw Error('消息不存在'); - const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat); - if (!retMsg) throw Error('消息为空'); - try { - retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!; - retMsg.message_seq = retMsg.message_id; - retMsg.real_id = retMsg.message_id; - } catch { - // ignored - } - return retMsg; + async _handle (payload: Payload, _adapter: string, config: NetworkAdapterConfig) { + if (!payload.message_id) { + throw Error('参数message_id不能为空'); } + const MsgShortId = MessageUnique.getShortIdByMsgId(payload.message_id.toString()); + const msgIdWithPeer = MessageUnique.getMsgIdAndPeerByShortId(MsgShortId ?? +payload.message_id); + if (!msgIdWithPeer) { + throw new Error('消息不存在'); + } + const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType }; + // const orimsg = this.obContext.recallMsgCache.get(msgIdWithPeer.MsgId); + let msg: RawMessage | undefined; + // if (orimsg) { + // msg = orimsg; + // } else { + msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0]; + // } + if (!msg) throw Error('消息不存在'); + const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg, config.messagePostFormat); + if (!retMsg) throw Error('消息为空'); + try { + retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!; + retMsg.message_seq = retMsg.message_id; + retMsg.real_id = retMsg.message_id; + } catch { + // ignored + } + return retMsg; + } } export default GetMsg; diff --git a/src/onebot/action/msg/MarkMsgAsRead.ts b/src/onebot/action/msg/MarkMsgAsRead.ts index f36a653a..ff340bcf 100644 --- a/src/onebot/action/msg/MarkMsgAsRead.ts +++ b/src/onebot/action/msg/MarkMsgAsRead.ts @@ -5,68 +5,68 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), - group_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), - message_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), + user_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), + group_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), + message_id: Type.Optional(Type.Union([Type.String(), Type.Number()])), }); type PlayloadType = Static; class MarkMsgAsRead extends OneBotAction { - async getPeer(payload: PlayloadType): Promise { - if (payload.message_id) { - const s_peer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)?.Peer; - if (s_peer) { - return s_peer; - } - const l_peer = MessageUnique.getPeerByMsgId(payload.message_id.toString())?.Peer; - if (l_peer) { - return l_peer; - } - } - if (payload.user_id) { - const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!peerUid) { - throw new Error( `私聊${payload.user_id}不存在`); - } - const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid); - return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid }; - } - if (!payload.group_id) { - throw new Error('缺少参数 group_id 或 user_id'); - } - return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() }; + async getPeer (payload: PlayloadType): Promise { + if (payload.message_id) { + const s_peer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)?.Peer; + if (s_peer) { + return s_peer; + } + const l_peer = MessageUnique.getPeerByMsgId(payload.message_id.toString())?.Peer; + if (l_peer) { + return l_peer; + } } + if (payload.user_id) { + const peerUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!peerUid) { + throw new Error(`私聊${payload.user_id}不存在`); + } + const isBuddy = await this.core.apis.FriendApi.isBuddy(peerUid); + return { chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid }; + } + if (!payload.group_id) { + throw new Error('缺少参数 group_id 或 user_id'); + } + return { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() }; + } - async _handle(payload: PlayloadType): Promise { - const ret = await this.core.apis.MsgApi.setMsgRead(await this.getPeer(payload)); - if (ret.result != 0) { - throw new Error('设置已读失败,' + ret.errMsg); - } - return null; + async _handle (payload: PlayloadType): Promise { + const ret = await this.core.apis.MsgApi.setMsgRead(await this.getPeer(payload)); + if (ret.result != 0) { + throw new Error('设置已读失败,' + ret.errMsg); } + return null; + } } // 以下为非标准实现 export class MarkPrivateMsgAsRead extends MarkMsgAsRead { - override payloadSchema = SchemaData; - override actionName = ActionName.MarkPrivateMsgAsRead; + override payloadSchema = SchemaData; + override actionName = ActionName.MarkPrivateMsgAsRead; } export class MarkGroupMsgAsRead extends MarkMsgAsRead { - override payloadSchema = SchemaData; - override actionName = ActionName.MarkGroupMsgAsRead; + override payloadSchema = SchemaData; + override actionName = ActionName.MarkGroupMsgAsRead; } export class GoCQHTTPMarkMsgAsRead extends MarkMsgAsRead { - override actionName = ActionName.GoCQHTTP_MarkMsgAsRead; + override actionName = ActionName.GoCQHTTP_MarkMsgAsRead; } export class MarkAllMsgAsRead extends OneBotAction { - override actionName = ActionName._MarkAllMsgAsRead; + override actionName = ActionName._MarkAllMsgAsRead; - async _handle(): Promise { - await this.core.apis.MsgApi.markAllMsgAsRead(); - return null; - } + async _handle (): Promise { + await this.core.apis.MsgApi.markAllMsgAsRead(); + return null; + } } diff --git a/src/onebot/action/msg/SendMsg.ts b/src/onebot/action/msg/SendMsg.ts index 60ef8322..d2e87586 100644 --- a/src/onebot/action/msg/SendMsg.ts +++ b/src/onebot/action/msg/SendMsg.ts @@ -1,10 +1,10 @@ import { - OB11MessageData, - OB11MessageDataType, - OB11MessageMixType, - OB11MessageNode, - OB11PostContext, - OB11PostSendMsg, + OB11MessageData, + OB11MessageDataType, + OB11MessageMixType, + OB11MessageNode, + OB11PostContext, + OB11PostSendMsg, } from '@/onebot/types'; import { ActionName, BaseCheckResult } from '@/onebot/action/router'; import { decodeCQCode } from '@/onebot/helper/cqcode'; @@ -17,395 +17,398 @@ import { PacketMsg } from '@/core/packet/message/message'; import { rawMsgWithSendMsg } from '@/core/packet/message/converter'; export interface ReturnDataType { - message_id: number; - res_id?: string; - forward_id?: string; + message_id: number; + res_id?: string; + forward_id?: string; } export enum ContextMode { - Normal = 0, - Private = 1, - Group = 2 + Normal = 0, + Private = 1, + Group = 2, } // Normalizes a mixed type (CQCode/a single segment/segment array) into a segment array. -export function normalize(message: OB11MessageMixType, autoEscape = false): OB11MessageData[] { - return typeof message === 'string' ? ( - autoEscape ? - [{ type: OB11MessageDataType.text, data: { text: message } }] : - decodeCQCode(message) - ) : Array.isArray(message) ? message : [message]; +export function normalize (message: OB11MessageMixType, autoEscape = false): OB11MessageData[] { + return typeof message === 'string' + ? ( + autoEscape + ? [{ type: OB11MessageDataType.text, data: { text: message } }] + : decodeCQCode(message) + ) + : Array.isArray(message) ? message : [message]; } -export async function createContext(core: NapCatCore, payload: OB11PostContext | undefined, contextMode: ContextMode = ContextMode.Normal): Promise { - if (!payload) { - throw new Error('请传递请求内容'); - } - if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) { +export async function createContext (core: NapCatCore, payload: OB11PostContext | undefined, contextMode: ContextMode = ContextMode.Normal): Promise { + if (!payload) { + throw new Error('请传递请求内容'); + } + if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) { + return { + chatType: ChatType.KCHATTYPEGROUP, + peerUid: payload.group_id.toString(), + }; + } + if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { + const Uid = await core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + if (!Uid) { + if (payload.group_id) { return { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: payload.group_id.toString(), + chatType: ChatType.KCHATTYPEGROUP, + peerUid: payload.group_id.toString(), + guildId: '', }; + } + throw new Error('无法获取用户信息'); } - if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { - const Uid = await core.apis.UserApi.getUidByUinV2(payload.user_id.toString()); - if (!Uid) { - if (payload.group_id) { - return { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: payload.group_id.toString(), - guildId: '' - } - } - throw new Error('无法获取用户信息'); - } - const isBuddy = await core.apis.FriendApi.isBuddy(Uid); - if (!isBuddy) { - const ret = await core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, Uid); - if (ret.tmpChatInfo?.groupCode) { - return { - chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, - peerUid: Uid, - guildId: '', - }; - } - if (payload.group_id) { - return { - chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, - peerUid: Uid, - guildId: payload.group_id.toString(), - }; - } - return { - chatType: ChatType.KCHATTYPEC2C, - peerUid: Uid, - guildId: '', - }; - } + const isBuddy = await core.apis.FriendApi.isBuddy(Uid); + if (!isBuddy) { + const ret = await core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, Uid); + if (ret.tmpChatInfo?.groupCode) { return { - chatType: ChatType.KCHATTYPEC2C, - peerUid: Uid, - guildId: '', + chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, + peerUid: Uid, + guildId: '', }; + } + if (payload.group_id) { + return { + chatType: ChatType.KCHATTYPETEMPC2CFROMGROUP, + peerUid: Uid, + guildId: payload.group_id.toString(), + }; + } + return { + chatType: ChatType.KCHATTYPEC2C, + peerUid: Uid, + guildId: '', + }; } - if (contextMode === ContextMode.Private && payload.group_id) { - throw new Error('当前私聊发送,请指定 user_id 而不是 group_id'); - } - if (contextMode === ContextMode.Group && payload.user_id) { - throw new Error('当前群聊发送,请指定 group_id 而不是 user_id'); - } - throw new Error('请指定正确的 group_id 或 user_id'); + return { + chatType: ChatType.KCHATTYPEC2C, + peerUid: Uid, + guildId: '', + }; + } + if (contextMode === ContextMode.Private && payload.group_id) { + throw new Error('当前私聊发送,请指定 user_id 而不是 group_id'); + } + if (contextMode === ContextMode.Group && payload.user_id) { + throw new Error('当前群聊发送,请指定 group_id 而不是 user_id'); + } + throw new Error('请指定正确的 group_id 或 user_id'); } -function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number { - if (Array.isArray(payload.message)) { - return payload.message.filter(msg => msg.type == msgType).length; - } - return 0; +function getSpecialMsgNum (payload: OB11PostSendMsg, msgType: OB11MessageDataType): number { + if (Array.isArray(payload.message)) { + return payload.message.filter(msg => msg.type == msgType).length; + } + return 0; } export class SendMsgBase extends OneBotAction { - protected override async check(payload: OB11PostSendMsg): Promise { - const messages = normalize(payload.message); - const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node); - if (nodeElementLength > 0 && nodeElementLength != messages.length) { - return { - valid: false, - message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', - }; - } - return { valid: true }; + protected override async check (payload: OB11PostSendMsg): Promise { + const messages = normalize(payload.message); + const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node); + if (nodeElementLength > 0 && nodeElementLength != messages.length) { + return { + valid: false, + message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素', + }; } - async _handle(payload: OB11PostSendMsg): Promise { - return this.base_handle(payload); + return { valid: true }; + } + + async _handle (payload: OB11PostSendMsg): Promise { + return this.base_handle(payload); + } + + async base_handle (payload: OB11PostSendMsg, contextMode: ContextMode = ContextMode.Normal): Promise { + if (payload.message_type === 'group') contextMode = ContextMode.Group; + if (payload.message_type === 'private') contextMode = ContextMode.Private; + const peer = await createContext(this.core, payload, contextMode); + + const messages = normalize( + payload.message, + typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape + ); + + if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { + const packetMode = this.core.apis.PacketApi.packetStatus; + let returnMsgAndResId: { message: RawMessage | null, res_id?: string } | null; + try { + returnMsgAndResId = packetMode + ? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt) + : await this.handleForwardedNodes(peer, messages as OB11MessageNode[]); + } catch (e: unknown) { + throw Error(packetMode ? `发送伪造合并转发消息失败: ${(e as Error)?.stack}` : `发送合并转发消息失败: ${(e as Error)?.stack}`); + } + if (!returnMsgAndResId) { + throw Error('发送合并转发消息失败:returnMsgAndResId 为空!'); + } + if (returnMsgAndResId.message) { + const msgShortId = MessageUnique.createUniqueMsgId({ + guildId: '', + peerUid: peer.peerUid, + chatType: peer.chatType, + }, (returnMsgAndResId.message).msgId); + + // 对gocq的forward_id进行兼容 + const resId = returnMsgAndResId.res_id!; + return { message_id: msgShortId!, res_id: resId, forward_id: resId }; + } else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) { + throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`); + } + } else { + // if (getSpecialMsgNum(payload, OB11MessageDataType.music)) { + // const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic; + // if (music) { + // } + // } } - async base_handle(payload: OB11PostSendMsg, contextMode: ContextMode = ContextMode.Normal): Promise { - if (payload.message_type === 'group') contextMode = ContextMode.Group; - if (payload.message_type === 'private') contextMode = ContextMode.Private; - const peer = await createContext(this.core, payload, contextMode); + // log("send msg:", peer, sendElements) - const messages = normalize( - payload.message, - typeof payload.auto_escape === 'string' ? payload.auto_escape === 'true' : !!payload.auto_escape, - ); + const { sendElements, deleteAfterSentFiles } = await this.obContext.apis.MsgApi + .createSendElements(messages, peer); + const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles); + return { message_id: returnMsg.id! }; + } - if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { - const packetMode = this.core.apis.PacketApi.packetStatus; - let returnMsgAndResId: { message: RawMessage | null, res_id?: string } | null; - try { - returnMsgAndResId = packetMode - ? await this.handleForwardedNodesPacket(peer, messages as OB11MessageNode[], payload.source, payload.news, payload.summary, payload.prompt) - : await this.handleForwardedNodes(peer, messages as OB11MessageNode[]); - } catch (e: unknown) { - throw Error(packetMode ? `发送伪造合并转发消息失败: ${(e as Error)?.stack}` : `发送合并转发消息失败: ${(e as Error)?.stack}`); - } - if (!returnMsgAndResId) { - throw Error('发送合并转发消息失败:returnMsgAndResId 为空!'); - } - if (returnMsgAndResId.message) { - const msgShortId = MessageUnique.createUniqueMsgId({ - guildId: '', - peerUid: peer.peerUid, - chatType: peer.chatType, - }, (returnMsgAndResId.message).msgId); + private async uploadForwardedNodesPacket (msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: { + text: string + }[], summary?: string, prompt?: string, parentMeta?: { + user_id: string, + nickname: string, + }, dp: number = 0): Promise<{ + finallySendElements: SendArkElement, + res_id?: string, + deleteAfterSentFiles: string[], + } | null> { + const packetMsg: PacketMsg[] = []; + const delFiles: string[] = []; + for (const node of messageNodes) { + if (dp >= 3) { + this.core.context.logger.logWarn('转发消息深度超过3层,将停止解析!'); + break; + } + if (!node.data.id) { + const OB11Data = normalize(node.type === OB11MessageDataType.node ? node.data.content : node); + let sendElements: SendMessageElement[]; - // 对gocq的forward_id进行兼容 - const resId = returnMsgAndResId.res_id!; - return { message_id: msgShortId!, res_id: resId, forward_id: resId }; - } else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) { - throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`); - } + if (getSpecialMsgNum({ message: OB11Data }, OB11MessageDataType.node)) { + const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, OB11Data as OB11MessageNode[], node.data.source, node.data.news, node.data.summary, node.data.prompt, { + user_id: (node.data.user_id ?? node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin, + nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户', + }, dp + 1); + sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : []; + delFiles.push(...(uploadReturnData?.deleteAfterSentFiles || [])); } else { - // if (getSpecialMsgNum(payload, OB11MessageDataType.music)) { - // const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic; - // if (music) { - // } - // } + const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer); + sendElements = sendElementsCreateReturn.sendElements; + delFiles.push(...sendElementsCreateReturn.deleteAfterSentFiles); } - // log("send msg:", peer, sendElements) - const { sendElements, deleteAfterSentFiles } = await this.obContext.apis.MsgApi - .createSendElements(messages, peer); - const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles); - return { message_id: returnMsg.id! }; - } - - private async uploadForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: { - text: string - }[], summary?: string, prompt?: string, parentMeta?: { - user_id: string, - nickname: string, - }, dp: number = 0): Promise<{ - finallySendElements: SendArkElement, - res_id?: string, - deleteAfterSentFiles: string[], - } | null> { - const packetMsg: PacketMsg[] = []; - let delFiles: string[] = []; - for (const node of messageNodes) { - if (dp >= 3) { - this.core.context.logger.logWarn('转发消息深度超过3层,将停止解析!'); - break; - } - if (!node.data.id) { - const OB11Data = normalize(node.type === OB11MessageDataType.node ? node.data.content : node); - let sendElements: SendMessageElement[]; - - if (getSpecialMsgNum({ message: OB11Data }, OB11MessageDataType.node)) { - const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, OB11Data as OB11MessageNode[], node.data.source, node.data.news, node.data.summary, node.data.prompt, { - user_id: (node.data.user_id ?? node.data.uin)?.toString() ?? parentMeta?.user_id ?? this.core.selfInfo.uin, - nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户', - }, dp + 1); - sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : []; - delFiles.push(...(uploadReturnData?.deleteAfterSentFiles || [])); - } else { - const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer); - sendElements = sendElementsCreateReturn.sendElements; - delFiles.push(...sendElementsCreateReturn.deleteAfterSentFiles); - } - - const packetMsgElements: rawMsgWithSendMsg = { - senderUin: Number((node.data.user_id ?? node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin, - senderName: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户', - groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0, - time: Number(node.data.time) || Date.now(), - msg: sendElements, - }; - this.core.context.logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`); - const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements); - this.core.context.logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`); - packetMsg.push(transformedMsg); - } else if (node.data.id) { - const id = node.data.id; - const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(+id) || MessageUnique.getPeerByMsgId(id); - if (!nodeMsg) { - this.core.context.logger.logError('转发消息失败,未找到消息', id); - continue; - } - const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0]; - this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`); - if (msg) { - let msgCache = await this.core.apis.FileApi.downloadRawMsgMedia([msg]); - delFiles.push(...msgCache); - const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer); - this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`); - packetMsg.push(transformedMsg); - } - } else { - this.core.context.logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${stringifyWithBigInt(node)}`); - } - } - if (packetMsg.length === 0) { - this.core.context.logger.logWarn('handleForwardedNodesPacket 元素为空!'); - return null; - } - const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0); - const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt); - return { - deleteAfterSentFiles: delFiles, - finallySendElements: { - elementType: ElementType.ARK, - elementId: '', - arkElement: { - bytesData: JSON.stringify(forwardJson), - }, - } as SendArkElement, - res_id: resid, + const packetMsgElements: rawMsgWithSendMsg = { + senderUin: Number((node.data.user_id ?? node.data.uin) ?? parentMeta?.user_id) || +this.core.selfInfo.uin, + senderName: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户', + groupId: msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0, + time: Number(node.data.time) || Date.now(), + msg: sendElements, }; - } - - private async handleForwardedNodesPacket(msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: { - text: string - }[], summary?: string, prompt?: string): Promise<{ - message: RawMessage | null, - res_id?: string - }> { - const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt); - const res_id = uploadReturnData?.res_id; - const finallySendElements = uploadReturnData?.finallySendElements; - if (!finallySendElements) throw Error('转发消息失败,生成节点为空'); - const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], uploadReturnData.deleteAfterSentFiles || []).catch(() => undefined); - return { message: returnMsg ?? null, res_id: res_id! }; - } - - private async handleForwardedNodes(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{ - message: RawMessage | null, - res_id?: string - }> { - const selfPeer = { - chatType: ChatType.KCHATTYPEC2C, - peerUid: this.core.selfInfo.uid, - }; - let nodeMsgIds: string[] = []; - for (const messageNode of messageNodes) { - const nodeId = messageNode.data.id; - if (nodeId) { - // 对Msgid和OB11ID混用情况兜底 - const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId); - if (!nodeMsg) { - this.core.context.logger.logError('转发消息失败,未找到消息', nodeId); - continue; - } - nodeMsgIds.push(nodeMsg.MsgId); - } else { - // 自定义的消息 - try { - const OB11Data = normalize(messageNode.data.content); - //筛选node消息 - const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息 - if (isNodeMsg !== 0) { - if (isNodeMsg !== OB11Data.length) { - this.core.context.logger.logError('子消息中包含非node消息 跳过不合法部分'); - continue; - } - const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node)); - if (nodeMsg) { - nodeMsgIds.push(nodeMsg.message!.msgId); - MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.message!.msgId); - } - //完成子卡片生成跳过后续 - continue; - } - const { sendElements } = await this.obContext.apis.MsgApi - .createSendElements(OB11Data, destPeer); - - //拆分消息 - - const MixElement = sendElements.filter( - element => - element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO && element.elementType !== ElementType.ARK - ); - const SingleElement = sendElements.filter( - element => - element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO || element.elementType === ElementType.ARK - ).map(e => [e]); - - const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0); - const MsgNodeList: Promise[] = []; - for (const sendElementsSplitElement of AllElement) { - MsgNodeList.push(this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(selfPeer, sendElementsSplitElement, []).catch(() => undefined)); - } - (await Promise.allSettled(MsgNodeList)).map((result) => { - if (result.status === 'fulfilled' && result.value) { - nodeMsgIds.push(result.value.msgId); - MessageUnique.createUniqueMsgId(selfPeer, result.value.msgId); - } - }); - } catch (e: unknown) { - this.core.context.logger.logDebug('生成转发消息节点失败', (e as Error).stack); - } - } + this.core.context.logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`); + const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements); + this.core.context.logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`); + packetMsg.push(transformedMsg); + } else if (node.data.id) { + const id = node.data.id; + const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(+id) || MessageUnique.getPeerByMsgId(id); + if (!nodeMsg) { + this.core.context.logger.logError('转发消息失败,未找到消息', id); + continue; } - const nodeMsgArray: Array = []; - let srcPeer: Peer | undefined = undefined; - let needSendSelf = false; - //检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送 - for (const msgId of nodeMsgIds) { - const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId); - if (!nodeMsgPeer) { - this.core.context.logger.logError('转发消息失败,未找到消息', msgId); - continue; + const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0]; + this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`); + if (msg) { + const msgCache = await this.core.apis.FileApi.downloadRawMsgMedia([msg]); + delFiles.push(...msgCache); + const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer); + this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`); + packetMsg.push(transformedMsg); + } + } else { + this.core.context.logger.logDebug(`handleForwardedNodesPacket 跳过元素 ${stringifyWithBigInt(node)}`); + } + } + if (packetMsg.length === 0) { + this.core.context.logger.logWarn('handleForwardedNodesPacket 元素为空!'); + return null; + } + const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0); + const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt); + return { + deleteAfterSentFiles: delFiles, + finallySendElements: { + elementType: ElementType.ARK, + elementId: '', + arkElement: { + bytesData: JSON.stringify(forwardJson), + }, + } as SendArkElement, + res_id: resid, + }; + } + + private async handleForwardedNodesPacket (msgPeer: Peer, messageNodes: OB11MessageNode[], source?: string, news?: { + text: string + }[], summary?: string, prompt?: string): Promise<{ + message: RawMessage | null, + res_id?: string + }> { + const uploadReturnData = await this.uploadForwardedNodesPacket(msgPeer, messageNodes, source, news, summary, prompt); + const res_id = uploadReturnData?.res_id; + const finallySendElements = uploadReturnData?.finallySendElements; + if (!finallySendElements) throw Error('转发消息失败,生成节点为空'); + const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], uploadReturnData.deleteAfterSentFiles || []).catch(() => undefined); + return { message: returnMsg ?? null, res_id: res_id! }; + } + + private async handleForwardedNodes (destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<{ + message: RawMessage | null, + res_id?: string + }> { + const selfPeer = { + chatType: ChatType.KCHATTYPEC2C, + peerUid: this.core.selfInfo.uid, + }; + let nodeMsgIds: string[] = []; + for (const messageNode of messageNodes) { + const nodeId = messageNode.data.id; + if (nodeId) { + // 对Msgid和OB11ID混用情况兜底 + const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId); + if (!nodeMsg) { + this.core.context.logger.logError('转发消息失败,未找到消息', nodeId); + continue; + } + nodeMsgIds.push(nodeMsg.MsgId); + } else { + // 自定义的消息 + try { + const OB11Data = normalize(messageNode.data.content); + // 筛选node消息 + const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;// 找到子转发消息 + if (isNodeMsg !== 0) { + if (isNodeMsg !== OB11Data.length) { + this.core.context.logger.logError('子消息中包含非node消息 跳过不合法部分'); + continue; } - const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0]; + const nodeMsg = await this.handleForwardedNodes(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node)); if (nodeMsg) { - srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }; - if (srcPeer.peerUid !== nodeMsg.peerUid) { - needSendSelf = true; - } - nodeMsgArray.push(nodeMsg); + nodeMsgIds.push(nodeMsg.message!.msgId); + MessageUnique.createUniqueMsgId(selfPeer, nodeMsg.message!.msgId); } - } - nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); - let retMsgIds: string[] = []; - if (needSendSelf) { - for (const [, msg] of nodeMsgArray.entries()) { - if (msg.peerUid === this.core.selfInfo.uid) { - retMsgIds.push(msg.msgId); - continue; - } - const ClonedMsg = await this.cloneMsg(msg); - if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId); + // 完成子卡片生成跳过后续 + continue; + } + const { sendElements } = await this.obContext.apis.MsgApi + .createSendElements(OB11Data, destPeer); + + // 拆分消息 + + const MixElement = sendElements.filter( + element => + element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO && element.elementType !== ElementType.ARK + ); + const SingleElement = sendElements.filter( + element => + element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO || element.elementType === ElementType.ARK + ).map(e => [e]); + + const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0); + const MsgNodeList: Promise[] = []; + for (const sendElementsSplitElement of AllElement) { + MsgNodeList.push(this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(selfPeer, sendElementsSplitElement, []).catch(() => undefined)); + } + (await Promise.allSettled(MsgNodeList)).map((result) => { + if (result.status === 'fulfilled' && result.value) { + nodeMsgIds.push(result.value.msgId); + MessageUnique.createUniqueMsgId(selfPeer, result.value.msgId); } - } else { - retMsgIds = nodeMsgIds; - } - if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空'); - try { - this.core.context.logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds); - return { - message: await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds) - }; + }); } catch (e: unknown) { - this.core.context.logger.logError('forward failed', (e as Error)?.stack); - return { - message: null - }; + this.core.context.logger.logDebug('生成转发消息节点失败', (e as Error).stack); } + } + } + const nodeMsgArray: Array = []; + let srcPeer: Peer | undefined; + let needSendSelf = false; + // 检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送 + for (const msgId of nodeMsgIds) { + const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId); + if (!nodeMsgPeer) { + this.core.context.logger.logError('转发消息失败,未找到消息', msgId); + continue; + } + const nodeMsg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0]; + if (nodeMsg) { + srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }; + if (srcPeer.peerUid !== nodeMsg.peerUid) { + needSendSelf = true; + } + nodeMsgArray.push(nodeMsg); + } + } + nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); + let retMsgIds: string[] = []; + if (needSendSelf) { + for (const [, msg] of nodeMsgArray.entries()) { + if (msg.peerUid === this.core.selfInfo.uid) { + retMsgIds.push(msg.msgId); + continue; + } + const ClonedMsg = await this.cloneMsg(msg); + if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId); + } + } else { + retMsgIds = nodeMsgIds; + } + if (retMsgIds.length === 0) throw Error('转发消息失败,生成节点为空'); + try { + this.core.context.logger.logDebug('开发转发', srcPeer, destPeer, retMsgIds); + return { + message: await this.core.apis.MsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds), + }; + } catch (e: unknown) { + this.core.context.logger.logError('forward failed', (e as Error)?.stack); + return { + message: null, + }; + } + } + + async cloneMsg (msg: RawMessage): Promise { + const selfPeer = { + chatType: ChatType.KCHATTYPEC2C, + peerUid: this.core.selfInfo.uid, + }; + // msg 为待克隆消息 + const sendElements: SendMessageElement[] = []; + + for (const element of msg.elements) { + sendElements.push(element as SendMessageElement); } - async cloneMsg(msg: RawMessage): Promise { - const selfPeer = { - chatType: ChatType.KCHATTYPEC2C, - peerUid: this.core.selfInfo.uid, - }; - //msg 为待克隆消息 - const sendElements: SendMessageElement[] = []; - - for (const element of msg.elements) { - sendElements.push(element as SendMessageElement); - } - - if (sendElements.length === 0) { - this.core.context.logger.logDebug('需要clone的消息无法解析,将会忽略掉', msg); - } - try { - return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements); - } catch (e: unknown) { - this.core.context.logger.logError((e as Error)?.stack, '克隆转发消息失败,将忽略本条消息', msg); - } - return; + if (sendElements.length === 0) { + this.core.context.logger.logDebug('需要clone的消息无法解析,将会忽略掉', msg); } + try { + return await this.core.apis.MsgApi.sendMsg(selfPeer, sendElements); + } catch (e: unknown) { + this.core.context.logger.logError((e as Error)?.stack, '克隆转发消息失败,将忽略本条消息', msg); + } + } } export default class SendMsg extends SendMsgBase { - override actionName = ActionName.SendMsg; + override actionName = ActionName.SendMsg; } diff --git a/src/onebot/action/msg/SendPrivateMsg.ts b/src/onebot/action/msg/SendPrivateMsg.ts index 64b05ca0..1a5062cc 100644 --- a/src/onebot/action/msg/SendPrivateMsg.ts +++ b/src/onebot/action/msg/SendPrivateMsg.ts @@ -4,15 +4,16 @@ import { OB11PostSendMsg } from '@/onebot/types'; // 未检测参数 class SendPrivateMsg extends SendMsgBase { - override actionName = ActionName.SendPrivateMsg; + override actionName = ActionName.SendPrivateMsg; - protected override async check(payload: OB11PostSendMsg): Promise { - payload.message_type = 'private'; - return super.check(payload); - } - override async _handle(payload: OB11PostSendMsg): Promise { - return this.base_handle(payload, ContextMode.Private); - } + protected override async check (payload: OB11PostSendMsg): Promise { + payload.message_type = 'private'; + return super.check(payload); + } + + override async _handle (payload: OB11PostSendMsg): Promise { + return this.base_handle(payload, ContextMode.Private); + } } export default SendPrivateMsg; diff --git a/src/onebot/action/msg/SetMsgEmojiLike.ts b/src/onebot/action/msg/SetMsgEmojiLike.ts index 02ce4e01..15dc13f7 100644 --- a/src/onebot/action/msg/SetMsgEmojiLike.ts +++ b/src/onebot/action/msg/SetMsgEmojiLike.ts @@ -4,37 +4,37 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), - emoji_id: Type.Union([Type.Number(), Type.String()]), - set: Type.Optional(Type.Union([Type.Boolean(), Type.String()])) + message_id: Type.Union([Type.Number(), Type.String()]), + emoji_id: Type.Union([Type.Number(), Type.String()]), + set: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export class SetMsgEmojiLike extends OneBotAction { - override actionName = ActionName.SetMsgEmojiLike; - override payloadSchema = SchemaData; + override actionName = ActionName.SetMsgEmojiLike; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); - if (!msg) { - throw new Error('msg not found'); - } - if (!payload.emoji_id) { - throw new Error('emojiId not found'); - } - payload.set = payload.set ?? true; - - const msgData = (await this.core.apis.MsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList; - if (!msgData || msgData.length === 0 || !msgData[0]?.msgSeq) { - throw new Error('find msg by msgid error'); - } - - return await this.core.apis.MsgApi.setEmojiLike( - msg.Peer, - msgData[0].msgSeq, - payload.emoji_id.toString(), - typeof payload.set === 'string' ? payload.set === 'true' : !!payload.set - ); + async _handle (payload: Payload) { + const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); + if (!msg) { + throw new Error('msg not found'); } -} \ No newline at end of file + if (!payload.emoji_id) { + throw new Error('emojiId not found'); + } + payload.set = payload.set ?? true; + + const msgData = (await this.core.apis.MsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId])).msgList; + if (!msgData || msgData.length === 0 || !msgData[0]?.msgSeq) { + throw new Error('find msg by msgid error'); + } + + return await this.core.apis.MsgApi.setEmojiLike( + msg.Peer, + msgData[0].msgSeq, + payload.emoji_id.toString(), + typeof payload.set === 'string' ? payload.set === 'true' : !!payload.set + ); + } +} diff --git a/src/onebot/action/new/GetDoubtFriendsAddRequest.ts b/src/onebot/action/new/GetDoubtFriendsAddRequest.ts index 7b8ae921..a20c5ea5 100644 --- a/src/onebot/action/new/GetDoubtFriendsAddRequest.ts +++ b/src/onebot/action/new/GetDoubtFriendsAddRequest.ts @@ -3,16 +3,16 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - count: Type.Number({ default: 50 }), + count: Type.Number({ default: 50 }), }); type Payload = Static; export class GetDoubtFriendsAddRequest extends OneBotAction { - override actionName = ActionName.GetDoubtFriendsAddRequest; - override payloadSchema = SchemaData; + override actionName = ActionName.GetDoubtFriendsAddRequest; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.FriendApi.getDoubtFriendRequest(payload.count); - } + async _handle (payload: Payload) { + return await this.core.apis.FriendApi.getDoubtFriendRequest(payload.count); + } } diff --git a/src/onebot/action/new/SetDoubtFriendsAddRequest.ts b/src/onebot/action/new/SetDoubtFriendsAddRequest.ts index 990d5607..f4fb7184 100644 --- a/src/onebot/action/new/SetDoubtFriendsAddRequest.ts +++ b/src/onebot/action/new/SetDoubtFriendsAddRequest.ts @@ -3,19 +3,19 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - flag: Type.String(), - //注意强制String 非isNumeric 不遵守则不符合设计 - approve: Type.Boolean({ default: true }), - //该字段没有语义 仅做保留 强制为True + flag: Type.String(), + // 注意强制String 非isNumeric 不遵守则不符合设计 + approve: Type.Boolean({ default: true }), + // 该字段没有语义 仅做保留 强制为True }); type Payload = Static; export class SetDoubtFriendsAddRequest extends OneBotAction { - override actionName = ActionName.SetDoubtFriendsAddRequest; - override payloadSchema = SchemaData; + override actionName = ActionName.SetDoubtFriendsAddRequest; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - return await this.core.apis.FriendApi.handleDoubtFriendRequest(payload.flag); - } + async _handle (payload: Payload) { + return await this.core.apis.FriendApi.handleDoubtFriendRequest(payload.flag); + } } diff --git a/src/onebot/action/packet/GetPacketStatus.ts b/src/onebot/action/packet/GetPacketStatus.ts index bf0bb3c3..3a3d61f7 100644 --- a/src/onebot/action/packet/GetPacketStatus.ts +++ b/src/onebot/action/packet/GetPacketStatus.ts @@ -1,24 +1,23 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName, BaseCheckResult } from '@/onebot/action/router'; - export abstract class GetPacketStatusDepends extends OneBotAction { - protected override async check(payload: PT): Promise{ - if (!this.core.apis.PacketApi.packetStatus) { - return { - valid: false, - message: 'packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!' + + protected override async check (payload: PT): Promise { + if (!this.core.apis.PacketApi.packetStatus) { + return { + valid: false, + message: 'packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!' + '错误堆栈信息:' + this.core.apis.PacketApi.clientLogStack, - }; - } - return await super.check(payload); + }; } + return await super.check(payload); + } } export class GetPacketStatus extends GetPacketStatusDepends { - override actionName = ActionName.GetPacketStatus; + override actionName = ActionName.GetPacketStatus; - async _handle() { - return; - } + async _handle () { + + } } diff --git a/src/onebot/action/packet/GetRkeyEx.ts b/src/onebot/action/packet/GetRkeyEx.ts index d330b8ee..e4af133c 100644 --- a/src/onebot/action/packet/GetRkeyEx.ts +++ b/src/onebot/action/packet/GetRkeyEx.ts @@ -2,17 +2,17 @@ import { ActionName } from '@/onebot/action/router'; import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; export class GetRkeyEx extends GetPacketStatusDepends { - override actionName = ActionName.GetRkeyEx; + override actionName = ActionName.GetRkeyEx; - async _handle() { - let rkeys = await this.core.apis.PacketApi.pkt.operation.FetchRkey(); - return rkeys.map(rkey => { - return { - type: rkey.type === 10 ? "private" : "group", - rkey: rkey.rkey, - created_at: rkey.time, - ttl: rkey.ttl, - }; - }); - } -} \ No newline at end of file + async _handle () { + const rkeys = await this.core.apis.PacketApi.pkt.operation.FetchRkey(); + return rkeys.map(rkey => { + return { + type: rkey.type === 10 ? 'private' : 'group', + rkey: rkey.rkey, + created_at: rkey.time, + ttl: rkey.ttl, + }; + }); + } +} diff --git a/src/onebot/action/packet/GetRkeyServer.ts b/src/onebot/action/packet/GetRkeyServer.ts index ebfa7049..95e60c6f 100644 --- a/src/onebot/action/packet/GetRkeyServer.ts +++ b/src/onebot/action/packet/GetRkeyServer.ts @@ -2,37 +2,38 @@ import { ActionName } from '@/onebot/action/router'; import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; export class GetRkeyServer extends GetPacketStatusDepends { - override actionName = ActionName.GetRkeyServer; + override actionName = ActionName.GetRkeyServer; - private rkeyCache: { - private_rkey?: string; - group_rkey?: string; - expired_time?: number; - name: string; - } | null = null; - private expiryTime: number | null = null; + private rkeyCache: { + private_rkey?: string; + group_rkey?: string; + expired_time?: number; + name: string; + } | null = null; - async _handle() { - // 检查缓存是否有效 - if (this.expiryTime && this.expiryTime > Math.floor(Date.now() / 1000) && this.rkeyCache) { - return this.rkeyCache; - } + private expiryTime: number | null = null; - // 获取新的 Rkey - let rkeys = await this.core.apis.PacketApi.pkt.operation.FetchRkey(); - let privateRkeyItem = rkeys.filter(rkey => rkey.type === 10)[0]; - let groupRkeyItem = rkeys.filter(rkey => rkey.type === 20)[0]; - - this.expiryTime = Math.floor(Date.now() / 1000) + Math.min(+groupRkeyItem!.ttl.toString(),+privateRkeyItem!.ttl.toString()); - - // 更新缓存 - this.rkeyCache = { - private_rkey: privateRkeyItem ? privateRkeyItem.rkey : undefined, - group_rkey: groupRkeyItem ? groupRkeyItem.rkey : undefined, - expired_time: this.expiryTime, - name: "NapCat 4" - }; - - return this.rkeyCache; + async _handle () { + // 检查缓存是否有效 + if (this.expiryTime && this.expiryTime > Math.floor(Date.now() / 1000) && this.rkeyCache) { + return this.rkeyCache; } -} \ No newline at end of file + + // 获取新的 Rkey + const rkeys = await this.core.apis.PacketApi.pkt.operation.FetchRkey(); + const privateRkeyItem = rkeys.filter(rkey => rkey.type === 10)[0]; + const groupRkeyItem = rkeys.filter(rkey => rkey.type === 20)[0]; + + this.expiryTime = Math.floor(Date.now() / 1000) + Math.min(+groupRkeyItem!.ttl.toString(), +privateRkeyItem!.ttl.toString()); + + // 更新缓存 + this.rkeyCache = { + private_rkey: privateRkeyItem ? privateRkeyItem.rkey : undefined, + group_rkey: groupRkeyItem ? groupRkeyItem.rkey : undefined, + expired_time: this.expiryTime, + name: 'NapCat 4', + }; + + return this.rkeyCache; + } +} diff --git a/src/onebot/action/packet/SendPoke.ts b/src/onebot/action/packet/SendPoke.ts index a767932b..cab04ca2 100644 --- a/src/onebot/action/packet/SendPoke.ts +++ b/src/onebot/action/packet/SendPoke.ts @@ -3,36 +3,35 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - group_id: Type.Optional(Type.String()), - user_id: Type.Optional(Type.String()), - target_id: Type.Optional(Type.String()), + group_id: Type.Optional(Type.String()), + user_id: Type.Optional(Type.String()), + target_id: Type.Optional(Type.String()), }); type Payload = Static; export class SendPokeBase extends GetPacketStatusDepends { - override payloadSchema = SchemaData; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - // 这里的 !! 可以传入空字符串 忽略这些数据有利用接口统一接口 - const target_id = !!payload.target_id ? payload.target_id : payload.user_id; - const peer_id = !!payload.group_id ? payload.group_id : payload.user_id; + async _handle (payload: Payload) { + // 这里的 !! 可以传入空字符串 忽略这些数据有利用接口统一接口 + const target_id = payload.target_id ? payload.target_id : payload.user_id; + const peer_id = payload.group_id ? payload.group_id : payload.user_id; - const is_group = !!payload.group_id; - if (!target_id || !peer_id) { - throw new Error('请检查参数,缺少 user_id 或 group_id'); - } - - await this.core.apis.PacketApi.pkt.operation.SendPoke(is_group, +peer_id, +target_id); + const is_group = !!payload.group_id; + if (!target_id || !peer_id) { + throw new Error('请检查参数,缺少 user_id 或 group_id'); } -} + await this.core.apis.PacketApi.pkt.operation.SendPoke(is_group, +peer_id, +target_id); + } +} export class SendPoke extends SendPokeBase { - override actionName = ActionName.SendPoke; + override actionName = ActionName.SendPoke; } export class GroupPoke extends SendPokeBase { - override actionName = ActionName.GroupPoke; + override actionName = ActionName.GroupPoke; } export class FriendPoke extends SendPokeBase { - override actionName = ActionName.FriendPoke; + override actionName = ActionName.FriendPoke; } diff --git a/src/onebot/action/packet/SetGroupTodo.ts b/src/onebot/action/packet/SetGroupTodo.ts index 0fc1e3dd..9a417e5c 100644 --- a/src/onebot/action/packet/SetGroupTodo.ts +++ b/src/onebot/action/packet/SetGroupTodo.ts @@ -1,4 +1,3 @@ - import { MessageUnique } from '@/common/message-unique'; import { ChatType, Peer } from '@/core'; import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus'; @@ -6,26 +5,26 @@ import { Static, Type } from '@sinclair/typebox'; import { ActionName } from '../router'; const SchemaData = Type.Object({ - group_id: Type.String(), - message_id: Type.String(), - message_seq: Type.Optional(Type.String()) + group_id: Type.String(), + message_id: Type.String(), + message_seq: Type.Optional(Type.String()), }); type Payload = Static; export class SetGroupTodo extends GetPacketStatusDepends { - override payloadSchema = SchemaData; - override actionName = ActionName.SetGroupTodo; - async _handle(payload: Payload) { - if (payload.message_seq) { - return await this.core.apis.PacketApi.pkt.operation.SetGroupTodo(+payload.group_id, payload.message_seq); - } - const peer: Peer = { - chatType: ChatType.KCHATTYPEGROUP, - peerUid: payload.group_id - }; - const { MsgId, Peer } = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) ?? { Peer: peer, MsgId: payload.message_id }; - let msg = (await this.core.apis.MsgApi.getMsgsByMsgId(Peer, [MsgId])).msgList[0]; - if (!msg) throw new Error('消息不存在'); - await this.core.apis.PacketApi.pkt.operation.SetGroupTodo(+payload.group_id, msg.msgSeq); + override payloadSchema = SchemaData; + override actionName = ActionName.SetGroupTodo; + async _handle (payload: Payload) { + if (payload.message_seq) { + return await this.core.apis.PacketApi.pkt.operation.SetGroupTodo(+payload.group_id, payload.message_seq); } -} \ No newline at end of file + const peer: Peer = { + chatType: ChatType.KCHATTYPEGROUP, + peerUid: payload.group_id, + }; + const { MsgId, Peer } = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) ?? { Peer: peer, MsgId: payload.message_id }; + const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(Peer, [MsgId])).msgList[0]; + if (!msg) throw new Error('消息不存在'); + await this.core.apis.PacketApi.pkt.operation.SetGroupTodo(+payload.group_id, msg.msgSeq); + } +} diff --git a/src/onebot/action/router.ts b/src/onebot/action/router.ts index 41872cf8..9f5fa768 100644 --- a/src/onebot/action/router.ts +++ b/src/onebot/action/router.ts @@ -1,183 +1,183 @@ export type BaseCheckResult = ValidCheckResult | InvalidCheckResult; export interface ValidCheckResult { - valid: true; + valid: true; } export interface InvalidCheckResult { - valid: false; - message: string; + valid: false; + message: string; } export const ActionName = { - // 所有 Normal Stream Api 表示并未流传输 表示与流传输有关 - CleanStreamTempFile: 'clean_stream_temp_file', + // 所有 Normal Stream Api 表示并未流传输 表示与流传输有关 + CleanStreamTempFile: 'clean_stream_temp_file', - // 所有 Upload/Download Stream Api 应当 _stream 结尾 - TestDownloadStream: 'test_download_stream', - UploadFileStream: 'upload_file_stream', - DownloadFileStream: 'download_file_stream', - DownloadFileRecordStream: 'download_file_record_stream', - DownloadFileImageStream: 'download_file_image_stream', + // 所有 Upload/Download Stream Api 应当 _stream 结尾 + TestDownloadStream: 'test_download_stream', + UploadFileStream: 'upload_file_stream', + DownloadFileStream: 'download_file_stream', + DownloadFileRecordStream: 'download_file_record_stream', + DownloadFileImageStream: 'download_file_image_stream', - DelGroupAlbumMedia: 'del_group_album_media', - SetGroupAlbumMediaLike: 'set_group_album_media_like', - DoGroupAlbumComment: 'do_group_album_comment', - GetGroupAlbumMediaList: 'get_group_album_media_list', - UploadImageToQunAlbum: 'upload_image_to_qun_album', - GetQunAlbumList: 'get_qun_album_list', - SetGroupTodo: 'set_group_todo', - SetGroupKickMembers: 'set_group_kick_members', - SetGroupRobotAddOption: 'set_group_robot_add_option', - SetGroupAddOption: 'set_group_add_option', - SetGroupSearch: 'set_group_search', - // new extends 完全差异OneBot类别 - GetDoubtFriendsAddRequest: 'get_doubt_friends_add_request', - SetDoubtFriendsAddRequest: 'set_doubt_friends_add_request', - // napcat - GetRkeyEx: 'get_rkey', - GetRkeyServer: 'get_rkey_server', - SetGroupRemark: 'set_group_remark', - NapCat_GetPrivateFileUrl: 'get_private_file_url', - ClickInlineKeyboardButton: 'click_inline_keyboard_button', - GetUnidirectionalFriendList: 'get_unidirectional_friend_list', - // onebot 11 - SendPrivateMsg: 'send_private_msg', - SendGroupMsg: 'send_group_msg', - SendMsg: 'send_msg', - DeleteMsg: 'delete_msg', - GetMsg: 'get_msg', - GoCQHTTP_GetForwardMsg: 'get_forward_msg', - SendLike: 'send_like', - SetGroupKick: 'set_group_kick', - SetGroupBan: 'set_group_ban', - // SetGroupAnoymousBan : 'set_group_anonymous_ban', - SetGroupWholeBan: 'set_group_whole_ban', - SetGroupAdmin: 'set_group_admin', - // SetGroupAnoymous : 'set_group_anonymous', - SetGroupCard: 'set_group_card', - SetGroupName: 'set_group_name', - SetGroupLeave: 'set_group_leave', - SetSpecialTitle: 'set_group_special_title', - SetFriendAddRequest: 'set_friend_add_request', - SetFriendRemark: 'set_friend_remark', - SetGroupAddRequest: 'set_group_add_request', - GetLoginInfo: 'get_login_info', - GoCQHTTP_GetStrangerInfo: 'get_stranger_info', - GetFriendList: 'get_friend_list', - GetGroupInfo: 'get_group_info', - GetGroupList: 'get_group_list', - GetGroupMemberInfo: 'get_group_member_info', - GetGroupMemberList: 'get_group_member_list', - GetGroupHonorInfo: 'get_group_honor_info', - GetCookies: 'get_cookies', - GetCSRF: 'get_csrf_token', - GetCredentials: 'get_credentials', - GetRecord: 'get_record', - GetImage: 'get_image', - CanSendImage: 'can_send_image', - CanSendRecord: 'can_send_record', - GetStatus: 'get_status', - GetVersionInfo: 'get_version_info', - // Reboot : 'set_restart', - CleanCache: 'clean_cache', - Exit: 'bot_exit', - // go-cqhttp - SetQQProfile: 'set_qq_profile', - // QidianGetAccountInfo : 'qidian_get_account_info', - GoCQHTTP_GetModelShow: '_get_model_show', - GoCQHTTP_SetModelShow: '_set_model_show', - GetOnlineClient: 'get_online_clients', - // GetUnidirectionalFriendList : 'get_unidirectional_friend_list', - GoCQHTTP_DeleteFriend: 'delete_friend', - // DeleteUnidirectionalFriendList : 'delete_unidirectional_friend', - GoCQHTTP_MarkMsgAsRead: 'mark_msg_as_read', - GoCQHTTP_SendGroupForwardMsg: 'send_group_forward_msg', - GoCQHTTP_SendPrivateForwardMsg: 'send_private_forward_msg', - GoCQHTTP_GetGroupMsgHistory: 'get_group_msg_history', - OCRImage: 'ocr_image', - IOCRImage: '.ocr_image', - GetGroupSystemMsg: 'get_group_system_msg', - GoCQHTTP_GetEssenceMsg: 'get_essence_msg_list', - GoCQHTTP_GetGroupAtAllRemain: 'get_group_at_all_remain', - SetGroupPortrait: 'set_group_portrait', - SetEssenceMsg: 'set_essence_msg', - DelEssenceMsg: 'delete_essence_msg', - GoCQHTTP_SendGroupNotice: '_send_group_notice', - GoCQHTTP_GetGroupNotice: '_get_group_notice', - GoCQHTTP_UploadGroupFile: 'upload_group_file', - GOCQHTTP_DeleteGroupFile: 'delete_group_file', - GoCQHTTP_CreateGroupFileFolder: 'create_group_file_folder', - GoCQHTTP_DeleteGroupFileFolder: 'delete_group_folder', - GoCQHTTP_GetGroupFileSystemInfo: 'get_group_file_system_info', - GoCQHTTP_GetGroupRootFiles: 'get_group_root_files', - GoCQHTTP_GetGroupFilesByFolder: 'get_group_files_by_folder', - GOCQHTTP_GetGroupFileUrl: 'get_group_file_url', - GOCQHTTP_UploadPrivateFile: 'upload_private_file', - // GOCQHTTP_ReloadEventFilter : 'reload_event_filter', - GoCQHTTP_DownloadFile: 'download_file', - GoCQHTTP_CheckUrlSafely: 'check_url_safely', - GoCQHTTP_GetWordSlices: '.get_word_slices', - GoCQHTTP_HandleQuickAction: '.handle_quick_operation', + DelGroupAlbumMedia: 'del_group_album_media', + SetGroupAlbumMediaLike: 'set_group_album_media_like', + DoGroupAlbumComment: 'do_group_album_comment', + GetGroupAlbumMediaList: 'get_group_album_media_list', + UploadImageToQunAlbum: 'upload_image_to_qun_album', + GetQunAlbumList: 'get_qun_album_list', + SetGroupTodo: 'set_group_todo', + SetGroupKickMembers: 'set_group_kick_members', + SetGroupRobotAddOption: 'set_group_robot_add_option', + SetGroupAddOption: 'set_group_add_option', + SetGroupSearch: 'set_group_search', + // new extends 完全差异OneBot类别 + GetDoubtFriendsAddRequest: 'get_doubt_friends_add_request', + SetDoubtFriendsAddRequest: 'set_doubt_friends_add_request', + // napcat + GetRkeyEx: 'get_rkey', + GetRkeyServer: 'get_rkey_server', + SetGroupRemark: 'set_group_remark', + NapCat_GetPrivateFileUrl: 'get_private_file_url', + ClickInlineKeyboardButton: 'click_inline_keyboard_button', + GetUnidirectionalFriendList: 'get_unidirectional_friend_list', + // onebot 11 + SendPrivateMsg: 'send_private_msg', + SendGroupMsg: 'send_group_msg', + SendMsg: 'send_msg', + DeleteMsg: 'delete_msg', + GetMsg: 'get_msg', + GoCQHTTP_GetForwardMsg: 'get_forward_msg', + SendLike: 'send_like', + SetGroupKick: 'set_group_kick', + SetGroupBan: 'set_group_ban', + // SetGroupAnoymousBan : 'set_group_anonymous_ban', + SetGroupWholeBan: 'set_group_whole_ban', + SetGroupAdmin: 'set_group_admin', + // SetGroupAnoymous : 'set_group_anonymous', + SetGroupCard: 'set_group_card', + SetGroupName: 'set_group_name', + SetGroupLeave: 'set_group_leave', + SetSpecialTitle: 'set_group_special_title', + SetFriendAddRequest: 'set_friend_add_request', + SetFriendRemark: 'set_friend_remark', + SetGroupAddRequest: 'set_group_add_request', + GetLoginInfo: 'get_login_info', + GoCQHTTP_GetStrangerInfo: 'get_stranger_info', + GetFriendList: 'get_friend_list', + GetGroupInfo: 'get_group_info', + GetGroupList: 'get_group_list', + GetGroupMemberInfo: 'get_group_member_info', + GetGroupMemberList: 'get_group_member_list', + GetGroupHonorInfo: 'get_group_honor_info', + GetCookies: 'get_cookies', + GetCSRF: 'get_csrf_token', + GetCredentials: 'get_credentials', + GetRecord: 'get_record', + GetImage: 'get_image', + CanSendImage: 'can_send_image', + CanSendRecord: 'can_send_record', + GetStatus: 'get_status', + GetVersionInfo: 'get_version_info', + // Reboot : 'set_restart', + CleanCache: 'clean_cache', + Exit: 'bot_exit', + // go-cqhttp + SetQQProfile: 'set_qq_profile', + // QidianGetAccountInfo : 'qidian_get_account_info', + GoCQHTTP_GetModelShow: '_get_model_show', + GoCQHTTP_SetModelShow: '_set_model_show', + GetOnlineClient: 'get_online_clients', + // GetUnidirectionalFriendList : 'get_unidirectional_friend_list', + GoCQHTTP_DeleteFriend: 'delete_friend', + // DeleteUnidirectionalFriendList : 'delete_unidirectional_friend', + GoCQHTTP_MarkMsgAsRead: 'mark_msg_as_read', + GoCQHTTP_SendGroupForwardMsg: 'send_group_forward_msg', + GoCQHTTP_SendPrivateForwardMsg: 'send_private_forward_msg', + GoCQHTTP_GetGroupMsgHistory: 'get_group_msg_history', + OCRImage: 'ocr_image', + IOCRImage: '.ocr_image', + GetGroupSystemMsg: 'get_group_system_msg', + GoCQHTTP_GetEssenceMsg: 'get_essence_msg_list', + GoCQHTTP_GetGroupAtAllRemain: 'get_group_at_all_remain', + SetGroupPortrait: 'set_group_portrait', + SetEssenceMsg: 'set_essence_msg', + DelEssenceMsg: 'delete_essence_msg', + GoCQHTTP_SendGroupNotice: '_send_group_notice', + GoCQHTTP_GetGroupNotice: '_get_group_notice', + GoCQHTTP_UploadGroupFile: 'upload_group_file', + GOCQHTTP_DeleteGroupFile: 'delete_group_file', + GoCQHTTP_CreateGroupFileFolder: 'create_group_file_folder', + GoCQHTTP_DeleteGroupFileFolder: 'delete_group_folder', + GoCQHTTP_GetGroupFileSystemInfo: 'get_group_file_system_info', + GoCQHTTP_GetGroupRootFiles: 'get_group_root_files', + GoCQHTTP_GetGroupFilesByFolder: 'get_group_files_by_folder', + GOCQHTTP_GetGroupFileUrl: 'get_group_file_url', + GOCQHTTP_UploadPrivateFile: 'upload_private_file', + // GOCQHTTP_ReloadEventFilter : 'reload_event_filter', + GoCQHTTP_DownloadFile: 'download_file', + GoCQHTTP_CheckUrlSafely: 'check_url_safely', + GoCQHTTP_GetWordSlices: '.get_word_slices', + GoCQHTTP_HandleQuickAction: '.handle_quick_operation', - // 以下为扩展napcat扩展 - Unknown: 'unknown', - SetDiyOnlineStatus: 'set_diy_online_status', - SharePeer: 'ArkSharePeer', - ShareGroupEx: 'ArkShareGroup', - // RebootNormal : 'reboot_normal', //无快速登录重新启动 - GetRobotUinRange: 'get_robot_uin_range', - SetOnlineStatus: 'set_online_status', - GetFriendsWithCategory: 'get_friends_with_category', - SetQQAvatar: 'set_qq_avatar', - GetFile: 'get_file', - ForwardFriendSingleMsg: 'forward_friend_single_msg', - ForwardGroupSingleMsg: 'forward_group_single_msg', - TranslateEnWordToZn: 'translate_en2zh', - SetMsgEmojiLike: 'set_msg_emoji_like', - GoCQHTTP_SendForwardMsg: 'send_forward_msg', - MarkPrivateMsgAsRead: 'mark_private_msg_as_read', - MarkGroupMsgAsRead: 'mark_group_msg_as_read', - GetFriendMsgHistory: 'get_friend_msg_history', - CreateCollection: 'create_collection', - GetCollectionList: 'get_collection_list', - SetLongNick: 'set_self_longnick', - GetRecentContact: 'get_recent_contact', - _MarkAllMsgAsRead: '_mark_all_as_read', - GetProfileLike: 'get_profile_like', - FetchCustomFace: 'fetch_custom_face', - FetchEmojiLike: 'fetch_emoji_like', - SetInputStatus: 'set_input_status', - GetGroupInfoEx: 'get_group_info_ex', - GetGroupDetailInfo: 'get_group_detail_info', - GetGroupIgnoreAddRequest: 'get_group_ignore_add_request', - DelGroupNotice: '_del_group_notice', - FriendPoke: 'friend_poke', - GroupPoke: 'group_poke', - GetPacketStatus: 'nc_get_packet_status', - GetUserStatus: 'nc_get_user_status', - GetRkey: 'nc_get_rkey', - GetGroupShutList: 'get_group_shut_list', + // 以下为扩展napcat扩展 + Unknown: 'unknown', + SetDiyOnlineStatus: 'set_diy_online_status', + SharePeer: 'ArkSharePeer', + ShareGroupEx: 'ArkShareGroup', + // RebootNormal : 'reboot_normal', //无快速登录重新启动 + GetRobotUinRange: 'get_robot_uin_range', + SetOnlineStatus: 'set_online_status', + GetFriendsWithCategory: 'get_friends_with_category', + SetQQAvatar: 'set_qq_avatar', + GetFile: 'get_file', + ForwardFriendSingleMsg: 'forward_friend_single_msg', + ForwardGroupSingleMsg: 'forward_group_single_msg', + TranslateEnWordToZn: 'translate_en2zh', + SetMsgEmojiLike: 'set_msg_emoji_like', + GoCQHTTP_SendForwardMsg: 'send_forward_msg', + MarkPrivateMsgAsRead: 'mark_private_msg_as_read', + MarkGroupMsgAsRead: 'mark_group_msg_as_read', + GetFriendMsgHistory: 'get_friend_msg_history', + CreateCollection: 'create_collection', + GetCollectionList: 'get_collection_list', + SetLongNick: 'set_self_longnick', + GetRecentContact: 'get_recent_contact', + _MarkAllMsgAsRead: '_mark_all_as_read', + GetProfileLike: 'get_profile_like', + FetchCustomFace: 'fetch_custom_face', + FetchEmojiLike: 'fetch_emoji_like', + SetInputStatus: 'set_input_status', + GetGroupInfoEx: 'get_group_info_ex', + GetGroupDetailInfo: 'get_group_detail_info', + GetGroupIgnoreAddRequest: 'get_group_ignore_add_request', + DelGroupNotice: '_del_group_notice', + FriendPoke: 'friend_poke', + GroupPoke: 'group_poke', + GetPacketStatus: 'nc_get_packet_status', + GetUserStatus: 'nc_get_user_status', + GetRkey: 'nc_get_rkey', + GetGroupShutList: 'get_group_shut_list', - MoveGroupFile: 'move_group_file', - TransGroupFile: 'trans_group_file', - RenameGroupFile: 'rename_group_file', + MoveGroupFile: 'move_group_file', + TransGroupFile: 'trans_group_file', + RenameGroupFile: 'rename_group_file', - GetGuildList: 'get_guild_list', - GetGuildProfile: 'get_guild_service_profile', + GetGuildList: 'get_guild_list', + GetGuildProfile: 'get_guild_service_profile', - GetGroupIgnoredNotifies: 'get_group_ignored_notifies', + GetGroupIgnoredNotifies: 'get_group_ignored_notifies', - SetGroupSign: 'set_group_sign', - SendGroupSign: 'send_group_sign', - SendPacket: 'send_packet', - GetMiniAppArk: 'get_mini_app_ark', - // UploadForwardMsg : "upload_forward_msg", - GetAiRecord: 'get_ai_record', - GetAiCharacters: 'get_ai_characters', - SendGroupAiRecord: 'send_group_ai_record', + SetGroupSign: 'set_group_sign', + SendGroupSign: 'send_group_sign', + SendPacket: 'send_packet', + GetMiniAppArk: 'get_mini_app_ark', + // UploadForwardMsg : "upload_forward_msg", + GetAiRecord: 'get_ai_record', + GetAiCharacters: 'get_ai_characters', + SendGroupAiRecord: 'send_group_ai_record', - GetClientkey: 'get_clientkey', + GetClientkey: 'get_clientkey', - SendPoke: 'send_poke', + SendPoke: 'send_poke', } as const; diff --git a/src/onebot/action/stream/BaseDownloadStream.ts b/src/onebot/action/stream/BaseDownloadStream.ts index 7a7379ea..6d7b0a04 100644 --- a/src/onebot/action/stream/BaseDownloadStream.ts +++ b/src/onebot/action/stream/BaseDownloadStream.ts @@ -4,96 +4,95 @@ import fs from 'fs'; import { FileNapCatOneBotUUID } from '@/common/file-uuid'; export interface ResolvedFileInfo { - downloadPath: string; - fileName: string; - fileSize: number; + downloadPath: string; + fileName: string; + fileSize: number; } export interface DownloadResult { - // 文件信息 - file_name?: string; - file_size?: number; - chunk_size?: number; + // 文件信息 + file_name?: string; + file_size?: number; + chunk_size?: number; - // 分片数据 - index?: number; - data?: string; - size?: number; - progress?: number; - base64_size?: number; + // 分片数据 + index?: number; + data?: string; + size?: number; + progress?: number; + base64_size?: number; - // 完成信息 - total_chunks?: number; - total_bytes?: number; - message?: string; - data_type?: 'file_info' | 'file_chunk' | 'file_complete'; + // 完成信息 + total_chunks?: number; + total_bytes?: number; + message?: string; + data_type?: 'file_info' | 'file_chunk' | 'file_complete'; - // 可选扩展字段 - width?: number; - height?: number; - out_format?: string; + // 可选扩展字段 + width?: number; + height?: number; + out_format?: string; } export abstract class BaseDownloadStream extends OneBotAction> { - protected async resolveDownload(file?: string): Promise { - const target = file || ''; - let downloadPath = ''; - let fileName = ''; - let fileSize = 0; + protected async resolveDownload (file?: string): Promise { + const target = file || ''; + let downloadPath = ''; + let fileName = ''; + let fileSize = 0; - const contextMsgFile = FileNapCatOneBotUUID.decode(target); - if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { - const { peer, msgId, elementId } = contextMsgFile; - downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); - const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList - .find(msg => msg.msgId === msgId); - const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); - const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; - if (!mixElementInner) throw new Error('element not found'); - fileSize = parseInt(mixElementInner.fileSize?.toString() ?? '0'); - fileName = mixElementInner.fileName ?? ''; - return { downloadPath, fileName, fileSize }; - } - - const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(target); - if (contextModelIdFile && contextModelIdFile.modelId) { - const { peer, modelId } = contextModelIdFile; - downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); - return { downloadPath, fileName, fileSize }; - } - - const searchResult = (await this.core.apis.FileApi.searchForFile([target])); - if (searchResult) { - downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, parseInt(searchResult.fileSize)); - fileSize = parseInt(searchResult.fileSize); - fileName = searchResult.fileName; - return { downloadPath, fileName, fileSize }; - } - - throw new Error('file not found'); + const contextMsgFile = FileNapCatOneBotUUID.decode(target); + if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { + const { peer, msgId, elementId } = contextMsgFile; + downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); + const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList + .find(msg => msg.msgId === msgId); + const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); + const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; + if (!mixElementInner) throw new Error('element not found'); + fileSize = parseInt(mixElementInner.fileSize?.toString() ?? '0'); + fileName = mixElementInner.fileName ?? ''; + return { downloadPath, fileName, fileSize }; } - protected async streamFileChunks(req: OneBotRequestToolkit, streamPath: string, chunkSize: number, chunkDataType: string): Promise<{ totalChunks: number; totalBytes: number }> - { - const stats = await fs.promises.stat(streamPath); - const totalSize = stats.size; - const readStream = fs.createReadStream(streamPath, { highWaterMark: chunkSize }); - let chunkIndex = 0; - let bytesRead = 0; - for await (const chunk of readStream) { - const base64Chunk = (chunk as Buffer).toString('base64'); - bytesRead += (chunk as Buffer).length; - await req.send({ - type: StreamStatus.Stream, - data_type: chunkDataType, - index: chunkIndex, - data: base64Chunk, - size: (chunk as Buffer).length, - progress: Math.round((bytesRead / totalSize) * 100), - base64_size: base64Chunk.length - } as unknown as StreamPacket); - chunkIndex++; - } - return { totalChunks: chunkIndex, totalBytes: bytesRead }; + const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(target); + if (contextModelIdFile && contextModelIdFile.modelId) { + const { peer, modelId } = contextModelIdFile; + downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); + return { downloadPath, fileName, fileSize }; } + + const searchResult = (await this.core.apis.FileApi.searchForFile([target])); + if (searchResult) { + downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, parseInt(searchResult.fileSize)); + fileSize = parseInt(searchResult.fileSize); + fileName = searchResult.fileName; + return { downloadPath, fileName, fileSize }; + } + + throw new Error('file not found'); + } + + protected async streamFileChunks (req: OneBotRequestToolkit, streamPath: string, chunkSize: number, chunkDataType: string): Promise<{ totalChunks: number; totalBytes: number }> { + const stats = await fs.promises.stat(streamPath); + const totalSize = stats.size; + const readStream = fs.createReadStream(streamPath, { highWaterMark: chunkSize }); + let chunkIndex = 0; + let bytesRead = 0; + for await (const chunk of readStream) { + const base64Chunk = (chunk as Buffer).toString('base64'); + bytesRead += (chunk as Buffer).length; + await req.send({ + type: StreamStatus.Stream, + data_type: chunkDataType, + index: chunkIndex, + data: base64Chunk, + size: (chunk as Buffer).length, + progress: Math.round((bytesRead / totalSize) * 100), + base64_size: base64Chunk.length, + } as unknown as StreamPacket); + chunkIndex++; + } + return { totalChunks: chunkIndex, totalBytes: bytesRead }; + } } diff --git a/src/onebot/action/stream/CleanStreamTempFile.ts b/src/onebot/action/stream/CleanStreamTempFile.ts index 48928fcd..6776d037 100644 --- a/src/onebot/action/stream/CleanStreamTempFile.ts +++ b/src/onebot/action/stream/CleanStreamTempFile.ts @@ -4,30 +4,29 @@ import { join } from 'node:path'; import { readdir, unlink } from 'node:fs/promises'; export class CleanStreamTempFile extends OneBotAction { - override actionName = ActionName.CleanStreamTempFile; + override actionName = ActionName.CleanStreamTempFile; - async _handle(_payload: void): Promise { + async _handle (_payload: void): Promise { + try { + // 获取临时文件夹路径 + const tempPath = this.core.NapCatTempPath; + + // 读取文件夹中的所有文件 + const files = await readdir(tempPath); + + // 删除每个文件 + const deletePromises = files.map(async (file) => { + const filePath = join(tempPath, file); try { - // 获取临时文件夹路径 - const tempPath = this.core.NapCatTempPath; - - // 读取文件夹中的所有文件 - const files = await readdir(tempPath); - - // 删除每个文件 - const deletePromises = files.map(async (file) => { - const filePath = join(tempPath, file); - try { - await unlink(filePath); - this.core.context.logger.log(`已删除文件: ${filePath}`); - } catch (err: unknown) { - this.core.context.logger.log(`删除文件 ${filePath} 失败: ${(err as Error).message}`); - - } - }); - await Promise.all(deletePromises); + await unlink(filePath); + this.core.context.logger.log(`已删除文件: ${filePath}`); } catch (err: unknown) { - this.core.context.logger.log(`清理流临时文件失败: ${(err as Error).message}`); + this.core.context.logger.log(`删除文件 ${filePath} 失败: ${(err as Error).message}`); } + }); + await Promise.all(deletePromises); + } catch (err: unknown) { + this.core.context.logger.log(`清理流临时文件失败: ${(err as Error).message}`); } + } } diff --git a/src/onebot/action/stream/DownloadFileImageStream.ts b/src/onebot/action/stream/DownloadFileImageStream.ts index a80985fd..ba7be4f3 100644 --- a/src/onebot/action/stream/DownloadFileImageStream.ts +++ b/src/onebot/action/stream/DownloadFileImageStream.ts @@ -8,53 +8,52 @@ import { imageSizeFallBack } from '@/image-size'; import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; const SchemaData = Type.Object({ - file: Type.Optional(Type.String()), - file_id: Type.Optional(Type.String()), - chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })) // 默认64KB分块 + file: Type.Optional(Type.String()), + file_id: Type.Optional(Type.String()), + chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })), // 默认64KB分块 }); type Payload = Static; export class DownloadFileImageStream extends BaseDownloadStream { - override actionName = ActionName.DownloadFileImageStream; - override payloadSchema = SchemaData; - override useStream = true; + override actionName = ActionName.DownloadFileImageStream; + override payloadSchema = SchemaData; + override useStream = true; - async _handle(payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { - try { - payload.file ||= payload.file_id || ''; - const chunkSize = payload.chunk_size || 64 * 1024; + async _handle (payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { + try { + payload.file ||= payload.file_id || ''; + const chunkSize = payload.chunk_size || 64 * 1024; - const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); + const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); - const stats = await fs.promises.stat(downloadPath); - const totalSize = fileSize || stats.size; - const { width, height } = await imageSizeFallBack(downloadPath); + const stats = await fs.promises.stat(downloadPath); + const totalSize = fileSize || stats.size; + const { width, height } = await imageSizeFallBack(downloadPath); - // 发送文件信息(与 DownloadFileStream 对齐,但包含宽高) - await req.send({ - type: StreamStatus.Stream, - data_type: 'file_info', - file_name: fileName, - file_size: totalSize, - chunk_size: chunkSize, - width, - height - }); + // 发送文件信息(与 DownloadFileStream 对齐,但包含宽高) + await req.send({ + type: StreamStatus.Stream, + data_type: 'file_info', + file_name: fileName, + file_size: totalSize, + chunk_size: chunkSize, + width, + height, + }); - const { totalChunks, totalBytes } = await this.streamFileChunks(req, downloadPath, chunkSize, 'file_chunk'); + const { totalChunks, totalBytes } = await this.streamFileChunks(req, downloadPath, chunkSize, 'file_chunk'); - // 返回完成状态(与 DownloadFileStream 对齐) - return { - type: StreamStatus.Response, - data_type: 'file_complete', - total_chunks: totalChunks, - total_bytes: totalBytes, - message: 'Download completed' - }; - - } catch (error) { - throw new Error(`Download failed: ${(error as Error).message}`); - } + // 返回完成状态(与 DownloadFileStream 对齐) + return { + type: StreamStatus.Response, + data_type: 'file_complete', + total_chunks: totalChunks, + total_bytes: totalBytes, + message: 'Download completed', + }; + } catch (error) { + throw new Error(`Download failed: ${(error as Error).message}`); } + } } diff --git a/src/onebot/action/stream/DownloadFileRecordStream.ts b/src/onebot/action/stream/DownloadFileRecordStream.ts index e0fdec1e..4d30481d 100644 --- a/src/onebot/action/stream/DownloadFileRecordStream.ts +++ b/src/onebot/action/stream/DownloadFileRecordStream.ts @@ -1,4 +1,3 @@ - import { ActionName } from '@/onebot/action/router'; import { OneBotRequestToolkit } from '@/onebot/action/OneBotAction'; import { Static, Type } from '@sinclair/typebox'; @@ -7,90 +6,87 @@ import { StreamPacket, StreamStatus } from './StreamBasic'; import fs from 'fs'; import { decode } from 'silk-wasm'; import { FFmpegService } from '@/common/ffmpeg'; -import { BaseDownloadStream } from './BaseDownloadStream'; +import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']; const SchemaData = Type.Object({ - file: Type.Optional(Type.String()), - file_id: Type.Optional(Type.String()), - chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })), // 默认64KB分块 - out_format: Type.Optional(Type.String()) + file: Type.Optional(Type.String()), + file_id: Type.Optional(Type.String()), + chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })), // 默认64KB分块 + out_format: Type.Optional(Type.String()), }); type Payload = Static; -import { DownloadResult } from './BaseDownloadStream'; - export class DownloadFileRecordStream extends BaseDownloadStream { - override actionName = ActionName.DownloadFileRecordStream; - override payloadSchema = SchemaData; - override useStream = true; + override actionName = ActionName.DownloadFileRecordStream; + override payloadSchema = SchemaData; + override useStream = true; - async _handle(payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { - try { - payload.file ||= payload.file_id || ''; - const chunkSize = payload.chunk_size || 64 * 1024; + async _handle (payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { + try { + payload.file ||= payload.file_id || ''; + const chunkSize = payload.chunk_size || 64 * 1024; - const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); + const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); - // 处理输出格式转换 - let streamPath = downloadPath; - if (payload.out_format && typeof payload.out_format === 'string') { - if (!out_format.includes(payload.out_format)) { - throw new Error('转换失败 out_format 字段可能格式不正确'); - } + // 处理输出格式转换 + let streamPath = downloadPath; + if (payload.out_format && typeof payload.out_format === 'string') { + if (!out_format.includes(payload.out_format)) { + throw new Error('转换失败 out_format 字段可能格式不正确'); + } - const pcmFile = `${downloadPath}.pcm`; - const outputFile = `${downloadPath}.${payload.out_format}`; + const pcmFile = `${downloadPath}.pcm`; + const outputFile = `${downloadPath}.${payload.out_format}`; - try { - // 如果已存在目标文件则跳过转换 - await fs.promises.access(outputFile); - streamPath = outputFile; - } catch { - // 尝试解码 silk 到 pcm 再用 ffmpeg 转换 - await this.decodeFile(downloadPath, pcmFile); - await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format); - streamPath = outputFile; - } - } + try { + // 如果已存在目标文件则跳过转换 + await fs.promises.access(outputFile); + streamPath = outputFile; + } catch { + // 尝试解码 silk 到 pcm 再用 ffmpeg 转换 + await this.decodeFile(downloadPath, pcmFile); + await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format); + streamPath = outputFile; + } + } - const stats = await fs.promises.stat(streamPath); - const totalSize = fileSize || stats.size; + const stats = await fs.promises.stat(streamPath); + const totalSize = fileSize || stats.size; - await req.send({ - type: StreamStatus.Stream, - data_type: 'file_info', - file_name: fileName, - file_size: totalSize, - chunk_size: chunkSize, - out_format: payload.out_format - }); + await req.send({ + type: StreamStatus.Stream, + data_type: 'file_info', + file_name: fileName, + file_size: totalSize, + chunk_size: chunkSize, + out_format: payload.out_format, + }); - const { totalChunks, totalBytes } = await this.streamFileChunks(req, streamPath, chunkSize, 'file_chunk'); + const { totalChunks, totalBytes } = await this.streamFileChunks(req, streamPath, chunkSize, 'file_chunk'); - return { - type: StreamStatus.Response, - data_type: 'file_complete', - total_chunks: totalChunks, - total_bytes: totalBytes, - message: 'Download completed' - }; + return { + type: StreamStatus.Response, + data_type: 'file_complete', + total_chunks: totalChunks, + total_bytes: totalBytes, + message: 'Download completed', + }; + } catch (error) { + throw new Error(`Download failed: ${(error as Error).message}`); + } + } - } catch (error) { - throw new Error(`Download failed: ${(error as Error).message}`); - } - } - - private async decodeFile(inputFile: string, outputFile: string): Promise { - try { - const inputData = await fs.promises.readFile(inputFile); - const decodedData = await decode(inputData, 24000); - await fs.promises.writeFile(outputFile, Buffer.from(decodedData.data)); - } catch (error) { - console.error('Error decoding file:', error); - throw error; - } - } + private async decodeFile (inputFile: string, outputFile: string): Promise { + try { + const inputData = await fs.promises.readFile(inputFile); + const decodedData = await decode(inputData, 24000); + await fs.promises.writeFile(outputFile, Buffer.from(decodedData.data)); + } catch (error) { + console.error('Error decoding file:', error); + throw error; + } + } } diff --git a/src/onebot/action/stream/DownloadFileStream.ts b/src/onebot/action/stream/DownloadFileStream.ts index 7062aab3..317c2c10 100644 --- a/src/onebot/action/stream/DownloadFileStream.ts +++ b/src/onebot/action/stream/DownloadFileStream.ts @@ -6,48 +6,47 @@ import { StreamPacket, StreamStatus } from './StreamBasic'; import fs from 'fs'; import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; const SchemaData = Type.Object({ - file: Type.Optional(Type.String()), - file_id: Type.Optional(Type.String()), - chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })) // 默认64KB分块 + file: Type.Optional(Type.String()), + file_id: Type.Optional(Type.String()), + chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })), // 默认64KB分块 }); type Payload = Static; export class DownloadFileStream extends BaseDownloadStream { - override actionName = ActionName.DownloadFileStream; - override payloadSchema = SchemaData; - override useStream = true; + override actionName = ActionName.DownloadFileStream; + override payloadSchema = SchemaData; + override useStream = true; - async _handle(payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { - try { - payload.file ||= payload.file_id || ''; - const chunkSize = payload.chunk_size || 64 * 1024; + async _handle (payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { + try { + payload.file ||= payload.file_id || ''; + const chunkSize = payload.chunk_size || 64 * 1024; - const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); + const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); - const stats = await fs.promises.stat(downloadPath); - const totalSize = fileSize || stats.size; + const stats = await fs.promises.stat(downloadPath); + const totalSize = fileSize || stats.size; - await req.send({ - type: StreamStatus.Stream, - data_type: 'file_info', - file_name: fileName, - file_size: totalSize, - chunk_size: chunkSize - }); + await req.send({ + type: StreamStatus.Stream, + data_type: 'file_info', + file_name: fileName, + file_size: totalSize, + chunk_size: chunkSize, + }); - const { totalChunks, totalBytes } = await this.streamFileChunks(req, downloadPath, chunkSize, 'file_chunk'); + const { totalChunks, totalBytes } = await this.streamFileChunks(req, downloadPath, chunkSize, 'file_chunk'); - return { - type: StreamStatus.Response, - data_type: 'file_complete', - total_chunks: totalChunks, - total_bytes: totalBytes, - message: 'Download completed' - }; - - } catch (error) { - throw new Error(`Download failed: ${(error as Error).message}`); - } + return { + type: StreamStatus.Response, + data_type: 'file_complete', + total_chunks: totalChunks, + total_bytes: totalBytes, + message: 'Download completed', + }; + } catch (error) { + throw new Error(`Download failed: ${(error as Error).message}`); } + } } diff --git a/src/onebot/action/stream/StreamBasic.ts b/src/onebot/action/stream/StreamBasic.ts index 899a6027..3a0f1c2b 100644 --- a/src/onebot/action/stream/StreamBasic.ts +++ b/src/onebot/action/stream/StreamBasic.ts @@ -1,16 +1,16 @@ -import { OneBotAction, OneBotRequestToolkit } from "../OneBotAction"; -import { NetworkAdapterConfig } from "@/onebot/config/config"; -export type StreamPacketBasic = { - type: StreamStatus; - data_type?: string; +import { OneBotAction, OneBotRequestToolkit } from '../OneBotAction'; +import { NetworkAdapterConfig } from '@/onebot/config/config'; +export type StreamPacketBasic = { + type: StreamStatus; + data_type?: string; }; export type StreamPacket = T & StreamPacketBasic; export enum StreamStatus { - Stream = 'stream', // 分片流数据包 - Response = 'response', // 流最终响应 - Reset = 'reset', // 重置流 - Error = 'error' // 流错误 + Stream = 'stream', // 分片流数据包 + Response = 'response', // 流最终响应 + Reset = 'reset', // 重置流 + Error = 'error', // 流错误 } export abstract class BasicStream extends OneBotAction> { - abstract override _handle(_payload: T, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise>; + abstract override _handle (_payload: T, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise>; } diff --git a/src/onebot/action/stream/TestStreamDownload.ts b/src/onebot/action/stream/TestStreamDownload.ts index 0c636d67..07e0df35 100644 --- a/src/onebot/action/stream/TestStreamDownload.ts +++ b/src/onebot/action/stream/TestStreamDownload.ts @@ -5,28 +5,28 @@ import { NetworkAdapterConfig } from '@/onebot/config/config'; import { StreamPacket, StreamStatus } from './StreamBasic'; const SchemaData = Type.Object({ - error: Type.Optional(Type.Boolean({ default: false })) + error: Type.Optional(Type.Boolean({ default: false })), }); type Payload = Static; export class TestDownloadStream extends OneBotAction> { - override actionName = ActionName.TestDownloadStream; - override payloadSchema = SchemaData; - override useStream = true; + override actionName = ActionName.TestDownloadStream; + override payloadSchema = SchemaData; + override useStream = true; - async _handle(_payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit) { - for (let i = 0; i < 10; i++) { - await req.send({ type: StreamStatus.Stream, data: `Index-> ${i + 1}`, data_type: 'data_chunk' }); - await new Promise(resolve => setTimeout(resolve, 100)); - } - if( _payload.error ){ - throw new Error('This is a test error'); - } - return { - type: StreamStatus.Response, - data_type: 'data_complete', - data: 'Stream transmission complete' - }; + async _handle (_payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit) { + for (let i = 0; i < 10; i++) { + await req.send({ type: StreamStatus.Stream, data: `Index-> ${i + 1}`, data_type: 'data_chunk' }); + await new Promise(resolve => setTimeout(resolve, 100)); } + if (_payload.error) { + throw new Error('This is a test error'); + } + return { + type: StreamStatus.Response, + data_type: 'data_complete', + data: 'Stream transmission complete', + }; + } } diff --git a/src/onebot/action/stream/UploadFileStream.ts b/src/onebot/action/stream/UploadFileStream.ts index 7281104e..f1986dd9 100644 --- a/src/onebot/action/stream/UploadFileStream.ts +++ b/src/onebot/action/stream/UploadFileStream.ts @@ -5,342 +5,339 @@ import { NetworkAdapterConfig } from '@/onebot/config/config'; import { StreamPacket, StreamStatus } from './StreamBasic'; import fs from 'fs'; import { join as joinPath } from 'node:path'; -import { randomUUID } from 'crypto'; -import { createHash } from 'crypto'; +import { randomUUID, createHash } from 'crypto'; + import { unlink } from 'node:fs'; // 简化配置 const CONFIG = { - TIMEOUT: 10 * 60 * 1000, // 10分钟超时 - MEMORY_THRESHOLD: 10 * 1024 * 1024, // 10MB,超过使用磁盘 - MEMORY_LIMIT: 100 * 1024 * 1024 // 100MB内存总限制 + TIMEOUT: 10 * 60 * 1000, // 10分钟超时 + MEMORY_THRESHOLD: 10 * 1024 * 1024, // 10MB,超过使用磁盘 + MEMORY_LIMIT: 100 * 1024 * 1024, // 100MB内存总限制 } as const; const SchemaData = Type.Object({ - stream_id: Type.String(), - chunk_data: Type.Optional(Type.String()), - chunk_index: Type.Optional(Type.Number()), - total_chunks: Type.Optional(Type.Number()), - file_size: Type.Optional(Type.Number()), - expected_sha256: Type.Optional(Type.String()), - is_complete: Type.Optional(Type.Boolean()), - filename: Type.Optional(Type.String()), - reset: Type.Optional(Type.Boolean()), - verify_only: Type.Optional(Type.Boolean()), - file_retention: Type.Number({ default: 5 * 60 * 1000 }) // 默认5分钟 回收 不设置或0为不回收 + stream_id: Type.String(), + chunk_data: Type.Optional(Type.String()), + chunk_index: Type.Optional(Type.Number()), + total_chunks: Type.Optional(Type.Number()), + file_size: Type.Optional(Type.Number()), + expected_sha256: Type.Optional(Type.String()), + is_complete: Type.Optional(Type.Boolean()), + filename: Type.Optional(Type.String()), + reset: Type.Optional(Type.Boolean()), + verify_only: Type.Optional(Type.Boolean()), + file_retention: Type.Number({ default: 5 * 60 * 1000 }), // 默认5分钟 回收 不设置或0为不回收 }); type Payload = Static; // 简化流状态接口 interface StreamState { - id: string; - filename: string; - totalChunks: number; - receivedChunks: number; - missingChunks: Set; + id: string; + filename: string; + totalChunks: number; + receivedChunks: number; + missingChunks: Set; - // 可选属性 - fileSize?: number; - expectedSha256?: string; + // 可选属性 + fileSize?: number; + expectedSha256?: string; - // 存储策略 - useMemory: boolean; - memoryChunks?: Map; - tempDir?: string; - finalPath?: string; - fileRetention?: number; + // 存储策略 + useMemory: boolean; + memoryChunks?: Map; + tempDir?: string; + finalPath?: string; + fileRetention?: number; - // 管理 - createdAt: number; - timeoutId: NodeJS.Timeout; + // 管理 + createdAt: number; + timeoutId: NodeJS.Timeout; } interface StreamResult { - stream_id: string; - status: 'file_created' | 'chunk_received' | 'file_complete'; - received_chunks: number; - total_chunks: number; - file_path?: string; - file_size?: number; - sha256?: string; + stream_id: string; + status: 'file_created' | 'chunk_received' | 'file_complete'; + received_chunks: number; + total_chunks: number; + file_path?: string; + file_size?: number; + sha256?: string; } export class UploadFileStream extends OneBotAction> { - override actionName = ActionName.UploadFileStream; - override payloadSchema = SchemaData; - override useStream = true; + override actionName = ActionName.UploadFileStream; + override payloadSchema = SchemaData; + override useStream = true; - private static streams = new Map(); - private static memoryUsage = 0; + private static streams = new Map(); + private static memoryUsage = 0; - async _handle(payload: Payload, _adaptername: string, _config: NetworkAdapterConfig): Promise> { - const { stream_id, reset, verify_only } = payload; + async _handle (payload: Payload, _adaptername: string, _config: NetworkAdapterConfig): Promise> { + const { stream_id, reset, verify_only } = payload; - if (reset) { - this.cleanupStream(stream_id); - throw new Error('Stream reset completed'); - } - - if (verify_only) { - const stream = UploadFileStream.streams.get(stream_id); - if (!stream) throw new Error('Stream not found'); - return this.getStreamStatus(stream); - } - - const stream = this.getOrCreateStream(payload); - - if (payload.chunk_data && payload.chunk_index !== undefined) { - return await this.processChunk(stream, payload.chunk_data, payload.chunk_index); - } - - if (payload.is_complete || stream.receivedChunks === stream.totalChunks) { - return await this.completeStream(stream); - } - - return this.getStreamStatus(stream); + if (reset) { + this.cleanupStream(stream_id); + throw new Error('Stream reset completed'); } - - - private getOrCreateStream(payload: Payload): StreamState { - let stream = UploadFileStream.streams.get(payload.stream_id); - - if (!stream) { - if (!payload.total_chunks) { - throw new Error('total_chunks required for new stream'); - } - stream = this.createStream(payload); - } - - return stream; + if (verify_only) { + const stream = UploadFileStream.streams.get(stream_id); + if (!stream) throw new Error('Stream not found'); + return this.getStreamStatus(stream); } - private createStream(payload: Payload): StreamState { - const { stream_id, total_chunks, file_size, filename, expected_sha256 } = payload; + const stream = this.getOrCreateStream(payload); - const useMemory = this.shouldUseMemory(file_size); - if (useMemory && file_size && (UploadFileStream.memoryUsage + file_size) > CONFIG.MEMORY_LIMIT) { - throw new Error('Memory limit exceeded'); - } + if (payload.chunk_data && payload.chunk_index !== undefined) { + return await this.processChunk(stream, payload.chunk_data, payload.chunk_index); + } - const stream: StreamState = { - id: stream_id, - filename: filename || `upload_${randomUUID()}`, - totalChunks: total_chunks!, - receivedChunks: 0, - missingChunks: new Set(Array.from({ length: total_chunks! }, (_, i) => i)), - fileSize: file_size, - expectedSha256: expected_sha256, - useMemory, - createdAt: Date.now(), - timeoutId: this.setupTimeout(stream_id), - fileRetention: payload.file_retention - }; + if (payload.is_complete || stream.receivedChunks === stream.totalChunks) { + return await this.completeStream(stream); + } + + return this.getStreamStatus(stream); + } + + private getOrCreateStream (payload: Payload): StreamState { + let stream = UploadFileStream.streams.get(payload.stream_id); + + if (!stream) { + if (!payload.total_chunks) { + throw new Error('total_chunks required for new stream'); + } + stream = this.createStream(payload); + } + + return stream; + } + + private createStream (payload: Payload): StreamState { + const { stream_id, total_chunks, file_size, filename, expected_sha256 } = payload; + + const useMemory = this.shouldUseMemory(file_size); + if (useMemory && file_size && (UploadFileStream.memoryUsage + file_size) > CONFIG.MEMORY_LIMIT) { + throw new Error('Memory limit exceeded'); + } + + const stream: StreamState = { + id: stream_id, + filename: filename || `upload_${randomUUID()}`, + totalChunks: total_chunks!, + receivedChunks: 0, + missingChunks: new Set(Array.from({ length: total_chunks! }, (_, i) => i)), + fileSize: file_size, + expectedSha256: expected_sha256, + useMemory, + createdAt: Date.now(), + timeoutId: this.setupTimeout(stream_id), + fileRetention: payload.file_retention, + }; + try { + if (useMemory) { + stream.memoryChunks = new Map(); + if (file_size) UploadFileStream.memoryUsage += file_size; + } else { + this.setupDiskStorage(stream); + } + + UploadFileStream.streams.set(stream_id, stream); + return stream; + } catch (error) { + // 如果设置存储失败,清理已创建的资源 + clearTimeout(stream.timeoutId); + if (stream.tempDir && fs.existsSync(stream.tempDir)) { try { - if (useMemory) { - stream.memoryChunks = new Map(); - if (file_size) UploadFileStream.memoryUsage += file_size; - } else { - this.setupDiskStorage(stream); - } - - UploadFileStream.streams.set(stream_id, stream); - return stream; - } catch (error) { - // 如果设置存储失败,清理已创建的资源 - clearTimeout(stream.timeoutId); - if (stream.tempDir && fs.existsSync(stream.tempDir)) { - try { - fs.rmSync(stream.tempDir, { recursive: true, force: true }); - } catch (cleanupError) { - console.error(`Failed to cleanup temp dir during creation error:`, cleanupError); - } - } - throw error; + fs.rmSync(stream.tempDir, { recursive: true, force: true }); + } catch (cleanupError) { + console.error('Failed to cleanup temp dir during creation error:', cleanupError); } + } + throw error; + } + } + + private shouldUseMemory (fileSize?: number): boolean { + return fileSize !== undefined && fileSize <= CONFIG.MEMORY_THRESHOLD; + } + + private setupDiskStorage (stream: StreamState): void { + const tempDir = joinPath(this.core.NapCatTempPath, `upload_${stream.id}`); + const finalPath = joinPath(this.core.NapCatTempPath, stream.filename); + + fs.mkdirSync(tempDir, { recursive: true }); + + stream.tempDir = tempDir; + stream.finalPath = finalPath; + } + + private setupTimeout (streamId: string): NodeJS.Timeout { + return setTimeout(() => { + console.log(`Stream ${streamId} timeout`); + this.cleanupStream(streamId); + }, CONFIG.TIMEOUT); + } + + private async processChunk (stream: StreamState, chunkData: string, chunkIndex: number): Promise> { + // 验证索引 + if (chunkIndex < 0 || chunkIndex >= stream.totalChunks) { + throw new Error(`Invalid chunk index: ${chunkIndex}`); } - private shouldUseMemory(fileSize?: number): boolean { - return fileSize !== undefined && fileSize <= CONFIG.MEMORY_THRESHOLD; + // 检查重复 + if (!stream.missingChunks.has(chunkIndex)) { + return this.getStreamStatus(stream); } - private setupDiskStorage(stream: StreamState): void { - const tempDir = joinPath(this.core.NapCatTempPath, `upload_${stream.id}`); - const finalPath = joinPath(this.core.NapCatTempPath, stream.filename); + const buffer = Buffer.from(chunkData, 'base64'); - fs.mkdirSync(tempDir, { recursive: true }); - - stream.tempDir = tempDir; - stream.finalPath = finalPath; + // 存储分片 + if (stream.useMemory) { + stream.memoryChunks!.set(chunkIndex, buffer); + } else { + const chunkPath = joinPath(stream.tempDir!, `${chunkIndex}.chunk`); + await fs.promises.writeFile(chunkPath, buffer); } - private setupTimeout(streamId: string): NodeJS.Timeout { - return setTimeout(() => { - console.log(`Stream ${streamId} timeout`); - this.cleanupStream(streamId); - }, CONFIG.TIMEOUT); - } + // 更新状态 + stream.missingChunks.delete(chunkIndex); + stream.receivedChunks++; + this.refreshTimeout(stream); - private async processChunk(stream: StreamState, chunkData: string, chunkIndex: number): Promise> { - // 验证索引 - if (chunkIndex < 0 || chunkIndex >= stream.totalChunks) { - throw new Error(`Invalid chunk index: ${chunkIndex}`); + return { + type: StreamStatus.Stream, + stream_id: stream.id, + status: 'chunk_received', + received_chunks: stream.receivedChunks, + total_chunks: stream.totalChunks, + }; + } + + private refreshTimeout (stream: StreamState): void { + clearTimeout(stream.timeoutId); + stream.timeoutId = this.setupTimeout(stream.id); + } + + private getStreamStatus (stream: StreamState): StreamPacket { + return { + type: StreamStatus.Stream, + stream_id: stream.id, + status: 'file_created', + received_chunks: stream.receivedChunks, + total_chunks: stream.totalChunks, + }; + } + + private async completeStream (stream: StreamState): Promise> { + // 合并分片 + const finalBuffer = stream.useMemory + ? await this.mergeMemoryChunks(stream) + : await this.mergeDiskChunks(stream); + + // 验证SHA256 + const sha256 = this.validateSha256(stream, finalBuffer); + + // 保存文件 + const finalPath = stream.finalPath || joinPath(this.core.NapCatTempPath, stream.filename); + await fs.promises.writeFile(finalPath, finalBuffer); + + // 清理资源但保留文件 + this.cleanupStream(stream.id, false); + if (stream.fileRetention && stream.fileRetention > 0) { + setTimeout(() => { + unlink(finalPath, err => { + if (err) this.core.context.logger.logError(`Failed to delete retained file ${finalPath}:`, err); + }); + }, stream.fileRetention); + } + return { + type: StreamStatus.Response, + stream_id: stream.id, + status: 'file_complete', + received_chunks: stream.receivedChunks, + total_chunks: stream.totalChunks, + file_path: finalPath, + file_size: finalBuffer.length, + sha256, + }; + } + + private async mergeMemoryChunks (stream: StreamState): Promise { + const chunks: Buffer[] = []; + for (let i = 0; i < stream.totalChunks; i++) { + const chunk = stream.memoryChunks!.get(i); + if (!chunk) throw new Error(`Missing memory chunk ${i}`); + chunks.push(chunk); + } + return Buffer.concat(chunks); + } + + private async mergeDiskChunks (stream: StreamState): Promise { + const chunks: Buffer[] = []; + for (let i = 0; i < stream.totalChunks; i++) { + const chunkPath = joinPath(stream.tempDir!, `${i}.chunk`); + if (!fs.existsSync(chunkPath)) throw new Error(`Missing chunk file ${i}`); + chunks.push(await fs.promises.readFile(chunkPath)); + } + return Buffer.concat(chunks); + } + + private validateSha256 (stream: StreamState, buffer: Buffer): string | undefined { + if (!stream.expectedSha256) return undefined; + + const actualSha256 = createHash('sha256').update(buffer).digest('hex'); + if (actualSha256 !== stream.expectedSha256) { + throw new Error(`SHA256 mismatch. Expected: ${stream.expectedSha256}, Got: ${actualSha256}`); + } + return actualSha256; + } + + private cleanupStream (streamId: string, deleteFinalFile = true): void { + const stream = UploadFileStream.streams.get(streamId); + if (!stream) return; + + try { + // 清理超时 + clearTimeout(stream.timeoutId); + + // 清理内存 + if (stream.useMemory) { + if (stream.fileSize) { + UploadFileStream.memoryUsage = Math.max(0, UploadFileStream.memoryUsage - stream.fileSize); } + stream.memoryChunks?.clear(); + } - // 检查重复 - if (!stream.missingChunks.has(chunkIndex)) { - return this.getStreamStatus(stream); - } - - const buffer = Buffer.from(chunkData, 'base64'); - - // 存储分片 - if (stream.useMemory) { - stream.memoryChunks!.set(chunkIndex, buffer); - } else { - const chunkPath = joinPath(stream.tempDir!, `${chunkIndex}.chunk`); - await fs.promises.writeFile(chunkPath, buffer); - } - - // 更新状态 - stream.missingChunks.delete(chunkIndex); - stream.receivedChunks++; - this.refreshTimeout(stream); - - return { - type: StreamStatus.Stream, - stream_id: stream.id, - status: 'chunk_received', - received_chunks: stream.receivedChunks, - total_chunks: stream.totalChunks - }; - } - - private refreshTimeout(stream: StreamState): void { - clearTimeout(stream.timeoutId); - stream.timeoutId = this.setupTimeout(stream.id); - } - - private getStreamStatus(stream: StreamState): StreamPacket { - return { - type: StreamStatus.Stream, - stream_id: stream.id, - status: 'file_created', - received_chunks: stream.receivedChunks, - total_chunks: stream.totalChunks - }; - } - - private async completeStream(stream: StreamState): Promise> { - // 合并分片 - const finalBuffer = stream.useMemory ? - await this.mergeMemoryChunks(stream) : - await this.mergeDiskChunks(stream); - - // 验证SHA256 - const sha256 = this.validateSha256(stream, finalBuffer); - - // 保存文件 - const finalPath = stream.finalPath || joinPath(this.core.NapCatTempPath, stream.filename); - await fs.promises.writeFile(finalPath, finalBuffer); - - // 清理资源但保留文件 - this.cleanupStream(stream.id, false); - if (stream.fileRetention && stream.fileRetention > 0) { - setTimeout(() => { - unlink(finalPath, err => { - if (err) this.core.context.logger.logError(`Failed to delete retained file ${finalPath}:`, err); - }); - }, stream.fileRetention); - } - return { - type: StreamStatus.Response, - stream_id: stream.id, - status: 'file_complete', - received_chunks: stream.receivedChunks, - total_chunks: stream.totalChunks, - file_path: finalPath, - file_size: finalBuffer.length, - sha256 - }; - } - - private async mergeMemoryChunks(stream: StreamState): Promise { - const chunks: Buffer[] = []; - for (let i = 0; i < stream.totalChunks; i++) { - const chunk = stream.memoryChunks!.get(i); - if (!chunk) throw new Error(`Missing memory chunk ${i}`); - chunks.push(chunk); - } - return Buffer.concat(chunks); - } - - private async mergeDiskChunks(stream: StreamState): Promise { - const chunks: Buffer[] = []; - for (let i = 0; i < stream.totalChunks; i++) { - const chunkPath = joinPath(stream.tempDir!, `${i}.chunk`); - if (!fs.existsSync(chunkPath)) throw new Error(`Missing chunk file ${i}`); - chunks.push(await fs.promises.readFile(chunkPath)); - } - return Buffer.concat(chunks); - } - - private validateSha256(stream: StreamState, buffer: Buffer): string | undefined { - if (!stream.expectedSha256) return undefined; - - const actualSha256 = createHash('sha256').update(buffer).digest('hex'); - if (actualSha256 !== stream.expectedSha256) { - throw new Error(`SHA256 mismatch. Expected: ${stream.expectedSha256}, Got: ${actualSha256}`); - } - return actualSha256; - } - - private cleanupStream(streamId: string, deleteFinalFile = true): void { - const stream = UploadFileStream.streams.get(streamId); - if (!stream) return; - + // 清理临时文件夹及其所有内容 + if (stream.tempDir) { try { - // 清理超时 - clearTimeout(stream.timeoutId); - - // 清理内存 - if (stream.useMemory) { - if (stream.fileSize) { - UploadFileStream.memoryUsage = Math.max(0, UploadFileStream.memoryUsage - stream.fileSize); - } - stream.memoryChunks?.clear(); - } - - // 清理临时文件夹及其所有内容 - if (stream.tempDir) { - try { - if (fs.existsSync(stream.tempDir)) { - fs.rmSync(stream.tempDir, { recursive: true, force: true }); - console.log(`Cleaned up temp directory: ${stream.tempDir}`); - } - } catch (error) { - console.error(`Failed to cleanup temp directory ${stream.tempDir}:`, error); - } - } - - // 删除最终文件(如果需要) - if (deleteFinalFile && stream.finalPath) { - try { - if (fs.existsSync(stream.finalPath)) { - fs.unlinkSync(stream.finalPath); - console.log(`Deleted final file: ${stream.finalPath}`); - } - } catch (error) { - console.error(`Failed to delete final file ${stream.finalPath}:`, error); - } - } - + if (fs.existsSync(stream.tempDir)) { + fs.rmSync(stream.tempDir, { recursive: true, force: true }); + console.log(`Cleaned up temp directory: ${stream.tempDir}`); + } } catch (error) { - console.error(`Cleanup error for stream ${streamId}:`, error); - } finally { - UploadFileStream.streams.delete(streamId); - console.log(`Stream ${streamId} cleaned up`); + console.error(`Failed to cleanup temp directory ${stream.tempDir}:`, error); } + } + + // 删除最终文件(如果需要) + if (deleteFinalFile && stream.finalPath) { + try { + if (fs.existsSync(stream.finalPath)) { + fs.unlinkSync(stream.finalPath); + console.log(`Deleted final file: ${stream.finalPath}`); + } + } catch (error) { + console.error(`Failed to delete final file ${stream.finalPath}:`, error); + } + } + } catch (error) { + console.error(`Cleanup error for stream ${streamId}:`, error); + } finally { + UploadFileStream.streams.delete(streamId); + console.log(`Stream ${streamId} cleaned up`); } + } } diff --git a/src/onebot/action/system/CanSendImage.ts b/src/onebot/action/system/CanSendImage.ts index 5f105aaa..db769877 100644 --- a/src/onebot/action/system/CanSendImage.ts +++ b/src/onebot/action/system/CanSendImage.ts @@ -2,5 +2,5 @@ import { ActionName } from '@/onebot/action/router'; import { CanSend } from './CanSendRecord'; export default class CanSendImage extends CanSend { - override actionName = ActionName.CanSendImage; + override actionName = ActionName.CanSendImage; } diff --git a/src/onebot/action/system/CanSendRecord.ts b/src/onebot/action/system/CanSendRecord.ts index 033943ba..63868baf 100644 --- a/src/onebot/action/system/CanSendRecord.ts +++ b/src/onebot/action/system/CanSendRecord.ts @@ -2,18 +2,17 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; interface ReturnType { - yes: boolean; + yes: boolean; } export class CanSend extends OneBotAction { - async _handle(): Promise { - return { - yes: true, - }; - } + async _handle (): Promise { + return { + yes: true, + }; + } } - -export default class CanSendRecord extends CanSend{ - override actionName = ActionName.CanSendRecord; +export default class CanSendRecord extends CanSend { + override actionName = ActionName.CanSendRecord; } diff --git a/src/onebot/action/system/CleanCache.ts b/src/onebot/action/system/CleanCache.ts index 7431e7c5..e3c66faf 100644 --- a/src/onebot/action/system/CleanCache.ts +++ b/src/onebot/action/system/CleanCache.ts @@ -4,66 +4,64 @@ import { unlink, readdir } from 'fs/promises'; import path, { join } from 'path'; export class CleanCache extends OneBotAction { - override actionName = ActionName.CleanCache; + override actionName = ActionName.CleanCache; - async _handle() { + async _handle () { + try { + // 获取临时文件夹路径 + const tempPath = this.core.NapCatTempPath; + + // 读取文件夹中的所有文件 + const files = await readdir(tempPath); + + // 删除每个文件 + const deletePromises = files.map(async (file) => { + const filePath = join(tempPath, file); try { - // 获取临时文件夹路径 - const tempPath = this.core.NapCatTempPath; + await unlink(filePath); + this.core.context.logger.log(`已删除文件: ${filePath}`); + } catch (err: unknown) { + this.core.context.logger.log(`删除文件 ${filePath} 失败: ${(err as Error).message}`); + } + }); - // 读取文件夹中的所有文件 - const files = await readdir(tempPath); + // 等待所有删除操作完成 + await Promise.all(deletePromises); + const basic_path = path.join(this.core.dataPath, this.core.selfInfo.uin || '10001', 'nt_qq', 'nt_data'); - // 删除每个文件 - const deletePromises = files.map(async (file) => { - const filePath = join(tempPath, file); - try { - await unlink(filePath); - this.core.context.logger.log(`已删除文件: ${filePath}`); - } catch (err: unknown) { - this.core.context.logger.log(`删除文件 ${filePath} 失败: ${(err as Error).message}`); + // 需要清理的目录列表 + const dirsToClean = ['Pic', 'Ptt', 'Video', 'File', 'log']; - } + // 清理每个指定目录 + for (const dir of dirsToClean) { + const dirPath = path.join(basic_path, dir); + try { + // 检查目录是否存在 + const files = await readdir(dirPath).catch(() => null); + if (files) { + // 删除目录下的所有文件 + const dirDeletePromises = files.map(async (file) => { + const filePath = path.join(dirPath, file); + try { + await unlink(filePath); + this.core.context.logger.log(`已删除文件: ${filePath}`); + } catch (err: unknown) { + this.core.context.logger.log(`删除文件 ${filePath} 失败: ${(err as Error).message}`); + } }); - // 等待所有删除操作完成 - await Promise.all(deletePromises); - let basic_path = path.join(this.core.dataPath, this.core.selfInfo.uin || '10001', 'nt_qq', 'nt_data'); - - // 需要清理的目录列表 - const dirsToClean = ['Pic', 'Ptt', 'Video', 'File', 'log']; - - // 清理每个指定目录 - for (const dir of dirsToClean) { - const dirPath = path.join(basic_path, dir); - try { - // 检查目录是否存在 - const files = await readdir(dirPath).catch(() => null); - if (files) { - // 删除目录下的所有文件 - const dirDeletePromises = files.map(async (file) => { - const filePath = path.join(dirPath, file); - try { - await unlink(filePath); - this.core.context.logger.log(`已删除文件: ${filePath}`); - } catch (err: unknown) { - this.core.context.logger.log(`删除文件 ${filePath} 失败: ${(err as Error).message}`); - } - }); - - await Promise.all(dirDeletePromises); - this.core.context.logger.log(`目录清理完成: ${dirPath}`); - } - } catch (err: unknown) { - this.core.context.logger.log(`清理目录 ${dirPath} 失败: ${(err as Error).message}`); - } - } - - - this.core.context.logger.log(`临时文件夹清理完成: ${tempPath}`); + await Promise.all(dirDeletePromises); + this.core.context.logger.log(`目录清理完成: ${dirPath}`); + } } catch (err: unknown) { - this.core.context.logger.log(`清理缓存失败: ${(err as Error).message}`); - throw err; + this.core.context.logger.log(`清理目录 ${dirPath} 失败: ${(err as Error).message}`); } + } + + this.core.context.logger.log(`临时文件夹清理完成: ${tempPath}`); + } catch (err: unknown) { + this.core.context.logger.log(`清理缓存失败: ${(err as Error).message}`); + throw err; } -} \ No newline at end of file + } +} diff --git a/src/onebot/action/system/GetCSRF.ts b/src/onebot/action/system/GetCSRF.ts index a1909615..b1270522 100644 --- a/src/onebot/action/system/GetCSRF.ts +++ b/src/onebot/action/system/GetCSRF.ts @@ -2,15 +2,15 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; export class GetCSRF extends OneBotAction { - override actionName = ActionName.GetCSRF; + override actionName = ActionName.GetCSRF; - async _handle() { - const sKey = await this.core.apis.UserApi.getSKey(); - if (!sKey) { - throw new Error('SKey is undefined'); - } - return { - token: +this.core.apis.WebApi.getBknFromSKey(sKey), - }; + async _handle () { + const sKey = await this.core.apis.UserApi.getSKey(); + if (!sKey) { + throw new Error('SKey is undefined'); } + return { + token: +this.core.apis.WebApi.getBknFromSKey(sKey), + }; + } } diff --git a/src/onebot/action/system/GetCredentials.ts b/src/onebot/action/system/GetCredentials.ts index 90ef79ca..3cb8d371 100644 --- a/src/onebot/action/system/GetCredentials.ts +++ b/src/onebot/action/system/GetCredentials.ts @@ -3,26 +3,25 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; interface Response { - cookies: string, - token: number + cookies: string, + token: number } const SchemaData = Type.Object({ - domain: Type.String() + domain: Type.String(), }); type Payload = Static; - export class GetCredentials extends OneBotAction { - override actionName = ActionName.GetCredentials; - override payloadSchema = SchemaData; + override actionName = ActionName.GetCredentials; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const cookiesObject = await this.core.apis.UserApi.getCookies(payload.domain); - //把获取到的cookiesObject转换成 k=v; 格式字符串拼接在一起 - const cookies = Object.entries(cookiesObject).map(([key, value]) => `${key}=${value}`).join('; '); - const bkn = cookiesObject?.['skey'] ? this.core.apis.WebApi.getBknFromCookie(cookiesObject) : ''; - return { cookies: cookies, token: +bkn }; - } + async _handle (payload: Payload) { + const cookiesObject = await this.core.apis.UserApi.getCookies(payload.domain); + // 把获取到的cookiesObject转换成 k=v; 格式字符串拼接在一起 + const cookies = Object.entries(cookiesObject).map(([key, value]) => `${key}=${value}`).join('; '); + const bkn = cookiesObject?.['skey'] ? this.core.apis.WebApi.getBknFromCookie(cookiesObject) : ''; + return { cookies, token: +bkn }; + } } diff --git a/src/onebot/action/system/GetLoginInfo.ts b/src/onebot/action/system/GetLoginInfo.ts index ca8ba296..ff59defa 100644 --- a/src/onebot/action/system/GetLoginInfo.ts +++ b/src/onebot/action/system/GetLoginInfo.ts @@ -4,11 +4,11 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; class GetLoginInfo extends OneBotAction { - override actionName = ActionName.GetLoginInfo; + override actionName = ActionName.GetLoginInfo; - async _handle() { - return OB11Construct.selfInfo(this.core.selfInfo); - } + async _handle () { + return OB11Construct.selfInfo(this.core.selfInfo); + } } export default GetLoginInfo; diff --git a/src/onebot/action/system/GetStatus.ts b/src/onebot/action/system/GetStatus.ts index c698c085..e0a05605 100644 --- a/src/onebot/action/system/GetStatus.ts +++ b/src/onebot/action/system/GetStatus.ts @@ -1,19 +1,19 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; -interface ResponseType{ - online: boolean; - good: boolean; - stat: unknown; +interface ResponseType { + online: boolean; + good: boolean; + stat: unknown; } export default class GetStatus extends OneBotAction { - override actionName = ActionName.GetStatus; + override actionName = ActionName.GetStatus; - async _handle(): Promise { - return { - online: !!this.core.selfInfo.online, - good: true, - stat: {}, - }; - } + async _handle (): Promise { + return { + online: !!this.core.selfInfo.online, + good: true, + stat: {}, + }; + } } diff --git a/src/onebot/action/system/GetSystemMsg.ts b/src/onebot/action/system/GetSystemMsg.ts index af712813..a63fb1f8 100644 --- a/src/onebot/action/system/GetSystemMsg.ts +++ b/src/onebot/action/system/GetSystemMsg.ts @@ -5,50 +5,50 @@ import { Notify } from '@/onebot/types'; import { Static, Type } from '@sinclair/typebox'; interface RetData { - invited_requests: Notify[]; - InvitedRequest: Notify[]; - join_requests: Notify[]; + invited_requests: Notify[]; + InvitedRequest: Notify[]; + join_requests: Notify[]; } const SchemaData = Type.Object({ - count: Type.Union([Type.Number(), Type.String()], { default: 50 }), + count: Type.Union([Type.Number(), Type.String()], { default: 50 }), }); type Payload = Static; export class GetGroupSystemMsg extends OneBotAction { - override actionName = ActionName.GetGroupSystemMsg; - override payloadSchema = SchemaData; + override actionName = ActionName.GetGroupSystemMsg; + override payloadSchema = SchemaData; - async _handle(params: Payload): Promise { - const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, +params.count); - const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] }; + async _handle (params: Payload): Promise { + const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, +params.count); + const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] }; - const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => { - const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0; - const actorUin = SSNotify.user2?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2.uid) : 0; - const commonData = { - request_id: +SSNotify.seq, - invitor_uin: invitorUin, - invitor_nick: SSNotify.user1?.nickName, - group_id: +SSNotify.group?.groupCode, - message: SSNotify?.postscript, - group_name: SSNotify.group?.groupName, - checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE, - actor: actorUin, - requester_nick: SSNotify.user1?.nickName, - }; + const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => { + const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0; + const actorUin = SSNotify.user2?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2.uid) : 0; + const commonData = { + request_id: +SSNotify.seq, + invitor_uin: invitorUin, + invitor_nick: SSNotify.user1?.nickName, + group_id: +SSNotify.group?.groupCode, + message: SSNotify?.postscript, + group_name: SSNotify.group?.groupName, + checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE, + actor: actorUin, + requester_nick: SSNotify.user1?.nickName, + }; - if (SSNotify.type === 1) { - retData.InvitedRequest.push(commonData); - } else if (SSNotify.type === 7) { - retData.join_requests.push(commonData); - } - }); + if (SSNotify.type === 1) { + retData.InvitedRequest.push(commonData); + } else if (SSNotify.type === 7) { + retData.join_requests.push(commonData); + } + }); - await Promise.all(notifyPromises); + await Promise.all(notifyPromises); - retData.invited_requests = retData.InvitedRequest; - return retData; - } + retData.invited_requests = retData.InvitedRequest; + return retData; + } } diff --git a/src/onebot/action/system/GetVersionInfo.ts b/src/onebot/action/system/GetVersionInfo.ts index 0d401f6b..e7d53a74 100644 --- a/src/onebot/action/system/GetVersionInfo.ts +++ b/src/onebot/action/system/GetVersionInfo.ts @@ -2,18 +2,18 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { napCatVersion } from '@/common/version'; interface ResponseType { - app_name: string; - protocol_version: string; - app_version: string; + app_name: string; + protocol_version: string; + app_version: string; } export default class GetVersionInfo extends OneBotAction { - override actionName = ActionName.GetVersionInfo; + override actionName = ActionName.GetVersionInfo; - async _handle(): Promise { - return { - app_name: 'NapCat.Onebot', - protocol_version: 'v11', - app_version: napCatVersion, - }; - } + async _handle (): Promise { + return { + app_name: 'NapCat.Onebot', + protocol_version: 'v11', + app_version: napCatVersion, + }; + } } diff --git a/src/onebot/action/user/GetCookies.ts b/src/onebot/action/user/GetCookies.ts index 00773fe3..2d8b057d 100644 --- a/src/onebot/action/user/GetCookies.ts +++ b/src/onebot/action/user/GetCookies.ts @@ -2,25 +2,25 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; interface Response { - cookies: string, - bkn: string + cookies: string, + bkn: string } const SchemaData = Type.Object({ - domain: Type.String() + domain: Type.String(), }); type Payload = Static; export class GetCookies extends OneBotAction { - override actionName = ActionName.GetCookies; - override payloadSchema = SchemaData; + override actionName = ActionName.GetCookies; + override payloadSchema = SchemaData; - async _handle(payload: Payload) { - const cookiesObject = await this.core.apis.UserApi.getCookies(payload.domain); - //把获取到的cookiesObject转换成 k=v; 格式字符串拼接在一起 - const cookies = Object.entries(cookiesObject).map(([key, value]) => `${key}=${value}`).join('; '); - const bkn = cookiesObject?.['skey'] ? this.core.apis.WebApi.getBknFromCookie(cookiesObject) : ''; - return { cookies, bkn }; - } + async _handle (payload: Payload) { + const cookiesObject = await this.core.apis.UserApi.getCookies(payload.domain); + // 把获取到的cookiesObject转换成 k=v; 格式字符串拼接在一起 + const cookies = Object.entries(cookiesObject).map(([key, value]) => `${key}=${value}`).join('; '); + const bkn = cookiesObject?.['skey'] ? this.core.apis.WebApi.getBknFromCookie(cookiesObject) : ''; + return { cookies, bkn }; + } } diff --git a/src/onebot/action/user/GetFriendList.ts b/src/onebot/action/user/GetFriendList.ts index 6850f0f6..620357d8 100644 --- a/src/onebot/action/user/GetFriendList.ts +++ b/src/onebot/action/user/GetFriendList.ts @@ -5,31 +5,31 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), + no_cache: Type.Optional(Type.Union([Type.Boolean(), Type.String()])), }); type Payload = Static; export default class GetFriendList extends OneBotAction { - override actionName = ActionName.GetFriendList; - override payloadSchema = SchemaData; + override actionName = ActionName.GetFriendList; + override payloadSchema = SchemaData; - async _handle(_payload: Payload) { - const buddyMap = await this.core.apis.FriendApi.getBuddyV2SimpleInfoMap(); - const isNocache = typeof _payload.no_cache === 'string' ? _payload.no_cache === 'true' : !!_payload.no_cache; - await Promise.all( - Array.from(buddyMap.values()).map(async (buddyInfo) => { - try { - const userDetail = await this.core.apis.UserApi.getUserDetailInfo(buddyInfo.coreInfo.uid, isNocache); - const data = buddyMap.get(buddyInfo.coreInfo.uid); - if (data) { - data.qqLevel = userDetail.qqLevel; - } - } catch (error) { - this.core.context.logger.logError('获取好友详细信息失败', error); - } - }) - ); - return OB11Construct.friends(Array.from(buddyMap.values())); - } -} \ No newline at end of file + async _handle (_payload: Payload) { + const buddyMap = await this.core.apis.FriendApi.getBuddyV2SimpleInfoMap(); + const isNocache = typeof _payload.no_cache === 'string' ? _payload.no_cache === 'true' : !!_payload.no_cache; + await Promise.all( + Array.from(buddyMap.values()).map(async (buddyInfo) => { + try { + const userDetail = await this.core.apis.UserApi.getUserDetailInfo(buddyInfo.coreInfo.uid, isNocache); + const data = buddyMap.get(buddyInfo.coreInfo.uid); + if (data) { + data.qqLevel = userDetail.qqLevel; + } + } catch (error) { + this.core.context.logger.logError('获取好友详细信息失败', error); + } + }) + ); + return OB11Construct.friends(Array.from(buddyMap.values())); + } +} diff --git a/src/onebot/action/user/GetRecentContact.ts b/src/onebot/action/user/GetRecentContact.ts index 91f25484..3b3f183b 100644 --- a/src/onebot/action/user/GetRecentContact.ts +++ b/src/onebot/action/user/GetRecentContact.ts @@ -1,49 +1,48 @@ - import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; import { NetworkAdapterConfig } from '@/onebot/config/config'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - count: Type.Union([Type.Number(), Type.String()], { default: 10 }), + count: Type.Union([Type.Number(), Type.String()], { default: 10 }), }); type Payload = Static; export default class GetRecentContact extends OneBotAction { - override actionName = ActionName.GetRecentContact; - override payloadSchema = SchemaData; + override actionName = ActionName.GetRecentContact; + override payloadSchema = SchemaData; - async _handle(payload: Payload, _adapter: string, config: NetworkAdapterConfig) { - const ret = await this.core.apis.UserApi.getRecentContactListSnapShot(+payload.count); - //烘焙消息 - return await Promise.all(ret.info.changedList.map(async (t) => { - const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]); - if (FastMsg.msgList.length > 0 && FastMsg.msgList[0]) { - //扩展ret.info.changedList - const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], config.messagePostFormat); - return { - lastestMsg: lastestMsg, - peerUin: t.peerUin, - remark: t.remark, - msgTime: t.msgTime, - chatType: t.chatType, - msgId: t.msgId, - sendNickName: t.sendNickName, - sendMemberName: t.sendMemberName, - peerName: t.peerName, - }; - } - return { - peerUin: t.peerUin, - remark: t.remark, - msgTime: t.msgTime, - chatType: t.chatType, - msgId: t.msgId, - sendNickName: t.sendNickName, - sendMemberName: t.sendMemberName, - peerName: t.peerName, - }; - })); - } + async _handle (payload: Payload, _adapter: string, config: NetworkAdapterConfig) { + const ret = await this.core.apis.UserApi.getRecentContactListSnapShot(+payload.count); + // 烘焙消息 + return await Promise.all(ret.info.changedList.map(async (t) => { + const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]); + if (FastMsg.msgList.length > 0 && FastMsg.msgList[0]) { + // 扩展ret.info.changedList + const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], config.messagePostFormat); + return { + lastestMsg, + peerUin: t.peerUin, + remark: t.remark, + msgTime: t.msgTime, + chatType: t.chatType, + msgId: t.msgId, + sendNickName: t.sendNickName, + sendMemberName: t.sendMemberName, + peerName: t.peerName, + }; + } + return { + peerUin: t.peerUin, + remark: t.remark, + msgTime: t.msgTime, + chatType: t.chatType, + msgId: t.msgId, + sendNickName: t.sendNickName, + sendMemberName: t.sendMemberName, + peerName: t.peerName, + }; + })); + } } diff --git a/src/onebot/action/user/SendLike.ts b/src/onebot/action/user/SendLike.ts index 828b46a5..4b2f3b7c 100644 --- a/src/onebot/action/user/SendLike.ts +++ b/src/onebot/action/user/SendLike.ts @@ -3,23 +3,23 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - times: Type.Union([Type.Number(), Type.String()], { default: 1 }), - user_id: Type.Union([Type.Number(), Type.String()]) + times: Type.Union([Type.Number(), Type.String()], { default: 1 }), + user_id: Type.Union([Type.Number(), Type.String()]), }); type Payload = Static; export default class SendLike extends OneBotAction { - override actionName = ActionName.SendLike; - override payloadSchema = SchemaData; + override actionName = ActionName.SendLike; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const qq = payload.user_id.toString(); - const uid: string = await this.core.apis.UserApi.getUidByUinV2(qq) ?? ''; - const result = await this.core.apis.UserApi.like(uid, +payload.times); - if (result.result !== 0) { - throw new Error(`点赞失败 ${result.errMsg}`); - } - return null; + async _handle (payload: Payload): Promise { + const qq = payload.user_id.toString(); + const uid: string = await this.core.apis.UserApi.getUidByUinV2(qq) ?? ''; + const result = await this.core.apis.UserApi.like(uid, +payload.times); + if (result.result !== 0) { + throw new Error(`点赞失败 ${result.errMsg}`); } + return null; + } } diff --git a/src/onebot/action/user/SetFriendAddRequest.ts b/src/onebot/action/user/SetFriendAddRequest.ts index bda5964a..3f2f0d97 100644 --- a/src/onebot/action/user/SetFriendAddRequest.ts +++ b/src/onebot/action/user/SetFriendAddRequest.ts @@ -3,27 +3,27 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - flag: Type.Union([Type.String(), Type.Number()]), - approve: Type.Optional(Type.Union([Type.String(), Type.Boolean()])), - remark: Type.Optional(Type.String()) + flag: Type.Union([Type.String(), Type.Number()]), + approve: Type.Optional(Type.Union([Type.String(), Type.Boolean()])), + remark: Type.Optional(Type.String()), }); type Payload = Static; export default class SetFriendAddRequest extends OneBotAction { - override actionName = ActionName.SetFriendAddRequest; - override payloadSchema = SchemaData; + override actionName = ActionName.SetFriendAddRequest; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - const approve = payload.approve?.toString() !== 'false'; - const notify = (await this.core.apis.FriendApi.getBuddyReq()).buddyReqs.find(e => e.reqTime == payload.flag.toString()); - if (!notify) { - throw new Error('No such request'); - } - await this.core.apis.FriendApi.handleFriendRequest(notify, approve); - if (payload.remark) { - await this.core.apis.FriendApi.setBuddyRemark(notify.friendUid, payload.remark); - } - return null; + async _handle (payload: Payload): Promise { + const approve = payload.approve?.toString() !== 'false'; + const notify = (await this.core.apis.FriendApi.getBuddyReq()).buddyReqs.find(e => e.reqTime == payload.flag.toString()); + if (!notify) { + throw new Error('No such request'); } + await this.core.apis.FriendApi.handleFriendRequest(notify, approve); + if (payload.remark) { + await this.core.apis.FriendApi.setBuddyRemark(notify.friendUid, payload.remark); + } + return null; + } } diff --git a/src/onebot/action/user/SetFriendRemark.ts b/src/onebot/action/user/SetFriendRemark.ts index 5cc3559c..fdfe2500 100644 --- a/src/onebot/action/user/SetFriendRemark.ts +++ b/src/onebot/action/user/SetFriendRemark.ts @@ -3,23 +3,23 @@ import { ActionName } from '@/onebot/action/router'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - user_id: Type.String(), - remark: Type.String() + user_id: Type.String(), + remark: Type.String(), }); type Payload = Static; export default class SetFriendRemark extends OneBotAction { - override actionName = ActionName.SetFriendRemark; - override payloadSchema = SchemaData; + override actionName = ActionName.SetFriendRemark; + override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { - let friendUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id); - let is_friend = await this.core.apis.FriendApi.isBuddy(friendUid); - if (!is_friend) { - throw new Error(`用户 ${payload.user_id} 不是好友`); - } - await this.core.apis.FriendApi.setBuddyRemark(friendUid, payload.remark); - return null; + async _handle (payload: Payload): Promise { + const friendUid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id); + const is_friend = await this.core.apis.FriendApi.isBuddy(friendUid); + if (!is_friend) { + throw new Error(`用户 ${payload.user_id} 不是好友`); } + await this.core.apis.FriendApi.setBuddyRemark(friendUid, payload.remark); + return null; + } } diff --git a/src/onebot/api/friend.ts b/src/onebot/api/friend.ts index 20438429..9a09134b 100644 --- a/src/onebot/api/friend.ts +++ b/src/onebot/api/friend.ts @@ -3,28 +3,28 @@ import { NapCatOneBot11Adapter } from '@/onebot'; import { OB11FriendPokeEvent } from '@/onebot/event/notice/OB11PokeEvent'; export class OneBotFriendApi { - obContext: NapCatOneBot11Adapter; - core: NapCatCore; - constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - this.obContext = obContext; - this.core = core; - } + obContext: NapCatOneBot11Adapter; + core: NapCatCore; + constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + this.obContext = obContext; + this.core = core; + } - //使用前预先判断 busiId 1061 - async parsePrivatePokeEvent(grayTipElement: GrayTipElement, uin: number) { - const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr); - const pokedetail: Array<{ uid: string }> = json.items; - //筛选item带有uid的元素 - const poke_uid = pokedetail.filter(item => item.uid); - if (poke_uid.length == 2 && poke_uid[0]?.uid && poke_uid[1]?.uid) { - return new OB11FriendPokeEvent( - this.core, - uin, - parseInt((await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid))), - parseInt((await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid))), - pokedetail, - ); - } - return undefined; + // 使用前预先判断 busiId 1061 + async parsePrivatePokeEvent (grayTipElement: GrayTipElement, uin: number) { + const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr); + const pokedetail: Array<{ uid: string }> = json.items; + // 筛选item带有uid的元素 + const poke_uid = pokedetail.filter(item => item.uid); + if (poke_uid.length == 2 && poke_uid[0]?.uid && poke_uid[1]?.uid) { + return new OB11FriendPokeEvent( + this.core, + uin, + parseInt((await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid))), + parseInt((await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid))), + pokedetail + ); } + return undefined; + } } diff --git a/src/onebot/api/group.ts b/src/onebot/api/group.ts index 7b068d7d..ad9c0b59 100644 --- a/src/onebot/api/group.ts +++ b/src/onebot/api/group.ts @@ -1,15 +1,15 @@ import { - ChatType, - FileElement, - GrayTipElement, - InstanceContext, - JsonGrayBusiId, - MessageElement, - NapCatCore, - NTGrayTipElementSubTypeV2, - RawMessage, - TipGroupElement, - TipGroupElementType, + ChatType, + FileElement, + GrayTipElement, + InstanceContext, + JsonGrayBusiId, + MessageElement, + NapCatCore, + NTGrayTipElementSubTypeV2, + RawMessage, + TipGroupElement, + TipGroupElementType, } from '@/core'; import { NapCatOneBot11Adapter } from '@/onebot'; import { OB11GroupBanEvent } from '@/onebot/event/notice/OB11GroupBanEvent'; @@ -28,337 +28,337 @@ import { NapProtoMsg } from '@napneko/nap-proto-core'; import { GroupReactNotify, PushMsg } from '@/core/packet/transformer/proto'; export class OneBotGroupApi { - obContext: NapCatOneBot11Adapter; - core: NapCatCore; - constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - this.obContext = obContext; - this.core = core; - } + obContext: NapCatOneBot11Adapter; + core: NapCatCore; + constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + this.obContext = obContext; + this.core = core; + } - async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) { - const groupElement = grayTipElement?.groupElement; - if (!groupElement?.shutUp) return undefined; - const memberUid = groupElement.shutUp.member.uid; - const adminUid = groupElement.shutUp.admin.uid; - let memberUin: string; - let duration = parseInt(groupElement.shutUp.duration); - const subType: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban'; - if (memberUid) { - memberUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, memberUid))?.uin ?? ''; - } else { - memberUin = '0'; // 0表示全员禁言 - if (duration > 0) { - duration = -1; - } - } - await this.core.apis.GroupApi.refreshGroupMemberCachePartial(GroupCode, memberUid); - const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, adminUid))?.uin; - if (memberUin && adminUin) { - return new OB11GroupBanEvent( - this.core, - parseInt(GroupCode), - parseInt(memberUin), - parseInt(adminUin), - duration, - subType, - ); - } - return undefined; + async parseGroupBanEvent (GroupCode: string, grayTipElement: GrayTipElement) { + const groupElement = grayTipElement?.groupElement; + if (!groupElement?.shutUp) return undefined; + const memberUid = groupElement.shutUp.member.uid; + const adminUid = groupElement.shutUp.admin.uid; + let memberUin: string; + let duration = parseInt(groupElement.shutUp.duration); + const subType: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban'; + if (memberUid) { + memberUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, memberUid))?.uin ?? ''; + } else { + memberUin = '0'; // 0表示全员禁言 + if (duration > 0) { + duration = -1; + } } + await this.core.apis.GroupApi.refreshGroupMemberCachePartial(GroupCode, memberUid); + const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, adminUid))?.uin; + if (memberUin && adminUin) { + return new OB11GroupBanEvent( + this.core, + parseInt(GroupCode), + parseInt(memberUin), + parseInt(adminUin), + duration, + subType + ); + } + return undefined; + } - async parseGroupEmojiLikeEventByGrayTip( - groupCode: string, - grayTipElement: GrayTipElement - ) { - if (this.core.apis.PacketApi.packetStatus === true) { - return; - //Raw包解析支持时禁用NT解析 - } - const emojiLikeData = new fastXmlParser.XMLParser({ - ignoreAttributes: false, - attributeNamePrefix: '', - }).parse(grayTipElement.xmlElement.content); - this.core.context.logger.logDebug('收到表情回应我的消息', emojiLikeData); - const senderUin = emojiLikeData.gtip.qq.jp; - const msgSeq = emojiLikeData.gtip.url.msgseq; - const emojiId = emojiLikeData.gtip.face.id; - return await this.createGroupEmojiLikeEvent(groupCode, senderUin, msgSeq, emojiId, true, 1); + async parseGroupEmojiLikeEventByGrayTip ( + groupCode: string, + grayTipElement: GrayTipElement + ) { + if (this.core.apis.PacketApi.packetStatus === true) { + return; + // Raw包解析支持时禁用NT解析 } + const emojiLikeData = new fastXmlParser.XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: '', + }).parse(grayTipElement.xmlElement.content); + this.core.context.logger.logDebug('收到表情回应我的消息', emojiLikeData); + const senderUin = emojiLikeData.gtip.qq.jp; + const msgSeq = emojiLikeData.gtip.url.msgseq; + const emojiId = emojiLikeData.gtip.face.id; + return await this.createGroupEmojiLikeEvent(groupCode, senderUin, msgSeq, emojiId, true, 1); + } - async createGroupEmojiLikeEvent( - groupCode: string, - senderUin: string, - msgSeq: string, - emojiId: string, - isAdd: boolean = true, - count: number = 1, - ) { - const peer = { - chatType: ChatType.KCHATTYPEGROUP, - guildId: '', - peerUid: groupCode, - }; - const replyMsgList = (await this.core.apis.MsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList; - if (replyMsgList.length < 1) { - return; - } - const replyMsg = replyMsgList[0]; - if (!replyMsg) { - this.core.context.logger.logError('解析表情回应消息失败: 未找到回应消息'); - return undefined; - } - return new OB11GroupMsgEmojiLikeEvent( - this.core, - parseInt(groupCode), - parseInt(senderUin), - MessageUnique.createUniqueMsgId({ chatType: ChatType.KCHATTYPEGROUP, guildId: '', peerUid: groupCode }, replyMsg.msgId), - [{ - emoji_id: emojiId, - count: count, - }], - isAdd, - msgSeq - ); + async createGroupEmojiLikeEvent ( + groupCode: string, + senderUin: string, + msgSeq: string, + emojiId: string, + isAdd: boolean = true, + count: number = 1 + ) { + const peer = { + chatType: ChatType.KCHATTYPEGROUP, + guildId: '', + peerUid: groupCode, + }; + const replyMsgList = (await this.core.apis.MsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList; + if (replyMsgList.length < 1) { + return; } + const replyMsg = replyMsgList[0]; + if (!replyMsg) { + this.core.context.logger.logError('解析表情回应消息失败: 未找到回应消息'); + return undefined; + } + return new OB11GroupMsgEmojiLikeEvent( + this.core, + parseInt(groupCode), + parseInt(senderUin), + MessageUnique.createUniqueMsgId({ chatType: ChatType.KCHATTYPEGROUP, guildId: '', peerUid: groupCode }, replyMsg.msgId), + [{ + emoji_id: emojiId, + count, + }], + isAdd, + msgSeq + ); + } - async parseCardChangedEvent(msg: RawMessage) { - if (msg.senderUin && msg.senderUin !== '0') { - const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin); - if (member && member.cardName !== msg.sendMemberName) { - const newCardName = msg.sendMemberName ?? ''; - const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName); - member.cardName = newCardName; - return event; - } - if (member && member.nick !== msg.sendNickName) { - await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid); - } - } - return undefined; - } - async registerParseGroupReactEvent() { - this.obContext.core.context.packetHandler.onCmd('trpc.msg.olpush.OlPushService.MsgPush', async (packet) => { - let data = new NapProtoMsg(PushMsg).decode(Buffer.from(packet.hex_data, 'hex')); - if (data.message.contentHead.type === 732 && data.message.contentHead.subType === 16) { - let 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); - const event = await this.createGroupEmojiLikeEvent( - groupCode, - senderUin, - seq, - code, - type === 1, - 1 - ); - if (event) { - this.obContext.networkManager.emitEvent(event); - } - } - } - }); - } - async parsePaiYiPai(msg: RawMessage, jsonStr: string) { - const json = JSON.parse(jsonStr); - //判断业务类型 - //Poke事件 - const pokedetail: Array<{ uid: string }> = json.items; - //筛选item带有uid的元素 - const poke_uid = pokedetail.filter(item => item.uid); - if (poke_uid.length == 2 && poke_uid[0]?.uid && poke_uid[1]?.uid) { - return new OB11GroupPokeEvent( - this.core, - parseInt(msg.peerUid), - +await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid), - +await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid), - pokedetail, - ); - } - return undefined; - } - - async parseOtherJsonEvent(msg: RawMessage, jsonStr: string, context: InstanceContext) { - const json = JSON.parse(jsonStr); - const type = json.items[json.items.length - 1]?.txt; + async parseCardChangedEvent (msg: RawMessage) { + if (msg.senderUin && msg.senderUin !== '0') { + const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin); + if (member && member.cardName !== msg.sendMemberName) { + const newCardName = msg.sendMemberName ?? ''; + const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName); + member.cardName = newCardName; + return event; + } + if (member && member.nick !== msg.sendNickName) { await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid); - if (type === '头衔') { - const memberUin = json.items[1].param[0]; - const title = json.items[3].txt; - context.logger.logDebug('收到群成员新头衔消息', json); - return new OB11GroupTitleEvent( - this.core, - +msg.peerUid, - +memberUin, - title, - ); - } else if (type === '移出') { - context.logger.logDebug('收到机器人被踢消息', json); - return; - } else { - context.logger.logWarn('收到未知的灰条消息', json); + } + } + return undefined; + } + + async registerParseGroupReactEvent () { + this.obContext.core.context.packetHandler.onCmd('trpc.msg.olpush.OlPushService.MsgPush', async (packet) => { + const data = new NapProtoMsg(PushMsg).decode(Buffer.from(packet.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; } - return; - } - - async parseEssenceMsg(msg: RawMessage, jsonStr: string) { - const json = JSON.parse(jsonStr); - const searchParams = new URL(json.items[0].jp).searchParams; - const msgSeq = searchParams.get('msgSeq')!; - const Group = searchParams.get('groupCode'); - if (!Group) return; - // const businessId = searchParams.get('businessid'); - const Peer = { - guildId: '', - chatType: ChatType.KCHATTYPEGROUP, - peerUid: Group, - }; - const msgData = await this.core.apis.MsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true); - const msgList = (await this.core.apis.WebApi.getGroupEssenceMsgAll(Group)).flatMap((e) => e.data.msg_list); - const realMsg = msgList.find((e) => e.msg_seq.toString() == msgSeq); - if (msgData.msgList[0]) { - return new OB11GroupEssenceEvent( - this.core, - parseInt(msg.peerUid), - MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!, - parseInt(msgData.msgList[0].senderUin ?? await this.core.apis.UserApi.getUinByUidV2(msgData.msgList[0].senderUid)), - parseInt(realMsg?.add_digest_uin ?? '0'), - ); + // 开始解析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); + const event = await this.createGroupEmojiLikeEvent( + groupCode, + senderUin, + seq, + code, + type === 1, + 1 + ); + if (event) { + this.obContext.networkManager.emitEvent(event); + } } - return; - // 获取MsgSeq+Peer可获取具体消息 + } + }); + } + + async parsePaiYiPai (msg: RawMessage, jsonStr: string) { + const json = JSON.parse(jsonStr); + // 判断业务类型 + // Poke事件 + const pokedetail: Array<{ uid: string }> = json.items; + // 筛选item带有uid的元素 + const poke_uid = pokedetail.filter(item => item.uid); + if (poke_uid.length == 2 && poke_uid[0]?.uid && poke_uid[1]?.uid) { + return new OB11GroupPokeEvent( + this.core, + parseInt(msg.peerUid), + +await this.core.apis.UserApi.getUinByUidV2(poke_uid[0].uid), + +await this.core.apis.UserApi.getUinByUidV2(poke_uid[1].uid), + pokedetail + ); + } + return undefined; + } + + async parseOtherJsonEvent (msg: RawMessage, jsonStr: string, context: InstanceContext) { + const json = JSON.parse(jsonStr); + const type = json.items[json.items.length - 1]?.txt; + await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid); + if (type === '头衔') { + const memberUin = json.items[1].param[0]; + const title = json.items[3].txt; + context.logger.logDebug('收到群成员新头衔消息', json); + return new OB11GroupTitleEvent( + this.core, + +msg.peerUid, + +memberUin, + title + ); + } else if (type === '移出') { + context.logger.logDebug('收到机器人被踢消息', json); + } else { + context.logger.logWarn('收到未知的灰条消息', json); + } + } + + async parseEssenceMsg (msg: RawMessage, jsonStr: string) { + const json = JSON.parse(jsonStr); + const searchParams = new URL(json.items[0].jp).searchParams; + const msgSeq = searchParams.get('msgSeq')!; + const Group = searchParams.get('groupCode'); + if (!Group) return; + // const businessId = searchParams.get('businessid'); + const Peer = { + guildId: '', + chatType: ChatType.KCHATTYPEGROUP, + peerUid: Group, + }; + const msgData = await this.core.apis.MsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true); + const msgList = (await this.core.apis.WebApi.getGroupEssenceMsgAll(Group)).flatMap((e) => e.data.msg_list); + const realMsg = msgList.find((e) => e.msg_seq.toString() == msgSeq); + if (msgData.msgList[0]) { + return new OB11GroupEssenceEvent( + this.core, + parseInt(msg.peerUid), + MessageUnique.getShortIdByMsgId(msgData.msgList[0].msgId)!, + parseInt(msgData.msgList[0].senderUin ?? await this.core.apis.UserApi.getUinByUidV2(msgData.msgList[0].senderUid)), + parseInt(realMsg?.add_digest_uin ?? '0') + ); } - async parseGroupUploadFileEvene(msg: RawMessage, element: FileElement, elementWrapper: MessageElement) { - return new OB11GroupUploadNoticeEvent( - this.core, - parseInt(msg.peerUid), parseInt(msg.senderUin || ''), - { - id: FileNapCatOneBotUUID.encode({ - chatType: ChatType.KCHATTYPEGROUP, - peerUid: msg.peerUid, - }, msg.msgId, elementWrapper.elementId, elementWrapper?.fileElement?.fileUuid, element.fileMd5 ?? element.fileUuid), - name: element.fileName, - size: parseInt(element.fileSize), - busid: element.fileBizId ?? 0, - }, - ); - } + // 获取MsgSeq+Peer可获取具体消息 + } - async parseGroupElement(msg: RawMessage, element: TipGroupElement, elementWrapper: GrayTipElement) { - if (element.type === TipGroupElementType.KGROUPNAMEMODIFIED) { - this.core.context.logger.logDebug('收到群名称变更事件', element); - return new OB11GroupNameEvent( - this.core, - parseInt(msg.peerUid), - parseInt(await this.core.apis.UserApi.getUinByUidV2(element.memberUid)), - element.groupName, - ); - } else if (element.type === TipGroupElementType.KSHUTUP) { - const event = await this.parseGroupBanEvent(msg.peerUid, elementWrapper); - return event; - } else if (element.type === TipGroupElementType.KMEMBERADD) { - // 自己的通知 协议推送为type->85 在这里实现为了避免邀请出现问题 - if (element.memberUid == this.core.selfInfo.uid) { - await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, true); - return new OB11GroupIncreaseEvent( - this.core, - parseInt(msg.peerUid), - +this.core.selfInfo.uin, - element.adminUid ? +await this.core.apis.UserApi.getUinByUidV2(element.adminUid) : 0, - 'approve' - ); - } - } - return; - } + async parseGroupUploadFileEvene (msg: RawMessage, element: FileElement, elementWrapper: MessageElement) { + return new OB11GroupUploadNoticeEvent( + this.core, + parseInt(msg.peerUid), parseInt(msg.senderUin || ''), + { + id: FileNapCatOneBotUUID.encode({ + chatType: ChatType.KCHATTYPEGROUP, + peerUid: msg.peerUid, + }, msg.msgId, elementWrapper.elementId, elementWrapper?.fileElement?.fileUuid, element.fileMd5 ?? element.fileUuid), + name: element.fileName, + size: parseInt(element.fileSize), + busid: element.fileBizId ?? 0, + } + ); + } - async parseSelfInviteEvent(msg: RawMessage, inviterUin: string, inviteeUin: string) { + async parseGroupElement (msg: RawMessage, element: TipGroupElement, elementWrapper: GrayTipElement) { + if (element.type === TipGroupElementType.KGROUPNAMEMODIFIED) { + this.core.context.logger.logDebug('收到群名称变更事件', element); + return new OB11GroupNameEvent( + this.core, + parseInt(msg.peerUid), + parseInt(await this.core.apis.UserApi.getUinByUidV2(element.memberUid)), + element.groupName + ); + } else if (element.type === TipGroupElementType.KSHUTUP) { + const event = await this.parseGroupBanEvent(msg.peerUid, elementWrapper); + return event; + } else if (element.type === TipGroupElementType.KMEMBERADD) { + // 自己的通知 协议推送为type->85 在这里实现为了避免邀请出现问题 + if (element.memberUid == this.core.selfInfo.uid) { + await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, true); return new OB11GroupIncreaseEvent( - this.core, - parseInt(msg.peerUid), - +inviteeUin, - +inviterUin, - 'invite' + this.core, + parseInt(msg.peerUid), + +this.core.selfInfo.uin, + element.adminUid ? +await this.core.apis.UserApi.getUinByUidV2(element.adminUid) : 0, + 'approve' ); + } } - async parse51TypeEvent(msg: RawMessage, grayTipElement: GrayTipElement) { - // 神经腾讯 没了妈妈想出来的 - // Warn 下面存在高并发危险 - if (grayTipElement.jsonGrayTipElement.jsonStr) { - const json: { - align: string, - items: Array<{ txt: string, type: string }> - } = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr); - if (json.items.length === 1 && json.items[0]?.txt.endsWith('加入群')) { - let old_members = structuredClone(this.core.apis.GroupApi.groupMemberCache.get(msg.peerUid)); - if (!old_members) return; - let new_members_map = await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, true); - if (!new_members_map) return; - let new_members = Array.from(new_members_map.values()); - // 对比members查找新成员 - let new_member = new_members.find((member) => old_members.get(member.uid) == undefined); - if (!new_member) return; - return new OB11GroupIncreaseEvent( - this.core, - +msg.peerUid, - +new_member.uin, - 0, - 'invite', - ); - } - } - return; - } - async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) { - if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) { - // 解析群组事件 由sysmsg解析 - return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement); - } else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) { - // 筛选自身入群情况 - // if (grayTipElement.xmlElement.busiId === '10145') { - // const inviteData = new fastXmlParser.XMLParser({ - // ignoreAttributes: false, - // attributeNamePrefix: '', - // }).parse(grayTipElement.xmlElement.content); + } - // const inviterUin: string = inviteData.gtip.qq[0].jp; - // const inviteeUin: string = inviteData.gtip.qq[1].jp; - // //刷新群缓存 - // if (inviteeUin === this.core.selfInfo.uin) { - // this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid).then().catch(); - // return this.parseSelfInviteEvent(msg, inviterUin, inviteeUin); - // } - // } else - if (grayTipElement.xmlElement?.templId === '10382') { - return await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, grayTipElement); - } else { - //return await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, grayTipElement); - } - } else if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) { - // 解析json事件 - if (grayTipElement.jsonGrayTipElement.busiId == 1061) { - return await this.parsePaiYiPai(msg, grayTipElement.jsonGrayTipElement.jsonStr); - } else if (grayTipElement.jsonGrayTipElement.busiId == JsonGrayBusiId.AIO_GROUP_ESSENCE_MSG_TIP) { - return await this.parseEssenceMsg(msg, grayTipElement.jsonGrayTipElement.jsonStr); - } else if (+(grayTipElement.jsonGrayTipElement.busiId ?? 0) == 51) { - // 51是什么?{"align":"center","items":[{"txt":"下一秒起床通过王者荣耀加入群","type":"nor"}] - return await this.parse51TypeEvent(msg, grayTipElement); - } else { - return await this.parseOtherJsonEvent(msg, grayTipElement.jsonGrayTipElement.jsonStr, this.core.context); - } - } - return undefined; + async parseSelfInviteEvent (msg: RawMessage, inviterUin: string, inviteeUin: string) { + return new OB11GroupIncreaseEvent( + this.core, + parseInt(msg.peerUid), + +inviteeUin, + +inviterUin, + 'invite' + ); + } + + async parse51TypeEvent (msg: RawMessage, grayTipElement: GrayTipElement) { + // 神经腾讯 没了妈妈想出来的 + // Warn 下面存在高并发危险 + if (grayTipElement.jsonGrayTipElement.jsonStr) { + const json: { + align: string, + items: Array<{ txt: string, type: string }> + } = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr); + if (json.items.length === 1 && json.items[0]?.txt.endsWith('加入群')) { + const old_members = structuredClone(this.core.apis.GroupApi.groupMemberCache.get(msg.peerUid)); + if (!old_members) return; + const new_members_map = await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, true); + if (!new_members_map) return; + const new_members = Array.from(new_members_map.values()); + // 对比members查找新成员 + const new_member = new_members.find((member) => old_members.get(member.uid) == undefined); + if (!new_member) return; + return new OB11GroupIncreaseEvent( + this.core, + +msg.peerUid, + +new_member.uin, + 0, + 'invite' + ); + } } + } + + async parseGrayTipElement (msg: RawMessage, grayTipElement: GrayTipElement) { + if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) { + // 解析群组事件 由sysmsg解析 + return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement); + } else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) { + // 筛选自身入群情况 + // if (grayTipElement.xmlElement.busiId === '10145') { + // const inviteData = new fastXmlParser.XMLParser({ + // ignoreAttributes: false, + // attributeNamePrefix: '', + // }).parse(grayTipElement.xmlElement.content); + + // const inviterUin: string = inviteData.gtip.qq[0].jp; + // const inviteeUin: string = inviteData.gtip.qq[1].jp; + // //刷新群缓存 + // if (inviteeUin === this.core.selfInfo.uin) { + // this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid).then().catch(); + // return this.parseSelfInviteEvent(msg, inviterUin, inviteeUin); + // } + // } else + if (grayTipElement.xmlElement?.templId === '10382') { + return await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, grayTipElement); + } else { + // return await this.obContext.apis.GroupApi.parseGroupIncreaseEvent(msg.peerUid, grayTipElement); + } + } else if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) { + // 解析json事件 + if (grayTipElement.jsonGrayTipElement.busiId == 1061) { + return await this.parsePaiYiPai(msg, grayTipElement.jsonGrayTipElement.jsonStr); + } else if (grayTipElement.jsonGrayTipElement.busiId == JsonGrayBusiId.AIO_GROUP_ESSENCE_MSG_TIP) { + return await this.parseEssenceMsg(msg, grayTipElement.jsonGrayTipElement.jsonStr); + } else if (+(grayTipElement.jsonGrayTipElement.busiId ?? 0) == 51) { + // 51是什么?{"align":"center","items":[{"txt":"下一秒起床通过王者荣耀加入群","type":"nor"}] + return await this.parse51TypeEvent(msg, grayTipElement); + } else { + return await this.parseOtherJsonEvent(msg, grayTipElement.jsonGrayTipElement.jsonStr, this.core.context); + } + } + return undefined; + } } diff --git a/src/onebot/api/index.ts b/src/onebot/api/index.ts index f4b10315..115c4410 100644 --- a/src/onebot/api/index.ts +++ b/src/onebot/api/index.ts @@ -2,4 +2,4 @@ export * from './friend'; export * from './group'; export * from './user'; export * from './msg'; -export * from './quick-action'; \ No newline at end of file +export * from './quick-action'; diff --git a/src/onebot/api/msg.ts b/src/onebot/api/msg.ts index e6cd2c22..944d0559 100644 --- a/src/onebot/api/msg.ts +++ b/src/onebot/api/msg.ts @@ -1,33 +1,33 @@ import { FileNapCatOneBotUUID } from '@/common/file-uuid'; import { MessageUnique } from '@/common/message-unique'; import { - ChatType, - CustomMusicSignPostData, - ElementType, - FaceIndex, - FaceType, - GrayTipElement, - GroupNotify, - IdMusicSignPostData, - MessageElement, - NapCatCore, - NTGrayTipElementSubTypeV2, - NTMsgAtType, - Peer, - RawMessage, - SendMessageElement, - SendTextElement, + ChatType, + CustomMusicSignPostData, + ElementType, + FaceIndex, + FaceType, + GrayTipElement, + GroupNotify, + IdMusicSignPostData, + MessageElement, + NapCatCore, + NTGrayTipElementSubTypeV2, + NTMsgAtType, + Peer, + RawMessage, + SendMessageElement, + SendTextElement, } from '@/core'; import faceConfig from '@/core/external/face_config.json'; import { - NapCatOneBot11Adapter, - OB11Message, - OB11MessageData, - OB11MessageDataType, - OB11MessageFileBase, - OB11MessageForward, - OB11MessageImage, - OB11MessageVideo, + NapCatOneBot11Adapter, + OB11Message, + OB11MessageData, + OB11MessageDataType, + OB11MessageFileBase, + OB11MessageForward, + OB11MessageImage, + OB11MessageVideo, } from '@/onebot'; import { OB11Construct } from '@/onebot/helper/data'; import { EventType } from '@/onebot/event/OneBotEvent'; @@ -49,1437 +49,1430 @@ import { cleanTaskQueue } from '@/common/clean-task'; import { registerResource } from '@/common/health'; type RawToOb11Converters = { - [Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: ( - element: Exclude, - msg: RawMessage, - elementWrapper: MessageElement, - context: RecvMessageContext - ) => Promise -} + [Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: ( + element: Exclude, + msg: RawMessage, + elementWrapper: MessageElement, + context: RecvMessageContext + ) => Promise +}; type Ob11ToRawConverters = { - [Key in OB11MessageDataType]: ( - sendMsg: Extract, - context: SendMessageContext, - ) => Promise -} + [Key in OB11MessageDataType]: ( + sendMsg: Extract, + context: SendMessageContext, + ) => Promise +}; export type SendMessageContext = { - deleteAfterSentFiles: string[], - peer: Peer -} + deleteAfterSentFiles: string[], + peer: Peer +}; export type RecvMessageContext = { - parseMultMsg: boolean, - disableGetUrl: boolean, - quick_reply: boolean -} + parseMultMsg: boolean, + disableGetUrl: boolean, + quick_reply: boolean +}; -function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters { - return key in parser; +function keyCanBeParsed (key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters { + return key in parser; } export class OneBotMsgApi { - obContext: NapCatOneBot11Adapter; - core: NapCatCore; - notifyGroupInvite: LRUCache = new LRUCache(50); - // seq -> notify - rawToOb11Converters: RawToOb11Converters = { - textElement: async element => { - if (element.atType === NTMsgAtType.ATTYPEUNKNOWN) { - let text = element.content; - // 兼容 9.7.x 换行符 - if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) { - text = text.replace(/\r/g, '\n'); - } - return { - type: OB11MessageDataType.text, - data: { text }, - }; - } else { - let qq: string = 'all'; - if (element.atType !== NTMsgAtType.ATTYPEALL) { - const { atNtUid, atUid } = element; - qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : String(Number(atUid) >>> 0); - } - return { - type: OB11MessageDataType.at, - data: { - qq: qq, - // name: content.slice(1); - }, - }; - } - }, - - picElement: async (element, msg, elementWrapper, { disableGetUrl }) => { - try { - const peer = { - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '', - }; - FileNapCatOneBotUUID.encode( - peer, - msg.msgId, - elementWrapper.elementId, - element.fileUuid, - element.fileName - ); - return { - type: OB11MessageDataType.image, - data: { - summary: element.summary, - file: element.fileName, - sub_type: element.picSubType, - url: disableGetUrl ? (element.filePath ?? '') : await this.core.apis.FileApi.getImageUrl(element), - file_size: element.fileSize, - }, - }; - } catch (e) { - this.core.context.logger.logError('获取图片url失败', (e as Error).stack); - return; - } - }, - - fileElement: async (element, msg, elementWrapper, { disableGetUrl }) => { - const peer = { - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '', - }; - FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid); - FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName); - if (this.core.apis.PacketApi.packetStatus && !disableGetUrl) { - let url; - try { - //url = await this.core.apis.FileApi.getFileUrl(msg.chatType, msg.peerUid, element.fileUuid, element.file10MMd5, 1500) - url = await registerResource( - 'file-url-get', - { - resourceFn: async () => { - return await this.core.apis.FileApi.getFileUrl(msg.chatType, msg.peerUid, element.fileUuid, element.file10MMd5, 1500); - }, - healthCheckFn: async () => { - return await this.core.apis.PacketApi.pkt.operation.FetchRkey().then(() => true).catch(() => false); - }, - testArgs: [], - healthCheckInterval: 30000, - maxHealthCheckFailures: 3 - }, - ); - } catch (error) { - url = ''; - } - if (url) { - return { - type: OB11MessageDataType.file, - data: { - file: element.fileName, - file_id: element.fileUuid, - file_size: element.fileSize, - url: url, - }, - } - } - } - return { - type: OB11MessageDataType.file, - data: { - file: element.fileName, - file_id: element.fileUuid, - file_size: element.fileSize - }, - }; - }, - - faceElement: async element => { - const faceIndex = element.faceIndex; - if (element.faceType == FaceType.Poke) { - return { - type: OB11MessageDataType.poke, - data: { - type: element?.pokeType?.toString() ?? '0', - id: faceIndex.toString(), - } - }; - - } - - if (faceIndex === FaceIndex.DICE) { - return { - type: OB11MessageDataType.dice, - data: { - result: element.resultId!, - }, - }; - } else if (faceIndex === FaceIndex.RPS) { - return { - type: OB11MessageDataType.rps, - data: { - result: element.resultId!, - }, - }; - } else { - return { - type: OB11MessageDataType.face, - data: { - id: element.faceIndex.toString(), - raw: element, - resultId: element.resultId, - chainCount: element.chainCount, - }, - }; - } - }, - - marketFaceElement: async (_, msg, elementWrapper) => { - const peer = { - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '', - }; - const { emojiId } = _; - const dir = emojiId.substring(0, 2); - const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`; - const filename = `${dir}-${emojiId}.gif`; - FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', filename); - return { - type: OB11MessageDataType.image, - data: { - summary: _.faceName, // 商城表情名称 - file: filename, - url: url, - key: _.key, - emoji_id: _.emojiId, - emoji_package_id: _.emojiPackageId, - }, - }; - }, - - replyElement: async (element, msg, _, quick_reply) => { - const peer = { - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '', - }; - - // 创建回复数据的通用方法 - const createReplyData = (msgId: string): OB11MessageData => ({ - type: OB11MessageDataType.reply, - data: { - id: MessageUnique.createUniqueMsgId(peer, msgId).toString(), - }, - }); - - // 查找记录 - const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords); - - // 特定账号的特殊处理 - if (records && (records.peerUin === '284840486' || records.peerUin === '1094950020')) { - return createReplyData(records.msgId); - } - - // 获取消息的通用方法组 - const tryFetchMethods = async (msgSeq: string, senderUid?: string, msgTime?: string, msgRandom?: string): Promise => { - try { - // 方法1:通过序号和时间筛选 - if (senderUid && msgTime) { - const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2( - peer, msgSeq, msgTime, [senderUid] - )).msgList; - - const replyMsg = msgRandom - ? replyMsgList.find(msg => msg.msgRandom === msgRandom) - : replyMsgList.find(msg => msg.msgSeq === msgSeq); - - if (replyMsg) return replyMsg; - if (quick_reply) { - this.core.context.logger.logWarn(`快速回复,跳过方法1查询,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); - return undefined; - } - this.core.context.logger.logWarn(`方法1查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); - } - - // 方法2:直接通过序号获取 - const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount( - peer, msgSeq, 1, true, true - )).msgList; - - const replyMsg = msgRandom - ? replyMsgList.find(msg => msg.msgRandom === msgRandom) - : replyMsgList.find(msg => msg.msgSeq === msgSeq); - - if (replyMsg) return replyMsg; - - this.core.context.logger.logWarn(`方法2查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); - - // 方法3:另一种筛选方式 - if (senderUid) { - const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3( - peer, msgSeq, [senderUid] - )).msgList; - - const replyMsg = msgRandom - ? replyMsgList.find(msg => msg.msgRandom === msgRandom) - : replyMsgList.find(msg => msg.msgSeq === msgSeq); - - if (replyMsg) return replyMsg; - - this.core.context.logger.logWarn(`方法3查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); - } - - return undefined; - } catch (error) { - this.core.context.logger.logError('查询回复消息出错', error); - return undefined; - } - }; - - // 有记录情况下,使用完整信息查询 - if (records && element.replyMsgTime && element.senderUidStr) { - const replyMsg = await tryFetchMethods( - element.replayMsgSeq, - element.senderUidStr, - records.msgTime, - records.msgRandom - ); - - if (replyMsg) { - return createReplyData(replyMsg.msgId); - } - - this.core.context.logger.logError('所有查找方法均失败,获取不到带记录的引用消息', element.replayMsgSeq); - } else { - // 旧版客户端或不完整记录的情况,也尝试使用相同流程 - this.core.context.logger.logWarn('似乎是旧版客户端,尝试仅通过序号获取引用消息', element.replayMsgSeq); - - const replyMsg = await tryFetchMethods(element.replayMsgSeq); - - if (replyMsg) { - return createReplyData(replyMsg.msgId); - } - - this.core.context.logger.logError('所有查找方法均失败,获取不到旧客户端的引用消息', element.replayMsgSeq); - } - - return null; - }, - videoElement: async (element, msg, elementWrapper, { disableGetUrl }) => { - const peer = { - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '', - }; - //读取视频链接并兜底 - let videoUrlWrappers: Awaited> | undefined; - - if (msg.peerUin === '284840486' || msg.peerUin === '1094950020') { - try { - videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({ - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '0', - }, msg.parentMsgIdList[0] ?? msg.msgId, elementWrapper.elementId); - } catch { - this.core.context.logger.logWarn('合并获取视频 URL 失败'); - } - } else { - try { - videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({ - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '0', - }, msg.msgId, elementWrapper.elementId); - } catch { - this.core.context.logger.logWarn('获取视频 URL 失败'); - } - } - - - //读取在线URL - let videoDownUrl: string | undefined; - - if (videoUrlWrappers) { - const videoDownUrlTemp = videoUrlWrappers.find((urlWrapper) => { - return !!(urlWrapper.url); - }); - if (videoDownUrlTemp) { - videoDownUrl = videoDownUrlTemp.url; - } - } - - //开始兜底 - if (!videoDownUrl && !disableGetUrl) { - if (this.core.apis.PacketApi.packetStatus) { - try { - //videoDownUrl = await this.core.apis.FileApi.getVideoUrlPacket(msg.peerUid, element.fileUuid, 1500); - videoDownUrl = await registerResource( - 'video-url-get', - { - resourceFn: async () => { - return await this.core.apis.FileApi.getVideoUrlPacket(msg.peerUid, element.fileUuid, 1500); - }, - healthCheckFn: async () => { - return await this.core.apis.PacketApi.pkt.operation.FetchRkey().then(() => true).catch(() => false); - }, - testArgs: [], - healthCheckInterval: 30000, - maxHealthCheckFailures: 3 - }, - ); - } catch (e) { - this.core.context.logger.logError('获取视频url失败', (e as Error).stack); - videoDownUrl = element.filePath; - } - } else { - videoDownUrl = element.filePath; - } - - } - const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName); - return { - type: OB11MessageDataType.video, - data: { - file: fileCode, - url: videoDownUrl, - file_size: element.fileSize, - }, - }; - }, - - pttElement: async (element, msg, elementWrapper, { disableGetUrl }) => { - const peer = { - chatType: msg.chatType, - peerUid: msg.peerUid, - guildId: '', - }; - const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', element.fileName); - let pttUrl = ''; - if (this.core.apis.PacketApi.packetStatus && !disableGetUrl) { - try { - pttUrl = await registerResource( - 'ptt-url-get', - { - resourceFn: async () => { - return await this.core.apis.FileApi.getPttUrl(msg.peerUid, element.fileUuid, 1500); - }, - healthCheckFn: async () => { - return await this.core.apis.PacketApi.pkt.operation.FetchRkey().then(() => true).catch(() => false); - }, - testArgs: [], - healthCheckInterval: 30000, - maxHealthCheckFailures: 3 - }, - ); - //pttUrl = await this.core.apis.FileApi.getPttUrl(msg.peerUid, element.fileUuid, 1500); - } catch (e) { - this.core.context.logger.logError('获取语音url失败', (e as Error).stack); - pttUrl = element.filePath; - } - } else { - pttUrl = element.filePath; - } - if (pttUrl) { - return { - type: OB11MessageDataType.voice, - data: { - file: fileCode, - path: element.filePath, - url: pttUrl, - file_size: element.fileSize, - }, - } - } - return { - type: OB11MessageDataType.voice, - data: { - file: fileCode, - file_size: element.fileSize, - path: element.filePath, - }, - }; - }, - - multiForwardMsgElement: async (element, msg, _wrapper, context) => { - const parentMsgPeer = msg.parentMsgPeer ?? { - chatType: msg.chatType, - guildId: '', - peerUid: msg.peerUid, - }; - let multiMsgs = await this.getMultiMessages(msg, parentMsgPeer); - // 拉取失败则跳过 - if (!multiMsgs || multiMsgs.length === 0) { - try { - multiMsgs = await this.core.apis.PacketApi.pkt.operation.FetchForwardMsg(element.resId); - } catch (e) { - this.core.context.logger.logError(`Protocol FetchForwardMsg fallback failed! - element = ${JSON.stringify(element)} , error=${e})`); - return null; - } - } - const forward: OB11MessageForward = { - type: OB11MessageDataType.forward, - data: { id: msg.msgId } - }; - if (!context.parseMultMsg) return forward; - forward.data.content = await this.parseMultiMessageContent( - multiMsgs, - parentMsgPeer, - msg.parentMsgIdList - ); - return forward; - }, - - arkElement: async (element) => { - return { - type: OB11MessageDataType.json, - data: { - data: element.bytesData, - }, - }; - }, - - markdownElement: async (element) => { - return { - type: OB11MessageDataType.markdown, - data: { - content: element.content, - }, - }; - }, - }; - - ob11ToRawConverters: Ob11ToRawConverters = { - [OB11MessageDataType.text]: async ({ data: { text } }) => ({ - elementType: ElementType.TEXT, - elementId: '', - textElement: { - content: text, - atType: NTMsgAtType.ATTYPEUNKNOWN, - atUid: '', - atTinyId: '', - atNtUid: '', - }, - }), - - [OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => { - function at(atUid: string, atNtUid: string, atType: NTMsgAtType, atName: string): SendTextElement { - return { - elementType: ElementType.TEXT, - elementId: '', - textElement: { - content: `@${atName}`, - atType, - atUid, - atTinyId: '', - atNtUid, - }, - }; - } - - if (!context.peer || !atQQ || context.peer.chatType == ChatType.KCHATTYPEC2C) return undefined; // 过滤掉空atQQ - if (atQQ === 'all') return at(atQQ, atQQ, NTMsgAtType.ATTYPEALL, '全体成员'); - const atMember = await this.core.apis.GroupApi.getGroupMember(context.peer.peerUid, atQQ); - if (atMember) { - return at(atQQ, atMember.uid, NTMsgAtType.ATTYPEONE, atMember.nick || atMember.cardName); - } - const uid = await this.core.apis.UserApi.getUidByUinV2(`${atQQ}`); - if (!uid) throw new Error('Get Uid Error'); - const info = await this.core.apis.UserApi.getUserDetailInfo(uid); - return at(atQQ, uid, NTMsgAtType.ATTYPEONE, info.nick || ''); - }, - - [OB11MessageDataType.reply]: async ({ data: { id } }) => { - const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id)); - if (!replyMsgM) { - this.core.context.logger.logWarn('回复消息不存在', id); - return undefined; - } - const replyMsg = (await this.core.apis.MsgApi.getMsgsByMsgId( - replyMsgM.Peer, [replyMsgM.MsgId])).msgList[0]; - return replyMsg ? - { - elementType: ElementType.REPLY, - elementId: '', - replyElement: { - replayMsgSeq: replyMsg.msgSeq, // raw.msgSeq - replayMsgId: replyMsg.msgId, // raw.msgId - senderUin: replyMsg.senderUin, - senderUinStr: replyMsg.senderUin, - replyMsgClientSeq: replyMsg.clientSeq, - _replyMsgPeer: replyMsgM.Peer - }, - } : - undefined; - }, - - [OB11MessageDataType.face]: async ({ data: { id, resultId, chainCount } }) => { - const parsedFaceId = +id; - // 从face_config.json中获取表情名称 - const sysFaces = faceConfig.sysface; - const face: { - QSid?: string, - QDes?: string, - AniStickerId?: string, - AniStickerType?: number, - AniStickerPackId?: string, - } | undefined = sysFaces.find((systemFace) => systemFace.QSid === parsedFaceId.toString()); - if (!face) { - this.core.context.logger.logError('不支持的ID', id); - return undefined; - } - let faceType = 1; - if (parsedFaceId >= 222) { - faceType = 2; - } - if (face.AniStickerType) { - faceType = 3; - } - return { - elementType: ElementType.FACE, - elementId: '', - faceElement: { - faceIndex: parsedFaceId, - faceType, - faceText: face.QDes, - stickerId: face.AniStickerId, - stickerType: face.AniStickerType, - packId: face.AniStickerPackId, - sourceType: 1, - resultId: resultId?.toString(), - chainCount, - }, - }; - }, - [OB11MessageDataType.mface]: async ({ - data: { - emoji_package_id, emoji_id, key, summary, - }, - }) => ({ - elementType: ElementType.MFACE, - elementId: '', - marketFaceElement: { - emojiPackageId: emoji_package_id, - emojiId: emoji_id, - key, - faceName: summary || '[商城表情]', - }, - }), - - // File service - [OB11MessageDataType.image]: async (sendMsg, context) => { - return await this.core.apis.FileApi.createValidSendPicElement( - context, - (await this.handleOb11FileLikeMessage(sendMsg, context)).path, - sendMsg.data.summary, - sendMsg.data.sub_type, - ); - }, - - [OB11MessageDataType.file]: async (sendMsg, context) => { - const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); - return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName); - }, - - [OB11MessageDataType.video]: async (sendMsg, context) => { - const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); - - let thumb = sendMsg.data.thumb; - if (thumb) { - const uri2LocalRes = await uriToLocalFile(this.core.NapCatTempPath, thumb); - if (uri2LocalRes.success) { - thumb = uri2LocalRes.path; - context.deleteAfterSentFiles.push(thumb); - } - } - - return await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb); - }, - - [OB11MessageDataType.voice]: async (sendMsg, context) => - this.core.apis.FileApi.createValidSendPttElement(context, - (await this.handleOb11FileLikeMessage(sendMsg, context)).path), - - [OB11MessageDataType.json]: async ({ data: { data } }) => ({ - elementType: ElementType.ARK, - elementId: '', - arkElement: { - bytesData: typeof data === 'string' ? data : JSON.stringify(data), - linkInfo: null, - subElementType: null, - }, - }), - - [OB11MessageDataType.dice]: async () => ({ - elementType: ElementType.FACE, - elementId: '', - faceElement: { - faceIndex: FaceIndex.DICE, - faceType: FaceType.AniSticke, - faceText: '[骰子]', - packId: '1', - stickerId: '33', - sourceType: 1, - stickerType: 2, - surpriseId: '', - // "randomType": 1, - }, - }), - - [OB11MessageDataType.rps]: async () => ({ - elementType: ElementType.FACE, - elementId: '', - faceElement: { - faceIndex: FaceIndex.RPS, - faceText: '[包剪锤]', - faceType: FaceType.AniSticke, - packId: '1', - stickerId: '34', - sourceType: 1, - stickerType: 2, - surpriseId: '', - // "randomType": 1, - }, - }), - - // Need signing - [OB11MessageDataType.markdown]: async ({ data: { content } }) => ({ - elementType: ElementType.MARKDOWN, - elementId: '', - markdownElement: { content }, - }), - - [OB11MessageDataType.music]: async ({ data }, context) => { - // 保留, 直到...找到更好的解决方案 - if (data.id !== undefined) { - if (!['qq', '163', 'kugou', 'kuwo', 'migu'].includes(data.type)) { - this.core.context.logger.logError('音乐卡片type错误, 只支持qq、163、kugou、kuwo、migu,当前type:', data.type); - return undefined; - } - } else { - if (!['qq', '163', 'kugou', 'kuwo', 'migu', 'custom'].includes(data.type)) { - this.core.context.logger.logError('音乐卡片type错误, 只支持qq、163、kugou、kuwo、migu、custom,当前type:', data.type); - return undefined; - } - if (!data.url) { - this.core.context.logger.logError('自定义音卡缺少参数url'); - return undefined; - } - if (!data.image) { - this.core.context.logger.logError('自定义音卡缺少参数image'); - return undefined; - } - } - - let postData: IdMusicSignPostData | CustomMusicSignPostData; - if (data.id === undefined && data.content) { - const { content, ...others } = data; - postData = { singer: content, ...others }; - } else { - postData = data; - } - let signUrl = this.obContext.configLoader.configData.musicSignUrl; - if (!signUrl) { - signUrl = 'https://ss.xingzhige.com/music_card/card';//感谢思思!已获思思许可 其余地方使用请自行询问 - //throw Error('音乐消息签名地址未配置'); - } - try { - const musicJson = await RequestUtil.HttpGetJson(signUrl, 'POST', postData); - return this.ob11ToRawConverters.json({ - data: { data: musicJson }, - type: OB11MessageDataType.json - }, context); - } catch (e) { - this.core.context.logger.logError('生成音乐消息失败', e); - } - return undefined; - }, - - [OB11MessageDataType.node]: async () => undefined, - - [OB11MessageDataType.forward]: async ({ data }, context) => { - // let id = data.id.toString(); - // let peer: Peer | undefined = context.peer; - // if (isNumeric(id)) { - // let msgid = ''; - // if (BigInt(data.id) > 2147483647n) { - // peer = MessageUnique.getPeerByMsgId(id)?.Peer; - // msgid = id; - // } else { - // let data = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id)); - // msgid = data?.MsgId ?? ''; - // peer = data?.Peer; - // } - // } - const jsonData = ForwardMsgBuilder.fromResId(data.id); - return this.ob11ToRawConverters.json({ - data: { data: JSON.stringify(jsonData) }, - type: OB11MessageDataType.json - }, context); - }, - - [OB11MessageDataType.xml]: async () => undefined, - - [OB11MessageDataType.poke]: async () => undefined, - - [OB11MessageDataType.location]: async () => ({ - elementType: ElementType.SHARELOCATION, - elementId: '', - shareLocationElement: { - text: '测试', - ext: '', - }, - }), - - [OB11MessageDataType.miniapp]: async () => undefined, - - [OB11MessageDataType.contact]: async ({ data: { type = 'qq', id } }, context) => { - if (type === 'qq') { - const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), ''); - return this.ob11ToRawConverters.json({ - data: { data: arkJson.arkMsg }, - type: OB11MessageDataType.json - }, context); - } else if (type === 'group') { - const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString()); - return this.ob11ToRawConverters.json({ - data: { data: arkJson.arkJson }, - type: OB11MessageDataType.json - }, context); - } - return undefined; + obContext: NapCatOneBot11Adapter; + core: NapCatCore; + notifyGroupInvite: LRUCache = new LRUCache(50); + // seq -> notify + rawToOb11Converters: RawToOb11Converters = { + textElement: async element => { + if (element.atType === NTMsgAtType.ATTYPEUNKNOWN) { + let text = element.content; + // 兼容 9.7.x 换行符 + if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) { + text = text.replace(/\r/g, '\n'); } - }; + return { + type: OB11MessageDataType.text, + data: { text }, + }; + } else { + let qq: string = 'all'; + if (element.atType !== NTMsgAtType.ATTYPEALL) { + const { atNtUid, atUid } = element; + qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : String(Number(atUid) >>> 0); + } + return { + type: OB11MessageDataType.at, + data: { + qq, + // name: content.slice(1); + }, + }; + } + }, - constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - this.obContext = obContext; - this.core = core; - } - /** + picElement: async (element, msg, elementWrapper, { disableGetUrl }) => { + try { + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + FileNapCatOneBotUUID.encode( + peer, + msg.msgId, + elementWrapper.elementId, + element.fileUuid, + element.fileName + ); + return { + type: OB11MessageDataType.image, + data: { + summary: element.summary, + file: element.fileName, + sub_type: element.picSubType, + url: disableGetUrl ? (element.filePath ?? '') : await this.core.apis.FileApi.getImageUrl(element), + file_size: element.fileSize, + }, + }; + } catch (e) { + this.core.context.logger.logError('获取图片url失败', (e as Error).stack); + } + }, + + fileElement: async (element, msg, elementWrapper, { disableGetUrl }) => { + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid); + FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName); + if (this.core.apis.PacketApi.packetStatus && !disableGetUrl) { + let url; + try { + // url = await this.core.apis.FileApi.getFileUrl(msg.chatType, msg.peerUid, element.fileUuid, element.file10MMd5, 1500) + url = await registerResource( + 'file-url-get', + { + resourceFn: async () => { + return await this.core.apis.FileApi.getFileUrl(msg.chatType, msg.peerUid, element.fileUuid, element.file10MMd5, 1500); + }, + healthCheckFn: async () => { + return await this.core.apis.PacketApi.pkt.operation.FetchRkey().then(() => true).catch(() => false); + }, + testArgs: [], + healthCheckInterval: 30000, + maxHealthCheckFailures: 3, + } + ); + } catch (error) { + url = ''; + } + if (url) { + return { + type: OB11MessageDataType.file, + data: { + file: element.fileName, + file_id: element.fileUuid, + file_size: element.fileSize, + url, + }, + }; + } + } + return { + type: OB11MessageDataType.file, + data: { + file: element.fileName, + file_id: element.fileUuid, + file_size: element.fileSize, + }, + }; + }, + + faceElement: async element => { + const faceIndex = element.faceIndex; + if (element.faceType == FaceType.Poke) { + return { + type: OB11MessageDataType.poke, + data: { + type: element?.pokeType?.toString() ?? '0', + id: faceIndex.toString(), + }, + }; + } + + if (faceIndex === FaceIndex.DICE) { + return { + type: OB11MessageDataType.dice, + data: { + result: element.resultId!, + }, + }; + } else if (faceIndex === FaceIndex.RPS) { + return { + type: OB11MessageDataType.rps, + data: { + result: element.resultId!, + }, + }; + } else { + return { + type: OB11MessageDataType.face, + data: { + id: element.faceIndex.toString(), + raw: element, + resultId: element.resultId, + chainCount: element.chainCount, + }, + }; + } + }, + + marketFaceElement: async (_, msg, elementWrapper) => { + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + const { emojiId } = _; + const dir = emojiId.substring(0, 2); + const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`; + const filename = `${dir}-${emojiId}.gif`; + FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', filename); + return { + type: OB11MessageDataType.image, + data: { + summary: _.faceName, // 商城表情名称 + file: filename, + url, + key: _.key, + emoji_id: _.emojiId, + emoji_package_id: _.emojiPackageId, + }, + }; + }, + + replyElement: async (element, msg, _, quick_reply) => { + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + + // 创建回复数据的通用方法 + const createReplyData = (msgId: string): OB11MessageData => ({ + type: OB11MessageDataType.reply, + data: { + id: MessageUnique.createUniqueMsgId(peer, msgId).toString(), + }, + }); + + // 查找记录 + const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords); + + // 特定账号的特殊处理 + if (records && (records.peerUin === '284840486' || records.peerUin === '1094950020')) { + return createReplyData(records.msgId); + } + + // 获取消息的通用方法组 + const tryFetchMethods = async (msgSeq: string, senderUid?: string, msgTime?: string, msgRandom?: string): Promise => { + try { + // 方法1:通过序号和时间筛选 + if (senderUid && msgTime) { + const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2( + peer, msgSeq, msgTime, [senderUid] + )).msgList; + + const replyMsg = msgRandom + ? replyMsgList.find(msg => msg.msgRandom === msgRandom) + : replyMsgList.find(msg => msg.msgSeq === msgSeq); + + if (replyMsg) return replyMsg; + if (quick_reply) { + this.core.context.logger.logWarn(`快速回复,跳过方法1查询,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); + return undefined; + } + this.core.context.logger.logWarn(`方法1查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); + } + + // 方法2:直接通过序号获取 + const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount( + peer, msgSeq, 1, true, true + )).msgList; + + const replyMsg = msgRandom + ? replyMsgList.find(msg => msg.msgRandom === msgRandom) + : replyMsgList.find(msg => msg.msgSeq === msgSeq); + + if (replyMsg) return replyMsg; + + this.core.context.logger.logWarn(`方法2查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); + + // 方法3:另一种筛选方式 + if (senderUid) { + const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3( + peer, msgSeq, [senderUid] + )).msgList; + + const replyMsg = msgRandom + ? replyMsgList.find(msg => msg.msgRandom === msgRandom) + : replyMsgList.find(msg => msg.msgSeq === msgSeq); + + if (replyMsg) return replyMsg; + + this.core.context.logger.logWarn(`方法3查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`); + } + + return undefined; + } catch (error) { + this.core.context.logger.logError('查询回复消息出错', error); + return undefined; + } + }; + + // 有记录情况下,使用完整信息查询 + if (records && element.replyMsgTime && element.senderUidStr) { + const replyMsg = await tryFetchMethods( + element.replayMsgSeq, + element.senderUidStr, + records.msgTime, + records.msgRandom + ); + + if (replyMsg) { + return createReplyData(replyMsg.msgId); + } + + this.core.context.logger.logError('所有查找方法均失败,获取不到带记录的引用消息', element.replayMsgSeq); + } else { + // 旧版客户端或不完整记录的情况,也尝试使用相同流程 + this.core.context.logger.logWarn('似乎是旧版客户端,尝试仅通过序号获取引用消息', element.replayMsgSeq); + + const replyMsg = await tryFetchMethods(element.replayMsgSeq); + + if (replyMsg) { + return createReplyData(replyMsg.msgId); + } + + this.core.context.logger.logError('所有查找方法均失败,获取不到旧客户端的引用消息', element.replayMsgSeq); + } + + return null; + }, + videoElement: async (element, msg, elementWrapper, { disableGetUrl }) => { + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + // 读取视频链接并兜底 + let videoUrlWrappers: Awaited> | undefined; + + if (msg.peerUin === '284840486' || msg.peerUin === '1094950020') { + try { + videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({ + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '0', + }, msg.parentMsgIdList[0] ?? msg.msgId, elementWrapper.elementId); + } catch { + this.core.context.logger.logWarn('合并获取视频 URL 失败'); + } + } else { + try { + videoUrlWrappers = await this.core.apis.FileApi.getVideoUrl({ + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '0', + }, msg.msgId, elementWrapper.elementId); + } catch { + this.core.context.logger.logWarn('获取视频 URL 失败'); + } + } + + // 读取在线URL + let videoDownUrl: string | undefined; + + if (videoUrlWrappers) { + const videoDownUrlTemp = videoUrlWrappers.find((urlWrapper) => { + return !!(urlWrapper.url); + }); + if (videoDownUrlTemp) { + videoDownUrl = videoDownUrlTemp.url; + } + } + + // 开始兜底 + if (!videoDownUrl && !disableGetUrl) { + if (this.core.apis.PacketApi.packetStatus) { + try { + // videoDownUrl = await this.core.apis.FileApi.getVideoUrlPacket(msg.peerUid, element.fileUuid, 1500); + videoDownUrl = await registerResource( + 'video-url-get', + { + resourceFn: async () => { + return await this.core.apis.FileApi.getVideoUrlPacket(msg.peerUid, element.fileUuid, 1500); + }, + healthCheckFn: async () => { + return await this.core.apis.PacketApi.pkt.operation.FetchRkey().then(() => true).catch(() => false); + }, + testArgs: [], + healthCheckInterval: 30000, + maxHealthCheckFailures: 3, + } + ); + } catch (e) { + this.core.context.logger.logError('获取视频url失败', (e as Error).stack); + videoDownUrl = element.filePath; + } + } else { + videoDownUrl = element.filePath; + } + } + const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName); + return { + type: OB11MessageDataType.video, + data: { + file: fileCode, + url: videoDownUrl, + file_size: element.fileSize, + }, + }; + }, + + pttElement: async (element, msg, elementWrapper, { disableGetUrl }) => { + const peer = { + chatType: msg.chatType, + peerUid: msg.peerUid, + guildId: '', + }; + const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', element.fileName); + let pttUrl = ''; + if (this.core.apis.PacketApi.packetStatus && !disableGetUrl) { + try { + pttUrl = await registerResource( + 'ptt-url-get', + { + resourceFn: async () => { + return await this.core.apis.FileApi.getPttUrl(msg.peerUid, element.fileUuid, 1500); + }, + healthCheckFn: async () => { + return await this.core.apis.PacketApi.pkt.operation.FetchRkey().then(() => true).catch(() => false); + }, + testArgs: [], + healthCheckInterval: 30000, + maxHealthCheckFailures: 3, + } + ); + // pttUrl = await this.core.apis.FileApi.getPttUrl(msg.peerUid, element.fileUuid, 1500); + } catch (e) { + this.core.context.logger.logError('获取语音url失败', (e as Error).stack); + pttUrl = element.filePath; + } + } else { + pttUrl = element.filePath; + } + if (pttUrl) { + return { + type: OB11MessageDataType.voice, + data: { + file: fileCode, + path: element.filePath, + url: pttUrl, + file_size: element.fileSize, + }, + }; + } + return { + type: OB11MessageDataType.voice, + data: { + file: fileCode, + file_size: element.fileSize, + path: element.filePath, + }, + }; + }, + + multiForwardMsgElement: async (element, msg, _wrapper, context) => { + const parentMsgPeer = msg.parentMsgPeer ?? { + chatType: msg.chatType, + guildId: '', + peerUid: msg.peerUid, + }; + let multiMsgs = await this.getMultiMessages(msg, parentMsgPeer); + // 拉取失败则跳过 + if (!multiMsgs || multiMsgs.length === 0) { + try { + multiMsgs = await this.core.apis.PacketApi.pkt.operation.FetchForwardMsg(element.resId); + } catch (e) { + this.core.context.logger.logError(`Protocol FetchForwardMsg fallback failed! + element = ${JSON.stringify(element)} , error=${e})`); + return null; + } + } + const forward: OB11MessageForward = { + type: OB11MessageDataType.forward, + data: { id: msg.msgId }, + }; + if (!context.parseMultMsg) return forward; + forward.data.content = await this.parseMultiMessageContent( + multiMsgs, + parentMsgPeer, + msg.parentMsgIdList + ); + return forward; + }, + + arkElement: async (element) => { + return { + type: OB11MessageDataType.json, + data: { + data: element.bytesData, + }, + }; + }, + + markdownElement: async (element) => { + return { + type: OB11MessageDataType.markdown, + data: { + content: element.content, + }, + }; + }, + }; + + ob11ToRawConverters: Ob11ToRawConverters = { + [OB11MessageDataType.text]: async ({ data: { text } }) => ({ + elementType: ElementType.TEXT, + elementId: '', + textElement: { + content: text, + atType: NTMsgAtType.ATTYPEUNKNOWN, + atUid: '', + atTinyId: '', + atNtUid: '', + }, + }), + + [OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => { + function at (atUid: string, atNtUid: string, atType: NTMsgAtType, atName: string): SendTextElement { + return { + elementType: ElementType.TEXT, + elementId: '', + textElement: { + content: `@${atName}`, + atType, + atUid, + atTinyId: '', + atNtUid, + }, + }; + } + + if (!context.peer || !atQQ || context.peer.chatType == ChatType.KCHATTYPEC2C) return undefined; // 过滤掉空atQQ + if (atQQ === 'all') return at(atQQ, atQQ, NTMsgAtType.ATTYPEALL, '全体成员'); + const atMember = await this.core.apis.GroupApi.getGroupMember(context.peer.peerUid, atQQ); + if (atMember) { + return at(atQQ, atMember.uid, NTMsgAtType.ATTYPEONE, atMember.nick || atMember.cardName); + } + const uid = await this.core.apis.UserApi.getUidByUinV2(`${atQQ}`); + if (!uid) throw new Error('Get Uid Error'); + const info = await this.core.apis.UserApi.getUserDetailInfo(uid); + return at(atQQ, uid, NTMsgAtType.ATTYPEONE, info.nick || ''); + }, + + [OB11MessageDataType.reply]: async ({ data: { id } }) => { + const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id)); + if (!replyMsgM) { + this.core.context.logger.logWarn('回复消息不存在', id); + return undefined; + } + const replyMsg = (await this.core.apis.MsgApi.getMsgsByMsgId( + replyMsgM.Peer, [replyMsgM.MsgId])).msgList[0]; + return replyMsg + ? { + elementType: ElementType.REPLY, + elementId: '', + replyElement: { + replayMsgSeq: replyMsg.msgSeq, // raw.msgSeq + replayMsgId: replyMsg.msgId, // raw.msgId + senderUin: replyMsg.senderUin, + senderUinStr: replyMsg.senderUin, + replyMsgClientSeq: replyMsg.clientSeq, + _replyMsgPeer: replyMsgM.Peer, + }, + } + : undefined; + }, + + [OB11MessageDataType.face]: async ({ data: { id, resultId, chainCount } }) => { + const parsedFaceId = +id; + // 从face_config.json中获取表情名称 + const sysFaces = faceConfig.sysface; + const face: { + QSid?: string, + QDes?: string, + AniStickerId?: string, + AniStickerType?: number, + AniStickerPackId?: string, + } | undefined = sysFaces.find((systemFace) => systemFace.QSid === parsedFaceId.toString()); + if (!face) { + this.core.context.logger.logError('不支持的ID', id); + return undefined; + } + let faceType = 1; + if (parsedFaceId >= 222) { + faceType = 2; + } + if (face.AniStickerType) { + faceType = 3; + } + return { + elementType: ElementType.FACE, + elementId: '', + faceElement: { + faceIndex: parsedFaceId, + faceType, + faceText: face.QDes, + stickerId: face.AniStickerId, + stickerType: face.AniStickerType, + packId: face.AniStickerPackId, + sourceType: 1, + resultId: resultId?.toString(), + chainCount, + }, + }; + }, + [OB11MessageDataType.mface]: async ({ + data: { + emoji_package_id, emoji_id, key, summary, + }, + }) => ({ + elementType: ElementType.MFACE, + elementId: '', + marketFaceElement: { + emojiPackageId: emoji_package_id, + emojiId: emoji_id, + key, + faceName: summary || '[商城表情]', + }, + }), + + // File service + [OB11MessageDataType.image]: async (sendMsg, context) => { + return await this.core.apis.FileApi.createValidSendPicElement( + context, + (await this.handleOb11FileLikeMessage(sendMsg, context)).path, + sendMsg.data.summary, + sendMsg.data.sub_type + ); + }, + + [OB11MessageDataType.file]: async (sendMsg, context) => { + const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); + return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName); + }, + + [OB11MessageDataType.video]: async (sendMsg, context) => { + const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); + + let thumb = sendMsg.data.thumb; + if (thumb) { + const uri2LocalRes = await uriToLocalFile(this.core.NapCatTempPath, thumb); + if (uri2LocalRes.success) { + thumb = uri2LocalRes.path; + context.deleteAfterSentFiles.push(thumb); + } + } + + return await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb); + }, + + [OB11MessageDataType.voice]: async (sendMsg, context) => + this.core.apis.FileApi.createValidSendPttElement(context, + (await this.handleOb11FileLikeMessage(sendMsg, context)).path), + + [OB11MessageDataType.json]: async ({ data: { data } }) => ({ + elementType: ElementType.ARK, + elementId: '', + arkElement: { + bytesData: typeof data === 'string' ? data : JSON.stringify(data), + linkInfo: null, + subElementType: null, + }, + }), + + [OB11MessageDataType.dice]: async () => ({ + elementType: ElementType.FACE, + elementId: '', + faceElement: { + faceIndex: FaceIndex.DICE, + faceType: FaceType.AniSticke, + faceText: '[骰子]', + packId: '1', + stickerId: '33', + sourceType: 1, + stickerType: 2, + surpriseId: '', + // "randomType": 1, + }, + }), + + [OB11MessageDataType.rps]: async () => ({ + elementType: ElementType.FACE, + elementId: '', + faceElement: { + faceIndex: FaceIndex.RPS, + faceText: '[包剪锤]', + faceType: FaceType.AniSticke, + packId: '1', + stickerId: '34', + sourceType: 1, + stickerType: 2, + surpriseId: '', + // "randomType": 1, + }, + }), + + // Need signing + [OB11MessageDataType.markdown]: async ({ data: { content } }) => ({ + elementType: ElementType.MARKDOWN, + elementId: '', + markdownElement: { content }, + }), + + [OB11MessageDataType.music]: async ({ data }, context) => { + // 保留, 直到...找到更好的解决方案 + if (data.id !== undefined) { + if (!['qq', '163', 'kugou', 'kuwo', 'migu'].includes(data.type)) { + this.core.context.logger.logError('音乐卡片type错误, 只支持qq、163、kugou、kuwo、migu,当前type:', data.type); + return undefined; + } + } else { + if (!['qq', '163', 'kugou', 'kuwo', 'migu', 'custom'].includes(data.type)) { + this.core.context.logger.logError('音乐卡片type错误, 只支持qq、163、kugou、kuwo、migu、custom,当前type:', data.type); + return undefined; + } + if (!data.url) { + this.core.context.logger.logError('自定义音卡缺少参数url'); + return undefined; + } + if (!data.image) { + this.core.context.logger.logError('自定义音卡缺少参数image'); + return undefined; + } + } + + let postData: IdMusicSignPostData | CustomMusicSignPostData; + if (data.id === undefined && data.content) { + const { content, ...others } = data; + postData = { singer: content, ...others }; + } else { + postData = data; + } + let signUrl = this.obContext.configLoader.configData.musicSignUrl; + if (!signUrl) { + signUrl = 'https://ss.xingzhige.com/music_card/card';// 感谢思思!已获思思许可 其余地方使用请自行询问 + // throw Error('音乐消息签名地址未配置'); + } + try { + const musicJson = await RequestUtil.HttpGetJson(signUrl, 'POST', postData); + return this.ob11ToRawConverters.json({ + data: { data: musicJson }, + type: OB11MessageDataType.json, + }, context); + } catch (e) { + this.core.context.logger.logError('生成音乐消息失败', e); + } + return undefined; + }, + + [OB11MessageDataType.node]: async () => undefined, + + [OB11MessageDataType.forward]: async ({ data }, context) => { + // let id = data.id.toString(); + // let peer: Peer | undefined = context.peer; + // if (isNumeric(id)) { + // let msgid = ''; + // if (BigInt(data.id) > 2147483647n) { + // peer = MessageUnique.getPeerByMsgId(id)?.Peer; + // msgid = id; + // } else { + // let data = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id)); + // msgid = data?.MsgId ?? ''; + // peer = data?.Peer; + // } + // } + const jsonData = ForwardMsgBuilder.fromResId(data.id); + return this.ob11ToRawConverters.json({ + data: { data: JSON.stringify(jsonData) }, + type: OB11MessageDataType.json, + }, context); + }, + + [OB11MessageDataType.xml]: async () => undefined, + + [OB11MessageDataType.poke]: async () => undefined, + + [OB11MessageDataType.location]: async () => ({ + elementType: ElementType.SHARELOCATION, + elementId: '', + shareLocationElement: { + text: '测试', + ext: '', + }, + }), + + [OB11MessageDataType.miniapp]: async () => undefined, + + [OB11MessageDataType.contact]: async ({ data: { type = 'qq', id } }, context) => { + if (type === 'qq') { + const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(id.toString(), ''); + return this.ob11ToRawConverters.json({ + data: { data: arkJson.arkMsg }, + type: OB11MessageDataType.json, + }, context); + } else if (type === 'group') { + const arkJson = await this.core.apis.GroupApi.getGroupRecommendContactArkJson(id.toString()); + return this.ob11ToRawConverters.json({ + data: { data: arkJson.arkJson }, + type: OB11MessageDataType.json, + }, context); + } + return undefined; + }, + }; + + constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + this.obContext = obContext; + this.core = core; + } + + /** * 解析带有JSON标记的文本 * @param text 要解析的文本 * @returns 解析后的结果数组,每个元素包含类型(text或json)和内容 */ - parseTextWithJson(text: string) { - // 匹配<{...}>格式的JSON - const regex = /<(\{.*?\})>/g; - const parts: Array<{ type: 'text' | 'json', content: string | object }> = []; - let lastIndex = 0; - let match; + parseTextWithJson (text: string) { + // 匹配<{...}>格式的JSON + const regex = /<(\{.*?\})>/g; + const parts: Array<{ type: 'text' | 'json', content: string | object }> = []; + let lastIndex = 0; + let match; - // 查找所有匹配项 - while ((match = regex.exec(text)) !== null) { - // 添加匹配前的文本 - if (match.index > lastIndex) { - parts.push({ - type: 'text', - content: text.substring(lastIndex, match.index) - }); - } - - // 添加JSON部分 - try { - const jsonContent = JSON.parse(match[1] ?? ''); - parts.push({ - type: 'json', - content: jsonContent - }); - } catch (e) { - // 如果JSON解析失败,作为普通文本处理 - parts.push({ - type: 'text', - content: match[0] - }); - } - - lastIndex = regex.lastIndex; - } - - // 添加最后一部分文本 - if (lastIndex < text.length) { - parts.push({ - type: 'text', - content: text.substring(lastIndex) - }); - } - - return parts; - } - - async parsePrivateMsgEvent(msg: RawMessage, grayTipElement: GrayTipElement) { - if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) { - if (grayTipElement.jsonGrayTipElement.busiId == 1061) { - const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(grayTipElement, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid))); - if (PokeEvent) { - return PokeEvent; - } - ; - } else if (grayTipElement.jsonGrayTipElement.busiId == 19324 && msg.peerUid !== '') { - return new OB11FriendAddNoticeEvent(this.core, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid))); - } - } - return; - } - - private async getMultiMessages(msg: RawMessage, parentMsgPeer: Peer) { - //判断是否在合并消息内 - msg.parentMsgIdList = msg.parentMsgIdList ?? []; - //首次列表不存在则开始创建 - msg.parentMsgIdList.push(msg.msgId); - //拉取下级消息 - if (msg.parentMsgIdList[0]) { - return (await this.core.apis.MsgApi.getMultiMsg( - parentMsgPeer, - msg.parentMsgIdList[0], - msg.msgId - ))?.msgList; - } - return undefined; - } - - private async parseMultiMessageContent( - multiMsgs: RawMessage[], - parentMsgPeer: Peer, - parentMsgIdList: string[] - ) { - const parsed = await Promise.all(multiMsgs.map(async msg => { - msg.parentMsgPeer = parentMsgPeer; - msg.parentMsgIdList = parentMsgIdList; - msg.id = MessageUnique.createUniqueMsgId(parentMsgPeer, msg.msgId); - //该ID仅用查看 无法调用 - return await this.parseMessage(msg, 'array', true); - })); - return parsed.filter(item => item !== undefined); - } - - async parseMessage( - msg: RawMessage, - messagePostFormat: string, - parseMultMsg: boolean = true, - disableGetUrl: boolean = false, - quick_reply: boolean = false - ) { - if (messagePostFormat === 'string') { - return (await this.parseMessageV2(msg, parseMultMsg, disableGetUrl, quick_reply))?.stringMsg; - } - return (await this.parseMessageV2(msg, parseMultMsg, disableGetUrl, quick_reply))?.arrayMsg; - } - - async parseMessageV2( - msg: RawMessage, - parseMultMsg: boolean = true, - disableGetUrl: boolean = false, - quick_reply: boolean = false - ) { - if (msg.senderUin == '0' || msg.senderUin == '') return; - if (msg.peerUin == '0' || msg.peerUin == '') return; - - const resMsg = this.initializeMessage(msg); - - if (this.core.selfInfo.uin == msg.senderUin) { - resMsg.message_sent_type = 'self'; - } - - if (msg.chatType == ChatType.KCHATTYPEGROUP) { - await this.handleGroupMessage(resMsg, msg); - } else if (msg.chatType == ChatType.KCHATTYPEC2C) { - await this.handlePrivateMessage(resMsg, msg); - } else if (msg.chatType == ChatType.KCHATTYPETEMPC2CFROMGROUP) { - await this.handleTempGroupMessage(resMsg, msg); - } else { - return undefined; - } - - const validSegments = await this.parseMessageSegments(msg, parseMultMsg, disableGetUrl, quick_reply); - resMsg.message = validSegments; - resMsg.raw_message = validSegments.map(msg => encodeCQCode(msg)).join('').trim(); - - const stringMsg = await this.convertArrayToStringMessage(resMsg); - return { stringMsg, arrayMsg: resMsg }; - } - - private initializeMessage(msg: RawMessage): OB11Message { - return { - self_id: parseInt(this.core.selfInfo.uin), - user_id: parseInt(msg.senderUin), - time: parseInt(msg.msgTime) || Date.now(), - message_id: msg.id!, - message_seq: msg.id!, - real_id: msg.id!, - real_seq: msg.msgSeq, - message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private', - sender: { - user_id: +(msg.senderUin ?? 0), - nickname: msg.sendNickName, - card: msg.sendMemberName ?? '', - }, - raw_message: '', - font: 14, - sub_type: 'friend', - message: [], - message_format: 'array', - post_type: this.core.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, - }; - } - - private async handleGroupMessage(resMsg: OB11Message, msg: RawMessage) { - resMsg.sub_type = 'normal'; - resMsg.group_id = parseInt(msg.peerUin); - resMsg.group_name = msg.peerName; - let member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); - if (!member) member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); - if (member) { - resMsg.sender.role = OB11Construct.groupMemberRole(member.role); - resMsg.sender.nickname = member.nick; - } - } - - private async handlePrivateMessage(resMsg: OB11Message, msg: RawMessage) { - resMsg.sub_type = 'friend'; - if (await this.core.apis.FriendApi.isBuddy(msg.senderUid)) { - const nickname = (await this.core.apis.UserApi.getCoreAndBaseInfo([msg.senderUid])).get(msg.senderUid)?.coreInfo.nick; - if (nickname) { - resMsg.sender.nickname = nickname; - return; - } - } - resMsg.sender.nickname = (await this.core.apis.UserApi.getUserDetailInfo(msg.senderUid)).nick; - } - - private async handleTempGroupMessage(resMsg: OB11Message, msg: RawMessage) { - resMsg.sub_type = 'group'; - const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid); - if (ret.result === 0) { - const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); - resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode); - resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话'; - resMsg.temp_source = 0; - } else { - resMsg.group_id = 284840486; - resMsg.temp_source = 0; - resMsg.sender.nickname = '临时会话'; - } - } - - private async parseMessageSegments(msg: RawMessage, parseMultMsg: boolean, disableGetUrl: boolean = false, quick_reply: boolean = false): Promise { - const msgSegments = await Promise.allSettled(msg.elements.map( - async (element) => { - for (const key in element) { - if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) { - const converters = this.rawToOb11Converters[key] as ( - element: Exclude, - msg: RawMessage, - elementWrapper: MessageElement, - context: RecvMessageContext - ) => Promise; - const parsedElement = await converters?.( - element[key], - msg, - element, - { parseMultMsg, disableGetUrl, quick_reply } - ); - if (key === 'faceElement' && !parsedElement) { - return null; - } - return parsedElement; - } - } - return; - }, - )); - - return msgSegments.filter(entry => { - if (entry.status === 'fulfilled') { - return !!entry.value; - } else { - this.core.context.logger.logError('消息段解析失败', entry.reason); - return false; - } - }).map((entry) => (>entry).value).filter(value => value != null); - } - - private async convertArrayToStringMessage(originMsg: OB11Message): Promise { - const msg = structuredClone(originMsg); - msg.message_format = 'string'; - msg.message = msg.raw_message; - return msg; - } - - async importArrayTostringMsg(originMsg: OB11Message) { - const msg = structuredClone(originMsg); - msg.message_format = 'string'; - msg.message = msg.raw_message; - return msg; - } - - async createSendElements( - messageData: OB11MessageData[], - peer: Peer, - ignoreTypes: OB11MessageDataType[] = [], - ) { - const deleteAfterSentFiles: string[] = []; - const callResultList: Array> = []; - for (const sendMsg of messageData) { - if (ignoreTypes.includes(sendMsg.type)) { - continue; - } - const converter = this.ob11ToRawConverters[sendMsg.type] as (( - sendMsg: Extract, - context: SendMessageContext, - ) => Promise) | undefined; - if (converter == undefined) { - throw new Error('未知的消息类型:' + sendMsg.type); - } - const callResult = converter( - sendMsg, - { peer, deleteAfterSentFiles }, - )?.catch(undefined); - callResultList.push(callResult); - } - const ret = await Promise.all(callResultList); - const sendElements: SendMessageElement[] = ret.filter(ele => !!ele); - return { sendElements, deleteAfterSentFiles }; - } - - async sendMsgWithOb11UniqueId(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[]) { - if (!sendElements.length) { - throw new Error('消息体无法解析, 请检查是否发送了不支持的消息类型'); - } - - const calculateTotalSize = async (elements: SendMessageElement[]): Promise => { - const sizePromises = elements.map(async element => { - switch (element.elementType) { - case ElementType.PTT: - return (await fsPromise.stat(element.pttElement.filePath)).size; - case ElementType.FILE: - return (await fsPromise.stat(element.fileElement.filePath)).size; - case ElementType.VIDEO: - return (await fsPromise.stat(element.videoElement.filePath)).size; - case ElementType.PIC: - return (await fsPromise.stat(element.picElement.sourcePath)).size; - default: - return 0; - } - }); - const sizes = await Promise.all(sizePromises); - return sizes.reduce((total, size) => total + size, 0); - }; - - const totalSize = await calculateTotalSize(sendElements).catch(e => { - this.core.context.logger.logError('发送消息计算预计时间异常', e); - return 0; + // 查找所有匹配项 + while ((match = regex.exec(text)) !== null) { + // 添加匹配前的文本 + if (match.index > lastIndex) { + parts.push({ + type: 'text', + content: text.substring(lastIndex, match.index), }); + } - const timeout = 10000 + (totalSize / 1024 / 256 * 1000); - try { - const returnMsg = await this.core.apis.MsgApi.sendMsg(peer, sendElements, timeout); - if (!returnMsg) throw new Error('发送消息失败'); - returnMsg.id = MessageUnique.createUniqueMsgId({ - chatType: peer.chatType, - guildId: '', - peerUid: peer.peerUid, - }, returnMsg.msgId); - return returnMsg; - } catch (error) { - throw error; - } finally { - cleanTaskQueue.addFiles(deleteAfterSentFiles, timeout); - // setTimeout(async () => { - // const deletePromises = deleteAfterSentFiles.map(async file => { - // try { - // if (await fsPromise.access(file, constants.W_OK).then(() => true).catch(() => false)) { - // await fsPromise.unlink(file); - // } - // } catch (e) { - // this.core.context.logger.logError('发送消息删除文件失败', e); - // } - // }); - // await Promise.all(deletePromises); - // }, 60000); - } + // 添加JSON部分 + try { + const jsonContent = JSON.parse(match[1] ?? ''); + parts.push({ + type: 'json', + content: jsonContent, + }); + } catch (e) { + // 如果JSON解析失败,作为普通文本处理 + parts.push({ + type: 'text', + content: match[0], + }); + } + + lastIndex = regex.lastIndex; } - private async handleOb11FileLikeMessage( - { data: inputdata }: OB11MessageFileBase, - { deleteAfterSentFiles }: SendMessageContext - ) { - let realUri = [inputdata.url, inputdata.file, inputdata.path].find(uri => uri && uri.trim()) ?? ''; - if (!realUri) { - this.core.context.logger.logError('文件消息缺少参数', inputdata); - throw new Error('文件消息缺少参数'); + // 添加最后一部分文本 + if (lastIndex < text.length) { + parts.push({ + type: 'text', + content: text.substring(lastIndex), + }); + } + + return parts; + } + + async parsePrivateMsgEvent (msg: RawMessage, grayTipElement: GrayTipElement) { + if (grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_JSON) { + if (grayTipElement.jsonGrayTipElement.busiId == 1061) { + const PokeEvent = await this.obContext.apis.FriendApi.parsePrivatePokeEvent(grayTipElement, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid))); + if (PokeEvent) { + return PokeEvent; } - realUri = await this.handleObfuckName(realUri) ?? realUri; - try { - const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, realUri); - if (!success) { - this.core.context.logger.logError('文件处理失败', errMsg); - throw new Error('文件处理失败: ' + errMsg); + } else if (grayTipElement.jsonGrayTipElement.busiId == 19324 && msg.peerUid !== '') { + return new OB11FriendAddNoticeEvent(this.core, Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid))); + } + } + } + + private async getMultiMessages (msg: RawMessage, parentMsgPeer: Peer) { + // 判断是否在合并消息内 + msg.parentMsgIdList = msg.parentMsgIdList ?? []; + // 首次列表不存在则开始创建 + msg.parentMsgIdList.push(msg.msgId); + // 拉取下级消息 + if (msg.parentMsgIdList[0]) { + return (await this.core.apis.MsgApi.getMultiMsg( + parentMsgPeer, + msg.parentMsgIdList[0], + msg.msgId + ))?.msgList; + } + return undefined; + } + + private async parseMultiMessageContent ( + multiMsgs: RawMessage[], + parentMsgPeer: Peer, + parentMsgIdList: string[] + ) { + const parsed = await Promise.all(multiMsgs.map(async msg => { + msg.parentMsgPeer = parentMsgPeer; + msg.parentMsgIdList = parentMsgIdList; + msg.id = MessageUnique.createUniqueMsgId(parentMsgPeer, msg.msgId); + // 该ID仅用查看 无法调用 + return await this.parseMessage(msg, 'array', true); + })); + return parsed.filter(item => item !== undefined); + } + + async parseMessage ( + msg: RawMessage, + messagePostFormat: string, + parseMultMsg: boolean = true, + disableGetUrl: boolean = false, + quick_reply: boolean = false + ) { + if (messagePostFormat === 'string') { + return (await this.parseMessageV2(msg, parseMultMsg, disableGetUrl, quick_reply))?.stringMsg; + } + return (await this.parseMessageV2(msg, parseMultMsg, disableGetUrl, quick_reply))?.arrayMsg; + } + + async parseMessageV2 ( + msg: RawMessage, + parseMultMsg: boolean = true, + disableGetUrl: boolean = false, + quick_reply: boolean = false + ) { + if (msg.senderUin == '0' || msg.senderUin == '') return; + if (msg.peerUin == '0' || msg.peerUin == '') return; + + const resMsg = this.initializeMessage(msg); + + if (this.core.selfInfo.uin == msg.senderUin) { + resMsg.message_sent_type = 'self'; + } + + if (msg.chatType == ChatType.KCHATTYPEGROUP) { + await this.handleGroupMessage(resMsg, msg); + } else if (msg.chatType == ChatType.KCHATTYPEC2C) { + await this.handlePrivateMessage(resMsg, msg); + } else if (msg.chatType == ChatType.KCHATTYPETEMPC2CFROMGROUP) { + await this.handleTempGroupMessage(resMsg, msg); + } else { + return undefined; + } + + const validSegments = await this.parseMessageSegments(msg, parseMultMsg, disableGetUrl, quick_reply); + resMsg.message = validSegments; + resMsg.raw_message = validSegments.map(msg => encodeCQCode(msg)).join('').trim(); + + const stringMsg = await this.convertArrayToStringMessage(resMsg); + return { stringMsg, arrayMsg: resMsg }; + } + + private initializeMessage (msg: RawMessage): OB11Message { + return { + self_id: parseInt(this.core.selfInfo.uin), + user_id: parseInt(msg.senderUin), + time: parseInt(msg.msgTime) || Date.now(), + message_id: msg.id!, + message_seq: msg.id!, + real_id: msg.id!, + real_seq: msg.msgSeq, + message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private', + sender: { + user_id: +(msg.senderUin ?? 0), + nickname: msg.sendNickName, + card: msg.sendMemberName ?? '', + }, + raw_message: '', + font: 14, + sub_type: 'friend', + message: [], + message_format: 'array', + post_type: this.core.selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, + }; + } + + private async handleGroupMessage (resMsg: OB11Message, msg: RawMessage) { + resMsg.sub_type = 'normal'; + resMsg.group_id = parseInt(msg.peerUin); + resMsg.group_name = msg.peerName; + let member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); + if (!member) member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); + if (member) { + resMsg.sender.role = OB11Construct.groupMemberRole(member.role); + resMsg.sender.nickname = member.nick; + } + } + + private async handlePrivateMessage (resMsg: OB11Message, msg: RawMessage) { + resMsg.sub_type = 'friend'; + if (await this.core.apis.FriendApi.isBuddy(msg.senderUid)) { + const nickname = (await this.core.apis.UserApi.getCoreAndBaseInfo([msg.senderUid])).get(msg.senderUid)?.coreInfo.nick; + if (nickname) { + resMsg.sender.nickname = nickname; + return; + } + } + resMsg.sender.nickname = (await this.core.apis.UserApi.getUserDetailInfo(msg.senderUid)).nick; + } + + private async handleTempGroupMessage (resMsg: OB11Message, msg: RawMessage) { + resMsg.sub_type = 'group'; + const ret = await this.core.apis.MsgApi.getTempChatInfo(ChatType.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid); + if (ret.result === 0) { + const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUin, msg.senderUin); + resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode); + resMsg.sender.nickname = member?.nick ?? member?.cardName ?? '临时会话'; + resMsg.temp_source = 0; + } else { + resMsg.group_id = 284840486; + resMsg.temp_source = 0; + resMsg.sender.nickname = '临时会话'; + } + } + + private async parseMessageSegments (msg: RawMessage, parseMultMsg: boolean, disableGetUrl: boolean = false, quick_reply: boolean = false): Promise { + const msgSegments = await Promise.allSettled(msg.elements.map( + async (element) => { + for (const key in element) { + if (keyCanBeParsed(key, this.rawToOb11Converters) && element[key]) { + const converters = this.rawToOb11Converters[key] as ( + element: Exclude, + msg: RawMessage, + elementWrapper: MessageElement, + context: RecvMessageContext + ) => Promise; + const parsedElement = await converters?.( + element[key], + msg, + element, + { parseMultMsg, disableGetUrl, quick_reply } + ); + if (key === 'faceElement' && !parsedElement) { + return null; } - deleteAfterSentFiles.push(path); - return { path, fileName: inputdata.name ?? fileName }; - } catch (e: unknown) { - throw new Error((e as Error).message); + return parsedElement; + } } + } + )); + + return msgSegments.filter(entry => { + if (entry.status === 'fulfilled') { + return !!entry.value; + } else { + this.core.context.logger.logError('消息段解析失败', entry.reason); + return false; + } + }).map((entry) => (>entry).value).filter(value => value != null); + } + + private async convertArrayToStringMessage (originMsg: OB11Message): Promise { + const msg = structuredClone(originMsg); + msg.message_format = 'string'; + msg.message = msg.raw_message; + return msg; + } + + async importArrayTostringMsg (originMsg: OB11Message) { + const msg = structuredClone(originMsg); + msg.message_format = 'string'; + msg.message = msg.raw_message; + return msg; + } + + async createSendElements ( + messageData: OB11MessageData[], + peer: Peer, + ignoreTypes: OB11MessageDataType[] = [] + ) { + const deleteAfterSentFiles: string[] = []; + const callResultList: Array> = []; + for (const sendMsg of messageData) { + if (ignoreTypes.includes(sendMsg.type)) { + continue; + } + const converter = this.ob11ToRawConverters[sendMsg.type] as (( + sendMsg: Extract, + context: SendMessageContext, + ) => Promise) | undefined; + if (converter == undefined) { + throw new Error('未知的消息类型:' + sendMsg.type); + } + const callResult = converter( + sendMsg, + { peer, deleteAfterSentFiles } + )?.catch(undefined); + callResultList.push(callResult); + } + const ret = await Promise.all(callResultList); + const sendElements: SendMessageElement[] = ret.filter(ele => !!ele); + return { sendElements, deleteAfterSentFiles }; + } + + async sendMsgWithOb11UniqueId (peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[]) { + if (!sendElements.length) { + throw new Error('消息体无法解析, 请检查是否发送了不支持的消息类型'); } - async handleObfuckName(name: string) { - const contextMsgFile = FileNapCatOneBotUUID.decode(name); - if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { - const { peer, msgId, elementId } = contextMsgFile; - const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList.find(msg => msg.msgId === msgId); - const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); - const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; - if (!mixElementInner) throw new Error('element not found'); - let url = ''; - if (mixElement?.picElement && rawMessage) { - const tempData = + const calculateTotalSize = async (elements: SendMessageElement[]): Promise => { + const sizePromises = elements.map(async element => { + switch (element.elementType) { + case ElementType.PTT: + return (await fsPromise.stat(element.pttElement.filePath)).size; + case ElementType.FILE: + return (await fsPromise.stat(element.fileElement.filePath)).size; + case ElementType.VIDEO: + return (await fsPromise.stat(element.videoElement.filePath)).size; + case ElementType.PIC: + return (await fsPromise.stat(element.picElement.sourcePath)).size; + default: + return 0; + } + }); + const sizes = await Promise.all(sizePromises); + return sizes.reduce((total, size) => total + size, 0); + }; + + const totalSize = await calculateTotalSize(sendElements).catch(e => { + this.core.context.logger.logError('发送消息计算预计时间异常', e); + return 0; + }); + + const timeout = 10000 + (totalSize / 1024 / 256 * 1000); + try { + const returnMsg = await this.core.apis.MsgApi.sendMsg(peer, sendElements, timeout); + if (!returnMsg) throw new Error('发送消息失败'); + returnMsg.id = MessageUnique.createUniqueMsgId({ + chatType: peer.chatType, + guildId: '', + peerUid: peer.peerUid, + }, returnMsg.msgId); + return returnMsg; + } catch (error) { + throw error; + } finally { + cleanTaskQueue.addFiles(deleteAfterSentFiles, timeout); + // setTimeout(async () => { + // const deletePromises = deleteAfterSentFiles.map(async file => { + // try { + // if (await fsPromise.access(file, constants.W_OK).then(() => true).catch(() => false)) { + // await fsPromise.unlink(file); + // } + // } catch (e) { + // this.core.context.logger.logError('发送消息删除文件失败', e); + // } + // }); + // await Promise.all(deletePromises); + // }, 60000); + } + } + + private async handleOb11FileLikeMessage ( + { data: inputdata }: OB11MessageFileBase, + { deleteAfterSentFiles }: SendMessageContext + ) { + let realUri = [inputdata.url, inputdata.file, inputdata.path].find(uri => uri && uri.trim()) ?? ''; + if (!realUri) { + this.core.context.logger.logError('文件消息缺少参数', inputdata); + throw new Error('文件消息缺少参数'); + } + realUri = await this.handleObfuckName(realUri) ?? realUri; + try { + const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, realUri); + if (!success) { + this.core.context.logger.logError('文件处理失败', errMsg); + throw new Error('文件处理失败: ' + errMsg); + } + deleteAfterSentFiles.push(path); + return { path, fileName: inputdata.name ?? fileName }; + } catch (e: unknown) { + throw new Error((e as Error).message); + } + } + + async handleObfuckName (name: string) { + const contextMsgFile = FileNapCatOneBotUUID.decode(name); + if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { + const { peer, msgId, elementId } = contextMsgFile; + const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList.find(msg => msg.msgId === msgId); + const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); + const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; + if (!mixElementInner) throw new Error('element not found'); + let url = ''; + if (mixElement?.picElement && rawMessage) { + const tempData = await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: false }) as OB11MessageImage | undefined; - url = tempData?.data.url ?? ''; - } - if (mixElement?.videoElement && rawMessage) { - const tempData = + url = tempData?.data.url ?? ''; + } + if (mixElement?.videoElement && rawMessage) { + const tempData = await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: false }) as OB11MessageVideo | undefined; - url = tempData?.data.url ?? ''; + url = tempData?.data.url ?? ''; + } + return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); + } + return undefined; + } + + groupChangDecreseType2String (type: number): GroupDecreaseSubType { + switch (type) { + case 130: + return 'leave'; + case 131: + return 'kick'; + case 3: + return 'kick_me'; + case 129: + return 'disband'; + default: + return 'kick'; + } + } + + async waitGroupNotify (groupUin: string, memberUid?: string, operatorUid?: string) { + const groupRole = this.core.apis.GroupApi.groupMemberCache.get(groupUin)?.get(this.core.selfInfo.uid.toString())?.role; + const isAdminOrOwner = groupRole === 3 || groupRole === 4; + if (isAdminOrOwner && !operatorUid) { + let dataNotify: GroupNotify | undefined; + await this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onGroupNotifiesUpdated', + (_doubt, notifies) => { + for (const notify of notifies) { + if (notify.group.groupCode === groupUin && notify.user1.uid === memberUid) { + dataNotify = notify; + return true; } - return url !== '' ? url : await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); - } - return undefined; + } + return false; + }, 1, 1000).catch(() => undefined); + if (dataNotify) { + return !dataNotify.actionUser.uid ? dataNotify.user2.uid : dataNotify.actionUser.uid; + } } - groupChangDecreseType2String(type: number): GroupDecreaseSubType { - switch (type) { - case 130: - return 'leave'; - case 131: - return 'kick'; - case 3: - return 'kick_me'; - case 129: - return 'disband'; - default: - return 'kick'; - } - } + return operatorUid; + } - async waitGroupNotify(groupUin: string, memberUid?: string, operatorUid?: string) { - const groupRole = this.core.apis.GroupApi.groupMemberCache.get(groupUin)?.get(this.core.selfInfo.uid.toString())?.role; - const isAdminOrOwner = groupRole === 3 || groupRole === 4; - if (isAdminOrOwner && !operatorUid) { - let dataNotify: GroupNotify | undefined; - await this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onGroupNotifiesUpdated', - (_doubt, notifies) => { - for (const notify of notifies) { - if (notify.group.groupCode === groupUin && notify.user1.uid === memberUid) { - dataNotify = notify; - return true; - } - } - return false; - }, 1, 1000).catch(() => undefined); - if (dataNotify) { - return !dataNotify.actionUser.uid ? dataNotify.user2.uid : dataNotify.actionUser.uid; - } - } + async parseSysMessage (msg: number[]) { + const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg)); + // 邀请需要解grayTipElement + if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) { + const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent); + await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), true); + const operatorUid = await this.waitGroupNotify( + groupChange.groupUin.toString(), + groupChange.memberUid, + groupChange.operatorInfo ? new TextDecoder('utf-8').decode(groupChange.operatorInfo) : undefined + ); + return new OB11GroupIncreaseEvent( + this.core, + groupChange.groupUin, + groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0, + operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(operatorUid) : 0, + groupChange.decreaseType == 131 ? 'invite' : 'approve' + ); + } else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) { + const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent); - return operatorUid; - } - - async parseSysMessage(msg: number[]) { - const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg)); - // 邀请需要解grayTipElement - if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) { - const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent); - await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), true); - const operatorUid = await this.waitGroupNotify( - groupChange.groupUin.toString(), - groupChange.memberUid, - groupChange.operatorInfo ? new TextDecoder('utf-8').decode(groupChange.operatorInfo) : undefined - ); - return new OB11GroupIncreaseEvent( - this.core, - groupChange.groupUin, - groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0, - operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(operatorUid) : 0, - groupChange.decreaseType == 131 ? 'invite' : 'approve', - ); - - } else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) { - const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent); - - let operator_uid_parse: string | undefined = undefined; - if (groupChange.operatorInfo) { - // 先判断是否可能是protobuf(自身被踢出或以0a开头) - if (groupChange.decreaseType === 3 || Buffer.from(groupChange.operatorInfo).toString('hex').startsWith('0a')) { - // 可能是protobuf,尝试解析 - try { - operator_uid_parse = new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid; - } catch (error) { - // protobuf解析失败,fallback到字符串解析 - try { - const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo); - // 检查是否包含非ASCII字符,如果包含则丢弃 - const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126); - operator_uid_parse = isAsciiOnly ? decoded : ''; - } catch (e2) { - operator_uid_parse = ''; - } - } - } else { - // 直接进行字符串解析 - try { - const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo); - // 检查是否包含非ASCII字符,如果包含则丢弃 - const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126); - operator_uid_parse = isAsciiOnly ? decoded : ''; - } catch (e) { - operator_uid_parse = ''; - } - } - } - - const operatorUid = await this.waitGroupNotify( - groupChange.groupUin.toString(), - groupChange.memberUid, - operator_uid_parse - ); - if (groupChange.memberUid === this.core.selfInfo.uid) { - setTimeout(() => { - this.core.apis.GroupApi.groupMemberCache.delete(groupChange.groupUin.toString()); - }, 5000); - // 自己被踢了 5S后回收 - } else { - await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), true); - } - return new OB11GroupDecreaseEvent( - this.core, - groupChange.groupUin, - groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0, - operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(operatorUid) : 0, - this.groupChangDecreseType2String(groupChange.decreaseType), - ); - } else if (SysMessage.contentHead.type == 44 && SysMessage.body?.msgContent) { - const groupAmin = new NapProtoMsg(GroupAdmin).decode(SysMessage.body.msgContent); - await this.core.apis.GroupApi.refreshGroupMemberCache(groupAmin.groupUin.toString(), true); - let enabled = false; - let uid = ''; - if (groupAmin.body.extraEnable != null) { - uid = groupAmin.body.extraEnable.adminUid; - enabled = true; - } else if (groupAmin.body.extraDisable != null) { - uid = groupAmin.body.extraDisable.adminUid; - enabled = false; - } - return new OB11GroupAdminNoticeEvent( - this.core, - groupAmin.groupUin, - +await this.core.apis.UserApi.getUinByUidV2(uid), - enabled ? 'set' : 'unset' - ); - } else if (SysMessage.contentHead.type == 87 && SysMessage.body?.msgContent) { - const groupInvite = new NapProtoMsg(GroupInvite).decode(SysMessage.body.msgContent); - let request_seq = ''; + let operator_uid_parse: string | undefined; + if (groupChange.operatorInfo) { + // 先判断是否可能是protobuf(自身被踢出或以0a开头) + if (groupChange.decreaseType === 3 || Buffer.from(groupChange.operatorInfo).toString('hex').startsWith('0a')) { + // 可能是protobuf,尝试解析 + try { + operator_uid_parse = new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid; + } catch (error) { + // protobuf解析失败,fallback到字符串解析 try { - await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onRecvMsg', (msgs) => { - for (const msg of msgs) { - if (msg.senderUid === groupInvite.invitorUid && msg.msgType === 11) { - const jumpUrl = JSON.parse(msg.elements.find(e => e.elementType === 10)?.arkElement?.bytesData ?? '').meta?.news?.jumpUrl; - const jumpUrlParams = new URLSearchParams(jumpUrl); - const groupcode = jumpUrlParams.get('groupcode'); - const receiveruin = jumpUrlParams.get('receiveruin'); - const msgseq = jumpUrlParams.get('msgseq'); - request_seq = msgseq ?? ''; - if (groupcode === groupInvite.groupUin.toString() && receiveruin === this.core.selfInfo.uin) { - return true; - } - } - } - return false; - }, 1, 1000); - } catch { - request_seq = ''; + const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo); + // 检查是否包含非ASCII字符,如果包含则丢弃 + const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126); + operator_uid_parse = isAsciiOnly ? decoded : ''; + } catch (e2) { + operator_uid_parse = ''; } - // 未拉取到seq - if (request_seq === '') { - return; - } - // 创建个假的 - this.notifyGroupInvite.put(request_seq, { - seq: request_seq, - type: 1, - group: { - groupCode: groupInvite.groupUin.toString(), - groupName: '', - }, - user1: { - uid: groupInvite.invitorUid, - nickName: '', - }, - user2: { - uid: this.core.selfInfo.uid, - nickName: '', - }, - actionUser: { - uid: groupInvite.invitorUid, - nickName: '', - }, - actionTime: Date.now().toString(), - postscript: '', - repeatSeqs: [], - warningTips: '', - invitationExt: { - srcType: 1, - groupCode: groupInvite.groupUin.toString(), - waitStatus: 1, - }, - status: 1 - }); - return new OB11GroupRequestEvent( - this.core, - +groupInvite.groupUin, - +await this.core.apis.UserApi.getUinByUidV2(groupInvite.invitorUid), - 'invite', - '', - request_seq - ); - } else if (SysMessage.contentHead.type == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) { - return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent); + } + } else { + // 直接进行字符串解析 + try { + const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo); + // 检查是否包含非ASCII字符,如果包含则丢弃 + const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126); + operator_uid_parse = isAsciiOnly ? decoded : ''; + } catch (e) { + operator_uid_parse = ''; + } } - // else if (SysMessage.contentHead.type == 732 && SysMessage.contentHead.subType == 16 && SysMessage.body?.msgContent) { - // let data_wrap = PBString(2); - // let user_wrap = PBUint64(5); - // let group_wrap = PBUint64(4); + } - // ProtoBuf(class extends ProtoBufBase { - // group = group_wrap; - // content = ProtoBufIn(5, { data: data_wrap, user: user_wrap }); - // }).decode(SysMessage.body?.msgContent.slice(7)); - // let xml_data = UnWrap(data_wrap); - // let group = UnWrap(group_wrap).toString(); - // //let user = UnWrap(user_wrap).toString(); - // const parsedParts = this.parseTextWithJson(xml_data); - // //解析JSON - // if (parsedParts[1] && parsedParts[3]) { - // let set_user_id: string = (parsedParts[1].content as { data: string }).data; - // let uid = await this.core.apis.UserApi.getUidByUinV2(set_user_id); - // let new_title: string = (parsedParts[3].content as { text: string }).text; - // console.log(this.core.apis.GroupApi.groupMemberCache.get(group)?.get(uid)?.memberSpecialTitle, new_title) - // if (this.core.apis.GroupApi.groupMemberCache.get(group)?.get(uid)?.memberSpecialTitle == new_title) { - // return; - // } - // await this.core.apis.GroupApi.refreshGroupMemberCachePartial(group, uid); - // //let json_data_1_url_search = new URL((parsedParts[3].content as { url: string }).url).searchParams; - // //let is_new: boolean = json_data_1_url_search.get('isnew') === '1'; - - // //console.log(group, set_user_id, is_new, new_title); - // return new GroupMemberTitle( - // this.core, - // +group, - // +set_user_id, - // new_title - // ); - // } - // } - return undefined; + const operatorUid = await this.waitGroupNotify( + groupChange.groupUin.toString(), + groupChange.memberUid, + operator_uid_parse + ); + if (groupChange.memberUid === this.core.selfInfo.uid) { + setTimeout(() => { + this.core.apis.GroupApi.groupMemberCache.delete(groupChange.groupUin.toString()); + }, 5000); + // 自己被踢了 5S后回收 + } else { + await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), true); + } + return new OB11GroupDecreaseEvent( + this.core, + groupChange.groupUin, + groupChange.memberUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.memberUid) : 0, + operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(operatorUid) : 0, + this.groupChangDecreseType2String(groupChange.decreaseType) + ); + } else if (SysMessage.contentHead.type == 44 && SysMessage.body?.msgContent) { + const groupAmin = new NapProtoMsg(GroupAdmin).decode(SysMessage.body.msgContent); + await this.core.apis.GroupApi.refreshGroupMemberCache(groupAmin.groupUin.toString(), true); + let enabled = false; + let uid = ''; + if (groupAmin.body.extraEnable != null) { + uid = groupAmin.body.extraEnable.adminUid; + enabled = true; + } else if (groupAmin.body.extraDisable != null) { + uid = groupAmin.body.extraDisable.adminUid; + enabled = false; + } + return new OB11GroupAdminNoticeEvent( + this.core, + groupAmin.groupUin, + +await this.core.apis.UserApi.getUinByUidV2(uid), + enabled ? 'set' : 'unset' + ); + } else if (SysMessage.contentHead.type == 87 && SysMessage.body?.msgContent) { + const groupInvite = new NapProtoMsg(GroupInvite).decode(SysMessage.body.msgContent); + let request_seq = ''; + try { + await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onRecvMsg', (msgs) => { + for (const msg of msgs) { + if (msg.senderUid === groupInvite.invitorUid && msg.msgType === 11) { + const jumpUrl = JSON.parse(msg.elements.find(e => e.elementType === 10)?.arkElement?.bytesData ?? '').meta?.news?.jumpUrl; + const jumpUrlParams = new URLSearchParams(jumpUrl); + const groupcode = jumpUrlParams.get('groupcode'); + const receiveruin = jumpUrlParams.get('receiveruin'); + const msgseq = jumpUrlParams.get('msgseq'); + request_seq = msgseq ?? ''; + if (groupcode === groupInvite.groupUin.toString() && receiveruin === this.core.selfInfo.uin) { + return true; + } + } + } + return false; + }, 1, 1000); + } catch { + request_seq = ''; + } + // 未拉取到seq + if (request_seq === '') { + return; + } + // 创建个假的 + this.notifyGroupInvite.put(request_seq, { + seq: request_seq, + type: 1, + group: { + groupCode: groupInvite.groupUin.toString(), + groupName: '', + }, + user1: { + uid: groupInvite.invitorUid, + nickName: '', + }, + user2: { + uid: this.core.selfInfo.uid, + nickName: '', + }, + actionUser: { + uid: groupInvite.invitorUid, + nickName: '', + }, + actionTime: Date.now().toString(), + postscript: '', + repeatSeqs: [], + warningTips: '', + invitationExt: { + srcType: 1, + groupCode: groupInvite.groupUin.toString(), + waitStatus: 1, + }, + status: 1, + }); + return new OB11GroupRequestEvent( + this.core, + +groupInvite.groupUin, + +await this.core.apis.UserApi.getUinByUidV2(groupInvite.invitorUid), + 'invite', + '', + request_seq + ); + } else if (SysMessage.contentHead.type == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) { + return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent); } + // else if (SysMessage.contentHead.type == 732 && SysMessage.contentHead.subType == 16 && SysMessage.body?.msgContent) { + // let data_wrap = PBString(2); + // let user_wrap = PBUint64(5); + // let group_wrap = PBUint64(4); + + // ProtoBuf(class extends ProtoBufBase { + // group = group_wrap; + // content = ProtoBufIn(5, { data: data_wrap, user: user_wrap }); + // }).decode(SysMessage.body?.msgContent.slice(7)); + // let xml_data = UnWrap(data_wrap); + // let group = UnWrap(group_wrap).toString(); + // //let user = UnWrap(user_wrap).toString(); + // const parsedParts = this.parseTextWithJson(xml_data); + // //解析JSON + // if (parsedParts[1] && parsedParts[3]) { + // let set_user_id: string = (parsedParts[1].content as { data: string }).data; + // let uid = await this.core.apis.UserApi.getUidByUinV2(set_user_id); + // let new_title: string = (parsedParts[3].content as { text: string }).text; + // console.log(this.core.apis.GroupApi.groupMemberCache.get(group)?.get(uid)?.memberSpecialTitle, new_title) + // if (this.core.apis.GroupApi.groupMemberCache.get(group)?.get(uid)?.memberSpecialTitle == new_title) { + // return; + // } + // await this.core.apis.GroupApi.refreshGroupMemberCachePartial(group, uid); + // //let json_data_1_url_search = new URL((parsedParts[3].content as { url: string }).url).searchParams; + // //let is_new: boolean = json_data_1_url_search.get('isnew') === '1'; + + // //console.log(group, set_user_id, is_new, new_title); + // return new GroupMemberTitle( + // this.core, + // +group, + // +set_user_id, + // new_title + // ); + // } + // } + return undefined; + } } diff --git a/src/onebot/api/quick-action.ts b/src/onebot/api/quick-action.ts index 709c1f20..d5dd83c5 100644 --- a/src/onebot/api/quick-action.ts +++ b/src/onebot/api/quick-action.ts @@ -1,14 +1,14 @@ import type { - NapCatOneBot11Adapter, - OB11Message, - OB11MessageAt, - OB11MessageData, - OB11MessageReply, - QuickAction, - QuickActionEvent, - QuickActionFriendRequest, - QuickActionGroupMessage, - QuickActionGroupRequest, + NapCatOneBot11Adapter, + OB11Message, + OB11MessageAt, + OB11MessageData, + OB11MessageReply, + QuickAction, + QuickActionEvent, + QuickActionFriendRequest, + QuickActionGroupMessage, + QuickActionGroupRequest, } from '@/onebot'; import { NTGroupRequestOperateTypes, type NapCatCore, type Peer } from '@/core'; import type { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest'; @@ -18,96 +18,96 @@ import { ContextMode, createContext, normalize } from '@/onebot/action/msg/SendM import { isNull } from '@/common/helper'; export class OneBotQuickActionApi { - obContext: NapCatOneBot11Adapter; - core: NapCatCore; - constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - this.obContext = obContext; - this.core = core; + obContext: NapCatOneBot11Adapter; + core: NapCatCore; + constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + this.obContext = obContext; + this.core = core; + } + + async handleQuickOperation (eventContext: QuickActionEvent, quickAction: QuickAction) { + if (eventContext.post_type === 'message') { + await this.handleMsg(eventContext as OB11Message, quickAction) + .catch(e => this.core.context.logger.logError(e)); } - - async handleQuickOperation(eventContext: QuickActionEvent, quickAction: QuickAction) { - if (eventContext.post_type === 'message') { - await this.handleMsg(eventContext as OB11Message, quickAction) - .catch(e => this.core.context.logger.logError(e)); - } - if (eventContext.post_type === 'request') { - const friendRequest = eventContext as OB11FriendRequestEvent; - const groupRequest = eventContext as OB11GroupRequestEvent; - if ((friendRequest).request_type === 'friend') { - await this.handleFriendRequest(friendRequest, quickAction) - .catch(e => this.core.context.logger.logError(e)); - } else if (groupRequest.request_type === 'group') { - await this.handleGroupRequest(groupRequest, quickAction) - .catch(e => this.core.context.logger.logError(e)); - } - } + if (eventContext.post_type === 'request') { + const friendRequest = eventContext as OB11FriendRequestEvent; + const groupRequest = eventContext as OB11GroupRequestEvent; + if ((friendRequest).request_type === 'friend') { + await this.handleFriendRequest(friendRequest, quickAction) + .catch(e => this.core.context.logger.logError(e)); + } else if (groupRequest.request_type === 'group') { + await this.handleGroupRequest(groupRequest, quickAction) + .catch(e => this.core.context.logger.logError(e)); + } } + } - async handleMsg(msg: OB11Message, quickAction: QuickAction) { - const reply = quickAction.reply; - const peerContextMode = msg.message_type == 'private' ? ContextMode.Private : ContextMode.Group; - const peer: Peer = await createContext(this.core, { - group_id: msg.group_id?.toString(), - user_id: msg.user_id?.toString(), - }, peerContextMode); + async handleMsg (msg: OB11Message, quickAction: QuickAction) { + const reply = quickAction.reply; + const peerContextMode = msg.message_type == 'private' ? ContextMode.Private : ContextMode.Group; + const peer: Peer = await createContext(this.core, { + group_id: msg.group_id?.toString(), + user_id: msg.user_id?.toString(), + }, peerContextMode); - if (reply) { - // let group: Group | undefined; - let replyMessage: OB11MessageData[] = []; + if (reply) { + // let group: Group | undefined; + let replyMessage: OB11MessageData[] = []; - if (msg.message_type == 'group') { - // group = await core.apis.GroupApi.getGroup(msg.group_id!.toString()); - replyMessage.push({ - type: 'reply', - data: { - id: msg.message_id.toString(), - }, - } as OB11MessageReply); - if ((quickAction as QuickActionGroupMessage).at_sender) { - replyMessage.push({ - type: 'at', - data: { - qq: msg.user_id.toString(), - }, - } as OB11MessageAt); - } - } - replyMessage = replyMessage.concat(normalize(reply, quickAction.auto_escape)); - const { - sendElements, - deleteAfterSentFiles, - } = await this.obContext.apis.MsgApi.createSendElements(replyMessage, peer); - this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles).then().catch(e => this.core.context.logger.logError(e)); + if (msg.message_type == 'group') { + // group = await core.apis.GroupApi.getGroup(msg.group_id!.toString()); + replyMessage.push({ + type: 'reply', + data: { + id: msg.message_id.toString(), + }, + } as OB11MessageReply); + if ((quickAction as QuickActionGroupMessage).at_sender) { + replyMessage.push({ + type: 'at', + data: { + qq: msg.user_id.toString(), + }, + } as OB11MessageAt); } + } + replyMessage = replyMessage.concat(normalize(reply, quickAction.auto_escape)); + const { + sendElements, + deleteAfterSentFiles, + } = await this.obContext.apis.MsgApi.createSendElements(replyMessage, peer); + this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles).then().catch(e => this.core.context.logger.logError(e)); } - async findNotify(flag: string) { - let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag); - if (!notify) { - notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag); - return { doubt: true, notify }; - } - return { doubt: false, notify }; + } + + async findNotify (flag: string) { + let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag); + if (!notify) { + notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag); + return { doubt: true, notify }; } + return { doubt: false, notify }; + } - async handleGroupRequest(request: OB11GroupRequestEvent, quickAction: QuickActionGroupRequest) { + async handleGroupRequest (request: OB11GroupRequestEvent, quickAction: QuickActionGroupRequest) { + const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(request.flag); + const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(request.flag); - const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(request.flag); - const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(request.flag); - - if (!isNull(quickAction.approve) && notify) { - this.core.apis.GroupApi.handleGroupRequest( - doubt, - notify, - quickAction.approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE, - quickAction.reason, - ).catch(e => this.core.context.logger.logError(e)); - } + if (!isNull(quickAction.approve) && notify) { + this.core.apis.GroupApi.handleGroupRequest( + doubt, + notify, + quickAction.approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE, + quickAction.reason + ).catch(e => this.core.context.logger.logError(e)); } + } - async handleFriendRequest(request: OB11FriendRequestEvent, quickAction: QuickActionFriendRequest) { - const notify = (await this.core.apis.FriendApi.getBuddyReq()).buddyReqs.find(e => e.reqTime == request.flag.toString()); - if (!isNull(quickAction.approve) && notify) { - this.core.apis.FriendApi.handleFriendRequest(notify, !!quickAction.approve).then().catch(e => this.core.context.logger.logError(e)); - } + async handleFriendRequest (request: OB11FriendRequestEvent, quickAction: QuickActionFriendRequest) { + const notify = (await this.core.apis.FriendApi.getBuddyReq()).buddyReqs.find(e => e.reqTime == request.flag.toString()); + if (!isNull(quickAction.approve) && notify) { + this.core.apis.FriendApi.handleFriendRequest(notify, !!quickAction.approve).then().catch(e => this.core.context.logger.logError(e)); } + } } diff --git a/src/onebot/api/user.ts b/src/onebot/api/user.ts index 6f4d3f85..b526d444 100644 --- a/src/onebot/api/user.ts +++ b/src/onebot/api/user.ts @@ -4,29 +4,29 @@ import { OB11ProfileLikeEvent } from '@/onebot/event/notice/OB11ProfileLikeEvent import { decodeProfileLikeTip } from '@/core/helper/adaptDecoder'; export class OneBotUserApi { - obContext: NapCatOneBot11Adapter; - core: NapCatCore; + obContext: NapCatOneBot11Adapter; + core: NapCatCore; - constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { - this.obContext = obContext; - this.core = core; - } + constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + this.obContext = obContext; + this.core = core; + } - async parseLikeEvent(wrappedBody: Uint8Array): Promise { - const likeTip = decodeProfileLikeTip(wrappedBody); - if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return; - this.core.context.logger.logDebug('收到点赞通知消息'); - const likeMsg = likeTip.content.msg; - if (!likeMsg) return; - const detail = likeMsg.detail; - if (!detail) return; - const times = detail.txt.match(/\d+/) ?? '0'; - return new OB11ProfileLikeEvent( - this.core, - Number(detail.uin), - detail.nickname, - parseInt(times[0], 10), - likeMsg.time, - ); - } + async parseLikeEvent (wrappedBody: Uint8Array): Promise { + const likeTip = decodeProfileLikeTip(wrappedBody); + if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return; + this.core.context.logger.logDebug('收到点赞通知消息'); + const likeMsg = likeTip.content.msg; + if (!likeMsg) return; + const detail = likeMsg.detail; + if (!detail) return; + const times = detail.txt.match(/\d+/) ?? '0'; + return new OB11ProfileLikeEvent( + this.core, + Number(detail.uin), + detail.nickname, + parseInt(times[0], 10), + likeMsg.time + ); + } } diff --git a/src/onebot/config/config.ts b/src/onebot/config/config.ts index fa4efcb0..b43c2c49 100644 --- a/src/onebot/config/config.ts +++ b/src/onebot/config/config.ts @@ -1,87 +1,87 @@ import { Type, Static } from '@sinclair/typebox'; import Ajv from 'ajv'; const HttpServerConfigSchema = Type.Object({ - name: Type.String({ default: 'http-server' }), - enable: Type.Boolean({ default: false }), - port: Type.Number({ default: 3000 }), - host: Type.String({ default: '127.0.0.1' }), - enableCors: Type.Boolean({ default: true }), - enableWebsocket: Type.Boolean({ default: true }), - messagePostFormat: Type.String({ default: 'array' }), - token: Type.String({ default: '' }), - debug: Type.Boolean({ default: false }) + name: Type.String({ default: 'http-server' }), + enable: Type.Boolean({ default: false }), + port: Type.Number({ default: 3000 }), + host: Type.String({ default: '127.0.0.1' }), + enableCors: Type.Boolean({ default: true }), + enableWebsocket: Type.Boolean({ default: true }), + messagePostFormat: Type.String({ default: 'array' }), + token: Type.String({ default: '' }), + debug: Type.Boolean({ default: false }), }); const HttpSseServerConfigSchema = Type.Object({ - name: Type.String({ default: 'http-sse-server' }), - enable: Type.Boolean({ default: false }), - port: Type.Number({ default: 3000 }), - host: Type.String({ default: '127.0.0.1' }), - enableCors: Type.Boolean({ default: true }), - enableWebsocket: Type.Boolean({ default: true }), - messagePostFormat: Type.String({ default: 'array' }), - token: Type.String({ default: '' }), - debug: Type.Boolean({ default: false }), - reportSelfMessage: Type.Boolean({ default: false }) + name: Type.String({ default: 'http-sse-server' }), + enable: Type.Boolean({ default: false }), + port: Type.Number({ default: 3000 }), + host: Type.String({ default: '127.0.0.1' }), + enableCors: Type.Boolean({ default: true }), + enableWebsocket: Type.Boolean({ default: true }), + messagePostFormat: Type.String({ default: 'array' }), + token: Type.String({ default: '' }), + debug: Type.Boolean({ default: false }), + reportSelfMessage: Type.Boolean({ default: false }), }); const HttpClientConfigSchema = Type.Object({ - name: Type.String({ default: 'http-client' }), - enable: Type.Boolean({ default: false }), - url: Type.String({ default: 'http://localhost:8080' }), - messagePostFormat: Type.String({ default: 'array' }), - reportSelfMessage: Type.Boolean({ default: false }), - token: Type.String({ default: '' }), - debug: Type.Boolean({ default: false }) + name: Type.String({ default: 'http-client' }), + enable: Type.Boolean({ default: false }), + url: Type.String({ default: 'http://localhost:8080' }), + messagePostFormat: Type.String({ default: 'array' }), + reportSelfMessage: Type.Boolean({ default: false }), + token: Type.String({ default: '' }), + debug: Type.Boolean({ default: false }), }); const WebsocketServerConfigSchema = Type.Object({ - name: Type.String({ default: 'websocket-server' }), - enable: Type.Boolean({ default: false }), - host: Type.String({ default: '127.0.0.1' }), - port: Type.Number({ default: 3001 }), - messagePostFormat: Type.String({ default: 'array' }), - reportSelfMessage: Type.Boolean({ default: false }), - token: Type.String({ default: '' }), - enableForcePushEvent: Type.Boolean({ default: true }), - debug: Type.Boolean({ default: false }), - heartInterval: Type.Number({ default: 30000 }) + name: Type.String({ default: 'websocket-server' }), + enable: Type.Boolean({ default: false }), + host: Type.String({ default: '127.0.0.1' }), + port: Type.Number({ default: 3001 }), + messagePostFormat: Type.String({ default: 'array' }), + reportSelfMessage: Type.Boolean({ default: false }), + token: Type.String({ default: '' }), + enableForcePushEvent: Type.Boolean({ default: true }), + debug: Type.Boolean({ default: false }), + heartInterval: Type.Number({ default: 30000 }), }); const WebsocketClientConfigSchema = Type.Object({ - name: Type.String({ default: 'websocket-client' }), - enable: Type.Boolean({ default: false }), - url: Type.String({ default: 'ws://localhost:8082' }), - messagePostFormat: Type.String({ default: 'array' }), - reportSelfMessage: Type.Boolean({ default: false }), - reconnectInterval: Type.Number({ default: 5000 }), - token: Type.String({ default: '' }), - debug: Type.Boolean({ default: false }), - heartInterval: Type.Number({ default: 30000 }) + name: Type.String({ default: 'websocket-client' }), + enable: Type.Boolean({ default: false }), + url: Type.String({ default: 'ws://localhost:8082' }), + messagePostFormat: Type.String({ default: 'array' }), + reportSelfMessage: Type.Boolean({ default: false }), + reconnectInterval: Type.Number({ default: 5000 }), + token: Type.String({ default: '' }), + debug: Type.Boolean({ default: false }), + heartInterval: Type.Number({ default: 30000 }), }); const PluginConfigSchema = Type.Object({ - name: Type.String({ default: 'plugin' }), - enable: Type.Boolean({ default: false }), - messagePostFormat: Type.String({ default: 'array' }), - reportSelfMessage: Type.Boolean({ default: false }), - debug: Type.Boolean({ default: false }), + name: Type.String({ default: 'plugin' }), + enable: Type.Boolean({ default: false }), + messagePostFormat: Type.String({ default: 'array' }), + reportSelfMessage: Type.Boolean({ default: false }), + debug: Type.Boolean({ default: false }), }); const NetworkConfigSchema = Type.Object({ - httpServers: Type.Array(HttpServerConfigSchema, { default: [] }), - httpSseServers: Type.Array(HttpSseServerConfigSchema, { default: [] }), - httpClients: Type.Array(HttpClientConfigSchema, { default: [] }), - websocketServers: Type.Array(WebsocketServerConfigSchema, { default: [] }), - websocketClients: Type.Array(WebsocketClientConfigSchema, { default: [] }), - plugins: Type.Array(PluginConfigSchema, { default: [] }) + httpServers: Type.Array(HttpServerConfigSchema, { default: [] }), + httpSseServers: Type.Array(HttpSseServerConfigSchema, { default: [] }), + httpClients: Type.Array(HttpClientConfigSchema, { default: [] }), + websocketServers: Type.Array(WebsocketServerConfigSchema, { default: [] }), + websocketClients: Type.Array(WebsocketClientConfigSchema, { default: [] }), + plugins: Type.Array(PluginConfigSchema, { default: [] }), }, { default: {} }); export const OneBotConfigSchema = Type.Object({ - network: NetworkConfigSchema, - musicSignUrl: Type.String({ default: '' }), - enableLocalFile2Url: Type.Boolean({ default: false }), - parseMultMsg: Type.Boolean({ default: false }) + network: NetworkConfigSchema, + musicSignUrl: Type.String({ default: '' }), + enableLocalFile2Url: Type.Boolean({ default: false }), + parseMultMsg: Type.Boolean({ default: false }), }); export type OneBotConfig = Static; @@ -95,13 +95,12 @@ export type PluginConfig = Static; export type NetworkAdapterConfig = HttpServerConfig | HttpSseServerConfig | HttpClientConfig | WebsocketServerConfig | WebsocketClientConfig | PluginConfig; export type NetworkConfigKey = keyof OneBotConfig['network']; - -export function loadConfig(config: Partial): OneBotConfig { - const ajv = new Ajv({ useDefaults: true, coerceTypes: true }); - const validate = ajv.compile(OneBotConfigSchema); - const valid = validate(config); - if (!valid) { - throw new Error(ajv.errorsText(validate.errors)); - } - return config as OneBotConfig; -} \ No newline at end of file +export function loadConfig (config: Partial): OneBotConfig { + const ajv = new Ajv({ useDefaults: true, coerceTypes: true }); + const validate = ajv.compile(OneBotConfigSchema); + const valid = validate(config); + if (!valid) { + throw new Error(ajv.errorsText(validate.errors)); + } + return config as OneBotConfig; +} diff --git a/src/onebot/config/index.ts b/src/onebot/config/index.ts index 2c1f8221..2ac5c514 100644 --- a/src/onebot/config/index.ts +++ b/src/onebot/config/index.ts @@ -4,7 +4,7 @@ import { OneBotConfig } from './config'; import { AnySchema } from 'ajv'; export class OB11ConfigLoader extends ConfigBase { - constructor(core: NapCatCore, configPath: string, schema: AnySchema) { - super('onebot11', core, configPath, schema); - } + constructor (core: NapCatCore, configPath: string, schema: AnySchema) { + super('onebot11', core, configPath, schema); + } } diff --git a/src/onebot/event/OneBotEvent.ts b/src/onebot/event/OneBotEvent.ts index 713d38c9..a05918eb 100644 --- a/src/onebot/event/OneBotEvent.ts +++ b/src/onebot/event/OneBotEvent.ts @@ -1,19 +1,19 @@ import { NapCatCore } from '@/core'; export enum EventType { - META = 'meta_event', - REQUEST = 'request', - NOTICE = 'notice', - MESSAGE = 'message', - MESSAGE_SENT = 'message_sent', + META = 'meta_event', + REQUEST = 'request', + NOTICE = 'notice', + MESSAGE = 'message', + MESSAGE_SENT = 'message_sent', } export abstract class OneBotEvent { - time = Math.floor(Date.now() / 1000); - self_id: number; - abstract post_type: EventType; + time = Math.floor(Date.now() / 1000); + self_id: number; + abstract post_type: EventType; - constructor(core: NapCatCore) { - this.self_id = parseInt(core.selfInfo.uin); - } + constructor (core: NapCatCore) { + this.self_id = parseInt(core.selfInfo.uin); + } } diff --git a/src/onebot/event/message/OB11BaseMessageEvent.ts b/src/onebot/event/message/OB11BaseMessageEvent.ts index f2f1d139..139fc2ba 100644 --- a/src/onebot/event/message/OB11BaseMessageEvent.ts +++ b/src/onebot/event/message/OB11BaseMessageEvent.ts @@ -1,5 +1,5 @@ import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent'; export abstract class OB11BaseMessageEvent extends OneBotEvent { - post_type = EventType.MESSAGE; -} \ No newline at end of file + post_type = EventType.MESSAGE; +} diff --git a/src/onebot/event/meta/OB11BaseMetaEvent.ts b/src/onebot/event/meta/OB11BaseMetaEvent.ts index 015a5958..046cfff0 100644 --- a/src/onebot/event/meta/OB11BaseMetaEvent.ts +++ b/src/onebot/event/meta/OB11BaseMetaEvent.ts @@ -1,6 +1,6 @@ import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent'; export abstract class OB11BaseMetaEvent extends OneBotEvent { - post_type = EventType.META; - abstract meta_event_type: string; + post_type = EventType.META; + abstract meta_event_type: string; } diff --git a/src/onebot/event/meta/OB11HeartbeatEvent.ts b/src/onebot/event/meta/OB11HeartbeatEvent.ts index 7171d9cc..ffaa00fd 100644 --- a/src/onebot/event/meta/OB11HeartbeatEvent.ts +++ b/src/onebot/event/meta/OB11HeartbeatEvent.ts @@ -2,21 +2,21 @@ import { OB11BaseMetaEvent } from './OB11BaseMetaEvent'; import { NapCatCore } from '@/core'; interface HeartbeatStatus { - online: boolean | undefined, - good: boolean + online: boolean | undefined, + good: boolean } export class OB11HeartbeatEvent extends OB11BaseMetaEvent { - meta_event_type = 'heartbeat'; - status: HeartbeatStatus; - interval: number; + meta_event_type = 'heartbeat'; + status: HeartbeatStatus; + interval: number; - public constructor(core: NapCatCore, interval: number, isOnline: boolean, isGood: boolean) { - super(core); - this.interval = interval; - this.status = { - online: isOnline, - good: isGood, - }; - } + public constructor (core: NapCatCore, interval: number, isOnline: boolean, isGood: boolean) { + super(core); + this.interval = interval; + this.status = { + online: isOnline, + good: isGood, + }; + } } diff --git a/src/onebot/event/meta/OB11LifeCycleEvent.ts b/src/onebot/event/meta/OB11LifeCycleEvent.ts index ba9ec149..ed15bef2 100644 --- a/src/onebot/event/meta/OB11LifeCycleEvent.ts +++ b/src/onebot/event/meta/OB11LifeCycleEvent.ts @@ -2,17 +2,17 @@ import { OB11BaseMetaEvent } from './OB11BaseMetaEvent'; import { NapCatCore } from '@/core'; export enum LifeCycleSubType { - ENABLE = 'enable', - DISABLE = 'disable', - CONNECT = 'connect' + ENABLE = 'enable', + DISABLE = 'disable', + CONNECT = 'connect', } export class OB11LifeCycleEvent extends OB11BaseMetaEvent { - meta_event_type = 'lifecycle'; - sub_type: LifeCycleSubType; + meta_event_type = 'lifecycle'; + sub_type: LifeCycleSubType; - public constructor(core: NapCatCore, subType: LifeCycleSubType) { - super(core); - this.sub_type = subType; - } + public constructor (core: NapCatCore, subType: LifeCycleSubType) { + super(core); + this.sub_type = subType; + } } diff --git a/src/onebot/event/notice/BotOfflineEvent.ts b/src/onebot/event/notice/BotOfflineEvent.ts index 6718a561..30e2336d 100644 --- a/src/onebot/event/notice/BotOfflineEvent.ts +++ b/src/onebot/event/notice/BotOfflineEvent.ts @@ -2,15 +2,15 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; export class BotOfflineEvent extends OB11BaseNoticeEvent { - notice_type = 'bot_offline'; - user_id: number; - tag: string = 'BotOfflineEvent'; - message: string = 'BotOfflineEvent'; + notice_type = 'bot_offline'; + user_id: number; + tag: string = 'BotOfflineEvent'; + message: string = 'BotOfflineEvent'; - public constructor(core: NapCatCore, tag: string, message: string) { - super(core); - this.user_id = +core.selfInfo.uin; - this.tag = tag; - this.message = message; - } + public constructor (core: NapCatCore, tag: string, message: string) { + super(core); + this.user_id = +core.selfInfo.uin; + this.tag = tag; + this.message = message; + } } diff --git a/src/onebot/event/notice/OB11BaseNoticeEvent.ts b/src/onebot/event/notice/OB11BaseNoticeEvent.ts index ad9240b5..94398f67 100644 --- a/src/onebot/event/notice/OB11BaseNoticeEvent.ts +++ b/src/onebot/event/notice/OB11BaseNoticeEvent.ts @@ -1,6 +1,6 @@ import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent'; export abstract class OB11BaseNoticeEvent extends OneBotEvent { - post_type = EventType.NOTICE; - abstract notice_type: string; + post_type = EventType.NOTICE; + abstract notice_type: string; } diff --git a/src/onebot/event/notice/OB11FriendAddNoticeEvent.ts b/src/onebot/event/notice/OB11FriendAddNoticeEvent.ts index 4ced647a..d9b3565c 100644 --- a/src/onebot/event/notice/OB11FriendAddNoticeEvent.ts +++ b/src/onebot/event/notice/OB11FriendAddNoticeEvent.ts @@ -2,11 +2,11 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent { - notice_type = 'friend_add'; - user_id: number; + notice_type = 'friend_add'; + user_id: number; - public constructor(core: NapCatCore, userId: number) { - super(core); - this.user_id = userId; - } + public constructor (core: NapCatCore, userId: number) { + super(core); + this.user_id = userId; + } } diff --git a/src/onebot/event/notice/OB11FriendRecallNoticeEvent.ts b/src/onebot/event/notice/OB11FriendRecallNoticeEvent.ts index 4fd3d444..7a49c93d 100644 --- a/src/onebot/event/notice/OB11FriendRecallNoticeEvent.ts +++ b/src/onebot/event/notice/OB11FriendRecallNoticeEvent.ts @@ -2,13 +2,13 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11FriendRecallNoticeEvent extends OB11BaseNoticeEvent { - notice_type = 'friend_recall'; - user_id: number; - message_id: number; + notice_type = 'friend_recall'; + user_id: number; + message_id: number; - public constructor(core: NapCatCore, userId: number, messageId: number) { - super(core); - this.user_id = userId; - this.message_id = messageId; - } + public constructor (core: NapCatCore, userId: number, messageId: number) { + super(core); + this.user_id = userId; + this.message_id = messageId; + } } diff --git a/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts b/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts index a5b58c5d..ea723970 100644 --- a/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts +++ b/src/onebot/event/notice/OB11GroupAdminNoticeEvent.ts @@ -2,11 +2,11 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent { - notice_type = 'group_admin'; - sub_type: 'set' | 'unset'; + notice_type = 'group_admin'; + sub_type: 'set' | 'unset'; - constructor(core: NapCatCore, group_id: number, user_id: number, sub_type: 'set' | 'unset') { - super(core, group_id, user_id); - this.sub_type = sub_type; - } + constructor (core: NapCatCore, group_id: number, user_id: number, sub_type: 'set' | 'unset') { + super(core, group_id, user_id); + this.sub_type = sub_type; + } } diff --git a/src/onebot/event/notice/OB11GroupBanEvent.ts b/src/onebot/event/notice/OB11GroupBanEvent.ts index 4f82c6f8..472ac028 100644 --- a/src/onebot/event/notice/OB11GroupBanEvent.ts +++ b/src/onebot/event/notice/OB11GroupBanEvent.ts @@ -2,17 +2,17 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupBanEvent extends OB11GroupNoticeEvent { - notice_type = 'group_ban'; - operator_id: number; - duration: number; - sub_type: 'ban' | 'lift_ban'; + notice_type = 'group_ban'; + operator_id: number; + duration: number; + sub_type: 'ban' | 'lift_ban'; - constructor(core: NapCatCore, groupId: number, userId: number, operatorId: number, duration: number, sub_type: 'ban' | 'lift_ban') { - super(core, groupId, userId); - this.group_id = groupId; - this.operator_id = operatorId; - this.user_id = userId; - this.duration = duration; - this.sub_type = sub_type; - } + constructor (core: NapCatCore, groupId: number, userId: number, operatorId: number, duration: number, sub_type: 'ban' | 'lift_ban') { + super(core, groupId, userId); + this.group_id = groupId; + this.operator_id = operatorId; + this.user_id = userId; + this.duration = duration; + this.sub_type = sub_type; + } } diff --git a/src/onebot/event/notice/OB11GroupCardEvent.ts b/src/onebot/event/notice/OB11GroupCardEvent.ts index c358a294..36d152d1 100644 --- a/src/onebot/event/notice/OB11GroupCardEvent.ts +++ b/src/onebot/event/notice/OB11GroupCardEvent.ts @@ -2,16 +2,15 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupCardEvent extends OB11GroupNoticeEvent { - notice_type = 'group_card'; - card_new: string; - card_old: string; + notice_type = 'group_card'; + card_new: string; + card_old: string; - - constructor(core: NapCatCore, groupId: number, userId: number, cardNew: string, cardOld: string) { - super(core, groupId, userId); - this.group_id = groupId; - this.user_id = userId; - this.card_new = cardNew; - this.card_old = cardOld; - } + constructor (core: NapCatCore, groupId: number, userId: number, cardNew: string, cardOld: string) { + super(core, groupId, userId); + this.group_id = groupId; + this.user_id = userId; + this.card_new = cardNew; + this.card_old = cardOld; + } } diff --git a/src/onebot/event/notice/OB11GroupDecreaseEvent.ts b/src/onebot/event/notice/OB11GroupDecreaseEvent.ts index 37a38b5e..779a906e 100644 --- a/src/onebot/event/notice/OB11GroupDecreaseEvent.ts +++ b/src/onebot/event/notice/OB11GroupDecreaseEvent.ts @@ -4,15 +4,15 @@ import { NapCatCore } from '@/core'; export type GroupDecreaseSubType = 'leave' | 'kick' | 'kick_me' | 'disband'; export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent { - notice_type = 'group_decrease'; - sub_type: GroupDecreaseSubType = 'leave'; - operator_id: number; + notice_type = 'group_decrease'; + sub_type: GroupDecreaseSubType = 'leave'; + operator_id: number; - constructor(core: NapCatCore, groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') { - super(core, groupId, userId); - this.group_id = groupId; - this.operator_id = operatorId; - this.user_id = userId; - this.sub_type = subType; - } + constructor (core: NapCatCore, groupId: number, userId: number, operatorId: number, subType: GroupDecreaseSubType = 'leave') { + super(core, groupId, userId); + this.group_id = groupId; + this.operator_id = operatorId; + this.user_id = userId; + this.sub_type = subType; + } } diff --git a/src/onebot/event/notice/OB11GroupEssenceEvent.ts b/src/onebot/event/notice/OB11GroupEssenceEvent.ts index d6346bd1..a748323c 100644 --- a/src/onebot/event/notice/OB11GroupEssenceEvent.ts +++ b/src/onebot/event/notice/OB11GroupEssenceEvent.ts @@ -2,18 +2,17 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupEssenceEvent extends OB11GroupNoticeEvent { - notice_type = 'essence'; - message_id: number; - sender_id: number; - operator_id: number; - sub_type: 'add' | 'delete' = 'add'; + notice_type = 'essence'; + message_id: number; + sender_id: number; + operator_id: number; + sub_type: 'add' | 'delete' = 'add'; - - constructor(core: NapCatCore, groupId: number, message_id: number, sender_id: number, operator_id: number) { - super(core, groupId, sender_id); - this.group_id = groupId; - this.operator_id = operator_id; - this.message_id = message_id; - this.sender_id = sender_id; - } + constructor (core: NapCatCore, groupId: number, message_id: number, sender_id: number, operator_id: number) { + super(core, groupId, sender_id); + this.group_id = groupId; + this.operator_id = operator_id; + this.message_id = message_id; + this.sender_id = sender_id; + } } diff --git a/src/onebot/event/notice/OB11GroupIncreaseEvent.ts b/src/onebot/event/notice/OB11GroupIncreaseEvent.ts index 84dfe960..1ee01e77 100644 --- a/src/onebot/event/notice/OB11GroupIncreaseEvent.ts +++ b/src/onebot/event/notice/OB11GroupIncreaseEvent.ts @@ -4,15 +4,15 @@ import { NapCatCore } from '@/core'; type GroupIncreaseSubType = 'approve' | 'invite'; export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent { - notice_type = 'group_increase'; - operator_id: number; - sub_type: GroupIncreaseSubType; + notice_type = 'group_increase'; + operator_id: number; + sub_type: GroupIncreaseSubType; - constructor(core: NapCatCore, groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = 'approve') { - super(core, groupId, userId); - this.group_id = groupId; - this.operator_id = operatorId; - this.user_id = userId; - this.sub_type = subType; - } + constructor (core: NapCatCore, groupId: number, userId: number, operatorId: number, subType: GroupIncreaseSubType = 'approve') { + super(core, groupId, userId); + this.group_id = groupId; + this.operator_id = operatorId; + this.user_id = userId; + this.sub_type = subType; + } } diff --git a/src/onebot/event/notice/OB11GroupNameEvent.ts b/src/onebot/event/notice/OB11GroupNameEvent.ts index 18730c00..bd359da2 100644 --- a/src/onebot/event/notice/OB11GroupNameEvent.ts +++ b/src/onebot/event/notice/OB11GroupNameEvent.ts @@ -2,12 +2,12 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupNameEvent extends OB11GroupNoticeEvent { - notice_type = 'notify'; - sub_type = 'group_name'; - name_new: string; + notice_type = 'notify'; + sub_type = 'group_name'; + name_new: string; - constructor(core: NapCatCore, groupId: number, userId: number, nameNew: string) { - super(core, groupId, userId); - this.name_new = nameNew; - } + constructor (core: NapCatCore, groupId: number, userId: number, nameNew: string) { + super(core, groupId, userId); + this.name_new = nameNew; + } } diff --git a/src/onebot/event/notice/OB11GroupNoticeEvent.ts b/src/onebot/event/notice/OB11GroupNoticeEvent.ts index 3be76c56..ab6b9615 100644 --- a/src/onebot/event/notice/OB11GroupNoticeEvent.ts +++ b/src/onebot/event/notice/OB11GroupNoticeEvent.ts @@ -2,12 +2,12 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent { - group_id: number; - user_id: number; + group_id: number; + user_id: number; - constructor(core: NapCatCore, group_id: number, user_id: number) { - super(core); - this.group_id = group_id; - this.user_id = user_id; - } + constructor (core: NapCatCore, group_id: number, user_id: number) { + super(core); + this.group_id = group_id; + this.user_id = user_id; + } } diff --git a/src/onebot/event/notice/OB11GroupRecallNoticeEvent.ts b/src/onebot/event/notice/OB11GroupRecallNoticeEvent.ts index 6cbf454f..47cbdb16 100644 --- a/src/onebot/event/notice/OB11GroupRecallNoticeEvent.ts +++ b/src/onebot/event/notice/OB11GroupRecallNoticeEvent.ts @@ -2,15 +2,15 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent { - notice_type = 'group_recall'; - operator_id: number; - message_id: number; + notice_type = 'group_recall'; + operator_id: number; + message_id: number; - constructor(core: NapCatCore, groupId: number, userId: number, operatorId: number, messageId: number) { - super(core, groupId, userId); - this.group_id = groupId; - this.user_id = userId; - this.operator_id = operatorId; - this.message_id = messageId; - } + constructor (core: NapCatCore, groupId: number, userId: number, operatorId: number, messageId: number) { + super(core, groupId, userId); + this.group_id = groupId; + this.user_id = userId; + this.operator_id = operatorId; + this.message_id = messageId; + } } diff --git a/src/onebot/event/notice/OB11GroupTitleEvent.ts b/src/onebot/event/notice/OB11GroupTitleEvent.ts index 096ed127..03e0f2a4 100644 --- a/src/onebot/event/notice/OB11GroupTitleEvent.ts +++ b/src/onebot/event/notice/OB11GroupTitleEvent.ts @@ -2,14 +2,14 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11GroupTitleEvent extends OB11GroupNoticeEvent { - notice_type = 'notify'; - sub_type = 'title'; - title: string; + notice_type = 'notify'; + sub_type = 'title'; + title: string; - constructor(core: NapCatCore, groupId: number, userId: number, title: string) { - super(core, groupId, userId); - this.group_id = groupId; - this.user_id = userId; - this.title = title; - } + constructor (core: NapCatCore, groupId: number, userId: number, title: string) { + super(core, groupId, userId); + this.group_id = groupId; + this.user_id = userId; + this.title = title; + } } diff --git a/src/onebot/event/notice/OB11GroupUploadNoticeEvent.ts b/src/onebot/event/notice/OB11GroupUploadNoticeEvent.ts index ba433292..68ff4f87 100644 --- a/src/onebot/event/notice/OB11GroupUploadNoticeEvent.ts +++ b/src/onebot/event/notice/OB11GroupUploadNoticeEvent.ts @@ -2,20 +2,20 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export interface GroupUploadFile { - id: string, - name: string, - size: number, - busid: number, + id: string, + name: string, + size: number, + busid: number, } export class OB11GroupUploadNoticeEvent extends OB11GroupNoticeEvent { - notice_type = 'group_upload'; - file: GroupUploadFile; + notice_type = 'group_upload'; + file: GroupUploadFile; - constructor(core: NapCatCore, groupId: number, userId: number, file: GroupUploadFile) { - super(core, groupId, userId); - this.group_id = groupId; - this.user_id = userId; - this.file = file; - } + constructor (core: NapCatCore, groupId: number, userId: number, file: GroupUploadFile) { + super(core, groupId, userId); + this.group_id = groupId; + this.user_id = userId; + this.file = file; + } } diff --git a/src/onebot/event/notice/OB11InputStatusEvent.ts b/src/onebot/event/notice/OB11InputStatusEvent.ts index 4b5777ac..4380fc38 100644 --- a/src/onebot/event/notice/OB11InputStatusEvent.ts +++ b/src/onebot/event/notice/OB11InputStatusEvent.ts @@ -1,20 +1,19 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; -//TODO: 输入状态事件 初步完成 Mlikiowa 需要做一些过滤 +// TODO: 输入状态事件 初步完成 Mlikiowa 需要做一些过滤 export class OB11InputStatusEvent extends OB11BaseNoticeEvent { - notice_type = 'notify'; - sub_type = 'input_status'; - status_text = '对方正在输入...'; - event_type = 1; - user_id = 0; - group_id = 0; + notice_type = 'notify'; + sub_type = 'input_status'; + status_text = '对方正在输入...'; + event_type = 1; + user_id = 0; + group_id = 0; - constructor(core: NapCatCore, user_id: number, eventType: number, status_text: string) { - super(core); - this.user_id = user_id; - this.event_type = eventType; - this.status_text = status_text; - } + constructor (core: NapCatCore, user_id: number, eventType: number, status_text: string) { + super(core); + this.user_id = user_id; + this.event_type = eventType; + this.status_text = status_text; + } } - diff --git a/src/onebot/event/notice/OB11MsgEmojiLikeEvent.ts b/src/onebot/event/notice/OB11MsgEmojiLikeEvent.ts index 6f0800b4..d4d74309 100644 --- a/src/onebot/event/notice/OB11MsgEmojiLikeEvent.ts +++ b/src/onebot/event/notice/OB11MsgEmojiLikeEvent.ts @@ -2,23 +2,23 @@ import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { NapCatCore } from '@/core'; export interface MsgEmojiLike { - emoji_id: string, - count: number + emoji_id: string, + count: number } export class OB11GroupMsgEmojiLikeEvent extends OB11GroupNoticeEvent { - notice_type = 'group_msg_emoji_like'; - message_id: number; - likes: MsgEmojiLike[]; - is_add: boolean; - message_seq?: string; + notice_type = 'group_msg_emoji_like'; + message_id: number; + likes: MsgEmojiLike[]; + is_add: boolean; + message_seq?: string; - constructor(core: NapCatCore, groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[], isAdd: boolean, messageSeq?: string) { - super(core, groupId, userId); - this.group_id = groupId; - this.user_id = userId; // 可为空,表示是对别人的消息操作,如果是对bot自己的消息则不为空 - this.message_id = messageId; - this.likes = likes; - this.is_add = isAdd; - } + constructor (core: NapCatCore, groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[], isAdd: boolean, messageSeq?: string) { + super(core, groupId, userId); + this.group_id = groupId; + this.user_id = userId; // 可为空,表示是对别人的消息操作,如果是对bot自己的消息则不为空 + this.message_id = messageId; + this.likes = likes; + this.is_add = isAdd; + } } diff --git a/src/onebot/event/notice/OB11PokeEvent.ts b/src/onebot/event/notice/OB11PokeEvent.ts index df0a6c86..043f9549 100644 --- a/src/onebot/event/notice/OB11PokeEvent.ts +++ b/src/onebot/event/notice/OB11PokeEvent.ts @@ -2,36 +2,36 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; class OB11PokeEvent extends OB11BaseNoticeEvent { - notice_type = 'notify'; - sub_type = 'poke'; - target_id = 0; - user_id = 0; + notice_type = 'notify'; + sub_type = 'poke'; + target_id = 0; + user_id = 0; } export class OB11FriendPokeEvent extends OB11PokeEvent { - raw_info: unknown; - sender_id: number; + raw_info: unknown; + sender_id: number; - //raw_message nb等框架标准为string - constructor(core: NapCatCore, user_id: number, sender_id: number, target_id: number, raw_message: unknown) { - super(core); - this.target_id = target_id; - this.user_id = user_id; - this.sender_id = sender_id; - this.raw_info = raw_message; - } + // raw_message nb等框架标准为string + constructor (core: NapCatCore, user_id: number, sender_id: number, target_id: number, raw_message: unknown) { + super(core); + this.target_id = target_id; + this.user_id = user_id; + this.sender_id = sender_id; + this.raw_info = raw_message; + } } export class OB11GroupPokeEvent extends OB11PokeEvent { - group_id: number; - raw_info: unknown; + group_id: number; + raw_info: unknown; - //raw_message nb等框架标准为string - constructor(core: NapCatCore, group_id: number, user_id: number, target_id: number, raw_message: unknown) { - super(core); - this.group_id = group_id; - this.target_id = target_id; - this.user_id = user_id; - this.raw_info = raw_message; - } + // raw_message nb等框架标准为string + constructor (core: NapCatCore, group_id: number, user_id: number, target_id: number, raw_message: unknown) { + super(core); + this.group_id = group_id; + this.target_id = target_id; + this.user_id = user_id; + this.raw_info = raw_message; + } } diff --git a/src/onebot/event/notice/OB11ProfileLikeEvent.ts b/src/onebot/event/notice/OB11ProfileLikeEvent.ts index 4409d4e7..d07ae566 100644 --- a/src/onebot/event/notice/OB11ProfileLikeEvent.ts +++ b/src/onebot/event/notice/OB11ProfileLikeEvent.ts @@ -2,18 +2,18 @@ import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'; import { NapCatCore } from '@/core'; export class OB11ProfileLikeEvent extends OB11BaseNoticeEvent { - notice_type = 'notify'; - sub_type = 'profile_like'; - operator_id: number; - operator_nick: string; - times: number; - override time: number; + notice_type = 'notify'; + sub_type = 'profile_like'; + operator_id: number; + operator_nick: string; + times: number; + override time: number; - constructor(core: NapCatCore, operatorId: number, operatorNick: string, times: number, time: number) { - super(core); - this.operator_id = operatorId; - this.operator_nick = operatorNick; - this.times = times; - this.time = time; - } + constructor (core: NapCatCore, operatorId: number, operatorNick: string, times: number, time: number) { + super(core); + this.operator_id = operatorId; + this.operator_nick = operatorNick; + this.times = times; + this.time = time; + } } diff --git a/src/onebot/event/request/OB11BaseRequestEvent.ts b/src/onebot/event/request/OB11BaseRequestEvent.ts index efe69425..491c7819 100644 --- a/src/onebot/event/request/OB11BaseRequestEvent.ts +++ b/src/onebot/event/request/OB11BaseRequestEvent.ts @@ -1,6 +1,6 @@ import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent'; export abstract class OB11BaseRequestEvent extends OneBotEvent { - readonly post_type = EventType.REQUEST; - abstract request_type: string; + readonly post_type = EventType.REQUEST; + abstract request_type: string; } diff --git a/src/onebot/event/request/OB11FriendRequest.ts b/src/onebot/event/request/OB11FriendRequest.ts index 64b3f5b0..f5418af0 100644 --- a/src/onebot/event/request/OB11FriendRequest.ts +++ b/src/onebot/event/request/OB11FriendRequest.ts @@ -2,16 +2,16 @@ import { NapCatCore } from '@/core'; import { OB11BaseRequestEvent } from './OB11BaseRequestEvent'; export class OB11FriendRequestEvent extends OB11BaseRequestEvent { - override request_type = 'friend'; + override request_type = 'friend'; - user_id: number; - comment: string; - flag: string; + user_id: number; + comment: string; + flag: string; - constructor(core: NapCatCore, user_id: number, comment: string, flag: string) { - super(core); - this.user_id = user_id; - this.comment = comment; - this.flag = flag; - } + constructor (core: NapCatCore, user_id: number, comment: string, flag: string) { + super(core); + this.user_id = user_id; + this.comment = comment; + this.flag = flag; + } } diff --git a/src/onebot/event/request/OB11GroupRequest.ts b/src/onebot/event/request/OB11GroupRequest.ts index 05bf62c9..968e1d4d 100644 --- a/src/onebot/event/request/OB11GroupRequest.ts +++ b/src/onebot/event/request/OB11GroupRequest.ts @@ -2,20 +2,20 @@ import { NapCatCore } from '@/core'; import { OB11BaseRequestEvent } from './OB11BaseRequestEvent'; export class OB11GroupRequestEvent extends OB11BaseRequestEvent { - override readonly request_type = 'group' as const; + override readonly request_type = 'group' as const; - group_id: number; - user_id: number; - comment: string; - flag: string; - sub_type: string; + group_id: number; + user_id: number; + comment: string; + flag: string; + sub_type: string; - constructor(core: NapCatCore, groupId: number, userId: number, sub_type: string, comment: string, flag: string) { - super(core); - this.group_id = groupId; - this.user_id = userId; - this.sub_type = sub_type; - this.comment = comment; - this.flag = flag; - } + constructor (core: NapCatCore, groupId: number, userId: number, sub_type: string, comment: string, flag: string) { + super(core); + this.group_id = groupId; + this.user_id = userId; + this.sub_type = sub_type; + this.comment = comment; + this.flag = flag; + } } diff --git a/src/onebot/helper/cqcode.ts b/src/onebot/helper/cqcode.ts index 35aaddae..099032eb 100644 --- a/src/onebot/helper/cqcode.ts +++ b/src/onebot/helper/cqcode.ts @@ -2,86 +2,85 @@ import { OB11MessageData } from '@/onebot/types'; const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)]/; -function unescape(source: string) { - return String(source) - .replace(/[/g, '[') - .replace(/]/g, ']') - .replace(/,/g, ',') - .replace(/&/g, '&'); +function unescape (source: string) { + return String(source) + .replace(/[/g, '[') + .replace(/]/g, ']') + .replace(/,/g, ',') + .replace(/&/g, '&'); } -function from(source: string) { - const capture = pattern.exec(source); - if (!capture) return null; - const [, type, attrs] = capture; - const data: Record = {}; - if (attrs) { - attrs.slice(1).split(',').forEach((str) => { - const index = str.indexOf('='); - data[str.slice(0, index)] = unescape(str.slice(index + 1)); - }); +function from (source: string) { + const capture = pattern.exec(source); + if (!capture) return null; + const [, type, attrs] = capture; + const data: Record = {}; + if (attrs) { + attrs.slice(1).split(',').forEach((str) => { + const index = str.indexOf('='); + data[str.slice(0, index)] = unescape(str.slice(index + 1)); + }); + } + return { type, data, capture }; +} + +function convert (type: string, data: unknown) { + return { + type, + data, + }; +} + +export function decodeCQCode (source: string): OB11MessageData[] { + const elements: Array = []; + let result: ReturnType; + while ((result = from(source))) { + const { type, data, capture } = result; + if (type) { + if (capture.index) { + elements.push(convert('text', { text: unescape(source.slice(0, capture.index)) })); + } + elements.push(convert(type, data)); + source = source.slice(capture.index + capture[0].length); } - return { type, data, capture }; + } + if (source) elements.push(convert('text', { text: unescape(source) })); + return elements as OB11MessageData[]; } -function convert(type: string, data: unknown) { - return { - type, - data, - }; +function CQCodeEscapeText (text: string) { + return text.replace(/&/g, '&') + .replace(/\[/g, '[') + .replace(/]/g, ']'); } -export function decodeCQCode(source: string): OB11MessageData[] { - const elements: Array = []; - let result: ReturnType; - while ((result = from(source))) { - const { type, data, capture } = result; - if (type) { - if (capture.index) { - elements.push(convert('text', { text: unescape(source.slice(0, capture.index)) })); - } - elements.push(convert(type, data)); - source = source.slice(capture.index + capture[0].length); - } +function CQCodeEscape (text: string) { + return text.replace(/&/g, '&') + .replace(/\[/g, '[') + .replace(/]/g, ']') + .replace(/,/g, ','); +} +export function encodeCQCode (data: OB11MessageData) { + if (data.type === 'text') { + return CQCodeEscapeText(data.data.text); + } + + let result = '[CQ:' + data.type; + for (const name in data.data) { + const value = (data.data as Record)[name]; + if (value === undefined) { + continue; } - if (source) elements.push(convert('text', { text: unescape(source) })); - return elements as OB11MessageData[]; -} - -function CQCodeEscapeText(text: string) { - return text.replace(/&/g, '&') - .replace(/\[/g, '[') - .replace(/]/g, ']'); -} - -function CQCodeEscape(text: string) { - return text.replace(/&/g, '&') - .replace(/\[/g, '[') - .replace(/]/g, ']') - .replace(/,/g, ','); -} - -export function encodeCQCode(data: OB11MessageData) { - if (data.type === 'text') { - return CQCodeEscapeText(data.data.text); + try { + const text = value?.toString(); + if (text) { + result += `,${name}=${CQCodeEscape(text)}`; + } + } catch (error) { + console.error(`Error encoding CQCode for ${name}:`, error); } - - let result = '[CQ:' + data.type; - for (const name in data.data) { - const value = (data.data as Record)[name]; - if (value === undefined) { - continue; - } - try { - const text = value?.toString(); - if (text) { - result += `,${name}=${CQCodeEscape(text)}`; - } - } catch (error) { - console.error(`Error encoding CQCode for ${name}:`, error); - } - } - result += ']'; - return result; -} \ No newline at end of file + } + result += ']'; + return result; +} diff --git a/src/onebot/helper/data.ts b/src/onebot/helper/data.ts index d9b0c0fa..d7a40a8f 100644 --- a/src/onebot/helper/data.ts +++ b/src/onebot/helper/data.ts @@ -2,137 +2,139 @@ import { calcQQLevel } from '@/common/helper'; import { FileNapCatOneBotUUID } from '@/common/file-uuid'; import { FriendV2, Group, GroupFileInfoUpdateParamType, GroupMember, SelfInfo, NTSex } from '@/core'; import { - OB11Group, - OB11GroupFile, - OB11GroupFileFolder, - OB11GroupMember, - OB11GroupMemberRole, - OB11User, - OB11UserSex, + OB11Group, + OB11GroupFile, + OB11GroupFileFolder, + OB11GroupMember, + OB11GroupMemberRole, + OB11User, + OB11UserSex, } from '@/onebot/types'; export class OB11Construct { - static selfInfo(selfInfo: SelfInfo): OB11User { - return { - user_id: +selfInfo.uin, - nickname: selfInfo.nick, - }; - } + static selfInfo (selfInfo: SelfInfo): OB11User { + return { + user_id: +selfInfo.uin, + nickname: selfInfo.nick, + }; + } - static friends(friends: FriendV2[]): OB11User[] { - return friends.map(rawFriend => ({ - birthday_year: rawFriend.baseInfo.birthday_year, - birthday_month: rawFriend.baseInfo.birthday_month, - birthday_day: rawFriend.baseInfo.birthday_day, - user_id: parseInt(rawFriend.coreInfo.uin), - age: rawFriend.baseInfo.age, - phone_num: rawFriend.baseInfo.phoneNum, - email: rawFriend.baseInfo.eMail, - category_id: rawFriend.baseInfo.categoryId, - nickname: rawFriend.coreInfo.nick ?? '', - remark: rawFriend.coreInfo.remark ?? rawFriend.coreInfo.nick, - sex: this.sex(rawFriend.baseInfo.sex), - level: rawFriend.qqLevel && calcQQLevel(rawFriend.qqLevel) || 0, - })); - } - static friend(friends: FriendV2): OB11User { - return { - birthday_year: friends.baseInfo.birthday_year, - birthday_month: friends.baseInfo.birthday_month, - birthday_day: friends.baseInfo.birthday_day, - user_id: parseInt(friends.coreInfo.uin), - age: friends.baseInfo.age, - phone_num: friends.baseInfo.phoneNum, - email: friends.baseInfo.eMail, - category_id: friends.baseInfo.categoryId, - nickname: friends.coreInfo.nick ?? '', - remark: friends.coreInfo.remark ?? friends.coreInfo.nick, - sex: this.sex(friends.baseInfo.sex), - level: 0, - }; - } - static groupMemberRole(role: number): OB11GroupMemberRole | undefined { - return { - 4: OB11GroupMemberRole.owner, - 3: OB11GroupMemberRole.admin, - 2: OB11GroupMemberRole.member, - }[role]; - } + static friends (friends: FriendV2[]): OB11User[] { + return friends.map(rawFriend => ({ + birthday_year: rawFriend.baseInfo.birthday_year, + birthday_month: rawFriend.baseInfo.birthday_month, + birthday_day: rawFriend.baseInfo.birthday_day, + user_id: parseInt(rawFriend.coreInfo.uin), + age: rawFriend.baseInfo.age, + phone_num: rawFriend.baseInfo.phoneNum, + email: rawFriend.baseInfo.eMail, + category_id: rawFriend.baseInfo.categoryId, + nickname: rawFriend.coreInfo.nick ?? '', + remark: rawFriend.coreInfo.remark ?? rawFriend.coreInfo.nick, + sex: this.sex(rawFriend.baseInfo.sex), + level: rawFriend.qqLevel && calcQQLevel(rawFriend.qqLevel) || 0, + })); + } - static sex(sex?: NTSex): OB11UserSex { - if (!sex) return OB11UserSex.unknown; - return { - [NTSex.GENDER_MALE]: OB11UserSex.male, - [NTSex.GENDER_FEMALE]: OB11UserSex.female, - [NTSex.GENDER_UNKOWN]: OB11UserSex.unknown, - [NTSex.GENDER_PRIVACY]: OB11UserSex.unknown, - }[sex] || OB11UserSex.unknown; - } + static friend (friends: FriendV2): OB11User { + return { + birthday_year: friends.baseInfo.birthday_year, + birthday_month: friends.baseInfo.birthday_month, + birthday_day: friends.baseInfo.birthday_day, + user_id: parseInt(friends.coreInfo.uin), + age: friends.baseInfo.age, + phone_num: friends.baseInfo.phoneNum, + email: friends.baseInfo.eMail, + category_id: friends.baseInfo.categoryId, + nickname: friends.coreInfo.nick ?? '', + remark: friends.coreInfo.remark ?? friends.coreInfo.nick, + sex: this.sex(friends.baseInfo.sex), + level: 0, + }; + } - static groupMember(group_id: string, member: GroupMember): OB11GroupMember { - return { - group_id: +group_id, - user_id: +member.uin, - nickname: member.nick, - card: member.cardName, - sex: this.sex(member.sex), - age: member.age ?? 0, - area: '', - level: member.memberRealLevel?.toString() ?? '0', - qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0, - join_time: +member.joinTime, - last_sent_time: +member.lastSpeakTime, - title_expire_time: 0, - unfriendly: false, - card_changeable: true, - is_robot: member.isRobot, - shut_up_timestamp: member.shutUpTime, - role: this.groupMemberRole(member.role), - title: member.memberSpecialTitle ?? '', - }; - } + static groupMemberRole (role: number): OB11GroupMemberRole | undefined { + return { + 4: OB11GroupMemberRole.owner, + 3: OB11GroupMemberRole.admin, + 2: OB11GroupMemberRole.member, + }[role]; + } - static group(group: Group): OB11Group { - return { - group_all_shut: (+group.groupShutupExpireTime > 0 )? -1 : 0, - group_remark: group.remarkName, - group_id: +group.groupCode, - group_name: group.groupName, - member_count: group.memberCount, - max_member_count: group.maxMember, - }; - } + static sex (sex?: NTSex): OB11UserSex { + if (!sex) return OB11UserSex.unknown; + return { + [NTSex.GENDER_MALE]: OB11UserSex.male, + [NTSex.GENDER_FEMALE]: OB11UserSex.female, + [NTSex.GENDER_UNKOWN]: OB11UserSex.unknown, + [NTSex.GENDER_PRIVACY]: OB11UserSex.unknown, + }[sex] || OB11UserSex.unknown; + } - static groups(groups: Group[]): OB11Group[] { - return groups.map(this.group); - } + static groupMember (group_id: string, member: GroupMember): OB11GroupMember { + return { + group_id: +group_id, + user_id: +member.uin, + nickname: member.nick, + card: member.cardName, + sex: this.sex(member.sex), + age: member.age ?? 0, + area: '', + level: member.memberRealLevel?.toString() ?? '0', + qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0, + join_time: +member.joinTime, + last_sent_time: +member.lastSpeakTime, + title_expire_time: 0, + unfriendly: false, + card_changeable: true, + is_robot: member.isRobot, + shut_up_timestamp: member.shutUpTime, + role: this.groupMemberRole(member.role), + title: member.memberSpecialTitle ?? '', + }; + } - static file(peerId: string, file: Exclude): OB11GroupFile { - return { - group_id: +peerId, - file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileId ?? ''), - file_name: file.fileName, - busid: file.busId, - size: +file.fileSize, - file_size: +file.fileSize, - upload_time: file.uploadTime, - dead_time: file.deadTime, - modify_time: file.modifyTime, - download_times: file.downloadTimes, - uploader: +file.uploaderUin, - uploader_name: file.uploaderName, - }; - } + static group (group: Group): OB11Group { + return { + group_all_shut: (+group.groupShutupExpireTime > 0) ? -1 : 0, + group_remark: group.remarkName, + group_id: +group.groupCode, + group_name: group.groupName, + member_count: group.memberCount, + max_member_count: group.maxMember, + }; + } - static folder(peerId: string, folder: Exclude): OB11GroupFileFolder { - return { - group_id: +peerId, - folder_id: folder.folderId, - folder: folder.folderId, - folder_name: folder.folderName, - create_time: folder.createTime, - creator: +folder.createUin, - creator_name: folder.creatorName, - total_file_count: folder.totalFileCount, - }; - } -} \ No newline at end of file + static groups (groups: Group[]): OB11Group[] { + return groups.map(this.group); + } + + static file (peerId: string, file: Exclude): OB11GroupFile { + return { + group_id: +peerId, + file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileId ?? ''), + file_name: file.fileName, + busid: file.busId, + size: +file.fileSize, + file_size: +file.fileSize, + upload_time: file.uploadTime, + dead_time: file.deadTime, + modify_time: file.modifyTime, + download_times: file.downloadTimes, + uploader: +file.uploaderUin, + uploader_name: file.uploaderName, + }; + } + + static folder (peerId: string, folder: Exclude): OB11GroupFileFolder { + return { + group_id: +peerId, + folder_id: folder.folderId, + folder: folder.folderId, + folder_name: folder.folderName, + create_time: folder.createTime, + creator: +folder.createUin, + creator_name: folder.creatorName, + total_file_count: folder.totalFileCount, + }; + } +} diff --git a/src/onebot/index.ts b/src/onebot/index.ts index afe53b5b..a5d34e31 100644 --- a/src/onebot/index.ts +++ b/src/onebot/index.ts @@ -1,38 +1,38 @@ import { - BuddyReqType, - ChatType, - GroupNotifyMsgStatus, - GroupNotifyMsgType, - InstanceContext, - NapCatCore, - NodeIKernelBuddyListener, - NodeIKernelGroupListener, - NodeIKernelMsgListener, - Peer, - RawMessage, - SendStatusType, - NTMsgType, - MessageElement, - ElementType, - NTMsgAtType, + BuddyReqType, + ChatType, + GroupNotifyMsgStatus, + GroupNotifyMsgType, + InstanceContext, + NapCatCore, + NodeIKernelBuddyListener, + NodeIKernelGroupListener, + NodeIKernelMsgListener, + Peer, + RawMessage, + SendStatusType, + NTMsgType, + MessageElement, + ElementType, + NTMsgAtType, } from '@/core'; import { OB11ConfigLoader } from '@/onebot/config'; import { pendingTokenToSend } from '@/webui/index'; import { - OB11HttpClientAdapter, - OB11WebSocketClientAdapter, - OB11NetworkManager, - OB11NetworkReloadType, - OB11HttpServerAdapter, - OB11WebSocketServerAdapter, + OB11HttpClientAdapter, + OB11WebSocketClientAdapter, + OB11NetworkManager, + OB11NetworkReloadType, + OB11HttpServerAdapter, + OB11WebSocketServerAdapter, } from '@/onebot/network'; import { NapCatPathWrapper } from '@/common/path'; import { - OneBotFriendApi, - OneBotGroupApi, - OneBotMsgApi, - OneBotQuickActionApi, - OneBotUserApi, + OneBotFriendApi, + OneBotGroupApi, + OneBotMsgApi, + OneBotQuickActionApi, + OneBotUserApi, } from '@/onebot/api'; import { ActionMap, createActionMap } from '@/onebot/action'; import { WebUiDataRuntime } from '@/webui/src/helper/Data'; @@ -45,9 +45,9 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent'; import { BotOfflineEvent } from './event/notice/BotOfflineEvent'; import { - NetworkAdapterConfig, - OneBotConfig, - OneBotConfigSchema, + NetworkAdapterConfig, + OneBotConfig, + OneBotConfigSchema, } from './config/config'; import { OB11Message } from './types'; import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; @@ -55,622 +55,617 @@ import { OB11HttpSSEServerAdapter } from './network/http-server-sse'; import { OB11PluginMangerAdapter } from './network/plugin-manger'; import { existsSync } from 'node:fs'; -//OneBot实现类 +// OneBot实现类 export class NapCatOneBot11Adapter { - readonly core: NapCatCore; - readonly context: InstanceContext; + readonly core: NapCatCore; + readonly context: InstanceContext; - configLoader: OB11ConfigLoader; - public readonly apis; - networkManager: OB11NetworkManager; - actions: ActionMap; - private readonly bootTime = Date.now() / 1000; - recallEventCache = new Map(); - constructor(core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { - this.core = core; - this.context = context; - this.configLoader = new OB11ConfigLoader(core, pathWrapper.configPath, OneBotConfigSchema); - this.apis = { - GroupApi: new OneBotGroupApi(this, core), - UserApi: new OneBotUserApi(this, core), - FriendApi: new OneBotFriendApi(this, core), - MsgApi: new OneBotMsgApi(this, core), - QuickActionApi: new OneBotQuickActionApi(this, core) - } as const; - this.actions = createActionMap(this, core); - this.networkManager = new OB11NetworkManager(); + configLoader: OB11ConfigLoader; + public readonly apis; + networkManager: OB11NetworkManager; + actions: ActionMap; + private readonly bootTime = Date.now() / 1000; + recallEventCache = new Map(); + constructor (core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { + this.core = core; + this.context = context; + this.configLoader = new OB11ConfigLoader(core, pathWrapper.configPath, OneBotConfigSchema); + this.apis = { + GroupApi: new OneBotGroupApi(this, core), + UserApi: new OneBotUserApi(this, core), + FriendApi: new OneBotFriendApi(this, core), + MsgApi: new OneBotMsgApi(this, core), + QuickActionApi: new OneBotQuickActionApi(this, core), + } as const; + this.actions = createActionMap(this, core); + this.networkManager = new OB11NetworkManager(); + } + + async creatOneBotLog (ob11Config: OneBotConfig) { + let log = '[network] 配置加载\n'; + for (const key of ob11Config.network.httpServers) { + log += `HTTP服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; } - async creatOneBotLog(ob11Config: OneBotConfig) { - let log = '[network] 配置加载\n'; - for (const key of ob11Config.network.httpServers) { - log += `HTTP服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; - } - for (const key of ob11Config.network.httpSseServers) { - log += `HTTP-SSE服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; - } - for (const key of ob11Config.network.httpClients) { - log += `HTTP上报服务: ${key.url}, : ${key.enable ? '已启动' : '未启动'}\n`; - } - for (const key of ob11Config.network.websocketServers) { - log += `WebSocket服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; - } - for (const key of ob11Config.network.websocketClients) { - log += `WebSocket反向服务: ${key.url}, : ${key.enable ? '已启动' : '未启动'}\n`; - } - return log; + for (const key of ob11Config.network.httpSseServers) { + log += `HTTP-SSE服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; } - async InitOneBot() { - const selfInfo = this.core.selfInfo; - const ob11Config = this.configLoader.configData; - this.core.apis.UserApi.getUserDetailInfo(selfInfo.uid, false) - .then(async (user) => { - selfInfo.nick = user.nick; - this.context.logger.setLogSelfInfo(selfInfo); + for (const key of ob11Config.network.httpClients) { + log += `HTTP上报服务: ${key.url}, : ${key.enable ? '已启动' : '未启动'}\n`; + } + for (const key of ob11Config.network.websocketServers) { + log += `WebSocket服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; + } + for (const key of ob11Config.network.websocketClients) { + log += `WebSocket反向服务: ${key.url}, : ${key.enable ? '已启动' : '未启动'}\n`; + } + return log; + } - // 检查是否有待发送的token - if (pendingTokenToSend) { - this.context.logger.log('[NapCat] [OneBot] 🔐 检测到待发送的WebUI Token,开始发送'); - try { - await this.core.apis.MsgApi.sendMsg( - { chatType: ChatType.KCHATTYPEC2C, peerUid: selfInfo.uid, guildId: '' }, - [{ - elementType: ElementType.TEXT, - elementId: '', - textElement: { - content: - '[NapCat] 温馨提示:\n' + - 'WebUI密码为默认密码,已进行强制修改\n' + - '新密码: ' + pendingTokenToSend, - atType: NTMsgAtType.ATTYPEUNKNOWN, - atUid: '', - atTinyId: '', - atNtUid: '', - } - }], - 5000 - ); - this.context.logger.log('[NapCat] [OneBot] ✅ WebUI Token 消息发送成功'); - } catch (error) { - this.context.logger.logError('[NapCat] [OneBot] ❌ WebUI Token 消息发送失败:', error); - } - } + async InitOneBot () { + const selfInfo = this.core.selfInfo; + const ob11Config = this.configLoader.configData; + this.core.apis.UserApi.getUserDetailInfo(selfInfo.uid, false) + .then(async (user) => { + selfInfo.nick = user.nick; + this.context.logger.setLogSelfInfo(selfInfo); - WebUiDataRuntime.getQQLoginCallback()(true); - }) - .catch(e => this.context.logger.logError(e)); - - const serviceInfo = await this.creatOneBotLog(ob11Config); - this.context.logger.log(`[Notice] [OneBot11] ${serviceInfo}`); - - //创建NetWork服务 - - // 注册Plugin 如果需要基于NapCat进行快速开发 - // this.networkManager.registerAdapter( - // new OB11PluginAdapter('myPlugin', this.core, this,this.actions) - // ); - if (existsSync(this.context.pathWrapper.pluginPath)) { - this.context.logger.log('[Plugins] 插件目录存在,开始加载插件'); - this.networkManager.registerAdapter( - new OB11PluginMangerAdapter('plugin_manager', this.core, this, this.actions) + // 检查是否有待发送的token + if (pendingTokenToSend) { + this.context.logger.log('[NapCat] [OneBot] 🔐 检测到待发送的WebUI Token,开始发送'); + try { + await this.core.apis.MsgApi.sendMsg( + { chatType: ChatType.KCHATTYPEC2C, peerUid: selfInfo.uid, guildId: '' }, + [{ + elementType: ElementType.TEXT, + elementId: '', + textElement: { + content: + '[NapCat] 温馨提示:\n' + + 'WebUI密码为默认密码,已进行强制修改\n' + + '新密码: ' + pendingTokenToSend, + atType: NTMsgAtType.ATTYPEUNKNOWN, + atUid: '', + atTinyId: '', + atNtUid: '', + }, + }], + 5000 ); + this.context.logger.log('[NapCat] [OneBot] ✅ WebUI Token 消息发送成功'); + } catch (error) { + this.context.logger.logError('[NapCat] [OneBot] ❌ WebUI Token 消息发送失败:', error); + } } - for (const key of ob11Config.network.httpServers) { - if (key.enable) { - this.networkManager.registerAdapter( - new OB11HttpServerAdapter(key.name, key, this.core, this, this.actions) - ); - } - } - for (const key of ob11Config.network.httpSseServers) { - if (key.enable) { - this.networkManager.registerAdapter( - new OB11HttpSSEServerAdapter(key.name, key, this.core, this, this.actions) - ); - } - } - for (const key of ob11Config.network.httpClients) { - if (key.enable) { - this.networkManager.registerAdapter( - new OB11HttpClientAdapter(key.name, key, this.core, this, this.actions) - ); - } - } - for (const key of ob11Config.network.websocketServers) { - if (key.enable) { - this.networkManager.registerAdapter( - new OB11WebSocketServerAdapter( - key.name, - key, - this.core, - this, - this.actions - ) - ); - } - } - for (const key of ob11Config.network.websocketClients) { - if (key.enable) { - this.networkManager.registerAdapter( - new OB11WebSocketClientAdapter( - key.name, - key, - this.core, - this, - this.actions - ) - ); - } - } - await this.networkManager.openAllAdapters(); - this.initMsgListener(); - this.initBuddyListener(); - this.initGroupListener(); + WebUiDataRuntime.getQQLoginCallback()(true); + }) + .catch(e => this.context.logger.logError(e)); - WebUiDataRuntime.setQQVersion(this.core.context.basicInfoWrapper.getFullQQVersion()); - WebUiDataRuntime.setQQLoginInfo(selfInfo); - WebUiDataRuntime.setQQLoginStatus(true); - WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => { - const prev = this.configLoader.configData; - this.configLoader.save(newConfig); - //this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`); - await this.reloadNetwork(prev, newConfig); - }); - this.apis.GroupApi.registerParseGroupReactEvent().catch(e => - this.context.logger.logError('注册群消息反应表情失败', e) + const serviceInfo = await this.creatOneBotLog(ob11Config); + this.context.logger.log(`[Notice] [OneBot11] ${serviceInfo}`); + + // 创建NetWork服务 + + // 注册Plugin 如果需要基于NapCat进行快速开发 + // this.networkManager.registerAdapter( + // new OB11PluginAdapter('myPlugin', this.core, this,this.actions) + // ); + if (existsSync(this.context.pathWrapper.pluginPath)) { + this.context.logger.log('[Plugins] 插件目录存在,开始加载插件'); + this.networkManager.registerAdapter( + new OB11PluginMangerAdapter('plugin_manager', this.core, this, this.actions) + ); + } + for (const key of ob11Config.network.httpServers) { + if (key.enable) { + this.networkManager.registerAdapter( + new OB11HttpServerAdapter(key.name, key, this.core, this, this.actions) ); + } } - - - private async reloadNetwork(prev: OneBotConfig, now: OneBotConfig): Promise { - const prevLog = await this.creatOneBotLog(prev); - const newLog = await this.creatOneBotLog(now); - this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`); - this.context.logger.log(`[Notice] [OneBot11] 配置变更后:\n${newLog}`); - - await this.handleConfigChange(prev.network.httpServers, now.network.httpServers, OB11HttpServerAdapter); - await this.handleConfigChange(prev.network.httpClients, now.network.httpClients, OB11HttpClientAdapter); - await this.handleConfigChange(prev.network.httpSseServers, now.network.httpSseServers, OB11HttpSSEServerAdapter); - await this.handleConfigChange(prev.network.websocketServers, now.network.websocketServers, OB11WebSocketServerAdapter); - await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter); + for (const key of ob11Config.network.httpSseServers) { + if (key.enable) { + this.networkManager.registerAdapter( + new OB11HttpSSEServerAdapter(key.name, key, this.core, this, this.actions) + ); + } } + for (const key of ob11Config.network.httpClients) { + if (key.enable) { + this.networkManager.registerAdapter( + new OB11HttpClientAdapter(key.name, key, this.core, this, this.actions) + ); + } + } + for (const key of ob11Config.network.websocketServers) { + if (key.enable) { + this.networkManager.registerAdapter( + new OB11WebSocketServerAdapter( + key.name, + key, + this.core, + this, + this.actions + ) + ); + } + } + for (const key of ob11Config.network.websocketClients) { + if (key.enable) { + this.networkManager.registerAdapter( + new OB11WebSocketClientAdapter( + key.name, + key, + this.core, + this, + this.actions + ) + ); + } + } + await this.networkManager.openAllAdapters(); - private async handleConfigChange( - prevConfig: NetworkAdapterConfig[], - nowConfig: NetworkAdapterConfig[], - adapterClass: new ( - ...args: ConstructorParameters> - ) => IOB11NetworkAdapter - ): Promise { - // 比较旧的在新的找不到的回收 - for (const adapterConfig of prevConfig) { - const existingAdapter = nowConfig.find((e) => e.name === adapterConfig.name); - if (!existingAdapter) { - const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name); - if (existingAdapter) { - await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]); - } - } - } - // 通知新配置重载 删除关闭的 加入新开的 - for (const adapterConfig of nowConfig) { - const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name); - if (existingAdapter) { - const networkChange = await existingAdapter.reload(adapterConfig); - if (networkChange === OB11NetworkReloadType.NetWorkClose) { - await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]); - } - } else if (adapterConfig.enable) { - const newAdapter = new adapterClass(adapterConfig.name, adapterConfig as CT, this.core, this, this.actions); - await this.networkManager.registerAdapterAndOpen(newAdapter); - } + this.initMsgListener(); + this.initBuddyListener(); + this.initGroupListener(); + + WebUiDataRuntime.setQQVersion(this.core.context.basicInfoWrapper.getFullQQVersion()); + WebUiDataRuntime.setQQLoginInfo(selfInfo); + WebUiDataRuntime.setQQLoginStatus(true); + WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => { + const prev = this.configLoader.configData; + this.configLoader.save(newConfig); + // this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`); + await this.reloadNetwork(prev, newConfig); + }); + this.apis.GroupApi.registerParseGroupReactEvent().catch(e => + this.context.logger.logError('注册群消息反应表情失败', e) + ); + } + + private async reloadNetwork (prev: OneBotConfig, now: OneBotConfig): Promise { + const prevLog = await this.creatOneBotLog(prev); + const newLog = await this.creatOneBotLog(now); + this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`); + this.context.logger.log(`[Notice] [OneBot11] 配置变更后:\n${newLog}`); + + await this.handleConfigChange(prev.network.httpServers, now.network.httpServers, OB11HttpServerAdapter); + await this.handleConfigChange(prev.network.httpClients, now.network.httpClients, OB11HttpClientAdapter); + await this.handleConfigChange(prev.network.httpSseServers, now.network.httpSseServers, OB11HttpSSEServerAdapter); + await this.handleConfigChange(prev.network.websocketServers, now.network.websocketServers, OB11WebSocketServerAdapter); + await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter); + } + + private async handleConfigChange ( + prevConfig: NetworkAdapterConfig[], + nowConfig: NetworkAdapterConfig[], + adapterClass: new ( + ...args: ConstructorParameters> + ) => IOB11NetworkAdapter + ): Promise { + // 比较旧的在新的找不到的回收 + for (const adapterConfig of prevConfig) { + const existingAdapter = nowConfig.find((e) => e.name === adapterConfig.name); + if (!existingAdapter) { + const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name); + if (existingAdapter) { + await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]); } + } } + // 通知新配置重载 删除关闭的 加入新开的 + for (const adapterConfig of nowConfig) { + const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name); + if (existingAdapter) { + const networkChange = await existingAdapter.reload(adapterConfig); + if (networkChange === OB11NetworkReloadType.NetWorkClose) { + await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]); + } + } else if (adapterConfig.enable) { + const newAdapter = new adapterClass(adapterConfig.name, adapterConfig as CT, this.core, this, this.actions); + await this.networkManager.registerAdapterAndOpen(newAdapter); + } + } + } - private initMsgListener() { - const msgListener = new NodeIKernelMsgListener(); - msgListener.onRecvSysMsg = (msg) => { - this.apis.MsgApi.parseSysMessage(msg) - .then((event) => { - if (event) this.networkManager.emitEvent(event); - }) - .catch((e) => - this.context.logger.logError( - 'constructSysMessage error: ', - e, - '\n Parse Hex:', - Buffer.from(msg).toString('hex') - ) - ); - }; + private initMsgListener () { + const msgListener = new NodeIKernelMsgListener(); + msgListener.onRecvSysMsg = (msg) => { + this.apis.MsgApi.parseSysMessage(msg) + .then((event) => { + if (event) this.networkManager.emitEvent(event); + }) + .catch((e) => + this.context.logger.logError( + 'constructSysMessage error: ', + e, + '\n Parse Hex:', + Buffer.from(msg).toString('hex') + ) + ); + }; - msgListener.onInputStatusPush = async (data) => { - const uin = await this.core.apis.UserApi.getUinByUidV2(data.fromUin); - this.context.logger.log(`[Notice] [输入状态] ${uin} ${data.statusText}`); - await this.networkManager.emitEvent( - new OB11InputStatusEvent(this.core, parseInt(uin), data.eventType, data.statusText) + msgListener.onInputStatusPush = async (data) => { + const uin = await this.core.apis.UserApi.getUinByUidV2(data.fromUin); + this.context.logger.log(`[Notice] [输入状态] ${uin} ${data.statusText}`); + await this.networkManager.emitEvent( + new OB11InputStatusEvent(this.core, parseInt(uin), data.eventType, data.statusText) + ); + }; + + msgListener.onRecvMsg = async (msg) => { + for (const m of msg) { + if (this.bootTime > parseInt(m.msgTime)) { + this.context.logger.logDebug(`消息时间${m.msgTime}早于启动时间${this.bootTime},忽略上报`); + continue; + } + m.id = MessageUnique.createUniqueMsgId( + { + chatType: m.chatType, + peerUid: m.peerUid, + guildId: '', + }, + m.msgId + ); + await this.emitMsg(m).catch((e) => + this.context.logger.logError('处理消息失败', e) + ); + } + }; + msgListener.onAddSendMsg = async (msg) => { + try { + if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) { + const [updatemsgs] = await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgInfoListUpdate', (msgList: RawMessage[]) => { + const report = msgList.find((e) => + e.senderUin == this.core.selfInfo.uin && e.sendStatus !== SendStatusType.KSEND_STATUS_SENDING && e.msgId === msg.msgId ); - }; - - msgListener.onRecvMsg = async (msg) => { - for (const m of msg) { - if (this.bootTime > parseInt(m.msgTime)) { - this.context.logger.logDebug(`消息时间${m.msgTime}早于启动时间${this.bootTime},忽略上报`); - continue; - } - m.id = MessageUnique.createUniqueMsgId( - { - chatType: m.chatType, - peerUid: m.peerUid, - guildId: '', - }, - m.msgId - ); - await this.emitMsg(m).catch((e) => - this.context.logger.logError('处理消息失败', e) - ); + return !!report; + }, 1, 10 * 60 * 1000); + // 10分钟 超时 + const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId); + // updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ NOSEQ一般是服务器未下发SEQ 这意味着这条消息不应该推送network + if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS) { + updatemsg.id = MessageUnique.createUniqueMsgId( + { + chatType: updatemsg.chatType, + peerUid: updatemsg.peerUid, + guildId: '', + }, + updatemsg.msgId + ); + this.emitMsg(updatemsg); + } + } + } catch (error) { + this.context.logger.logError('处理发送消息失败', error); + } + }; + msgListener.onMsgRecall = async (chatType: ChatType, uid: string, msgSeq: string) => { + const peer: Peer = { + chatType, + peerUid: uid, + guildId: '', + }; + let msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS); + const element = msg?.elements.find(e => !!e.grayTipElement?.revokeElement); + if (msg && element?.grayTipElement?.revokeElement.isSelfOperate) { + const isSelfDevice = this.recallEventCache.has(msg.msgId); + if (isSelfDevice) { + await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgRecall', + (chatType: ChatType, uid: string, msgSeq: string) => { + return chatType === msg?.chatType && uid === msg?.peerUid && msgSeq === msg?.msgSeq; } - }; - msgListener.onAddSendMsg = async (msg) => { + ).catch(() => { + msg = undefined; + this.context.logger.logDebug('自操作消息撤回事件'); + }); + } + } + if (msg && element) { + const recallEvent = await this.emitRecallMsg(msg, element); + try { + if (recallEvent) { + await this.networkManager.emitEvent(recallEvent); + } + } catch (e) { + this.context.logger.logError('处理消息撤回失败', e); + } + } + }; + msgListener.onKickedOffLine = async (kick) => { + const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc); + this.networkManager + .emitEvent(event) + .catch((e) => this.context.logger.logError('处理Bot掉线失败', e)); + }; + this.context.session.getMsgService().addKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)); + } + + private initBuddyListener () { + const buddyListener = new NodeIKernelBuddyListener(); + + buddyListener.onBuddyReqChange = async (reqs) => { + this.core.apis.FriendApi.clearBuddyReqUnreadCnt(); + for (let i = 0; i < reqs.unreadNums; i++) { + const req = reqs.buddyReqs[i]; + if (!req) continue; + if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM) || !req.isUnread) { + continue; + } + try { + const requesterUin = await this.core.apis.UserApi.getUinByUidV2(req.friendUid); + await this.networkManager.emitEvent( + new OB11FriendRequestEvent( + this.core, + +requesterUin, + req.extWords, + req.reqTime + ) + ); + } catch (e) { + this.context.logger.logDebug('获取加好友者QQ号失败', e); + } + } + }; + this.context.session + .getBuddyService() + .addKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger)); + } + + private initGroupListener () { + const groupListener = new NodeIKernelGroupListener(); + + groupListener.onGroupNotifiesUpdated = async (_, notifies) => { + await this.core.apis.GroupApi.clearGroupNotifiesUnreadCount(false); + if (!notifies[0]?.type) return; + if ( + ![ + GroupNotifyMsgType.SET_ADMIN, + GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_CANCELED, + GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_ADMIN, + ].includes(notifies[0]?.type) + ) { + for (const notify of notifies) { + const notifyTime = parseInt(notify.seq) / 1000 / 1000; + // log(`群通知时间${notifyTime}`, `启动时间${this.bootTime}`); + if (notifyTime < this.bootTime) { + continue; + } + const flag = notify.seq; + this.context.logger.logDebug('收到群通知', notify); + if ( + [GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) && + notify.status === GroupNotifyMsgStatus.KUNHANDLE + ) { + this.context.logger.logDebug('有加群请求'); try { - if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) { - const [updatemsgs] = await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgInfoListUpdate', (msgList: RawMessage[]) => { - const report = msgList.find((e) => - e.senderUin == this.core.selfInfo.uin && e.sendStatus !== SendStatusType.KSEND_STATUS_SENDING && e.msgId === msg.msgId - ); - return !!report; - }, 1, 10 * 60 * 1000); - // 10分钟 超时 - const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId); - // updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ NOSEQ一般是服务器未下发SEQ 这意味着这条消息不应该推送network - if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS) { - updatemsg.id = MessageUnique.createUniqueMsgId( - { - chatType: updatemsg.chatType, - peerUid: updatemsg.peerUid, - guildId: '', - }, - updatemsg.msgId - ); - this.emitMsg(updatemsg); - } - } - } catch (error) { - this.context.logger.logError('处理发送消息失败', error); + const requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid); + const groupRequestEvent = new OB11GroupRequestEvent( + this.core, + parseInt(notify.group.groupCode), + parseInt(requestUin), + 'add', + notify.postscript, + flag + ); + this.networkManager + .emitEvent(groupRequestEvent) + .catch((e) => + this.context.logger.logError('处理加群请求失败', e) + ); + } catch (e) { + this.context.logger.logError( + '获取加群人QQ号失败 Uid:', + notify.user1.uid, + e + ); } - }; - msgListener.onMsgRecall = async (chatType: ChatType, uid: string, msgSeq: string) => { - const peer: Peer = { - chatType: chatType, - peerUid: uid, - guildId: '' - }; - let msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS); - const element = msg?.elements.find(e => !!e.grayTipElement?.revokeElement); - if (msg && element?.grayTipElement?.revokeElement.isSelfOperate) { - const isSelfDevice = this.recallEventCache.has(msg.msgId); - if (isSelfDevice) { - await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgRecall', - (chatType: ChatType, uid: string, msgSeq: string) => { - return chatType === msg?.chatType && uid === msg?.peerUid && msgSeq === msg?.msgSeq; - } - ).catch(() => { - msg = undefined; - this.context.logger.logDebug('自操作消息撤回事件'); - }); - } - } - if (msg && element) { - const recallEvent = await this.emitRecallMsg(msg, element); - try { - if (recallEvent) { - await this.networkManager.emitEvent(recallEvent); - } - } catch (e) { - this.context.logger.logError('处理消息撤回失败', e); - } - } - - }; - msgListener.onKickedOffLine = async (kick) => { - const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc); + } else if ( + notify.type === GroupNotifyMsgType.INVITED_BY_MEMBER && + notify.status === GroupNotifyMsgStatus.KUNHANDLE + ) { + this.context.logger.logDebug(`收到邀请我加群通知:${notify}`); + const groupInviteEvent = new OB11GroupRequestEvent( + this.core, + +notify.group.groupCode, + +await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid), + 'invite', + notify.postscript, + flag + ); this.networkManager - .emitEvent(event) - .catch((e) => this.context.logger.logError('处理Bot掉线失败', e)); - }; - this.context.session.getMsgService().addKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)); - } - - private initBuddyListener() { - const buddyListener = new NodeIKernelBuddyListener(); - - buddyListener.onBuddyReqChange = async (reqs) => { - this.core.apis.FriendApi.clearBuddyReqUnreadCnt(); - for (let i = 0; i < reqs.unreadNums; i++) { - const req = reqs.buddyReqs[i]; - if (!req) continue; - if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM) || !req.isUnread) { - continue; - } - try { - const requesterUin = await this.core.apis.UserApi.getUinByUidV2(req.friendUid); - await this.networkManager.emitEvent( - new OB11FriendRequestEvent( - this.core, - +requesterUin, - req.extWords, - req.reqTime - ) - ); - } catch (e) { - this.context.logger.logDebug('获取加好友者QQ号失败', e); - } - } - }; - this.context.session - .getBuddyService() - .addKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger)); - } - - private initGroupListener() { - const groupListener = new NodeIKernelGroupListener(); - - groupListener.onGroupNotifiesUpdated = async (_, notifies) => { - await this.core.apis.GroupApi.clearGroupNotifiesUnreadCount(false); - if (!notifies[0]?.type) return; - if ( - ![ - GroupNotifyMsgType.SET_ADMIN, - GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_CANCELED, - GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_ADMIN, - ].includes(notifies[0]?.type) - ) { - for (const notify of notifies) { - const notifyTime = parseInt(notify.seq) / 1000 / 1000; - // log(`群通知时间${notifyTime}`, `启动时间${this.bootTime}`); - if (notifyTime < this.bootTime) { - continue; - } - const flag = notify.seq; - this.context.logger.logDebug('收到群通知', notify); - if ( - [GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) && - notify.status == GroupNotifyMsgStatus.KUNHANDLE - ) { - this.context.logger.logDebug('有加群请求'); - try { - const requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid); - const groupRequestEvent = new OB11GroupRequestEvent( - this.core, - parseInt(notify.group.groupCode), - parseInt(requestUin), - 'add', - notify.postscript, - flag - ); - this.networkManager - .emitEvent(groupRequestEvent) - .catch((e) => - this.context.logger.logError('处理加群请求失败', e) - ); - } catch (e) { - this.context.logger.logError( - '获取加群人QQ号失败 Uid:', - notify.user1.uid, - e - ); - } - } else if ( - notify.type == GroupNotifyMsgType.INVITED_BY_MEMBER && - notify.status == GroupNotifyMsgStatus.KUNHANDLE - ) { - this.context.logger.logDebug(`收到邀请我加群通知:${notify}`); - const groupInviteEvent = new OB11GroupRequestEvent( - this.core, - +notify.group.groupCode, - +await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid), - 'invite', - notify.postscript, - flag - ); - this.networkManager - .emitEvent(groupInviteEvent) - .catch((e) => - this.context.logger.logError('处理邀请本人加群失败', e) - ); - } else if ( - notify.type == GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS && - notify.status == GroupNotifyMsgStatus.KUNHANDLE - ) { - this.context.logger.logDebug(`收到群员邀请加群通知:${notify}`); - const groupInviteEvent = new OB11GroupRequestEvent( - this.core, - +notify.group.groupCode, - +await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid), - 'add', - notify.postscript, - flag - ); - this.networkManager - .emitEvent(groupInviteEvent) - .catch((e) => - this.context.logger.logError('处理邀请本人加群失败', e) - ); - } - } - } - }; - this.context.session - .getGroupService() - .addKernelGroupListener(proxiedListenerOf(groupListener, this.context.logger)); - } - - private async emitMsg(message: RawMessage) { - const network = await this.networkManager.getAllConfig(); - this.context.logger.logDebug('收到新消息 RawMessage', message); - await Promise.allSettled([ - this.handleMsg(message, network), - message.chatType == ChatType.KCHATTYPEGROUP ? this.handleGroupEvent(message) : this.handlePrivateMsgEvent(message) - ]); - } - - private async handleMsg(message: RawMessage, network: Array) { - // 过滤无效消息 - if (message.msgType === NTMsgType.KMSGTYPENULL) { - return; + .emitEvent(groupInviteEvent) + .catch((e) => + this.context.logger.logError('处理邀请本人加群失败', e) + ); + } else if ( + notify.type === GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS && + notify.status === GroupNotifyMsgStatus.KUNHANDLE + ) { + this.context.logger.logDebug(`收到群员邀请加群通知:${notify}`); + const groupInviteEvent = new OB11GroupRequestEvent( + this.core, + +notify.group.groupCode, + +await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid), + 'add', + notify.postscript, + flag + ); + this.networkManager + .emitEvent(groupInviteEvent) + .catch((e) => + this.context.logger.logError('处理邀请本人加群失败', e) + ); + } } - try { - const ob11Msg = await this.apis.MsgApi.parseMessageV2(message, this.configLoader.configData.parseMultMsg); - if (ob11Msg) { - const isSelfMsg = this.isSelfMessage(ob11Msg); - this.context.logger.logDebug('转化为 OB11Message', ob11Msg); - const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message); - this.handleDebugNetwork(network, msgMap, message); - this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg); - this.networkManager.emitEventByNames(msgMap); - } + } + }; + this.context.session + .getGroupService() + .addKernelGroupListener(proxiedListenerOf(groupListener, this.context.logger)); + } - } catch (e) { - this.context.logger.logError('constructMessage error: ', e); + private async emitMsg (message: RawMessage) { + const network = await this.networkManager.getAllConfig(); + this.context.logger.logDebug('收到新消息 RawMessage', message); + await Promise.allSettled([ + this.handleMsg(message, network), + message.chatType === ChatType.KCHATTYPEGROUP ? this.handleGroupEvent(message) : this.handlePrivateMsgEvent(message), + ]); + } + + private async handleMsg (message: RawMessage, network: Array) { + // 过滤无效消息 + if (message.msgType === NTMsgType.KMSGTYPENULL) { + return; + } + try { + const ob11Msg = await this.apis.MsgApi.parseMessageV2(message, this.configLoader.configData.parseMultMsg); + if (ob11Msg) { + const isSelfMsg = this.isSelfMessage(ob11Msg); + this.context.logger.logDebug('转化为 OB11Message', ob11Msg); + const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message); + this.handleDebugNetwork(network, msgMap, message); + this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg); + this.networkManager.emitEventByNames(msgMap); + } + } catch (e) { + this.context.logger.logError('constructMessage error: ', e); + } + } + + private isSelfMessage (ob11Msg: { + stringMsg: OB11Message; + arrayMsg: OB11Message; + }): boolean { + return ob11Msg.stringMsg.user_id.toString() === this.core.selfInfo.uin || + ob11Msg.arrayMsg.user_id.toString() === this.core.selfInfo.uin; + } + + private createMsgMap (network: Array, ob11Msg: { + stringMsg: OB11Message; + arrayMsg: OB11Message; + }, isSelfMsg: boolean, message: RawMessage): Map { + const msgMap: Map = new Map(); + network.filter(e => e.enable).forEach(e => { + if (isSelfMsg || message.chatType !== ChatType.KCHATTYPEGROUP) { + ob11Msg.stringMsg.target_id = parseInt(message.peerUin); + ob11Msg.arrayMsg.target_id = parseInt(message.peerUin); + } + if ('messagePostFormat' in e && e.messagePostFormat == 'string') { + msgMap.set(e.name, structuredClone(ob11Msg.stringMsg)); + } else { + msgMap.set(e.name, structuredClone(ob11Msg.arrayMsg)); + } + }); + return msgMap; + } + + private handleDebugNetwork (network: Array, msgMap: Map, message: RawMessage) { + const debugNetwork = network.filter(e => e.enable && e.debug); + if (debugNetwork.length > 0) { + debugNetwork.forEach(adapter => { + const msg = msgMap.get(adapter.name); + if (msg) { + msg.raw = message; } + }); + } else if (msgMap.size === 0) { + this.context.logger.logDebug('没有可用的网络适配器发送消息,消息内容:', message); } + } - private isSelfMessage(ob11Msg: { - stringMsg: OB11Message - arrayMsg: OB11Message - }): boolean { - return ob11Msg.stringMsg.user_id.toString() == this.core.selfInfo.uin || - ob11Msg.arrayMsg.user_id.toString() == this.core.selfInfo.uin; + private handleNotReportSelfNetwork (network: Array, msgMap: Map, isSelfMsg: boolean) { + if (isSelfMsg) { + const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e))); + notReportSelfNetwork.forEach(adapter => { + msgMap.delete(adapter.name); + }); } + } - private createMsgMap(network: Array, ob11Msg: { - stringMsg: OB11Message - arrayMsg: OB11Message - }, isSelfMsg: boolean, message: RawMessage): Map { - const msgMap: Map = new Map(); - network.filter(e => e.enable).forEach(e => { - if (isSelfMsg || message.chatType !== ChatType.KCHATTYPEGROUP) { - ob11Msg.stringMsg.target_id = parseInt(message.peerUin); - ob11Msg.arrayMsg.target_id = parseInt(message.peerUin); - } - if ('messagePostFormat' in e && e.messagePostFormat == 'string') { - msgMap.set(e.name, structuredClone(ob11Msg.stringMsg)); - } else { - msgMap.set(e.name, structuredClone(ob11Msg.arrayMsg)); - } - - }); - return msgMap; - } - - private handleDebugNetwork(network: Array, msgMap: Map, message: RawMessage) { - const debugNetwork = network.filter(e => e.enable && e.debug); - if (debugNetwork.length > 0) { - debugNetwork.forEach(adapter => { - const msg = msgMap.get(adapter.name); - if (msg) { - msg.raw = message; - } - }); - } else if (msgMap.size === 0) { - return; + private async handleGroupEvent (message: RawMessage) { + try { + // 群名片修改事件解析 任何都该判断 + if (message.senderUin && message.senderUin !== '0') { + const cardChangedEvent = await this.apis.GroupApi.parseCardChangedEvent(message); + if (cardChangedEvent) { + await this.networkManager.emitEvent(cardChangedEvent); } - } - - private handleNotReportSelfNetwork(network: Array, msgMap: Map, isSelfMsg: boolean) { - if (isSelfMsg) { - const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e))); - notReportSelfNetwork.forEach(adapter => { - msgMap.delete(adapter.name); - }); + } + if (message.msgType === NTMsgType.KMSGTYPEFILE) { + // 文件为单元素消息 + const elementWrapper = message.elements.find(e => !!e.fileElement); + if (elementWrapper?.fileElement) { + const uploadGroupFileEvent = await this.apis.GroupApi.parseGroupUploadFileEvene(message, elementWrapper.fileElement, elementWrapper); + if (uploadGroupFileEvent) { + await this.networkManager.emitEvent(uploadGroupFileEvent); + } } - } - - private async handleGroupEvent(message: RawMessage) { - try { - // 群名片修改事件解析 任何都该判断 - if (message.senderUin && message.senderUin !== '0') { - const cardChangedEvent = await this.apis.GroupApi.parseCardChangedEvent(message); - if (cardChangedEvent) { - await this.networkManager.emitEvent(cardChangedEvent); - } - } - if (message.msgType === NTMsgType.KMSGTYPEFILE) { - // 文件为单元素消息 - const elementWrapper = message.elements.find(e => !!e.fileElement); - if (elementWrapper?.fileElement) { - const uploadGroupFileEvent = await this.apis.GroupApi.parseGroupUploadFileEvene(message, elementWrapper.fileElement, elementWrapper); - if (uploadGroupFileEvent) { - await this.networkManager.emitEvent(uploadGroupFileEvent); - } - } - } else if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) { - // 灰条为单元素消息 - const grayTipElement = message.elements[0]?.grayTipElement; - if (grayTipElement) { - const event = await this.apis.GroupApi.parseGrayTipElement(message, grayTipElement); - if (event) { - await this.networkManager.emitEvent(event); - } - } - } - } catch (e) { - this.context.logger.logError('constructGroupEvent error: ', e); + } else if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) { + // 灰条为单元素消息 + const grayTipElement = message.elements[0]?.grayTipElement; + if (grayTipElement) { + const event = await this.apis.GroupApi.parseGrayTipElement(message, grayTipElement); + if (event) { + await this.networkManager.emitEvent(event); + } } + } + } catch (e) { + this.context.logger.logError('constructGroupEvent error: ', e); } + } - private async handlePrivateMsgEvent(message: RawMessage) { - try { - if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) { - // 灰条为单元素消息 - const grayTipElement = message.elements[0]?.grayTipElement; - if (grayTipElement) { - const event = await this.apis.MsgApi.parsePrivateMsgEvent(message, grayTipElement); - if (event) { - await this.networkManager.emitEvent(event); - } - - } - } - } catch (e) { - this.context.logger.logError('constructPrivateEvent error: ', e); + private async handlePrivateMsgEvent (message: RawMessage) { + try { + if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) { + // 灰条为单元素消息 + const grayTipElement = message.elements[0]?.grayTipElement; + if (grayTipElement) { + const event = await this.apis.MsgApi.parsePrivateMsgEvent(message, grayTipElement); + if (event) { + await this.networkManager.emitEvent(event); + } } + } + } catch (e) { + this.context.logger.logError('constructPrivateEvent error: ', e); } + } - private async emitRecallMsg(message: RawMessage, element: MessageElement) { - const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' }; - const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) ?? MessageUnique.createUniqueMsgId(peer, message.msgId); - if (message.chatType == ChatType.KCHATTYPEC2C) { - return await this.emitFriendRecallMsg(message, oriMessageId, element); - } else if (message.chatType == ChatType.KCHATTYPEGROUP) { - return await this.emitGroupRecallMsg(message, oriMessageId, element); - } - return; + private async emitRecallMsg (message: RawMessage, element: MessageElement) { + const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' }; + const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) ?? MessageUnique.createUniqueMsgId(peer, message.msgId); + if (message.chatType == ChatType.KCHATTYPEC2C) { + return await this.emitFriendRecallMsg(message, oriMessageId, element); + } else if (message.chatType == ChatType.KCHATTYPEGROUP) { + return await this.emitGroupRecallMsg(message, oriMessageId, element); } + } - private async emitFriendRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) { - const operatorUid = element.grayTipElement?.revokeElement.operatorUid; - if (!operatorUid) return undefined; - return new OB11FriendRecallNoticeEvent( - this.core, - +message.senderUin, - oriMessageId - ); - } - - private async emitGroupRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) { - const operatorUid = element.grayTipElement?.revokeElement.operatorUid; - if (!operatorUid) return undefined; - const operatorId = await this.core.apis.UserApi.getUinByUidV2(operatorUid); - return new OB11GroupRecallNoticeEvent( - this.core, - +message.peerUin, - +message.senderUin, - +operatorId, - oriMessageId - ); - } + private async emitFriendRecallMsg (message: RawMessage, oriMessageId: number, element: MessageElement) { + const operatorUid = element.grayTipElement?.revokeElement.operatorUid; + if (!operatorUid) return undefined; + return new OB11FriendRecallNoticeEvent( + this.core, + +message.senderUin, + oriMessageId + ); + } + private async emitGroupRecallMsg (message: RawMessage, oriMessageId: number, element: MessageElement) { + const operatorUid = element.grayTipElement?.revokeElement.operatorUid; + if (!operatorUid) return undefined; + const operatorId = await this.core.apis.UserApi.getUinByUidV2(operatorUid); + return new OB11GroupRecallNoticeEvent( + this.core, + +message.peerUin, + +message.senderUin, + +operatorId, + oriMessageId + ); + } } export * from './types'; diff --git a/src/onebot/network/adapter.ts b/src/onebot/network/adapter.ts index 173df954..4c0cd35c 100644 --- a/src/onebot/network/adapter.ts +++ b/src/onebot/network/adapter.ts @@ -6,28 +6,28 @@ import { ActionMap } from '@/onebot/action'; import { OB11EmitEventContent, OB11NetworkReloadType } from '@/onebot/network/index'; export abstract class IOB11NetworkAdapter { - name: string; - isEnable: boolean = false; - config: CT; - readonly logger: LogWrapper; - readonly core: NapCatCore; - readonly obContext: NapCatOneBot11Adapter; - readonly actions: ActionMap; + name: string; + isEnable: boolean = false; + config: CT; + readonly logger: LogWrapper; + readonly core: NapCatCore; + readonly obContext: NapCatOneBot11Adapter; + readonly actions: ActionMap; - constructor(name: string, config: CT, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { - this.name = name; - this.config = structuredClone(config); - this.core = core; - this.obContext = obContext; - this.actions = actions; - this.logger = core.context.logger; - } + constructor (name: string, config: CT, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { + this.name = name; + this.config = structuredClone(config); + this.core = core; + this.obContext = obContext; + this.actions = actions; + this.logger = core.context.logger; + } - abstract onEvent(event: T): Promise; + abstract onEvent(event: T): Promise; - abstract open(): void | Promise; + abstract open (): void | Promise; - abstract close(): void | Promise; + abstract close (): void | Promise; - abstract reload(config: unknown): OB11NetworkReloadType | Promise; + abstract reload (config: unknown): OB11NetworkReloadType | Promise; } diff --git a/src/onebot/network/http-client.ts b/src/onebot/network/http-client.ts index 45d4298d..982048fb 100644 --- a/src/onebot/network/http-client.ts +++ b/src/onebot/network/http-client.ts @@ -10,61 +10,61 @@ import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; import json5 from 'json5'; export class OB11HttpClientAdapter extends IOB11NetworkAdapter { - constructor( - name: string, config: HttpClientConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap - ) { - super(name, config, core, obContext, actions); + constructor ( + name: string, config: HttpClientConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap + ) { + super(name, config, core, obContext, actions); + } + + async onEvent(event: T) { + this.emitEventAsync(event).catch(e => this.logger.logError('[OneBot] [Http Client] 新消息事件HTTP上报返回快速操作失败', e)); + } + + async emitEventAsync(event: T) { + if (!this.isEnable) return; + const headers: Record = { + 'Content-Type': 'application/json', + 'x-self-id': this.core.selfInfo.uin, + }; + + const msgStr = JSON.stringify(event); + if (this.config.token) { + const hmac = createHmac('sha1', this.config.token); + hmac.update(msgStr); + headers['x-signature'] = 'sha1=' + hmac.digest('hex'); } - async onEvent(event: T) { - this.emitEventAsync(event).catch(e => this.logger.logError('[OneBot] [Http Client] 新消息事件HTTP上报返回快速操作失败', e)); + const data = await RequestUtil.HttpGetText(this.config.url, 'POST', msgStr, headers); + const resJson: QuickAction = data ? json5.parse(data) : {}; + + await this.obContext.apis.QuickActionApi.handleQuickOperation(event as QuickActionEvent, resJson); + } + + open () { + this.isEnable = true; + } + + close () { + this.isEnable = false; + } + + async reload (newConfig: HttpClientConfig) { + const wasEnabled = this.isEnable; + const oldUrl = this.config.url; + this.config = newConfig; + + if (newConfig.enable && !wasEnabled) { + this.open(); + return OB11NetworkReloadType.NetWorkOpen; + } else if (!newConfig.enable && wasEnabled) { + this.close(); + return OB11NetworkReloadType.NetWorkClose; } - async emitEventAsync(event: T) { - if (!this.isEnable) return; - const headers: Record = { - 'Content-Type': 'application/json', - 'x-self-id': this.core.selfInfo.uin, - }; - - const msgStr = JSON.stringify(event); - if (this.config.token) { - const hmac = createHmac('sha1', this.config.token); - hmac.update(msgStr); - headers['x-signature'] = 'sha1=' + hmac.digest('hex'); - } - - const data = await RequestUtil.HttpGetText(this.config.url, 'POST', msgStr, headers); - const resJson: QuickAction = data ? json5.parse(data) : {}; - - await this.obContext.apis.QuickActionApi.handleQuickOperation(event as QuickActionEvent, resJson); + if (oldUrl !== newConfig.url) { + return OB11NetworkReloadType.NetWorkReload; } - open() { - this.isEnable = true; - } - - close() { - this.isEnable = false; - } - - async reload(newConfig: HttpClientConfig) { - const wasEnabled = this.isEnable; - const oldUrl = this.config.url; - this.config = newConfig; - - if (newConfig.enable && !wasEnabled) { - this.open(); - return OB11NetworkReloadType.NetWorkOpen; - } else if (!newConfig.enable && wasEnabled) { - this.close(); - return OB11NetworkReloadType.NetWorkClose; - } - - if (oldUrl !== newConfig.url) { - return OB11NetworkReloadType.NetWorkReload; - } - - return OB11NetworkReloadType.Normal; - } + return OB11NetworkReloadType.Normal; + } } diff --git a/src/onebot/network/http-server-sse.ts b/src/onebot/network/http-server-sse.ts index 2368b99d..9ae3ec8c 100644 --- a/src/onebot/network/http-server-sse.ts +++ b/src/onebot/network/http-server-sse.ts @@ -3,42 +3,41 @@ import { Request, Response } from 'express'; import { OB11HttpServerAdapter } from './http-server'; export class OB11HttpSSEServerAdapter extends OB11HttpServerAdapter { - private sseClients: Response[] = []; + private sseClients: Response[] = []; - override async handleRequest(req: Request, res: Response) { - if (req.path === '/_events') { - this.createSseSupport(req, res); - } else { - super.httpApiRequest(req, res, true); - } + override async handleRequest (req: Request, res: Response) { + if (req.path === '/_events') { + this.createSseSupport(req, res); + } else { + super.httpApiRequest(req, res, true); } + } - private async createSseSupport(req: Request, res: Response) { - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Cache-Control', 'no-cache'); - res.setHeader('Connection', 'keep-alive'); - res.flushHeaders(); + private async createSseSupport (req: Request, res: Response) { + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); - this.sseClients.push(res); - req.on('close', () => { - this.sseClients = this.sseClients.filter((client) => client !== res); + this.sseClients.push(res); + req.on('close', () => { + this.sseClients = this.sseClients.filter((client) => client !== res); + }); + } + + override async onEvent(event: T) { + const promises: Promise[] = []; + this.sseClients.forEach((res) => { + promises.push(new Promise((resolve, reject) => { + res.write(`data: ${JSON.stringify(event)}\n\n`, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } }); - - } - - override async onEvent(event: T) { - let promises: Promise[] = []; - this.sseClients.forEach((res) => { - promises.push(new Promise((resolve, reject) => { - res.write(`data: ${JSON.stringify(event)}\n\n`, (err) => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - })); - }); - await Promise.allSettled(promises); - } + })); + }); + await Promise.allSettled(promises); + } } diff --git a/src/onebot/network/http-server.ts b/src/onebot/network/http-server.ts index 75f9c96a..d217a041 100644 --- a/src/onebot/network/http-server.ts +++ b/src/onebot/network/http-server.ts @@ -12,174 +12,171 @@ import json5 from 'json5'; import { isFinished } from 'on-finished'; import typeis from 'type-is'; export class OB11HttpServerAdapter extends IOB11NetworkAdapter { - private app: Express | undefined; - private server: http.Server | undefined; + private app: Express | undefined; + private server: http.Server | undefined; - constructor(name: string, config: HttpServerConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { - super(name, config, core, obContext, actions); - } + constructor (name: string, config: HttpServerConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { + super(name, config, core, obContext, actions); + } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - override async onEvent(_event: T) { - // http server is passive, no need to emit event - } + override async onEvent(_event: T) { + // http server is passive, no need to emit event + } - open() { - try { - if (this.isEnable) { - this.core.context.logger.logError('Cannot open a closed HTTP server'); - return; - } - if (!this.isEnable) { - this.initializeServer(); - this.isEnable = true; - } - } catch (e) { - this.core.context.logger.logError(`[OneBot] [HTTP Server Adapter] Boot Error: ${e}`); - } - - } - - async close() { - this.isEnable = false; - this.server?.close(); - this.app = undefined; - } - - - private initializeServer() { - this.app = express(); - this.server = http.createServer(this.app); - - this.app.use(cors()); - this.app.use(express.urlencoded({ extended: true, limit: '5000mb' })); - - this.app.use((req, res, next) => { - if (isFinished(req)) { - next(); - return; - } - if (!typeis.hasBody(req)) { - next(); - return; - } - // 兼容处理没有带content-type的请求 - req.headers['content-type'] = 'application/json'; - let rawData = ''; - req.on('data', (chunk) => { - rawData += chunk; - }); - req.on('end', () => { - try { - req.body = { ...json5.parse(rawData || '{}'), ...req.body }; - next(); - } catch { - return res.status(400).send('Invalid JSON'); - } - return; - }); - req.on('error', () => { - return res.status(400).send('Invalid JSON'); - }); - }); - //@ts-expect-error authorize - this.app.use((req, res, next) => this.authorize(this.config.token, req, res, next)); - this.app.use(async (req, res) => { - await this.handleRequest(req, res); - }); - this.server.listen(this.config.port, this.config.host, () => { - this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On ${this.config.host}:${this.config.port}`); - }); - } - - private authorize(token: string | undefined, req: Request, res: Response, next: NextFunction) { - if (!token || token.length == 0) return next();//客户端未设置密钥 - const HeaderClientToken = req.headers.authorization?.split('Bearer ').pop() || ''; - const QueryClientToken = req.query['access_token']; - const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken; - if (ClientToken === token) { - return next(); - } else { - return res.status(403).send(JSON.stringify({ message: 'token verify failed!' })); - } - } - - async httpApiRequest(req: Request, res: Response, request_sse: boolean = false) { - let payload = req.body; - if (req.method == 'get') { - payload = req.query; - } else if (req.query) { - payload = { ...req.body, ...req.query }; - } - if (req.path === '' || req.path === '/') { - const hello = OB11Response.ok({}); - hello.message = 'NapCat4 Is Running'; - return res.json(hello); - } - const actionName = req.path.split('/')[1]; - const payload_echo = payload['echo']; - const real_echo = payload_echo ?? Math.random().toString(36).substring(2, 15); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const action = this.actions.get(actionName as any); - if (action) { - const useStream = action.useStream; - try { - const result = await action.handle(payload, this.name, this.config, { - send: request_sse ? async (data: object) => { - await this.onEvent({ ...OB11Response.ok(data, real_echo, true) } as unknown as OB11EmitEventContent); - } : async (data: object) => { - let newPromise = new Promise((resolve, _reject) => { - res.write(JSON.stringify({ ...OB11Response.ok(data, real_echo, true) }) + "\r\n\r\n", () => { - resolve(); - }); - }); - return newPromise; - } - }, real_echo); - if (useStream) { - res.write(JSON.stringify({ ...result }) + "\r\n\r\n"); - return res.end(); - }; - return res.json(result); - } catch (error: unknown) { - return res.json(OB11Response.error((error as Error)?.stack?.toString() || (error as Error)?.message || 'Error Handle', 200, real_echo)); - } - } else { - return res.json(OB11Response.error('不支持的Api ' + actionName, 200, real_echo)); - } - } - - async handleRequest(req: Request, res: Response) { - if (!this.isEnable) { - this.core.context.logger.log('[OneBot] [HTTP Server Adapter] Server is closed'); - res.json(OB11Response.error('Server is closed', 200)); - return; - } - this.httpApiRequest(req, res); + open () { + try { + if (this.isEnable) { + this.core.context.logger.logError('Cannot open a closed HTTP server'); return; + } + if (!this.isEnable) { + this.initializeServer(); + this.isEnable = true; + } + } catch (e) { + this.core.context.logger.logError(`[OneBot] [HTTP Server Adapter] Boot Error: ${e}`); } + } - async reload(newConfig: HttpServerConfig) { - const wasEnabled = this.isEnable; - const oldPort = this.config.port; - this.config = newConfig; + async close () { + this.isEnable = false; + this.server?.close(); + this.app = undefined; + } - if (newConfig.enable && !wasEnabled) { - this.open(); - return OB11NetworkReloadType.NetWorkOpen; - } else if (!newConfig.enable && wasEnabled) { - this.close(); - return OB11NetworkReloadType.NetWorkClose; + private initializeServer () { + this.app = express(); + this.server = http.createServer(this.app); + + this.app.use(cors()); + this.app.use(express.urlencoded({ extended: true, limit: '5000mb' })); + + this.app.use((req, res, next) => { + if (isFinished(req)) { + next(); + return; + } + if (!typeis.hasBody(req)) { + next(); + return; + } + // 兼容处理没有带content-type的请求 + req.headers['content-type'] = 'application/json'; + let rawData = ''; + req.on('data', (chunk) => { + rawData += chunk; + }); + req.on('end', () => { + try { + req.body = { ...json5.parse(rawData || '{}'), ...req.body }; + next(); + } catch { + return res.status(400).send('Invalid JSON'); } + }); + req.on('error', () => { + return res.status(400).send('Invalid JSON'); + }); + }); + // @ts-expect-error authorize + this.app.use((req, res, next) => this.authorize(this.config.token, req, res, next)); + this.app.use(async (req, res) => { + await this.handleRequest(req, res); + }); + this.server.listen(this.config.port, this.config.host, () => { + this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On ${this.config.host}:${this.config.port}`); + }); + } - if (oldPort !== newConfig.port) { - this.close(); - if (newConfig.enable) { - this.open(); + private authorize (token: string | undefined, req: Request, res: Response, next: NextFunction) { + if (!token || token.length == 0) return next();// 客户端未设置密钥 + const HeaderClientToken = req.headers.authorization?.split('Bearer ').pop() || ''; + const QueryClientToken = req.query['access_token']; + const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken; + if (ClientToken === token) { + return next(); + } else { + return res.status(403).send(JSON.stringify({ message: 'token verify failed!' })); + } + } + + async httpApiRequest (req: Request, res: Response, request_sse: boolean = false) { + let payload = req.body; + if (req.method == 'get') { + payload = req.query; + } else if (req.query) { + payload = { ...req.body, ...req.query }; + } + if (req.path === '' || req.path === '/') { + const hello = OB11Response.ok({}); + hello.message = 'NapCat4 Is Running'; + return res.json(hello); + } + const actionName = req.path.split('/')[1]; + const payload_echo = payload['echo']; + const real_echo = payload_echo ?? Math.random().toString(36).substring(2, 15); + + const action = this.actions.get(actionName as any); + if (action) { + const useStream = action.useStream; + try { + const result = await action.handle(payload, this.name, this.config, { + send: request_sse + ? async (data: object) => { + await this.onEvent({ ...OB11Response.ok(data, real_echo, true) } as unknown as OB11EmitEventContent); } - return OB11NetworkReloadType.NetWorkReload; + : async (data: object) => { + const newPromise = new Promise((resolve, _reject) => { + res.write(JSON.stringify({ ...OB11Response.ok(data, real_echo, true) }) + '\r\n\r\n', () => { + resolve(); + }); + }); + return newPromise; + }, + }, real_echo); + if (useStream) { + res.write(JSON.stringify({ ...result }) + '\r\n\r\n'); + return res.end(); } - - return OB11NetworkReloadType.Normal; + return res.json(result); + } catch (error: unknown) { + return res.json(OB11Response.error((error as Error)?.stack?.toString() || (error as Error)?.message || 'Error Handle', 200, real_echo)); + } + } else { + return res.json(OB11Response.error('不支持的Api ' + actionName, 200, real_echo)); } + } + + async handleRequest (req: Request, res: Response) { + if (!this.isEnable) { + this.core.context.logger.log('[OneBot] [HTTP Server Adapter] Server is closed'); + res.json(OB11Response.error('Server is closed', 200)); + return; + } + this.httpApiRequest(req, res); + } + + async reload (newConfig: HttpServerConfig) { + const wasEnabled = this.isEnable; + const oldPort = this.config.port; + this.config = newConfig; + + if (newConfig.enable && !wasEnabled) { + this.open(); + return OB11NetworkReloadType.NetWorkOpen; + } else if (!newConfig.enable && wasEnabled) { + this.close(); + return OB11NetworkReloadType.NetWorkClose; + } + + if (oldPort !== newConfig.port) { + this.close(); + if (newConfig.enable) { + this.open(); + } + return OB11NetworkReloadType.NetWorkReload; + } + + return OB11NetworkReloadType.Normal; + } } diff --git a/src/onebot/network/index.ts b/src/onebot/network/index.ts index 0faff7da..38962145 100644 --- a/src/onebot/network/index.ts +++ b/src/onebot/network/index.ts @@ -5,100 +5,103 @@ import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; export type OB11EmitEventContent = OneBotEvent | OB11Message; export enum OB11NetworkReloadType { - Normal = 0, - ConfigChange = 1, - NetWorkReload = 2, - NetWorkClose = 3, - NetWorkOpen = 4 + Normal = 0, + ConfigChange = 1, + NetWorkReload = 2, + NetWorkClose = 3, + NetWorkOpen = 4, } export class OB11NetworkManager { - adapters: Map> = new Map(); + adapters: Map> = new Map(); - async openAllAdapters() { - return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.open())); - } + async openAllAdapters () { + return Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.open())); + } - async emitEvent(event: OB11EmitEventContent) { - return Promise.all(Array.from(this.adapters.values()).map(async adapter => { - if (adapter.isEnable) { - return await adapter.onEvent(event); - } - })); - } + async emitEvent (event: OB11EmitEventContent) { + return Promise.all(Array.from(this.adapters.values()).map(async adapter => { + if (adapter.isEnable) { + return await adapter.onEvent(event); + } + })); + } - async emitEvents(events: OB11EmitEventContent[]) { - return Promise.all(events.map(event => this.emitEvent(event))); - } + async emitEvents (events: OB11EmitEventContent[]) { + return Promise.all(events.map(event => this.emitEvent(event))); + } - async emitEventByName(names: string[], event: OB11EmitEventContent) { - return Promise.all(names.map(async name => { - const adapter = this.adapters.get(name); - if (adapter && adapter.isEnable) { - return await adapter.onEvent(event); - } - })); - } + async emitEventByName (names: string[], event: OB11EmitEventContent) { + return Promise.all(names.map(async name => { + const adapter = this.adapters.get(name); + if (adapter && adapter.isEnable) { + return await adapter.onEvent(event); + } + })); + } - async emitEventByNames(map: Map) { - return Promise.all(Array.from(map.entries()).map(async ([name, event]) => { - const adapter = this.adapters.get(name); - if (adapter && adapter.isEnable) { - return await adapter.onEvent(event); - } - })); - } + async emitEventByNames (map: Map) { + return Promise.all(Array.from(map.entries()).map(async ([name, event]) => { + const adapter = this.adapters.get(name); + if (adapter && adapter.isEnable) { + return await adapter.onEvent(event); + } + })); + } - registerAdapter(adapter: IOB11NetworkAdapter) { - this.adapters.set(adapter.name, adapter); - } + registerAdapter(adapter: IOB11NetworkAdapter) { + this.adapters.set(adapter.name, adapter); + } - async registerAdapterAndOpen(adapter: IOB11NetworkAdapter) { - this.registerAdapter(adapter); - await adapter.open(); - } + async registerAdapterAndOpen(adapter: IOB11NetworkAdapter) { + this.registerAdapter(adapter); + await adapter.open(); + } - async closeSomeAdapters(adaptersToClose: IOB11NetworkAdapter[]) { - for (const adapter of adaptersToClose) { - this.adapters.delete(adapter.name); - await adapter.close(); - } - } - async closeSomeAdaterWhenOpen(adaptersToClose: IOB11NetworkAdapter[]) { - for (const adapter of adaptersToClose) { - this.adapters.delete(adapter.name); - if (adapter.isEnable) { - await adapter.close(); - } - } + async closeSomeAdapters(adaptersToClose: IOB11NetworkAdapter[]) { + for (const adapter of adaptersToClose) { + this.adapters.delete(adapter.name); + await adapter.close(); } + } - findSomeAdapter(name: string) { - return this.adapters.get(name); + async closeSomeAdaterWhenOpen(adaptersToClose: IOB11NetworkAdapter[]) { + for (const adapter of adaptersToClose) { + this.adapters.delete(adapter.name); + if (adapter.isEnable) { + await adapter.close(); + } } + } - async closeAdapterByPredicate(closeFilter: (adapter: IOB11NetworkAdapter) => boolean) { - const adaptersToClose = Array.from(this.adapters.values()).filter(closeFilter); - await this.closeSomeAdapters(adaptersToClose); - } + findSomeAdapter (name: string) { + return this.adapters.get(name); + } - async closeAllAdapters() { - await Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.close())); - this.adapters.clear(); - } + async closeAdapterByPredicate (closeFilter: (adapter: IOB11NetworkAdapter) => boolean) { + const adaptersToClose = Array.from(this.adapters.values()).filter(closeFilter); + await this.closeSomeAdapters(adaptersToClose); + } - async readloadAdapter(name: string, config: T) { - const adapter = this.adapters.get(name); - if (adapter) { - await adapter.reload(config); - } - } - async readloadSomeAdapters(configMap: Map) { - await Promise.all(Array.from(configMap.entries()).map(([name, config]) => this.readloadAdapter(name, config))); - } - async getAllConfig() { - return Array.from(this.adapters.values()).map(adapter => adapter.config); + async closeAllAdapters () { + await Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.close())); + this.adapters.clear(); + } + + async readloadAdapter(name: string, config: T) { + const adapter = this.adapters.get(name); + if (adapter) { + await adapter.reload(config); } + } + + async readloadSomeAdapters(configMap: Map) { + await Promise.all(Array.from(configMap.entries()).map(([name, config]) => this.readloadAdapter(name, config))); + } + + async getAllConfig () { + return Array.from(this.adapters.values()).map(adapter => adapter.config); + } } export * from './http-client'; diff --git a/src/onebot/network/plugin-manger.ts b/src/onebot/network/plugin-manger.ts index fcd29723..b1e354d0 100644 --- a/src/onebot/network/plugin-manger.ts +++ b/src/onebot/network/plugin-manger.ts @@ -8,372 +8,372 @@ import fs from 'fs'; import path from 'path'; export interface PluginPackageJson { - name?: string; - version?: string; - main?: string; + name?: string; + version?: string; + main?: string; } export interface PluginModule { - plugin_init: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; - plugin_onmessage?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: OB11Message, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; - plugin_onevent?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: T, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; - plugin_cleanup?: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; + plugin_init: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; + plugin_onmessage?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: OB11Message, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; + plugin_onevent?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: T, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; + plugin_cleanup?: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginMangerAdapter) => void | Promise; } export interface LoadedPlugin { - name: string; - version?: string; - pluginPath: string; - entryPath: string; - packageJson?: PluginPackageJson; - module: PluginModule; + name: string; + version?: string; + pluginPath: string; + entryPath: string; + packageJson?: PluginPackageJson; + module: PluginModule; } export class OB11PluginMangerAdapter extends IOB11NetworkAdapter { - private readonly pluginPath: string; - private loadedPlugins: Map = new Map(); - declare config: PluginConfig; - constructor( - name: string, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap - ) { - const config = { - name: name, - messagePostFormat: 'array', - reportSelfMessage: true, - enable: true, - debug: true, - }; - super(name, config, core, obContext, actions); - this.pluginPath = this.core.context.pathWrapper.pluginPath; - } + private readonly pluginPath: string; + private loadedPlugins: Map = new Map(); + declare config: PluginConfig; + constructor ( + name: string, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap + ) { + const config = { + name, + messagePostFormat: 'array', + reportSelfMessage: true, + enable: true, + debug: true, + }; + super(name, config, core, obContext, actions); + this.pluginPath = this.core.context.pathWrapper.pluginPath; + } - /** + /** * 扫描并加载插件 */ - private async loadPlugins(): Promise { - try { - // 确保插件目录存在 - if (!fs.existsSync(this.pluginPath)) { - this.logger.logWarn(`[Plugin Adapter] Plugin directory does not exist: ${this.pluginPath}`); - fs.mkdirSync(this.pluginPath, { recursive: true }); - return; - } + private async loadPlugins (): Promise { + try { + // 确保插件目录存在 + if (!fs.existsSync(this.pluginPath)) { + this.logger.logWarn(`[Plugin Adapter] Plugin directory does not exist: ${this.pluginPath}`); + fs.mkdirSync(this.pluginPath, { recursive: true }); + return; + } - const items = fs.readdirSync(this.pluginPath, { withFileTypes: true }); + const items = fs.readdirSync(this.pluginPath, { withFileTypes: true }); - // 扫描文件和目录 - for (const item of items) { - if (item.isFile()) { - // 处理单文件插件 - await this.loadFilePlugin(item.name); - } else if (item.isDirectory()) { - // 处理目录插件 - await this.loadDirectoryPlugin(item.name); - } - } - - this.logger.log(`[Plugin Adapter] Loaded ${this.loadedPlugins.size} plugins`); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error loading plugins:`, error); + // 扫描文件和目录 + for (const item of items) { + if (item.isFile()) { + // 处理单文件插件 + await this.loadFilePlugin(item.name); + } else if (item.isDirectory()) { + // 处理目录插件 + await this.loadDirectoryPlugin(item.name); } - } + } - /** + this.logger.log(`[Plugin Adapter] Loaded ${this.loadedPlugins.size} plugins`); + } catch (error) { + this.logger.logError('[Plugin Adapter] Error loading plugins:', error); + } + } + + /** * 加载单文件插件 (.mjs, .js) */ - private async loadFilePlugin(filename: string): Promise { - // 只处理支持的文件类型 - if (!this.isSupportedFile(filename)) { - return; - } - - const filePath = path.join(this.pluginPath, filename); - const pluginName = path.parse(filename).name; - - try { - const module = await this.importModule(filePath); - if (!this.isValidPluginModule(module)) { - this.logger.logWarn(`[Plugin Adapter] File ${filename} is not a valid plugin (missing plugin methods)`); - return; - } - - const plugin: LoadedPlugin = { - name: pluginName, - pluginPath: this.pluginPath, - entryPath: filePath, - module: module - }; - - await this.registerPlugin(plugin); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error loading file plugin ${filename}:`, error); - } + private async loadFilePlugin (filename: string): Promise { + // 只处理支持的文件类型 + if (!this.isSupportedFile(filename)) { + return; } - /** + const filePath = path.join(this.pluginPath, filename); + const pluginName = path.parse(filename).name; + + try { + const module = await this.importModule(filePath); + if (!this.isValidPluginModule(module)) { + this.logger.logWarn(`[Plugin Adapter] File ${filename} is not a valid plugin (missing plugin methods)`); + return; + } + + const plugin: LoadedPlugin = { + name: pluginName, + pluginPath: this.pluginPath, + entryPath: filePath, + module, + }; + + await this.registerPlugin(plugin); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error loading file plugin ${filename}:`, error); + } + } + + /** * 加载目录插件 */ - private async loadDirectoryPlugin(dirname: string): Promise { - const pluginDir = path.join(this.pluginPath, dirname); + private async loadDirectoryPlugin (dirname: string): Promise { + const pluginDir = path.join(this.pluginPath, dirname); + try { + // 尝试读取 package.json + let packageJson: PluginPackageJson | undefined; + const packageJsonPath = path.join(pluginDir, 'package.json'); + + if (fs.existsSync(packageJsonPath)) { try { - // 尝试读取 package.json - let packageJson: PluginPackageJson | undefined; - const packageJsonPath = path.join(pluginDir, 'package.json'); - - if (fs.existsSync(packageJsonPath)) { - try { - const packageContent = fs.readFileSync(packageJsonPath, 'utf-8'); - packageJson = JSON.parse(packageContent); - } catch (error) { - this.logger.logWarn(`[Plugin Adapter] Invalid package.json in ${dirname}:`, error); - } - } - - // 确定入口文件 - const entryFile = this.findEntryFile(pluginDir, packageJson); - if (!entryFile) { - this.logger.logWarn(`[Plugin Adapter] No valid entry file found for plugin directory: ${dirname}`); - return; - } - - const entryPath = path.join(pluginDir, entryFile); - const module = await this.importModule(entryPath); - - if (!this.isValidPluginModule(module)) { - this.logger.logWarn(`[Plugin Adapter] Directory ${dirname} does not contain a valid plugin`); - return; - } - - const plugin: LoadedPlugin = { - name: packageJson?.name || dirname, - version: packageJson?.version, - pluginPath: pluginDir, - entryPath: entryPath, - packageJson: packageJson, - module: module - }; - - await this.registerPlugin(plugin); + const packageContent = fs.readFileSync(packageJsonPath, 'utf-8'); + packageJson = JSON.parse(packageContent); } catch (error) { - this.logger.logError(`[Plugin Adapter] Error loading directory plugin ${dirname}:`, error); + this.logger.logWarn(`[Plugin Adapter] Invalid package.json in ${dirname}:`, error); } - } + } - /** + // 确定入口文件 + const entryFile = this.findEntryFile(pluginDir, packageJson); + if (!entryFile) { + this.logger.logWarn(`[Plugin Adapter] No valid entry file found for plugin directory: ${dirname}`); + return; + } + + const entryPath = path.join(pluginDir, entryFile); + const module = await this.importModule(entryPath); + + if (!this.isValidPluginModule(module)) { + this.logger.logWarn(`[Plugin Adapter] Directory ${dirname} does not contain a valid plugin`); + return; + } + + const plugin: LoadedPlugin = { + name: packageJson?.name || dirname, + version: packageJson?.version, + pluginPath: pluginDir, + entryPath, + packageJson, + module, + }; + + await this.registerPlugin(plugin); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error loading directory plugin ${dirname}:`, error); + } + } + + /** * 查找插件目录的入口文件 */ - private findEntryFile(pluginDir: string, packageJson?: PluginPackageJson): string | null { - // 优先级:package.json main > 默认文件名 - const possibleEntries = [ - packageJson?.main, - 'index.mjs', - 'index.js', - 'main.mjs', - 'main.js' - ].filter(Boolean) as string[]; + private findEntryFile (pluginDir: string, packageJson?: PluginPackageJson): string | null { + // 优先级:package.json main > 默认文件名 + const possibleEntries = [ + packageJson?.main, + 'index.mjs', + 'index.js', + 'main.mjs', + 'main.js', + ].filter(Boolean) as string[]; - for (const entry of possibleEntries) { - const entryPath = path.join(pluginDir, entry); - if (fs.existsSync(entryPath) && fs.statSync(entryPath).isFile()) { - return entry; - } - } - - return null; + for (const entry of possibleEntries) { + const entryPath = path.join(pluginDir, entry); + if (fs.existsSync(entryPath) && fs.statSync(entryPath).isFile()) { + return entry; + } } - /** + return null; + } + + /** * 检查是否为支持的文件类型 */ - private isSupportedFile(filename: string): boolean { - const ext = path.extname(filename).toLowerCase(); - return ['.mjs', '.js'].includes(ext); - } + private isSupportedFile (filename: string): boolean { + const ext = path.extname(filename).toLowerCase(); + return ['.mjs', '.js'].includes(ext); + } - /** + /** * 动态导入模块 */ - private async importModule(filePath: string): Promise { - const fileUrl = `file://${filePath.replace(/\\/g, '/')}`; - return await import(fileUrl); - } + private async importModule (filePath: string): Promise { + const fileUrl = `file://${filePath.replace(/\\/g, '/')}`; + return await import(fileUrl); + } - /** + /** * 检查模块是否为有效的插件模块 */ - private isValidPluginModule(module: any): module is PluginModule { - return module && typeof module.plugin_init === 'function'; - } + private isValidPluginModule (module: any): module is PluginModule { + return module && typeof module.plugin_init === 'function'; + } - /** + /** * 注册插件 */ - private async registerPlugin(plugin: LoadedPlugin): Promise { - // 检查名称冲突 - if (this.loadedPlugins.has(plugin.name)) { - this.logger.logWarn(`[Plugin Adapter] Plugin name conflict: ${plugin.name}, skipping...`); - return; - } - - this.loadedPlugins.set(plugin.name, plugin); - this.logger.log(`[Plugin Adapter] Registered plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`); - - // 调用插件初始化方法(必须存在) - try { - await plugin.module.plugin_init(this.core, this.obContext, this.actions, this); - this.logger.log(`[Plugin Adapter] Initialized plugin: ${plugin.name}`); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error initializing plugin ${plugin.name}:`, error); - } + private async registerPlugin (plugin: LoadedPlugin): Promise { + // 检查名称冲突 + if (this.loadedPlugins.has(plugin.name)) { + this.logger.logWarn(`[Plugin Adapter] Plugin name conflict: ${plugin.name}, skipping...`); + return; } - /** + this.loadedPlugins.set(plugin.name, plugin); + this.logger.log(`[Plugin Adapter] Registered plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`); + + // 调用插件初始化方法(必须存在) + try { + await plugin.module.plugin_init(this.core, this.obContext, this.actions, this); + this.logger.log(`[Plugin Adapter] Initialized plugin: ${plugin.name}`); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error initializing plugin ${plugin.name}:`, error); + } + } + + /** * 卸载插件 */ - private async unloadPlugin(pluginName: string): Promise { - const plugin = this.loadedPlugins.get(pluginName); - if (!plugin) { - return; - } - - // 调用插件清理方法 - if (typeof plugin.module.plugin_cleanup === 'function') { - try { - await plugin.module.plugin_cleanup(this.core, this.obContext, this.actions, this); - this.logger.log(`[Plugin Adapter] Cleaned up plugin: ${pluginName}`); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error cleaning up plugin ${pluginName}:`, error); - } - } - - this.loadedPlugins.delete(pluginName); - this.logger.log(`[Plugin Adapter] Unloaded plugin: ${pluginName}`); + private async unloadPlugin (pluginName: string): Promise { + const plugin = this.loadedPlugins.get(pluginName); + if (!plugin) { + return; } - async onEvent(event: T) { - if (!this.isEnable) { - return; - } - - // 遍历所有已加载的插件,调用它们的事件处理方法 - try { - await Promise.allSettled( - Array.from(this.loadedPlugins.values()).map((plugin) => - this.callPluginEventHandler(plugin, event) - ) - ); - } catch (error) { - this.logger.logError('[Plugin Adapter] Error handling event:', error); - } + // 调用插件清理方法 + if (typeof plugin.module.plugin_cleanup === 'function') { + try { + await plugin.module.plugin_cleanup(this.core, this.obContext, this.actions, this); + this.logger.log(`[Plugin Adapter] Cleaned up plugin: ${pluginName}`); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error cleaning up plugin ${pluginName}:`, error); + } } - /** + this.loadedPlugins.delete(pluginName); + this.logger.log(`[Plugin Adapter] Unloaded plugin: ${pluginName}`); + } + + async onEvent(event: T) { + if (!this.isEnable) { + return; + } + + // 遍历所有已加载的插件,调用它们的事件处理方法 + try { + await Promise.allSettled( + Array.from(this.loadedPlugins.values()).map((plugin) => + this.callPluginEventHandler(plugin, event) + ) + ); + } catch (error) { + this.logger.logError('[Plugin Adapter] Error handling event:', error); + } + } + + /** * 调用插件的事件处理方法 */ - private async callPluginEventHandler(plugin: LoadedPlugin, event: OB11EmitEventContent): Promise { - try { - // 优先使用 plugin_onevent 方法 - if (typeof plugin.module.plugin_onevent === 'function') { - await plugin.module.plugin_onevent(this.name, this.core, this.obContext, event, this.actions, this); - } + private async callPluginEventHandler (plugin: LoadedPlugin, event: OB11EmitEventContent): Promise { + try { + // 优先使用 plugin_onevent 方法 + if (typeof plugin.module.plugin_onevent === 'function') { + await plugin.module.plugin_onevent(this.name, this.core, this.obContext, event, this.actions, this); + } - // 如果是消息事件并且插件有 plugin_onmessage 方法,也调用 - if ((event as any).message_type && typeof plugin.module.plugin_onmessage === 'function') { - await plugin.module.plugin_onmessage(this.name, this.core, this.obContext, event as OB11Message, this.actions, this); - } - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error calling plugin ${plugin.name} event handler:`, error); - } + // 如果是消息事件并且插件有 plugin_onmessage 方法,也调用 + if ((event as any).message_type && typeof plugin.module.plugin_onmessage === 'function') { + await plugin.module.plugin_onmessage(this.name, this.core, this.obContext, event as OB11Message, this.actions, this); + } + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error calling plugin ${plugin.name} event handler:`, error); + } + } + + async open () { + if (this.isEnable) { + return; } - async open() { - if (this.isEnable) { - return; - } + this.logger.log('[Plugin Adapter] Opening plugin adapter...'); + this.isEnable = true; - this.logger.log('[Plugin Adapter] Opening plugin adapter...'); - this.isEnable = true; + // 加载所有插件 + await this.loadPlugins(); - // 加载所有插件 - await this.loadPlugins(); + this.logger.log(`[Plugin Adapter] Plugin adapter opened with ${this.loadedPlugins.size} plugins loaded`); + } - this.logger.log(`[Plugin Adapter] Plugin adapter opened with ${this.loadedPlugins.size} plugins loaded`); + async close () { + if (!this.isEnable) { + return; } - async close() { - if (!this.isEnable) { - return; - } + this.logger.log('[Plugin Adapter] Closing plugin adapter...'); + this.isEnable = false; - this.logger.log('[Plugin Adapter] Closing plugin adapter...'); - this.isEnable = false; - - // 卸载所有插件 - const pluginNames = Array.from(this.loadedPlugins.keys()); - for (const pluginName of pluginNames) { - await this.unloadPlugin(pluginName); - } - - this.logger.log('[Plugin Adapter] Plugin adapter closed'); + // 卸载所有插件 + const pluginNames = Array.from(this.loadedPlugins.keys()); + for (const pluginName of pluginNames) { + await this.unloadPlugin(pluginName); } - async reload() { - this.logger.log('[Plugin Adapter] Reloading plugin adapter...'); + this.logger.log('[Plugin Adapter] Plugin adapter closed'); + } - // 先关闭然后重新打开 - await this.close(); - await this.open(); + async reload () { + this.logger.log('[Plugin Adapter] Reloading plugin adapter...'); - this.logger.log('[Plugin Adapter] Plugin adapter reloaded'); - return OB11NetworkReloadType.Normal; - } + // 先关闭然后重新打开 + await this.close(); + await this.open(); - /** + this.logger.log('[Plugin Adapter] Plugin adapter reloaded'); + return OB11NetworkReloadType.Normal; + } + + /** * 获取已加载的插件列表 */ - public getLoadedPlugins(): LoadedPlugin[] { - return Array.from(this.loadedPlugins.values()); - } + public getLoadedPlugins (): LoadedPlugin[] { + return Array.from(this.loadedPlugins.values()); + } - /** + /** * 获取插件信息 */ - public getPluginInfo(pluginName: string): LoadedPlugin | undefined { - return this.loadedPlugins.get(pluginName); - } + public getPluginInfo (pluginName: string): LoadedPlugin | undefined { + return this.loadedPlugins.get(pluginName); + } - /** + /** * 重载指定插件 */ - public async reloadPlugin(pluginName: string): Promise { - const plugin = this.loadedPlugins.get(pluginName); - if (!plugin) { - this.logger.logWarn(`[Plugin Adapter] Plugin ${pluginName} not found`); - return false; - } + public async reloadPlugin (pluginName: string): Promise { + const plugin = this.loadedPlugins.get(pluginName); + if (!plugin) { + this.logger.logWarn(`[Plugin Adapter] Plugin ${pluginName} not found`); + return false; + } - try { - // 卸载插件 - await this.unloadPlugin(pluginName); + try { + // 卸载插件 + await this.unloadPlugin(pluginName); - // 重新加载插件 - const isDirectory = fs.statSync(plugin.pluginPath).isDirectory() && + // 重新加载插件 + const isDirectory = fs.statSync(plugin.pluginPath).isDirectory() && plugin.pluginPath !== this.pluginPath; - if (isDirectory) { - const dirname = path.basename(plugin.pluginPath); - await this.loadDirectoryPlugin(dirname); - } else { - const filename = path.basename(plugin.entryPath); - await this.loadFilePlugin(filename); - } + if (isDirectory) { + const dirname = path.basename(plugin.pluginPath); + await this.loadDirectoryPlugin(dirname); + } else { + const filename = path.basename(plugin.entryPath); + await this.loadFilePlugin(filename); + } - this.logger.log(`[Plugin Adapter] Plugin ${pluginName} reloaded successfully`); - return true; - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error reloading plugin ${pluginName}:`, error); - return false; - } + this.logger.log(`[Plugin Adapter] Plugin ${pluginName} reloaded successfully`); + return true; + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error reloading plugin ${pluginName}:`, error); + return false; } + } } diff --git a/src/onebot/network/plugin.ts b/src/onebot/network/plugin.ts index c805edb8..389f2f22 100644 --- a/src/onebot/network/plugin.ts +++ b/src/onebot/network/plugin.ts @@ -8,366 +8,366 @@ import fs from 'fs'; import path from 'path'; export interface PluginPackageJson { - name?: string; - version?: string; - main?: string; + name?: string; + version?: string; + main?: string; } export interface PluginModule { - plugin_init: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; - plugin_onmessage?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: OB11Message, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; - plugin_onevent?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: T, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; - plugin_cleanup?: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; + plugin_init: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; + plugin_onmessage?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: OB11Message, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; + plugin_onevent?: (adapter: string, core: NapCatCore, obCtx: NapCatOneBot11Adapter, event: T, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; + plugin_cleanup?: (core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap, instance: OB11PluginAdapter) => void | Promise; } export interface LoadedPlugin { - name: string; - version?: string; - pluginPath: string; - entryPath: string; - packageJson?: PluginPackageJson; - module: PluginModule; + name: string; + version?: string; + pluginPath: string; + entryPath: string; + packageJson?: PluginPackageJson; + module: PluginModule; } export class OB11PluginAdapter extends IOB11NetworkAdapter { - private readonly pluginPath: string; - private loadedPlugins: Map = new Map(); + private readonly pluginPath: string; + private loadedPlugins: Map = new Map(); - constructor( - name: string, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap - ) { - const config = { - name: name, - messagePostFormat: 'array', - reportSelfMessage: true, - enable: true, - debug: true, - }; - super(name, config, core, obContext, actions); - this.pluginPath = this.core.context.pathWrapper.pluginPath; - } + constructor ( + name: string, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap + ) { + const config = { + name, + messagePostFormat: 'array', + reportSelfMessage: true, + enable: true, + debug: true, + }; + super(name, config, core, obContext, actions); + this.pluginPath = this.core.context.pathWrapper.pluginPath; + } - /** + /** * 扫描并加载插件 */ - private async loadPlugins(): Promise { - try { - // 确保插件目录存在 - if (!fs.existsSync(this.pluginPath)) { - this.logger.logWarn(`[Plugin Adapter] Plugin directory does not exist: ${this.pluginPath}`); - fs.mkdirSync(this.pluginPath, { recursive: true }); - return; - } + private async loadPlugins (): Promise { + try { + // 确保插件目录存在 + if (!fs.existsSync(this.pluginPath)) { + this.logger.logWarn(`[Plugin Adapter] Plugin directory does not exist: ${this.pluginPath}`); + fs.mkdirSync(this.pluginPath, { recursive: true }); + return; + } - const items = fs.readdirSync(this.pluginPath, { withFileTypes: true }); + const items = fs.readdirSync(this.pluginPath, { withFileTypes: true }); - // 扫描文件和目录 - for (const item of items) { - if (item.isFile()) { - // 处理单文件插件 - await this.loadFilePlugin(item.name); - } else if (item.isDirectory()) { - // 处理目录插件 - await this.loadDirectoryPlugin(item.name); - } - } - - this.logger.log(`[Plugin Adapter] Loaded ${this.loadedPlugins.size} plugins`); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error loading plugins:`, error); + // 扫描文件和目录 + for (const item of items) { + if (item.isFile()) { + // 处理单文件插件 + await this.loadFilePlugin(item.name); + } else if (item.isDirectory()) { + // 处理目录插件 + await this.loadDirectoryPlugin(item.name); } - } + } - /** + this.logger.log(`[Plugin Adapter] Loaded ${this.loadedPlugins.size} plugins`); + } catch (error) { + this.logger.logError('[Plugin Adapter] Error loading plugins:', error); + } + } + + /** * 加载单文件插件 (.mjs, .js) */ - private async loadFilePlugin(filename: string): Promise { - // 只处理支持的文件类型 - if (!this.isSupportedFile(filename)) { - return; - } - - const filePath = path.join(this.pluginPath, filename); - const pluginName = path.parse(filename).name; - - try { - const module = await this.importModule(filePath); - if (!this.isValidPluginModule(module)) { - this.logger.logWarn(`[Plugin Adapter] File ${filename} is not a valid plugin (missing plugin methods)`); - return; - } - - const plugin: LoadedPlugin = { - name: pluginName, - pluginPath: this.pluginPath, - entryPath: filePath, - module: module - }; - - await this.registerPlugin(plugin); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error loading file plugin ${filename}:`, error); - } + private async loadFilePlugin (filename: string): Promise { + // 只处理支持的文件类型 + if (!this.isSupportedFile(filename)) { + return; } - /** + const filePath = path.join(this.pluginPath, filename); + const pluginName = path.parse(filename).name; + + try { + const module = await this.importModule(filePath); + if (!this.isValidPluginModule(module)) { + this.logger.logWarn(`[Plugin Adapter] File ${filename} is not a valid plugin (missing plugin methods)`); + return; + } + + const plugin: LoadedPlugin = { + name: pluginName, + pluginPath: this.pluginPath, + entryPath: filePath, + module, + }; + + await this.registerPlugin(plugin); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error loading file plugin ${filename}:`, error); + } + } + + /** * 加载目录插件 */ - private async loadDirectoryPlugin(dirname: string): Promise { - const pluginDir = path.join(this.pluginPath, dirname); + private async loadDirectoryPlugin (dirname: string): Promise { + const pluginDir = path.join(this.pluginPath, dirname); + try { + // 尝试读取 package.json + let packageJson: PluginPackageJson | undefined; + const packageJsonPath = path.join(pluginDir, 'package.json'); + + if (fs.existsSync(packageJsonPath)) { try { - // 尝试读取 package.json - let packageJson: PluginPackageJson | undefined; - const packageJsonPath = path.join(pluginDir, 'package.json'); - - if (fs.existsSync(packageJsonPath)) { - try { - const packageContent = fs.readFileSync(packageJsonPath, 'utf-8'); - packageJson = JSON.parse(packageContent); - } catch (error) { - this.logger.logWarn(`[Plugin Adapter] Invalid package.json in ${dirname}:`, error); - } - } - - // 确定入口文件 - const entryFile = this.findEntryFile(pluginDir, packageJson); - if (!entryFile) { - this.logger.logWarn(`[Plugin Adapter] No valid entry file found for plugin directory: ${dirname}`); - return; - } - - const entryPath = path.join(pluginDir, entryFile); - const module = await this.importModule(entryPath); - - if (!this.isValidPluginModule(module)) { - this.logger.logWarn(`[Plugin Adapter] Directory ${dirname} does not contain a valid plugin`); - return; - } - - const plugin: LoadedPlugin = { - name: packageJson?.name || dirname, - version: packageJson?.version, - pluginPath: pluginDir, - entryPath: entryPath, - packageJson: packageJson, - module: module - }; - - await this.registerPlugin(plugin); + const packageContent = fs.readFileSync(packageJsonPath, 'utf-8'); + packageJson = JSON.parse(packageContent); } catch (error) { - this.logger.logError(`[Plugin Adapter] Error loading directory plugin ${dirname}:`, error); + this.logger.logWarn(`[Plugin Adapter] Invalid package.json in ${dirname}:`, error); } - } + } - /** + // 确定入口文件 + const entryFile = this.findEntryFile(pluginDir, packageJson); + if (!entryFile) { + this.logger.logWarn(`[Plugin Adapter] No valid entry file found for plugin directory: ${dirname}`); + return; + } + + const entryPath = path.join(pluginDir, entryFile); + const module = await this.importModule(entryPath); + + if (!this.isValidPluginModule(module)) { + this.logger.logWarn(`[Plugin Adapter] Directory ${dirname} does not contain a valid plugin`); + return; + } + + const plugin: LoadedPlugin = { + name: packageJson?.name || dirname, + version: packageJson?.version, + pluginPath: pluginDir, + entryPath, + packageJson, + module, + }; + + await this.registerPlugin(plugin); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error loading directory plugin ${dirname}:`, error); + } + } + + /** * 查找插件目录的入口文件 */ - private findEntryFile(pluginDir: string, packageJson?: PluginPackageJson): string | null { - // 优先级:package.json main > 默认文件名 - const possibleEntries = [ - packageJson?.main, - 'index.mjs', - 'index.js', - 'main.mjs', - 'main.js' - ].filter(Boolean) as string[]; + private findEntryFile (pluginDir: string, packageJson?: PluginPackageJson): string | null { + // 优先级:package.json main > 默认文件名 + const possibleEntries = [ + packageJson?.main, + 'index.mjs', + 'index.js', + 'main.mjs', + 'main.js', + ].filter(Boolean) as string[]; - for (const entry of possibleEntries) { - const entryPath = path.join(pluginDir, entry); - if (fs.existsSync(entryPath) && fs.statSync(entryPath).isFile()) { - return entry; - } - } - - return null; + for (const entry of possibleEntries) { + const entryPath = path.join(pluginDir, entry); + if (fs.existsSync(entryPath) && fs.statSync(entryPath).isFile()) { + return entry; + } } - /** + return null; + } + + /** * 检查是否为支持的文件类型 */ - private isSupportedFile(filename: string): boolean { - const ext = path.extname(filename).toLowerCase(); - return ['.mjs', '.js'].includes(ext); - } + private isSupportedFile (filename: string): boolean { + const ext = path.extname(filename).toLowerCase(); + return ['.mjs', '.js'].includes(ext); + } - /** + /** * 动态导入模块 */ - private async importModule(filePath: string): Promise { - const fileUrl = `file://${filePath.replace(/\\/g, '/')}`; - return await import(fileUrl); - } + private async importModule (filePath: string): Promise { + const fileUrl = `file://${filePath.replace(/\\/g, '/')}`; + return await import(fileUrl); + } - /** + /** * 检查模块是否为有效的插件模块 */ - private isValidPluginModule(module: any): module is PluginModule { - return module && typeof module.plugin_init === 'function'; - } + private isValidPluginModule (module: any): module is PluginModule { + return module && typeof module.plugin_init === 'function'; + } - /** + /** * 注册插件 */ - private async registerPlugin(plugin: LoadedPlugin): Promise { - // 检查名称冲突 - if (this.loadedPlugins.has(plugin.name)) { - this.logger.logWarn(`[Plugin Adapter] Plugin name conflict: ${plugin.name}, skipping...`); - return; - } - - this.loadedPlugins.set(plugin.name, plugin); - this.logger.log(`[Plugin Adapter] Registered plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`); - - // 调用插件初始化方法(必须存在) - try { - await plugin.module.plugin_init(this.core, this.obContext, this.actions, this); - this.logger.log(`[Plugin Adapter] Initialized plugin: ${plugin.name}`); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error initializing plugin ${plugin.name}:`, error); - } + private async registerPlugin (plugin: LoadedPlugin): Promise { + // 检查名称冲突 + if (this.loadedPlugins.has(plugin.name)) { + this.logger.logWarn(`[Plugin Adapter] Plugin name conflict: ${plugin.name}, skipping...`); + return; } - /** + this.loadedPlugins.set(plugin.name, plugin); + this.logger.log(`[Plugin Adapter] Registered plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ''}`); + + // 调用插件初始化方法(必须存在) + try { + await plugin.module.plugin_init(this.core, this.obContext, this.actions, this); + this.logger.log(`[Plugin Adapter] Initialized plugin: ${plugin.name}`); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error initializing plugin ${plugin.name}:`, error); + } + } + + /** * 卸载插件 */ - private async unloadPlugin(pluginName: string): Promise { - const plugin = this.loadedPlugins.get(pluginName); - if (!plugin) { - return; - } - - // 调用插件清理方法 - if (typeof plugin.module.plugin_cleanup === 'function') { - try { - await plugin.module.plugin_cleanup(this.core, this.obContext, this.actions, this); - this.logger.log(`[Plugin Adapter] Cleaned up plugin: ${pluginName}`); - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error cleaning up plugin ${pluginName}:`, error); - } - } - - this.loadedPlugins.delete(pluginName); - this.logger.log(`[Plugin Adapter] Unloaded plugin: ${pluginName}`); + private async unloadPlugin (pluginName: string): Promise { + const plugin = this.loadedPlugins.get(pluginName); + if (!plugin) { + return; } - async onEvent(event: T) { - if (!this.isEnable) { - return; - } - - // 遍历所有已加载的插件,调用它们的事件处理方法 - for (const [, plugin] of this.loadedPlugins) { - this.callPluginEventHandler(plugin, event); - } + // 调用插件清理方法 + if (typeof plugin.module.plugin_cleanup === 'function') { + try { + await plugin.module.plugin_cleanup(this.core, this.obContext, this.actions, this); + this.logger.log(`[Plugin Adapter] Cleaned up plugin: ${pluginName}`); + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error cleaning up plugin ${pluginName}:`, error); + } } - /** + this.loadedPlugins.delete(pluginName); + this.logger.log(`[Plugin Adapter] Unloaded plugin: ${pluginName}`); + } + + async onEvent(event: T) { + if (!this.isEnable) { + return; + } + + // 遍历所有已加载的插件,调用它们的事件处理方法 + for (const [, plugin] of this.loadedPlugins) { + this.callPluginEventHandler(plugin, event); + } + } + + /** * 调用插件的事件处理方法 */ - private async callPluginEventHandler(plugin: LoadedPlugin, event: OB11EmitEventContent): Promise { - try { - // 优先使用 plugin_onevent 方法 - if (typeof plugin.module.plugin_onevent === 'function') { - await plugin.module.plugin_onevent(this.name, this.core, this.obContext, event, this.actions, this); - } + private async callPluginEventHandler (plugin: LoadedPlugin, event: OB11EmitEventContent): Promise { + try { + // 优先使用 plugin_onevent 方法 + if (typeof plugin.module.plugin_onevent === 'function') { + await plugin.module.plugin_onevent(this.name, this.core, this.obContext, event, this.actions, this); + } - // 如果是消息事件并且插件有 plugin_onmessage 方法,也调用 - if ((event as any).message_type && typeof plugin.module.plugin_onmessage === 'function') { - await plugin.module.plugin_onmessage(this.name, this.core, this.obContext, event as OB11Message, this.actions, this); - } - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error calling plugin ${plugin.name} event handler:`, error); - } + // 如果是消息事件并且插件有 plugin_onmessage 方法,也调用 + if ((event as any).message_type && typeof plugin.module.plugin_onmessage === 'function') { + await plugin.module.plugin_onmessage(this.name, this.core, this.obContext, event as OB11Message, this.actions, this); + } + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error calling plugin ${plugin.name} event handler:`, error); + } + } + + async open () { + if (this.isEnable) { + return; } - async open() { - if (this.isEnable) { - return; - } + this.logger.log('[Plugin Adapter] Opening plugin adapter...'); + this.isEnable = true; - this.logger.log('[Plugin Adapter] Opening plugin adapter...'); - this.isEnable = true; + // 加载所有插件 + await this.loadPlugins(); - // 加载所有插件 - await this.loadPlugins(); + this.logger.log(`[Plugin Adapter] Plugin adapter opened with ${this.loadedPlugins.size} plugins loaded`); + } - this.logger.log(`[Plugin Adapter] Plugin adapter opened with ${this.loadedPlugins.size} plugins loaded`); + async close () { + if (!this.isEnable) { + return; } - async close() { - if (!this.isEnable) { - return; - } + this.logger.log('[Plugin Adapter] Closing plugin adapter...'); + this.isEnable = false; - this.logger.log('[Plugin Adapter] Closing plugin adapter...'); - this.isEnable = false; - - // 卸载所有插件 - const pluginNames = Array.from(this.loadedPlugins.keys()); - for (const pluginName of pluginNames) { - await this.unloadPlugin(pluginName); - } - - this.logger.log('[Plugin Adapter] Plugin adapter closed'); + // 卸载所有插件 + const pluginNames = Array.from(this.loadedPlugins.keys()); + for (const pluginName of pluginNames) { + await this.unloadPlugin(pluginName); } - async reload() { - this.logger.log('[Plugin Adapter] Reloading plugin adapter...'); + this.logger.log('[Plugin Adapter] Plugin adapter closed'); + } - // 先关闭然后重新打开 - await this.close(); - await this.open(); + async reload () { + this.logger.log('[Plugin Adapter] Reloading plugin adapter...'); - this.logger.log('[Plugin Adapter] Plugin adapter reloaded'); - return OB11NetworkReloadType.Normal; - } + // 先关闭然后重新打开 + await this.close(); + await this.open(); - /** + this.logger.log('[Plugin Adapter] Plugin adapter reloaded'); + return OB11NetworkReloadType.Normal; + } + + /** * 获取已加载的插件列表 */ - public getLoadedPlugins(): LoadedPlugin[] { - return Array.from(this.loadedPlugins.values()); - } + public getLoadedPlugins (): LoadedPlugin[] { + return Array.from(this.loadedPlugins.values()); + } - /** + /** * 获取插件信息 */ - public getPluginInfo(pluginName: string): LoadedPlugin | undefined { - return this.loadedPlugins.get(pluginName); - } + public getPluginInfo (pluginName: string): LoadedPlugin | undefined { + return this.loadedPlugins.get(pluginName); + } - /** + /** * 重载指定插件 */ - public async reloadPlugin(pluginName: string): Promise { - const plugin = this.loadedPlugins.get(pluginName); - if (!plugin) { - this.logger.logWarn(`[Plugin Adapter] Plugin ${pluginName} not found`); - return false; - } + public async reloadPlugin (pluginName: string): Promise { + const plugin = this.loadedPlugins.get(pluginName); + if (!plugin) { + this.logger.logWarn(`[Plugin Adapter] Plugin ${pluginName} not found`); + return false; + } - try { - // 卸载插件 - await this.unloadPlugin(pluginName); + try { + // 卸载插件 + await this.unloadPlugin(pluginName); - // 重新加载插件 - const isDirectory = fs.statSync(plugin.pluginPath).isDirectory() && + // 重新加载插件 + const isDirectory = fs.statSync(plugin.pluginPath).isDirectory() && plugin.pluginPath !== this.pluginPath; - if (isDirectory) { - const dirname = path.basename(plugin.pluginPath); - await this.loadDirectoryPlugin(dirname); - } else { - const filename = path.basename(plugin.entryPath); - await this.loadFilePlugin(filename); - } + if (isDirectory) { + const dirname = path.basename(plugin.pluginPath); + await this.loadDirectoryPlugin(dirname); + } else { + const filename = path.basename(plugin.entryPath); + await this.loadFilePlugin(filename); + } - this.logger.log(`[Plugin Adapter] Plugin ${pluginName} reloaded successfully`); - return true; - } catch (error) { - this.logger.logError(`[Plugin Adapter] Error reloading plugin ${pluginName}:`, error); - return false; - } + this.logger.log(`[Plugin Adapter] Plugin ${pluginName} reloaded successfully`); + return true; + } catch (error) { + this.logger.logError(`[Plugin Adapter] Error reloading plugin ${pluginName}:`, error); + return false; } + } } diff --git a/src/onebot/network/websocket-client.ts b/src/onebot/network/websocket-client.ts index 9d9a750a..7bed8f74 100644 --- a/src/onebot/network/websocket-client.ts +++ b/src/onebot/network/websocket-client.ts @@ -12,194 +12,192 @@ import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; import json5 from 'json5'; export class OB11WebSocketClientAdapter extends IOB11NetworkAdapter { - private connection: WebSocket | null = null; - private heartbeatRef: NodeJS.Timeout | null = null; + private connection: WebSocket | null = null; + private heartbeatRef: NodeJS.Timeout | null = null; - constructor(name: string, config: WebsocketClientConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { - super(name, config, core, obContext, actions); + constructor (name: string, config: WebsocketClientConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap) { + super(name, config, core, obContext, actions); + } + + async onEvent(event: T) { + if (this.connection && this.connection.readyState === WebSocket.OPEN) { + this.connection.send(JSON.stringify(event)); } + } - async onEvent(event: T) { + async open () { + if (this.connection) { + return; + } + if (this.config.heartInterval > 0) { + this.heartbeatRef = setInterval(() => { if (this.connection && this.connection.readyState === WebSocket.OPEN) { - this.connection.send(JSON.stringify(event)); + this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.config.heartInterval, this.core.selfInfo.online ?? true, true))); } + }, this.config.heartInterval); } + this.isEnable = true; + try { + await this.tryConnect(); + } catch (error) { + this.logger.logError('[OneBot] [WebSocket Client] TryConnect Error , info -> ', error); + } + } - async open() { - if (this.connection) { - return; - } - if (this.config.heartInterval > 0) { - this.heartbeatRef = setInterval(() => { - if (this.connection && this.connection.readyState === WebSocket.OPEN) { - this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.config.heartInterval, this.core.selfInfo.online ?? true, true))); - } - }, this.config.heartInterval); - } - this.isEnable = true; + close () { + if (!this.isEnable) { + this.logger.logDebug('Cannot close a closed WebSocket connection'); + return; + } + this.isEnable = false; + if (this.connection) { + this.connection.close(); + this.connection = null; + } + if (this.heartbeatRef) { + clearInterval(this.heartbeatRef); + this.heartbeatRef = null; + } + } + + private async checkStateAndReply(data: T) { + return new Promise((resolve, reject) => { + if (this.connection && this.connection.readyState === WebSocket.OPEN) { + this.connection.send(JSON.stringify(data)); + resolve(); + } else { + reject(new Error('WebSocket is not open')); + } + }); + } + + private async tryConnect () { + if (!this.connection && this.isEnable) { + let isClosedByError = false; + + this.connection = new WebSocket(this.config.url, { + maxPayload: 1024 * 1024 * 1024, + handshakeTimeout: 2000, + perMessageDeflate: false, + headers: { + 'X-Self-ID': this.core.selfInfo.uin, + Authorization: `Bearer ${this.config.token}`, + 'x-client-role': 'Universal', // 为koishi adpter适配 + 'User-Agent': 'OneBot/11', + }, + + }); + this.connection.on('ping', () => { + this.connection?.pong(); + }); + this.connection.on('pong', () => { + // this.logger.logDebug('[OneBot] [WebSocket Client] 收到pong'); + }); + this.connection.on('open', () => { try { - await this.tryConnect(); - } catch (error) { - this.logger.logError('[OneBot] [WebSocket Client] TryConnect Error , info -> ', error); - - } - - } - - close() { - if (!this.isEnable) { - this.logger.logDebug('Cannot close a closed WebSocket connection'); - return; - } - this.isEnable = false; - if (this.connection) { - this.connection.close(); - this.connection = null; - } - if (this.heartbeatRef) { - clearInterval(this.heartbeatRef); - this.heartbeatRef = null; - } - } - - private async checkStateAndReply(data: T) { - return new Promise((resolve, reject) => { - if (this.connection && this.connection.readyState === WebSocket.OPEN) { - this.connection.send(JSON.stringify(data)); - resolve(); - } else { - reject(new Error('WebSocket is not open')); - } - }); - } - - private async tryConnect() { - if (!this.connection && this.isEnable) { - let isClosedByError = false; - - this.connection = new WebSocket(this.config.url, { - maxPayload: 1024 * 1024 * 1024, - handshakeTimeout: 2000, - perMessageDeflate: false, - headers: { - 'X-Self-ID': this.core.selfInfo.uin, - 'Authorization': `Bearer ${this.config.token}`, - 'x-client-role': 'Universal', // 为koishi adpter适配 - 'User-Agent': 'OneBot/11', - }, - - }); - this.connection.on('ping', () => { - this.connection?.pong(); - }); - this.connection.on('pong', () => { - //this.logger.logDebug('[OneBot] [WebSocket Client] 收到pong'); - }); - this.connection.on('open', () => { - try { - this.connectEvent(this.core).catch(e => this.logger.logError('[OneBot] [WebSocket Client] 发送连接生命周期失败', e)); - } catch (e) { - this.logger.logError('[OneBot] [WebSocket Client] 发送连接生命周期失败', e); - } - - }); - this.connection.on('message', (data) => { - this.handleMessage(data); - }); - this.connection.once('close', () => { - if (!isClosedByError) { - this.logger.logError(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接意外关闭`); - this.logger.logError(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`); - if (this.isEnable) { - this.connection = null; - setTimeout(() => this.tryConnect(), this.config.reconnectInterval); - } - } - }); - this.connection.on('error', (err) => { - isClosedByError = true; - this.logger.logError(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接错误`, err); - this.logger.logError(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`); - if (this.isEnable) { - this.connection = null; - setTimeout(() => this.tryConnect(), this.config.reconnectInterval); - } - }); - } - } - - async connectEvent(core: NapCatCore) { - try { - await this.checkStateAndReply(new OB11LifeCycleEvent(core, LifeCycleSubType.CONNECT)); + this.connectEvent(this.core).catch(e => this.logger.logError('[OneBot] [WebSocket Client] 发送连接生命周期失败', e)); } catch (e) { - this.logger.logError('[OneBot] [WebSocket Client] 发送生命周期失败', e); + this.logger.logError('[OneBot] [WebSocket Client] 发送连接生命周期失败', e); } + }); + this.connection.on('message', (data) => { + this.handleMessage(data); + }); + this.connection.once('close', () => { + if (!isClosedByError) { + this.logger.logError(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接意外关闭`); + this.logger.logError(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`); + if (this.isEnable) { + this.connection = null; + setTimeout(() => this.tryConnect(), this.config.reconnectInterval); + } + } + }); + this.connection.on('error', (err) => { + isClosedByError = true; + this.logger.logError(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接错误`, err); + this.logger.logError(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`); + if (this.isEnable) { + this.connection = null; + setTimeout(() => this.tryConnect(), this.config.reconnectInterval); + } + }); } - private async handleMessage(message: RawData) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let receiveData: { action: typeof ActionName[keyof typeof ActionName], params?: any, echo?: any } = { action: ActionName.Unknown, params: {} }; - let echo = undefined; + } - try { - receiveData = json5.parse(message.toString()); - echo = receiveData.echo; - this.logger.logDebug('[OneBot] [WebSocket Client] 收到正向Websocket消息', receiveData); - } catch { - await this.checkStateAndReply(OB11Response.error('json解析失败,请检查数据格式', 1400, echo)); - return; - } - receiveData.params = (receiveData?.params) ? receiveData.params : {};// 兼容类型验证 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const action = this.actions.get(receiveData.action as any); - if (!action) { - this.logger.logError('[OneBot] [WebSocket Client] 发生错误', '不支持的Api ' + receiveData.action); - await this.checkStateAndReply(OB11Response.error('不支持的Api ' + receiveData.action, 1404, echo)); - return; - } - const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { - send: async (data: object) => { - await this.checkStateAndReply({ ...OB11Response.ok(data, echo ?? '', true) }); - } - }); - await this.checkStateAndReply({ ...retdata }); + async connectEvent (core: NapCatCore) { + try { + await this.checkStateAndReply(new OB11LifeCycleEvent(core, LifeCycleSubType.CONNECT)); + } catch (e) { + this.logger.logError('[OneBot] [WebSocket Client] 发送生命周期失败', e); } - async reload(newConfig: WebsocketClientConfig) { - const wasEnabled = this.isEnable; - const oldUrl = this.config.url; - const oldHeartInterval = this.config.heartInterval; - this.config = newConfig; + } - if (newConfig.enable && !wasEnabled) { - this.open(); - return OB11NetworkReloadType.NetWorkOpen; - } else if (!newConfig.enable && wasEnabled) { - this.close(); - return OB11NetworkReloadType.NetWorkClose; - } + private async handleMessage (message: RawData) { + let receiveData: { action: typeof ActionName[keyof typeof ActionName], params?: any, echo?: any } = { action: ActionName.Unknown, params: {} }; + let echo; - if (oldUrl !== newConfig.url) { - this.close(); - if (newConfig.enable) { - this.open(); - } - return OB11NetworkReloadType.NetWorkReload; - } - - if (oldHeartInterval !== newConfig.heartInterval) { - if (this.heartbeatRef) { - clearInterval(this.heartbeatRef); - this.heartbeatRef = null; - } - if (newConfig.heartInterval > 0 && this.isEnable) { - this.heartbeatRef = setInterval(() => { - if (this.connection && this.connection.readyState === WebSocket.OPEN) { - this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, newConfig.heartInterval, this.core.selfInfo.online ?? true, true))); - } - }, newConfig.heartInterval); - } - return OB11NetworkReloadType.NetWorkReload; - } - - return OB11NetworkReloadType.Normal; + try { + receiveData = json5.parse(message.toString()); + echo = receiveData.echo; + this.logger.logDebug('[OneBot] [WebSocket Client] 收到正向Websocket消息', receiveData); + } catch { + await this.checkStateAndReply(OB11Response.error('json解析失败,请检查数据格式', 1400, echo)); + return; } + receiveData.params = (receiveData?.params) ? receiveData.params : {};// 兼容类型验证 + + const action = this.actions.get(receiveData.action as any); + if (!action) { + this.logger.logError('[OneBot] [WebSocket Client] 发生错误', '不支持的Api ' + receiveData.action); + await this.checkStateAndReply(OB11Response.error('不支持的Api ' + receiveData.action, 1404, echo)); + return; + } + const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { + send: async (data: object) => { + await this.checkStateAndReply({ ...OB11Response.ok(data, echo ?? '', true) }); + }, + }); + await this.checkStateAndReply({ ...retdata }); + } + + async reload (newConfig: WebsocketClientConfig) { + const wasEnabled = this.isEnable; + const oldUrl = this.config.url; + const oldHeartInterval = this.config.heartInterval; + this.config = newConfig; + + if (newConfig.enable && !wasEnabled) { + this.open(); + return OB11NetworkReloadType.NetWorkOpen; + } else if (!newConfig.enable && wasEnabled) { + this.close(); + return OB11NetworkReloadType.NetWorkClose; + } + + if (oldUrl !== newConfig.url) { + this.close(); + if (newConfig.enable) { + this.open(); + } + return OB11NetworkReloadType.NetWorkReload; + } + + if (oldHeartInterval !== newConfig.heartInterval) { + if (this.heartbeatRef) { + clearInterval(this.heartbeatRef); + this.heartbeatRef = null; + } + if (newConfig.heartInterval > 0 && this.isEnable) { + this.heartbeatRef = setInterval(() => { + if (this.connection && this.connection.readyState === WebSocket.OPEN) { + this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, newConfig.heartInterval, this.core.selfInfo.online ?? true, true))); + } + }, newConfig.heartInterval); + } + return OB11NetworkReloadType.NetWorkReload; + } + + return OB11NetworkReloadType.Normal; + } } diff --git a/src/onebot/network/websocket-server.ts b/src/onebot/network/websocket-server.ts index 7fc6d2b4..f4b5a859 100644 --- a/src/onebot/network/websocket-server.ts +++ b/src/onebot/network/websocket-server.ts @@ -15,239 +15,235 @@ import { IOB11NetworkAdapter } from '@/onebot/network/adapter'; import json5 from 'json5'; export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter { - wsServer?: WebSocketServer; - wsClients: WebSocket[] = []; - wsClientsMutex = new Mutex(); - private heartbeatIntervalId: NodeJS.Timeout | null = null; - wsClientWithEvent: WebSocket[] = []; + wsServer?: WebSocketServer; + wsClients: WebSocket[] = []; + wsClientsMutex = new Mutex(); + private heartbeatIntervalId: NodeJS.Timeout | null = null; + wsClientWithEvent: WebSocket[] = []; - constructor( - name: string, config: WebsocketServerConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap - ) { - super(name, config, core, obContext, actions); - this.wsServer = new WebSocketServer({ - port: this.config.port, - host: this.config.host === '0.0.0.0' ? '' : this.config.host, - maxPayload: 1024 * 1024 * 1024, - }); - this.createServer(this.wsServer); + constructor ( + name: string, config: WebsocketServerConfig, core: NapCatCore, obContext: NapCatOneBot11Adapter, actions: ActionMap + ) { + super(name, config, core, obContext, actions); + this.wsServer = new WebSocketServer({ + port: this.config.port, + host: this.config.host === '0.0.0.0' ? '' : this.config.host, + maxPayload: 1024 * 1024 * 1024, + }); + this.createServer(this.wsServer); + } - } - createServer(newServer: WebSocketServer) { - newServer.on('connection', async (wsClient, wsReq) => { - if (!this.isEnable) { - wsClient.close(); - return; - } - // 鉴权 close 不会立刻销毁 当前返回可避免挂载message事件 close 并未立刻关闭 而是存在timer操作后关闭 - // 引发高危漏洞 - if (!this.authorize(this.config.token, wsClient, wsReq)) { - return; - } - const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url; - const isApiConnect = paramUrl === '/api' || paramUrl === '/api/'; - if (!isApiConnect) { - this.connectEvent(this.core, wsClient); - } - - wsClient.on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Client Error:', err.message)); - wsClient.on('message', (message) => { - this.handleMessage(wsClient, message).then().catch(e => this.logger.logError(e)); - }); - wsClient.on('ping', () => { - wsClient.pong(); - }); - wsClient.on('pong', () => { - //this.logger.logDebug('[OneBot] [WebSocket Server] Pong received'); - }); - wsClient.once('close', () => { - this.wsClientsMutex.runExclusive(async () => { - const NormolIndex = this.wsClients.indexOf(wsClient); - if (NormolIndex !== -1) { - this.wsClients.splice(NormolIndex, 1); - } - const EventIndex = this.wsClientWithEvent.indexOf(wsClient); - if (EventIndex !== -1) { - this.wsClientWithEvent.splice(EventIndex, 1); - } - - }); - }); - await this.wsClientsMutex.runExclusive(async () => { - if (!isApiConnect) { - this.wsClientWithEvent.push(wsClient); - } - this.wsClients.push(wsClient); - }); - }).on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Server Error:', err.message)); - } - connectEvent(core: NapCatCore, wsClient: WebSocket) { - try { - this.checkStateAndReply(new OB11LifeCycleEvent(core, LifeCycleSubType.CONNECT), wsClient).catch(e => this.logger.logError('[OneBot] [WebSocket Server] 发送生命周期失败', e)); - } catch (e) { - this.logger.logError('[OneBot] [WebSocket Server] 发送生命周期失败', e); - } - } - - async onEvent(event: T) { - this.wsClientsMutex.runExclusive(async () => { - let promises = this.wsClientWithEvent.map((wsClient) => { - return new Promise((resolve, reject) => { - if (wsClient.readyState === WebSocket.OPEN) { - wsClient.send(JSON.stringify(event)); - resolve(); - } else { - reject(new Error('WebSocket is not open')); - } - }); - }); - await Promise.allSettled(promises); - }); - } - - open() { - if (this.isEnable) { - this.logger.logError('[OneBot] [WebSocket Server] Cannot open a opened WebSocket server'); - return; - } - const addressInfo = this.wsServer?.address(); - this.logger.log('[OneBot] [WebSocket Server] Server Started', typeof (addressInfo) === 'string' ? addressInfo : addressInfo?.address + ':' + addressInfo?.port); - - this.isEnable = true; - if (this.config.heartInterval > 0) { - this.registerHeartBeat(); - } - - } - - async close() { - this.isEnable = false; - this.wsServer?.close((err) => { - if (err) { - this.logger.logError('[OneBot] [WebSocket Server] Error closing server:', err.message); - } else { - this.logger.log('[OneBot] [WebSocket Server] Server Closed'); - } - - }); - if (this.heartbeatIntervalId) { - clearInterval(this.heartbeatIntervalId); - this.heartbeatIntervalId = null; - } - await this.wsClientsMutex.runExclusive(async () => { - this.wsClients.forEach((wsClient) => { - wsClient.close(); - }); - this.wsClients = []; - this.wsClientWithEvent = []; - }); - } - - private registerHeartBeat() { - this.heartbeatIntervalId = setInterval(() => { - this.wsClientsMutex.runExclusive(async () => { - this.wsClientWithEvent.forEach((wsClient) => { - if (wsClient.readyState === WebSocket.OPEN) { - wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.config.heartInterval, this.core.selfInfo.online ?? true, true))); - } - }); - }); - }, this.config.heartInterval); - } - - private authorize(token: string | undefined, wsClient: WebSocket, wsReq: IncomingMessage) { - if (!token || token.length == 0) return true;//客户端未设置密钥 - const QueryClientToken = urlParse.parse(wsReq?.url || '', true).query['access_token']; - const HeaderClientToken = wsReq.headers.authorization?.split('Bearer ').pop() || ''; - const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken; - if (ClientToken === token) { - return true; - } - wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败'))); + createServer (newServer: WebSocketServer) { + newServer.on('connection', async (wsClient, wsReq) => { + if (!this.isEnable) { wsClient.close(); - return false; - } + return; + } + // 鉴权 close 不会立刻销毁 当前返回可避免挂载message事件 close 并未立刻关闭 而是存在timer操作后关闭 + // 引发高危漏洞 + if (!this.authorize(this.config.token, wsClient, wsReq)) { + return; + } + const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url; + const isApiConnect = paramUrl === '/api' || paramUrl === '/api/'; + if (!isApiConnect) { + this.connectEvent(this.core, wsClient); + } - private async checkStateAndReply(data: T, wsClient: WebSocket) { - return await new Promise((resolve, reject) => { - if (wsClient.readyState === WebSocket.OPEN) { - wsClient.send(JSON.stringify(data)); - resolve(); - } else { - reject(new Error('WebSocket is not open')); - } + wsClient.on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Client Error:', err.message)); + wsClient.on('message', (message) => { + this.handleMessage(wsClient, message).then().catch(e => this.logger.logError(e)); + }); + wsClient.on('ping', () => { + wsClient.pong(); + }); + wsClient.on('pong', () => { + // this.logger.logDebug('[OneBot] [WebSocket Server] Pong received'); + }); + wsClient.once('close', () => { + this.wsClientsMutex.runExclusive(async () => { + const NormolIndex = this.wsClients.indexOf(wsClient); + if (NormolIndex !== -1) { + this.wsClients.splice(NormolIndex, 1); + } + const EventIndex = this.wsClientWithEvent.indexOf(wsClient); + if (EventIndex !== -1) { + this.wsClientWithEvent.splice(EventIndex, 1); + } }); - } + }); + await this.wsClientsMutex.runExclusive(async () => { + if (!isApiConnect) { + this.wsClientWithEvent.push(wsClient); + } + this.wsClients.push(wsClient); + }); + }).on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Server Error:', err.message)); + } - private async handleMessage(wsClient: WebSocket, message: RawData) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let receiveData: { action: typeof ActionName[keyof typeof ActionName], params?: any, echo?: any } = { action: ActionName.Unknown, params: {} }; - let echo = undefined; - try { - receiveData = json5.parse(message.toString()); - echo = receiveData.echo; - //this.logger.logDebug('收到正向Websocket消息', receiveData); - } catch { - await this.checkStateAndReply(OB11Response.error('json解析失败,请检查数据格式', 1400, echo), wsClient); - return; - } - receiveData.params = (receiveData?.params) ? receiveData.params : {};//兼容类型验证 不然类型校验爆炸 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const action = this.actions.get(receiveData.action as any); - if (!action) { - this.logger.logError('[OneBot] [WebSocket Client] 发生错误', '不支持的API ' + receiveData.action); - await this.checkStateAndReply(OB11Response.error('不支持的API ' + receiveData.action, 1404, echo), wsClient); - return; - } - const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { - send: async (data: object) => { - await this.checkStateAndReply({ ...OB11Response.ok(data, echo ?? '', true) }, wsClient); - } + connectEvent (core: NapCatCore, wsClient: WebSocket) { + try { + this.checkStateAndReply(new OB11LifeCycleEvent(core, LifeCycleSubType.CONNECT), wsClient).catch(e => this.logger.logError('[OneBot] [WebSocket Server] 发送生命周期失败', e)); + } catch (e) { + this.logger.logError('[OneBot] [WebSocket Server] 发送生命周期失败', e); + } + } + + async onEvent(event: T) { + this.wsClientsMutex.runExclusive(async () => { + const promises = this.wsClientWithEvent.map((wsClient) => { + return new Promise((resolve, reject) => { + if (wsClient.readyState === WebSocket.OPEN) { + wsClient.send(JSON.stringify(event)); + resolve(); + } else { + reject(new Error('WebSocket is not open')); + } }); - await this.checkStateAndReply({ ...retdata }, wsClient); + }); + await Promise.allSettled(promises); + }); + } + + open () { + if (this.isEnable) { + this.logger.logError('[OneBot] [WebSocket Server] Cannot open a opened WebSocket server'); + return; + } + const addressInfo = this.wsServer?.address(); + this.logger.log('[OneBot] [WebSocket Server] Server Started', typeof (addressInfo) === 'string' ? addressInfo : addressInfo?.address + ':' + addressInfo?.port); + + this.isEnable = true; + if (this.config.heartInterval > 0) { + this.registerHeartBeat(); + } + } + + async close () { + this.isEnable = false; + this.wsServer?.close((err) => { + if (err) { + this.logger.logError('[OneBot] [WebSocket Server] Error closing server:', err.message); + } else { + this.logger.log('[OneBot] [WebSocket Server] Server Closed'); + } + }); + if (this.heartbeatIntervalId) { + clearInterval(this.heartbeatIntervalId); + this.heartbeatIntervalId = null; + } + await this.wsClientsMutex.runExclusive(async () => { + this.wsClients.forEach((wsClient) => { + wsClient.close(); + }); + this.wsClients = []; + this.wsClientWithEvent = []; + }); + } + + private registerHeartBeat () { + this.heartbeatIntervalId = setInterval(() => { + this.wsClientsMutex.runExclusive(async () => { + this.wsClientWithEvent.forEach((wsClient) => { + if (wsClient.readyState === WebSocket.OPEN) { + wsClient.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.config.heartInterval, this.core.selfInfo.online ?? true, true))); + } + }); + }); + }, this.config.heartInterval); + } + + private authorize (token: string | undefined, wsClient: WebSocket, wsReq: IncomingMessage) { + if (!token || token.length == 0) return true;// 客户端未设置密钥 + const QueryClientToken = urlParse.parse(wsReq?.url || '', true).query['access_token']; + const HeaderClientToken = wsReq.headers.authorization?.split('Bearer ').pop() || ''; + const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken; + if (ClientToken === token) { + return true; + } + wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败'))); + wsClient.close(); + return false; + } + + private async checkStateAndReply(data: T, wsClient: WebSocket) { + return await new Promise((resolve, reject) => { + if (wsClient.readyState === WebSocket.OPEN) { + wsClient.send(JSON.stringify(data)); + resolve(); + } else { + reject(new Error('WebSocket is not open')); + } + }); + } + + private async handleMessage (wsClient: WebSocket, message: RawData) { + let receiveData: { action: typeof ActionName[keyof typeof ActionName], params?: any, echo?: any } = { action: ActionName.Unknown, params: {} }; + let echo; + try { + receiveData = json5.parse(message.toString()); + echo = receiveData.echo; + // this.logger.logDebug('收到正向Websocket消息', receiveData); + } catch { + await this.checkStateAndReply(OB11Response.error('json解析失败,请检查数据格式', 1400, echo), wsClient); + return; + } + receiveData.params = (receiveData?.params) ? receiveData.params : {};// 兼容类型验证 不然类型校验爆炸 + + const action = this.actions.get(receiveData.action as any); + if (!action) { + this.logger.logError('[OneBot] [WebSocket Client] 发生错误', '不支持的API ' + receiveData.action); + await this.checkStateAndReply(OB11Response.error('不支持的API ' + receiveData.action, 1404, echo), wsClient); + return; + } + const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name, this.config, { + send: async (data: object) => { + await this.checkStateAndReply({ ...OB11Response.ok(data, echo ?? '', true) }, wsClient); + }, + }); + await this.checkStateAndReply({ ...retdata }, wsClient); + } + + async reload (newConfig: WebsocketServerConfig) { + const wasEnabled = this.isEnable; + const oldPort = this.config.port; + const oldHost = this.config.host; + const oldHeartbeatInterval = this.config.heartInterval; + this.config = newConfig; + + if (newConfig.enable && !wasEnabled) { + this.open(); + return OB11NetworkReloadType.NetWorkOpen; + } else if (!newConfig.enable && wasEnabled) { + this.close(); + return OB11NetworkReloadType.NetWorkClose; } - async reload(newConfig: WebsocketServerConfig) { - const wasEnabled = this.isEnable; - const oldPort = this.config.port; - const oldHost = this.config.host; - const oldHeartbeatInterval = this.config.heartInterval; - this.config = newConfig; - - if (newConfig.enable && !wasEnabled) { - this.open(); - return OB11NetworkReloadType.NetWorkOpen; - } else if (!newConfig.enable && wasEnabled) { - this.close(); - return OB11NetworkReloadType.NetWorkClose; - } - - if (oldPort !== newConfig.port || oldHost !== newConfig.host) { - this.close(); - this.wsServer = new WebSocketServer({ - port: newConfig.port, - host: newConfig.host === '0.0.0.0' ? '' : newConfig.host, - maxPayload: 1024 * 1024 * 1024, - }); - this.createServer(this.wsServer); - if (newConfig.enable) { - this.open(); - } - return OB11NetworkReloadType.NetWorkReload; - } - - if (oldHeartbeatInterval !== newConfig.heartInterval) { - if (this.heartbeatIntervalId) { - clearInterval(this.heartbeatIntervalId); - this.heartbeatIntervalId = null; - } - if (newConfig.heartInterval > 0 && this.isEnable) { - this.registerHeartBeat(); - } - return OB11NetworkReloadType.NetWorkReload; - } - - return OB11NetworkReloadType.Normal; + if (oldPort !== newConfig.port || oldHost !== newConfig.host) { + this.close(); + this.wsServer = new WebSocketServer({ + port: newConfig.port, + host: newConfig.host === '0.0.0.0' ? '' : newConfig.host, + maxPayload: 1024 * 1024 * 1024, + }); + this.createServer(this.wsServer); + if (newConfig.enable) { + this.open(); + } + return OB11NetworkReloadType.NetWorkReload; } + + if (oldHeartbeatInterval !== newConfig.heartInterval) { + if (this.heartbeatIntervalId) { + clearInterval(this.heartbeatIntervalId); + this.heartbeatIntervalId = null; + } + if (newConfig.heartInterval > 0 && this.isEnable) { + this.registerHeartBeat(); + } + return OB11NetworkReloadType.NetWorkReload; + } + + return OB11NetworkReloadType.Normal; + } } - diff --git a/src/onebot/types/data.ts b/src/onebot/types/data.ts index 32e9bbb8..d8e3daab 100644 --- a/src/onebot/types/data.ts +++ b/src/onebot/types/data.ts @@ -1,108 +1,108 @@ export interface OB11User { - birthday_year?: number; // 生日 - birthday_month?: number; // 生日 - birthday_day?: number; // 生日 - phone_num?: string; // 手机号 - email?: string; // 邮箱 - category_id?: number; // 分组ID - user_id: number; // 用户ID - nickname: string; // 昵称 - remark?: string; // 备注 - sex?: OB11UserSex; // 性别 - level?: number; // 等级 - age?: number; // 年龄 - qid?: string; // QID - login_days?: number; // 登录天数 - categoryName?: string; // 分组名称 - categoryId?: number; // 分组ID 999为特别关心 + birthday_year?: number; // 生日 + birthday_month?: number; // 生日 + birthday_day?: number; // 生日 + phone_num?: string; // 手机号 + email?: string; // 邮箱 + category_id?: number; // 分组ID + user_id: number; // 用户ID + nickname: string; // 昵称 + remark?: string; // 备注 + sex?: OB11UserSex; // 性别 + level?: number; // 等级 + age?: number; // 年龄 + qid?: string; // QID + login_days?: number; // 登录天数 + categoryName?: string; // 分组名称 + categoryId?: number; // 分组ID 999为特别关心 } export interface Notify { - request_id: number; - invitor_uin: number; - invitor_nick?: string; - group_id?: number; - group_name?: string; - message?: string; - checked: boolean; - actor: number; - requester_nick?: string; + request_id: number; + invitor_uin: number; + invitor_nick?: string; + group_id?: number; + group_name?: string; + message?: string; + checked: boolean; + actor: number; + requester_nick?: string; } export enum OB11UserSex { - male = 'male', // 男性 - female = 'female', // 女性 - unknown = 'unknown' // 未知 + male = 'male', // 男性 + female = 'female', // 女性 + unknown = 'unknown', // 未知 } export enum OB11GroupMemberRole { - owner = 'owner', // 群主 - admin = 'admin', // 管理员 - member = 'member', // 成员 + owner = 'owner', // 群主 + admin = 'admin', // 管理员 + member = 'member', // 成员 } export interface OB11GroupMember { - group_id: number; // 群ID - user_id: number; // 用户ID - nickname: string; // 昵称 - card?: string; // 群名片 - sex?: OB11UserSex; // 性别 - age?: number; // 年龄 - join_time?: number; // 加入时间 - last_sent_time?: number; // 最后发言时间 - level?: string; // 群等级 - qq_level?: number; // QQ等级 - role?: OB11GroupMemberRole; // 群角色 - title?: string; // 头衔 - area?: string; // 地区 - unfriendly?: boolean; // 是否不友好 - title_expire_time?: number; // 头衔过期时间 - card_changeable?: boolean; // 名片是否可修改 - shut_up_timestamp?: number; // 禁言时间戳 - is_robot?: boolean; // 是否机器人 - qage?: number; // QQ年龄 + group_id: number; // 群ID + user_id: number; // 用户ID + nickname: string; // 昵称 + card?: string; // 群名片 + sex?: OB11UserSex; // 性别 + age?: number; // 年龄 + join_time?: number; // 加入时间 + last_sent_time?: number; // 最后发言时间 + level?: string; // 群等级 + qq_level?: number; // QQ等级 + role?: OB11GroupMemberRole; // 群角色 + title?: string; // 头衔 + area?: string; // 地区 + unfriendly?: boolean; // 是否不友好 + title_expire_time?: number; // 头衔过期时间 + card_changeable?: boolean; // 名片是否可修改 + shut_up_timestamp?: number; // 禁言时间戳 + is_robot?: boolean; // 是否机器人 + qage?: number; // QQ年龄 } export interface OB11Group { - group_all_shut: number; // 群全员禁言 - group_remark: string; // 群备注 - group_id: number; // 群ID - group_name: string; // 群名称 - member_count?: number; // 成员数量 - max_member_count?: number; // 最大成员数量 + group_all_shut: number; // 群全员禁言 + group_remark: string; // 群备注 + group_id: number; // 群ID + group_name: string; // 群名称 + member_count?: number; // 成员数量 + max_member_count?: number; // 最大成员数量 } export interface OB11Sender { - user_id: number; // 用户ID - nickname: string; // 昵称 - sex?: OB11UserSex; // 性别 - age?: number; // 年龄 - card?: string; // 群名片 - level?: string; // 群等级 - role?: OB11GroupMemberRole; // 群角色 + user_id: number; // 用户ID + nickname: string; // 昵称 + sex?: OB11UserSex; // 性别 + age?: number; // 年龄 + card?: string; // 群名片 + level?: string; // 群等级 + role?: OB11GroupMemberRole; // 群角色 } export interface OB11GroupFile { - file_size: number; // 文件大小 GOCQHTTP 群文件Api扩展 - group_id: number; // 群ID - file_id: string; // 文件ID - file_name: string; // 文件名称 - busid: number; // 业务ID - size: number; // 文件大小 - upload_time: number; // 上传时间 - dead_time: number; // 过期时间 - modify_time: number; // 修改时间 - download_times: number; // 下载次数 - uploader: number; // 上传者ID - uploader_name: string; // 上传者名称 + file_size: number; // 文件大小 GOCQHTTP 群文件Api扩展 + group_id: number; // 群ID + file_id: string; // 文件ID + file_name: string; // 文件名称 + busid: number; // 业务ID + size: number; // 文件大小 + upload_time: number; // 上传时间 + dead_time: number; // 过期时间 + modify_time: number; // 修改时间 + download_times: number; // 下载次数 + uploader: number; // 上传者ID + uploader_name: string; // 上传者名称 } export interface OB11GroupFileFolder { - group_id: number; // 群ID - folder_id: string; // 文件夹ID - folder: string; // 文件夹路径 - folder_name: string; // 文件夹名称 - create_time: number; // 创建时间 - creator: number; // 创建者ID - creator_name: string; // 创建者名称 - total_file_count: number; // 文件总数 -} \ No newline at end of file + group_id: number; // 群ID + folder_id: string; // 文件夹ID + folder: string; // 文件夹路径 + folder_name: string; // 文件夹名称 + create_time: number; // 创建时间 + creator: number; // 创建者ID + creator_name: string; // 创建者名称 + total_file_count: number; // 文件总数 +} diff --git a/src/onebot/types/message.ts b/src/onebot/types/message.ts index b743d1b2..ba4613e0 100644 --- a/src/onebot/types/message.ts +++ b/src/onebot/types/message.ts @@ -4,172 +4,172 @@ import { CustomMusicSignPostData, IdMusicSignPostData, PicSubType, RawMessage } // 消息类型枚举 export enum OB11MessageType { - private = 'private', - group = 'group' + private = 'private', + group = 'group', } // 消息接口定义 export interface OB11Message { - real_seq?: string;// 自行扩展 - temp_source?: number; - message_sent_type?: string; - target_id?: number; // 自己发送消息/私聊消息 - self_id?: number; - time: number; - message_id: number; - message_seq: number; // 和message_id一样 - real_id: number; - user_id: number | string; // number - group_id?: number | string; // number - group_name?: string; // string - message_type: 'private' | 'group'; - sub_type?: 'friend' | 'group' | 'normal'; - sender: OB11Sender; - message: OB11MessageData[] | string; - message_format: 'array' | 'string'; - raw_message: string; - font: number; - post_type?: EventType; - raw?: RawMessage; + real_seq?: string;// 自行扩展 + temp_source?: number; + message_sent_type?: string; + target_id?: number; // 自己发送消息/私聊消息 + self_id?: number; + time: number; + message_id: number; + message_seq: number; // 和message_id一样 + real_id: number; + user_id: number | string; // number + group_id?: number | string; // number + group_name?: string; // string + message_type: 'private' | 'group'; + sub_type?: 'friend' | 'group' | 'normal'; + sender: OB11Sender; + message: OB11MessageData[] | string; + message_format: 'array' | 'string'; + raw_message: string; + font: number; + post_type?: EventType; + raw?: RawMessage; } // 合并转发消息接口定义 export interface OB11ForwardMessage extends OB11Message { - content: OB11MessageData[] | string; + content: OB11MessageData[] | string; } // 返回数据接口定义 export interface OB11Return { - status: string; - retcode: number; - data: DataType; - message: string; - echo?: unknown; // ws调用api才有此字段 - wording?: string; // go-cqhttp字段,错误信息 - stream?: 'stream-action' | 'normal-action' ; // 流式返回标记 + status: string; + retcode: number; + data: DataType; + message: string; + echo?: unknown; // ws调用api才有此字段 + wording?: string; // go-cqhttp字段,错误信息 + stream?: 'stream-action' | 'normal-action' ; // 流式返回标记 } // 消息数据类型枚举 export enum OB11MessageDataType { - text = 'text', - image = 'image', - music = 'music', - video = 'video', - voice = 'record', - file = 'file', - at = 'at', - reply = 'reply', - json = 'json', - face = 'face', - mface = 'mface', // 商城表情 - markdown = 'markdown', - node = 'node', // 合并转发消息节点 - forward = 'forward', // 合并转发消息,用于上报 - xml = 'xml', - poke = 'poke', - dice = 'dice', - rps = 'rps', - miniapp = 'miniapp', // json类 - contact = 'contact', - location = 'location' + text = 'text', + image = 'image', + music = 'music', + video = 'video', + voice = 'record', + file = 'file', + at = 'at', + reply = 'reply', + json = 'json', + face = 'face', + mface = 'mface', // 商城表情 + markdown = 'markdown', + node = 'node', // 合并转发消息节点 + forward = 'forward', // 合并转发消息,用于上报 + xml = 'xml', + poke = 'poke', + dice = 'dice', + rps = 'rps', + miniapp = 'miniapp', // json类 + contact = 'contact', + location = 'location', } export interface OB11MessagePoke { - type: OB11MessageDataType.poke; - data: { - type: string; - id: string; - }; + type: OB11MessageDataType.poke; + data: { + type: string; + id: string; + }; } // 商城表情消息接口定义 export interface OB11MessageMFace { - type: OB11MessageDataType.mface; - data: { - emoji_package_id: number; - emoji_id: string; - key: string; - summary: string; - }; + type: OB11MessageDataType.mface; + data: { + emoji_package_id: number; + emoji_id: string; + key: string; + summary: string; + }; } // 纯文本消息接口定义 export interface OB11MessageText { - type: OB11MessageDataType.text; - data: { - text: string; // 纯文本 - }; + type: OB11MessageDataType.text; + data: { + text: string; // 纯文本 + }; } // 联系人消息接口定义 export interface OB11MessageContext { - type: OB11MessageDataType.contact; - data: { - type: 'qq' | 'group'; - id: string; - }; + type: OB11MessageDataType.contact; + data: { + type: 'qq' | 'group'; + id: string; + }; } // 文件消息基础接口定义 export interface OB11MessageFileBase { - data: { - path?: string; - thumb?: string; - name?: string; - file: string; - url?: string; - }; + data: { + path?: string; + thumb?: string; + name?: string; + file: string; + url?: string; + }; } // 图片消息接口定义 export interface OB11MessageImage extends OB11MessageFileBase { - type: OB11MessageDataType.image; - data: OB11MessageFileBase['data'] & { - summary?: string; // 图片摘要 - sub_type?: PicSubType; - }; + type: OB11MessageDataType.image; + data: OB11MessageFileBase['data'] & { + summary?: string; // 图片摘要 + sub_type?: PicSubType; + }; } // 语音消息接口定义 export interface OB11MessageRecord extends OB11MessageFileBase { - type: OB11MessageDataType.voice; + type: OB11MessageDataType.voice; } // 文件消息接口定义 export interface OB11MessageFile extends OB11MessageFileBase { - type: OB11MessageDataType.file; + type: OB11MessageDataType.file; } // 视频消息接口定义 export interface OB11MessageVideo extends OB11MessageFileBase { - type: OB11MessageDataType.video; + type: OB11MessageDataType.video; } // @消息接口定义 export interface OB11MessageAt { - type: OB11MessageDataType.at; - data: { - qq: string; // `${number}` | 'all' - name?: string; - }; + type: OB11MessageDataType.at; + data: { + qq: string; // `${number}` | 'all' + name?: string; + }; } // 回复消息接口定义 export interface OB11MessageReply { - type: OB11MessageDataType.reply; - data: { - id: string; - }; + type: OB11MessageDataType.reply; + data: { + id: string; + }; } // 表情消息接口定义 export interface OB11MessageFace { - type: OB11MessageDataType.face; - data: { - id: string; - resultId?: string; - chainCount?: number; - }; + type: OB11MessageDataType.face; + data: { + id: string; + resultId?: string; + chainCount?: number; + }; } // 混合消息类型定义 @@ -177,79 +177,79 @@ export type OB11MessageMixType = OB11MessageData[] | string | OB11MessageData; // 合并转发消息节点接口定义 export interface OB11MessageNode { - type: OB11MessageDataType.node; - data: { - id?: string; - user_id?: number | string; // number - uin?: number | string; // number, compatible with go-cqhttp - nickname: string; - name?: string; // compatible with go-cqhttp - content: OB11MessageMixType; - source?: string; - news?: { text: string }[]; - summary?: string; - prompt?: string; - time?: string; - }; + type: OB11MessageDataType.node; + data: { + id?: string; + user_id?: number | string; // number + uin?: number | string; // number, compatible with go-cqhttp + nickname: string; + name?: string; // compatible with go-cqhttp + content: OB11MessageMixType; + source?: string; + news?: { text: string }[]; + summary?: string; + prompt?: string; + time?: string; + }; } // 合并转发消息节点纯文本接口定义 export type OB11MessageNodePlain = OB11MessageNode & { - data: { - content?: Array; - message: Array; - }; + data: { + content?: Array; + message: Array; + }; }; // 音乐消息接口定义 export interface OB11MessageIdMusic { - type: OB11MessageDataType.music; - data: IdMusicSignPostData; + type: OB11MessageDataType.music; + data: IdMusicSignPostData; } // 自定义音乐消息接口定义 export interface OB11MessageCustomMusic { - type: OB11MessageDataType.music; - data: Omit & { content?: string }; + type: OB11MessageDataType.music; + data: Omit & { content?: string }; } // JSON消息接口定义 export interface OB11MessageJson { - type: OB11MessageDataType.json; - data: { config?: { token: string }, data: string | object }; + type: OB11MessageDataType.json; + data: { config?: { token: string }, data: string | object }; } // 骰子消息接口定义 export interface OB11MessageDice { - type: OB11MessageDataType.dice; - data: { - result: number /* intended */ | string /* in fact */; - }; + type: OB11MessageDataType.dice; + data: { + result: number /* intended */ | string /* in fact */; + }; } // 猜拳消息接口定义 export interface OB11MessageRPS { - type: OB11MessageDataType.rps; - data: { - result: number | string; - }; + type: OB11MessageDataType.rps; + data: { + result: number | string; + }; } // Markdown消息接口定义 export interface OB11MessageMarkdown { - type: OB11MessageDataType.markdown; - data: { - content: string; - }; + type: OB11MessageDataType.markdown; + data: { + content: string; + }; } // 合并转发消息接口定义 export interface OB11MessageForward { - type: OB11MessageDataType.forward; - data: { - id: string; - content?: OB11Message[]; - }; + type: OB11MessageDataType.forward; + data: { + id: string; + content?: OB11Message[]; + }; } // 消息数据类型定义 @@ -263,22 +263,22 @@ export type OB11MessageData = // 发送消息接口定义 export interface OB11PostSendMsg { - message_type?: 'private' | 'group'; - user_id?: string; - group_id?: string; - message: OB11MessageMixType; - messages?: OB11MessageMixType; - auto_escape?: boolean | string; - source?: string; - news?: { text: string }[]; - summary?: string; - prompt?: string; - time?: string; + message_type?: 'private' | 'group'; + user_id?: string; + group_id?: string; + message: OB11MessageMixType; + messages?: OB11MessageMixType; + auto_escape?: boolean | string; + source?: string; + news?: { text: string }[]; + summary?: string; + prompt?: string; + time?: string; } // 上下文接口定义 export interface OB11PostContext { - message_type?: 'private' | 'group'; - user_id?: string; - group_id?: string; + message_type?: 'private' | 'group'; + user_id?: string; + group_id?: string; } diff --git a/src/onebot/types/quick.ts b/src/onebot/types/quick.ts index 7c0f9dd0..37cc84b5 100644 --- a/src/onebot/types/quick.ts +++ b/src/onebot/types/quick.ts @@ -6,27 +6,27 @@ export type QuickActionEvent = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeE export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent; export interface QuickActionPrivateMessage { - reply?: string; - auto_escape?: boolean; + reply?: string; + auto_escape?: boolean; } export interface QuickActionGroupMessage extends QuickActionPrivateMessage { - // 回复群消息 - at_sender?: boolean; - delete?: boolean; - kick?: boolean; - ban?: boolean; - ban_duration?: number; + // 回复群消息 + at_sender?: boolean; + delete?: boolean; + kick?: boolean; + ban?: boolean; + ban_duration?: number; } export interface QuickActionFriendRequest { - approve?: boolean; - remark?: string; + approve?: boolean; + remark?: string; } export interface QuickActionGroupRequest { - approve?: boolean; - reason?: string; + approve?: boolean; + reason?: string; } export type QuickAction = diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 6f17f54f..aaf76247 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -4,8 +4,8 @@ import { ActionMap } from '@/onebot/action'; import { OB11PluginMangerAdapter } from '@/onebot/network/plugin-manger'; export const plugin_onmessage = async (adapter: string, _core: NapCatCore, _obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap, instance: OB11PluginMangerAdapter) => { - if (message.raw_message === 'ping') { - const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config); - console.log(ret); - } + if (message.raw_message === 'ping') { + const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config); + console.log(ret); + } }; diff --git a/src/pty/index.ts b/src/pty/index.ts index 25c969ce..3265f033 100644 --- a/src/pty/index.ts +++ b/src/pty/index.ts @@ -8,22 +8,22 @@ import path, { dirname } from 'node:path'; let terminalCtor: typeof WindowsTerminal | typeof UnixTerminal; if (process.platform === 'win32') { - terminalCtor = WindowsTerminal; + terminalCtor = WindowsTerminal; } else { - terminalCtor = UnixTerminal; + terminalCtor = UnixTerminal; } -export function spawn(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal { - return new terminalCtor(file, args, opt); +export function spawn (file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal { + return new terminalCtor(file, args, opt); } -export function open(options: IPtyOpenOptions): ITerminal { - return terminalCtor.open(options) as ITerminal; +export function open (options: IPtyOpenOptions): ITerminal { + return terminalCtor.open(options) as ITerminal; +} +export function require_dlopen (modulename: string) { + const module = { exports: {} }; + const import__dirname = dirname(fileURLToPath(import.meta.url)); + process.dlopen(module, path.join(import__dirname, modulename)); + + return module.exports as any; } -export function require_dlopen(modulename: string) { - const module = { exports: {} }; - const import__dirname = dirname(fileURLToPath(import.meta.url)); - process.dlopen(module, path.join(import__dirname, modulename)); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return module.exports as any; -} \ No newline at end of file diff --git a/src/pty/native.d.ts b/src/pty/native.d.ts index 46821efb..973aca57 100644 --- a/src/pty/native.d.ts +++ b/src/pty/native.d.ts @@ -3,52 +3,52 @@ */ interface IConptyNative { - startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess; - connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number }; - resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void; - clear(ptyId: number, useConptyDll: boolean): void; - kill(ptyId: number, useConptyDll: boolean): void; - } - - interface IWinptyNative { - startProcess(file: string, commandLine: string, env: string[], cwd: string, cols: number, rows: number, debug: boolean): IWinptyProcess; - resize(pid: number, cols: number, rows: number): void; - kill(pid: number, innerPid: number): void; - getProcessList(pid: number): number[]; - getExitCode(innerPid: number): number; - } - - interface IUnixNative { - fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, helperPath: string, onExitCallback: (code: number, signal: number) => void): IUnixProcess; - open(cols: number, rows: number): IUnixOpenProcess; - process(fd: number, pty?: string): string; - resize(fd: number, cols: number, rows: number): void; - } - - interface IConptyProcess { - pty: number; - fd: number; - conin: string; - conout: string; - } - - interface IWinptyProcess { - pty: number; - fd: number; - conin: string; - conout: string; - pid: number; - innerPid: number; - } - - interface IUnixProcess { - fd: number; - pid: number; - pty: string; - } - - interface IUnixOpenProcess { - master: number; - slave: number; - pty: string; - } + startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess; + connect(ptyId: number, commandLine: string, cwd: string, env: string[], onExitCallback: (exitCode: number) => void): { pid: number }; + resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void; + clear(ptyId: number, useConptyDll: boolean): void; + kill(ptyId: number, useConptyDll: boolean): void; +} + +interface IWinptyNative { + startProcess(file: string, commandLine: string, env: string[], cwd: string, cols: number, rows: number, debug: boolean): IWinptyProcess; + resize(pid: number, cols: number, rows: number): void; + kill(pid: number, innerPid: number): void; + getProcessList(pid: number): number[]; + getExitCode(innerPid: number): number; +} + +interface IUnixNative { + fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, helperPath: string, onExitCallback: (code: number, signal: number) => void): IUnixProcess; + open(cols: number, rows: number): IUnixOpenProcess; + process(fd: number, pty?: string): string; + resize(fd: number, cols: number, rows: number): void; +} + +interface IConptyProcess { + pty: number; + fd: number; + conin: string; + conout: string; +} + +interface IWinptyProcess { + pty: number; + fd: number; + conin: string; + conout: string; + pid: number; + innerPid: number; +} + +interface IUnixProcess { + fd: number; + pid: number; + pty: string; +} + +interface IUnixOpenProcess { + master: number; + slave: number; + pty: string; +} diff --git a/src/pty/node-pty.d.ts b/src/pty/node-pty.d.ts index ad244456..e6951673 100644 --- a/src/pty/node-pty.d.ts +++ b/src/pty/node-pty.d.ts @@ -4,7 +4,7 @@ */ declare module '@/pty' { - /** + /** * Forks a process as a pseudoterminal. * @param file The file to launch. * @param args The file's arguments as argv (string[]) or in a pre-escaped CommandLine format @@ -15,44 +15,44 @@ declare module '@/pty' { * @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx * @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx */ - export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty; - - export interface IBasePtyForkOptions { - - /** + export function spawn (file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty; + + export interface IBasePtyForkOptions { + + /** * Name of the terminal to be set in environment ($TERM variable). */ - name?: string; - - /** + name?: string; + + /** * Number of intial cols of the pty. */ - cols?: number; - - /** + cols?: number; + + /** * Number of initial rows of the pty. */ - rows?: number; - - /** + rows?: number; + + /** * Working directory to be set for the child program. */ - cwd?: string; - - /** + cwd?: string; + + /** * Environment to be set for the child program. */ - env?: { [key: string]: string | undefined }; - - /** + env?: { [key: string]: string | undefined }; + + /** * String encoding of the underlying pty. * If set, incoming data will be decoded to strings and outgoing strings to bytes applying this encoding. * If unset, incoming data will be delivered as raw bytes (Buffer type). * By default 'utf8' is assumed, to unset it explicitly set it to `null`. */ - encoding?: string | null; - - /** + encoding?: string | null; + + /** * (EXPERIMENTAL) * Whether to enable flow control handling (false by default). If enabled a message of `flowControlPause` * will pause the socket and thus blocking the child program execution due to buffer back pressure. @@ -61,172 +61,171 @@ declare module '@/pty' { * If flow control is enabled the `flowControlPause` and `flowControlResume` messages are not forwarded to * the underlying pseudoterminal. */ - handleFlowControl?: boolean; - - /** + handleFlowControl?: boolean; + + /** * (EXPERIMENTAL) * The string that should pause the pty when `handleFlowControl` is true. Default is XOFF ('\x13'). */ - flowControlPause?: string; - - /** + flowControlPause?: string; + + /** * (EXPERIMENTAL) * The string that should resume the pty when `handleFlowControl` is true. Default is XON ('\x11'). */ - flowControlResume?: string; - } - - export interface IPtyForkOptions extends IBasePtyForkOptions { - /** + flowControlResume?: string; + } + + export interface IPtyForkOptions extends IBasePtyForkOptions { + /** * Security warning: use this option with great caution, * as opened file descriptors with higher privileges might leak to the child program. */ - uid?: number; - gid?: number; - } - - export interface IWindowsPtyForkOptions extends IBasePtyForkOptions { - /** + uid?: number; + gid?: number; + } + + export interface IWindowsPtyForkOptions extends IBasePtyForkOptions { + /** * Whether to use the ConPTY system on Windows. When this is not set, ConPTY will be used when * the Windows build number is >= 18309 (instead of winpty). Note that ConPTY is available from * build 17134 but is too unstable to enable by default. * * This setting does nothing on non-Windows. */ - useConpty?: boolean; - - /** + useConpty?: boolean; + + /** * (EXPERIMENTAL) * * Whether to use the conpty.dll shipped with the node-pty package instead of the one built into * Windows. Defaults to false. */ - useConptyDll?: boolean; - - /** + useConptyDll?: boolean; + + /** * Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty. * @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole */ - conptyInheritCursor?: boolean; - } - - /** + conptyInheritCursor?: boolean; + } + + /** * An interface representing a pseudoterminal, on Windows this is emulated via the winpty library. */ - export interface IPty { - /** + export interface IPty { + /** * The process ID of the outer process. */ - readonly pid: number; - - /** + readonly pid: number; + + /** * The column size in characters. */ - readonly cols: number; - - /** + readonly cols: number; + + /** * The row size in characters. */ - readonly rows: number; - - /** + readonly rows: number; + + /** * The title of the active process. */ - readonly process: string; - - /** + readonly process: string; + + /** * (EXPERIMENTAL) * Whether to handle flow control. Useful to disable/re-enable flow control during runtime. * Use this for binary data that is likely to contain the `flowControlPause` string by accident. */ - handleFlowControl: boolean; - - /** + handleFlowControl: boolean; + + /** * Adds an event listener for when a data event fires. This happens when data is returned from * the pty. * @returns an `IDisposable` to stop listening. */ - readonly onData: IEvent; - - /** + readonly onData: IEvent; + + /** * Adds an event listener for when an exit event fires. This happens when the pty exits. * @returns an `IDisposable` to stop listening. */ - readonly onExit: IEvent<{ exitCode: number, signal?: number }>; - - /** + readonly onExit: IEvent<{ exitCode: number, signal?: number }>; + + /** * Resizes the dimensions of the pty. * @param columns The number of columns to use. * @param rows The number of rows to use. */ - resize(columns: number, rows: number): void; - - // Re-added this interface as homebridge-config-ui-x leverages it https://github.com/microsoft/node-pty/issues/282 - - /** + resize(columns: number, rows: number): void; + + // Re-added this interface as homebridge-config-ui-x leverages it https://github.com/microsoft/node-pty/issues/282 + + /** * Adds a listener to the data event, fired when data is returned from the pty. * @param event The name of the event. * @param listener The callback function. * @deprecated Use IPty.onData */ - on(event: 'data', listener: (data: string) => void): void; - - /** + on(event: 'data', listener: (data: string) => void): void; + + /** * Adds a listener to the exit event, fired when the pty exits. * @param event The name of the event. * @param listener The callback function, exitCode is the exit code of the process and signal is * the signal that triggered the exit. signal is not supported on Windows. * @deprecated Use IPty.onExit */ - on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void; - - /** + on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void; + + /** * Clears the pty's internal representation of its buffer. This is a no-op * unless on Windows/ConPTY. This is useful if the buffer is cleared on the * frontend in order to synchronize state with the backend to avoid ConPTY * possibly reprinting the screen. */ - clear(): void; - - /** + clear(): void; + + /** * Writes data to the pty. * @param data The data to write. */ - write(data: string): void; - - /** + write(data: string): void; + + /** * Kills the pty. * @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on * Windows. * @throws Will throw when signal is used on Windows. */ - kill(signal?: string): void; - - /** + kill(signal?: string): void; + + /** * Pauses the pty for customizable flow control. */ - pause(): void; - - /** + pause(): void; + + /** * Resumes the pty for customizable flow control. */ - resume(): void; - } - - /** + resume(): void; + } + + /** * An object that can be disposed via a dispose function. */ - export interface IDisposable { - dispose(): void; - } - - /** + export interface IDisposable { + dispose(): void; + } + + /** * An event that can be listened to. * @returns an `IDisposable` to stop listening. */ - export interface IEvent { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (listener: (e: T) => any): IDisposable; - } + export interface IEvent { + + (listener: (e: T) => any): IDisposable; } - \ No newline at end of file +} diff --git a/src/pty/prebuild-loader.ts b/src/pty/prebuild-loader.ts index 96eec18f..5a5a1ed6 100644 --- a/src/pty/prebuild-loader.ts +++ b/src/pty/prebuild-loader.ts @@ -1,11 +1,10 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { require_dlopen } from '.'; -export function pty_loader() { - let pty: any; - try { - pty = require_dlopen('./pty/' + process.platform + '.' + process.arch + '/pty.node'); - } catch { - pty = undefined; - } - return pty; -}; +export function pty_loader () { + let pty: any; + try { + pty = require_dlopen('./pty/' + process.platform + '.' + process.arch + '/pty.node'); + } catch { + pty = undefined; + } + return pty; +} diff --git a/src/pty/unixTerminal.ts b/src/pty/unixTerminal.ts index 3e89dadc..50290ba9 100644 --- a/src/pty/unixTerminal.ts +++ b/src/pty/unixTerminal.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable no-undef */ /** * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License) * Copyright (c) 2016, Daniel Imms (MIT License). @@ -28,272 +26,272 @@ const DEFAULT_NAME = 'xterm'; const DESTROY_SOCKET_TIMEOUT_MS = 200; export class UnixTerminal extends Terminal { - protected override _fd: number; - protected override _pty: string; + protected override _fd: number; + protected override _pty: string; - protected override _file: string; - protected override _name: string; + protected override _file: string; + protected override _name: string; - protected override _readable: boolean; - protected override _writable: boolean; + protected override _readable: boolean; + protected override _writable: boolean; - private _boundClose: boolean = false; - private _emittedClose: boolean = false; - private _master: net.Socket | undefined; - private _slave: net.Socket | undefined; + private _boundClose: boolean = false; + private _emittedClose: boolean = false; + private _master: net.Socket | undefined; + private _slave: net.Socket | undefined; - public get master(): net.Socket | undefined { return this._master; } - public get slave(): net.Socket | undefined { return this._slave; } + public get master (): net.Socket | undefined { return this._master; } + public get slave (): net.Socket | undefined { return this._slave; } - constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) { - super(opt); + constructor (file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) { + super(opt); - if (typeof args === 'string') { - throw new Error('args as a string is not supported on unix.'); - } - - // Initialize arguments - args = args || []; - file = file || DEFAULT_FILE; - opt = opt || {}; - opt.env = opt.env || process.env; - - this._cols = opt.cols || DEFAULT_COLS; - this._rows = opt.rows || DEFAULT_ROWS; - const uid = opt.uid ?? -1; - const gid = opt.gid ?? -1; - const env: IProcessEnv = assign({}, opt.env); - - if (opt.env === process.env) { - this._sanitizeEnv(env); - } - - const cwd = opt.cwd || process.cwd(); - env['PWD'] = cwd; - const name = opt.name || env['TERM'] || DEFAULT_NAME; - env['TERM'] = name; - const parsedEnv = this._parseEnv(env); - - const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); - - const onexit = (code: number, signal: number): void => { - // XXX Sometimes a data event is emitted after exit. Wait til socket is - // destroyed. - if (!this._emittedClose) { - if (this._boundClose) { - return; - } - this._boundClose = true; - // From macOS High Sierra 10.13.2 sometimes the socket never gets - // closed. A timeout is applied here to avoid the terminal never being - // destroyed when this occurs. - let timeout: NodeJS.Timeout | null = setTimeout(() => { - timeout = null; - // Destroying the socket now will cause the close event to fire - this._socket.destroy(); - }, DESTROY_SOCKET_TIMEOUT_MS); - this.once('close', () => { - if (timeout !== null) { - clearTimeout(timeout); - } - this.emit('exit', code, signal); - }); - return; - } - this.emit('exit', code, signal); - }; - - // fork - const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit); - - this._socket = new tty.ReadStream(term.fd); - if (encoding !== null) { - this._socket.setEncoding(encoding as BufferEncoding); - } - - // setup - this._socket.on('error', (err: any) => { - // NOTE: fs.ReadStream gets EAGAIN twice at first: - if (err.code) { - if (~err.code.indexOf('EAGAIN')) { - return; - } - } - - // close - this._close(); - // EIO on exit from fs.ReadStream: - if (!this._emittedClose) { - this._emittedClose = true; - this.emit('close'); - } - - // EIO, happens when someone closes our child process: the only process in - // the terminal. - // node < 0.6.14: errno 5 - // node >= 0.6.14: read EIO - if (err.code) { - if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) { - return; - } - } - - // throw anything else - if (this.listeners('error').length < 2) { - throw err; - } - }); - - this._pid = term.pid; - this._fd = term.fd; - this._pty = term.pty; - - this._file = file; - this._name = name; - - this._readable = true; - this._writable = true; - - this._socket.on('close', () => { - if (this._emittedClose) { - return; - } - this._emittedClose = true; - this._close(); - this.emit('close'); - }); - - this._forwardEvents(); + if (typeof args === 'string') { + throw new Error('args as a string is not supported on unix.'); } - protected _write(data: string): void { - this._socket.write(data); + // Initialize arguments + args = args || []; + file = file || DEFAULT_FILE; + opt = opt || {}; + opt.env = opt.env || process.env; + + this._cols = opt.cols || DEFAULT_COLS; + this._rows = opt.rows || DEFAULT_ROWS; + const uid = opt.uid ?? -1; + const gid = opt.gid ?? -1; + const env: IProcessEnv = assign({}, opt.env); + + if (opt.env === process.env) { + this._sanitizeEnv(env); } - /* Accessors */ - get fd(): number { return this._fd; } - get ptsName(): string { return this._pty; } + const cwd = opt.cwd || process.cwd(); + env['PWD'] = cwd; + const name = opt.name || env['TERM'] || DEFAULT_NAME; + env['TERM'] = name; + const parsedEnv = this._parseEnv(env); - /** + const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); + + const onexit = (code: number, signal: number): void => { + // XXX Sometimes a data event is emitted after exit. Wait til socket is + // destroyed. + if (!this._emittedClose) { + if (this._boundClose) { + return; + } + this._boundClose = true; + // From macOS High Sierra 10.13.2 sometimes the socket never gets + // closed. A timeout is applied here to avoid the terminal never being + // destroyed when this occurs. + let timeout: NodeJS.Timeout | null = setTimeout(() => { + timeout = null; + // Destroying the socket now will cause the close event to fire + this._socket.destroy(); + }, DESTROY_SOCKET_TIMEOUT_MS); + this.once('close', () => { + if (timeout !== null) { + clearTimeout(timeout); + } + this.emit('exit', code, signal); + }); + return; + } + this.emit('exit', code, signal); + }; + + // fork + const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit); + + this._socket = new tty.ReadStream(term.fd); + if (encoding !== null) { + this._socket.setEncoding(encoding as BufferEncoding); + } + + // setup + this._socket.on('error', (err: any) => { + // NOTE: fs.ReadStream gets EAGAIN twice at first: + if (err.code) { + if (~err.code.indexOf('EAGAIN')) { + return; + } + } + + // close + this._close(); + // EIO on exit from fs.ReadStream: + if (!this._emittedClose) { + this._emittedClose = true; + this.emit('close'); + } + + // EIO, happens when someone closes our child process: the only process in + // the terminal. + // node < 0.6.14: errno 5 + // node >= 0.6.14: read EIO + if (err.code) { + if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) { + return; + } + } + + // throw anything else + if (this.listeners('error').length < 2) { + throw err; + } + }); + + this._pid = term.pid; + this._fd = term.fd; + this._pty = term.pty; + + this._file = file; + this._name = name; + + this._readable = true; + this._writable = true; + + this._socket.on('close', () => { + if (this._emittedClose) { + return; + } + this._emittedClose = true; + this._close(); + this.emit('close'); + }); + + this._forwardEvents(); + } + + protected _write (data: string): void { + this._socket.write(data); + } + + /* Accessors */ + get fd (): number { return this._fd; } + get ptsName (): string { return this._pty; } + + /** * openpty */ - public static open(opt: IPtyOpenOptions): UnixTerminal { - const self: UnixTerminal = Object.create(UnixTerminal.prototype); - opt = opt || {}; + public static open (opt: IPtyOpenOptions): UnixTerminal { + const self: UnixTerminal = Object.create(UnixTerminal.prototype); + opt = opt || {}; - if (arguments.length > 1) { - opt = { - cols: arguments[1], - rows: arguments[2] - }; - } - - const cols = opt.cols || DEFAULT_COLS; - const rows = opt.rows || DEFAULT_ROWS; - const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); - - // open - const term: IUnixOpenProcess = pty.open(cols, rows); - - self._master = new tty.ReadStream(term.master); - if (encoding !== null) { - self._master.setEncoding(encoding as BufferEncoding); - } - self._master.resume(); - - self._slave = new tty.ReadStream(term.slave); - if (encoding !== null) { - self._slave.setEncoding(encoding as BufferEncoding); - } - self._slave.resume(); - - self._socket = self._master; - self._pid = -1; - self._fd = term.master; - self._pty = term.pty; - - self._file = process.argv[0] || 'node'; - self._name = process.env['TERM'] || ''; - - self._readable = true; - self._writable = true; - - self._socket.on('error', err => { - self._close(); - if (self.listeners('error').length < 2) { - throw err; - } - }); - - self._socket.on('close', () => { - self._close(); - }); - - return self; + if (arguments.length > 1) { + opt = { + cols: arguments[1], + rows: arguments[2], + }; } - public destroy(): void { - this._close(); + const cols = opt.cols || DEFAULT_COLS; + const rows = opt.rows || DEFAULT_ROWS; + const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding); - // Need to close the read stream so node stops reading a dead file - // descriptor. Then we can safely SIGHUP the shell. - this._socket.once('close', () => { - this.kill('SIGHUP'); - }); + // open + const term: IUnixOpenProcess = pty.open(cols, rows); - this._socket.destroy(); + self._master = new tty.ReadStream(term.master); + if (encoding !== null) { + self._master.setEncoding(encoding as BufferEncoding); } + self._master.resume(); - public kill(signal?: string): void { - try { - process.kill(this.pid, signal || 'SIGHUP'); - } catch { /* swallow */ } + self._slave = new tty.ReadStream(term.slave); + if (encoding !== null) { + self._slave.setEncoding(encoding as BufferEncoding); } + self._slave.resume(); - /** + self._socket = self._master; + self._pid = -1; + self._fd = term.master; + self._pty = term.pty; + + self._file = process.argv[0] || 'node'; + self._name = process.env['TERM'] || ''; + + self._readable = true; + self._writable = true; + + self._socket.on('error', err => { + self._close(); + if (self.listeners('error').length < 2) { + throw err; + } + }); + + self._socket.on('close', () => { + self._close(); + }); + + return self; + } + + public destroy (): void { + this._close(); + + // Need to close the read stream so node stops reading a dead file + // descriptor. Then we can safely SIGHUP the shell. + this._socket.once('close', () => { + this.kill('SIGHUP'); + }); + + this._socket.destroy(); + } + + public kill (signal?: string): void { + try { + process.kill(this.pid, signal || 'SIGHUP'); + } catch { /* swallow */ } + } + + /** * Gets the name of the process. */ - public get process(): string { - if (process.platform === 'darwin') { - const title = pty.process(this._fd); - return (title !== 'kernel_task') ? title : this._file; - } - - return pty.process(this._fd, this._pty) || this._file; + public get process (): string { + if (process.platform === 'darwin') { + const title = pty.process(this._fd); + return (title !== 'kernel_task') ? title : this._file; } - /** + return pty.process(this._fd, this._pty) || this._file; + } + + /** * TTY */ - public resize(cols: number, rows: number): void { - if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) { - throw new Error('resizing must be done using positive cols and rows'); - } - pty.resize(this._fd, cols, rows); - this._cols = cols; - this._rows = rows; + public resize (cols: number, rows: number): void { + if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) { + throw new Error('resizing must be done using positive cols and rows'); } + pty.resize(this._fd, cols, rows); + this._cols = cols; + this._rows = rows; + } - public clear(): void { + public clear (): void { - } + } - private _sanitizeEnv(env: IProcessEnv): void { - // Make sure we didn't start our server from inside tmux. - delete env['TMUX']; - delete env['TMUX_PANE']; + private _sanitizeEnv (env: IProcessEnv): void { + // Make sure we didn't start our server from inside tmux. + delete env['TMUX']; + delete env['TMUX_PANE']; - // Make sure we didn't start our server from inside screen. - // http://web.mit.edu/gnu/doc/html/screen_20.html - delete env['STY']; - delete env['WINDOW']; + // Make sure we didn't start our server from inside screen. + // http://web.mit.edu/gnu/doc/html/screen_20.html + delete env['STY']; + delete env['WINDOW']; - // Delete some variables that might confuse our terminal. - delete env['WINDOWID']; - delete env['TERMCAP']; - delete env['COLUMNS']; - delete env['LINES']; - } + // Delete some variables that might confuse our terminal. + delete env['WINDOWID']; + delete env['TERMCAP']; + delete env['COLUMNS']; + delete env['LINES']; + } } diff --git a/src/pty/windowsConoutConnection.ts b/src/pty/windowsConoutConnection.ts index e1d303a1..911883b2 100644 --- a/src/pty/windowsConoutConnection.ts +++ b/src/pty/windowsConoutConnection.ts @@ -30,51 +30,51 @@ const FLUSH_DATA_INTERVAL = 1000; * - https://docs.microsoft.com/en-us/windows/console/closepseudoconsole */ export class ConoutConnection implements IDisposable { - private _worker: Worker; - private _drainTimeout: NodeJS.Timeout | undefined; - private _isDisposed: boolean = false; + private _worker: Worker; + private _drainTimeout: NodeJS.Timeout | undefined; + private _isDisposed: boolean = false; - private _onReady = new EventEmitter2(); - public get onReady(): IEvent { return this._onReady.event; } + private _onReady = new EventEmitter2(); + public get onReady (): IEvent { return this._onReady.event; } - constructor( + constructor ( private _conoutPipeName: string - ) { - const workerData: IWorkerData = { conoutPipeName: _conoutPipeName }; - const scriptPath = dirname(fileURLToPath(import.meta.url)); - this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.mjs'), { workerData }); - this._worker.on('message', (message: ConoutWorkerMessage) => { - switch (message) { - case ConoutWorkerMessage.READY: - this._onReady.fire(); - return; - default: - console.warn('Unexpected ConoutWorkerMessage', message); - } - }); - } + ) { + const workerData: IWorkerData = { conoutPipeName: _conoutPipeName }; + const scriptPath = dirname(fileURLToPath(import.meta.url)); + this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.mjs'), { workerData }); + this._worker.on('message', (message: ConoutWorkerMessage) => { + switch (message) { + case ConoutWorkerMessage.READY: + this._onReady.fire(); + return; + default: + console.warn('Unexpected ConoutWorkerMessage', message); + } + }); + } - dispose(): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - // Drain all data from the socket before closing - this._drainDataAndClose(); + dispose (): void { + if (this._isDisposed) { + return; } + this._isDisposed = true; + // Drain all data from the socket before closing + this._drainDataAndClose(); + } - connectSocket(socket: Socket): void { - socket.connect(getWorkerPipeName(this._conoutPipeName)); - } + connectSocket (socket: Socket): void { + socket.connect(getWorkerPipeName(this._conoutPipeName)); + } - private _drainDataAndClose(): void { - if (this._drainTimeout) { - clearTimeout(this._drainTimeout); - } - this._drainTimeout = setTimeout(() => this._destroySocket(), FLUSH_DATA_INTERVAL); + private _drainDataAndClose (): void { + if (this._drainTimeout) { + clearTimeout(this._drainTimeout); } + this._drainTimeout = setTimeout(() => this._destroySocket(), FLUSH_DATA_INTERVAL); + } - private async _destroySocket(): Promise { - await this._worker.terminate(); - } + private async _destroySocket (): Promise { + await this._worker.terminate(); + } } diff --git a/src/pty/windowsPtyAgent.ts b/src/pty/windowsPtyAgent.ts index 7ffbceb4..47b085e7 100644 --- a/src/pty/windowsPtyAgent.ts +++ b/src/pty/windowsPtyAgent.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable no-undef */ /** * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License) * Copyright (c) 2016, Daniel Imms (MIT License). @@ -32,286 +30,286 @@ const FLUSH_DATA_INTERVAL = 1000; * and winpty. */ export class WindowsPtyAgent { - private _inSocket: Socket; - private _outSocket: Socket; - private _pid: number = 0; - private _innerPid: number = 0; - private _closeTimeout: NodeJS.Timer | undefined; - private _exitCode: number | undefined; - private _conoutSocketWorker: ConoutConnection; + private _inSocket: Socket; + private _outSocket: Socket; + private _pid: number = 0; + private _innerPid: number = 0; + private _closeTimeout: NodeJS.Timer | undefined; + private _exitCode: number | undefined; + private _conoutSocketWorker: ConoutConnection; - private _fd: any; - private _pty: number; - private _ptyNative: IConptyNative | IWinptyNative; + private _fd: any; + private _pty: number; + private _ptyNative: IConptyNative | IWinptyNative; - public get inSocket(): Socket { return this._inSocket; } - public get outSocket(): Socket { return this._outSocket; } - public get fd(): any { return this._fd; } - public get innerPid(): number { return this._innerPid; } - public get pty(): number { return this._pty; } + public get inSocket (): Socket { return this._inSocket; } + public get outSocket (): Socket { return this._outSocket; } + public get fd (): any { return this._fd; } + public get innerPid (): number { return this._innerPid; } + public get pty (): number { return this._pty; } - constructor( - file: string, - args: ArgvOrCommandLine, - env: string[], - cwd: string, - cols: number, - rows: number, - debug: boolean, - private _useConpty: boolean | undefined, - private _useConptyDll: boolean = false, - conptyInheritCursor: boolean = false - ) { - if (this._useConpty === undefined || this._useConpty === true) { - this._useConpty = this._getWindowsBuildNumber() >= 18309; - } - if (this._useConpty) { - if (!conptyNative) { - conptyNative = require_dlopen('./native/pty/' + process.platform + '.' + process.arch + '/conpty.node'); - } - } else { - if (!winptyNative) { - winptyNative = require_dlopen('./native/pty/' + process.platform + '.' + process.arch + '/pty.node'); - } - } - this._ptyNative = this._useConpty ? conptyNative : winptyNative; + constructor ( + file: string, + args: ArgvOrCommandLine, + env: string[], + cwd: string, + cols: number, + rows: number, + debug: boolean, + private _useConpty: boolean | undefined, + private _useConptyDll: boolean = false, + conptyInheritCursor: boolean = false + ) { + if (this._useConpty === undefined || this._useConpty === true) { + this._useConpty = this._getWindowsBuildNumber() >= 18309; + } + if (this._useConpty) { + if (!conptyNative) { + conptyNative = require_dlopen('./native/pty/' + process.platform + '.' + process.arch + '/conpty.node'); + } + } else { + if (!winptyNative) { + winptyNative = require_dlopen('./native/pty/' + process.platform + '.' + process.arch + '/pty.node'); + } + } + this._ptyNative = this._useConpty ? conptyNative : winptyNative; - // Sanitize input variable. - cwd = path.resolve(cwd); + // Sanitize input variable. + cwd = path.resolve(cwd); - // Compose command line - const commandLine = argsToCommandLine(file, args); + // Compose command line + const commandLine = argsToCommandLine(file, args); - // Open pty session. - let term: IConptyProcess | IWinptyProcess; - if (this._useConpty) { - term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll); - } else { - term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug); - this._pid = (term as IWinptyProcess).pid; - this._innerPid = (term as IWinptyProcess).innerPid; - } + // Open pty session. + let term: IConptyProcess | IWinptyProcess; + if (this._useConpty) { + term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll); + } else { + term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug); + this._pid = (term as IWinptyProcess).pid; + this._innerPid = (term as IWinptyProcess).innerPid; + } - // Not available on windows. - this._fd = term.fd; + // Not available on windows. + this._fd = term.fd; - // Generated incremental number that has no real purpose besides using it - // as a terminal id. - this._pty = term.pty; + // Generated incremental number that has no real purpose besides using it + // as a terminal id. + this._pty = term.pty; - // Create terminal pipe IPC channel and forward to a local unix socket. - this._outSocket = new Socket(); - this._outSocket.setEncoding('utf8'); - // The conout socket must be ready out on another thread to avoid deadlocks - this._conoutSocketWorker = new ConoutConnection(term.conout); - this._conoutSocketWorker.onReady(() => { - this._conoutSocketWorker.connectSocket(this._outSocket); + // Create terminal pipe IPC channel and forward to a local unix socket. + this._outSocket = new Socket(); + this._outSocket.setEncoding('utf8'); + // The conout socket must be ready out on another thread to avoid deadlocks + this._conoutSocketWorker = new ConoutConnection(term.conout); + this._conoutSocketWorker.onReady(() => { + this._conoutSocketWorker.connectSocket(this._outSocket); + }); + this._outSocket.on('connect', () => { + this._outSocket.emit('ready_datapipe'); + }); + + const inSocketFD = fs.openSync(term.conin, 'w'); + this._inSocket = new Socket({ + fd: inSocketFD, + readable: false, + writable: true, + }); + this._inSocket.setEncoding('utf8'); + + if (this._useConpty) { + const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, c => this._$onProcessExit(c)); + this._innerPid = connect.pid; + } + } + + public resize (cols: number, rows: number): void { + if (this._useConpty) { + if (this._exitCode !== undefined) { + throw new Error('Cannot resize a pty that has already exited'); + } + (this._ptyNative as IConptyNative).resize(this._pty, cols, rows, this._useConptyDll); + return; + } + (this._ptyNative as IWinptyNative).resize(this._pid, cols, rows); + } + + public clear (): void { + if (this._useConpty) { + (this._ptyNative as IConptyNative).clear(this._pty, this._useConptyDll); + } + } + + public kill (): void { + this._inSocket.readable = false; + this._outSocket.readable = false; + // Tell the agent to kill the pty, this releases handles to the process + if (this._useConpty) { + this._getConsoleProcessList().then(consoleProcessList => { + consoleProcessList.forEach((pid: number) => { + try { + process.kill(pid); + } catch { + // Ignore if process cannot be found (kill ESRCH error) + } }); - this._outSocket.on('connect', () => { - this._outSocket.emit('ready_datapipe'); - }); - - const inSocketFD = fs.openSync(term.conin, 'w'); - this._inSocket = new Socket({ - fd: inSocketFD, - readable: false, - writable: true - }); - this._inSocket.setEncoding('utf8'); - - if (this._useConpty) { - const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, c => this._$onProcessExit(c)); - this._innerPid = connect.pid; + (this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll); + }); + } else { + // Because pty.kill closes the handle, it will kill most processes by itself. + // Process IDs can be reused as soon as all handles to them are + // dropped, so we want to immediately kill the entire console process list. + // If we do not force kill all processes here, node servers in particular + // seem to become detached and remain running (see + // Microsoft/vscode#26807). + const processList: number[] = (this._ptyNative as IWinptyNative).getProcessList(this._pid); + (this._ptyNative as IWinptyNative).kill(this._pid, this._innerPid); + processList.forEach(pid => { + try { + process.kill(pid); + } catch { + // Ignore if process cannot be found (kill ESRCH error) } + }); } + this._conoutSocketWorker.dispose(); + } - public resize(cols: number, rows: number): void { - if (this._useConpty) { - if (this._exitCode !== undefined) { - throw new Error('Cannot resize a pty that has already exited'); - } - (this._ptyNative as IConptyNative).resize(this._pty, cols, rows, this._useConptyDll); - return; - } - (this._ptyNative as IWinptyNative).resize(this._pid, cols, rows); + private _getConsoleProcessList (): Promise { + const import__dirname = dirname(fileURLToPath(import.meta.url)); + return new Promise(resolve => { + const agent = fork(path.join(import__dirname, 'conpty_console_list_agent'), [this._innerPid.toString()]); + agent.on('message', message => { + clearTimeout(timeout); + // @ts-expect-error no need to check if it is null + resolve(message.consoleProcessList); + }); + const timeout = setTimeout(() => { + // Something went wrong, just send back the shell PID + agent.kill(); + resolve([this._innerPid]); + }, 5000); + }); + } + + public get exitCode (): number | undefined { + if (this._useConpty) { + return this._exitCode; } + const winptyExitCode = (this._ptyNative as IWinptyNative).getExitCode(this._innerPid); + return winptyExitCode === -1 ? undefined : winptyExitCode; + } - public clear(): void { - if (this._useConpty) { - (this._ptyNative as IConptyNative).clear(this._pty, this._useConptyDll); - } + private _getWindowsBuildNumber (): number { + const release = os.release(); + // Limit input length to prevent potential DoS attacks + if (release.length > 50) { + return 0; } - - public kill(): void { - this._inSocket.readable = false; - this._outSocket.readable = false; - // Tell the agent to kill the pty, this releases handles to the process - if (this._useConpty) { - this._getConsoleProcessList().then(consoleProcessList => { - consoleProcessList.forEach((pid: number) => { - try { - process.kill(pid); - } catch { - // Ignore if process cannot be found (kill ESRCH error) - } - }); - (this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll); - }); - } else { - // Because pty.kill closes the handle, it will kill most processes by itself. - // Process IDs can be reused as soon as all handles to them are - // dropped, so we want to immediately kill the entire console process list. - // If we do not force kill all processes here, node servers in particular - // seem to become detached and remain running (see - // Microsoft/vscode#26807). - const processList: number[] = (this._ptyNative as IWinptyNative).getProcessList(this._pid); - (this._ptyNative as IWinptyNative).kill(this._pid, this._innerPid); - processList.forEach(pid => { - try { - process.kill(pid); - } catch { - // Ignore if process cannot be found (kill ESRCH error) - } - }); - } - this._conoutSocketWorker.dispose(); + // Use non-global regex with more specific pattern to prevent backtracking + const osVersion = /^(\d{1,5})\.(\d{1,5})\.(\d{1,10})/.exec(release); + let buildNumber: number = 0; + if (osVersion && osVersion.length === 4) { + buildNumber = parseInt(osVersion[3]!); } + return buildNumber; + } - private _getConsoleProcessList(): Promise { - const import__dirname = dirname(fileURLToPath(import.meta.url)); - return new Promise(resolve => { - const agent = fork(path.join(import__dirname, 'conpty_console_list_agent'), [this._innerPid.toString()]); - agent.on('message', message => { - clearTimeout(timeout); - // @ts-expect-error no need to check if it is null - resolve(message.consoleProcessList); - }); - const timeout = setTimeout(() => { - // Something went wrong, just send back the shell PID - agent.kill(); - resolve([this._innerPid]); - }, 5000); - }); - } + private _generatePipeName (): string { + return `conpty-${Math.random() * 10000000}`; + } - public get exitCode(): number | undefined { - if (this._useConpty) { - return this._exitCode; - } - const winptyExitCode = (this._ptyNative as IWinptyNative).getExitCode(this._innerPid); - return winptyExitCode === -1 ? undefined : winptyExitCode; - } - - private _getWindowsBuildNumber(): number { - const release = os.release(); - // Limit input length to prevent potential DoS attacks - if (release.length > 50) { - return 0; - } - // Use non-global regex with more specific pattern to prevent backtracking - const osVersion = /^(\d{1,5})\.(\d{1,5})\.(\d{1,10})/.exec(release); - let buildNumber: number = 0; - if (osVersion && osVersion.length === 4) { - buildNumber = parseInt(osVersion[3]!); - } - return buildNumber; - } - - private _generatePipeName(): string { - return `conpty-${Math.random() * 10000000}`; - } - - /** + /** * Triggered from the native side when a contpy process exits. */ - private _$onProcessExit(exitCode: number): void { - this._exitCode = exitCode; - this._flushDataAndCleanUp(); - this._outSocket.on('data', () => this._flushDataAndCleanUp()); - } + private _$onProcessExit (exitCode: number): void { + this._exitCode = exitCode; + this._flushDataAndCleanUp(); + this._outSocket.on('data', () => this._flushDataAndCleanUp()); + } - private _flushDataAndCleanUp(): void { - if (this._closeTimeout) { - // @ts-expect-error no need to check if it is null - clearTimeout(this._closeTimeout); - } - this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL); + private _flushDataAndCleanUp (): void { + if (this._closeTimeout) { + // @ts-expect-error no need to check if it is null + clearTimeout(this._closeTimeout); } + this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL); + } - private _cleanUpProcess(): void { - this._inSocket.readable = false; - this._outSocket.readable = false; - this._outSocket.destroy(); - } + private _cleanUpProcess (): void { + this._inSocket.readable = false; + this._outSocket.readable = false; + this._outSocket.destroy(); + } } // Convert argc/argv into a Win32 command-line following the escaping convention // documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from // winpty project. -export function argsToCommandLine(file: string, args: ArgvOrCommandLine): string { - if (isCommandLine(args)) { - if (args.length === 0) { - return file; - } - return `${argsToCommandLine(file, [])} ${args}`; +export function argsToCommandLine (file: string, args: ArgvOrCommandLine): string { + if (isCommandLine(args)) { + if (args.length === 0) { + return file; } - const argv = [file]; - Array.prototype.push.apply(argv, args); - let result = ''; - for (let argIndex = 0; argIndex < argv.length; argIndex++) { - if (argIndex > 0) { - result += ' '; - } - const arg = argv[argIndex]; - // if it is empty or it contains whitespace and is not already quoted - const hasLopsidedEnclosingQuote = xOr((arg![0] !== '"'), (arg![arg!.length - 1] !== '"')); - const hasNoEnclosingQuotes = ((arg![0] !== '"') && (arg![arg!.length - 1] !== '"')); - const quote = + return `${argsToCommandLine(file, [])} ${args}`; + } + const argv = [file]; + Array.prototype.push.apply(argv, args); + let result = ''; + for (let argIndex = 0; argIndex < argv.length; argIndex++) { + if (argIndex > 0) { + result += ' '; + } + const arg = argv[argIndex]; + // if it is empty or it contains whitespace and is not already quoted + const hasLopsidedEnclosingQuote = xOr((arg![0] !== '"'), (arg![arg!.length - 1] !== '"')); + const hasNoEnclosingQuotes = ((arg![0] !== '"') && (arg![arg!.length - 1] !== '"')); + const quote = arg === '' || (arg!.indexOf(' ') !== -1 || arg!.indexOf('\t') !== -1) && ((arg!.length > 1) && (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes)); - if (quote) { - result += '"'; - } - let bsCount = 0; - for (let i = 0; i < arg!.length; i++) { - const p = arg![i]; - if (p === '\\') { - bsCount++; - } else if (p === '"') { - result += repeatText('\\', bsCount * 2 + 1); - result += '"'; - bsCount = 0; - } else { - result += repeatText('\\', bsCount); - bsCount = 0; - result += p; - } - } - if (quote) { - result += repeatText('\\', bsCount * 2); - result += '"'; - } else { - result += repeatText('\\', bsCount); - } + if (quote) { + result += '"'; } - return result; -} - -function isCommandLine(args: ArgvOrCommandLine): args is string { - return typeof args === 'string'; -} - -function repeatText(text: string, count: number): string { - let result = ''; - for (let i = 0; i < count; i++) { - result += text; + let bsCount = 0; + for (let i = 0; i < arg!.length; i++) { + const p = arg![i]; + if (p === '\\') { + bsCount++; + } else if (p === '"') { + result += repeatText('\\', bsCount * 2 + 1); + result += '"'; + bsCount = 0; + } else { + result += repeatText('\\', bsCount); + bsCount = 0; + result += p; + } } - return result; + if (quote) { + result += repeatText('\\', bsCount * 2); + result += '"'; + } else { + result += repeatText('\\', bsCount); + } + } + return result; } -function xOr(arg1: boolean, arg2: boolean): boolean { - return ((arg1 && !arg2) || (!arg1 && arg2)); +function isCommandLine (args: ArgvOrCommandLine): args is string { + return typeof args === 'string'; +} + +function repeatText (text: string, count: number): string { + let result = ''; + for (let i = 0; i < count; i++) { + result += text; + } + return result; +} + +function xOr (arg1: boolean, arg2: boolean): boolean { + return ((arg1 && !arg2) || (!arg1 && arg2)); } diff --git a/src/pty/windowsTerminal.ts b/src/pty/windowsTerminal.ts index a8bd37ed..6126ddc2 100644 --- a/src/pty/windowsTerminal.ts +++ b/src/pty/windowsTerminal.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ /** * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License) * Copyright (c) 2016, Daniel Imms (MIT License). @@ -16,197 +15,191 @@ const DEFAULT_FILE = 'cmd.exe'; const DEFAULT_NAME = 'Windows Shell'; export class WindowsTerminal extends Terminal { - private _isReady: boolean; - private _deferreds: any[]; - private _agent: WindowsPtyAgent; + private _isReady: boolean; + private _deferreds: any[]; + private _agent: WindowsPtyAgent; - constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) { - super(opt); + constructor (file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) { + super(opt); - this._checkType('args', args, 'string', true); + this._checkType('args', args, 'string', true); - // Initialize arguments - args = args || []; - file = file || DEFAULT_FILE; - opt = opt || {}; - opt.env = opt.env || process.env; + // Initialize arguments + args = args || []; + file = file || DEFAULT_FILE; + opt = opt || {}; + opt.env = opt.env || process.env; - if (opt.encoding) { - console.warn('Setting encoding on Windows is not supported'); + if (opt.encoding) { + console.warn('Setting encoding on Windows is not supported'); + } + + const env = assign({}, opt.env); + this._cols = opt.cols || DEFAULT_COLS; + this._rows = opt.rows || DEFAULT_ROWS; + const cwd = opt.cwd || process.cwd(); + const name = opt.name || env.TERM || DEFAULT_NAME; + const parsedEnv = this._parseEnv(env); + + // If the terminal is ready + this._isReady = false; + + // Functions that need to run after `ready` event is emitted. + this._deferreds = []; + + // Create new termal. + this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor); + this._socket = this._agent.outSocket; + + // Not available until `ready` event emitted. + this._pid = this._agent.innerPid; + this._fd = this._agent.fd; + this._pty = this._agent.pty; + + // The forked windows terminal is not available until `ready` event is + // emitted. + this._socket.on('ready_datapipe', () => { + // These events needs to be forwarded. + ['connect', 'data', 'end', 'timeout', 'drain'].forEach(event => { + this._socket.on(event, () => { + // Wait until the first data event is fired then we can run deferreds. + if (!this._isReady && event === 'data') { + // Terminal is now ready and we can avoid having to defer method + // calls. + this._isReady = true; + + // Execute all deferred methods + this._deferreds.forEach(fn => { + // NB! In order to ensure that `this` has all its references + // updated any variable that need to be available in `this` before + // the deferred is run has to be declared above this forEach + // statement. + fn.run(); + }); + + // Reset + this._deferreds = []; + } + }); + }); + + // Shutdown if `error` event is emitted. + this._socket.on('error', err => { + // Close terminal session. + this._close(); + + // EIO, happens when someone closes our child process: the only process + // in the terminal. + // node < 0.6.14: errno 5 + // node >= 0.6.14: read EIO + if ((err).code) { + if (~(err).code.indexOf('errno 5') || ~(err).code.indexOf('EIO')) return; } - const env = assign({}, opt.env); - this._cols = opt.cols || DEFAULT_COLS; - this._rows = opt.rows || DEFAULT_ROWS; - const cwd = opt.cwd || process.cwd(); - const name = opt.name || env.TERM || DEFAULT_NAME; - const parsedEnv = this._parseEnv(env); + // Throw anything else. + if (this.listeners('error').length < 2) { + throw err; + } + }); - // If the terminal is ready - this._isReady = false; + // Cleanup after the socket is closed. + this._socket.on('close', () => { + this.emit('exit', this._agent.exitCode); + this._close(); + }); + }); - // Functions that need to run after `ready` event is emitted. - this._deferreds = []; + this._file = file; + this._name = name; - // Create new termal. - this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor); - this._socket = this._agent.outSocket; + this._readable = true; + this._writable = true; - // Not available until `ready` event emitted. - this._pid = this._agent.innerPid; - this._fd = this._agent.fd; - this._pty = this._agent.pty; + this._forwardEvents(); + } - // The forked windows terminal is not available until `ready` event is - // emitted. - this._socket.on('ready_datapipe', () => { + protected _write (data: string): void { + this._defer(this._doWrite, data); + } - // These events needs to be forwarded. - ['connect', 'data', 'end', 'timeout', 'drain'].forEach(event => { - this._socket.on(event, () => { + private _doWrite (data: string): void { + this._agent.inSocket.write(data); + } - // Wait until the first data event is fired then we can run deferreds. - if (!this._isReady && event === 'data') { - - // Terminal is now ready and we can avoid having to defer method - // calls. - this._isReady = true; - - // Execute all deferred methods - this._deferreds.forEach(fn => { - // NB! In order to ensure that `this` has all its references - // updated any variable that need to be available in `this` before - // the deferred is run has to be declared above this forEach - // statement. - fn.run(); - }); - - // Reset - this._deferreds = []; - - } - }); - }); - - // Shutdown if `error` event is emitted. - this._socket.on('error', err => { - // Close terminal session. - this._close(); - - // EIO, happens when someone closes our child process: the only process - // in the terminal. - // node < 0.6.14: errno 5 - // node >= 0.6.14: read EIO - if ((err).code) { - if (~(err).code.indexOf('errno 5') || ~(err).code.indexOf('EIO')) return; - } - - // Throw anything else. - if (this.listeners('error').length < 2) { - throw err; - } - }); - - // Cleanup after the socket is closed. - this._socket.on('close', () => { - this.emit('exit', this._agent.exitCode); - this._close(); - }); - - }); - - this._file = file; - this._name = name; - - this._readable = true; - this._writable = true; - - this._forwardEvents(); - } - - protected _write(data: string): void { - this._defer(this._doWrite, data); - } - - private _doWrite(data: string): void { - this._agent.inSocket.write(data); - } - - /** + /** * openpty */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public static open(_options?: IPtyOpenOptions): void { - throw new Error('open() not supported on windows, use Fork() instead.'); - } + public static open (_options?: IPtyOpenOptions): void { + throw new Error('open() not supported on windows, use Fork() instead.'); + } - /** + /** * TTY */ - public resize(cols: number, rows: number): void { - if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) { - throw new Error('resizing must be done using positive cols and rows'); - } - this._deferNoArgs(() => { - this._agent.resize(cols, rows); - this._cols = cols; - this._rows = rows; - }); + public resize (cols: number, rows: number): void { + if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) { + throw new Error('resizing must be done using positive cols and rows'); + } + this._deferNoArgs(() => { + this._agent.resize(cols, rows); + this._cols = cols; + this._rows = rows; + }); + } + + public clear (): void { + this._deferNoArgs(() => { + this._agent.clear(); + }); + } + + public destroy (): void { + this._deferNoArgs(() => { + this.kill(); + }); + } + + public kill (signal?: string): void { + this._deferNoArgs(() => { + if (signal) { + throw new Error('Signals not supported on windows.'); + } + this._close(); + this._agent.kill(); + }); + } + + // @ts-expect-error - This is a private method that is not part of the public API. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _deferNoArgs(deferredFn: () => void): void { + // If the terminal is ready, execute. + if (this._isReady) { + deferredFn.call(this); + return; } - public clear(): void { - this._deferNoArgs(() => { - this._agent.clear(); - }); + // Queue until terminal is ready. + this._deferreds.push({ + run: () => deferredFn.call(this), + }); + } + + private _defer(deferredFn: (arg: A) => void, arg: A): void { + // If the terminal is ready, execute. + if (this._isReady) { + deferredFn.call(this, arg); + return; } - public destroy(): void { - this._deferNoArgs(() => { - this.kill(); - }); - } + // Queue until terminal is ready. + this._deferreds.push({ + run: () => deferredFn.call(this, arg), + }); + } - public kill(signal?: string): void { - this._deferNoArgs(() => { - if (signal) { - throw new Error('Signals not supported on windows.'); - } - this._close(); - this._agent.kill(); - }); - } - - // @ts-expect-error - This is a private method that is not part of the public API. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private _deferNoArgs(deferredFn: () => void): void { - // If the terminal is ready, execute. - if (this._isReady) { - deferredFn.call(this); - return; - } - - // Queue until terminal is ready. - this._deferreds.push({ - run: () => deferredFn.call(this) - }); - } - - private _defer(deferredFn: (arg: A) => void, arg: A): void { - // If the terminal is ready, execute. - if (this._isReady) { - deferredFn.call(this, arg); - return; - } - - // Queue until terminal is ready. - this._deferreds.push({ - run: () => deferredFn.call(this, arg) - }); - } - - public get process(): string { return this._name; } - public get master(): Socket { throw new Error('master is not supported on Windows'); } - public get slave(): Socket { throw new Error('slave is not supported on Windows'); } + public get process (): string { return this._name; } + public get master (): Socket { throw new Error('master is not supported on Windows'); } + public get slave (): Socket { throw new Error('slave is not supported on Windows'); } } diff --git a/src/pty/worker/conoutSocketWorker.ts b/src/pty/worker/conoutSocketWorker.ts index 64aeed00..cee739ab 100644 --- a/src/pty/worker/conoutSocketWorker.ts +++ b/src/pty/worker/conoutSocketWorker.ts @@ -10,13 +10,13 @@ const conoutPipeName = (workerData as IWorkerData).conoutPipeName; const conoutSocket = new Socket(); conoutSocket.setEncoding('utf8'); conoutSocket.connect(conoutPipeName, () => { - const server = createServer(workerSocket => { - conoutSocket.pipe(workerSocket); - }); - server.listen(getWorkerPipeName(conoutPipeName)); + const server = createServer(workerSocket => { + conoutSocket.pipe(workerSocket); + }); + server.listen(getWorkerPipeName(conoutPipeName)); - if (!parentPort) { - throw new Error('worker_threads parentPort is null'); - } - parentPort.postMessage(ConoutWorkerMessage.READY); + if (!parentPort) { + throw new Error('worker_threads parentPort is null'); + } + parentPort.postMessage(ConoutWorkerMessage.READY); }); diff --git a/src/qrcode/lib/main.ts b/src/qrcode/lib/main.ts index b22b0883..b2e499dc 100644 --- a/src/qrcode/lib/main.ts +++ b/src/qrcode/lib/main.ts @@ -5,107 +5,106 @@ const black = '\x1b[40m \x1b[0m'; const white = '\x1b[47m \x1b[0m'; const toCell = (value: boolean | null): string => { - return value ? black : white; + return value ? black : white; }; const repeat = (color: string) => { - return { - times: (count: number): string => { - return new Array(count + 1).join(color); - } - }; + return { + times: (count: number): string => { + return new Array(count + 1).join(color); + }, + }; }; const fill = (length: number, value: boolean): boolean[] => { - const arr = new Array(length); - for (let i = 0; i < length; i++) { - arr[i] = value; - } - return arr; + const arr = new Array(length); + for (let i = 0; i < length; i++) { + arr[i] = value; + } + return arr; }; interface GenerateOptions { - small?: boolean; + small?: boolean; } type Callback = (output: string) => void; export default { - error: QRErrorCorrectLevel.L, + error: QRErrorCorrectLevel.L, - generate(input: string, opts: GenerateOptions | Callback, cb?: Callback): void { - if (typeof opts === 'function') { - cb = opts; - opts = {}; - } - - const qrcode = new QRCode(-1, this.error); - qrcode.addData(input); - qrcode.make(); - - let output = ''; - if (opts && (opts as GenerateOptions).small) { - const BLACK = true, WHITE = false; - const moduleCount = qrcode.getModuleCount(); - const moduleData = qrcode.modules ? qrcode.modules.slice() : []; - - const oddRow = moduleCount % 2 === 1; - if (oddRow) { - moduleData.push(fill(moduleCount, WHITE)); - } - - const platte = { - WHITE_ALL: '\u2588', - WHITE_BLACK: '\u2580', - BLACK_WHITE: '\u2584', - BLACK_ALL: ' ', - }; - - const borderTop = repeat(platte.BLACK_WHITE).times(moduleCount + 2); // 修改这里 - const borderBottom = repeat(platte.WHITE_BLACK).times(moduleCount + 2); // 修改这里 - output += borderTop + '\n'; - - for (let row = 0; row < moduleCount; row += 2) { - output += platte.WHITE_ALL; - - for (let col = 0; col < moduleCount; col++) { - if (moduleData[row]?.[col] === WHITE && moduleData[row + 1]?.[col] === WHITE) { - output += platte.WHITE_ALL; - } else if (moduleData[row]?.[col] === WHITE && moduleData[row + 1]?.[col] === BLACK) { - output += platte.WHITE_BLACK; - } else if (moduleData[row]?.[col] === BLACK && moduleData[row + 1]?.[col] === WHITE) { - output += platte.BLACK_WHITE; - } else { - output += platte.BLACK_ALL; - } - - } - - output += platte.WHITE_ALL + '\n'; - } - - if (!oddRow) { - output += borderBottom; - } - } else { - const border = repeat(white).times(qrcode.getModuleCount() + 2); // 修改这里 - - output += border + '\n'; - if (qrcode.modules) { - qrcode.modules.forEach((row: (boolean | null)[]) => { - output += white; - output += row.map(toCell).join(''); - output += white + '\n'; - }); - } - output += border; - } - - if (cb) cb(output); - else console.log(output); - }, - - setErrorLevel(error: keyof typeof QRErrorCorrectLevel): void { - this.error = QRErrorCorrectLevel[error] || this.error; + generate (input: string, opts: GenerateOptions | Callback, cb?: Callback): void { + if (typeof opts === 'function') { + cb = opts; + opts = {}; } -}; \ No newline at end of file + + const qrcode = new QRCode(-1, this.error); + qrcode.addData(input); + qrcode.make(); + + let output = ''; + if (opts && (opts as GenerateOptions).small) { + const BLACK = true; const WHITE = false; + const moduleCount = qrcode.getModuleCount(); + const moduleData = qrcode.modules ? qrcode.modules.slice() : []; + + const oddRow = moduleCount % 2 === 1; + if (oddRow) { + moduleData.push(fill(moduleCount, WHITE)); + } + + const platte = { + WHITE_ALL: '\u2588', + WHITE_BLACK: '\u2580', + BLACK_WHITE: '\u2584', + BLACK_ALL: ' ', + }; + + const borderTop = repeat(platte.BLACK_WHITE).times(moduleCount + 2); // 修改这里 + const borderBottom = repeat(platte.WHITE_BLACK).times(moduleCount + 2); // 修改这里 + output += borderTop + '\n'; + + for (let row = 0; row < moduleCount; row += 2) { + output += platte.WHITE_ALL; + + for (let col = 0; col < moduleCount; col++) { + if (moduleData[row]?.[col] === WHITE && moduleData[row + 1]?.[col] === WHITE) { + output += platte.WHITE_ALL; + } else if (moduleData[row]?.[col] === WHITE && moduleData[row + 1]?.[col] === BLACK) { + output += platte.WHITE_BLACK; + } else if (moduleData[row]?.[col] === BLACK && moduleData[row + 1]?.[col] === WHITE) { + output += platte.BLACK_WHITE; + } else { + output += platte.BLACK_ALL; + } + } + + output += platte.WHITE_ALL + '\n'; + } + + if (!oddRow) { + output += borderBottom; + } + } else { + const border = repeat(white).times(qrcode.getModuleCount() + 2); // 修改这里 + + output += border + '\n'; + if (qrcode.modules) { + qrcode.modules.forEach((row: (boolean | null)[]) => { + output += white; + output += row.map(toCell).join(''); + output += white + '\n'; + }); + } + output += border; + } + + if (cb) cb(output); + else console.log(output); + }, + + setErrorLevel (error: keyof typeof QRErrorCorrectLevel): void { + this.error = QRErrorCorrectLevel[error] || this.error; + }, +}; diff --git a/src/qrcode/vendor/QRCode/QR8bitByte.ts b/src/qrcode/vendor/QRCode/QR8bitByte.ts index 65f05adf..607b33d7 100644 --- a/src/qrcode/vendor/QRCode/QR8bitByte.ts +++ b/src/qrcode/vendor/QRCode/QR8bitByte.ts @@ -1,24 +1,24 @@ import { MODE_8BIT_BYTE } from './QRMode'; class QR8bitByte { - mode: number; - data: string; + mode: number; + data: string; - constructor(data: string) { - this.mode = MODE_8BIT_BYTE; - this.data = data; - } + constructor (data: string) { + this.mode = MODE_8BIT_BYTE; + this.data = data; + } - getLength(): number { - return this.data.length; - } + getLength (): number { + return this.data.length; + } - write(buffer: { put: (arg0: number, arg1: number) => void }): void { - for (let i = 0; i < this.data.length; i++) { - // not JIS ... - buffer.put(this.data.charCodeAt(i), 8); - } + write (buffer: { put: (arg0: number, arg1: number) => void }): void { + for (let i = 0; i < this.data.length; i++) { + // not JIS ... + buffer.put(this.data.charCodeAt(i), 8); } + } } -export default QR8bitByte; \ No newline at end of file +export default QR8bitByte; diff --git a/src/qrcode/vendor/QRCode/QRBitBuffer.ts b/src/qrcode/vendor/QRCode/QRBitBuffer.ts index 9200d18c..08d95e2d 100644 --- a/src/qrcode/vendor/QRCode/QRBitBuffer.ts +++ b/src/qrcode/vendor/QRCode/QRBitBuffer.ts @@ -1,39 +1,39 @@ class QRBitBuffer { - buffer: number[]; - length: number; + buffer: number[]; + length: number; - constructor() { - this.buffer = []; - this.length = 0; + constructor () { + this.buffer = []; + this.length = 0; + } + + get (index: number): boolean { + const bufIndex = Math.floor(index / 8); + return ((this.buffer[bufIndex]! >>> (7 - index % 8)) & 1) === 1; + } + + put (num: number, length: number): void { + for (let i = 0; i < length; i++) { + this.putBit(((num >>> (length - i - 1)) & 1) === 1); + } + } + + getLengthInBits (): number { + return this.length; + } + + putBit (bit: boolean): void { + const bufIndex = Math.floor(this.length / 8); + if (this.buffer.length <= bufIndex) { + this.buffer.push(0); } - get(index: number): boolean { - const bufIndex = Math.floor(index / 8); - return ((this.buffer[bufIndex]! >>> (7 - index % 8)) & 1) === 1; + if (bit) { + this.buffer[bufIndex]! |= (0x80 >>> (this.length % 8)); } - put(num: number, length: number): void { - for (let i = 0; i < length; i++) { - this.putBit(((num >>> (length - i - 1)) & 1) === 1); - } - } - - getLengthInBits(): number { - return this.length; - } - - putBit(bit: boolean): void { - const bufIndex = Math.floor(this.length / 8); - if (this.buffer.length <= bufIndex) { - this.buffer.push(0); - } - - if (bit) { - this.buffer[bufIndex]! |= (0x80 >>> (this.length % 8)); - } - - this.length++; - } + this.length++; + } } -export default QRBitBuffer; \ No newline at end of file +export default QRBitBuffer; diff --git a/src/qrcode/vendor/QRCode/QRErrorCorrectLevel.ts b/src/qrcode/vendor/QRCode/QRErrorCorrectLevel.ts index 285f5654..0dfd90a6 100644 --- a/src/qrcode/vendor/QRCode/QRErrorCorrectLevel.ts +++ b/src/qrcode/vendor/QRCode/QRErrorCorrectLevel.ts @@ -1,8 +1,8 @@ const QRErrorCorrectLevel = { - L: 1, - M: 0, - Q: 3, - H: 2 + L: 1, + M: 0, + Q: 3, + H: 2, }; -export { QRErrorCorrectLevel }; \ No newline at end of file +export { QRErrorCorrectLevel }; diff --git a/src/qrcode/vendor/QRCode/QRMaskPattern.ts b/src/qrcode/vendor/QRCode/QRMaskPattern.ts index 0867d045..d81972b8 100644 --- a/src/qrcode/vendor/QRCode/QRMaskPattern.ts +++ b/src/qrcode/vendor/QRCode/QRMaskPattern.ts @@ -1,12 +1,12 @@ const QRMaskPattern = { - PATTERN000: 0, - PATTERN001: 1, - PATTERN010: 2, - PATTERN011: 3, - PATTERN100: 4, - PATTERN101: 5, - PATTERN110: 6, - PATTERN111: 7 + PATTERN000: 0, + PATTERN001: 1, + PATTERN010: 2, + PATTERN011: 3, + PATTERN100: 4, + PATTERN101: 5, + PATTERN110: 6, + PATTERN111: 7, }; -export default QRMaskPattern; \ No newline at end of file +export default QRMaskPattern; diff --git a/src/qrcode/vendor/QRCode/QRMath.ts b/src/qrcode/vendor/QRCode/QRMath.ts index bdd25faa..78373926 100644 --- a/src/qrcode/vendor/QRCode/QRMath.ts +++ b/src/qrcode/vendor/QRCode/QRMath.ts @@ -1,37 +1,37 @@ const QRMath = { - glog(n: number): number { - if (n < 1) { - throw new Error('glog(' + n + ')'); - } - return QRMath.LOG_TABLE[n]!; - }, + glog (n: number): number { + if (n < 1) { + throw new Error('glog(' + n + ')'); + } + return QRMath.LOG_TABLE[n]!; + }, - gexp(n: number): number { - while (n < 0) { - n += 255; - } - while (n >= 256) { - n -= 255; - } - return QRMath.EXP_TABLE[n]!; - }, + gexp (n: number): number { + while (n < 0) { + n += 255; + } + while (n >= 256) { + n -= 255; + } + return QRMath.EXP_TABLE[n]!; + }, - EXP_TABLE: new Array(256).fill(0), + EXP_TABLE: new Array(256).fill(0), - LOG_TABLE: new Array(256).fill(0) + LOG_TABLE: new Array(256).fill(0), }; for (let i = 0; i < 8; i++) { - QRMath.EXP_TABLE[i] = 1 << i; + QRMath.EXP_TABLE[i] = 1 << i; } for (let i = 8; i < 256; i++) { - QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4]! - ^ QRMath.EXP_TABLE[i - 5]! - ^ QRMath.EXP_TABLE[i - 6]! - ^ QRMath.EXP_TABLE[i - 8]!; + QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4]! ^ + QRMath.EXP_TABLE[i - 5]! ^ + QRMath.EXP_TABLE[i - 6]! ^ + QRMath.EXP_TABLE[i - 8]!; } for (let i = 0; i < 255; i++) { - QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]!] = i; + QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]!] = i; } -export default QRMath; \ No newline at end of file +export default QRMath; diff --git a/src/qrcode/vendor/QRCode/QRMode.ts b/src/qrcode/vendor/QRCode/QRMode.ts index 526b0fa9..0a5cdc0d 100644 --- a/src/qrcode/vendor/QRCode/QRMode.ts +++ b/src/qrcode/vendor/QRCode/QRMode.ts @@ -3,9 +3,9 @@ export const MODE_ALPHA_NUM = 1 << 1; export const MODE_8BIT_BYTE = 1 << 2; export const MODE_KANJI = 1 << 3; export const QRMode = { - MODE_NUMBER, - MODE_ALPHA_NUM, - MODE_8BIT_BYTE, - MODE_KANJI + MODE_NUMBER, + MODE_ALPHA_NUM, + MODE_8BIT_BYTE, + MODE_KANJI, }; -export default QRMode; \ No newline at end of file +export default QRMode; diff --git a/src/qrcode/vendor/QRCode/QRPolynomial.ts b/src/qrcode/vendor/QRCode/QRPolynomial.ts index b67d918c..69ca180d 100644 --- a/src/qrcode/vendor/QRCode/QRPolynomial.ts +++ b/src/qrcode/vendor/QRCode/QRPolynomial.ts @@ -1,65 +1,65 @@ import QRMath from './QRMath'; class QRPolynomial { - private num: number[]; + private num: number[]; - constructor(num: number[], shift: number) { - if (num.length === undefined) { - throw new Error(num.length + '/' + shift); - } - - let offset = 0; - - while (offset < num.length && num[offset] === 0) { - offset++; - } - - this.num = new Array(num.length - offset + shift); - for (let i = 0; i < num.length - offset; i++) { - this.num[i] = num[i + offset]!; - } + constructor (num: number[], shift: number) { + if (num.length === undefined) { + throw new Error(num.length + '/' + shift); } - get(index: number): number { - return this.num[index]!; + let offset = 0; + + while (offset < num.length && num[offset] === 0) { + offset++; } - getLength(): number { - return this.num.length; + this.num = new Array(num.length - offset + shift); + for (let i = 0; i < num.length - offset; i++) { + this.num[i] = num[i + offset]!; + } + } + + get (index: number): number { + return this.num[index]!; + } + + getLength (): number { + return this.num.length; + } + + multiply (e: QRPolynomial): QRPolynomial { + const num = new Array(this.getLength() + e.getLength() - 1); + + for (let i = 0; i < this.getLength(); i++) { + for (let j = 0; j < e.getLength(); j++) { + num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j))); + } } - multiply(e: QRPolynomial): QRPolynomial { - const num = new Array(this.getLength() + e.getLength() - 1); + return new QRPolynomial(num, 0); + } - for (let i = 0; i < this.getLength(); i++) { - for (let j = 0; j < e.getLength(); j++) { - num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j))); - } - } - - return new QRPolynomial(num, 0); + mod (e: QRPolynomial): QRPolynomial { + if (this.getLength() - e.getLength() < 0) { + return this; } - mod(e: QRPolynomial): QRPolynomial { - if (this.getLength() - e.getLength() < 0) { - return this; - } + const ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); - const ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); + const num = new Array(this.getLength()); - const num = new Array(this.getLength()); - - for (let i = 0; i < this.getLength(); i++) { - num[i] = this.get(i); - } - - for (let x = 0; x < e.getLength(); x++) { - num[x] ^= QRMath.gexp(QRMath.glog(e.get(x)) + ratio); - } - - // recursive call - return new QRPolynomial(num, 0).mod(e); + for (let i = 0; i < this.getLength(); i++) { + num[i] = this.get(i); } + + for (let x = 0; x < e.getLength(); x++) { + num[x] ^= QRMath.gexp(QRMath.glog(e.get(x)) + ratio); + } + + // recursive call + return new QRPolynomial(num, 0).mod(e); + } } -export default QRPolynomial; \ No newline at end of file +export default QRPolynomial; diff --git a/src/qrcode/vendor/QRCode/QRRSBlock.ts b/src/qrcode/vendor/QRCode/QRRSBlock.ts index f759023e..5c93c155 100644 --- a/src/qrcode/vendor/QRCode/QRRSBlock.ts +++ b/src/qrcode/vendor/QRCode/QRRSBlock.ts @@ -1,302 +1,302 @@ import { QRErrorCorrectLevel } from './QRErrorCorrectLevel'; class QRRSBlock { - totalCount: number; - dataCount: number; + totalCount: number; + dataCount: number; - constructor(totalCount: number, dataCount: number) { - this.totalCount = totalCount; - this.dataCount = dataCount; - } + constructor (totalCount: number, dataCount: number) { + this.totalCount = totalCount; + this.dataCount = dataCount; + } - static RS_BLOCK_TABLE: number[][] = [ - // L - // M - // Q - // H + static RS_BLOCK_TABLE: number[][] = [ + // L + // M + // Q + // H - // 1 - [1, 26, 19], - [1, 26, 16], - [1, 26, 13], - [1, 26, 9], + // 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], - // 2 - [1, 44, 34], - [1, 44, 28], - [1, 44, 22], - [1, 44, 16], + // 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], - // 3 - [1, 70, 55], - [1, 70, 44], - [2, 35, 17], - [2, 35, 13], + // 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], - // 4 - [1, 100, 80], - [2, 50, 32], - [2, 50, 24], - [4, 25, 9], + // 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], - // 5 - [1, 134, 108], - [2, 67, 43], - [2, 33, 15, 2, 34, 16], - [2, 33, 11, 2, 34, 12], + // 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], - // 6 - [2, 86, 68], - [4, 43, 27], - [4, 43, 19], - [4, 43, 15], + // 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], - // 7 - [2, 98, 78], - [4, 49, 31], - [2, 32, 14, 4, 33, 15], - [4, 39, 13, 1, 40, 14], + // 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], - // 8 - [2, 121, 97], - [2, 60, 38, 2, 61, 39], - [4, 40, 18, 2, 41, 19], - [4, 40, 14, 2, 41, 15], + // 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], - // 9 - [2, 146, 116], - [3, 58, 36, 2, 59, 37], - [4, 36, 16, 4, 37, 17], - [4, 36, 12, 4, 37, 13], + // 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], - // 10 - [2, 86, 68, 2, 87, 69], - [4, 69, 43, 1, 70, 44], - [6, 43, 19, 2, 44, 20], - [6, 43, 15, 2, 44, 16], + // 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], - // 11 - [4, 101, 81], - [1, 80, 50, 4, 81, 51], - [4, 50, 22, 4, 51, 23], - [3, 36, 12, 8, 37, 13], + // 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], - // 12 - [2, 116, 92, 2, 117, 93], - [6, 58, 36, 2, 59, 37], - [4, 46, 20, 6, 47, 21], - [7, 42, 14, 4, 43, 15], + // 12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], - // 13 - [4, 133, 107], - [8, 59, 37, 1, 60, 38], - [8, 44, 20, 4, 45, 21], - [12, 33, 11, 4, 34, 12], + // 13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], - // 14 - [3, 145, 115, 1, 146, 116], - [4, 64, 40, 5, 65, 41], - [11, 36, 16, 5, 37, 17], - [11, 36, 12, 5, 37, 13], + // 14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], - // 15 - [5, 109, 87, 1, 110, 88], - [5, 65, 41, 5, 66, 42], - [5, 54, 24, 7, 55, 25], - [11, 36, 12], + // 15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12], - // 16 - [5, 122, 98, 1, 123, 99], - [7, 73, 45, 3, 74, 46], - [15, 43, 19, 2, 44, 20], - [3, 45, 15, 13, 46, 16], + // 16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], - // 17 - [1, 135, 107, 5, 136, 108], - [10, 74, 46, 1, 75, 47], - [1, 50, 22, 15, 51, 23], - [2, 42, 14, 17, 43, 15], + // 17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], - // 18 - [5, 150, 120, 1, 151, 121], - [9, 69, 43, 4, 70, 44], - [17, 50, 22, 1, 51, 23], - [2, 42, 14, 19, 43, 15], + // 18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], - // 19 - [3, 141, 113, 4, 142, 114], - [3, 70, 44, 11, 71, 45], - [17, 47, 21, 4, 48, 22], - [9, 39, 13, 16, 40, 14], + // 19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], - // 20 - [3, 135, 107, 5, 136, 108], - [3, 67, 41, 13, 68, 42], - [15, 54, 24, 5, 55, 25], - [15, 43, 15, 10, 44, 16], + // 20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], - // 21 - [4, 144, 116, 4, 145, 117], - [17, 68, 42], - [17, 50, 22, 6, 51, 23], - [19, 46, 16, 6, 47, 17], + // 21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], - // 22 - [2, 139, 111, 7, 140, 112], - [17, 74, 46], - [7, 54, 24, 16, 55, 25], - [34, 37, 13], + // 22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], - // 23 - [4, 151, 121, 5, 152, 122], - [4, 75, 47, 14, 76, 48], - [11, 54, 24, 14, 55, 25], - [16, 45, 15, 14, 46, 16], + // 23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], - // 24 - [6, 147, 117, 4, 148, 118], - [6, 73, 45, 14, 74, 46], - [11, 54, 24, 16, 55, 25], - [30, 46, 16, 2, 47, 17], + // 24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], - // 25 - [8, 132, 106, 4, 133, 107], - [8, 75, 47, 13, 76, 48], - [7, 54, 24, 22, 55, 25], - [22, 45, 15, 13, 46, 16], + // 25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], - // 26 - [10, 142, 114, 2, 143, 115], - [19, 74, 46, 4, 75, 47], - [28, 50, 22, 6, 51, 23], - [33, 46, 16, 4, 47, 17], + // 26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], - // 27 - [8, 152, 122, 4, 153, 123], - [22, 73, 45, 3, 74, 46], - [8, 53, 23, 26, 54, 24], - [12, 45, 15, 28, 46, 16], + // 27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], - // 28 - [3, 147, 117, 10, 148, 118], - [3, 73, 45, 23, 74, 46], - [4, 54, 24, 31, 55, 25], - [11, 45, 15, 31, 46, 16], + // 28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], - // 29 - [7, 146, 116, 7, 147, 117], - [21, 73, 45, 7, 74, 46], - [1, 53, 23, 37, 54, 24], - [19, 45, 15, 26, 46, 16], + // 29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], - // 30 - [5, 145, 115, 10, 146, 116], - [19, 75, 47, 10, 76, 48], - [15, 54, 24, 25, 55, 25], - [23, 45, 15, 25, 46, 16], + // 30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], - // 31 - [13, 145, 115, 3, 146, 116], - [2, 74, 46, 29, 75, 47], - [42, 54, 24, 1, 55, 25], - [23, 45, 15, 28, 46, 16], + // 31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], - // 32 - [17, 145, 115], - [10, 74, 46, 23, 75, 47], - [10, 54, 24, 35, 55, 25], - [19, 45, 15, 35, 46, 16], + // 32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], - // 33 - [17, 145, 115, 1, 146, 116], - [14, 74, 46, 21, 75, 47], - [29, 54, 24, 19, 55, 25], - [11, 45, 15, 46, 46, 16], + // 33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], - // 34 - [13, 145, 115, 6, 146, 116], - [14, 74, 46, 23, 75, 47], - [44, 54, 24, 7, 55, 25], - [59, 46, 16, 1, 47, 17], + // 34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], - // 35 - [12, 151, 121, 7, 152, 122], - [12, 75, 47, 26, 76, 48], - [39, 54, 24, 14, 55, 25], - [22, 45, 15, 41, 46, 16], + // 35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], - // 36 - [6, 151, 121, 14, 152, 122], - [6, 75, 47, 34, 76, 48], - [46, 54, 24, 10, 55, 25], - [2, 45, 15, 64, 46, 16], + // 36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], - // 37 - [17, 152, 122, 4, 153, 123], - [29, 74, 46, 14, 75, 47], - [49, 54, 24, 10, 55, 25], - [24, 45, 15, 46, 46, 16], + // 37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], - // 38 - [4, 152, 122, 18, 153, 123], - [13, 74, 46, 32, 75, 47], - [48, 54, 24, 14, 55, 25], - [42, 45, 15, 32, 46, 16], + // 38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], - // 39 - [20, 147, 117, 4, 148, 118], - [40, 75, 47, 7, 76, 48], - [43, 54, 24, 22, 55, 25], - [10, 45, 15, 67, 46, 16], + // 39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], - // 40 - [19, 148, 118, 6, 149, 119], - [18, 75, 47, 31, 76, 48], - [34, 54, 24, 34, 55, 25], - [20, 45, 15, 61, 46, 16] - ]; + // 40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16], + ]; } -function getRSBlocks(typeNumber: number, errorCorrectLevel: number): QRRSBlock[] { - const rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel); +function getRSBlocks (typeNumber: number, errorCorrectLevel: number): QRRSBlock[] { + const rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel); - if (rsBlock === undefined) { - throw new Error(`bad rs block @ typeNumber: ${typeNumber} / errorCorrectLevel: ${errorCorrectLevel}`); + if (rsBlock === undefined) { + throw new Error(`bad rs block @ typeNumber: ${typeNumber} / errorCorrectLevel: ${errorCorrectLevel}`); + } + + const length = rsBlock.length / 3; + const list: QRRSBlock[] = []; + + for (let i = 0; i < length; i++) { + const count = rsBlock[i * 3 + 0]; + const totalCount = rsBlock[i * 3 + 1]!; + const dataCount = rsBlock[i * 3 + 2]!; + + if (count === undefined) { + throw new Error(`count is undefined for typeNumber: ${typeNumber} / errorCorrectLevel: ${errorCorrectLevel}`); } - const length = rsBlock.length / 3; - const list: QRRSBlock[] = []; - - for (let i = 0; i < length; i++) { - const count = rsBlock[i * 3 + 0]; - const totalCount = rsBlock[i * 3 + 1]!; - const dataCount = rsBlock[i * 3 + 2]!; - - if (count === undefined) { - throw new Error(`count is undefined for typeNumber: ${typeNumber} / errorCorrectLevel: ${errorCorrectLevel}`); - } - - for (let j = 0; j < count; j++) { - list.push(new QRRSBlock(totalCount, dataCount)); - } + for (let j = 0; j < count; j++) { + list.push(new QRRSBlock(totalCount, dataCount)); } + } - return list; + return list; } -function getRsBlockTable(typeNumber: number, errorCorrectLevel: number): number[] | undefined { - switch (errorCorrectLevel) { +function getRsBlockTable (typeNumber: number, errorCorrectLevel: number): number[] | undefined { + switch (errorCorrectLevel) { case QRErrorCorrectLevel.L: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; case QRErrorCorrectLevel.M: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; case QRErrorCorrectLevel.Q: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; case QRErrorCorrectLevel.H: - return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; default: - return undefined; - } + return undefined; + } } -export { QRRSBlock, getRSBlocks, getRsBlockTable }; \ No newline at end of file +export { QRRSBlock, getRSBlocks, getRsBlockTable }; diff --git a/src/qrcode/vendor/QRCode/QRUtil.ts b/src/qrcode/vendor/QRCode/QRUtil.ts index cc10eca9..ece399ad 100644 --- a/src/qrcode/vendor/QRCode/QRUtil.ts +++ b/src/qrcode/vendor/QRCode/QRUtil.ts @@ -1,4 +1,3 @@ - import QRPolynomial from './QRPolynomial'; import QRMath from './QRMath'; import QRMaskPattern from './QRMaskPattern'; @@ -6,82 +5,82 @@ import QRCode from '.'; import QRMode from './QRMode'; export const PATTERN_POSITION_TABLE = [ - [], - [6, 18], - [6, 22], - [6, 26], - [6, 30], - [6, 34], - [6, 22, 38], - [6, 24, 42], - [6, 26, 46], - [6, 28, 50], - [6, 30, 54], - [6, 32, 58], - [6, 34, 62], - [6, 26, 46, 66], - [6, 26, 48, 70], - [6, 26, 50, 74], - [6, 30, 54, 78], - [6, 30, 56, 82], - [6, 30, 58, 86], - [6, 34, 62, 90], - [6, 28, 50, 72, 94], - [6, 26, 50, 74, 98], - [6, 30, 54, 78, 102], - [6, 28, 54, 80, 106], - [6, 32, 58, 84, 110], - [6, 30, 58, 86, 114], - [6, 34, 62, 90, 118], - [6, 26, 50, 74, 98, 122], - [6, 30, 54, 78, 102, 126], - [6, 26, 52, 78, 104, 130], - [6, 30, 56, 82, 108, 134], - [6, 34, 60, 86, 112, 138], - [6, 30, 58, 86, 114, 142], - [6, 34, 62, 90, 118, 146], - [6, 30, 54, 78, 102, 126, 150], - [6, 24, 50, 76, 102, 128, 154], - [6, 28, 54, 80, 106, 132, 158], - [6, 32, 58, 84, 110, 136, 162], - [6, 26, 54, 82, 110, 138, 166], - [6, 30, 58, 86, 114, 142, 170] + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], ]; export const G15: number = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); export const G18: number = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); export const G15_MASK: number = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); -export function getBCHTypeInfo(data: number): number { - let d: number = data << 10; - while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { - d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15))); - } - return ((data << 10) | d) ^ G15_MASK; +export function getBCHTypeInfo (data: number): number { + let d: number = data << 10; + while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { + d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15))); + } + return ((data << 10) | d) ^ G15_MASK; } -export function getBCHTypeNumber(data: number): number { - let d: number = data << 12; - while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { - d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18))); - } - return (data << 12) | d; +export function getBCHTypeNumber (data: number): number { + let d: number = data << 12; + while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { + d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18))); + } + return (data << 12) | d; } -export function getBCHDigit(data: number): number { - let digit: number = 0; - while (data !== 0) { - digit++; - data >>>= 1; - } - return digit; +export function getBCHDigit (data: number): number { + let digit: number = 0; + while (data !== 0) { + digit++; + data >>>= 1; + } + return digit; } -export function getPatternPosition(typeNumber: number): number[] { - return PATTERN_POSITION_TABLE[typeNumber - 1]!; +export function getPatternPosition (typeNumber: number): number[] { + return PATTERN_POSITION_TABLE[typeNumber - 1]!; } -export function getMask(maskPattern: number, i: number, j: number): boolean { - switch (maskPattern) { +export function getMask (maskPattern: number, i: number, j: number): boolean { + switch (maskPattern) { case QRMaskPattern.PATTERN000: return (i + j) % 2 === 0; case QRMaskPattern.PATTERN001: return i % 2 === 0; case QRMaskPattern.PATTERN010: return j % 3 === 0; @@ -91,121 +90,121 @@ export function getMask(maskPattern: number, i: number, j: number): boolean { case QRMaskPattern.PATTERN110: return ((i * j) % 2 + (i * j) % 3) % 2 === 0; case QRMaskPattern.PATTERN111: return ((i * j) % 3 + (i + j) % 2) % 2 === 0; default: throw new Error('bad maskPattern:' + maskPattern); - } + } } -export function getErrorCorrectPolynomial(errorCorrectLength: number): QRPolynomial { - let a: QRPolynomial = new QRPolynomial([1], 0); - for (let i = 0; i < errorCorrectLength; i++) { - a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); - } - return a; +export function getErrorCorrectPolynomial (errorCorrectLength: number): QRPolynomial { + let a: QRPolynomial = new QRPolynomial([1], 0); + for (let i = 0; i < errorCorrectLength; i++) { + a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); + } + return a; } -export function getLengthInBits(mode: number, type: number): number { - if (1 <= type && type < 10) { - // 1 - 9 - switch (mode) { - case QRMode.MODE_NUMBER: return 10; - case QRMode.MODE_ALPHA_NUM: return 9; - case QRMode.MODE_8BIT_BYTE: return 8; - case QRMode.MODE_KANJI: return 8; - default: throw new Error('mode:' + mode); - } - } else if (type < 27) { - // 10 - 26 - switch (mode) { - case QRMode.MODE_NUMBER: return 12; - case QRMode.MODE_ALPHA_NUM: return 11; - case QRMode.MODE_8BIT_BYTE: return 16; - case QRMode.MODE_KANJI: return 10; - default: throw new Error('mode:' + mode); - } - } else if (type < 41) { - // 27 - 40 - switch (mode) { - case QRMode.MODE_NUMBER: return 14; - case QRMode.MODE_ALPHA_NUM: return 13; - case QRMode.MODE_8BIT_BYTE: return 16; - case QRMode.MODE_KANJI: return 12; - default: throw new Error('mode:' + mode); - } - } else { - throw new Error('type:' + type); +export function getLengthInBits (mode: number, type: number): number { + if (type >= 1 && type < 10) { + // 1 - 9 + switch (mode) { + case QRMode.MODE_NUMBER: return 10; + case QRMode.MODE_ALPHA_NUM: return 9; + case QRMode.MODE_8BIT_BYTE: return 8; + case QRMode.MODE_KANJI: return 8; + default: throw new Error('mode:' + mode); } + } else if (type < 27) { + // 10 - 26 + switch (mode) { + case QRMode.MODE_NUMBER: return 12; + case QRMode.MODE_ALPHA_NUM: return 11; + case QRMode.MODE_8BIT_BYTE: return 16; + case QRMode.MODE_KANJI: return 10; + default: throw new Error('mode:' + mode); + } + } else if (type < 41) { + // 27 - 40 + switch (mode) { + case QRMode.MODE_NUMBER: return 14; + case QRMode.MODE_ALPHA_NUM: return 13; + case QRMode.MODE_8BIT_BYTE: return 16; + case QRMode.MODE_KANJI: return 12; + default: throw new Error('mode:' + mode); + } + } else { + throw new Error('type:' + type); + } } -export function getLostPoint(qrCode: QRCode): number { - const moduleCount: number = qrCode.getModuleCount(); - let lostPoint: number = 0; +export function getLostPoint (qrCode: QRCode): number { + const moduleCount: number = qrCode.getModuleCount(); + let lostPoint: number = 0; - // LEVEL1 - for (let row = 0; row < moduleCount; row++) { - for (let col = 0; col < moduleCount; col++) { - let sameCount: number = 0; - const dark: boolean = qrCode.isDark(row, col); - for (let r = -1; r <= 1; r++) { - if (row + r < 0 || moduleCount <= row + r) continue; - for (let c = -1; c <= 1; c++) { - if (col + c < 0 || moduleCount <= col + c) continue; - if (r === 0 && c === 0) continue; - if (dark === qrCode.isDark(row + r, col + c)) sameCount++; - } - } - if (sameCount > 5) lostPoint += (3 + sameCount - 5); + // LEVEL1 + for (let row = 0; row < moduleCount; row++) { + for (let col = 0; col < moduleCount; col++) { + let sameCount: number = 0; + const dark: boolean = qrCode.isDark(row, col); + for (let r = -1; r <= 1; r++) { + if (row + r < 0 || moduleCount <= row + r) continue; + for (let c = -1; c <= 1; c++) { + if (col + c < 0 || moduleCount <= col + c) continue; + if (r === 0 && c === 0) continue; + if (dark === qrCode.isDark(row + r, col + c)) sameCount++; } + } + if (sameCount > 5) lostPoint += (3 + sameCount - 5); } + } - // LEVEL2 - for (let row = 0; row < moduleCount - 1; row++) { - for (let col = 0; col < moduleCount - 1; col++) { - let count: number = 0; - if (qrCode.isDark(row, col)) count++; - if (qrCode.isDark(row + 1, col)) count++; - if (qrCode.isDark(row, col + 1)) count++; - if (qrCode.isDark(row + 1, col + 1)) count++; - if (count === 0 || count === 4) lostPoint += 3; - } + // LEVEL2 + for (let row = 0; row < moduleCount - 1; row++) { + for (let col = 0; col < moduleCount - 1; col++) { + let count: number = 0; + if (qrCode.isDark(row, col)) count++; + if (qrCode.isDark(row + 1, col)) count++; + if (qrCode.isDark(row, col + 1)) count++; + if (qrCode.isDark(row + 1, col + 1)) count++; + if (count === 0 || count === 4) lostPoint += 3; } + } - // LEVEL3 - for (let row = 0; row < moduleCount; row++) { - for (let col = 0; col < moduleCount - 6; col++) { - if (qrCode.isDark(row, col) && + // LEVEL3 + for (let row = 0; row < moduleCount; row++) { + for (let col = 0; col < moduleCount - 6; col++) { + if (qrCode.isDark(row, col) && !qrCode.isDark(row, col + 1) && qrCode.isDark(row, col + 2) && qrCode.isDark(row, col + 3) && qrCode.isDark(row, col + 4) && !qrCode.isDark(row, col + 5) && qrCode.isDark(row, col + 6)) { - lostPoint += 40; - } - } + lostPoint += 40; + } } + } - for (let col = 0; col < moduleCount; col++) { - for (let row = 0; row < moduleCount - 6; row++) { - if (qrCode.isDark(row, col) && + for (let col = 0; col < moduleCount; col++) { + for (let row = 0; row < moduleCount - 6; row++) { + if (qrCode.isDark(row, col) && !qrCode.isDark(row + 1, col) && qrCode.isDark(row + 2, col) && qrCode.isDark(row + 3, col) && qrCode.isDark(row + 4, col) && !qrCode.isDark(row + 5, col) && qrCode.isDark(row + 6, col)) { - lostPoint += 40; - } - } + lostPoint += 40; + } } + } - // LEVEL4 - let darkCount: number = 0; - for (let col = 0; col < moduleCount; col++) { - for (let row = 0; row < moduleCount; row++) { - if (qrCode.isDark(row, col)) darkCount++; - } + // LEVEL4 + let darkCount: number = 0; + for (let col = 0; col < moduleCount; col++) { + for (let row = 0; row < moduleCount; row++) { + if (qrCode.isDark(row, col)) darkCount++; } - const ratio: number = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; - lostPoint += ratio * 10; + } + const ratio: number = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; - return lostPoint; -} \ No newline at end of file + return lostPoint; +} diff --git a/src/qrcode/vendor/QRCode/index.ts b/src/qrcode/vendor/QRCode/index.ts index bff5f1a4..669b01f7 100644 --- a/src/qrcode/vendor/QRCode/index.ts +++ b/src/qrcode/vendor/QRCode/index.ts @@ -6,408 +6,407 @@ import { getRSBlocks, QRRSBlock as RSBlock } from './QRRSBlock'; import QRBitBuffer from './QRBitBuffer'; class QRCode { - typeNumber: number; - errorCorrectLevel: number; - modules: (boolean | null)[][] | null; - moduleCount: number; - dataCache: number[] | null; - dataList: QR8bitByte[]; + typeNumber: number; + errorCorrectLevel: number; + modules: (boolean | null)[][] | null; + moduleCount: number; + dataCache: number[] | null; + dataList: QR8bitByte[]; - static PAD0 = 0xEC; - static PAD1 = 0x11; + static PAD0 = 0xEC; + static PAD1 = 0x11; - constructor(typeNumber: number, errorCorrectLevel: number) { - this.typeNumber = typeNumber; - this.errorCorrectLevel = errorCorrectLevel; - this.modules = null; - this.moduleCount = 0; - this.dataCache = null; - this.dataList = []; + constructor (typeNumber: number, errorCorrectLevel: number) { + this.typeNumber = typeNumber; + this.errorCorrectLevel = errorCorrectLevel; + this.modules = null; + this.moduleCount = 0; + this.dataCache = null; + this.dataList = []; + } + + addData (data: string): void { + const newData = new QR8bitByte(data); + this.dataList.push(newData); + this.dataCache = null; + } + + isDark (row: number, col: number): boolean { + if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) { + throw new Error(`${row},${col}`); } - - addData(data: string): void { - const newData = new QR8bitByte(data); - this.dataList.push(newData); - this.dataCache = null; + if (this.modules === null || this.modules[row] === null || this.modules[row]![col] === null) { + throw new Error(`Module at (${row},${col}) is null`); } + return this.modules[row]![col]!; + } - isDark(row: number, col: number): boolean { - if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) { - throw new Error(`${row},${col}`); - } - if (this.modules === null || this.modules[row] === null || this.modules[row]![col] === null) { - throw new Error(`Module at (${row},${col}) is null`); - } - return this.modules[row]![col]!; - } + getModuleCount (): number { + return this.moduleCount; + } - getModuleCount(): number { - return this.moduleCount; - } - - make(): void { - if (this.typeNumber < 1) { - let typeNumber = 1; - for (typeNumber = 1; typeNumber < 40; typeNumber++) { - const rsBlocks = getRSBlocks(typeNumber, this.errorCorrectLevel); - - const buffer = new QRBitBuffer(); - let totalDataCount = 0; - for (let i = 0; i < rsBlocks.length; i++) { - totalDataCount += rsBlocks[i]!.dataCount; - } - - for (let x = 0; x < this.dataList.length; x++) { - const data = this.dataList[x]; - buffer.put(data!.mode, 4); - buffer.put(data!.getLength(), getLengthInBits(data!.mode, typeNumber)); - data!.write(buffer); - } - if (buffer.getLengthInBits() <= totalDataCount * 8) - break; - } - this.typeNumber = typeNumber; - } - this.makeImpl(false, this.getBestMaskPattern()); - } - - makeImpl(test: boolean, maskPattern: number): void { - this.moduleCount = this.typeNumber * 4 + 17; - this.modules = new Array(this.moduleCount); - - for (let row = 0; row < this.moduleCount; row++) { - this.modules[row] = new Array(this.moduleCount); - for (let col = 0; col < this.moduleCount; col++) { - this.modules[row]![col] = null; - } - } - - this.setupPositionProbePattern(0, 0); - this.setupPositionProbePattern(this.moduleCount - 7, 0); - this.setupPositionProbePattern(0, this.moduleCount - 7); - this.setupPositionAdjustPattern(); - this.setupTimingPattern(); - this.setupTypeInfo(test, maskPattern); - - if (this.typeNumber >= 7) { - this.setupTypeNumber(test); - } - - if (this.dataCache === null) { - this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList); - } - - this.mapData(this.dataCache, maskPattern); - } - - setupPositionProbePattern(row: number, col: number): void { - for (let r = -1; r <= 7; r++) { - if (row + r <= -1 || this.moduleCount <= row + r) continue; - - for (let c = -1; c <= 7; c++) { - if (col + c <= -1 || this.moduleCount <= col + c) continue; - - if ((0 <= r && r <= 6 && (c === 0 || c === 6)) || - (0 <= c && c <= 6 && (r === 0 || r === 6)) || - (2 <= r && r <= 4 && 2 <= c && c <= 4)) { - this.modules![row + r]![col + c] = true; - } else { - this.modules![row + r]![col + c] = false; - } - } - } - } - - getBestMaskPattern(): number { - let minLostPoint = 0; - let pattern = 0; - - for (let i = 0; i < 8; i++) { - this.makeImpl(true, i); - const lostPoint = getLostPoint(this); - - if (i === 0 || minLostPoint > lostPoint) { - minLostPoint = lostPoint; - pattern = i; - } - } - - return pattern; - } - - createMovieClip(target_mc: { createEmptyMovieClip: (name: string, depth: number) => { beginFill: (color: number, alpha: number) => void; moveTo: (x: number, y: number) => void; lineTo: (x: number, y: number) => void; endFill: () => void; } }, instance_name: string, depth: number): { beginFill: (color: number, alpha: number) => void; moveTo: (x: number, y: number) => void; lineTo: (x: number, y: number) => void; endFill: () => void; } { - const qr_mc = target_mc.createEmptyMovieClip(instance_name, depth); - const cs = 1; - - this.make(); - - for (let row = 0; row < this.modules!.length; row++) { - const y = row * cs; - - for (let col = 0; col < this.modules![row]!.length; col++) { - const x = col * cs; - const dark = this.modules![row]![col]; - - if (dark) { - qr_mc.beginFill(0, 100); - qr_mc.moveTo(x, y); - qr_mc.lineTo(x + cs, y); - qr_mc.lineTo(x + cs, y + cs); - qr_mc.lineTo(x, y + cs); - qr_mc.endFill(); - } - } - } - - return qr_mc; - } - - setupTimingPattern(): void { - for (let r = 8; r < this.moduleCount - 8; r++) { - if (this.modules![r]![6] !== null) { - continue; - } - this.modules![r]![6] = (r % 2 === 0); - } - - for (let c = 8; c < this.moduleCount - 8; c++) { - if (this.modules![6]![c] !== null) { - continue; - } - this.modules![6]![c] = (c % 2 === 0); - } - } - - setupPositionAdjustPattern(): void { - const pos = getPatternPosition(this.typeNumber); - - for (let i = 0; i < pos.length; i++) { - for (let j = 0; j < pos.length; j++) { - const row = pos[i]; - const col = pos[j]; - - if (this.modules![row!]![col!] !== null) { - continue; - } - - for (let r = -2; r <= 2; r++) { - for (let c = -2; c <= 2; c++) { - if (Math.abs(r) === 2 || - Math.abs(c) === 2 || - (r === 0 && c === 0)) { - this.modules![row! + r]![col! + c] = true; - } else { - this.modules![row! + r]![col! + c] = false; - } - } - } - } - } - } - - setupTypeNumber(test: boolean): void { - const bits = getBCHTypeNumber(this.typeNumber); - let mod: boolean; - - for (let i = 0; i < 18; i++) { - mod = (!test && ((bits >> i) & 1) === 1); - this.modules![Math.floor(i / 3)]![i % 3 + this.moduleCount - 8 - 3] = mod; - } - - for (let x = 0; x < 18; x++) { - mod = (!test && ((bits >> x) & 1) === 1); - this.modules![x % 3 + this.moduleCount - 8 - 3]![Math.floor(x / 3)] = mod; - } - } - - setupTypeInfo(test: boolean, maskPattern: number): void { - const data = (this.errorCorrectLevel << 3) | maskPattern; - const bits = getBCHTypeInfo(data); - let mod: boolean; - - // vertical - for (let v = 0; v < 15; v++) { - mod = (!test && ((bits >> v) & 1) === 1); - - if (v < 6) { - this.modules![v]![8] = mod; - } else if (v < 8) { - this.modules![v + 1]![8] = mod; - } else { - this.modules![this.moduleCount - 15 + v]![8] = mod; - } - } - - // horizontal - for (let h = 0; h < 15; h++) { - mod = (!test && ((bits >> h) & 1) === 1); - - if (h < 8) { - this.modules![8]![this.moduleCount - h - 1] = mod; - } else if (h < 9) { - this.modules![8]![15 - h - 1 + 1] = mod; - } else { - this.modules![8]![15 - h - 1] = mod; - } - } - - // fixed module - this.modules![this.moduleCount - 8]![8] = (!test); - } - - mapData(data: number[], maskPattern: number): void { - let inc = -1; - let row = this.moduleCount - 1; - let bitIndex = 7; - let byteIndex = 0; - - for (let col = this.moduleCount - 1; col > 0; col -= 2) { - if (col === 6) col--; - - while (true) { - for (let c = 0; c < 2; c++) { - if (this.modules![row]![col - c] === null) { - let dark = false; - - if (byteIndex < data.length) { - dark = (((data[byteIndex] ?? 0) >>> bitIndex) & 1) === 1; - } - - const mask = getMask(maskPattern, row, col - c); - - if (mask) { - dark = !dark; - } - - this.modules![row]![col - c] = dark; - bitIndex--; - - if (bitIndex === -1) { - byteIndex++; - bitIndex = 7; - } - } - } - - row += inc; - - if (row < 0 || this.moduleCount <= row) { - row -= inc; - inc = -inc; - break; - } - } - } - } - - static createData(typeNumber: number, errorCorrectLevel: number, dataList: QR8bitByte[]): number[] { - const rsBlocks = getRSBlocks(typeNumber, errorCorrectLevel); + make (): void { + if (this.typeNumber < 1) { + let typeNumber = 1; + for (typeNumber = 1; typeNumber < 40; typeNumber++) { + const rsBlocks = getRSBlocks(typeNumber, this.errorCorrectLevel); const buffer = new QRBitBuffer(); - - for (let i = 0; i < dataList.length; i++) { - const data = dataList[i]; - buffer.put(data!.mode, 4); - buffer.put(data!.getLength(), getLengthInBits(data!.mode, typeNumber)); - data!.write(buffer); - } - - // calc num max data. let totalDataCount = 0; - for (let x = 0; x < rsBlocks.length; x++) { - totalDataCount += rsBlocks[x]!.dataCount; + for (let i = 0; i < rsBlocks.length; i++) { + totalDataCount += rsBlocks[i]!.dataCount; } - if (buffer.getLengthInBits() > totalDataCount * 8) { - throw new Error(`code length overflow. (${buffer.getLengthInBits()} > ${totalDataCount * 8})`); + for (let x = 0; x < this.dataList.length; x++) { + const data = this.dataList[x]; + buffer.put(data!.mode, 4); + buffer.put(data!.getLength(), getLengthInBits(data!.mode, typeNumber)); + data!.write(buffer); } + if (buffer.getLengthInBits() <= totalDataCount * 8) { break; } + } + this.typeNumber = typeNumber; + } + this.makeImpl(false, this.getBestMaskPattern()); + } - // end code - if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { - buffer.put(0, 4); - } + makeImpl (test: boolean, maskPattern: number): void { + this.moduleCount = this.typeNumber * 4 + 17; + this.modules = new Array(this.moduleCount); - // padding - while (buffer.getLengthInBits() % 8 !== 0) { - buffer.putBit(false); - } - - // padding - while (true) { - if (buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(QRCode.PAD0, 8); - - if (buffer.getLengthInBits() >= totalDataCount * 8) { - break; - } - buffer.put(QRCode.PAD1, 8); - } - - return QRCode.createBytes(buffer, rsBlocks); + for (let row = 0; row < this.moduleCount; row++) { + this.modules[row] = new Array(this.moduleCount); + for (let col = 0; col < this.moduleCount; col++) { + this.modules[row]![col] = null; + } } - static createBytes(buffer: QRBitBuffer, rsBlocks: RSBlock[]): number[] { - let offset = 0; + this.setupPositionProbePattern(0, 0); + this.setupPositionProbePattern(this.moduleCount - 7, 0); + this.setupPositionProbePattern(0, this.moduleCount - 7); + this.setupPositionAdjustPattern(); + this.setupTimingPattern(); + this.setupTypeInfo(test, maskPattern); - let maxDcCount = 0; - let maxEcCount = 0; - - const dcdata: number[][] = new Array(rsBlocks.length); - const ecdata: number[][] = new Array(rsBlocks.length); - - for (let r = 0; r < rsBlocks.length; r++) { - const dcCount = rsBlocks[r]!.dataCount; - const ecCount = rsBlocks[r]!.totalCount - dcCount; - - maxDcCount = Math.max(maxDcCount, dcCount); - maxEcCount = Math.max(maxEcCount, ecCount); - - dcdata[r] = new Array(dcCount); - - for (let i = 0; i < dcdata[r]!.length; i++) { - dcdata[r]![i] = 0xff & buffer.buffer[i + offset]!; - } - offset += dcCount; - - const rsPoly = getErrorCorrectPolynomial(ecCount); - const rawPoly = new QRPolynomial(dcdata[r]!, rsPoly.getLength() - 1); - - const modPoly = rawPoly.mod(rsPoly); - ecdata[r] = new Array(rsPoly.getLength() - 1); - for (let x = 0; x < ecdata[r]!.length; x++) { - const modIndex = x + modPoly.getLength() - ecdata[r]!.length; - ecdata[r]![x] = (modIndex >= 0) ? modPoly.get(modIndex) : 0; - } - } - - let totalCodeCount = 0; - for (let y = 0; y < rsBlocks.length; y++) { - totalCodeCount += rsBlocks[y]!.totalCount; - } - - const data: number[] = new Array(totalCodeCount); - let index = 0; - - for (let z = 0; z < maxDcCount; z++) { - for (let s = 0; s < rsBlocks.length; s++) { - if (z < dcdata[s]!.length) { - data[index++] = dcdata[s]![z]!; - } - } - } - - for (let xx = 0; xx < maxEcCount; xx++) { - for (let t = 0; t < rsBlocks.length; t++) { - if (xx < ecdata[t]!.length) { - data[index++] = ecdata[t]![xx]!; - } - } - } - - return data; + if (this.typeNumber >= 7) { + this.setupTypeNumber(test); } + + if (this.dataCache === null) { + this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList); + } + + this.mapData(this.dataCache, maskPattern); + } + + setupPositionProbePattern (row: number, col: number): void { + for (let r = -1; r <= 7; r++) { + if (row + r <= -1 || this.moduleCount <= row + r) continue; + + for (let c = -1; c <= 7; c++) { + if (col + c <= -1 || this.moduleCount <= col + c) continue; + + if ((r >= 0 && r <= 6 && (c === 0 || c === 6)) || + (c >= 0 && c <= 6 && (r === 0 || r === 6)) || + (r >= 2 && r <= 4 && c >= 2 && c <= 4)) { + this.modules![row + r]![col + c] = true; + } else { + this.modules![row + r]![col + c] = false; + } + } + } + } + + getBestMaskPattern (): number { + let minLostPoint = 0; + let pattern = 0; + + for (let i = 0; i < 8; i++) { + this.makeImpl(true, i); + const lostPoint = getLostPoint(this); + + if (i === 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } + + return pattern; + } + + createMovieClip (target_mc: { createEmptyMovieClip: (name: string, depth: number) => { beginFill: (color: number, alpha: number) => void; moveTo: (x: number, y: number) => void; lineTo: (x: number, y: number) => void; endFill: () => void; }; }, instance_name: string, depth: number): { beginFill: (color: number, alpha: number) => void; moveTo: (x: number, y: number) => void; lineTo: (x: number, y: number) => void; endFill: () => void; } { + const qr_mc = target_mc.createEmptyMovieClip(instance_name, depth); + const cs = 1; + + this.make(); + + for (let row = 0; row < this.modules!.length; row++) { + const y = row * cs; + + for (let col = 0; col < this.modules![row]!.length; col++) { + const x = col * cs; + const dark = this.modules![row]![col]; + + if (dark) { + qr_mc.beginFill(0, 100); + qr_mc.moveTo(x, y); + qr_mc.lineTo(x + cs, y); + qr_mc.lineTo(x + cs, y + cs); + qr_mc.lineTo(x, y + cs); + qr_mc.endFill(); + } + } + } + + return qr_mc; + } + + setupTimingPattern (): void { + for (let r = 8; r < this.moduleCount - 8; r++) { + if (this.modules![r]![6] !== null) { + continue; + } + this.modules![r]![6] = (r % 2 === 0); + } + + for (let c = 8; c < this.moduleCount - 8; c++) { + if (this.modules![6]![c] !== null) { + continue; + } + this.modules![6]![c] = (c % 2 === 0); + } + } + + setupPositionAdjustPattern (): void { + const pos = getPatternPosition(this.typeNumber); + + for (let i = 0; i < pos.length; i++) { + for (let j = 0; j < pos.length; j++) { + const row = pos[i]; + const col = pos[j]; + + if (this.modules![row!]![col!] !== null) { + continue; + } + + for (let r = -2; r <= 2; r++) { + for (let c = -2; c <= 2; c++) { + if (Math.abs(r) === 2 || + Math.abs(c) === 2 || + (r === 0 && c === 0)) { + this.modules![row! + r]![col! + c] = true; + } else { + this.modules![row! + r]![col! + c] = false; + } + } + } + } + } + } + + setupTypeNumber (test: boolean): void { + const bits = getBCHTypeNumber(this.typeNumber); + let mod: boolean; + + for (let i = 0; i < 18; i++) { + mod = (!test && ((bits >> i) & 1) === 1); + this.modules![Math.floor(i / 3)]![i % 3 + this.moduleCount - 8 - 3] = mod; + } + + for (let x = 0; x < 18; x++) { + mod = (!test && ((bits >> x) & 1) === 1); + this.modules![x % 3 + this.moduleCount - 8 - 3]![Math.floor(x / 3)] = mod; + } + } + + setupTypeInfo (test: boolean, maskPattern: number): void { + const data = (this.errorCorrectLevel << 3) | maskPattern; + const bits = getBCHTypeInfo(data); + let mod: boolean; + + // vertical + for (let v = 0; v < 15; v++) { + mod = (!test && ((bits >> v) & 1) === 1); + + if (v < 6) { + this.modules![v]![8] = mod; + } else if (v < 8) { + this.modules![v + 1]![8] = mod; + } else { + this.modules![this.moduleCount - 15 + v]![8] = mod; + } + } + + // horizontal + for (let h = 0; h < 15; h++) { + mod = (!test && ((bits >> h) & 1) === 1); + + if (h < 8) { + this.modules![8]![this.moduleCount - h - 1] = mod; + } else if (h < 9) { + this.modules![8]![15 - h - 1 + 1] = mod; + } else { + this.modules![8]![15 - h - 1] = mod; + } + } + + // fixed module + this.modules![this.moduleCount - 8]![8] = (!test); + } + + mapData (data: number[], maskPattern: number): void { + let inc = -1; + let row = this.moduleCount - 1; + let bitIndex = 7; + let byteIndex = 0; + + for (let col = this.moduleCount - 1; col > 0; col -= 2) { + if (col === 6) col--; + + while (true) { + for (let c = 0; c < 2; c++) { + if (this.modules![row]![col - c] === null) { + let dark = false; + + if (byteIndex < data.length) { + dark = (((data[byteIndex] ?? 0) >>> bitIndex) & 1) === 1; + } + + const mask = getMask(maskPattern, row, col - c); + + if (mask) { + dark = !dark; + } + + this.modules![row]![col - c] = dark; + bitIndex--; + + if (bitIndex === -1) { + byteIndex++; + bitIndex = 7; + } + } + } + + row += inc; + + if (row < 0 || this.moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + } + + static createData (typeNumber: number, errorCorrectLevel: number, dataList: QR8bitByte[]): number[] { + const rsBlocks = getRSBlocks(typeNumber, errorCorrectLevel); + + const buffer = new QRBitBuffer(); + + for (let i = 0; i < dataList.length; i++) { + const data = dataList[i]; + buffer.put(data!.mode, 4); + buffer.put(data!.getLength(), getLengthInBits(data!.mode, typeNumber)); + data!.write(buffer); + } + + // calc num max data. + let totalDataCount = 0; + for (let x = 0; x < rsBlocks.length; x++) { + totalDataCount += rsBlocks[x]!.dataCount; + } + + if (buffer.getLengthInBits() > totalDataCount * 8) { + throw new Error(`code length overflow. (${buffer.getLengthInBits()} > ${totalDataCount * 8})`); + } + + // end code + if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { + buffer.put(0, 4); + } + + // padding + while (buffer.getLengthInBits() % 8 !== 0) { + buffer.putBit(false); + } + + // padding + while (true) { + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(QRCode.PAD0, 8); + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(QRCode.PAD1, 8); + } + + return QRCode.createBytes(buffer, rsBlocks); + } + + static createBytes (buffer: QRBitBuffer, rsBlocks: RSBlock[]): number[] { + let offset = 0; + + let maxDcCount = 0; + let maxEcCount = 0; + + const dcdata: number[][] = new Array(rsBlocks.length); + const ecdata: number[][] = new Array(rsBlocks.length); + + for (let r = 0; r < rsBlocks.length; r++) { + const dcCount = rsBlocks[r]!.dataCount; + const ecCount = rsBlocks[r]!.totalCount - dcCount; + + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); + + dcdata[r] = new Array(dcCount); + + for (let i = 0; i < dcdata[r]!.length; i++) { + dcdata[r]![i] = 0xff & buffer.buffer[i + offset]!; + } + offset += dcCount; + + const rsPoly = getErrorCorrectPolynomial(ecCount); + const rawPoly = new QRPolynomial(dcdata[r]!, rsPoly.getLength() - 1); + + const modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.getLength() - 1); + for (let x = 0; x < ecdata[r]!.length; x++) { + const modIndex = x + modPoly.getLength() - ecdata[r]!.length; + ecdata[r]![x] = (modIndex >= 0) ? modPoly.get(modIndex) : 0; + } + } + + let totalCodeCount = 0; + for (let y = 0; y < rsBlocks.length; y++) { + totalCodeCount += rsBlocks[y]!.totalCount; + } + + const data: number[] = new Array(totalCodeCount); + let index = 0; + + for (let z = 0; z < maxDcCount; z++) { + for (let s = 0; s < rsBlocks.length; s++) { + if (z < dcdata[s]!.length) { + data[index++] = dcdata[s]![z]!; + } + } + } + + for (let xx = 0; xx < maxEcCount; xx++) { + for (let t = 0; t < rsBlocks.length; t++) { + if (xx < ecdata[t]!.length) { + data[index++] = ecdata[t]![xx]!; + } + } + } + + return data; + } } -export default QRCode; \ No newline at end of file +export default QRCode; diff --git a/src/shell/base.ts b/src/shell/base.ts index 88540a0b..eafd477e 100644 --- a/src/shell/base.ts +++ b/src/shell/base.ts @@ -5,17 +5,17 @@ import { NodeIKernelLoginListener, NodeIKernelSessionListener } from '@/core/lis import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from '@/core/adapters'; import { NapCatPathWrapper } from '@/common/path'; import { - genSessionConfig, - InstanceContext, - loadQQWrapper, - NapCatCore, - NapCatCoreWorkingEnv, - NodeIQQNTStartupSessionWrapper, - NodeIQQNTWrapperEngine, - NodeIQQNTWrapperSession, - PlatformType, - WrapperNodeApi, - WrapperSessionInitConfig, + genSessionConfig, + InstanceContext, + loadQQWrapper, + NapCatCore, + NapCatCoreWorkingEnv, + NodeIQQNTStartupSessionWrapper, + NodeIQQNTWrapperEngine, + NodeIQQNTWrapperSession, + PlatformType, + WrapperNodeApi, + WrapperSessionInitConfig, } from '@/core'; import { QQBasicInfoWrapper } from '@/common/qq-basic-info'; import { hostname, systemVersion } from '@/common/system'; @@ -36,416 +36,413 @@ import { FFmpegService } from '@/common/ffmpeg'; import { connectToNamedPipe } from '@/shell/pipe'; import { NativePacketHandler } from '@/core/packet/handler/client'; // NapCat Shell App ES 入口文件 -async function handleUncaughtExceptions(logger: LogWrapper) { - process.on('uncaughtException', (err) => { - logger.logError('[NapCat] [Error] Unhandled Exception:', err.message); - }); - process.on('unhandledRejection', (reason) => { - logger.logError('[NapCat] [Error] unhandledRejection:', reason); - }); +async function handleUncaughtExceptions (logger: LogWrapper) { + process.on('uncaughtException', (err) => { + logger.logError('[NapCat] [Error] Unhandled Exception:', err.message); + }); + process.on('unhandledRejection', (reason) => { + logger.logError('[NapCat] [Error] unhandledRejection:', reason); + }); } -function getDataPaths(wrapper: WrapperNodeApi): [string, string] { - if (os.platform() === 'darwin') { - const userPath = os.homedir(); - const appDataPath = path.resolve(userPath, './Library/Application Support/QQ'); - return [appDataPath, path.join(appDataPath, 'global')]; - } - let dataPath = wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig(); - if (!dataPath) { - dataPath = path.resolve(os.homedir(), './.config/QQ'); - fs.mkdirSync(dataPath, { recursive: true }); - } - const dataPathGlobal = path.resolve(dataPath, './nt_qq/global'); - return [dataPath, dataPathGlobal]; +function getDataPaths (wrapper: WrapperNodeApi): [string, string] { + if (os.platform() === 'darwin') { + const userPath = os.homedir(); + const appDataPath = path.resolve(userPath, './Library/Application Support/QQ'); + return [appDataPath, path.join(appDataPath, 'global')]; + } + let dataPath = wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig(); + if (!dataPath) { + dataPath = path.resolve(os.homedir(), './.config/QQ'); + fs.mkdirSync(dataPath, { recursive: true }); + } + const dataPathGlobal = path.resolve(dataPath, './nt_qq/global'); + return [dataPath, dataPathGlobal]; } -function getPlatformType(): PlatformType { - const platformMapping: Partial> = { - win32: PlatformType.KWINDOWS, - darwin: PlatformType.KMAC, - linux: PlatformType.KLINUX, - }; - return platformMapping[os.platform()] ?? PlatformType.KWINDOWS; +function getPlatformType (): PlatformType { + const platformMapping: Partial> = { + win32: PlatformType.KWINDOWS, + darwin: PlatformType.KMAC, + linux: PlatformType.KLINUX, + }; + return platformMapping[os.platform()] ?? PlatformType.KWINDOWS; } -async function initializeEngine( - engine: NodeIQQNTWrapperEngine, - basicInfoWrapper: QQBasicInfoWrapper, - dataPathGlobal: string, - systemPlatform: PlatformType, - systemVersion: string +async function initializeEngine ( + engine: NodeIQQNTWrapperEngine, + basicInfoWrapper: QQBasicInfoWrapper, + dataPathGlobal: string, + systemPlatform: PlatformType, + systemVersion: string ) { - engine.initWithDeskTopConfig( - { - base_path_prefix: '', - platform_type: systemPlatform, - app_type: 4, - app_version: basicInfoWrapper.getFullQQVersion(), - os_version: systemVersion, - use_xlog: false, - qua: basicInfoWrapper.QQVersionQua ?? '', - global_path_config: { - desktopGlobalPath: dataPathGlobal, - }, - thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 }, - }, - new NodeIGlobalAdapter(), - ); + engine.initWithDeskTopConfig( + { + base_path_prefix: '', + platform_type: systemPlatform, + app_type: 4, + app_version: basicInfoWrapper.getFullQQVersion(), + os_version: systemVersion, + use_xlog: false, + qua: basicInfoWrapper.QQVersionQua ?? '', + global_path_config: { + desktopGlobalPath: dataPathGlobal, + }, + thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 }, + }, + new NodeIGlobalAdapter() + ); } -async function initializeLoginService( - loginService: NodeIKernelLoginService, - basicInfoWrapper: QQBasicInfoWrapper, - dataPathGlobal: string, - systemVersion: string, - hostname: string +async function initializeLoginService ( + loginService: NodeIKernelLoginService, + basicInfoWrapper: QQBasicInfoWrapper, + dataPathGlobal: string, + systemVersion: string, + hostname: string ) { - loginService.initConfig({ - machineId: '', - appid: basicInfoWrapper.QQVersionAppid ?? '', - platVer: systemVersion, - commonPath: dataPathGlobal, - clientVer: basicInfoWrapper.getFullQQVersion(), - hostName: hostname, - }); + loginService.initConfig({ + machineId: '', + appid: basicInfoWrapper.QQVersionAppid ?? '', + platVer: systemVersion, + commonPath: dataPathGlobal, + clientVer: basicInfoWrapper.getFullQQVersion(), + hostName: hostname, + }); } -async function handleLogin( - loginService: NodeIKernelLoginService, - logger: LogWrapper, - pathWrapper: NapCatPathWrapper, - quickLoginUin: string | undefined, - historyLoginList: LoginListItem[] +async function handleLogin ( + loginService: NodeIKernelLoginService, + logger: LogWrapper, + pathWrapper: NapCatPathWrapper, + quickLoginUin: string | undefined, + historyLoginList: LoginListItem[] ): Promise { - let context = { isLogined: false }; - let inner_resolve: (value: SelfInfo) => void; - let selfInfo: Promise = new Promise((resolve) => { - inner_resolve = resolve; + const context = { isLogined: false }; + let inner_resolve: (value: SelfInfo) => void; + const selfInfo: Promise = new Promise((resolve) => { + inner_resolve = resolve; + }); + // 连接服务 + + const loginListener = new NodeIKernelLoginListener(); + loginListener.onUserLoggedIn = (userid: string) => { + logger.logError(`当前账号(${userid})已登录,无法重复登录`); + }; + loginListener.onQRCodeLoginSucceed = async (loginResult) => { + context.isLogined = true; + inner_resolve({ + uid: loginResult.uid, + uin: loginResult.uin, + nick: '', + online: true, }); - // 连接服务 + }; + loginListener.onLoginConnected = () => { + waitForNetworkConnection(loginService, logger).then(() => { + handleLoginInner(context, logger, loginService, quickLoginUin, historyLoginList).then().catch(e => logger.logError(e)); + loginListener.onLoginConnected = () => { }; + }); + }; + loginListener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => { + WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl); - const loginListener = new NodeIKernelLoginListener(); - loginListener.onUserLoggedIn = (userid: string) => { - logger.logError(`当前账号(${userid})已登录,无法重复登录`); - }; - loginListener.onQRCodeLoginSucceed = async (loginResult) => { - context.isLogined = true; - inner_resolve({ - uid: loginResult.uid, - uin: loginResult.uin, - nick: '', - online: true, - }); + const realBase64 = pngBase64QrcodeData.replace(/^data:image\/\w+;base64,/, ''); + const buffer = Buffer.from(realBase64, 'base64'); + logger.logWarn('请扫描下面的二维码,然后在手Q上授权登录:'); + const qrcodePath = path.join(pathWrapper.cachePath, 'qrcode.png'); + qrcode.generate(qrcodeUrl, { small: true }, (res) => { + logger.logWarn([ + '\n', + res, + '二维码解码URL: ' + qrcodeUrl, + '如果控制台二维码无法扫码,可以复制解码url到二维码生成网站生成二维码再扫码,也可以打开下方的二维码路径图片进行扫码。', + ].join('\n')); + fs.writeFile(qrcodePath, buffer, {}, () => { + logger.logWarn('二维码已保存到', qrcodePath); + }); + }); + }; - }; - loginListener.onLoginConnected = () => { - waitForNetworkConnection(loginService, logger).then(() => { - handleLoginInner(context, logger, loginService, quickLoginUin, historyLoginList).then().catch(e => logger.logError(e)); - loginListener.onLoginConnected = () => { }; - }); + loginListener.onQRCodeSessionFailed = (errType: number, errCode: number) => { + if (!context.isLogined) { + logger.logError('[Core] [Login] Login Error,ErrType: ', errType, ' ErrCode:', errCode); + if (errType == 1 && errCode == 3) { + // 二维码过期刷新 + } + loginService.getQRCodePicture(); } - loginListener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => { - WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl); + }; - const realBase64 = pngBase64QrcodeData.replace(/^data:image\/\w+;base64,/, ''); - const buffer = Buffer.from(realBase64, 'base64'); - logger.logWarn('请扫描下面的二维码,然后在手Q上授权登录:'); - const qrcodePath = path.join(pathWrapper.cachePath, 'qrcode.png'); - qrcode.generate(qrcodeUrl, { small: true }, (res) => { - logger.logWarn([ - '\n', - res, - '二维码解码URL: ' + qrcodeUrl, - '如果控制台二维码无法扫码,可以复制解码url到二维码生成网站生成二维码再扫码,也可以打开下方的二维码路径图片进行扫码。', - ].join('\n')); - fs.writeFile(qrcodePath, buffer, {}, () => { - logger.logWarn('二维码已保存到', qrcodePath); - }); - }); - }; + loginListener.onLoginFailed = (...args) => { + logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args)); + }; - loginListener.onQRCodeSessionFailed = (errType: number, errCode: number) => { - if (!context.isLogined) { - logger.logError('[Core] [Login] Login Error,ErrType: ', errType, ' ErrCode:', errCode); - if (errType == 1 && errCode == 3) { - // 二维码过期刷新 - } - loginService.getQRCodePicture(); - } - }; - - loginListener.onLoginFailed = (...args) => { - logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args)); - }; - - loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger)); - loginService.connect(); - return await selfInfo; + loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger)); + loginService.connect(); + return await selfInfo; } -async function handleLoginInner(context: { isLogined: boolean }, logger: LogWrapper, loginService: NodeIKernelLoginService, quickLoginUin: string | undefined, historyLoginList: LoginListItem[]) { - WebUiDataRuntime.setQuickLoginCall(async (uin: string) => { - return await new Promise((resolve) => { - if (uin) { - logger.log('正在快速登录 ', uin); - loginService.quickLoginWithUin(uin).then(res => { - if (res.loginErrorInfo.errMsg) { - resolve({ result: false, message: res.loginErrorInfo.errMsg }); - } - resolve({ result: true, message: '' }); - }).catch((e) => { - logger.logError(e); - resolve({ result: false, message: '快速登录发生错误' }); - }); - } else { - resolve({ result: false, message: '快速登录失败' }); - } +async function handleLoginInner (context: { isLogined: boolean; }, logger: LogWrapper, loginService: NodeIKernelLoginService, quickLoginUin: string | undefined, historyLoginList: LoginListItem[]) { + WebUiDataRuntime.setQuickLoginCall(async (uin: string) => { + return await new Promise((resolve) => { + if (uin) { + logger.log('正在快速登录 ', uin); + loginService.quickLoginWithUin(uin).then(res => { + if (res.loginErrorInfo.errMsg) { + resolve({ result: false, message: res.loginErrorInfo.errMsg }); + } + resolve({ result: true, message: '' }); + }).catch((e) => { + logger.logError(e); + resolve({ result: false, message: '快速登录发生错误' }); }); + } else { + resolve({ result: false, message: '快速登录失败' }); + } }); - if (quickLoginUin) { - if (historyLoginList.some(u => u.uin === quickLoginUin)) { - logger.log('正在快速登录 ', quickLoginUin); - loginService.quickLoginWithUin(quickLoginUin) - .then(result => { - if (result.loginErrorInfo.errMsg) { - logger.logError('快速登录错误:', result.loginErrorInfo.errMsg); - if (!context.isLogined) loginService.getQRCodePicture(); - } - }) - .catch(); - } else { - logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式'); + }); + if (quickLoginUin) { + if (historyLoginList.some(u => u.uin === quickLoginUin)) { + logger.log('正在快速登录 ', quickLoginUin); + loginService.quickLoginWithUin(quickLoginUin) + .then(result => { + if (result.loginErrorInfo.errMsg) { + logger.logError('快速登录错误:', result.loginErrorInfo.errMsg); if (!context.isLogined) loginService.getQRCodePicture(); - } + } + }) + .catch(); } else { - logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式'); - if (historyLoginList.length > 0) { - logger.log(`可用于快速登录的 QQ:\n${historyLoginList - .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`) - .join('\n') - }`); - } - loginService.getQRCodePicture(); - try { - await WebUiDataRuntime.runWebUiConfigQuickFunction(); - } catch (error) { - logger.logError('WebUi 快速登录失败 执行失败', error); - } + logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式'); + if (!context.isLogined) loginService.getQRCodePicture(); } - - loginService.getLoginList().then((res) => { - // 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList - const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin); - WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString())); - WebUiDataRuntime.setQQNewLoginList(list); - }); -} - -async function initializeSession( - session: NodeIQQNTWrapperSession, - sessionConfig: WrapperSessionInitConfig, - startupSession: NodeIQQNTStartupSessionWrapper | null -) { - return new Promise((resolve, reject) => { - const sessionListener = new NodeIKernelSessionListener(); - sessionListener.onOpentelemetryInit = (info) => { - if (info.is_init) { - resolve(); - } else { - reject(new Error('opentelemetry init failed')); - } - }; - session.init( - sessionConfig, - new NodeIDependsAdapter(), - new NodeIDispatcherAdapter(), - sessionListener, - ); - if (startupSession) { - startupSession.start(); - } else { - try { - session.startNT(0); - } catch { - try { - session.startNT(); - } catch (e: unknown) { - reject(new Error('init failed ' + (e as Error).message)); - } - } - } - }); -} -async function handleProxy(session: NodeIQQNTWrapperSession, logger: LogWrapper) { - if (process.env['NAPCAT_PROXY_PORT']) { - session.getMSFService().setNetworkProxy({ - userName: '', - userPwd: '', - address: process.env['NAPCAT_PROXY_ADDRESS'] || '127.0.0.1', - port: +process.env['NAPCAT_PROXY_PORT'], - proxyType: 2, - domain: '', - isSocket: true - }); - logger.logWarn('已设置代理', process.env['NAPCAT_PROXY_ADDRESS'], process.env['NAPCAT_PROXY_PORT']); - } else if (process.env['NAPCAT_PROXY_CLOSE']) { - session.getMSFService().setNetworkProxy({ - userName: '', - userPwd: '', - address: '', - port: 0, - proxyType: 0, - domain: '', - isSocket: false - }); + } else { + logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式'); + if (historyLoginList.length > 0) { + logger.log(`可用于快速登录的 QQ:\n${historyLoginList + .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`) + .join('\n') + }`); } -} - -async function waitForNetworkConnection(loginService: NodeIKernelLoginService, logger: LogWrapper) { - let network_ok = false; - let tryCount = 0; - while (!network_ok) { - network_ok = loginService.getMsfStatus() !== 3;// win 11 0连接 1未连接 - logger.log('等待网络连接...'); - await sleep(500); - tryCount++; - } - logger.log('网络已连接'); - return network_ok; -} - -export async function NCoreInitShell() { - console.log('NapCat Shell App Loading...'); - const pathWrapper = new NapCatPathWrapper(); - const logger = new LogWrapper(pathWrapper.logsPath); - handleUncaughtExceptions(logger); - - // 初始化 FFmpeg 服务 - await FFmpegService.init(pathWrapper.binaryPath, logger); - - await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e)); - const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); - const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion()); - const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 - - // nativePacketHandler.onAll((packet) => { - // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); - // }); - await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion()); - - const o3Service = wrapper.NodeIO3MiscService.get(); - o3Service.addO3MiscListener(new NodeIO3MiscListener()); - - logger.log('[NapCat] [Core] NapCat.Core Version: ' + napCatVersion); - InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e)); - - const engine = wrapper.NodeIQQNTWrapperEngine.get(); - const loginService = wrapper.NodeIKernelLoginService.get(); - let session: NodeIQQNTWrapperSession; - let startupSession: NodeIQQNTStartupSessionWrapper | null = null; + loginService.getQRCodePicture(); try { - startupSession = wrapper.NodeIQQNTStartupSessionWrapper.create(); - session = wrapper.NodeIQQNTWrapperSession.getNTWrapperSession('nt_1'); - } catch (e: unknown) { - try { - session = wrapper.NodeIQQNTWrapperSession.create(); - } catch (error) { - logger.logError('创建 StartupSession 失败', e); - logger.logError('创建 Session 失败', error); - throw error; - } + await WebUiDataRuntime.runWebUiConfigQuickFunction(); + } catch (error) { + logger.logError('WebUi 快速登录失败 执行失败', error); } - const [dataPath, dataPathGlobal] = getDataPaths(wrapper); - const systemPlatform = getPlatformType(); + } - if (!basicInfoWrapper.QQVersionAppid || !basicInfoWrapper.QQVersionQua) throw new Error('QQVersionAppid or QQVersionQua is not defined'); - - await initializeEngine(engine, basicInfoWrapper, dataPathGlobal, systemPlatform, systemVersion); - await initializeLoginService(loginService, basicInfoWrapper, dataPathGlobal, systemVersion, hostname); - handleProxy(session, logger); - program.option('-q, --qq [number]', 'QQ号').parse(process.argv); - const cmdOptions = program.opts(); - const quickLoginUin = cmdOptions['qq']; - const historyLoginList = (await loginService.getLoginList()).LocalLoginInfoList; - - const dataTimestape = new Date().getTime().toString(); - o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']); - - const selfInfo = await handleLogin(loginService, logger, pathWrapper, quickLoginUin, historyLoginList); - const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129'; - o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex'))); - - let guid = loginService.getMachineGuid(); - guid = guid.slice(0, 8) + '-' + guid.slice(8, 12) + '-' + guid.slice(12, 16) + '-' + guid.slice(16, 20) + '-' + guid.slice(20); - o3Service.reportAmgomWeather('login', 'a6', [dataTimestape, '184', '329']); - - const sessionConfig = await genSessionConfig( - guid, - basicInfoWrapper.QQVersionAppid, - basicInfoWrapper.getFullQQVersion(), - selfInfo.uin, - selfInfo.uid, - dataPath, - ); - - await initializeSession(session, sessionConfig, startupSession); - - const accountDataPath = path.resolve(dataPath, './NapCat/data'); - //判断dataPath是否为根目录 或者 D:/ 之类的盘目录 - if (dataPath !== '/' && /^[a-zA-Z]:\\$/.test(dataPath) === false) { - try { - fs.mkdirSync(accountDataPath, { recursive: true }); - } catch (error) { - logger.logError('创建accountDataPath失败', error); - } - } - - logger.logDebug('本账号数据/缓存目录:', accountDataPath); - - await new NapCatShell( - wrapper, - session, - logger, - loginService, - selfInfo, - basicInfoWrapper, - pathWrapper, - nativePacketHandler - ).InitNapCat(); + loginService.getLoginList().then((res) => { + // 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList + const list = res.LocalLoginInfoList.filter((item) => item.isQuickLogin); + WebUiDataRuntime.setQQQuickLoginList(list.map((item) => item.uin.toString())); + WebUiDataRuntime.setQQNewLoginList(list); + }); } +async function initializeSession ( + session: NodeIQQNTWrapperSession, + sessionConfig: WrapperSessionInitConfig, + startupSession: NodeIQQNTStartupSessionWrapper | null +) { + return new Promise((resolve, reject) => { + const sessionListener = new NodeIKernelSessionListener(); + sessionListener.onOpentelemetryInit = (info) => { + if (info.is_init) { + resolve(); + } else { + reject(new Error('opentelemetry init failed')); + } + }; + session.init( + sessionConfig, + new NodeIDependsAdapter(), + new NodeIDispatcherAdapter(), + sessionListener + ); + if (startupSession) { + startupSession.start(); + } else { + try { + session.startNT(0); + } catch { + try { + session.startNT(); + } catch (e: unknown) { + reject(new Error('init failed ' + (e as Error).message)); + } + } + } + }); +} +async function handleProxy (session: NodeIQQNTWrapperSession, logger: LogWrapper) { + if (process.env['NAPCAT_PROXY_PORT']) { + session.getMSFService().setNetworkProxy({ + userName: '', + userPwd: '', + address: process.env['NAPCAT_PROXY_ADDRESS'] || '127.0.0.1', + port: +process.env['NAPCAT_PROXY_PORT'], + proxyType: 2, + domain: '', + isSocket: true, + }); + logger.logWarn('已设置代理', process.env['NAPCAT_PROXY_ADDRESS'], process.env['NAPCAT_PROXY_PORT']); + } else if (process.env['NAPCAT_PROXY_CLOSE']) { + session.getMSFService().setNetworkProxy({ + userName: '', + userPwd: '', + address: '', + port: 0, + proxyType: 0, + domain: '', + isSocket: false, + }); + } +} + +async function waitForNetworkConnection (loginService: NodeIKernelLoginService, logger: LogWrapper) { + let network_ok = false; + let tryCount = 0; + while (!network_ok) { + network_ok = loginService.getMsfStatus() !== 3;// win 11 0连接 1未连接 + logger.log('等待网络连接...'); + await sleep(500); + tryCount++; + } + logger.log('网络已连接'); + return network_ok; +} + +export async function NCoreInitShell () { + console.log('NapCat Shell App Loading...'); + const pathWrapper = new NapCatPathWrapper(); + const logger = new LogWrapper(pathWrapper.logsPath); + handleUncaughtExceptions(logger); + + // 初始化 FFmpeg 服务 + await FFmpegService.init(pathWrapper.binaryPath, logger); + + await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e)); + const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); + const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion()); + const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 + + // nativePacketHandler.onAll((packet) => { + // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); + // }); + await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion()); + + const o3Service = wrapper.NodeIO3MiscService.get(); + o3Service.addO3MiscListener(new NodeIO3MiscListener()); + + logger.log('[NapCat] [Core] NapCat.Core Version: ' + napCatVersion); + InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e)); + + const engine = wrapper.NodeIQQNTWrapperEngine.get(); + const loginService = wrapper.NodeIKernelLoginService.get(); + let session: NodeIQQNTWrapperSession; + let startupSession: NodeIQQNTStartupSessionWrapper | null = null; + try { + startupSession = wrapper.NodeIQQNTStartupSessionWrapper.create(); + session = wrapper.NodeIQQNTWrapperSession.getNTWrapperSession('nt_1'); + } catch (e: unknown) { + try { + session = wrapper.NodeIQQNTWrapperSession.create(); + } catch (error) { + logger.logError('创建 StartupSession 失败', e); + logger.logError('创建 Session 失败', error); + throw error; + } + } + const [dataPath, dataPathGlobal] = getDataPaths(wrapper); + const systemPlatform = getPlatformType(); + + if (!basicInfoWrapper.QQVersionAppid || !basicInfoWrapper.QQVersionQua) throw new Error('QQVersionAppid or QQVersionQua is not defined'); + + await initializeEngine(engine, basicInfoWrapper, dataPathGlobal, systemPlatform, systemVersion); + await initializeLoginService(loginService, basicInfoWrapper, dataPathGlobal, systemVersion, hostname); + handleProxy(session, logger); + program.option('-q, --qq [number]', 'QQ号').parse(process.argv); + const cmdOptions = program.opts(); + const quickLoginUin = cmdOptions['qq']; + const historyLoginList = (await loginService.getLoginList()).LocalLoginInfoList; + + const dataTimestape = new Date().getTime().toString(); + o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']); + + const selfInfo = await handleLogin(loginService, logger, pathWrapper, quickLoginUin, historyLoginList); + const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129'; + o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex'))); + + let guid = loginService.getMachineGuid(); + guid = guid.slice(0, 8) + '-' + guid.slice(8, 12) + '-' + guid.slice(12, 16) + '-' + guid.slice(16, 20) + '-' + guid.slice(20); + o3Service.reportAmgomWeather('login', 'a6', [dataTimestape, '184', '329']); + + const sessionConfig = await genSessionConfig( + guid, + basicInfoWrapper.QQVersionAppid, + basicInfoWrapper.getFullQQVersion(), + selfInfo.uin, + selfInfo.uid, + dataPath + ); + + await initializeSession(session, sessionConfig, startupSession); + + const accountDataPath = path.resolve(dataPath, './NapCat/data'); + // 判断dataPath是否为根目录 或者 D:/ 之类的盘目录 + if (dataPath !== '/' && /^[a-zA-Z]:\\$/.test(dataPath) === false) { + try { + fs.mkdirSync(accountDataPath, { recursive: true }); + } catch (error) { + logger.logError('创建accountDataPath失败', error); + } + } + + logger.logDebug('本账号数据/缓存目录:', accountDataPath); + + await new NapCatShell( + wrapper, + session, + logger, + loginService, + selfInfo, + basicInfoWrapper, + pathWrapper, + nativePacketHandler + ).InitNapCat(); +} export class NapCatShell { - readonly core: NapCatCore; - readonly context: InstanceContext; + readonly core: NapCatCore; + readonly context: InstanceContext; - constructor( - wrapper: WrapperNodeApi, - session: NodeIQQNTWrapperSession, - logger: LogWrapper, - loginService: NodeIKernelLoginService, - selfInfo: SelfInfo, - basicInfoWrapper: QQBasicInfoWrapper, - pathWrapper: NapCatPathWrapper, - packetHandler: NativePacketHandler, - ) { - this.context = { - packetHandler, - workingEnv: NapCatCoreWorkingEnv.Shell, - wrapper, - session, - logger, - loginService, - basicInfoWrapper, - pathWrapper, - }; - this.core = new NapCatCore(this.context, selfInfo); + constructor ( + wrapper: WrapperNodeApi, + session: NodeIQQNTWrapperSession, + logger: LogWrapper, + loginService: NodeIKernelLoginService, + selfInfo: SelfInfo, + basicInfoWrapper: QQBasicInfoWrapper, + pathWrapper: NapCatPathWrapper, + packetHandler: NativePacketHandler + ) { + this.context = { + packetHandler, + workingEnv: NapCatCoreWorkingEnv.Shell, + wrapper, + session, + logger, + loginService, + basicInfoWrapper, + pathWrapper, + }; + this.core = new NapCatCore(this.context, selfInfo); + } - } - async InitNapCat() { - await this.core.initCore(); - new NapCatOneBot11Adapter(this.core, this.context, this.context.pathWrapper).InitOneBot() - .catch(e => this.context.logger.logError('初始化OneBot失败', e)); - } + async InitNapCat () { + await this.core.initCore(); + new NapCatOneBot11Adapter(this.core, this.context, this.context.pathWrapper).InitOneBot() + .catch(e => this.context.logger.logError('初始化OneBot失败', e)); + } } - diff --git a/src/shell/napcat.ts b/src/shell/napcat.ts index 7b300cb0..c65d65d6 100644 --- a/src/shell/napcat.ts +++ b/src/shell/napcat.ts @@ -1,2 +1,2 @@ import { NCoreInitShell } from './base'; -NCoreInitShell(); \ No newline at end of file +NCoreInitShell(); diff --git a/src/shell/pipe.ts b/src/shell/pipe.ts index 45320bd8..b1bdcdb7 100644 --- a/src/shell/pipe.ts +++ b/src/shell/pipe.ts @@ -9,115 +9,114 @@ import { Writable } from 'stream'; * @param timeoutMs 连接超时时间(毫秒),默认5000ms * @returns Promise,连接成功时resolve,失败时reject */ -export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000): Promise<{ disconnect: () => void }> { - return new Promise((resolve, reject) => { - if (process.platform !== 'win32') { - // 非Windows平台不reject,而是返回一个空的disconnect函数 - return resolve({ disconnect: () => { } }); - } +export function connectToNamedPipe (logger: LogWrapper, timeoutMs: number = 5000): Promise<{ disconnect: () => void }> { + return new Promise((resolve, reject) => { + if (process.platform !== 'win32') { + // 非Windows平台不reject,而是返回一个空的disconnect函数 + return resolve({ disconnect: () => { } }); + } - const pid = process.pid; - const pipePath = `\\\\.\\pipe\\NapCat_${pid}`; + const pid = process.pid; + const pipePath = `\\\\.\\pipe\\NapCat_${pid}`; - // 设置连接超时 - const timeoutId = setTimeout(() => { - reject(new Error(`连接命名管道超时: ${pipePath}`)); - }, timeoutMs); + // 设置连接超时 + const timeoutId = setTimeout(() => { + reject(new Error(`连接命名管道超时: ${pipePath}`)); + }, timeoutMs); - try { - const originalStdoutWrite = process.stdout.write.bind(process.stdout); - const pipeSocket = net.connect(pipePath, () => { - // 清除超时 - clearTimeout(timeoutId); + try { + const originalStdoutWrite = process.stdout.write.bind(process.stdout); + const pipeSocket = net.connect(pipePath, () => { + // 清除超时 + clearTimeout(timeoutId); - // 优化网络性能设置 - pipeSocket.setNoDelay(true); // 减少延迟 + // 优化网络性能设置 + pipeSocket.setNoDelay(true); // 减少延迟 - // 设置更高的高水位线,允许更多数据缓冲 + // 设置更高的高水位线,允许更多数据缓冲 - logger.log(`[StdOut] 已重定向到命名管道: ${pipePath}`); + logger.log(`[StdOut] 已重定向到命名管道: ${pipePath}`); - // 创建拥有更优雅背压处理的 Writable 流 - const pipeWritable = new Writable({ - highWaterMark: 1024 * 64, // 64KB 高水位线 - write(chunk, encoding, callback) { - if (!pipeSocket.writable) { - // 如果管道不可写,退回到原始stdout - logger.log('[StdOut] 管道不可写,回退到控制台输出'); - return originalStdoutWrite(chunk, encoding, callback); - } + // 创建拥有更优雅背压处理的 Writable 流 + const pipeWritable = new Writable({ + highWaterMark: 1024 * 64, // 64KB 高水位线 + write (chunk, encoding, callback) { + if (!pipeSocket.writable) { + // 如果管道不可写,退回到原始stdout + logger.log('[StdOut] 管道不可写,回退到控制台输出'); + return originalStdoutWrite(chunk, encoding, callback); + } - // 尝试写入数据到管道 - const canContinue = pipeSocket.write(chunk, encoding, () => { - // 数据已被发送或放入内部缓冲区 - }); - - if (canContinue) { - // 如果返回true,表示可以继续写入更多数据 - // 立即通知写入流可以继续 - process.nextTick(callback); - } else { - // 如果返回false,表示内部缓冲区已满 - // 等待drain事件再恢复写入 - pipeSocket.once('drain', () => { - callback(); - }); - } - // 明确返回true,表示写入已处理 - return true; - } - }); - - // 重定向stdout - process.stdout.write = ( - chunk: any, - encoding?: BufferEncoding | (() => void), - cb?: () => void - ): boolean => { - if (typeof encoding === 'function') { - cb = encoding; - encoding = undefined; - } - - // 使用优化的writable流处理写入 - return pipeWritable.write(chunk, encoding as BufferEncoding, cb as () => void); - }; - - // 提供断开连接的方法 - const disconnect = () => { - process.stdout.write = originalStdoutWrite; - pipeSocket.end(); - logger.log(`已手动断开命名管道连接: ${pipePath}`); - }; - - // 返回成功和断开连接的方法 - resolve({ disconnect }); + // 尝试写入数据到管道 + const canContinue = pipeSocket.write(chunk, encoding, () => { + // 数据已被发送或放入内部缓冲区 }); - // 管道错误处理 - pipeSocket.on('error', (err) => { - clearTimeout(timeoutId); - process.stdout.write = originalStdoutWrite; - logger.log(`连接命名管道 ${pipePath} 时出错:`, err); - reject(err); - }); + if (canContinue) { + // 如果返回true,表示可以继续写入更多数据 + // 立即通知写入流可以继续 + process.nextTick(callback); + } else { + // 如果返回false,表示内部缓冲区已满 + // 等待drain事件再恢复写入 + pipeSocket.once('drain', () => { + callback(); + }); + } + // 明确返回true,表示写入已处理 + return true; + }, + }); - // 管道关闭处理 - pipeSocket.on('end', () => { - process.stdout.write = originalStdoutWrite; - logger.log('命名管道连接已关闭'); - }); + // 重定向stdout + process.stdout.write = ( + chunk: any, + encoding?: BufferEncoding | (() => void), + cb?: () => void + ): boolean => { + if (typeof encoding === 'function') { + cb = encoding; + encoding = undefined; + } - // 确保在连接意外关闭时恢复stdout - pipeSocket.on('close', () => { - process.stdout.write = originalStdoutWrite; - logger.log('命名管道连接已关闭'); - }); + // 使用优化的writable流处理写入 + return pipeWritable.write(chunk, encoding as BufferEncoding, cb as () => void); + }; - } catch (error) { - clearTimeout(timeoutId); - logger.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error); - reject(error); - } - }); -} \ No newline at end of file + // 提供断开连接的方法 + const disconnect = () => { + process.stdout.write = originalStdoutWrite; + pipeSocket.end(); + logger.log(`已手动断开命名管道连接: ${pipePath}`); + }; + + // 返回成功和断开连接的方法 + resolve({ disconnect }); + }); + + // 管道错误处理 + pipeSocket.on('error', (err) => { + clearTimeout(timeoutId); + process.stdout.write = originalStdoutWrite; + logger.log(`连接命名管道 ${pipePath} 时出错:`, err); + reject(err); + }); + + // 管道关闭处理 + pipeSocket.on('end', () => { + process.stdout.write = originalStdoutWrite; + logger.log('命名管道连接已关闭'); + }); + + // 确保在连接意外关闭时恢复stdout + pipeSocket.on('close', () => { + process.stdout.write = originalStdoutWrite; + logger.log('命名管道连接已关闭'); + }); + } catch (error) { + clearTimeout(timeoutId); + logger.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error); + reject(error); + } + }); +} diff --git a/src/universal/napcat.ts b/src/universal/napcat.ts index 71710c8d..2542fe64 100644 --- a/src/universal/napcat.ts +++ b/src/universal/napcat.ts @@ -3,5 +3,5 @@ export * from '@/framework/napcat'; export * from '@/shell/base'; if ((global as unknown as { LiteLoader: unknown }).LiteLoader === undefined) { - NCoreInitShell(); -} \ No newline at end of file + NCoreInitShell(); +} diff --git a/src/webui/index.ts b/src/webui/index.ts index 730961f1..42179365 100644 --- a/src/webui/index.ts +++ b/src/webui/index.ts @@ -4,7 +4,7 @@ import express from 'express'; import { createServer } from 'http'; -import { randomUUID } from 'node:crypto' +import { randomUUID } from 'node:crypto'; import { createServer as createHttpsServer } from 'https'; import { LogWrapper } from '@/common/log'; import { NapCatPathWrapper } from '@/common/path'; @@ -15,7 +15,10 @@ import { createUrl, getRandomToken } from '@webapi/utils/url'; import { sendError } from '@webapi/utils/response'; import { join } from 'node:path'; import { terminalManager } from '@webapi/terminal/terminal_manager'; -import multer from 'multer'; // 引入multer用于错误捕获 +import multer from 'multer'; +import * as net from 'node:net'; +import { WebUiDataRuntime } from './src/helper/Data'; +import { existsSync, readFileSync } from 'node:fs'; // 引入multer用于错误捕获 // 实例化Express const app = express(); @@ -28,9 +31,6 @@ const app = express(); export let WebUiConfig: WebUiConfigWrapper; export let webUiPathWrapper: NapCatPathWrapper; const MAX_PORT_TRY = 100; -import * as net from 'node:net'; -import { WebUiDataRuntime } from './src/helper/Data'; -import { existsSync, readFileSync } from 'node:fs'; export let webUiRuntimePort = 6099; // 全局变量:存储需要在QQ登录成功后发送的新token @@ -44,242 +44,242 @@ export let pendingTokenToSend: string | null = null; */ let initialWebUiToken: string = ''; -export function setInitialWebUiToken(token: string) { - initialWebUiToken = token; +export function setInitialWebUiToken (token: string) { + initialWebUiToken = token; } -export function getInitialWebUiToken(): string { - return initialWebUiToken; +export function getInitialWebUiToken (): string { + return initialWebUiToken; } -export function setPendingTokenToSend(token: string | null) { - pendingTokenToSend = token; +export function setPendingTokenToSend (token: string | null) { + pendingTokenToSend = token; } -export async function InitPort(parsedConfig: WebUiConfigType): Promise<[string, number,string]> { - try { - await tryUseHost(parsedConfig.host); - const port = await tryUsePort(parsedConfig.port, parsedConfig.host); - return [parsedConfig.host, port, parsedConfig.token]; - } catch (error) { - console.log('host或port不可用', error); - return ['', 0, randomUUID()]; - } +export async function InitPort (parsedConfig: WebUiConfigType): Promise<[string, number, string]> { + try { + await tryUseHost(parsedConfig.host); + const port = await tryUsePort(parsedConfig.port, parsedConfig.host); + return [parsedConfig.host, port, parsedConfig.token]; + } catch (error) { + console.log('host或port不可用', error); + return ['', 0, randomUUID()]; + } } -async function checkCertificates(logger: LogWrapper): Promise<{ key: string, cert: string } | null> { - try { - const certPath = join(webUiPathWrapper.configPath, 'cert.pem'); - const keyPath = join(webUiPathWrapper.configPath, 'key.pem'); +async function checkCertificates (logger: LogWrapper): Promise<{ key: string, cert: string } | null> { + try { + const certPath = join(webUiPathWrapper.configPath, 'cert.pem'); + const keyPath = join(webUiPathWrapper.configPath, 'key.pem'); - if (existsSync(certPath) && existsSync(keyPath)) { - const cert = readFileSync(certPath, 'utf8'); - const key = readFileSync(keyPath, 'utf8'); - logger.log('[NapCat] [WebUi] 找到SSL证书,将启用HTTPS模式'); - return { cert, key }; - } - return null; - } catch (error) { - logger.log('[NapCat] [WebUi] 检查SSL证书时出错: ' + error); - return null; + if (existsSync(certPath) && existsSync(keyPath)) { + const cert = readFileSync(certPath, 'utf8'); + const key = readFileSync(keyPath, 'utf8'); + logger.log('[NapCat] [WebUi] 找到SSL证书,将启用HTTPS模式'); + return { cert, key }; } + return null; + } catch (error) { + logger.log('[NapCat] [WebUi] 检查SSL证书时出错: ' + error); + return null; + } } -export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapper) { - webUiPathWrapper = pathWrapper; - WebUiConfig = new WebUiConfigWrapper(); - let config = await WebUiConfig.GetWebUIConfig(); +export async function InitWebUi (logger: LogWrapper, pathWrapper: NapCatPathWrapper) { + webUiPathWrapper = pathWrapper; + WebUiConfig = new WebUiConfigWrapper(); + let config = await WebUiConfig.GetWebUIConfig(); - // 检查并更新默认密码 - 最高优先级 - if (config.token === 'napcat' || !config.token) { - const randomToken = getRandomToken(8); - await WebUiConfig.UpdateWebUIConfig({ token: randomToken }); - logger.log(`[NapCat] [WebUi] 检测到默认密码,已自动更新为安全密码`); - - // 存储token到全局变量,等待QQ登录成功后发送 - setPendingTokenToSend(randomToken); - logger.log(`[NapCat] [WebUi] 新密码将在QQ登录成功后发送给用户`); - - // 重新获取更新后的配置 - config = await WebUiConfig.GetWebUIConfig(); + // 检查并更新默认密码 - 最高优先级 + if (config.token === 'napcat' || !config.token) { + const randomToken = getRandomToken(8); + await WebUiConfig.UpdateWebUIConfig({ token: randomToken }); + logger.log('[NapCat] [WebUi] 检测到默认密码,已自动更新为安全密码'); + + // 存储token到全局变量,等待QQ登录成功后发送 + setPendingTokenToSend(randomToken); + logger.log('[NapCat] [WebUi] 新密码将在QQ登录成功后发送给用户'); + + // 重新获取更新后的配置 + config = await WebUiConfig.GetWebUIConfig(); + } + + // 存储启动时的初始token用于鉴权 + setInitialWebUiToken(config.token); + + // 检查是否禁用WebUI + if (config.disableWebUI) { + logger.log('[NapCat] [WebUi] WebUI is disabled by configuration.'); + return; + } + + const [host, port, token] = await InitPort(config); + webUiRuntimePort = port; + if (port == 0) { + logger.log('[NapCat] [WebUi] Current WebUi is not run.'); + return; + } + WebUiDataRuntime.setWebUiConfigQuickFunction( + async () => { + const autoLoginAccount = process.env['NAPCAT_QUICK_ACCOUNT'] || WebUiConfig.getAutoLoginAccount(); + if (autoLoginAccount) { + try { + const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount); + if (!result) { + throw new Error(message); + } + console.log(`[NapCat] [WebUi] Auto login account: ${autoLoginAccount}`); + } catch (error) { + console.log('[NapCat] [WebUi] Auto login account failed.' + error); + } + } + }); + // ------------注册中间件------------ + // 使用express的json中间件 + app.use(express.json()); + + // CORS中间件 + // TODO: + app.use(cors); + + // 如果是webui字体文件,挂载字体文件 + app.use('/webui/fonts/AaCute.woff', async (_req, res, next) => { + const isFontExist = await WebUiConfig.CheckWebUIFontExist(); + if (isFontExist) { + res.sendFile(WebUiConfig.GetWebUIFontPath()); + } else { + next(); } + }); - // 存储启动时的初始token用于鉴权 - setInitialWebUiToken(config.token); + // 如果是自定义色彩,构建一个css文件 + app.use('/files/theme.css', async (_req, res) => { + const colors = await WebUiConfig.GetTheme(); - // 检查是否禁用WebUI - if (config.disableWebUI) { - logger.log('[NapCat] [WebUi] WebUI is disabled by configuration.'); - return; + let css = ':root, .light, [data-theme="light"] {'; + for (const key in colors.light) { + css += `${key}: ${colors.light[key]};`; } - - const [host, port, token] = await InitPort(config); - webUiRuntimePort = port; - if (port == 0) { - logger.log('[NapCat] [WebUi] Current WebUi is not run.'); - return; + css += '}'; + css += '.dark, [data-theme="dark"] {'; + for (const key in colors.dark) { + css += `${key}: ${colors.dark[key]};`; } - WebUiDataRuntime.setWebUiConfigQuickFunction( - async () => { - let autoLoginAccount = process.env['NAPCAT_QUICK_ACCOUNT'] || WebUiConfig.getAutoLoginAccount(); - if (autoLoginAccount) { - try { - const { result, message } = await WebUiDataRuntime.requestQuickLogin(autoLoginAccount); - if (!result) { - throw new Error(message); - } - console.log(`[NapCat] [WebUi] Auto login account: ${autoLoginAccount}`); - } catch (error) { - console.log(`[NapCat] [WebUi] Auto login account failed.` + error); - } - } - }); - // ------------注册中间件------------ - // 使用express的json中间件 - app.use(express.json()); + css += '}'; - // CORS中间件 - // TODO: - app.use(cors); + res.send(css); + }); - // 如果是webui字体文件,挂载字体文件 - app.use('/webui/fonts/AaCute.woff', async (_req, res, next) => { - const isFontExist = await WebUiConfig.CheckWebUIFontExist(); - if (isFontExist) { - res.sendFile(WebUiConfig.GetWebUIFontPath()); - } else { - next(); - } - }); + // ------------中间件结束------------ - // 如果是自定义色彩,构建一个css文件 - app.use('/files/theme.css', async (_req, res) => { - const colors = await WebUiConfig.GetTheme(); + // ------------挂载路由------------ + // 挂载静态路由(前端),路径为 /webui + app.use('/webui', express.static(pathWrapper.staticPath, { + maxAge: '1d', + })); + // 初始化WebSocket服务器 + const sslCerts = await checkCertificates(logger); + const isHttps = !!sslCerts; + const server = isHttps && sslCerts ? createHttpsServer(sslCerts, app) : createServer(app); + server.on('upgrade', (request, socket, head) => { + terminalManager.initialize(request, socket, head, logger); + }); + // 挂载API接口 + app.use('/api', ALLRouter); + // 所有剩下的请求都转到静态页面 + const indexFile = join(pathWrapper.staticPath, 'index.html'); - let css = ':root, .light, [data-theme="light"] {'; - for (const key in colors.light) { - css += `${key}: ${colors.light[key]};`; - } - css += '}'; - css += '.dark, [data-theme="dark"] {'; - for (const key in colors.dark) { - css += `${key}: ${colors.dark[key]};`; - } - css += '}'; + app.all(/\/webui\/(.*)/, (_req, res) => { + res.sendFile(indexFile); + }); - res.send(css); - }); + // 初始服务(先放个首页) + app.all('/', (_req, res) => { + res.status(301).header('Location', '/webui').send(); + }); - // ------------中间件结束------------ + // 错误处理中间件,捕获multer的错误 + app.use((err: Error, _: express.Request, res: express.Response, next: express.NextFunction) => { + if (err instanceof multer.MulterError) { + return sendError(res, err.message, true); + } + next(err); + }); - // ------------挂载路由------------ - // 挂载静态路由(前端),路径为 /webui - app.use('/webui', express.static(pathWrapper.staticPath, { - maxAge: '1d' - })); - // 初始化WebSocket服务器 - const sslCerts = await checkCertificates(logger); - const isHttps = !!sslCerts; - let server = isHttps && sslCerts ? createHttpsServer(sslCerts, app) : createServer(app); - server.on('upgrade', (request, socket, head) => { - terminalManager.initialize(request, socket, head, logger); - }); - // 挂载API接口 - app.use('/api', ALLRouter); - // 所有剩下的请求都转到静态页面 - const indexFile = join(pathWrapper.staticPath, 'index.html'); + // 全局错误处理中间件(非multer错误) + app.use((_: Error, __: express.Request, res: express.Response, ___: express.NextFunction) => { + sendError(res, 'An unknown error occurred.', true); + }); - app.all(/\/webui\/(.*)/, (_req, res) => { - res.sendFile(indexFile); - }); - - // 初始服务(先放个首页) - app.all('/', (_req, res) => { - res.status(301).header('Location', '/webui').send(); - }); - - // 错误处理中间件,捕获multer的错误 - app.use((err: Error, _: express.Request, res: express.Response, next: express.NextFunction) => { - if (err instanceof multer.MulterError) { - return sendError(res, err.message, true); - } - next(err); - }); - - // 全局错误处理中间件(非multer错误) - app.use((_: Error, __: express.Request, res: express.Response, ___: express.NextFunction) => { - sendError(res, 'An unknown error occurred.', true); - }); - - // ------------启动服务------------ - server.listen(port, host, async () => { - let searchParams = { token: token }; - logger.log(`[NapCat] [WebUi] WebUi Token: ${token}`); - logger.log( + // ------------启动服务------------ + server.listen(port, host, async () => { + const searchParams = { token }; + logger.log(`[NapCat] [WebUi] WebUi Token: ${token}`); + logger.log( `[NapCat] [WebUi] WebUi User Panel Url: ${createUrl('127.0.0.1', port.toString(), '/webui', searchParams)}` - ); - if (host !== '') { - logger.log( + ); + if (host !== '') { + logger.log( `[NapCat] [WebUi] WebUi User Panel Url: ${createUrl(host, port.toString(), '/webui', searchParams)}` - ); - } - }); + ); + } + }); - // ------------Over!------------ + // ------------Over!------------ } -async function tryUseHost(host: string): Promise { - return new Promise((resolve, reject) => { - try { - const server = net.createServer(); - server.on('listening', () => { - server.close(); - resolve(host); - }); +async function tryUseHost (host: string): Promise { + return new Promise((resolve, reject) => { + try { + const server = net.createServer(); + server.on('listening', () => { + server.close(); + resolve(host); + }); - server.on('error', (err: any) => { - if (err.code === 'EADDRNOTAVAIL') { - reject(new Error('主机地址验证失败,可能为非本机地址')); - } else { - reject(new Error(`遇到错误: ${err.code}`)); - } - }); - - // 尝试监听 让系统随机分配一个端口 - server.listen(0, host); - } catch (error) { - // 这里捕获到的错误应该是启动服务器时的同步错误 - reject(new Error(`服务器启动时发生错误: ${error}`)); + server.on('error', (err: any) => { + if (err.code === 'EADDRNOTAVAIL') { + reject(new Error('主机地址验证失败,可能为非本机地址')); + } else { + reject(new Error(`遇到错误: ${err.code}`)); } - }); + }); + + // 尝试监听 让系统随机分配一个端口 + server.listen(0, host); + } catch (error) { + // 这里捕获到的错误应该是启动服务器时的同步错误 + reject(new Error(`服务器启动时发生错误: ${error}`)); + } + }); } -async function tryUsePort(port: number, host: string, tryCount: number = 0): Promise { - return new Promise((resolve, reject) => { - try { - const server = net.createServer(); - server.on('listening', () => { - server.close(); - resolve(port); - }); +async function tryUsePort (port: number, host: string, tryCount: number = 0): Promise { + return new Promise((resolve, reject) => { + try { + const server = net.createServer(); + server.on('listening', () => { + server.close(); + resolve(port); + }); - server.on('error', (err: any) => { - if (err.code === 'EADDRINUSE') { - if (tryCount < MAX_PORT_TRY) { - // 使用循环代替递归 - resolve(tryUsePort(port + 1, host, tryCount + 1)); - } else { - reject(new Error(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`)); - } - } else { - reject(new Error(`遇到错误: ${err.code}`)); - } - }); - - // 尝试监听端口 - server.listen(port, host); - } catch (error) { - // 这里捕获到的错误应该是启动服务器时的同步错误 - reject(new Error(`服务器启动时发生错误: ${error}`)); + server.on('error', (err: any) => { + if (err.code === 'EADDRINUSE') { + if (tryCount < MAX_PORT_TRY) { + // 使用循环代替递归 + resolve(tryUsePort(port + 1, host, tryCount + 1)); + } else { + reject(new Error(`端口尝试失败,达到最大尝试次数: ${MAX_PORT_TRY}`)); + } + } else { + reject(new Error(`遇到错误: ${err.code}`)); } - }); + }); + + // 尝试监听端口 + server.listen(port, host); + } catch (error) { + // 这里捕获到的错误应该是启动服务器时的同步错误 + reject(new Error(`服务器启动时发生错误: ${error}`)); + } + }); } diff --git a/src/webui/src/api/Auth.ts b/src/webui/src/api/Auth.ts index d7f9c8ce..eb3f80c1 100644 --- a/src/webui/src/api/Auth.ts +++ b/src/webui/src/api/Auth.ts @@ -9,144 +9,144 @@ import { isEmpty } from '@webapi/utils/check'; // 登录 export const LoginHandler: RequestHandler = async (req, res) => { - // 获取WebUI配置 - const WebUiConfigData = await WebUiConfig.GetWebUIConfig(); - // 获取请求体中的hash - const { hash } = req.body; - // 获取客户端IP - const clientIP = req.ip || req.socket.remoteAddress || ''; + // 获取WebUI配置 + const WebUiConfigData = await WebUiConfig.GetWebUIConfig(); + // 获取请求体中的hash + const { hash } = req.body; + // 获取客户端IP + const clientIP = req.ip || req.socket.remoteAddress || ''; - // 如果token为空,返回错误信息 - if (isEmpty(hash)) { - return sendError(res, 'token is empty'); - } - // 检查登录频率 - if (!WebUiDataRuntime.checkLoginRate(clientIP, WebUiConfigData.loginRate)) { - return sendError(res, 'login rate limit'); - } - // 使用启动时缓存的token进行验证,而不是动态读取配置文件 - const initialToken = getInitialWebUiToken(); - if (!initialToken) { - return sendError(res, 'Server token not initialized'); - } - //验证初始token hash是否等于提交的token hash - if (!AuthHelper.comparePasswordHash(initialToken, hash)) { - return sendError(res, 'token is invalid'); - } + // 如果token为空,返回错误信息 + if (isEmpty(hash)) { + return sendError(res, 'token is empty'); + } + // 检查登录频率 + if (!WebUiDataRuntime.checkLoginRate(clientIP, WebUiConfigData.loginRate)) { + return sendError(res, 'login rate limit'); + } + // 使用启动时缓存的token进行验证,而不是动态读取配置文件 + const initialToken = getInitialWebUiToken(); + if (!initialToken) { + return sendError(res, 'Server token not initialized'); + } + // 验证初始token hash是否等于提交的token hash + if (!AuthHelper.comparePasswordHash(initialToken, hash)) { + return sendError(res, 'token is invalid'); + } - // 签发凭证 - const signCredential = Buffer.from(JSON.stringify(AuthHelper.signCredential(hash))).toString( - 'base64' - ); + // 签发凭证 + const signCredential = Buffer.from(JSON.stringify(AuthHelper.signCredential(hash))).toString( + 'base64' + ); // 返回成功信息 - return sendSuccess(res, { - Credential: signCredential, - }); + return sendSuccess(res, { + Credential: signCredential, + }); }; // 退出登录 export const LogoutHandler: RequestHandler = async (req, res) => { - const authorization = req.headers.authorization; - try { - const CredentialBase64: string = authorization?.split(' ')[1] as string; - const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); - AuthHelper.revokeCredential(Credential); - return sendSuccess(res, 'Logged out successfully'); - } catch (e) { - return sendError(res, 'Logout failed'); - } + const authorization = req.headers.authorization; + try { + const CredentialBase64: string = authorization?.split(' ')[1] as string; + const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); + AuthHelper.revokeCredential(Credential); + return sendSuccess(res, 'Logged out successfully'); + } catch (e) { + return sendError(res, 'Logout failed'); + } }; // 检查登录状态 export const checkHandler: RequestHandler = async (req, res) => { - // 获取请求头中的Authorization - const authorization = req.headers.authorization; - // 检查凭证 - try { - // 从Authorization中获取凭证 - const CredentialBase64: string = authorization?.split(' ')[1] as string; - // 解析凭证 - const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); + // 获取请求头中的Authorization + const authorization = req.headers.authorization; + // 检查凭证 + try { + // 从Authorization中获取凭证 + const CredentialBase64: string = authorization?.split(' ')[1] as string; + // 解析凭证 + const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); - // 检查凭证是否已被注销 - if (AuthHelper.isCredentialRevoked(Credential)) { - return sendError(res, 'Token has been revoked'); - } - - // 使用启动时缓存的token进行验证 - const initialToken = getInitialWebUiToken(); - if (!initialToken) { - return sendError(res, 'Server token not initialized'); - } - // 验证凭证是否在一小时内有效 - const valid = AuthHelper.validateCredentialWithinOneHour(initialToken, Credential); - // 返回成功信息 - if (valid) return sendSuccess(res, null); - // 返回错误信息 - return sendError(res, 'Authorization Failed'); - } catch (e) { - // 返回错误信息 - return sendError(res, 'Authorization Failed'); + // 检查凭证是否已被注销 + if (AuthHelper.isCredentialRevoked(Credential)) { + return sendError(res, 'Token has been revoked'); } + + // 使用启动时缓存的token进行验证 + const initialToken = getInitialWebUiToken(); + if (!initialToken) { + return sendError(res, 'Server token not initialized'); + } + // 验证凭证是否在一小时内有效 + const valid = AuthHelper.validateCredentialWithinOneHour(initialToken, Credential); + // 返回成功信息 + if (valid) return sendSuccess(res, null); + // 返回错误信息 + return sendError(res, 'Authorization Failed'); + } catch (e) { + // 返回错误信息 + return sendError(res, 'Authorization Failed'); + } }; // 修改密码(token) export const UpdateTokenHandler: RequestHandler = async (req, res) => { - const { oldToken, newToken } = req.body; - const authorization = req.headers.authorization; + const { oldToken, newToken } = req.body; + const authorization = req.headers.authorization; - if (isEmpty(newToken)) { - return sendError(res, 'newToken is empty'); + if (isEmpty(newToken)) { + return sendError(res, 'newToken is empty'); + } + + // 强制要求旧密码 + if (isEmpty(oldToken)) { + return sendError(res, 'oldToken is required'); + } + + // 检查新旧密码是否相同 + if (oldToken === newToken) { + return sendError(res, '新密码不能与旧密码相同'); + } + + // 检查新密码强度 + if (newToken.length < 6) { + return sendError(res, '新密码至少需要6个字符'); + } + + // 检查是否包含字母 + if (!/[a-zA-Z]/.test(newToken)) { + return sendError(res, '新密码必须包含字母'); + } + + // 检查是否包含数字 + if (!/[0-9]/.test(newToken)) { + return sendError(res, '新密码必须包含数字'); + } + + try { + // 注销当前的Token + if (authorization) { + const CredentialBase64: string = authorization.split(' ')[1] as string; + const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); + AuthHelper.revokeCredential(Credential); } - // 强制要求旧密码 - if (isEmpty(oldToken)) { - return sendError(res, 'oldToken is required'); + // 使用启动时缓存的token进行验证 + const initialToken = getInitialWebUiToken(); + if (!initialToken) { + return sendError(res, 'Server token not initialized'); } + if (initialToken !== oldToken) { + return sendError(res, '旧 token 不匹配'); + } + // 直接更新配置文件中的token,不需要通过WebUiConfig.UpdateToken方法 + await WebUiConfig.UpdateWebUIConfig({ token: newToken }); + // 更新内存中的缓存token,使新密码立即生效 + setInitialWebUiToken(newToken); - // 检查新旧密码是否相同 - if (oldToken === newToken) { - return sendError(res, '新密码不能与旧密码相同'); - } - - // 检查新密码强度 - if (newToken.length < 6) { - return sendError(res, '新密码至少需要6个字符'); - } - - // 检查是否包含字母 - if (!/[a-zA-Z]/.test(newToken)) { - return sendError(res, '新密码必须包含字母'); - } - - // 检查是否包含数字 - if (!/[0-9]/.test(newToken)) { - return sendError(res, '新密码必须包含数字'); - } - - try { - // 注销当前的Token - if (authorization) { - const CredentialBase64: string = authorization.split(' ')[1] as string; - const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString()); - AuthHelper.revokeCredential(Credential); - } - - // 使用启动时缓存的token进行验证 - const initialToken = getInitialWebUiToken(); - if (!initialToken) { - return sendError(res, 'Server token not initialized'); - } - if (initialToken !== oldToken) { - return sendError(res, '旧 token 不匹配'); - } - // 直接更新配置文件中的token,不需要通过WebUiConfig.UpdateToken方法 - await WebUiConfig.UpdateWebUIConfig({ token: newToken }); - // 更新内存中的缓存token,使新密码立即生效 - setInitialWebUiToken(newToken); - - return sendSuccess(res, 'Token updated successfully'); - } catch (e: any) { - return sendError(res, `Failed to update token: ${e.message}`); - } + return sendSuccess(res, 'Token updated successfully'); + } catch (e: any) { + return sendError(res, `Failed to update token: ${e.message}`); + } }; diff --git a/src/webui/src/api/BaseInfo.ts b/src/webui/src/api/BaseInfo.ts index a7d3c514..299171af 100644 --- a/src/webui/src/api/BaseInfo.ts +++ b/src/webui/src/api/BaseInfo.ts @@ -5,22 +5,22 @@ import { sendSuccess } from '@webapi/utils/response'; import { WebUiConfig } from '@/webui'; export const PackageInfoHandler: RequestHandler = (_, res) => { - const data = WebUiDataRuntime.getPackageJson(); - sendSuccess(res, data); + const data = WebUiDataRuntime.getPackageJson(); + sendSuccess(res, data); }; export const QQVersionHandler: RequestHandler = (_, res) => { - const data = WebUiDataRuntime.getQQVersion(); - sendSuccess(res, data); + const data = WebUiDataRuntime.getQQVersion(); + sendSuccess(res, data); }; export const GetThemeConfigHandler: RequestHandler = async (_, res) => { - const data = await WebUiConfig.GetTheme(); - sendSuccess(res, data); + const data = await WebUiConfig.GetTheme(); + sendSuccess(res, data); }; export const SetThemeConfigHandler: RequestHandler = async (req, res) => { - const { theme } = req.body; - await WebUiConfig.UpdateTheme(theme); - sendSuccess(res, { message: '更新成功' }); + const { theme } = req.body; + await WebUiConfig.UpdateTheme(theme); + sendSuccess(res, { message: '更新成功' }); }; diff --git a/src/webui/src/api/File.ts b/src/webui/src/api/File.ts index c90c7886..e546af7e 100644 --- a/src/webui/src/api/File.ts +++ b/src/webui/src/api/File.ts @@ -15,100 +15,100 @@ const isWindows = os.platform() === 'win32'; // 安全地从查询参数中提取字符串值,防止类型混淆 const getQueryStringParam = (param: any): string => { - if (Array.isArray(param)) { - return String(param[0] || ''); - } - return String(param || ''); + if (Array.isArray(param)) { + return String(param[0] || ''); + } + return String(param || ''); }; // 获取系统根目录列表(Windows返回盘符列表,其他系统返回['/']) const getRootDirs = async (): Promise => { - if (!isWindows) return ['/']; + if (!isWindows) return ['/']; - // Windows 驱动器字母 (A-Z) - const drives: string[] = []; - for (let i = 65; i <= 90; i++) { - const driveLetter = String.fromCharCode(i); - try { - await fsProm.access(`${driveLetter}:\\`); - drives.push(`${driveLetter}:`); - } catch { - // 如果驱动器不存在或无法访问,跳过 - continue; - } + // Windows 驱动器字母 (A-Z) + const drives: string[] = []; + for (let i = 65; i <= 90; i++) { + const driveLetter = String.fromCharCode(i); + try { + await fsProm.access(`${driveLetter}:\\`); + drives.push(`${driveLetter}:`); + } catch { + // 如果驱动器不存在或无法访问,跳过 + continue; } - return drives.length > 0 ? drives : ['C:']; + } + return drives.length > 0 ? drives : ['C:']; }; // 规范化路径并进行安全验证 const normalizePath = (inputPath: string): string => { - if (!inputPath) { - // 对于空路径,Windows返回用户主目录,其他系统返回根目录 - return isWindows ? process.env['USERPROFILE'] || 'C:\\' : '/'; - } - - // 对输入路径进行清理,移除潜在的危险字符 - const cleanedPath = inputPath.replace(/[\x00-\x1f\x7f]/g, ''); // 移除控制字符 - - // 如果是Windows且输入为纯盘符(可能带或不带斜杠),统一返回 "X:\" - if (isWindows && /^[A-Z]:[\\/]*$/i.test(cleanedPath)) { - return cleanedPath.slice(0, 2) + '\\'; - } - - // 安全验证:检查是否包含危险的路径遍历模式(在规范化之前) - if (containsPathTraversal(cleanedPath)) { - throw new Error('Invalid path: path traversal detected'); - } - - // 进行路径规范化 - const normalized = path.resolve(cleanedPath); - - // 再次检查规范化后的路径,确保没有绕过安全检查 - if (containsPathTraversal(normalized)) { - throw new Error('Invalid path: path traversal detected after normalization'); - } - - // 额外安全检查:确保规范化后的路径不包含连续的路径分隔符 - const finalPath = normalized.replace(/[\\\/]+/g, path.sep); - - return finalPath; + if (!inputPath) { + // 对于空路径,Windows返回用户主目录,其他系统返回根目录 + return isWindows ? process.env['USERPROFILE'] || 'C:\\' : '/'; + } + + // 对输入路径进行清理,移除潜在的危险字符 + const cleanedPath = inputPath.replace(/[\x00-\x1f\x7f]/g, ''); // 移除控制字符 + + // 如果是Windows且输入为纯盘符(可能带或不带斜杠),统一返回 "X:\" + if (isWindows && /^[A-Z]:[\\/]*$/i.test(cleanedPath)) { + return cleanedPath.slice(0, 2) + '\\'; + } + + // 安全验证:检查是否包含危险的路径遍历模式(在规范化之前) + if (containsPathTraversal(cleanedPath)) { + throw new Error('Invalid path: path traversal detected'); + } + + // 进行路径规范化 + const normalized = path.resolve(cleanedPath); + + // 再次检查规范化后的路径,确保没有绕过安全检查 + if (containsPathTraversal(normalized)) { + throw new Error('Invalid path: path traversal detected after normalization'); + } + + // 额外安全检查:确保规范化后的路径不包含连续的路径分隔符 + const finalPath = normalized.replace(/[\\\/]+/g, path.sep); + + return finalPath; }; // 检查路径是否包含路径遍历攻击模式 const containsPathTraversal = (inputPath: string): boolean => { - // 对输入进行URL解码,防止编码绕过 - let decodedPath = inputPath; - try { - decodedPath = decodeURIComponent(inputPath); - } catch { - // 如果解码失败,使用原始路径 - } - - // 将路径统一为正斜杠格式进行检查 - const normalizedForCheck = decodedPath.replace(/\\/g, '/'); - - // 检查危险模式 - 更全面的路径遍历检测 - const dangerousPatterns = [ - /\.\.\//, // ../ 模式 - /\/\.\./, // /.. 模式 - /^\.\./, // 以.. 开头 - /\.\.$/, // 以.. 结尾 - /\.\.\\/, // ..\ 模式(Windows) - /\\\.\./, // \.. 模式(Windows) - /%2e%2e/i, // URL编码的.. - /%252e%252e/i, // 双重URL编码的.. - /\.\.\x00/, // null字节攻击 - /\0/, // null字节 - ]; - - return dangerousPatterns.some(pattern => pattern.test(normalizedForCheck)); + // 对输入进行URL解码,防止编码绕过 + let decodedPath = inputPath; + try { + decodedPath = decodeURIComponent(inputPath); + } catch { + // 如果解码失败,使用原始路径 + } + + // 将路径统一为正斜杠格式进行检查 + const normalizedForCheck = decodedPath.replace(/\\/g, '/'); + + // 检查危险模式 - 更全面的路径遍历检测 + const dangerousPatterns = [ + /\.\.\//, // ../ 模式 + /\/\.\./, // /.. 模式 + /^\.\./, // 以.. 开头 + /\.\.$/, // 以.. 结尾 + /\.\.\\/, // ..\ 模式(Windows) + /\\\.\./, // \.. 模式(Windows) + /%2e%2e/i, // URL编码的.. + /%252e%252e/i, // 双重URL编码的.. + /\.\.\x00/, // null字节攻击 + /\0/, // null字节 + ]; + + return dangerousPatterns.some(pattern => pattern.test(normalizedForCheck)); }; interface FileInfo { - name: string; - isDirectory: boolean; - size: number; - mtime: Date; + name: string; + isDirectory: boolean; + size: number; + mtime: Date; } // 添加系统文件黑名单 @@ -116,538 +116,538 @@ const SYSTEM_FILES = new Set(['pagefile.sys', 'swapfile.sys', 'hiberfil.sys', 'S // 检查是否为WebUI配置文件 const isWebUIConfigFile = (filePath: string): boolean => { - // 先用字符串快速筛选 - if (!filePath.includes('webui.json')) { - return false; - } - - // 进入更严格的路径判断 - 统一路径分隔符为 / - const webUIConfigPath = path.resolve(webUiPathWrapper.configPath, 'webui.json').replace(/\\/g, '/'); - const targetPath = path.resolve(filePath).replace(/\\/g, '/'); - - // 统一分隔符后进行路径比较 - return targetPath === webUIConfigPath; + // 先用字符串快速筛选 + if (!filePath.includes('webui.json')) { + return false; + } + + // 进入更严格的路径判断 - 统一路径分隔符为 / + const webUIConfigPath = path.resolve(webUiPathWrapper.configPath, 'webui.json').replace(/\\/g, '/'); + const targetPath = path.resolve(filePath).replace(/\\/g, '/'); + + // 统一分隔符后进行路径比较 + return targetPath === webUIConfigPath; }; // WebUI配置文件脱敏处理 const sanitizeWebUIConfig = (content: string): string => { - try { - const config = JSON.parse(content); - if (config.token) { - config.token = '******'; - } - return JSON.stringify(config, null, 4); - } catch { - // 如果解析失败,返回原内容 - return content; + try { + const config = JSON.parse(content); + if (config.token) { + config.token = '******'; } + return JSON.stringify(config, null, 4); + } catch { + // 如果解析失败,返回原内容 + return content; + } }; // 检查同类型的文件或目录是否存在 const checkSameTypeExists = async (pathToCheck: string, isDirectory: boolean): Promise => { - try { - const stat = await fsProm.stat(pathToCheck); - // 只有当类型相同时才认为是冲突 - return stat.isDirectory() === isDirectory; - } catch { - return false; - } + try { + const stat = await fsProm.stat(pathToCheck); + // 只有当类型相同时才认为是冲突 + return stat.isDirectory() === isDirectory; + } catch { + return false; + } }; // 获取目录内容 export const ListFilesHandler: RequestHandler = async (req, res) => { + try { + const requestPath = getQueryStringParam(req.query['path']) || (isWindows ? process.env['USERPROFILE'] || 'C:\\' : '/'); + + let normalizedPath: string; try { - const requestPath = getQueryStringParam(req.query['path']) || (isWindows ? process.env['USERPROFILE'] || 'C:\\' : '/'); - - let normalizedPath: string; - try { - normalizedPath = normalizePath(requestPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - const onlyDirectory = req.query['onlyDirectory'] === 'true'; - - // 如果是根路径且在Windows系统上,返回盘符列表 - if (isWindows && (!requestPath || requestPath === '/' || requestPath === '\\')) { - const drives = await getRootDirs(); - const driveInfos: FileInfo[] = await Promise.all( - drives.map(async (drive) => { - try { - const stat = await fsProm.stat(`${drive}\\`); - return { - name: drive, - isDirectory: true, - size: 0, - mtime: stat.mtime, - }; - } catch { - return { - name: drive, - isDirectory: true, - size: 0, - mtime: new Date(), - }; - } - }) - ); - return sendSuccess(res, driveInfos); - } - - const files = await fsProm.readdir(normalizedPath); - let fileInfos: FileInfo[] = []; - - for (const file of files) { - // 跳过系统文件 - if (SYSTEM_FILES.has(file)) continue; - - try { - const fullPath = path.join(normalizedPath, file); - const stat = await fsProm.stat(fullPath); - fileInfos.push({ - name: file, - isDirectory: stat.isDirectory(), - size: stat.size, - mtime: stat.mtime, - }); - } catch (error) { - // 忽略无法访问的文件 - // console.warn(`无法访问文件 ${file}:`, error); - continue; - } - } - - // 如果请求参数 onlyDirectory 为 true,则只返回目录信息 - if (onlyDirectory) { - fileInfos = fileInfos.filter((info) => info.isDirectory); - } - - return sendSuccess(res, fileInfos); - } catch (error) { - console.error('读取目录失败:', error); - return sendError(res, '读取目录失败'); + normalizedPath = normalizePath(requestPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + const onlyDirectory = req.query['onlyDirectory'] === 'true'; + + // 如果是根路径且在Windows系统上,返回盘符列表 + if (isWindows && (!requestPath || requestPath === '/' || requestPath === '\\')) { + const drives = await getRootDirs(); + const driveInfos: FileInfo[] = await Promise.all( + drives.map(async (drive) => { + try { + const stat = await fsProm.stat(`${drive}\\`); + return { + name: drive, + isDirectory: true, + size: 0, + mtime: stat.mtime, + }; + } catch { + return { + name: drive, + isDirectory: true, + size: 0, + mtime: new Date(), + }; + } + }) + ); + return sendSuccess(res, driveInfos); + } + + const files = await fsProm.readdir(normalizedPath); + let fileInfos: FileInfo[] = []; + + for (const file of files) { + // 跳过系统文件 + if (SYSTEM_FILES.has(file)) continue; + + try { + const fullPath = path.join(normalizedPath, file); + const stat = await fsProm.stat(fullPath); + fileInfos.push({ + name: file, + isDirectory: stat.isDirectory(), + size: stat.size, + mtime: stat.mtime, + }); + } catch (error) { + // 忽略无法访问的文件 + // console.warn(`无法访问文件 ${file}:`, error); + continue; + } + } + + // 如果请求参数 onlyDirectory 为 true,则只返回目录信息 + if (onlyDirectory) { + fileInfos = fileInfos.filter((info) => info.isDirectory); + } + + return sendSuccess(res, fileInfos); + } catch (error) { + console.error('读取目录失败:', error); + return sendError(res, '读取目录失败'); + } }; // 创建目录 export const CreateDirHandler: RequestHandler = async (req, res) => { + try { + const { path: dirPath } = req.body; + + let normalizedPath: string; try { - const { path: dirPath } = req.body; - - let normalizedPath: string; - try { - normalizedPath = normalizePath(dirPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - // 检查是否已存在同类型(目录) - if (await checkSameTypeExists(normalizedPath, true)) { - return sendError(res, '同名目录已存在'); - } - - await fsProm.mkdir(normalizedPath, { recursive: true }); - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '创建目录失败'); + normalizedPath = normalizePath(dirPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + // 检查是否已存在同类型(目录) + if (await checkSameTypeExists(normalizedPath, true)) { + return sendError(res, '同名目录已存在'); + } + + await fsProm.mkdir(normalizedPath, { recursive: true }); + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '创建目录失败'); + } }; // 删除文件/目录 export const DeleteHandler: RequestHandler = async (req, res) => { + try { + const { path: targetPath } = req.body; + + let normalizedPath: string; try { - const { path: targetPath } = req.body; - - let normalizedPath: string; - try { - normalizedPath = normalizePath(targetPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - const stat = await fsProm.stat(normalizedPath); - if (stat.isDirectory()) { - await fsProm.rm(normalizedPath, { recursive: true }); - } else { - await fsProm.unlink(normalizedPath); - } - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '删除失败'); + normalizedPath = normalizePath(targetPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + const stat = await fsProm.stat(normalizedPath); + if (stat.isDirectory()) { + await fsProm.rm(normalizedPath, { recursive: true }); + } else { + await fsProm.unlink(normalizedPath); + } + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '删除失败'); + } }; // 批量删除文件/目录 export const BatchDeleteHandler: RequestHandler = async (req, res) => { - try { - const { paths } = req.body; - for (const targetPath of paths) { - let normalizedPath: string; - try { - normalizedPath = normalizePath(targetPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - const stat = await fsProm.stat(normalizedPath); - if (stat.isDirectory()) { - await fsProm.rm(normalizedPath, { recursive: true }); - } else { - await fsProm.unlink(normalizedPath); - } - } - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '批量删除失败'); + try { + const { paths } = req.body; + for (const targetPath of paths) { + let normalizedPath: string; + try { + normalizedPath = normalizePath(targetPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); + } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + const stat = await fsProm.stat(normalizedPath); + if (stat.isDirectory()) { + await fsProm.rm(normalizedPath, { recursive: true }); + } else { + await fsProm.unlink(normalizedPath); + } } + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '批量删除失败'); + } }; // 读取文件内容 export const ReadFileHandler: RequestHandler = async (req, res) => { + try { + let filePath: string; try { - let filePath: string; - try { - filePath = normalizePath(getQueryStringParam(req.query['path'])); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(filePath)) { - return sendError(res, '路径必须是绝对路径'); - } - - let content = await fsProm.readFile(filePath, 'utf-8'); - - // 如果是WebUI配置文件,对token进行脱敏处理 - if (isWebUIConfigFile(filePath)) { - content = sanitizeWebUIConfig(content); - } - - return sendSuccess(res, content); - } catch (error) { - return sendError(res, '读取文件失败'); + filePath = normalizePath(getQueryStringParam(req.query['path'])); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(filePath)) { + return sendError(res, '路径必须是绝对路径'); + } + + let content = await fsProm.readFile(filePath, 'utf-8'); + + // 如果是WebUI配置文件,对token进行脱敏处理 + if (isWebUIConfigFile(filePath)) { + content = sanitizeWebUIConfig(content); + } + + return sendSuccess(res, content); + } catch (error) { + return sendError(res, '读取文件失败'); + } }; // 写入文件内容 export const WriteFileHandler: RequestHandler = async (req, res) => { + try { + const { path: filePath, content } = req.body; + + // 安全的路径规范化,如果检测到路径遍历攻击会抛出异常 + let normalizedPath: string; try { - const { path: filePath, content } = req.body; - - // 安全的路径规范化,如果检测到路径遍历攻击会抛出异常 - let normalizedPath: string; - try { - normalizedPath = normalizePath(filePath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - let finalContent = content; - - // 检查是否为WebUI配置文件 - if (isWebUIConfigFile(normalizedPath)) { - try { - // 解析要写入的配置 - const configToWrite = JSON.parse(content); - // 获取内存中的token,覆盖前端传来的token - const memoryToken = getInitialWebUiToken(); - if (memoryToken) { - configToWrite.token = memoryToken; - finalContent = JSON.stringify(configToWrite, null, 4); - } - } catch (e) { - // 如果解析失败 说明不符合json格式 不允许写入 - return sendError(res, '写入的WebUI配置文件内容格式错误,必须是合法的JSON'); - } - } - - await fsProm.writeFile(normalizedPath, finalContent, 'utf-8'); - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '写入文件失败'); + normalizedPath = normalizePath(filePath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + let finalContent = content; + + // 检查是否为WebUI配置文件 + if (isWebUIConfigFile(normalizedPath)) { + try { + // 解析要写入的配置 + const configToWrite = JSON.parse(content); + // 获取内存中的token,覆盖前端传来的token + const memoryToken = getInitialWebUiToken(); + if (memoryToken) { + configToWrite.token = memoryToken; + finalContent = JSON.stringify(configToWrite, null, 4); + } + } catch (e) { + // 如果解析失败 说明不符合json格式 不允许写入 + return sendError(res, '写入的WebUI配置文件内容格式错误,必须是合法的JSON'); + } + } + + await fsProm.writeFile(normalizedPath, finalContent, 'utf-8'); + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '写入文件失败'); + } }; // 创建新文件 export const CreateFileHandler: RequestHandler = async (req, res) => { + try { + const { path: filePath } = req.body; + + let normalizedPath: string; try { - const { path: filePath } = req.body; - - let normalizedPath: string; - try { - normalizedPath = normalizePath(filePath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - // 检查是否已存在同类型(文件) - if (await checkSameTypeExists(normalizedPath, false)) { - return sendError(res, '同名文件已存在'); - } - - await fsProm.writeFile(normalizedPath, '', 'utf-8'); - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '创建文件失败'); + normalizedPath = normalizePath(filePath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + // 检查是否已存在同类型(文件) + if (await checkSameTypeExists(normalizedPath, false)) { + return sendError(res, '同名文件已存在'); + } + + await fsProm.writeFile(normalizedPath, '', 'utf-8'); + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '创建文件失败'); + } }; // 重命名文件/目录 export const RenameHandler: RequestHandler = async (req, res) => { + try { + const { oldPath, newPath } = req.body; + + let normalizedOldPath: string; + let normalizedNewPath: string; try { - const { oldPath, newPath } = req.body; - - let normalizedOldPath: string; - let normalizedNewPath: string; - try { - normalizedOldPath = normalizePath(oldPath); - normalizedNewPath = normalizePath(newPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedOldPath) || !path.isAbsolute(normalizedNewPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - await fsProm.rename(normalizedOldPath, normalizedNewPath); - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '重命名失败'); + normalizedOldPath = normalizePath(oldPath); + normalizedNewPath = normalizePath(newPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedOldPath) || !path.isAbsolute(normalizedNewPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + await fsProm.rename(normalizedOldPath, normalizedNewPath); + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '重命名失败'); + } }; // 移动文件/目录 export const MoveHandler: RequestHandler = async (req, res) => { + try { + const { sourcePath, targetPath } = req.body; + + let normalizedSourcePath: string; + let normalizedTargetPath: string; try { - const { sourcePath, targetPath } = req.body; - - let normalizedSourcePath: string; - let normalizedTargetPath: string; - try { - normalizedSourcePath = normalizePath(sourcePath); - normalizedTargetPath = normalizePath(targetPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedSourcePath) || !path.isAbsolute(normalizedTargetPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - await fsProm.rename(normalizedSourcePath, normalizedTargetPath); - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '移动失败'); + normalizedSourcePath = normalizePath(sourcePath); + normalizedTargetPath = normalizePath(targetPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedSourcePath) || !path.isAbsolute(normalizedTargetPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + await fsProm.rename(normalizedSourcePath, normalizedTargetPath); + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '移动失败'); + } }; // 批量移动 export const BatchMoveHandler: RequestHandler = async (req, res) => { - try { - const { items } = req.body; - for (const { sourcePath, targetPath } of items) { - let normalizedSourcePath: string; - let normalizedTargetPath: string; - try { - normalizedSourcePath = normalizePath(sourcePath); - normalizedTargetPath = normalizePath(targetPath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(normalizedSourcePath) || !path.isAbsolute(normalizedTargetPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - await fsProm.rename(normalizedSourcePath, normalizedTargetPath); - } - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '批量移动失败'); + try { + const { items } = req.body; + for (const { sourcePath, targetPath } of items) { + let normalizedSourcePath: string; + let normalizedTargetPath: string; + try { + normalizedSourcePath = normalizePath(sourcePath); + normalizedTargetPath = normalizePath(targetPath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); + } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(normalizedSourcePath) || !path.isAbsolute(normalizedTargetPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + await fsProm.rename(normalizedSourcePath, normalizedTargetPath); } + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '批量移动失败'); + } }; // 新增:文件下载处理方法(注意流式传输,不将整个文件读入内存) export const DownloadHandler: RequestHandler = async (req, res) => { + try { + let filePath: string; try { - let filePath: string; - try { - filePath = normalizePath(getQueryStringParam(req.query['path'])); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - if (!filePath) { - return sendError(res, '参数错误'); - } - - // 额外安全检查:确保路径是绝对路径 - if (!path.isAbsolute(filePath)) { - return sendError(res, '路径必须是绝对路径'); - } - - const stat = await fsProm.stat(filePath); - - res.setHeader('Content-Type', 'application/octet-stream'); - let filename = path.basename(filePath); - if (stat.isDirectory()) { - filename = path.basename(filePath) + '.zip'; - res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`); - const zipStream = new PassThrough(); - compressing.zip.compressDir(filePath, zipStream as unknown as fs.WriteStream).catch((err) => { - console.error('压缩目录失败:', err); - res.end(); - }); - zipStream.pipe(res); - return; - } - res.setHeader('Content-Length', stat.size); - res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`); - const stream = fs.createReadStream(filePath); - stream.pipe(res); - } catch (error) { - return sendError(res, '下载失败'); + filePath = normalizePath(getQueryStringParam(req.query['path'])); + } catch (pathError) { + return sendError(res, '无效的文件路径'); } + + if (!filePath) { + return sendError(res, '参数错误'); + } + + // 额外安全检查:确保路径是绝对路径 + if (!path.isAbsolute(filePath)) { + return sendError(res, '路径必须是绝对路径'); + } + + const stat = await fsProm.stat(filePath); + + res.setHeader('Content-Type', 'application/octet-stream'); + let filename = path.basename(filePath); + if (stat.isDirectory()) { + filename = path.basename(filePath) + '.zip'; + res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`); + const zipStream = new PassThrough(); + compressing.zip.compressDir(filePath, zipStream as unknown as fs.WriteStream).catch((err) => { + console.error('压缩目录失败:', err); + res.end(); + }); + zipStream.pipe(res); + return; + } + res.setHeader('Content-Length', stat.size); + res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`); + const stream = fs.createReadStream(filePath); + stream.pipe(res); + } catch (error) { + return sendError(res, '下载失败'); + } }; // 批量下载:将多个文件/目录打包为 zip 文件下载 export const BatchDownloadHandler: RequestHandler = async (req, res) => { - try { - const { paths } = req.body as { paths: string[] }; - if (!paths || !Array.isArray(paths) || paths.length === 0) { - return sendError(res, '参数错误'); - } - res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', 'attachment; filename=files.zip'); - - const zipStream = new compressing.zip.Stream(); - // 修改:根据文件类型设置 relativePath - for (const filePath of paths) { - let normalizedPath: string; - try { - normalizedPath = normalizePath(filePath); - } catch (pathError) { - return sendError(res, '无效的文件路径'); - } - - // 额外安全检查:确保规范化后的路径是绝对路径 - if (!path.isAbsolute(normalizedPath)) { - return sendError(res, '路径必须是绝对路径'); - } - - const stat = await fsProm.stat(normalizedPath); - if (stat.isDirectory()) { - zipStream.addEntry(normalizedPath, { relativePath: '' }); - } else { - // 确保相对路径只使用文件名,防止路径遍历 - const safeName = path.basename(normalizedPath); - zipStream.addEntry(normalizedPath, { relativePath: safeName }); - } - } - zipStream.pipe(res); - res.on('finish', () => { - zipStream.destroy(); - }); - } catch (error) { - return sendError(res, '下载失败'); + try { + const { paths } = req.body as { paths: string[] }; + if (!paths || !Array.isArray(paths) || paths.length === 0) { + return sendError(res, '参数错误'); } + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment; filename=files.zip'); + + const zipStream = new compressing.zip.Stream(); + // 修改:根据文件类型设置 relativePath + for (const filePath of paths) { + let normalizedPath: string; + try { + normalizedPath = normalizePath(filePath); + } catch (pathError) { + return sendError(res, '无效的文件路径'); + } + + // 额外安全检查:确保规范化后的路径是绝对路径 + if (!path.isAbsolute(normalizedPath)) { + return sendError(res, '路径必须是绝对路径'); + } + + const stat = await fsProm.stat(normalizedPath); + if (stat.isDirectory()) { + zipStream.addEntry(normalizedPath, { relativePath: '' }); + } else { + // 确保相对路径只使用文件名,防止路径遍历 + const safeName = path.basename(normalizedPath); + zipStream.addEntry(normalizedPath, { relativePath: safeName }); + } + } + zipStream.pipe(res); + res.on('finish', () => { + zipStream.destroy(); + }); + } catch (error) { + return sendError(res, '下载失败'); + } }; // 修改上传处理方法 export const UploadHandler: RequestHandler = async (req, res) => { - try { - await diskUploader(req, res); - return sendSuccess(res, true, '文件上传成功', true); - } catch (error) { - let errorMessage = '文件上传失败'; + try { + await diskUploader(req, res); + return sendSuccess(res, true, '文件上传成功', true); + } catch (error) { + let errorMessage = '文件上传失败'; - if (error instanceof multer.MulterError) { - switch (error.code) { - case 'LIMIT_FILE_SIZE': - errorMessage = '文件大小超过限制(40MB)'; - break; - case 'LIMIT_UNEXPECTED_FILE': - errorMessage = '无效的文件上传字段'; - break; - default: - errorMessage = `上传错误: ${error.message}`; - } - } else if (error instanceof Error) { - errorMessage = error.message; - } - return sendError(res, errorMessage, true); + if (error instanceof multer.MulterError) { + switch (error.code) { + case 'LIMIT_FILE_SIZE': + errorMessage = '文件大小超过限制(40MB)'; + break; + case 'LIMIT_UNEXPECTED_FILE': + errorMessage = '无效的文件上传字段'; + break; + default: + errorMessage = `上传错误: ${error.message}`; + } + } else if (error instanceof Error) { + errorMessage = error.message; } + return sendError(res, errorMessage, true); + } }; // 上传WebUI字体文件处理方法 export const UploadWebUIFontHandler: RequestHandler = async (req, res) => { - try { - await webUIFontUploader(req, res); - return sendSuccess(res, true, '字体文件上传成功', true); - } catch (error) { - let errorMessage = '字体文件上传失败'; + try { + await webUIFontUploader(req, res); + return sendSuccess(res, true, '字体文件上传成功', true); + } catch (error) { + let errorMessage = '字体文件上传失败'; - if (error instanceof multer.MulterError) { - switch (error.code) { - case 'LIMIT_FILE_SIZE': - errorMessage = '字体文件大小超过限制(40MB)'; - break; - case 'LIMIT_UNEXPECTED_FILE': - errorMessage = '无效的文件上传字段'; - break; - default: - errorMessage = `上传错误: ${error.message}`; - } - } else if (error instanceof Error) { - errorMessage = error.message; - } - return sendError(res, errorMessage, true); + if (error instanceof multer.MulterError) { + switch (error.code) { + case 'LIMIT_FILE_SIZE': + errorMessage = '字体文件大小超过限制(40MB)'; + break; + case 'LIMIT_UNEXPECTED_FILE': + errorMessage = '无效的文件上传字段'; + break; + default: + errorMessage = `上传错误: ${error.message}`; + } + } else if (error instanceof Error) { + errorMessage = error.message; } + return sendError(res, errorMessage, true); + } }; // 删除WebUI字体文件处理方法 export const DeleteWebUIFontHandler: RequestHandler = async (_req, res) => { - try { - const fontPath = WebUiConfig.GetWebUIFontPath(); - const exists = await WebUiConfig.CheckWebUIFontExist(); + try { + const fontPath = WebUiConfig.GetWebUIFontPath(); + const exists = await WebUiConfig.CheckWebUIFontExist(); - if (!exists) { - return sendSuccess(res, true); - } - - await fsProm.unlink(fontPath); - return sendSuccess(res, true); - } catch (error) { - return sendError(res, '删除字体文件失败'); + if (!exists) { + return sendSuccess(res, true); } + + await fsProm.unlink(fontPath); + return sendSuccess(res, true); + } catch (error) { + return sendError(res, '删除字体文件失败'); + } }; diff --git a/src/webui/src/api/Log.ts b/src/webui/src/api/Log.ts index 6f08cf9d..8c990f50 100644 --- a/src/webui/src/api/Log.ts +++ b/src/webui/src/api/Log.ts @@ -8,73 +8,73 @@ const isMacOS = process.platform === 'darwin'; // 日志脱敏函数 const sanitizeLog = (log: string): string => { - // 脱敏 token 参数,将 token=xxx 替换为 token=*** - return log.replace(/token=[\w\d]+/gi, 'token=***'); + // 脱敏 token 参数,将 token=xxx 替换为 token=*** + return log.replace(/token=[\w\d]+/gi, 'token=***'); }; // 日志记录 export const LogHandler: RequestHandler = async (req, res) => { - const filename = req.query['id']; - if (!filename || typeof filename !== 'string') { - return sendError(res, 'ID不能为空'); - } + const filename = req.query['id']; + if (!filename || typeof filename !== 'string') { + return sendError(res, 'ID不能为空'); + } - if (filename.includes('..')) { - return sendError(res, 'ID不合法'); - } - const logContent = await WebUiConfig.GetLogContent(filename); - const sanitizedLogContent = sanitizeLog(logContent); - return sendSuccess(res, sanitizedLogContent); + if (filename.includes('..')) { + return sendError(res, 'ID不合法'); + } + const logContent = await WebUiConfig.GetLogContent(filename); + const sanitizedLogContent = sanitizeLog(logContent); + return sendSuccess(res, sanitizedLogContent); }; // 日志列表 export const LogListHandler: RequestHandler = async (_, res) => { - const logList = await WebUiConfig.GetLogsList(); - return sendSuccess(res, logList); + const logList = await WebUiConfig.GetLogsList(); + return sendSuccess(res, logList); }; // 实时日志(SSE) export const LogRealTimeHandler: RequestHandler = async (req, res) => { - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Connection', 'keep-alive'); - const listener = (log: string) => { - try { - const sanitizedLog = sanitizeLog(log); - res.write(`data: ${sanitizedLog}\n\n`); - } catch (error) { - console.error('向客户端写入日志数据时出错:', error); - } - }; - logSubscription.subscribe(listener); - req.on('close', () => { - logSubscription.unsubscribe(listener); - }); + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Connection', 'keep-alive'); + const listener = (log: string) => { + try { + const sanitizedLog = sanitizeLog(log); + res.write(`data: ${sanitizedLog}\n\n`); + } catch (error) { + console.error('向客户端写入日志数据时出错:', error); + } + }; + logSubscription.subscribe(listener); + req.on('close', () => { + logSubscription.unsubscribe(listener); + }); }; // 终端相关处理器 export const CreateTerminalHandler: RequestHandler = async (req, res) => { - if (isMacOS) { - return sendError(res, 'MacOS不支持终端'); - } - try { - const { cols, rows } = req.body; - const { id } = terminalManager.createTerminal(cols, rows); - return sendSuccess(res, { id }); - } catch (error) { - console.error('Failed to create terminal:', error); - return sendError(res, '创建终端失败'); - } + if (isMacOS) { + return sendError(res, 'MacOS不支持终端'); + } + try { + const { cols, rows } = req.body; + const { id } = terminalManager.createTerminal(cols, rows); + return sendSuccess(res, { id }); + } catch (error) { + console.error('Failed to create terminal:', error); + return sendError(res, '创建终端失败'); + } }; export const GetTerminalListHandler: RequestHandler = (_, res) => { - const list = terminalManager.getTerminalList(); - return sendSuccess(res, list); + const list = terminalManager.getTerminalList(); + return sendSuccess(res, list); }; export const CloseTerminalHandler: RequestHandler = (req, res) => { - const id = req.params['id']; - if (!id) { - return sendError(res, 'ID不能为空'); - } - terminalManager.closeTerminal(id); - return sendSuccess(res, {}); + const id = req.params['id']; + if (!id) { + return sendError(res, 'ID不能为空'); + } + terminalManager.closeTerminal(id); + return sendSuccess(res, {}); }; diff --git a/src/webui/src/api/OB11Config.ts b/src/webui/src/api/OB11Config.ts index dad6f7ba..4892a777 100644 --- a/src/webui/src/api/OB11Config.ts +++ b/src/webui/src/api/OB11Config.ts @@ -10,51 +10,51 @@ import json5 from 'json5'; // 获取OneBot11配置 export const OB11GetConfigHandler: RequestHandler = (_, res) => { - // 获取QQ登录状态 - const isLogin = WebUiDataRuntime.getQQLoginStatus(); - // 如果未登录,返回错误 - if (!isLogin) { - return sendError(res, 'Not Login'); - } - // 获取登录的QQ号 - const uin = WebUiDataRuntime.getQQLoginUin(); - // 读取配置文件路径 - const configFilePath = resolve(webUiPathWrapper.configPath, `./onebot11_${uin}.json`); - // 尝试解析配置文件 - try { - // 读取配置文件内容 - const configFileContent = existsSync(configFilePath) - ? readFileSync(configFilePath).toString() - : readFileSync(resolve(webUiPathWrapper.configPath, './onebot11.json')).toString(); - // 解析配置文件并加载配置 - const data = loadConfig(json5.parse(configFileContent)) as OneBotConfig; - // 返回配置文件 - return sendSuccess(res, data); - } catch (e) { - return sendError(res, 'Config Get Error'); - } + // 获取QQ登录状态 + const isLogin = WebUiDataRuntime.getQQLoginStatus(); + // 如果未登录,返回错误 + if (!isLogin) { + return sendError(res, 'Not Login'); + } + // 获取登录的QQ号 + const uin = WebUiDataRuntime.getQQLoginUin(); + // 读取配置文件路径 + const configFilePath = resolve(webUiPathWrapper.configPath, `./onebot11_${uin}.json`); + // 尝试解析配置文件 + try { + // 读取配置文件内容 + const configFileContent = existsSync(configFilePath) + ? readFileSync(configFilePath).toString() + : readFileSync(resolve(webUiPathWrapper.configPath, './onebot11.json')).toString(); + // 解析配置文件并加载配置 + const data = loadConfig(json5.parse(configFileContent)) as OneBotConfig; + // 返回配置文件 + return sendSuccess(res, data); + } catch (e) { + return sendError(res, 'Config Get Error'); + } }; // 写入OneBot11配置 export const OB11SetConfigHandler: RequestHandler = async (req, res) => { - // 获取QQ登录状态 - const isLogin = WebUiDataRuntime.getQQLoginStatus(); - // 如果未登录,返回错误 - if (!isLogin) { - return sendError(res, 'Not Login'); - } - // 如果配置为空,返回错误 - if (isEmpty(req.body.config)) { - return sendError(res, 'config is empty'); - } + // 获取QQ登录状态 + const isLogin = WebUiDataRuntime.getQQLoginStatus(); + // 如果未登录,返回错误 + if (!isLogin) { + return sendError(res, 'Not Login'); + } + // 如果配置为空,返回错误 + if (isEmpty(req.body.config)) { + return sendError(res, 'config is empty'); + } + // 写入配置 + try { + // 解析并加载配置 + const config = loadConfig(json5.parse(req.body.config)) as OneBotConfig; // 写入配置 - try { - // 解析并加载配置 - const config = loadConfig(json5.parse(req.body.config)) as OneBotConfig; - // 写入配置 - await WebUiDataRuntime.setOB11Config(config); - return sendSuccess(res, null); - } catch (e) { - return sendError(res, 'Error: ' + e); - } + await WebUiDataRuntime.setOB11Config(config); + return sendSuccess(res, null); + } catch (e) { + return sendError(res, 'Error: ' + e); + } }; diff --git a/src/webui/src/api/Proxy.ts b/src/webui/src/api/Proxy.ts index b5b14dc3..da4cf1ea 100644 --- a/src/webui/src/api/Proxy.ts +++ b/src/webui/src/api/Proxy.ts @@ -3,12 +3,12 @@ import { RequestUtil } from '@/common/request'; import { sendError, sendSuccess } from '../utils/response'; export const GetProxyHandler: RequestHandler = async (req, res) => { - let { url } = req.query; - if (url && typeof url === 'string') { - url = decodeURIComponent(url); - const responseText = await RequestUtil.HttpGetText(url); - return sendSuccess(res, responseText); - } else { - return sendError(res, 'url参数不合法'); - } -}; \ No newline at end of file + let { url } = req.query; + if (url && typeof url === 'string') { + url = decodeURIComponent(url); + const responseText = await RequestUtil.HttpGetText(url); + return sendSuccess(res, responseText); + } else { + return sendError(res, 'url参数不合法'); + } +}; diff --git a/src/webui/src/api/QQLogin.ts b/src/webui/src/api/QQLogin.ts index 7da2000e..085f0ea7 100644 --- a/src/webui/src/api/QQLogin.ts +++ b/src/webui/src/api/QQLogin.ts @@ -7,84 +7,84 @@ import { WebUiConfig } from '@/webui'; // 获取QQ登录二维码 export const QQGetQRcodeHandler: RequestHandler = async (_, res) => { - // 判断是否已经登录 - if (WebUiDataRuntime.getQQLoginStatus()) { - // 已经登录 - return sendError(res, 'QQ Is Logined'); - } - // 获取二维码 - const qrcodeUrl = WebUiDataRuntime.getQQLoginQrcodeURL(); - // 判断二维码是否为空 - if (isEmpty(qrcodeUrl)) { - return sendError(res, 'QRCode Get Error'); - } - // 返回二维码URL - const data = { - qrcode: qrcodeUrl, - }; - return sendSuccess(res, data); + // 判断是否已经登录 + if (WebUiDataRuntime.getQQLoginStatus()) { + // 已经登录 + return sendError(res, 'QQ Is Logined'); + } + // 获取二维码 + const qrcodeUrl = WebUiDataRuntime.getQQLoginQrcodeURL(); + // 判断二维码是否为空 + if (isEmpty(qrcodeUrl)) { + return sendError(res, 'QRCode Get Error'); + } + // 返回二维码URL + const data = { + qrcode: qrcodeUrl, + }; + return sendSuccess(res, data); }; // 获取QQ登录状态 export const QQCheckLoginStatusHandler: RequestHandler = async (_, res) => { - const data = { - isLogin: WebUiDataRuntime.getQQLoginStatus(), - qrcodeurl: WebUiDataRuntime.getQQLoginQrcodeURL(), - }; - return sendSuccess(res, data); + const data = { + isLogin: WebUiDataRuntime.getQQLoginStatus(), + qrcodeurl: WebUiDataRuntime.getQQLoginQrcodeURL(), + }; + return sendSuccess(res, data); }; // 快速登录 export const QQSetQuickLoginHandler: RequestHandler = async (req, res) => { - // 获取QQ号 - const { uin } = req.body; - // 判断是否已经登录 - const isLogin = WebUiDataRuntime.getQQLoginStatus(); - if (isLogin) { - return sendError(res, 'QQ Is Logined'); - } - // 判断QQ号是否为空 - if (isEmpty(uin)) { - return sendError(res, 'uin is empty'); - } + // 获取QQ号 + const { uin } = req.body; + // 判断是否已经登录 + const isLogin = WebUiDataRuntime.getQQLoginStatus(); + if (isLogin) { + return sendError(res, 'QQ Is Logined'); + } + // 判断QQ号是否为空 + if (isEmpty(uin)) { + return sendError(res, 'uin is empty'); + } - // 获取快速登录状态 - const { result, message } = await WebUiDataRuntime.requestQuickLogin(uin); - if (!result) { - return sendError(res, message); - } - //本来应该验证 但是http不宜这么搞 建议前端验证 - //isLogin = WebUiDataRuntime.getQQLoginStatus(); - return sendSuccess(res, null); + // 获取快速登录状态 + const { result, message } = await WebUiDataRuntime.requestQuickLogin(uin); + if (!result) { + return sendError(res, message); + } + // 本来应该验证 但是http不宜这么搞 建议前端验证 + // isLogin = WebUiDataRuntime.getQQLoginStatus(); + return sendSuccess(res, null); }; // 获取快速登录列表 export const QQGetQuickLoginListHandler: RequestHandler = async (_, res) => { - const quickLoginList = WebUiDataRuntime.getQQQuickLoginList(); - return sendSuccess(res, quickLoginList); + const quickLoginList = WebUiDataRuntime.getQQQuickLoginList(); + return sendSuccess(res, quickLoginList); }; // 获取快速登录列表(新) export const QQGetLoginListNewHandler: RequestHandler = async (_, res) => { - const newLoginList = WebUiDataRuntime.getQQNewLoginList(); - return sendSuccess(res, newLoginList); + const newLoginList = WebUiDataRuntime.getQQNewLoginList(); + return sendSuccess(res, newLoginList); }; // 获取登录的QQ的信息 export const getQQLoginInfoHandler: RequestHandler = async (_, res) => { - const data = WebUiDataRuntime.getQQLoginInfo(); - return sendSuccess(res, data); + const data = WebUiDataRuntime.getQQLoginInfo(); + return sendSuccess(res, data); }; // 获取自动登录QQ账号 export const getAutoLoginAccountHandler: RequestHandler = async (_, res) => { - const data = WebUiConfig.getAutoLoginAccount(); - return sendSuccess(res, data); + const data = WebUiConfig.getAutoLoginAccount(); + return sendSuccess(res, data); }; // 设置自动登录QQ账号 export const setAutoLoginAccountHandler: RequestHandler = async (req, res) => { - const { uin } = req.body; - await WebUiConfig.UpdateAutoLoginAccount(uin); - return sendSuccess(res, null); + const { uin } = req.body; + await WebUiConfig.UpdateAutoLoginAccount(uin); + return sendSuccess(res, null); }; diff --git a/src/webui/src/api/Status.ts b/src/webui/src/api/Status.ts index 5566bd6a..5e15c0ba 100644 --- a/src/webui/src/api/Status.ts +++ b/src/webui/src/api/Status.ts @@ -2,18 +2,18 @@ import { RequestHandler } from 'express'; import { SystemStatus, statusHelperSubscription } from '@/core/helper/status'; export const StatusRealTimeHandler: RequestHandler = async (req, res) => { - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Connection', 'keep-alive'); - const sendStatus = (status: SystemStatus) => { - try{ - res.write(`data: ${JSON.stringify(status)}\n\n`); - } catch (e) { - console.error(`An error occurred when writing sendStatus data to client: ${e}`); - } - }; - statusHelperSubscription.on('statusUpdate', sendStatus); - req.on('close', () => { - statusHelperSubscription.off('statusUpdate', sendStatus); - res.end(); - }); + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Connection', 'keep-alive'); + const sendStatus = (status: SystemStatus) => { + try { + res.write(`data: ${JSON.stringify(status)}\n\n`); + } catch (e) { + console.error(`An error occurred when writing sendStatus data to client: ${e}`); + } + }; + statusHelperSubscription.on('statusUpdate', sendStatus); + req.on('close', () => { + statusHelperSubscription.off('statusUpdate', sendStatus); + res.end(); + }); }; diff --git a/src/webui/src/api/WebUIConfig.ts b/src/webui/src/api/WebUIConfig.ts index 0c6bdd5f..cc40cf30 100644 --- a/src/webui/src/api/WebUIConfig.ts +++ b/src/webui/src/api/WebUIConfig.ts @@ -5,123 +5,123 @@ import { isEmpty } from '@webapi/utils/check'; // 获取WebUI基础配置 export const GetWebUIConfigHandler: RequestHandler = async (_, res) => { - try { - const config = await WebUiConfig.GetWebUIConfig(); - return sendSuccess(res, { - host: config.host, - port: config.port, - loginRate: config.loginRate, - disableWebUI: config.disableWebUI, - disableNonLANAccess: config.disableNonLANAccess - }); - } catch (error) { - const msg = (error as Error).message; - return sendError(res, `获取WebUI配置失败: ${msg}`); - } + try { + const config = await WebUiConfig.GetWebUIConfig(); + return sendSuccess(res, { + host: config.host, + port: config.port, + loginRate: config.loginRate, + disableWebUI: config.disableWebUI, + disableNonLANAccess: config.disableNonLANAccess, + }); + } catch (error) { + const msg = (error as Error).message; + return sendError(res, `获取WebUI配置失败: ${msg}`); + } }; // 获取是否禁用WebUI export const GetDisableWebUIHandler: RequestHandler = async (_, res) => { - try { - const disable = await WebUiConfig.GetDisableWebUI(); - return sendSuccess(res, disable); - } catch (error) { - const msg = (error as Error).message; - return sendError(res, `获取WebUI禁用状态失败: ${msg}`); - } + try { + const disable = await WebUiConfig.GetDisableWebUI(); + return sendSuccess(res, disable); + } catch (error) { + const msg = (error as Error).message; + return sendError(res, `获取WebUI禁用状态失败: ${msg}`); + } }; // 更新是否禁用WebUI export const UpdateDisableWebUIHandler: RequestHandler = async (req, res) => { - try { - const { disable } = req.body; + try { + const { disable } = req.body; - if (typeof disable !== 'boolean') { - return sendError(res, 'disable参数必须是布尔值'); - } - - await WebUiConfig.UpdateDisableWebUI(disable); - return sendSuccess(res, null); - } catch (error) { - const msg = (error as Error).message; - return sendError(res, `更新WebUI禁用状态失败: ${msg}`); + if (typeof disable !== 'boolean') { + return sendError(res, 'disable参数必须是布尔值'); } + + await WebUiConfig.UpdateDisableWebUI(disable); + return sendSuccess(res, null); + } catch (error) { + const msg = (error as Error).message; + return sendError(res, `更新WebUI禁用状态失败: ${msg}`); + } }; // 获取是否禁用非局域网访问 export const GetDisableNonLANAccessHandler: RequestHandler = async (_, res) => { - try { - const disable = await WebUiConfig.GetDisableNonLANAccess(); - return sendSuccess(res, disable); - } catch (error) { - const msg = (error as Error).message; - return sendError(res, `获取非局域网访问禁用状态失败: ${msg}`); - } + try { + const disable = await WebUiConfig.GetDisableNonLANAccess(); + return sendSuccess(res, disable); + } catch (error) { + const msg = (error as Error).message; + return sendError(res, `获取非局域网访问禁用状态失败: ${msg}`); + } }; // 更新是否禁用非局域网访问 export const UpdateDisableNonLANAccessHandler: RequestHandler = async (req, res) => { - try { - const { disable } = req.body; + try { + const { disable } = req.body; - if (typeof disable !== 'boolean') { - return sendError(res, 'disable参数必须是布尔值'); - } - - await WebUiConfig.UpdateDisableNonLANAccess(disable); - return sendSuccess(res, null); - } catch (error) { - const msg = (error as Error).message; - return sendError(res, `更新非局域网访问禁用状态失败: ${msg}`); + if (typeof disable !== 'boolean') { + return sendError(res, 'disable参数必须是布尔值'); } + + await WebUiConfig.UpdateDisableNonLANAccess(disable); + return sendSuccess(res, null); + } catch (error) { + const msg = (error as Error).message; + return sendError(res, `更新非局域网访问禁用状态失败: ${msg}`); + } }; // 更新WebUI基础配置 export const UpdateWebUIConfigHandler: RequestHandler = async (req, res) => { - try { - const { host, port, loginRate, disableWebUI, disableNonLANAccess } = req.body; + try { + const { host, port, loginRate, disableWebUI, disableNonLANAccess } = req.body; - const updateConfig: any = {}; + const updateConfig: any = {}; - if (host !== undefined) { - if (isEmpty(host)) { - return sendError(res, 'host不能为空'); - } - updateConfig.host = host; - } - - if (port !== undefined) { - if (!Number.isInteger(port) || port < 1 || port > 65535) { - return sendError(res, 'port必须是1-65535之间的整数'); - } - updateConfig.port = port; - } - - if (loginRate !== undefined) { - if (!Number.isInteger(loginRate) || loginRate < 1) { - return sendError(res, 'loginRate必须是大于0的整数'); - } - updateConfig.loginRate = loginRate; - } - - if (disableWebUI !== undefined) { - if (typeof disableWebUI !== 'boolean') { - return sendError(res, 'disableWebUI必须是布尔值'); - } - updateConfig.disableWebUI = disableWebUI; - } - - if (disableNonLANAccess !== undefined) { - if (typeof disableNonLANAccess !== 'boolean') { - return sendError(res, 'disableNonLANAccess必须是布尔值'); - } - updateConfig.disableNonLANAccess = disableNonLANAccess; - } - - await WebUiConfig.UpdateWebUIConfig(updateConfig); - return sendSuccess(res, null); - } catch (error) { - const msg = (error as Error).message; - return sendError(res, `更新WebUI配置失败: ${msg}`); + if (host !== undefined) { + if (isEmpty(host)) { + return sendError(res, 'host不能为空'); + } + updateConfig.host = host; } -}; \ No newline at end of file + + if (port !== undefined) { + if (!Number.isInteger(port) || port < 1 || port > 65535) { + return sendError(res, 'port必须是1-65535之间的整数'); + } + updateConfig.port = port; + } + + if (loginRate !== undefined) { + if (!Number.isInteger(loginRate) || loginRate < 1) { + return sendError(res, 'loginRate必须是大于0的整数'); + } + updateConfig.loginRate = loginRate; + } + + if (disableWebUI !== undefined) { + if (typeof disableWebUI !== 'boolean') { + return sendError(res, 'disableWebUI必须是布尔值'); + } + updateConfig.disableWebUI = disableWebUI; + } + + if (disableNonLANAccess !== undefined) { + if (typeof disableNonLANAccess !== 'boolean') { + return sendError(res, 'disableNonLANAccess必须是布尔值'); + } + updateConfig.disableNonLANAccess = disableNonLANAccess; + } + + await WebUiConfig.UpdateWebUIConfig(updateConfig); + return sendSuccess(res, null); + } catch (error) { + const msg = (error as Error).message; + return sendError(res, `更新WebUI配置失败: ${msg}`); + } +}; diff --git a/src/webui/src/const/status.ts b/src/webui/src/const/status.ts index 449fb416..c65e9386 100644 --- a/src/webui/src/const/status.ts +++ b/src/webui/src/const/status.ts @@ -1,13 +1,13 @@ export enum HttpStatusCode { - OK = 200, - BadRequest = 400, - Unauthorized = 401, - Forbidden = 403, - NotFound = 404, - InternalServerError = 500, + OK = 200, + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403, + NotFound = 404, + InternalServerError = 500, } export enum ResponseCode { - Success = 0, - Error = -1, + Success = 0, + Error = -1, } diff --git a/src/webui/src/helper/Data.ts b/src/webui/src/helper/Data.ts index ef52e0ab..a585565e 100644 --- a/src/webui/src/helper/Data.ts +++ b/src/webui/src/helper/Data.ts @@ -3,149 +3,149 @@ import packageJson from '../../../../package.json'; import store from '@/common/store'; const LoginRuntime: LoginRuntimeType = { - LoginCurrentTime: Date.now(), - LoginCurrentRate: 0, - QQLoginStatus: false, //已实现 但太傻了 得去那边注册个回调刷新 - QQQRCodeURL: '', - QQLoginUin: '', - QQLoginInfo: { - uid: '', - uin: '', - nick: '', + LoginCurrentTime: Date.now(), + LoginCurrentRate: 0, + QQLoginStatus: false, // 已实现 但太傻了 得去那边注册个回调刷新 + QQQRCodeURL: '', + QQLoginUin: '', + QQLoginInfo: { + uid: '', + uin: '', + nick: '', + }, + QQVersion: 'unknown', + onQQLoginStatusChange: async (status: boolean) => { + LoginRuntime.QQLoginStatus = status; + }, + onWebUiTokenChange: async (_token: string) => { + + }, + NapCatHelper: { + onOB11ConfigChanged: async () => { + }, - QQVersion: 'unknown', - onQQLoginStatusChange: async (status: boolean) => { - LoginRuntime.QQLoginStatus = status; + onQuickLoginRequested: async () => { + return { result: false, message: '' }; }, - onWebUiTokenChange: async (_token: string) => { - return; - }, - NapCatHelper: { - onOB11ConfigChanged: async () => { - return; - }, - onQuickLoginRequested: async () => { - return { result: false, message: '' }; - }, - QQLoginList: [], - NewQQLoginList: [], - }, - packageJson: packageJson, - WebUiConfigQuickFunction: async () => { - return; - } + QQLoginList: [], + NewQQLoginList: [], + }, + packageJson, + WebUiConfigQuickFunction: async () => { + + }, }; export const WebUiDataRuntime = { - setWebUiTokenChangeCallback(func: (token: string) => Promise): void { - LoginRuntime.onWebUiTokenChange = func; - }, - getWebUiTokenChangeCallback(): (token: string) => Promise { - return LoginRuntime.onWebUiTokenChange; - }, - checkLoginRate(ip: string, RateLimit: number): boolean { - const key = `login_rate:${ip}`; - const count = store.get(key) || 0; + setWebUiTokenChangeCallback (func: (token: string) => Promise): void { + LoginRuntime.onWebUiTokenChange = func; + }, + getWebUiTokenChangeCallback (): (token: string) => Promise { + return LoginRuntime.onWebUiTokenChange; + }, + checkLoginRate (ip: string, RateLimit: number): boolean { + const key = `login_rate:${ip}`; + const count = store.get(key) || 0; - if (count === 0) { - // 第一次访问,设置计数器为1,并设置60秒过期 - store.set(key, 1, 60); - return true; - } - - if (count >= RateLimit) { - return false; - } - - store.incr(key); - return true; - }, - - getQQLoginStatus(): LoginRuntimeType['QQLoginStatus'] { - return LoginRuntime.QQLoginStatus; - }, - - setQQLoginCallback(func: (status: boolean) => Promise): void { - LoginRuntime.onQQLoginStatusChange = func; - }, - - getQQLoginCallback(): (status: boolean) => Promise { - return LoginRuntime.onQQLoginStatusChange; - }, - - setQQLoginStatus(status: LoginRuntimeType['QQLoginStatus']): void { - LoginRuntime.QQLoginStatus = status; - }, - - setQQLoginQrcodeURL(url: LoginRuntimeType['QQQRCodeURL']): void { - LoginRuntime.QQQRCodeURL = url; - }, - - getQQLoginQrcodeURL(): LoginRuntimeType['QQQRCodeURL'] { - return LoginRuntime.QQQRCodeURL; - }, - - setQQLoginInfo(info: LoginRuntimeType['QQLoginInfo']): void { - LoginRuntime.QQLoginInfo = info; - LoginRuntime.QQLoginUin = info.uin.toString(); - }, - - getQQLoginInfo(): LoginRuntimeType['QQLoginInfo'] { - return LoginRuntime.QQLoginInfo; - }, - - getQQLoginUin(): LoginRuntimeType['QQLoginUin'] { - return LoginRuntime.QQLoginUin; - }, - - getQQQuickLoginList(): LoginRuntimeType['NapCatHelper']['QQLoginList'] { - return LoginRuntime.NapCatHelper.QQLoginList; - }, - - setQQQuickLoginList(list: LoginRuntimeType['NapCatHelper']['QQLoginList']): void { - LoginRuntime.NapCatHelper.QQLoginList = list; - }, - - getQQNewLoginList(): LoginRuntimeType['NapCatHelper']['NewQQLoginList'] { - return LoginRuntime.NapCatHelper.NewQQLoginList; - }, - - setQQNewLoginList(list: LoginRuntimeType['NapCatHelper']['NewQQLoginList']): void { - LoginRuntime.NapCatHelper.NewQQLoginList = list; - }, - - setQuickLoginCall(func: LoginRuntimeType['NapCatHelper']['onQuickLoginRequested']): void { - LoginRuntime.NapCatHelper.onQuickLoginRequested = func; - }, - - requestQuickLogin: function (uin) { - return LoginRuntime.NapCatHelper.onQuickLoginRequested(uin); - } as LoginRuntimeType['NapCatHelper']['onQuickLoginRequested'], - - setOnOB11ConfigChanged(func: LoginRuntimeType['NapCatHelper']['onOB11ConfigChanged']): void { - LoginRuntime.NapCatHelper.onOB11ConfigChanged = func; - }, - - setOB11Config: function (ob11) { - return LoginRuntime.NapCatHelper.onOB11ConfigChanged(ob11); - } as LoginRuntimeType['NapCatHelper']['onOB11ConfigChanged'], - - getPackageJson() { - return LoginRuntime.packageJson; - }, - - setQQVersion(version: string) { - LoginRuntime.QQVersion = version; - }, - - getQQVersion() { - return LoginRuntime.QQVersion; - }, - - setWebUiConfigQuickFunction(func: LoginRuntimeType['WebUiConfigQuickFunction']): void { - LoginRuntime.WebUiConfigQuickFunction = func; - }, - runWebUiConfigQuickFunction: async function () { - await LoginRuntime.WebUiConfigQuickFunction(); + if (count === 0) { + // 第一次访问,设置计数器为1,并设置60秒过期 + store.set(key, 1, 60); + return true; } + + if (count >= RateLimit) { + return false; + } + + store.incr(key); + return true; + }, + + getQQLoginStatus (): LoginRuntimeType['QQLoginStatus'] { + return LoginRuntime.QQLoginStatus; + }, + + setQQLoginCallback (func: (status: boolean) => Promise): void { + LoginRuntime.onQQLoginStatusChange = func; + }, + + getQQLoginCallback (): (status: boolean) => Promise { + return LoginRuntime.onQQLoginStatusChange; + }, + + setQQLoginStatus (status: LoginRuntimeType['QQLoginStatus']): void { + LoginRuntime.QQLoginStatus = status; + }, + + setQQLoginQrcodeURL (url: LoginRuntimeType['QQQRCodeURL']): void { + LoginRuntime.QQQRCodeURL = url; + }, + + getQQLoginQrcodeURL (): LoginRuntimeType['QQQRCodeURL'] { + return LoginRuntime.QQQRCodeURL; + }, + + setQQLoginInfo (info: LoginRuntimeType['QQLoginInfo']): void { + LoginRuntime.QQLoginInfo = info; + LoginRuntime.QQLoginUin = info.uin.toString(); + }, + + getQQLoginInfo (): LoginRuntimeType['QQLoginInfo'] { + return LoginRuntime.QQLoginInfo; + }, + + getQQLoginUin (): LoginRuntimeType['QQLoginUin'] { + return LoginRuntime.QQLoginUin; + }, + + getQQQuickLoginList (): LoginRuntimeType['NapCatHelper']['QQLoginList'] { + return LoginRuntime.NapCatHelper.QQLoginList; + }, + + setQQQuickLoginList (list: LoginRuntimeType['NapCatHelper']['QQLoginList']): void { + LoginRuntime.NapCatHelper.QQLoginList = list; + }, + + getQQNewLoginList (): LoginRuntimeType['NapCatHelper']['NewQQLoginList'] { + return LoginRuntime.NapCatHelper.NewQQLoginList; + }, + + setQQNewLoginList (list: LoginRuntimeType['NapCatHelper']['NewQQLoginList']): void { + LoginRuntime.NapCatHelper.NewQQLoginList = list; + }, + + setQuickLoginCall (func: LoginRuntimeType['NapCatHelper']['onQuickLoginRequested']): void { + LoginRuntime.NapCatHelper.onQuickLoginRequested = func; + }, + + requestQuickLogin: function (uin) { + return LoginRuntime.NapCatHelper.onQuickLoginRequested(uin); + } as LoginRuntimeType['NapCatHelper']['onQuickLoginRequested'], + + setOnOB11ConfigChanged (func: LoginRuntimeType['NapCatHelper']['onOB11ConfigChanged']): void { + LoginRuntime.NapCatHelper.onOB11ConfigChanged = func; + }, + + setOB11Config: function (ob11) { + return LoginRuntime.NapCatHelper.onOB11ConfigChanged(ob11); + } as LoginRuntimeType['NapCatHelper']['onOB11ConfigChanged'], + + getPackageJson () { + return LoginRuntime.packageJson; + }, + + setQQVersion (version: string) { + LoginRuntime.QQVersion = version; + }, + + getQQVersion () { + return LoginRuntime.QQVersion; + }, + + setWebUiConfigQuickFunction (func: LoginRuntimeType['WebUiConfigQuickFunction']): void { + LoginRuntime.WebUiConfigQuickFunction = func; + }, + runWebUiConfigQuickFunction: async function () { + await LoginRuntime.WebUiConfigQuickFunction(); + }, }; diff --git a/src/webui/src/helper/SignToken.ts b/src/webui/src/helper/SignToken.ts index 495bad56..71046287 100644 --- a/src/webui/src/helper/SignToken.ts +++ b/src/webui/src/helper/SignToken.ts @@ -1,106 +1,106 @@ import crypto from 'crypto'; import store from '@/common/store'; export class AuthHelper { - private static readonly secretKey = Math.random().toString(36).slice(2); + private static readonly secretKey = Math.random().toString(36).slice(2); - /** + /** * 签名凭证方法。 * @param hash 待签名的凭证字符串。 * @returns 签名后的凭证对象。 */ - public static signCredential(hash: string): WebUiCredentialJson { - const innerJson: WebUiCredentialInnerJson = { - CreatedTime: Date.now(), - HashEncoded: hash, - }; - const jsonString = JSON.stringify(innerJson); - const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex'); - return { Data: innerJson, Hmac: hmac }; - } + public static signCredential (hash: string): WebUiCredentialJson { + const innerJson: WebUiCredentialInnerJson = { + CreatedTime: Date.now(), + HashEncoded: hash, + }; + const jsonString = JSON.stringify(innerJson); + const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex'); + return { Data: innerJson, Hmac: hmac }; + } - /** + /** * 检查凭证是否被篡改的方法。 * @param credentialJson 凭证的JSON对象。 * @returns 布尔值,表示凭证是否有效。 */ - public static checkCredential(credentialJson: WebUiCredentialJson): boolean { - try { - const jsonString = JSON.stringify(credentialJson.Data); - const calculatedHmac = crypto - .createHmac('sha256', AuthHelper.secretKey) - .update(jsonString, 'utf8') - .digest('hex'); - return calculatedHmac === credentialJson.Hmac; - } catch (error) { - return false; - } + public static checkCredential (credentialJson: WebUiCredentialJson): boolean { + try { + const jsonString = JSON.stringify(credentialJson.Data); + const calculatedHmac = crypto + .createHmac('sha256', AuthHelper.secretKey) + .update(jsonString, 'utf8') + .digest('hex'); + return calculatedHmac === credentialJson.Hmac; + } catch (error) { + return false; } + } - /** + /** * 验证凭证在1小时内有效且token与原始token相同。 * @param token 待验证的原始token。 * @param credentialJson 已签名的凭证JSON对象。 * @returns 布尔值,表示凭证是否有效且token匹配。 */ - public static validateCredentialWithinOneHour(token: string, credentialJson: WebUiCredentialJson): boolean { - // 首先检查凭证是否被篡改 - const isValid = AuthHelper.checkCredential(credentialJson); - if (!isValid) { - return false; - } - - // 检查凭证是否在黑名单中 - if (AuthHelper.isCredentialRevoked(credentialJson)) { - return false; - } - - const currentTime = Date.now() / 1000; - const createdTime = credentialJson.Data.CreatedTime; - const timeDifference = currentTime - createdTime; - return timeDifference <= 3600 && credentialJson.Data.HashEncoded === AuthHelper.generatePasswordHash(token); + public static validateCredentialWithinOneHour (token: string, credentialJson: WebUiCredentialJson): boolean { + // 首先检查凭证是否被篡改 + const isValid = AuthHelper.checkCredential(credentialJson); + if (!isValid) { + return false; } - /** + // 检查凭证是否在黑名单中 + if (AuthHelper.isCredentialRevoked(credentialJson)) { + return false; + } + + const currentTime = Date.now() / 1000; + const createdTime = credentialJson.Data.CreatedTime; + const timeDifference = currentTime - createdTime; + return timeDifference <= 3600 && credentialJson.Data.HashEncoded === AuthHelper.generatePasswordHash(token); + } + + /** * 注销指定的Token凭证 * @param credentialJson 凭证JSON对象 * @returns void */ - public static revokeCredential(credentialJson: WebUiCredentialJson): void { - const jsonString = JSON.stringify(credentialJson.Data); - const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex'); + public static revokeCredential (credentialJson: WebUiCredentialJson): void { + const jsonString = JSON.stringify(credentialJson.Data); + const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex'); - // 将已注销的凭证添加到黑名单中,有效期1小时 - store.set(`revoked:${hmac}`, true, 3600); - } + // 将已注销的凭证添加到黑名单中,有效期1小时 + store.set(`revoked:${hmac}`, true, 3600); + } - /** + /** * 检查凭证是否已被注销 * @param credentialJson 凭证JSON对象 * @returns 布尔值,表示凭证是否已被注销 */ - public static isCredentialRevoked(credentialJson: WebUiCredentialJson): boolean { - const jsonString = JSON.stringify(credentialJson.Data); - const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex'); + public static isCredentialRevoked (credentialJson: WebUiCredentialJson): boolean { + const jsonString = JSON.stringify(credentialJson.Data); + const hmac = crypto.createHmac('sha256', AuthHelper.secretKey).update(jsonString, 'utf8').digest('hex'); - return store.exists(`revoked:${hmac}`) > 0; - } + return store.exists(`revoked:${hmac}`) > 0; + } - /** + /** * 生成密码Hash * @param password 密码 * @returns 生成的Hash值 */ - public static generatePasswordHash(password: string): string { - return crypto.createHash('sha256').update(password + '.napcat').digest().toString('hex') - } + public static generatePasswordHash (password: string): string { + return crypto.createHash('sha256').update(password + '.napcat').digest().toString('hex'); + } - /** + /** * 对比密码和Hash值 * @param password 密码 * @param hash Hash值 * @returns 布尔值,表示密码是否匹配Hash值 */ - public static comparePasswordHash(password: string, hash: string): boolean { - return this.generatePasswordHash(password) === hash; - } + public static comparePasswordHash (password: string, hash: string): boolean { + return this.generatePasswordHash(password) === hash; + } } diff --git a/src/webui/src/helper/config.ts b/src/webui/src/helper/config.ts index 2f611ad8..7b00ac9d 100644 --- a/src/webui/src/helper/config.ts +++ b/src/webui/src/helper/config.ts @@ -7,234 +7,234 @@ import { resolve } from 'node:path'; import { deepMerge } from '../utils/object'; import { themeType } from '../types/theme'; -import { getRandomToken } from '../utils/url' +import { getRandomToken } from '../utils/url'; // 限制尝试端口的次数,避免死循环 // 定义配置的类型 const WebUiConfigSchema = Type.Object({ - host: Type.String({ default: '0.0.0.0' }), - port: Type.Number({ default: 6099 }), - token: Type.String({ default: getRandomToken(12) }), - loginRate: Type.Number({ default: 10 }), - autoLoginAccount: Type.String({ default: '' }), - theme: themeType, - // 是否关闭WebUI - disableWebUI: Type.Boolean({ default: false }), - // 是否关闭非局域网访问 - disableNonLANAccess: Type.Boolean({ default: false }), + host: Type.String({ default: '0.0.0.0' }), + port: Type.Number({ default: 6099 }), + token: Type.String({ default: getRandomToken(12) }), + loginRate: Type.Number({ default: 10 }), + autoLoginAccount: Type.String({ default: '' }), + theme: themeType, + // 是否关闭WebUI + disableWebUI: Type.Boolean({ default: false }), + // 是否关闭非局域网访问 + disableNonLANAccess: Type.Boolean({ default: false }), }); export type WebUiConfigType = Static; // 读取当前目录下名为 webui.json 的配置文件,如果不存在则创建初始化配置文件 export class WebUiConfigWrapper { - WebUiConfigData: WebUiConfigType | undefined = undefined; + WebUiConfigData: WebUiConfigType | undefined = undefined; - private validateAndApplyDefaults(config: Partial): WebUiConfigType { - new Ajv({ coerceTypes: true, useDefaults: true }).compile(WebUiConfigSchema)(config); - return config as WebUiConfigType; + private validateAndApplyDefaults (config: Partial): WebUiConfigType { + new Ajv({ coerceTypes: true, useDefaults: true }).compile(WebUiConfigSchema)(config); + return config as WebUiConfigType; + } + + private async ensureConfigFileExists (configPath: string): Promise { + const configExists = await fs + .access(configPath, constants.F_OK) + .then(() => true) + .catch(() => false); + if (!configExists) { + await fs.writeFile(configPath, JSON.stringify(this.validateAndApplyDefaults({}), null, 4)); + } + } + + private async readAndValidateConfig (configPath: string): Promise { + const fileContent = await fs.readFile(configPath, 'utf-8'); + return this.validateAndApplyDefaults(JSON.parse(fileContent)); + } + + private async writeConfig (configPath: string, config: WebUiConfigType): Promise { + const hasWritePermission = await fs + .access(configPath, constants.W_OK) + .then(() => true) + .catch(() => false); + if (hasWritePermission) { + await fs.writeFile(configPath, JSON.stringify(config, null, 4)); + } else { + console.warn(`文件: ${configPath} 没有写入权限, 配置的更改部分可能会在重启后还原.`); + } + } + + async GetWebUIConfig (): Promise { + if (this.WebUiConfigData) { + return this.WebUiConfigData; } - private async ensureConfigFileExists(configPath: string): Promise { - const configExists = await fs - .access(configPath, constants.F_OK) - .then(() => true) - .catch(() => false); - if (!configExists) { - await fs.writeFile(configPath, JSON.stringify(this.validateAndApplyDefaults({}), null, 4)); - } + try { + const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); + await this.ensureConfigFileExists(configPath); + const parsedConfig = await this.readAndValidateConfig(configPath); + // 使用内存中缓存的token进行覆盖,确保强兼容性 + this.WebUiConfigData = { + ...parsedConfig, + // 首次读取内存中是没有token的,需要进行一层兜底 + token: getInitialWebUiToken() || parsedConfig.token, + }; + return this.WebUiConfigData; + } catch (e) { + console.log('读取配置文件失败', e); + const defaultConfig = this.validateAndApplyDefaults({}); + this.WebUiConfigData = { + ...defaultConfig, + token: getInitialWebUiToken() || defaultConfig.token, + }; + return this.WebUiConfigData; } + } - private async readAndValidateConfig(configPath: string): Promise { - const fileContent = await fs.readFile(configPath, 'utf-8'); - return this.validateAndApplyDefaults(JSON.parse(fileContent)); - } + async UpdateWebUIConfig (newConfig: Partial): Promise { + const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); + // 使用原始配置进行合并,避免内存token覆盖影响配置更新 + const currentConfig = await this.GetRawWebUIConfig(); + const mergedConfig = deepMerge({ ...currentConfig }, newConfig); + const updatedConfig = this.validateAndApplyDefaults(mergedConfig); + await this.writeConfig(configPath, updatedConfig); + this.WebUiConfigData = updatedConfig; + } - private async writeConfig(configPath: string, config: WebUiConfigType): Promise { - const hasWritePermission = await fs - .access(configPath, constants.W_OK) - .then(() => true) - .catch(() => false); - if (hasWritePermission) { - await fs.writeFile(configPath, JSON.stringify(config, null, 4)); - } else { - console.warn(`文件: ${configPath} 没有写入权限, 配置的更改部分可能会在重启后还原.`); - } - } - - async GetWebUIConfig(): Promise { - if (this.WebUiConfigData) { - return this.WebUiConfigData - } - - try { - const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); - await this.ensureConfigFileExists(configPath); - const parsedConfig = await this.readAndValidateConfig(configPath); - // 使用内存中缓存的token进行覆盖,确保强兼容性 - this.WebUiConfigData = { - ...parsedConfig, - // 首次读取内存中是没有token的,需要进行一层兜底 - token: getInitialWebUiToken() || parsedConfig.token, - }; - return this.WebUiConfigData; - } catch (e) { - console.log('读取配置文件失败', e); - const defaultConfig = this.validateAndApplyDefaults({}); - this.WebUiConfigData = { - ...defaultConfig, - token: getInitialWebUiToken() || defaultConfig.token, - } - return this.WebUiConfigData; - } - } - - async UpdateWebUIConfig(newConfig: Partial): Promise { - const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); - // 使用原始配置进行合并,避免内存token覆盖影响配置更新 - const currentConfig = await this.GetRawWebUIConfig(); - const mergedConfig = deepMerge({ ...currentConfig }, newConfig); - const updatedConfig = this.validateAndApplyDefaults(mergedConfig); - await this.writeConfig(configPath, updatedConfig); - this.WebUiConfigData = updatedConfig; - } - - /** + /** * 获取配置文件中实际存储的配置(不被内存token覆盖) * 主要用于配置更新和特殊场景 */ - async GetRawWebUIConfig(): Promise { - if (this.WebUiConfigData) { - return this.WebUiConfigData; - } - try { - const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); - await this.ensureConfigFileExists(configPath); - const parsedConfig = await this.readAndValidateConfig(configPath); - this.WebUiConfigData = parsedConfig; - return this.WebUiConfigData; - } catch (e) { - console.log('读取配置文件失败', e); - return this.validateAndApplyDefaults({}); - } + async GetRawWebUIConfig (): Promise { + if (this.WebUiConfigData) { + return this.WebUiConfigData; } - - async UpdateToken(oldToken: string, newToken: string): Promise { - // 使用内存中缓存的token进行验证,确保强兼容性 - const cachedToken = getInitialWebUiToken(); - const tokenToCheck = cachedToken || (await this.GetWebUIConfig()).token; - - if (tokenToCheck !== oldToken) { - throw new Error('旧 token 不匹配'); - } - await this.UpdateWebUIConfig({ token: newToken }); + try { + const configPath = resolve(webUiPathWrapper.configPath, './webui.json'); + await this.ensureConfigFileExists(configPath); + const parsedConfig = await this.readAndValidateConfig(configPath); + this.WebUiConfigData = parsedConfig; + return this.WebUiConfigData; + } catch (e) { + console.log('读取配置文件失败', e); + return this.validateAndApplyDefaults({}); } + } - // 获取日志文件夹路径 - async GetLogsPath(): Promise { - return resolve(webUiPathWrapper.logsPath); + async UpdateToken (oldToken: string, newToken: string): Promise { + // 使用内存中缓存的token进行验证,确保强兼容性 + const cachedToken = getInitialWebUiToken(); + const tokenToCheck = cachedToken || (await this.GetWebUIConfig()).token; + + if (tokenToCheck !== oldToken) { + throw new Error('旧 token 不匹配'); } + await this.UpdateWebUIConfig({ token: newToken }); + } - // 获取日志列表 - async GetLogsList(): Promise { - const logsPath = resolve(webUiPathWrapper.logsPath); - const logsExist = await fs - .access(logsPath, constants.F_OK) - .then(() => true) - .catch(() => false); - if (logsExist) { - return (await fs.readdir(logsPath)) - .filter((file) => file.endsWith('.log')) - .map((file) => file.replace('.log', '')); - } - return []; + // 获取日志文件夹路径 + async GetLogsPath (): Promise { + return resolve(webUiPathWrapper.logsPath); + } + + // 获取日志列表 + async GetLogsList (): Promise { + const logsPath = resolve(webUiPathWrapper.logsPath); + const logsExist = await fs + .access(logsPath, constants.F_OK) + .then(() => true) + .catch(() => false); + if (logsExist) { + return (await fs.readdir(logsPath)) + .filter((file) => file.endsWith('.log')) + .map((file) => file.replace('.log', '')); } + return []; + } - // 获取指定日志文件内容 - async GetLogContent(filename: string): Promise { - const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`); - const logExists = await fs - .access(logPath, constants.R_OK) - .then(() => true) - .catch(() => false); - if (logExists) { - return await fs.readFile(logPath, 'utf-8'); - } - return ''; + // 获取指定日志文件内容 + async GetLogContent (filename: string): Promise { + const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`); + const logExists = await fs + .access(logPath, constants.R_OK) + .then(() => true) + .catch(() => false); + if (logExists) { + return await fs.readFile(logPath, 'utf-8'); } + return ''; + } - // 获取字体文件夹内的字体列表 - async GetFontList(): Promise { - const fontsPath = resolve(webUiPathWrapper.configPath, './fonts'); - const fontsExist = await fs - .access(fontsPath, constants.F_OK) - .then(() => true) - .catch(() => false); - if (fontsExist) { - return (await fs.readdir(fontsPath)).filter((file) => file.endsWith('.ttf')); - } - return []; + // 获取字体文件夹内的字体列表 + async GetFontList (): Promise { + const fontsPath = resolve(webUiPathWrapper.configPath, './fonts'); + const fontsExist = await fs + .access(fontsPath, constants.F_OK) + .then(() => true) + .catch(() => false); + if (fontsExist) { + return (await fs.readdir(fontsPath)).filter((file) => file.endsWith('.ttf')); } + return []; + } - // 判断字体是否存在(webui.woff) - async CheckWebUIFontExist(): Promise { - const fontsPath = resolve(webUiPathWrapper.configPath, './fonts'); - return await fs - .access(resolve(fontsPath, './webui.woff'), constants.F_OK) - .then(() => true) - .catch(() => false); - } + // 判断字体是否存在(webui.woff) + async CheckWebUIFontExist (): Promise { + const fontsPath = resolve(webUiPathWrapper.configPath, './fonts'); + return await fs + .access(resolve(fontsPath, './webui.woff'), constants.F_OK) + .then(() => true) + .catch(() => false); + } - // 获取webui字体文件路径 - GetWebUIFontPath(): string { - return resolve(webUiPathWrapper.configPath, './fonts/webui.woff'); - } + // 获取webui字体文件路径 + GetWebUIFontPath (): string { + return resolve(webUiPathWrapper.configPath, './fonts/webui.woff'); + } - getAutoLoginAccount(): string | undefined { - return this.WebUiConfigData?.autoLoginAccount; - } + getAutoLoginAccount (): string | undefined { + return this.WebUiConfigData?.autoLoginAccount; + } - // 获取自动登录账号 - async GetAutoLoginAccount(): Promise { - return (await this.GetWebUIConfig()).autoLoginAccount; - } + // 获取自动登录账号 + async GetAutoLoginAccount (): Promise { + return (await this.GetWebUIConfig()).autoLoginAccount; + } - // 更新自动登录账号 - async UpdateAutoLoginAccount(uin: string): Promise { - await this.UpdateWebUIConfig({ autoLoginAccount: uin }); - } + // 更新自动登录账号 + async UpdateAutoLoginAccount (uin: string): Promise { + await this.UpdateWebUIConfig({ autoLoginAccount: uin }); + } - // 获取主题内容 - async GetTheme(): Promise { - const config = await this.GetWebUIConfig(); + // 获取主题内容 + async GetTheme (): Promise { + const config = await this.GetWebUIConfig(); - return config.theme; - } + return config.theme; + } - // 更新主题内容 - async UpdateTheme(theme: WebUiConfigType['theme']): Promise { - await this.UpdateWebUIConfig({ theme: theme }); - } + // 更新主题内容 + async UpdateTheme (theme: WebUiConfigType['theme']): Promise { + await this.UpdateWebUIConfig({ theme }); + } - // 获取是否禁用WebUI - async GetDisableWebUI(): Promise { - const config = await this.GetWebUIConfig(); - return config.disableWebUI; - } + // 获取是否禁用WebUI + async GetDisableWebUI (): Promise { + const config = await this.GetWebUIConfig(); + return config.disableWebUI; + } - // 更新是否禁用WebUI - async UpdateDisableWebUI(disable: boolean): Promise { - await this.UpdateWebUIConfig({ disableWebUI: disable }); - } + // 更新是否禁用WebUI + async UpdateDisableWebUI (disable: boolean): Promise { + await this.UpdateWebUIConfig({ disableWebUI: disable }); + } - // 获取是否禁用非局域网访问 - async GetDisableNonLANAccess(): Promise { - const config = await this.GetWebUIConfig(); - return config.disableNonLANAccess; - } + // 获取是否禁用非局域网访问 + async GetDisableNonLANAccess (): Promise { + const config = await this.GetWebUIConfig(); + return config.disableNonLANAccess; + } - // 更新是否禁用非局域网访问 - async UpdateDisableNonLANAccess(disable: boolean): Promise { - await this.UpdateWebUIConfig({ disableNonLANAccess: disable }); - } + // 更新是否禁用非局域网访问 + async UpdateDisableNonLANAccess (disable: boolean): Promise { + await this.UpdateWebUIConfig({ disableNonLANAccess: disable }); + } } diff --git a/src/webui/src/middleware/auth.ts b/src/webui/src/middleware/auth.ts index fd799568..170586a2 100644 --- a/src/webui/src/middleware/auth.ts +++ b/src/webui/src/middleware/auth.ts @@ -6,45 +6,45 @@ import { AuthHelper } from '@webapi/helper/SignToken'; import { sendError } from '@webapi/utils/response'; // 鉴权中间件 -export async function auth(req: Request, res: Response, next: NextFunction) { - // 判断当前url是否为/login 如果是跳过鉴权 - if (req.url == '/auth/login') { - return next(); - } +export async function auth (req: Request, res: Response, next: NextFunction) { + // 判断当前url是否为/login 如果是跳过鉴权 + if (req.url == '/auth/login') { + return next(); + } - // 判断是否有Authorization头 - if (req.headers?.authorization) { - // 切割参数以获取token - const authorization = req.headers.authorization.split(' '); - // 当Bearer后面没有参数时 - if (authorization.length < 2) { - return sendError(res, 'Unauthorized'); - } - // 获取token - const hash = authorization[1]; - if(!hash) return sendError(res, 'Unauthorized'); - // 解析token - let Credential: WebUiCredentialJson; - try { - Credential = JSON.parse(Buffer.from(hash, 'base64').toString('utf-8')); - } catch (e) { - return sendError(res, 'Unauthorized'); - } - // 使用启动时缓存的token进行验证,而不是动态读取配置文件 因为有可能运行时手动修改了密码 - const initialToken = getInitialWebUiToken(); - if (!initialToken) { - return sendError(res, 'Server token not initialized'); - } - // 验证凭证在1小时内有效 - const credentialJson = AuthHelper.validateCredentialWithinOneHour(initialToken, Credential); - if (credentialJson) { - // 通过验证 - return next(); - } - // 验证失败 - return sendError(res, 'Unauthorized'); + // 判断是否有Authorization头 + if (req.headers?.authorization) { + // 切割参数以获取token + const authorization = req.headers.authorization.split(' '); + // 当Bearer后面没有参数时 + if (authorization.length < 2) { + return sendError(res, 'Unauthorized'); } - - // 没有Authorization头 + // 获取token + const hash = authorization[1]; + if (!hash) return sendError(res, 'Unauthorized'); + // 解析token + let Credential: WebUiCredentialJson; + try { + Credential = JSON.parse(Buffer.from(hash, 'base64').toString('utf-8')); + } catch (e) { + return sendError(res, 'Unauthorized'); + } + // 使用启动时缓存的token进行验证,而不是动态读取配置文件 因为有可能运行时手动修改了密码 + const initialToken = getInitialWebUiToken(); + if (!initialToken) { + return sendError(res, 'Server token not initialized'); + } + // 验证凭证在1小时内有效 + const credentialJson = AuthHelper.validateCredentialWithinOneHour(initialToken, Credential); + if (credentialJson) { + // 通过验证 + return next(); + } + // 验证失败 return sendError(res, 'Unauthorized'); + } + + // 没有Authorization头 + return sendError(res, 'Unauthorized'); } diff --git a/src/webui/src/middleware/cors.ts b/src/webui/src/middleware/cors.ts index e144256f..0fee3cb9 100644 --- a/src/webui/src/middleware/cors.ts +++ b/src/webui/src/middleware/cors.ts @@ -2,61 +2,61 @@ import type { RequestHandler } from 'express'; import { WebUiConfig } from '@/webui'; // 检查是否为局域网IP地址 -function isLANIP(ip: string): boolean { - if (!ip) return false; - - // 移除IPv6的前缀,如果存在 - const cleanIP = ip.replace(/^::ffff:/, ''); - - // 本地回环地址 - if (cleanIP === '127.0.0.1' || cleanIP === 'localhost' || cleanIP === '::1') { - return true; - } - - // 检查IPv4私有网络地址 - const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; - const match = cleanIP.match(ipv4Regex); - - if (match) { - const [, a, b] = match.map(Number); - - // 10.0.0.0/8 - if (a === 10) return true; - - // 172.16.0.0/12 - if (a === 172 && b !== undefined && b >= 16 && b <= 31) return true; - - // 192.168.0.0/16 - if (a === 192 && b === 168) return true; - - // 169.254.0.0/16 (链路本地地址) - if (a === 169 && b === 254) return true; - } - - return false; +function isLANIP (ip: string): boolean { + if (!ip) return false; + + // 移除IPv6的前缀,如果存在 + const cleanIP = ip.replace(/^::ffff:/, ''); + + // 本地回环地址 + if (cleanIP === '127.0.0.1' || cleanIP === 'localhost' || cleanIP === '::1') { + return true; + } + + // 检查IPv4私有网络地址 + const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; + const match = cleanIP.match(ipv4Regex); + + if (match) { + const [, a, b] = match.map(Number); + + // 10.0.0.0/8 + if (a === 10) return true; + + // 172.16.0.0/12 + if (a === 172 && b !== undefined && b >= 16 && b <= 31) return true; + + // 192.168.0.0/16 + if (a === 192 && b === 168) return true; + + // 169.254.0.0/16 (链路本地地址) + if (a === 169 && b === 254) return true; + } + + return false; } // CORS 中间件,跨域用 export const cors: RequestHandler = async (req, res, next) => { - // 检查是否禁用非局域网访问 - const config = await WebUiConfig.GetWebUIConfig(); - if (config.disableNonLANAccess) { - const clientIP = req.ip || req.socket.remoteAddress || ''; - if (!isLANIP(clientIP)) { - res.status(403).json({ error: '非局域网访问被禁止' }); - return; - } + // 检查是否禁用非局域网访问 + const config = await WebUiConfig.GetWebUIConfig(); + if (config.disableNonLANAccess) { + const clientIP = req.ip || req.socket.remoteAddress || ''; + if (!isLANIP(clientIP)) { + res.status(403).json({ error: '非局域网访问被禁止' }); + return; } - - const origin = req.headers.origin || '*'; - res.header('Access-Control-Allow-Origin', origin); - res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); - res.header('Access-Control-Allow-Credentials', 'true'); + } - if (req.method === 'OPTIONS') { - res.sendStatus(204); - return; - } - next(); -}; \ No newline at end of file + const origin = req.headers.origin || '*'; + res.header('Access-Control-Allow-Origin', origin); + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); + res.header('Access-Control-Allow-Credentials', 'true'); + + if (req.method === 'OPTIONS') { + res.sendStatus(204); + return; + } + next(); +}; diff --git a/src/webui/src/performance-api.ts b/src/webui/src/performance-api.ts index 9b024219..1cfb3cbf 100644 --- a/src/webui/src/performance-api.ts +++ b/src/webui/src/performance-api.ts @@ -4,119 +4,119 @@ import { Router, Request, Response } from 'express'; import { performanceMonitor } from '@/common/performance-monitor'; -export function createPerformanceRouter(): Router { - const router = Router(); +export function createPerformanceRouter (): Router { + const router = Router(); - // 获取所有统计数据 - router.get('/stats', (_req: Request, res: Response) => { - try { - const stats = performanceMonitor.getStats(); - res.json({ - success: true, - data: stats, - count: stats.length - }); - } catch (error) { - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); - } - }); + // 获取所有统计数据 + router.get('/stats', (_req: Request, res: Response) => { + try { + const stats = performanceMonitor.getStats(); + res.json({ + success: true, + data: stats, + count: stats.length, + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); - // 获取总耗时排行榜 - router.get('/stats/total-time', (req: Request, res: Response) => { - try { - const limit = parseInt(req.query['limit'] as string) || 20; - const stats = performanceMonitor.getTopByTotalTime(limit); - res.json({ - success: true, - data: stats, - count: stats.length - }); - } catch (error) { - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); - } - }); + // 获取总耗时排行榜 + router.get('/stats/total-time', (req: Request, res: Response) => { + try { + const limit = parseInt(req.query['limit'] as string) || 20; + const stats = performanceMonitor.getTopByTotalTime(limit); + res.json({ + success: true, + data: stats, + count: stats.length, + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); - // 获取调用次数排行榜 - router.get('/stats/call-count', (req: Request, res: Response) => { - try { - const limit = parseInt(req.query['limit'] as string) || 20; - const stats = performanceMonitor.getTopByCallCount(limit); - res.json({ - success: true, - data: stats, - count: stats.length - }); - } catch (error) { - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); - } - }); + // 获取调用次数排行榜 + router.get('/stats/call-count', (req: Request, res: Response) => { + try { + const limit = parseInt(req.query['limit'] as string) || 20; + const stats = performanceMonitor.getTopByCallCount(limit); + res.json({ + success: true, + data: stats, + count: stats.length, + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); - // 获取平均耗时排行榜 - router.get('/stats/average-time', (req: Request, res: Response) => { - try { - const limit = parseInt(req.query['limit'] as string) || 20; - const stats = performanceMonitor.getTopByAverageTime(limit); - res.json({ - success: true, - data: stats, - count: stats.length - }); - } catch (error) { - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); - } - }); + // 获取平均耗时排行榜 + router.get('/stats/average-time', (req: Request, res: Response) => { + try { + const limit = parseInt(req.query['limit'] as string) || 20; + const stats = performanceMonitor.getTopByAverageTime(limit); + res.json({ + success: true, + data: stats, + count: stats.length, + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); - // 清空统计数据 - router.post('/clear', (_req: Request, res: Response) => { - try { - performanceMonitor.clear(); - res.json({ - success: true, - message: 'Performance statistics cleared' - }); - } catch (error) { - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); - } - }); + // 清空统计数据 + router.post('/clear', (_req: Request, res: Response) => { + try { + performanceMonitor.clear(); + res.json({ + success: true, + message: 'Performance statistics cleared', + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); - // 获取性能报告页面 - router.get('/report', (_req: Request, res: Response) => { - try { - const totalTimeStats = performanceMonitor.getTopByTotalTime(10); - const callCountStats = performanceMonitor.getTopByCallCount(10); - const averageTimeStats = performanceMonitor.getTopByAverageTime(10); + // 获取性能报告页面 + router.get('/report', (_req: Request, res: Response) => { + try { + const totalTimeStats = performanceMonitor.getTopByTotalTime(10); + const callCountStats = performanceMonitor.getTopByCallCount(10); + const averageTimeStats = performanceMonitor.getTopByAverageTime(10); - const html = generateReportHTML(totalTimeStats, callCountStats, averageTimeStats); - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.send(html); - } catch (error) { - res.status(500).json({ - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); - } - }); + const html = generateReportHTML(totalTimeStats, callCountStats, averageTimeStats); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(html); + } catch (error) { + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + }); - return router; + return router; } -function generateReportHTML(totalTimeStats: any[], callCountStats: any[], averageTimeStats: any[]): string { - return ` +function generateReportHTML (totalTimeStats: any[], callCountStats: any[], averageTimeStats: any[]): string { + return ` diff --git a/src/webui/src/router/File.ts b/src/webui/src/router/File.ts index ecb46dfc..6069f033 100644 --- a/src/webui/src/router/File.ts +++ b/src/webui/src/router/File.ts @@ -1,31 +1,31 @@ import { Router } from 'express'; import rateLimit from 'express-rate-limit'; import { - ListFilesHandler, - CreateDirHandler, - DeleteHandler, - ReadFileHandler, - WriteFileHandler, - CreateFileHandler, - BatchDeleteHandler, // 添加这一行 - RenameHandler, - MoveHandler, - BatchMoveHandler, - DownloadHandler, - BatchDownloadHandler, // 新增下载处理方法 - UploadHandler, - UploadWebUIFontHandler, - DeleteWebUIFontHandler, // 添加上传处理器 + ListFilesHandler, + CreateDirHandler, + DeleteHandler, + ReadFileHandler, + WriteFileHandler, + CreateFileHandler, + BatchDeleteHandler, // 添加这一行 + RenameHandler, + MoveHandler, + BatchMoveHandler, + DownloadHandler, + BatchDownloadHandler, // 新增下载处理方法 + UploadHandler, + UploadWebUIFontHandler, + DeleteWebUIFontHandler, // 添加上传处理器 } from '../api/File'; const router = Router(); const apiLimiter = rateLimit({ - windowMs: 1 * 60 * 1000, // 1分钟内 - max: 60, // 最大60个请求 - validate: { - xForwardedForHeader: false, - }, + windowMs: 1 * 60 * 1000, // 1分钟内 + max: 60, // 最大60个请求 + validate: { + xForwardedForHeader: false, + }, }); router.use(apiLimiter); diff --git a/src/webui/src/router/Log.ts b/src/webui/src/router/Log.ts index 9ec28887..fcc021a0 100644 --- a/src/webui/src/router/Log.ts +++ b/src/webui/src/router/Log.ts @@ -1,11 +1,11 @@ import { Router } from 'express'; import { - LogHandler, - LogListHandler, - LogRealTimeHandler, - CreateTerminalHandler, - GetTerminalListHandler, - CloseTerminalHandler, + LogHandler, + LogListHandler, + LogRealTimeHandler, + CreateTerminalHandler, + GetTerminalListHandler, + CloseTerminalHandler, } from '../api/Log'; const router = Router(); diff --git a/src/webui/src/router/QQLogin.ts b/src/webui/src/router/QQLogin.ts index aecb5b44..b1f3b60d 100644 --- a/src/webui/src/router/QQLogin.ts +++ b/src/webui/src/router/QQLogin.ts @@ -1,14 +1,14 @@ import { Router } from 'express'; import { - QQCheckLoginStatusHandler, - QQGetQRcodeHandler, - QQGetQuickLoginListHandler, - QQSetQuickLoginHandler, - QQGetLoginListNewHandler, - getQQLoginInfoHandler, - getAutoLoginAccountHandler, - setAutoLoginAccountHandler, + QQCheckLoginStatusHandler, + QQGetQRcodeHandler, + QQGetQuickLoginListHandler, + QQSetQuickLoginHandler, + QQGetLoginListNewHandler, + getQQLoginInfoHandler, + getAutoLoginAccountHandler, + setAutoLoginAccountHandler, } from '@webapi/api/QQLogin'; const router = Router(); diff --git a/src/webui/src/router/WebUIConfig.ts b/src/webui/src/router/WebUIConfig.ts index 4feafaa2..e5b1e5dc 100644 --- a/src/webui/src/router/WebUIConfig.ts +++ b/src/webui/src/router/WebUIConfig.ts @@ -1,11 +1,11 @@ import { Router } from 'express'; import { - GetWebUIConfigHandler, - GetDisableWebUIHandler, - UpdateDisableWebUIHandler, - GetDisableNonLANAccessHandler, - UpdateDisableNonLANAccessHandler, - UpdateWebUIConfigHandler + GetWebUIConfigHandler, + GetDisableWebUIHandler, + UpdateDisableWebUIHandler, + GetDisableNonLANAccessHandler, + UpdateDisableNonLANAccessHandler, + UpdateWebUIConfigHandler, } from '@webapi/api/WebUIConfig'; const router = Router(); @@ -28,4 +28,4 @@ router.get('/GetDisableNonLANAccess', GetDisableNonLANAccessHandler); // 更新是否禁用非局域网访问 router.post('/UpdateDisableNonLANAccess', UpdateDisableNonLANAccessHandler); -export { router as WebUIConfigRouter }; \ No newline at end of file +export { router as WebUIConfigRouter }; diff --git a/src/webui/src/router/auth.ts b/src/webui/src/router/auth.ts index 1e1237fa..d168dbb7 100644 --- a/src/webui/src/router/auth.ts +++ b/src/webui/src/router/auth.ts @@ -1,10 +1,10 @@ import { Router } from 'express'; import { - checkHandler, - LoginHandler, - LogoutHandler, - UpdateTokenHandler, + checkHandler, + LoginHandler, + LogoutHandler, + UpdateTokenHandler, } from '@webapi/api/Auth'; const router = Router(); diff --git a/src/webui/src/router/index.ts b/src/webui/src/router/index.ts index dd2c638a..f1c01c6c 100644 --- a/src/webui/src/router/index.ts +++ b/src/webui/src/router/index.ts @@ -22,7 +22,7 @@ router.use(auth); // router:测试用 router.all('/test', (_, res) => { - return sendSuccess(res); + return sendSuccess(res); }); // router:基础信息相关路由 router.use('/base', BaseRouter); diff --git a/src/webui/src/terminal/init-dynamic-dirname.ts b/src/webui/src/terminal/init-dynamic-dirname.ts index b4e99f4c..02b2f890 100644 --- a/src/webui/src/terminal/init-dynamic-dirname.ts +++ b/src/webui/src/terminal/init-dynamic-dirname.ts @@ -1,30 +1,30 @@ -import path from 'path' -import { fileURLToPath } from 'url' +import path from 'path'; +import { fileURLToPath } from 'url'; export function callsites () { - const _prepareStackTrace = Error.prepareStackTrace - try { - let result: NodeJS.CallSite[] = [] - Error.prepareStackTrace = (_, callSites) => { - const callSitesWithoutCurrent = callSites.slice(1) - result = callSitesWithoutCurrent - return callSitesWithoutCurrent - } + const _prepareStackTrace = Error.prepareStackTrace; + try { + let result: NodeJS.CallSite[] = []; + Error.prepareStackTrace = (_, callSites) => { + const callSitesWithoutCurrent = callSites.slice(1); + result = callSitesWithoutCurrent; + return callSitesWithoutCurrent; + }; - new Error().stack - return result - } finally { - Error.prepareStackTrace = _prepareStackTrace - } + new Error().stack; + return result; + } finally { + Error.prepareStackTrace = _prepareStackTrace; + } } Object.defineProperty(global, '__dirname', { - get () { - const sites = callsites() - const file = sites?.[1]?.getFileName() - if (file) { - return path.dirname(fileURLToPath(file)) - } - return '' - }, -}) + get () { + const sites = callsites(); + const file = sites?.[1]?.getFileName(); + if (file) { + return path.dirname(fileURLToPath(file)); + } + return ''; + }, +}); diff --git a/src/webui/src/terminal/terminal_manager.ts b/src/webui/src/terminal/terminal_manager.ts index 135363d8..94ab3c54 100644 --- a/src/webui/src/terminal/terminal_manager.ts +++ b/src/webui/src/terminal/terminal_manager.ts @@ -8,176 +8,176 @@ import { IPty, spawn as ptySpawn } from '@/pty'; import { randomUUID } from 'crypto'; interface TerminalInstance { - pty: IPty; // 改用 PTY 实例 - lastAccess: number; - sockets: Set; - // 新增标识,用于防止重复关闭 - isClosing: boolean; - // 新增:存储终端历史输出 - buffer: string; + pty: IPty; // 改用 PTY 实例 + lastAccess: number; + sockets: Set; + // 新增标识,用于防止重复关闭 + isClosing: boolean; + // 新增:存储终端历史输出 + buffer: string; } class TerminalManager { - private terminals: Map = new Map(); - private wss: WebSocketServer | null = null; + private terminals: Map = new Map(); + private wss: WebSocketServer | null = null; - initialize(req: any, socket: any, head: any, logger?: LogWrapper) { - logger?.log('[NapCat] [WebUi] terminal websocket initialized'); - this.wss = new WebSocketServer({ - noServer: true, - verifyClient: async (info, cb) => { - // 验证 token - const url = new URL(info.req.url || '', 'ws://localhost'); - const token = url.searchParams.get('token'); - const terminalId = url.searchParams.get('id'); + initialize (req: any, socket: any, head: any, logger?: LogWrapper) { + logger?.log('[NapCat] [WebUi] terminal websocket initialized'); + this.wss = new WebSocketServer({ + noServer: true, + verifyClient: async (info, cb) => { + // 验证 token + const url = new URL(info.req.url || '', 'ws://localhost'); + const token = url.searchParams.get('token'); + const terminalId = url.searchParams.get('id'); - if (!token || !terminalId) { - cb(false, 401, 'Unauthorized'); - return; - } - - // 解析token - let Credential: WebUiCredentialJson; - try { - Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); - } catch (e) { - cb(false, 401, 'Unauthorized'); - return; - } - const config = await WebUiConfig.GetWebUIConfig(); - const validate = AuthHelper.validateCredentialWithinOneHour(config.token, Credential); - if (!validate) { - cb(false, 401, 'Unauthorized'); - return; - } - cb(true); - }, - }); - this.wss.handleUpgrade(req, socket, head, (ws) => { - this.wss?.emit('connection', ws, req); - }); - this.wss.on('connection', async (ws, req) => { - logger?.log('建立终端连接'); - try { - const url = new URL(req.url || '', 'ws://localhost'); - const terminalId = url.searchParams.get('id')!; - - const instance = this.terminals.get(terminalId); - - if (!instance) { - ws.close(); - return; - } - - instance.sockets.add(ws); - instance.lastAccess = Date.now(); - - // 新增:发送当前终端内容给新连接 - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: 'output', data: instance.buffer })); - } - - ws.on('message', (data) => { - if (instance) { - const result = JSON.parse(data.toString()); - if (result.type === 'input') { - instance.pty.write(result.data); - } - // 新增:处理 resize 消息 - if (result.type === 'resize') { - instance.pty.resize(result.cols, result.rows); - } - } - }); - - ws.on('close', () => { - instance.sockets.delete(ws); - if (instance.sockets.size === 0 && !instance.isClosing) { - instance.isClosing = true; - if (os.platform() === 'win32') { - process.kill(instance.pty.pid); - } else { - instance.pty.kill(); - } - } - }); - } catch (err) { - console.error('WebSocket authentication failed:', err); - ws.close(); - } - }); - } - - // 修改:新增 cols 和 rows 参数,同步 xterm 尺寸,防止错位 - createTerminal(cols: number, rows: number) { - const id = randomUUID(); - const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; - const pty = ptySpawn(shell, [], { - name: 'xterm-256color', - cols, // 使用客户端传入的 cols - rows, // 使用客户端传入的 rows - cwd: process.cwd(), - env: { - ...process.env, - LANG: os.platform() === 'win32' ? 'chcp 65001' : 'zh_CN.UTF-8', - TERM: 'xterm-256color', - }, - }); - - const instance: TerminalInstance = { - pty, - lastAccess: Date.now(), - sockets: new Set(), - isClosing: false, - buffer: '', // 初始化终端内容缓存 - }; - - pty.onData((data: any) => { - // 追加数据到 buffer - instance.buffer += data; - // 发送数据给已连接的 websocket - instance.sockets.forEach((ws) => { - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: 'output', data })); - } - }); - }); - - pty.onExit(() => { - this.closeTerminal(id); - }); - - this.terminals.set(id, instance); - // 返回生成的 id 及对应实例,方便后续通知客户端使用该 id - return { id, instance }; - } - - closeTerminal(id: string) { - const instance = this.terminals.get(id); - if (instance) { - if (!instance.isClosing) { - instance.isClosing = true; - if (os.platform() === 'win32') { - process.kill(instance.pty.pid); - } else { - instance.pty.kill(); - } - } - instance.sockets.forEach((ws) => ws.close()); - this.terminals.delete(id); + if (!token || !terminalId) { + cb(false, 401, 'Unauthorized'); + return; } - } - getTerminal(id: string) { - return this.terminals.get(id); - } + // 解析token + let Credential: WebUiCredentialJson; + try { + Credential = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); + } catch (e) { + cb(false, 401, 'Unauthorized'); + return; + } + const config = await WebUiConfig.GetWebUIConfig(); + const validate = AuthHelper.validateCredentialWithinOneHour(config.token, Credential); + if (!validate) { + cb(false, 401, 'Unauthorized'); + return; + } + cb(true); + }, + }); + this.wss.handleUpgrade(req, socket, head, (ws) => { + this.wss?.emit('connection', ws, req); + }); + this.wss.on('connection', async (ws, req) => { + logger?.log('建立终端连接'); + try { + const url = new URL(req.url || '', 'ws://localhost'); + const terminalId = url.searchParams.get('id')!; - getTerminalList() { - return Array.from(this.terminals.keys()).map((id) => ({ - id, - lastAccess: this.terminals.get(id)!.lastAccess, - })); + const instance = this.terminals.get(terminalId); + + if (!instance) { + ws.close(); + return; + } + + instance.sockets.add(ws); + instance.lastAccess = Date.now(); + + // 新增:发送当前终端内容给新连接 + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'output', data: instance.buffer })); + } + + ws.on('message', (data) => { + if (instance) { + const result = JSON.parse(data.toString()); + if (result.type === 'input') { + instance.pty.write(result.data); + } + // 新增:处理 resize 消息 + if (result.type === 'resize') { + instance.pty.resize(result.cols, result.rows); + } + } + }); + + ws.on('close', () => { + instance.sockets.delete(ws); + if (instance.sockets.size === 0 && !instance.isClosing) { + instance.isClosing = true; + if (os.platform() === 'win32') { + process.kill(instance.pty.pid); + } else { + instance.pty.kill(); + } + } + }); + } catch (err) { + console.error('WebSocket authentication failed:', err); + ws.close(); + } + }); + } + + // 修改:新增 cols 和 rows 参数,同步 xterm 尺寸,防止错位 + createTerminal (cols: number, rows: number) { + const id = randomUUID(); + const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; + const pty = ptySpawn(shell, [], { + name: 'xterm-256color', + cols, // 使用客户端传入的 cols + rows, // 使用客户端传入的 rows + cwd: process.cwd(), + env: { + ...process.env, + LANG: os.platform() === 'win32' ? 'chcp 65001' : 'zh_CN.UTF-8', + TERM: 'xterm-256color', + }, + }); + + const instance: TerminalInstance = { + pty, + lastAccess: Date.now(), + sockets: new Set(), + isClosing: false, + buffer: '', // 初始化终端内容缓存 + }; + + pty.onData((data: any) => { + // 追加数据到 buffer + instance.buffer += data; + // 发送数据给已连接的 websocket + instance.sockets.forEach((ws) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'output', data })); + } + }); + }); + + pty.onExit(() => { + this.closeTerminal(id); + }); + + this.terminals.set(id, instance); + // 返回生成的 id 及对应实例,方便后续通知客户端使用该 id + return { id, instance }; + } + + closeTerminal (id: string) { + const instance = this.terminals.get(id); + if (instance) { + if (!instance.isClosing) { + instance.isClosing = true; + if (os.platform() === 'win32') { + process.kill(instance.pty.pid); + } else { + instance.pty.kill(); + } + } + instance.sockets.forEach((ws) => ws.close()); + this.terminals.delete(id); } + } + + getTerminal (id: string) { + return this.terminals.get(id); + } + + getTerminalList () { + return Array.from(this.terminals.keys()).map((id) => ({ + id, + lastAccess: this.terminals.get(id)!.lastAccess, + })); + } } export const terminalManager = new TerminalManager(); diff --git a/src/webui/src/types/config.d.ts b/src/webui/src/types/config.d.ts index b50b32f4..42aefc28 100644 --- a/src/webui/src/types/config.d.ts +++ b/src/webui/src/types/config.d.ts @@ -1,6 +1,6 @@ interface WebUiConfigType { - host: string; - port: number; - token: string; - loginRate: number; + host: string; + port: number; + token: string; + loginRate: number; } diff --git a/src/webui/src/types/data.d.ts b/src/webui/src/types/data.d.ts index 118a95bb..b5785f88 100644 --- a/src/webui/src/types/data.d.ts +++ b/src/webui/src/types/data.d.ts @@ -2,21 +2,21 @@ import type { LoginListItem, SelfInfo } from '@/core'; import type { OneBotConfig } from '@/onebot/config/config'; interface LoginRuntimeType { - LoginCurrentTime: number; - LoginCurrentRate: number; - QQLoginStatus: boolean; - QQQRCodeURL: string; - QQLoginUin: string; - QQLoginInfo: SelfInfo; - QQVersion: string; - onQQLoginStatusChange: (status: boolean) => Promise; - onWebUiTokenChange: (token: string) => Promise; - WebUiConfigQuickFunction: () => Promise; - NapCatHelper: { - onQuickLoginRequested: (uin: string) => Promise<{ result: boolean; message: string }>; - onOB11ConfigChanged: (ob11: OneBotConfig) => Promise; - QQLoginList: string[]; - NewQQLoginList: LoginListItem[]; - }; - packageJson: object; + LoginCurrentTime: number; + LoginCurrentRate: number; + QQLoginStatus: boolean; + QQQRCodeURL: string; + QQLoginUin: string; + QQLoginInfo: SelfInfo; + QQVersion: string; + onQQLoginStatusChange: (status: boolean) => Promise; + onWebUiTokenChange: (token: string) => Promise; + WebUiConfigQuickFunction: () => Promise; + NapCatHelper: { + onQuickLoginRequested: (uin: string) => Promise<{ result: boolean; message: string }>; + onOB11ConfigChanged: (ob11: OneBotConfig) => Promise; + QQLoginList: string[]; + NewQQLoginList: LoginListItem[]; + }; + packageJson: object; } diff --git a/src/webui/src/types/server.d.ts b/src/webui/src/types/server.d.ts index 40deb63c..0519ebfb 100644 --- a/src/webui/src/types/server.d.ts +++ b/src/webui/src/types/server.d.ts @@ -1,7 +1,7 @@ interface APIResponse { - code: number; - message: string; - data: T; + code: number; + message: string; + data: T; } type Protocol = 'http' | 'https' | 'ws' | 'wss'; diff --git a/src/webui/src/types/sign_token.d.ts b/src/webui/src/types/sign_token.d.ts index 1b6514d1..d5cfaa5a 100644 --- a/src/webui/src/types/sign_token.d.ts +++ b/src/webui/src/types/sign_token.d.ts @@ -1,9 +1,9 @@ interface WebUiCredentialInnerJson { - CreatedTime: number; - HashEncoded: string; + CreatedTime: number; + HashEncoded: string; } interface WebUiCredentialJson { - Data: WebUiCredentialInnerJson; - Hmac: string; + Data: WebUiCredentialInnerJson; + Hmac: string; } diff --git a/src/webui/src/types/theme.ts b/src/webui/src/types/theme.ts index 593eb96c..00d7a2b9 100644 --- a/src/webui/src/types/theme.ts +++ b/src/webui/src/types/theme.ts @@ -1,260 +1,260 @@ import { Type } from '@sinclair/typebox'; export const themeType = Type.Object( - { - dark: Type.Record(Type.String(), Type.String()), - light: Type.Record(Type.String(), Type.String()), - }, - { - default: { - dark: { - '--heroui-background': '0 0% 0%', - '--heroui-foreground-50': '240 5.88% 10%', - '--heroui-foreground-100': '240 3.7% 15.88%', - '--heroui-foreground-200': '240 5.26% 26.08%', - '--heroui-foreground-300': '240 5.2% 33.92%', - '--heroui-foreground-400': '240 3.83% 46.08%', - '--heroui-foreground-500': '240 5.03% 64.9%', - '--heroui-foreground-600': '240 4.88% 83.92%', - '--heroui-foreground-700': '240 5.88% 90%', - '--heroui-foreground-800': '240 4.76% 95.88%', - '--heroui-foreground-900': '0 0% 98.04%', - '--heroui-foreground': '210 5.56% 92.94%', - '--heroui-focus': '212.01999999999998 100% 46.67%', - '--heroui-overlay': '0 0% 0%', - '--heroui-divider': '0 0% 100%', - '--heroui-divider-opacity': '0.15', - '--heroui-content1': '240 5.88% 10%', - '--heroui-content1-foreground': '0 0% 98.04%', - '--heroui-content2': '240 3.7% 15.88%', - '--heroui-content2-foreground': '240 4.76% 95.88%', - '--heroui-content3': '240 5.26% 26.08%', - '--heroui-content3-foreground': '240 5.88% 90%', - '--heroui-content4': '240 5.2% 33.92%', - '--heroui-content4-foreground': '240 4.88% 83.92%', - '--heroui-default-50': '240 5.88% 10%', - '--heroui-default-100': '240 3.7% 15.88%', - '--heroui-default-200': '240 5.26% 26.08%', - '--heroui-default-300': '240 5.2% 33.92%', - '--heroui-default-400': '240 3.83% 46.08%', - '--heroui-default-500': '240 5.03% 64.9%', - '--heroui-default-600': '240 4.88% 83.92%', - '--heroui-default-700': '240 5.88% 90%', - '--heroui-default-800': '240 4.76% 95.88%', - '--heroui-default-900': '0 0% 98.04%', - '--heroui-default-foreground': '0 0% 100%', - '--heroui-default': '240 5.26% 26.08%', - '--heroui-danger-50': '301.89 82.61% 22.55%', - '--heroui-danger-100': '308.18 76.39% 28.24%', - '--heroui-danger-200': '313.85 70.65% 36.08%', - '--heroui-danger-300': '319.73 65.64% 44.51%', - '--heroui-danger-400': '325.82 69.62% 53.53%', - '--heroui-danger-500': '331.82 75% 65.49%', - '--heroui-danger-600': '337.84 83.46% 73.92%', - '--heroui-danger-700': '343.42 90.48% 83.53%', - '--heroui-danger-800': '350.53 90.48% 91.76%', - '--heroui-danger-900': '324 90.91% 95.69%', - '--heroui-danger-foreground': '0 0% 100%', - '--heroui-danger': '325.82 69.62% 53.53%', - '--heroui-primary-50': '340 84.91% 10.39%', - '--heroui-primary-100': '339.33 86.54% 20.39%', - '--heroui-primary-200': '339.11 85.99% 30.78%', - '--heroui-primary-300': '339 86.54% 40.78%', - '--heroui-primary-400': '339.2 90.36% 51.18%', - '--heroui-primary-500': '339 90% 60.78%', - '--heroui-primary-600': '339.11 90.6% 70.78%', - '--heroui-primary-700': '339.33 90% 80.39%', - '--heroui-primary-800': '340 91.84% 90.39%', - '--heroui-primary-900': '339.13 92% 95.1%', - '--heroui-primary-foreground': '0 0% 100%', - '--heroui-primary': '339.2 90.36% 51.18%', - '--heroui-secondary-50': '270 66.67% 9.41%', - '--heroui-secondary-100': '270 66.67% 18.82%', - '--heroui-secondary-200': '270 66.67% 28.24%', - '--heroui-secondary-300': '270 66.67% 37.65%', - '--heroui-secondary-400': '270 66.67% 47.06%', - '--heroui-secondary-500': '270 59.26% 57.65%', - '--heroui-secondary-600': '270 59.26% 68.24%', - '--heroui-secondary-700': '270 59.26% 78.82%', - '--heroui-secondary-800': '270 59.26% 89.41%', - '--heroui-secondary-900': '270 61.54% 94.9%', - '--heroui-secondary-foreground': '0 0% 100%', - '--heroui-secondary': '270 59.26% 57.65%', - '--heroui-success-50': '145.71 77.78% 8.82%', - '--heroui-success-100': '146.2 79.78% 17.45%', - '--heroui-success-200': '145.79 79.26% 26.47%', - '--heroui-success-300': '146.01 79.89% 35.1%', - '--heroui-success-400': '145.96 79.46% 43.92%', - '--heroui-success-500': '146.01 62.45% 55.1%', - '--heroui-success-600': '145.79 62.57% 66.47%', - '--heroui-success-700': '146.2 61.74% 77.45%', - '--heroui-success-800': '145.71 61.4% 88.82%', - '--heroui-success-900': '146.67 64.29% 94.51%', - '--heroui-success-foreground': '0 0% 0%', - '--heroui-success': '145.96 79.46% 43.92%', - '--heroui-warning-50': '37.14 75% 10.98%', - '--heroui-warning-100': '37.14 75% 21.96%', - '--heroui-warning-200': '36.96 73.96% 33.14%', - '--heroui-warning-300': '37.01 74.22% 44.12%', - '--heroui-warning-400': '37.03 91.27% 55.1%', - '--heroui-warning-500': '37.01 91.26% 64.12%', - '--heroui-warning-600': '36.96 91.24% 73.14%', - '--heroui-warning-700': '37.14 91.3% 81.96%', - '--heroui-warning-800': '37.14 91.3% 90.98%', - '--heroui-warning-900': '54.55 91.67% 95.29%', - '--heroui-warning-foreground': '0 0% 0%', - '--heroui-warning': '37.03 91.27% 55.1%', - '--heroui-code-background': '240 5.56% 7.06%', - '--heroui-strong': '190.14 94.67% 44.12%', - '--heroui-code-mdx': '190.14 94.67% 44.12%', - '--heroui-divider-weight': '1px', - '--heroui-disabled-opacity': '.5', - '--heroui-font-size-tiny': '0.75rem', - '--heroui-font-size-small': '0.875rem', - '--heroui-font-size-medium': '1rem', - '--heroui-font-size-large': '1.125rem', - '--heroui-line-height-tiny': '1rem', - '--heroui-line-height-small': '1.25rem', - '--heroui-line-height-medium': '1.5rem', - '--heroui-line-height-large': '1.75rem', - '--heroui-radius-small': '8px', - '--heroui-radius-medium': '12px', - '--heroui-radius-large': '14px', - '--heroui-border-width-small': '1px', - '--heroui-border-width-medium': '2px', - '--heroui-border-width-large': '3px', - '--heroui-box-shadow-small': + { + dark: Type.Record(Type.String(), Type.String()), + light: Type.Record(Type.String(), Type.String()), + }, + { + default: { + dark: { + '--heroui-background': '0 0% 0%', + '--heroui-foreground-50': '240 5.88% 10%', + '--heroui-foreground-100': '240 3.7% 15.88%', + '--heroui-foreground-200': '240 5.26% 26.08%', + '--heroui-foreground-300': '240 5.2% 33.92%', + '--heroui-foreground-400': '240 3.83% 46.08%', + '--heroui-foreground-500': '240 5.03% 64.9%', + '--heroui-foreground-600': '240 4.88% 83.92%', + '--heroui-foreground-700': '240 5.88% 90%', + '--heroui-foreground-800': '240 4.76% 95.88%', + '--heroui-foreground-900': '0 0% 98.04%', + '--heroui-foreground': '210 5.56% 92.94%', + '--heroui-focus': '212.01999999999998 100% 46.67%', + '--heroui-overlay': '0 0% 0%', + '--heroui-divider': '0 0% 100%', + '--heroui-divider-opacity': '0.15', + '--heroui-content1': '240 5.88% 10%', + '--heroui-content1-foreground': '0 0% 98.04%', + '--heroui-content2': '240 3.7% 15.88%', + '--heroui-content2-foreground': '240 4.76% 95.88%', + '--heroui-content3': '240 5.26% 26.08%', + '--heroui-content3-foreground': '240 5.88% 90%', + '--heroui-content4': '240 5.2% 33.92%', + '--heroui-content4-foreground': '240 4.88% 83.92%', + '--heroui-default-50': '240 5.88% 10%', + '--heroui-default-100': '240 3.7% 15.88%', + '--heroui-default-200': '240 5.26% 26.08%', + '--heroui-default-300': '240 5.2% 33.92%', + '--heroui-default-400': '240 3.83% 46.08%', + '--heroui-default-500': '240 5.03% 64.9%', + '--heroui-default-600': '240 4.88% 83.92%', + '--heroui-default-700': '240 5.88% 90%', + '--heroui-default-800': '240 4.76% 95.88%', + '--heroui-default-900': '0 0% 98.04%', + '--heroui-default-foreground': '0 0% 100%', + '--heroui-default': '240 5.26% 26.08%', + '--heroui-danger-50': '301.89 82.61% 22.55%', + '--heroui-danger-100': '308.18 76.39% 28.24%', + '--heroui-danger-200': '313.85 70.65% 36.08%', + '--heroui-danger-300': '319.73 65.64% 44.51%', + '--heroui-danger-400': '325.82 69.62% 53.53%', + '--heroui-danger-500': '331.82 75% 65.49%', + '--heroui-danger-600': '337.84 83.46% 73.92%', + '--heroui-danger-700': '343.42 90.48% 83.53%', + '--heroui-danger-800': '350.53 90.48% 91.76%', + '--heroui-danger-900': '324 90.91% 95.69%', + '--heroui-danger-foreground': '0 0% 100%', + '--heroui-danger': '325.82 69.62% 53.53%', + '--heroui-primary-50': '340 84.91% 10.39%', + '--heroui-primary-100': '339.33 86.54% 20.39%', + '--heroui-primary-200': '339.11 85.99% 30.78%', + '--heroui-primary-300': '339 86.54% 40.78%', + '--heroui-primary-400': '339.2 90.36% 51.18%', + '--heroui-primary-500': '339 90% 60.78%', + '--heroui-primary-600': '339.11 90.6% 70.78%', + '--heroui-primary-700': '339.33 90% 80.39%', + '--heroui-primary-800': '340 91.84% 90.39%', + '--heroui-primary-900': '339.13 92% 95.1%', + '--heroui-primary-foreground': '0 0% 100%', + '--heroui-primary': '339.2 90.36% 51.18%', + '--heroui-secondary-50': '270 66.67% 9.41%', + '--heroui-secondary-100': '270 66.67% 18.82%', + '--heroui-secondary-200': '270 66.67% 28.24%', + '--heroui-secondary-300': '270 66.67% 37.65%', + '--heroui-secondary-400': '270 66.67% 47.06%', + '--heroui-secondary-500': '270 59.26% 57.65%', + '--heroui-secondary-600': '270 59.26% 68.24%', + '--heroui-secondary-700': '270 59.26% 78.82%', + '--heroui-secondary-800': '270 59.26% 89.41%', + '--heroui-secondary-900': '270 61.54% 94.9%', + '--heroui-secondary-foreground': '0 0% 100%', + '--heroui-secondary': '270 59.26% 57.65%', + '--heroui-success-50': '145.71 77.78% 8.82%', + '--heroui-success-100': '146.2 79.78% 17.45%', + '--heroui-success-200': '145.79 79.26% 26.47%', + '--heroui-success-300': '146.01 79.89% 35.1%', + '--heroui-success-400': '145.96 79.46% 43.92%', + '--heroui-success-500': '146.01 62.45% 55.1%', + '--heroui-success-600': '145.79 62.57% 66.47%', + '--heroui-success-700': '146.2 61.74% 77.45%', + '--heroui-success-800': '145.71 61.4% 88.82%', + '--heroui-success-900': '146.67 64.29% 94.51%', + '--heroui-success-foreground': '0 0% 0%', + '--heroui-success': '145.96 79.46% 43.92%', + '--heroui-warning-50': '37.14 75% 10.98%', + '--heroui-warning-100': '37.14 75% 21.96%', + '--heroui-warning-200': '36.96 73.96% 33.14%', + '--heroui-warning-300': '37.01 74.22% 44.12%', + '--heroui-warning-400': '37.03 91.27% 55.1%', + '--heroui-warning-500': '37.01 91.26% 64.12%', + '--heroui-warning-600': '36.96 91.24% 73.14%', + '--heroui-warning-700': '37.14 91.3% 81.96%', + '--heroui-warning-800': '37.14 91.3% 90.98%', + '--heroui-warning-900': '54.55 91.67% 95.29%', + '--heroui-warning-foreground': '0 0% 0%', + '--heroui-warning': '37.03 91.27% 55.1%', + '--heroui-code-background': '240 5.56% 7.06%', + '--heroui-strong': '190.14 94.67% 44.12%', + '--heroui-code-mdx': '190.14 94.67% 44.12%', + '--heroui-divider-weight': '1px', + '--heroui-disabled-opacity': '.5', + '--heroui-font-size-tiny': '0.75rem', + '--heroui-font-size-small': '0.875rem', + '--heroui-font-size-medium': '1rem', + '--heroui-font-size-large': '1.125rem', + '--heroui-line-height-tiny': '1rem', + '--heroui-line-height-small': '1.25rem', + '--heroui-line-height-medium': '1.5rem', + '--heroui-line-height-large': '1.75rem', + '--heroui-radius-small': '8px', + '--heroui-radius-medium': '12px', + '--heroui-radius-large': '14px', + '--heroui-border-width-small': '1px', + '--heroui-border-width-medium': '2px', + '--heroui-border-width-large': '3px', + '--heroui-box-shadow-small': '0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)', - '--heroui-box-shadow-medium': + '--heroui-box-shadow-medium': '0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)', - '--heroui-box-shadow-large': + '--heroui-box-shadow-large': '0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)', - '--heroui-hover-opacity': '.9', - }, - light: { - '--heroui-background': '0 0% 100%', - '--heroui-foreground-50': '240 5.88% 95%', - '--heroui-foreground-100': '240 3.7% 90%', - '--heroui-foreground-200': '240 5.26% 80%', - '--heroui-foreground-300': '240 5.2% 70%', - '--heroui-foreground-400': '240 3.83% 60%', - '--heroui-foreground-500': '240 5.03% 50%', - '--heroui-foreground-600': '240 4.88% 40%', - '--heroui-foreground-700': '240 5.88% 30%', - '--heroui-foreground-800': '240 4.76% 20%', - '--heroui-foreground-900': '0 0% 10%', - '--heroui-foreground': '210 5.56% 7.06%', - '--heroui-focus': '212.01999999999998 100% 53.33%', - '--heroui-overlay': '0 0% 100%', - '--heroui-divider': '0 0% 0%', - '--heroui-divider-opacity': '0.85', - '--heroui-content1': '240 5.88% 95%', - '--heroui-content1-foreground': '0 0% 10%', - '--heroui-content2': '240 3.7% 90%', - '--heroui-content2-foreground': '240 4.76% 20%', - '--heroui-content3': '240 5.26% 80%', - '--heroui-content3-foreground': '240 5.88% 30%', - '--heroui-content4': '240 5.2% 70%', - '--heroui-content4-foreground': '240 4.88% 40%', - '--heroui-default-50': '240 5.88% 95%', - '--heroui-default-100': '240 3.7% 90%', - '--heroui-default-200': '240 5.26% 80%', - '--heroui-default-300': '240 5.2% 70%', - '--heroui-default-400': '240 3.83% 60%', - '--heroui-default-500': '240 5.03% 50%', - '--heroui-default-600': '240 4.88% 40%', - '--heroui-default-700': '240 5.88% 30%', - '--heroui-default-800': '240 4.76% 20%', - '--heroui-default-900': '0 0% 10%', - '--heroui-default-foreground': '0 0% 0%', - '--heroui-default': '240 5.26% 80%', - '--heroui-danger-50': '324 90.91% 95.69%', - '--heroui-danger-100': '350.53 90.48% 91.76%', - '--heroui-danger-200': '343.42 90.48% 83.53%', - '--heroui-danger-300': '337.84 83.46% 73.92%', - '--heroui-danger-400': '331.82 75% 65.49%', - '--heroui-danger-500': '325.82 69.62% 53.53%', - '--heroui-danger-600': '319.73 65.64% 44.51%', - '--heroui-danger-700': '313.85 70.65% 36.08%', - '--heroui-danger-800': '308.18 76.39% 28.24%', - '--heroui-danger-900': '301.89 82.61% 22.55%', - '--heroui-danger-foreground': '0 0% 100%', - '--heroui-danger': '325.82 69.62% 53.53%', - '--heroui-primary-50': '339.13 92% 95.1%', - '--heroui-primary-100': '340 91.84% 90.39%', - '--heroui-primary-200': '339.33 90% 80.39%', - '--heroui-primary-300': '339.11 90.6% 70.78%', - '--heroui-primary-400': '339 90% 60.78%', - '--heroui-primary-500': '339.2 90.36% 51.18%', - '--heroui-primary-600': '339 86.54% 40.78%', - '--heroui-primary-700': '339.11 85.99% 30.78%', - '--heroui-primary-800': '339.33 86.54% 20.39%', - '--heroui-primary-900': '340 84.91% 10.39%', - '--heroui-primary-foreground': '0 0% 100%', - '--heroui-primary': '339.2 90.36% 51.18%', - '--heroui-secondary-50': '270 61.54% 94.9%', - '--heroui-secondary-100': '270 59.26% 89.41%', - '--heroui-secondary-200': '270 59.26% 78.82%', - '--heroui-secondary-300': '270 59.26% 68.24%', - '--heroui-secondary-400': '270 59.26% 57.65%', - '--heroui-secondary-500': '270 66.67% 47.06%', - '--heroui-secondary-600': '270 66.67% 37.65%', - '--heroui-secondary-700': '270 66.67% 28.24%', - '--heroui-secondary-800': '270 66.67% 18.82%', - '--heroui-secondary-900': '270 66.67% 9.41%', - '--heroui-secondary-foreground': '0 0% 100%', - '--heroui-secondary': '270 66.67% 47.06%', - '--heroui-success-50': '146.67 64.29% 94.51%', - '--heroui-success-100': '145.71 61.4% 88.82%', - '--heroui-success-200': '146.2 61.74% 77.45%', - '--heroui-success-300': '145.79 62.57% 66.47%', - '--heroui-success-400': '146.01 62.45% 55.1%', - '--heroui-success-500': '145.96 79.46% 43.92%', - '--heroui-success-600': '146.01 79.89% 35.1%', - '--heroui-success-700': '145.79 79.26% 26.47%', - '--heroui-success-800': '146.2 79.78% 17.45%', - '--heroui-success-900': '145.71 77.78% 8.82%', - '--heroui-success-foreground': '0 0% 0%', - '--heroui-success': '145.96 79.46% 43.92%', - '--heroui-warning-50': '54.55 91.67% 95.29%', - '--heroui-warning-100': '37.14 91.3% 90.98%', - '--heroui-warning-200': '37.14 91.3% 81.96%', - '--heroui-warning-300': '36.96 91.24% 73.14%', - '--heroui-warning-400': '37.01 91.26% 64.12%', - '--heroui-warning-500': '37.03 91.27% 55.1%', - '--heroui-warning-600': '37.01 74.22% 44.12%', - '--heroui-warning-700': '36.96 73.96% 33.14%', - '--heroui-warning-800': '37.14 75% 21.96%', - '--heroui-warning-900': '37.14 75% 10.98%', - '--heroui-warning-foreground': '0 0% 0%', - '--heroui-warning': '37.03 91.27% 55.1%', - '--heroui-code-background': '221.25 17.39% 18.04%', - '--heroui-strong': '316.95 100% 65.29%', - '--heroui-code-mdx': '316.95 100% 65.29%', - '--heroui-divider-weight': '1px', - '--heroui-disabled-opacity': '.5', - '--heroui-font-size-tiny': '0.75rem', - '--heroui-font-size-small': '0.875rem', - '--heroui-font-size-medium': '1rem', - '--heroui-font-size-large': '1.125rem', - '--heroui-line-height-tiny': '1rem', - '--heroui-line-height-small': '1.25rem', - '--heroui-line-height-medium': '1.5rem', - '--heroui-line-height-large': '1.75rem', - '--heroui-radius-small': '8px', - '--heroui-radius-medium': '12px', - '--heroui-radius-large': '14px', - '--heroui-border-width-small': '1px', - '--heroui-border-width-medium': '2px', - '--heroui-border-width-large': '3px', - '--heroui-box-shadow-small': + '--heroui-hover-opacity': '.9', + }, + light: { + '--heroui-background': '0 0% 100%', + '--heroui-foreground-50': '240 5.88% 95%', + '--heroui-foreground-100': '240 3.7% 90%', + '--heroui-foreground-200': '240 5.26% 80%', + '--heroui-foreground-300': '240 5.2% 70%', + '--heroui-foreground-400': '240 3.83% 60%', + '--heroui-foreground-500': '240 5.03% 50%', + '--heroui-foreground-600': '240 4.88% 40%', + '--heroui-foreground-700': '240 5.88% 30%', + '--heroui-foreground-800': '240 4.76% 20%', + '--heroui-foreground-900': '0 0% 10%', + '--heroui-foreground': '210 5.56% 7.06%', + '--heroui-focus': '212.01999999999998 100% 53.33%', + '--heroui-overlay': '0 0% 100%', + '--heroui-divider': '0 0% 0%', + '--heroui-divider-opacity': '0.85', + '--heroui-content1': '240 5.88% 95%', + '--heroui-content1-foreground': '0 0% 10%', + '--heroui-content2': '240 3.7% 90%', + '--heroui-content2-foreground': '240 4.76% 20%', + '--heroui-content3': '240 5.26% 80%', + '--heroui-content3-foreground': '240 5.88% 30%', + '--heroui-content4': '240 5.2% 70%', + '--heroui-content4-foreground': '240 4.88% 40%', + '--heroui-default-50': '240 5.88% 95%', + '--heroui-default-100': '240 3.7% 90%', + '--heroui-default-200': '240 5.26% 80%', + '--heroui-default-300': '240 5.2% 70%', + '--heroui-default-400': '240 3.83% 60%', + '--heroui-default-500': '240 5.03% 50%', + '--heroui-default-600': '240 4.88% 40%', + '--heroui-default-700': '240 5.88% 30%', + '--heroui-default-800': '240 4.76% 20%', + '--heroui-default-900': '0 0% 10%', + '--heroui-default-foreground': '0 0% 0%', + '--heroui-default': '240 5.26% 80%', + '--heroui-danger-50': '324 90.91% 95.69%', + '--heroui-danger-100': '350.53 90.48% 91.76%', + '--heroui-danger-200': '343.42 90.48% 83.53%', + '--heroui-danger-300': '337.84 83.46% 73.92%', + '--heroui-danger-400': '331.82 75% 65.49%', + '--heroui-danger-500': '325.82 69.62% 53.53%', + '--heroui-danger-600': '319.73 65.64% 44.51%', + '--heroui-danger-700': '313.85 70.65% 36.08%', + '--heroui-danger-800': '308.18 76.39% 28.24%', + '--heroui-danger-900': '301.89 82.61% 22.55%', + '--heroui-danger-foreground': '0 0% 100%', + '--heroui-danger': '325.82 69.62% 53.53%', + '--heroui-primary-50': '339.13 92% 95.1%', + '--heroui-primary-100': '340 91.84% 90.39%', + '--heroui-primary-200': '339.33 90% 80.39%', + '--heroui-primary-300': '339.11 90.6% 70.78%', + '--heroui-primary-400': '339 90% 60.78%', + '--heroui-primary-500': '339.2 90.36% 51.18%', + '--heroui-primary-600': '339 86.54% 40.78%', + '--heroui-primary-700': '339.11 85.99% 30.78%', + '--heroui-primary-800': '339.33 86.54% 20.39%', + '--heroui-primary-900': '340 84.91% 10.39%', + '--heroui-primary-foreground': '0 0% 100%', + '--heroui-primary': '339.2 90.36% 51.18%', + '--heroui-secondary-50': '270 61.54% 94.9%', + '--heroui-secondary-100': '270 59.26% 89.41%', + '--heroui-secondary-200': '270 59.26% 78.82%', + '--heroui-secondary-300': '270 59.26% 68.24%', + '--heroui-secondary-400': '270 59.26% 57.65%', + '--heroui-secondary-500': '270 66.67% 47.06%', + '--heroui-secondary-600': '270 66.67% 37.65%', + '--heroui-secondary-700': '270 66.67% 28.24%', + '--heroui-secondary-800': '270 66.67% 18.82%', + '--heroui-secondary-900': '270 66.67% 9.41%', + '--heroui-secondary-foreground': '0 0% 100%', + '--heroui-secondary': '270 66.67% 47.06%', + '--heroui-success-50': '146.67 64.29% 94.51%', + '--heroui-success-100': '145.71 61.4% 88.82%', + '--heroui-success-200': '146.2 61.74% 77.45%', + '--heroui-success-300': '145.79 62.57% 66.47%', + '--heroui-success-400': '146.01 62.45% 55.1%', + '--heroui-success-500': '145.96 79.46% 43.92%', + '--heroui-success-600': '146.01 79.89% 35.1%', + '--heroui-success-700': '145.79 79.26% 26.47%', + '--heroui-success-800': '146.2 79.78% 17.45%', + '--heroui-success-900': '145.71 77.78% 8.82%', + '--heroui-success-foreground': '0 0% 0%', + '--heroui-success': '145.96 79.46% 43.92%', + '--heroui-warning-50': '54.55 91.67% 95.29%', + '--heroui-warning-100': '37.14 91.3% 90.98%', + '--heroui-warning-200': '37.14 91.3% 81.96%', + '--heroui-warning-300': '36.96 91.24% 73.14%', + '--heroui-warning-400': '37.01 91.26% 64.12%', + '--heroui-warning-500': '37.03 91.27% 55.1%', + '--heroui-warning-600': '37.01 74.22% 44.12%', + '--heroui-warning-700': '36.96 73.96% 33.14%', + '--heroui-warning-800': '37.14 75% 21.96%', + '--heroui-warning-900': '37.14 75% 10.98%', + '--heroui-warning-foreground': '0 0% 0%', + '--heroui-warning': '37.03 91.27% 55.1%', + '--heroui-code-background': '221.25 17.39% 18.04%', + '--heroui-strong': '316.95 100% 65.29%', + '--heroui-code-mdx': '316.95 100% 65.29%', + '--heroui-divider-weight': '1px', + '--heroui-disabled-opacity': '.5', + '--heroui-font-size-tiny': '0.75rem', + '--heroui-font-size-small': '0.875rem', + '--heroui-font-size-medium': '1rem', + '--heroui-font-size-large': '1.125rem', + '--heroui-line-height-tiny': '1rem', + '--heroui-line-height-small': '1.25rem', + '--heroui-line-height-medium': '1.5rem', + '--heroui-line-height-large': '1.75rem', + '--heroui-radius-small': '8px', + '--heroui-radius-medium': '12px', + '--heroui-radius-large': '14px', + '--heroui-border-width-small': '1px', + '--heroui-border-width-medium': '2px', + '--heroui-border-width-large': '3px', + '--heroui-box-shadow-small': '0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)', - '--heroui-box-shadow-medium': + '--heroui-box-shadow-medium': '0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)', - '--heroui-box-shadow-large': + '--heroui-box-shadow-large': '0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)', - '--heroui-hover-opacity': '.8', - }, - }, - } + '--heroui-hover-opacity': '.8', + }, + }, + } ); diff --git a/src/webui/src/uploader/disk.ts b/src/webui/src/uploader/disk.ts index 91fc1e04..da54187b 100644 --- a/src/webui/src/uploader/disk.ts +++ b/src/webui/src/uploader/disk.ts @@ -7,99 +7,99 @@ const isWindows = process.platform === 'win32'; // 修改:使用 Buffer 转码文件名,解决文件上传时乱码问题 const decodeFileName = (fileName: string): string => { - try { - return Buffer.from(fileName, 'binary').toString('utf8'); - } catch { - return fileName; - } + try { + return Buffer.from(fileName, 'binary').toString('utf8'); + } catch { + return fileName; + } }; export const createDiskStorage = (uploadPath: string) => { - return multer.diskStorage({ - destination: ( - _: Request, - file: Express.Multer.File, - cb: (error: Error | null, destination: string) => void - ) => { - try { - const decodedName = decodeFileName(file.originalname); + return multer.diskStorage({ + destination: ( + _: Request, + file: Express.Multer.File, + cb: (error: Error | null, destination: string) => void + ) => { + try { + const decodedName = decodeFileName(file.originalname); - if (!uploadPath) { - return cb(new Error('上传路径不能为空'), ''); - } + if (!uploadPath) { + return cb(new Error('上传路径不能为空'), ''); + } - if (isWindows && uploadPath === '\\') { - return cb(new Error('根目录不允许上传文件'), ''); - } + if (isWindows && uploadPath === '\\') { + return cb(new Error('根目录不允许上传文件'), ''); + } - // 处理文件夹上传的情况 - if (decodedName.includes('/') || decodedName.includes('\\')) { - const fullPath = path.join(uploadPath, path.dirname(decodedName)); - fs.mkdirSync(fullPath, { recursive: true }); - cb(null, fullPath); - } else { - cb(null, uploadPath); - } - } catch (error) { - cb(error as Error, ''); - } - }, - filename: (_: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => { - try { - const decodedName = decodeFileName(file.originalname); - const fileName = path.basename(decodedName); + // 处理文件夹上传的情况 + if (decodedName.includes('/') || decodedName.includes('\\')) { + const fullPath = path.join(uploadPath, path.dirname(decodedName)); + fs.mkdirSync(fullPath, { recursive: true }); + cb(null, fullPath); + } else { + cb(null, uploadPath); + } + } catch (error) { + cb(error as Error, ''); + } + }, + filename: (_: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => { + try { + const decodedName = decodeFileName(file.originalname); + const fileName = path.basename(decodedName); - // 检查文件是否存在 - const fullPath = path.join(uploadPath, decodedName); - if (fs.existsSync(fullPath)) { - const ext = path.extname(fileName); - const name = path.basename(fileName, ext); - cb(null, `${name}-${randomUUID()}${ext}`); - } else { - cb(null, fileName); - } - } catch (error) { - cb(error as Error, ''); - } - }, - }); + // 检查文件是否存在 + const fullPath = path.join(uploadPath, decodedName); + if (fs.existsSync(fullPath)) { + const ext = path.extname(fileName); + const name = path.basename(fileName, ext); + cb(null, `${name}-${randomUUID()}${ext}`); + } else { + cb(null, fileName); + } + } catch (error) { + cb(error as Error, ''); + } + }, + }); }; export const createDiskUpload = (uploadPath: string) => { - const upload = multer({ - storage: createDiskStorage(uploadPath), - limits: { - fileSize: 100 * 1024 * 1024, // 100MB 文件大小限制 - files: 20, // 最多同时上传20个文件 - fieldSize: 1024 * 1024, // 1MB 字段大小限制 - fields: 10 // 最多10个字段 - } - }).array('files'); - return upload; + const upload = multer({ + storage: createDiskStorage(uploadPath), + limits: { + fileSize: 100 * 1024 * 1024, // 100MB 文件大小限制 + files: 20, // 最多同时上传20个文件 + fieldSize: 1024 * 1024, // 1MB 字段大小限制 + fields: 10, // 最多10个字段 + }, + }).array('files'); + return upload; }; const diskUploader = (req: Request, res: Response) => { - const uploadPath = (req.query['path'] || '') as string; - return new Promise((resolve, reject) => { - createDiskUpload(uploadPath)(req, res, (error) => { - if (error) { - // 错误处理 - if (error.code === 'LIMIT_FILE_SIZE') { - return reject(new Error('文件大小超过限制(最大100MB)')); - } - if (error.code === 'LIMIT_FILE_COUNT') { - return reject(new Error('文件数量超过限制(最多20个文件)')); - } - if (error.code === 'LIMIT_FIELD_VALUE') { - return reject(new Error('字段值大小超过限制')); - } - if (error.code === 'LIMIT_FIELD_COUNT') { - return reject(new Error('字段数量超过限制')); - } - return reject(error); - } - return resolve(true); - }); + const uploadPath = (req.query['path'] || '') as string; + return new Promise((resolve, reject) => { + createDiskUpload(uploadPath)(req, res, (error) => { + if (error) { + // 错误处理 + if (error.code === 'LIMIT_FILE_SIZE') { + return reject(new Error('文件大小超过限制(最大100MB)')); + } + if (error.code === 'LIMIT_FILE_COUNT') { + return reject(new Error('文件数量超过限制(最多20个文件)')); + } + if (error.code === 'LIMIT_FIELD_VALUE') { + return reject(new Error('字段值大小超过限制')); + } + if (error.code === 'LIMIT_FIELD_COUNT') { + return reject(new Error('字段数量超过限制')); + } + return reject(error); + } + return resolve(true); }); + }); }; export default diskUploader; diff --git a/src/webui/src/uploader/webui_font.ts b/src/webui/src/uploader/webui_font.ts index c2b267c9..46c43c51 100644 --- a/src/webui/src/uploader/webui_font.ts +++ b/src/webui/src/uploader/webui_font.ts @@ -5,48 +5,48 @@ import type { Request, Response } from 'express'; import { WebUiConfig } from '@/webui'; export const webUIFontStorage = multer.diskStorage({ - destination: (_, __, cb) => { - try { - const fontsPath = path.dirname(WebUiConfig.GetWebUIFontPath()); - // 确保字体目录存在 - fs.mkdirSync(fontsPath, { recursive: true }); - cb(null, fontsPath); - } catch (error) { - // 确保错误信息被正确传递 - cb(new Error(`创建字体目录失败:${(error as Error).message}`), ''); - } - }, - filename: (_, __, cb) => { - // 统一保存为webui.woff - cb(null, 'webui.woff'); - }, + destination: (_, __, cb) => { + try { + const fontsPath = path.dirname(WebUiConfig.GetWebUIFontPath()); + // 确保字体目录存在 + fs.mkdirSync(fontsPath, { recursive: true }); + cb(null, fontsPath); + } catch (error) { + // 确保错误信息被正确传递 + cb(new Error(`创建字体目录失败:${(error as Error).message}`), ''); + } + }, + filename: (_, __, cb) => { + // 统一保存为webui.woff + cb(null, 'webui.woff'); + }, }); export const webUIFontUpload = multer({ - storage: webUIFontStorage, - fileFilter: (_, file, cb) => { - // 再次验证文件类型 - if (!file.originalname.toLowerCase().endsWith('.woff')) { - cb(new Error('只支持WOFF格式的字体文件')); - return; - } - cb(null, true); - }, - limits: { - fileSize: 40 * 1024 * 1024, // 限制40MB - }, + storage: webUIFontStorage, + fileFilter: (_, file, cb) => { + // 再次验证文件类型 + if (!file.originalname.toLowerCase().endsWith('.woff')) { + cb(new Error('只支持WOFF格式的字体文件')); + return; + } + cb(null, true); + }, + limits: { + fileSize: 40 * 1024 * 1024, // 限制40MB + }, }).single('file'); const webUIFontUploader = (req: Request, res: Response) => { - return new Promise((resolve, reject) => { - webUIFontUpload(req, res, (error) => { - if (error) { - // 错误处理 - // sendError(res, error.message, true); - return reject(error); - } - return resolve(true); - }); + return new Promise((resolve, reject) => { + webUIFontUpload(req, res, (error) => { + if (error) { + // 错误处理 + // sendError(res, error.message, true); + return reject(error); + } + return resolve(true); }); + }); }; export default webUIFontUploader; diff --git a/src/webui/src/utils/object.ts b/src/webui/src/utils/object.ts index e0fe8b55..2b1d2849 100644 --- a/src/webui/src/utils/object.ts +++ b/src/webui/src/utils/object.ts @@ -1,22 +1,22 @@ -export function deepMerge>(target: T, source: Partial): T { - for (const key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - // 如果 source[key] 为 undefined,则跳过(保留 target[key]) - if (source[key] === undefined) { - continue; - } - if ( - target[key] !== undefined && +export function deepMerge> (target: T, source: Partial): T { + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + // 如果 source[key] 为 undefined,则跳过(保留 target[key]) + if (source[key] === undefined) { + continue; + } + if ( + target[key] !== undefined && typeof target[key] === 'object' && !Array.isArray(target[key]) && typeof source[key] === 'object' && !Array.isArray(source[key]) - ) { - target[key] = deepMerge({ ...target[key] }, source[key]!) as T[Extract]; - } else { - target[key] = source[key]! as T[Extract]; - } - } + ) { + target[key] = deepMerge({ ...target[key] }, source[key]!) as T[Extract]; + } else { + target[key] = source[key]! as T[Extract]; + } } - return target; + } + return target; } diff --git a/src/webui/src/utils/response.ts b/src/webui/src/utils/response.ts index 81417809..9fe5908c 100644 --- a/src/webui/src/utils/response.ts +++ b/src/webui/src/utils/response.ts @@ -3,45 +3,45 @@ import type { Response } from 'express'; import { ResponseCode, HttpStatusCode } from '@webapi/const/status'; export const sendResponse = ( - res: Response, - data?: T, - code: ResponseCode = 0, - message = 'success', - useSend: boolean = false + res: Response, + data?: T, + code: ResponseCode = 0, + message = 'success', + useSend: boolean = false ) => { - const result = { - code, - message, - data, - }; - if (useSend) { - res.status(HttpStatusCode.OK).send(JSON.stringify(result)); - return; - } - res.status(HttpStatusCode.OK).json(result); + const result = { + code, + message, + data, + }; + if (useSend) { + res.status(HttpStatusCode.OK).send(JSON.stringify(result)); + return; + } + res.status(HttpStatusCode.OK).json(result); }; export const sendError = (res: Response, message = 'error', useSend: boolean = false) => { - const result = { - code: ResponseCode.Error, - message, - }; - if (useSend) { - res.status(HttpStatusCode.OK).send(JSON.stringify(result)); - return; - } - res.status(HttpStatusCode.OK).json(result); + const result = { + code: ResponseCode.Error, + message, + }; + if (useSend) { + res.status(HttpStatusCode.OK).send(JSON.stringify(result)); + return; + } + res.status(HttpStatusCode.OK).json(result); }; export const sendSuccess = (res: Response, data?: T, message = 'success', useSend: boolean = false) => { - const result = { - code: ResponseCode.Success, - data, - message, - }; - if (useSend) { - res.status(HttpStatusCode.OK).send(JSON.stringify(result)); - return; - } - res.status(HttpStatusCode.OK).json(result); + const result = { + code: ResponseCode.Success, + data, + message, + }; + if (useSend) { + res.status(HttpStatusCode.OK).send(JSON.stringify(result)); + return; + } + res.status(HttpStatusCode.OK).json(result); }; diff --git a/src/webui/src/utils/url.ts b/src/webui/src/utils/url.ts index fdeebebd..0d373963 100644 --- a/src/webui/src/utils/url.ts +++ b/src/webui/src/utils/url.ts @@ -1,47 +1,47 @@ /** * @file URL工具 */ -import fs from 'node:fs' -import { isIP } from 'node:net' -import { randomBytes } from 'node:crypto' +import fs from 'node:fs'; +import { isIP } from 'node:net'; +import { randomBytes } from 'node:crypto'; -type Protocol = 'http' | 'https' +type Protocol = 'http' | 'https'; -let isDockerCached: boolean +let isDockerCached: boolean; function hasDockerEnv () { - try { - fs.statSync('/.dockerenv') - return true - } catch { - return false - } + try { + fs.statSync('/.dockerenv'); + return true; + } catch { + return false; + } } function hasDockerCGroup () { - try { - return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker') - } catch { - return false - } + try { + return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker'); + } catch { + return false; + } } const hasContainerEnv = () => { - try { - fs.statSync('/run/.containerenv') - return true - } catch { - return false - } -} + try { + fs.statSync('/run/.containerenv'); + return true; + } catch { + return false; + } +}; const isDocker = () => { - if (isDockerCached === undefined) { - isDockerCached = hasContainerEnv() || hasDockerEnv() || hasDockerCGroup() - } + if (isDockerCached === undefined) { + isDockerCached = hasContainerEnv() || hasDockerEnv() || hasDockerCGroup(); + } - return isDockerCached -} + return isDockerCached; +}; /** * 获取默认host地址 @@ -50,8 +50,8 @@ const isDocker = () => { * @example getDefaultHost() => '0.0.0.0' // Docker环境 */ export const getDefaultHost = (): string => { - return isDocker() ? '0.0.0.0' : '127.0.0.1' -} + return isDocker() ? '0.0.0.0' : '127.0.0.1'; +}; /** * 将 host(主机地址) 转换为标准格式 @@ -62,9 +62,9 @@ export const getDefaultHost = (): string => { * @example normalizeHost('2001:4860:4801:51::27') => '[2001:4860:4801:51::27]' */ export const normalizeHost = (host: string) => { - if (isIP(host) === 6) return `[${host}]` - return host -} + if (isIP(host) === 6) return `[${host}]`; + return host; +}; /** * 创建URL @@ -77,24 +77,24 @@ export const normalizeHost = (host: string) => { * @example createUrl('baidu.com', '80', void 0, void 0, 'https') => 'https://baidu.com:80/' */ export const createUrl = ( - host: string, - port: string, - path = '/', - search?: Record, - protocol: Protocol = 'http' + host: string, + port: string, + path = '/', + search?: Record, + protocol: Protocol = 'http' ) => { - const url = new URL(`${protocol}://${normalizeHost(host)}`) - url.port = port - url.pathname = path - if (search) { - for (const key in search) { - url.searchParams.set(key, search[key]) - } + const url = new URL(`${protocol}://${normalizeHost(host)}`); + url.port = port; + url.pathname = path; + if (search) { + for (const key in search) { + url.searchParams.set(key, search[key]); } + } - /** 进行url解码 对特殊字符进行处理 */ - return decodeURIComponent(url.toString()) -} + /** 进行url解码 对特殊字符进行处理 */ + return decodeURIComponent(url.toString()); +}; /** * 生成随机Token @@ -103,5 +103,5 @@ export const createUrl = ( * @example getRandomToken */ export const getRandomToken = (length = 8) => { - return randomBytes(36).toString('hex').slice(0, length) -} + return randomBytes(36).toString('hex').slice(0, length); +};