mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
964014fc5c | ||
![]() |
fc2bb6d8c3 | ||
![]() |
1b10252d76 | ||
![]() |
ad8af12a10 | ||
![]() |
b040c9b118 | ||
![]() |
f6da7da90b | ||
![]() |
a745185408 | ||
![]() |
d3336f9027 | ||
![]() |
daf42c8203 | ||
![]() |
0a18bae3b5 | ||
![]() |
919705966c | ||
![]() |
2c54aee63e | ||
![]() |
3f80bdf2a3 | ||
![]() |
1c429b8dd3 | ||
![]() |
5669e2b0b7 | ||
![]() |
1a6a43babf | ||
![]() |
2650db5ddc | ||
![]() |
255491a107 | ||
![]() |
5c64147dfa | ||
![]() |
39f4118577 | ||
![]() |
f7f6e4736a | ||
![]() |
c635da7ebb | ||
![]() |
58124b006a | ||
![]() |
563aeccd0f |
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "2.3.7",
|
"version": "2.4.4",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.3.7",
|
"version": "2.4.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "vite build --mode framework",
|
"build:framework": "vite build --mode framework",
|
||||||
"build:shell": "vite build --mode shell",
|
"build:shell": "vite build --mode shell",
|
||||||
|
@@ -1,66 +1,64 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
|
||||||
import fsPromise from 'fs/promises';
|
import fsPromise from 'fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
|
import { encode, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
|
||||||
import { LogWrapper } from './log';
|
import { LogWrapper } from './log';
|
||||||
|
|
||||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||||
async function guessDuration(pttPath: string) {
|
const EXIT_CODES = [0, 255];
|
||||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||||
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
|
|
||||||
duration = Math.floor(duration);
|
|
||||||
duration = Math.max(1, duration);
|
|
||||||
logger.log('通过文件大小估算语音的时长:', duration);
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function guessDuration(pttPath: string, logger: LogWrapper) {
|
||||||
|
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||||
|
let duration = Math.max(1, Math.floor(pttFileInfo.size / 1024 / 3)); // 3kb/s
|
||||||
|
logger.log('通过文件大小估算语音的时长:', duration);
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convert(filePath: string, pcmPath: string, logger: LogWrapper): Promise<Buffer> {
|
||||||
|
return new Promise<Buffer>((resolve, reject) => {
|
||||||
|
const cp = spawn(FFMPEG_PATH, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
||||||
|
cp.on('error', err => {
|
||||||
|
logger.log('FFmpeg处理转换出错: ', err.message);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
cp.on('exit', async (code, signal) => {
|
||||||
|
if (code == null || EXIT_CODES.includes(code)) {
|
||||||
|
try {
|
||||||
|
const data = await fsPromise.readFile(pcmPath);
|
||||||
|
await fsPromise.unlink(pcmPath);
|
||||||
|
resolve(data);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||||
|
reject(new Error('FFmpeg处理转换失败'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleWavFile(file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper): Promise<Buffer> {
|
||||||
|
const { fmt } = getWavFileInfo(file);
|
||||||
|
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
||||||
|
return await convert(filePath, pcmPath, logger);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||||
try {
|
try {
|
||||||
const file = await fsPromise.readFile(filePath);
|
const file = await fsPromise.readFile(filePath);
|
||||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||||
if (!isSilk(file)) {
|
if (!isSilk(file)) {
|
||||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||||
const _isWav = isWav(file);
|
const pcmPath = `${pttPath}.pcm`;
|
||||||
const pcmPath = pttPath + '.pcm';
|
const input = isWav(file) ? await handleWavFile(file, filePath, pcmPath, logger) : await convert(filePath, pcmPath, logger);
|
||||||
let sampleRate = 0;
|
const silk = await encode(input, 24000);
|
||||||
const convert = () => {
|
await fsPromise.writeFile(pttPath, silk.data);
|
||||||
return new Promise<Buffer>((resolve, reject) => {
|
|
||||||
// todo: 通过配置文件获取ffmpeg路径
|
|
||||||
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
|
|
||||||
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
|
||||||
cp.on('error', err => {
|
|
||||||
logger.log('FFmpeg处理转换出错: ', err.message);
|
|
||||||
return reject(err);
|
|
||||||
});
|
|
||||||
cp.on('exit', (code, signal) => {
|
|
||||||
const EXIT_CODES = [0, 255];
|
|
||||||
if (code == null || EXIT_CODES.includes(code)) {
|
|
||||||
sampleRate = 24000;
|
|
||||||
const data = fs.readFileSync(pcmPath);
|
|
||||||
fs.unlink(pcmPath, (err) => {
|
|
||||||
});
|
|
||||||
return resolve(data);
|
|
||||||
}
|
|
||||||
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
|
||||||
reject(Error('FFmpeg处理转换失败'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let input: Buffer;
|
|
||||||
if (!_isWav) {
|
|
||||||
input = await convert();
|
|
||||||
} else {
|
|
||||||
input = file;
|
|
||||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
|
||||||
const { fmt } = getWavFileInfo(input);
|
|
||||||
// log(`wav文件信息`, fmt)
|
|
||||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
|
||||||
input = await convert();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const silk = await encode(input, sampleRate);
|
|
||||||
fs.writeFileSync(pttPath, silk.data);
|
|
||||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||||
return {
|
return {
|
||||||
converted: true,
|
converted: true,
|
||||||
@@ -68,15 +66,13 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
duration: silk.duration / 1000,
|
duration: silk.duration / 1000,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const silk = file;
|
|
||||||
let duration = 0;
|
let duration = 0;
|
||||||
try {
|
try {
|
||||||
duration = getDuration(silk) / 1000;
|
duration = getDuration(file) / 1000;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||||
duration = await guessDuration(filePath);
|
duration = await guessDuration(filePath, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
converted: false,
|
converted: false,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
@@ -87,4 +83,4 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
|||||||
logger.logError('convert silk failed', error.stack);
|
logger.logError('convert silk failed', error.stack);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -215,6 +215,10 @@ export async function checkUriType(Uri: string) {
|
|||||||
}
|
}
|
||||||
return { Uri: filePath, Type: FileUriType.Local };
|
return { Uri: filePath, Type: FileUriType.Local };
|
||||||
}
|
}
|
||||||
|
if (uri.startsWith('data:')) {
|
||||||
|
const data = uri.split(',')[1];
|
||||||
|
if (data) return { Uri: data, Type: FileUriType.Base64 };
|
||||||
|
}
|
||||||
}, Uri);
|
}, Uri);
|
||||||
if (OtherFileRet) return OtherFileRet;
|
if (OtherFileRet) return OtherFileRet;
|
||||||
|
|
||||||
@@ -231,7 +235,8 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
|||||||
//解析File协议和本地文件
|
//解析File协议和本地文件
|
||||||
if (UriType == FileUriType.Local) {
|
if (UriType == FileUriType.Local) {
|
||||||
const fileExt = path.extname(HandledUri);
|
const fileExt = path.extname(HandledUri);
|
||||||
const filename = path.basename(HandledUri, fileExt);
|
let filename = path.basename(HandledUri, fileExt);
|
||||||
|
filename += fileExt;
|
||||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
|
||||||
}
|
}
|
||||||
//接下来都要有文件名
|
//接下来都要有文件名
|
||||||
@@ -251,7 +256,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
|||||||
const filePath = path.join(dir, filename);
|
const filePath = path.join(dir, filename);
|
||||||
const buffer = await httpDownload(HandledUri);
|
const buffer = await httpDownload(HandledUri);
|
||||||
fs.writeFileSync(filePath, buffer);
|
fs.writeFileSync(filePath, buffer);
|
||||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: false };
|
||||||
}
|
}
|
||||||
//解析Base64
|
//解析Base64
|
||||||
if (UriType == FileUriType.Base64) {
|
if (UriType == FileUriType.Base64) {
|
||||||
@@ -266,7 +271,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
|
|||||||
fileExt = ext;
|
fileExt = ext;
|
||||||
filename = filename + '.' + ext;
|
filename = filename + '.' + ext;
|
||||||
}
|
}
|
||||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: true };
|
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: false };
|
||||||
}
|
}
|
||||||
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
|
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
|
||||||
}
|
}
|
||||||
|
@@ -25,8 +25,13 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FileNapCatOneBotUUID {
|
export class FileNapCatOneBotUUID {
|
||||||
static encodeModelId(peer: Peer, modelId: string, fileId: string): string {
|
static encodeModelId(peer: Peer, modelId: string, fileId: string, endString: string = ""): string {
|
||||||
return `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
|
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}`;
|
||||||
|
//前四个字节塞data长度
|
||||||
|
const length = Buffer.alloc(4 + data.length);
|
||||||
|
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
|
||||||
|
length.write(data, 4);
|
||||||
|
return length.toString('hex') + endString;
|
||||||
}
|
}
|
||||||
|
|
||||||
static decodeModelId(uuid: string): undefined | {
|
static decodeModelId(uuid: string): undefined | {
|
||||||
@@ -34,8 +39,14 @@ export class FileNapCatOneBotUUID {
|
|||||||
modelId: string,
|
modelId: string,
|
||||||
fileId: string
|
fileId: string
|
||||||
} {
|
} {
|
||||||
if (!uuid.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
//前四个字节是data长度
|
||||||
const data = uuid.split('|');
|
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||||
|
//根据length计算需要读取的长度
|
||||||
|
const dataId = uuid.slice(8, 8 + length);
|
||||||
|
//hex还原为string
|
||||||
|
const realData = Buffer.from(dataId, 'hex').toString();
|
||||||
|
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
|
||||||
|
const data = realData.split('|');
|
||||||
if (data.length !== 6) return undefined;
|
if (data.length !== 6) return undefined;
|
||||||
const [, , chatType, peerUid, modelId, fileId] = data;
|
const [, , chatType, peerUid, modelId, fileId] = data;
|
||||||
return {
|
return {
|
||||||
@@ -48,8 +59,14 @@ export class FileNapCatOneBotUUID {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static encode(peer: Peer, msgId: string, elementId: string): string {
|
static encode(peer: Peer, msgId: string, elementId: string, endString: string = ""): string {
|
||||||
return `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
|
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}`;
|
||||||
|
//前四个字节塞data长度
|
||||||
|
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
|
||||||
|
const length = Buffer.alloc(4 + data.length);
|
||||||
|
length.writeUInt32BE(data.length * 2, 0);
|
||||||
|
length.write(data, 4);
|
||||||
|
return length.toString('hex') + endString;
|
||||||
}
|
}
|
||||||
|
|
||||||
static decode(uuid: string): undefined | {
|
static decode(uuid: string): undefined | {
|
||||||
@@ -57,8 +74,14 @@ export class FileNapCatOneBotUUID {
|
|||||||
msgId: string,
|
msgId: string,
|
||||||
elementId: string
|
elementId: string
|
||||||
} {
|
} {
|
||||||
if (!uuid.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
//前四个字节是data长度
|
||||||
const data = uuid.split('|');
|
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
|
||||||
|
//根据length计算需要读取的长度
|
||||||
|
const dataId = uuid.slice(8, 8 + length);
|
||||||
|
//hex还原为string
|
||||||
|
const realData = Buffer.from(dataId, 'hex').toString();
|
||||||
|
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
|
||||||
|
const data = realData.split('|');
|
||||||
if (data.length !== 6) return undefined;
|
if (data.length !== 6) return undefined;
|
||||||
const [, , chatType, peerUid, msgId, elementId] = data;
|
const [, , chatType, peerUid, msgId, elementId] = data;
|
||||||
return {
|
return {
|
||||||
|
@@ -53,23 +53,23 @@ export class QQBasicInfoWrapper {
|
|||||||
//此方法不要直接使用
|
//此方法不要直接使用
|
||||||
getQUAInternal() {
|
getQUAInternal() {
|
||||||
switch (systemPlatform) {
|
switch (systemPlatform) {
|
||||||
case 'linux':
|
case 'linux':
|
||||||
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
return `V1_LNX_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
return `V1_MAC_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||||
default:
|
default:
|
||||||
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
return `V1_WIN_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppidInternal() {
|
getAppidInternal() {
|
||||||
switch (systemPlatform) {
|
switch (systemPlatform) {
|
||||||
case 'linux':
|
case 'linux':
|
||||||
return '537243600';
|
return '537243600';
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
return '537243441';
|
return '537243441';
|
||||||
default:
|
default:
|
||||||
return '537243538';
|
return '537243538';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '2.3.7';
|
export const napCatVersion = '2.4.4';
|
||||||
|
@@ -1,63 +0,0 @@
|
|||||||
import {
|
|
||||||
CacheFileListItem,
|
|
||||||
CacheFileType,
|
|
||||||
ChatCacheListItemBasic,
|
|
||||||
ChatType,
|
|
||||||
InstanceContext,
|
|
||||||
NapCatCore,
|
|
||||||
} from '@/core';
|
|
||||||
|
|
||||||
export class NTQQCacheApi {
|
|
||||||
context: InstanceContext;
|
|
||||||
core: NapCatCore;
|
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
|
||||||
this.context = context;
|
|
||||||
this.core = core;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setCacheSilentScan(isSilent: boolean = true) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
getCacheSessionPathList() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
|
||||||
// 参数未验证
|
|
||||||
return this.context.session.getStorageCleanService().clearCacheDataByKeys(cacheKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
addCacheScannedPaths(pathMap: object = {}) {
|
|
||||||
return this.context.session.getStorageCleanService().addCacheScanedPaths(pathMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
scanCache() {
|
|
||||||
//return (await this.context.session.getStorageCleanService().scanCache()).size;
|
|
||||||
}
|
|
||||||
|
|
||||||
getHotUpdateCachePath() {
|
|
||||||
// 未实现
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
getDesktopTmpPath() {
|
|
||||||
// 未实现
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
|
|
||||||
return this.context.session.getStorageCleanService().getChatCacheInfo(type, pageSize, 1, pageIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
|
|
||||||
// const _lastRecord = lastRecord ? lastRecord : { fileType: fileType };
|
|
||||||
// 需要五个参数
|
|
||||||
// return napCatCore.session.getStorageCleanService().getFileCacheInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
|
||||||
return this.context.session.getStorageCleanService().clearChatCacheInfo(chats, fileKeys);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -33,7 +33,7 @@ export class NTQQFileApi {
|
|||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this.rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey', this.context.logger);
|
this.rkeyManager = new RkeyManager('https://llob.linyuchen.net/rkey', this.context.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
async copyFile(filePath: string, destPath: string) {
|
async copyFile(filePath: string, destPath: string) {
|
||||||
@@ -207,7 +207,9 @@ export class NTQQFileApi {
|
|||||||
throw new Error('文件异常,大小为0');
|
throw new Error('文件异常,大小为0');
|
||||||
}
|
}
|
||||||
if (converted) {
|
if (converted) {
|
||||||
fsPromises.unlink(silkPath);
|
fsPromises.unlink(silkPath).then().catch(
|
||||||
|
(e) => this.context.logger.logError('删除临时文件失败', e)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
elementType: ElementType.PTT,
|
elementType: ElementType.PTT,
|
||||||
@@ -246,7 +248,6 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
|
||||||
//logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force);
|
|
||||||
// 用于下载收到的消息中的图片等
|
// 用于下载收到的消息中的图片等
|
||||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||||
if (force) {
|
if (force) {
|
||||||
@@ -346,8 +347,8 @@ export class NTQQFileApi {
|
|||||||
if (url) {
|
if (url) {
|
||||||
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
|
const parsedUrl = new URL(IMAGE_HTTP_HOST + url);
|
||||||
const imageAppid = parsedUrl.searchParams.get('appid');
|
const imageAppid = parsedUrl.searchParams.get('appid');
|
||||||
const isNTFlavoredPic = imageAppid && ['1406', '1407'].includes(imageAppid);
|
const isNTV2 = imageAppid && ['1406', '1407'].includes(imageAppid);
|
||||||
if (isNTFlavoredPic) {
|
if (isNTV2) {
|
||||||
let rkey = parsedUrl.searchParams.get('rkey');
|
let rkey = parsedUrl.searchParams.get('rkey');
|
||||||
if (rkey) {
|
if (rkey) {
|
||||||
return IMAGE_HTTP_HOST_NT + url;
|
return IMAGE_HTTP_HOST_NT + url;
|
||||||
@@ -356,11 +357,9 @@ export class NTQQFileApi {
|
|||||||
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||||
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
|
return IMAGE_HTTP_HOST_NT + url + `${rkey}`;
|
||||||
} else {
|
} else {
|
||||||
// 老的图片url,不需要rkey
|
|
||||||
return IMAGE_HTTP_HOST + url;
|
return IMAGE_HTTP_HOST + url;
|
||||||
}
|
}
|
||||||
} else if (fileMd5 || md5HexStr) {
|
} else if (fileMd5 || md5HexStr) {
|
||||||
// 没有url,需要自己拼接
|
|
||||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 ?? md5HexStr)!.toUpperCase()}/0`;
|
||||||
}
|
}
|
||||||
this.context.logger.logDebug('图片url获取失败', element);
|
this.context.logger.logDebug('图片url获取失败', element);
|
||||||
|
@@ -32,6 +32,10 @@ export class NTQQGroupApi {
|
|||||||
for (const group of this.groups) {
|
for (const group of this.groups) {
|
||||||
this.groupCache.set(group.groupCode, group);
|
this.groupCache.set(group.groupCode, group);
|
||||||
}
|
}
|
||||||
|
// let text = await this.context.session.getMsgService().sendSsoCmdReqByContend(
|
||||||
|
// 'LightAppSvc.mini_app_share.AdaptShareInfo',
|
||||||
|
// JSON.stringify({ data: 'test' }));
|
||||||
|
// console.log(text);
|
||||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +267,7 @@ export class NTQQGroupApi {
|
|||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||||
let data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||||
return eventWrapper.callNormalEventV2(
|
return eventWrapper.callNormalEventV2(
|
||||||
'NodeIKernelGroupService/getMemberInfo',
|
'NodeIKernelGroupService/getMemberInfo',
|
||||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||||
@@ -278,7 +282,7 @@ export class NTQQGroupApi {
|
|||||||
return data[3].get(uid);
|
return data[3].get(uid);
|
||||||
}
|
}
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
let trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
const trydata = await this.getGroupMemberEx(GroupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||||
if (trydata) return trydata;
|
if (trydata) return trydata;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@@ -5,5 +5,4 @@ export * from './msg';
|
|||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './webapi';
|
export * from './webapi';
|
||||||
export * from './sign';
|
export * from './sign';
|
||||||
export * from './system';
|
export * from './system';
|
||||||
export * from './cache';
|
|
@@ -157,7 +157,7 @@ export class NTQQWebApi {
|
|||||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let settings = JSON.stringify({
|
const settings = JSON.stringify({
|
||||||
is_show_edit_card: is_show_edit_card,
|
is_show_edit_card: is_show_edit_card,
|
||||||
tip_window_type: tip_window_type,
|
tip_window_type: tip_window_type,
|
||||||
confirm_required: confirm_required
|
confirm_required: confirm_required
|
||||||
@@ -167,7 +167,7 @@ export class NTQQWebApi {
|
|||||||
imgWidth: imgWidth.toString(),
|
imgWidth: imgWidth.toString(),
|
||||||
imgHeight: imgHeight.toString(),
|
imgHeight: imgHeight.toString(),
|
||||||
};
|
};
|
||||||
let ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||||
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||||
bkn: this.getBknFromCookie(cookieObject),
|
bkn: this.getBknFromCookie(cookieObject),
|
||||||
qid: GroupCode,
|
qid: GroupCode,
|
||||||
|
@@ -153,7 +153,10 @@ interface CommonExt {
|
|||||||
labels: any[];
|
labels: any[];
|
||||||
qqLevel: QQLevel;
|
qqLevel: QQLevel;
|
||||||
}
|
}
|
||||||
|
export enum BuddyListReqType {
|
||||||
|
KNOMAL,
|
||||||
|
KLETTER
|
||||||
|
}
|
||||||
interface Pic {
|
interface Pic {
|
||||||
picId: string;
|
picId: string;
|
||||||
picTime: number;
|
picTime: number;
|
||||||
@@ -375,8 +378,4 @@ export enum ProfileBizType {
|
|||||||
KVAS,
|
KVAS,
|
||||||
KQZONE,
|
KQZONE,
|
||||||
KOTHER
|
KOTHER
|
||||||
}export enum BuddyListReqType {
|
}
|
||||||
KNOMAL,
|
|
||||||
KLETTER
|
|
||||||
}
|
|
||||||
|
|
@@ -145,8 +145,10 @@ export class NapCatCore {
|
|||||||
if (Info.status == 20) {
|
if (Info.status == 20) {
|
||||||
this.selfInfo.online = false;
|
this.selfInfo.online = false;
|
||||||
this.context.logger.log("账号状态变更为离线");
|
this.context.logger.log("账号状态变更为离线");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.selfInfo.online = true;
|
||||||
}
|
}
|
||||||
this.selfInfo.online = true;
|
|
||||||
};
|
};
|
||||||
this.context.session.getProfileService().addKernelProfileListener(
|
this.context.session.getProfileService().addKernelProfileListener(
|
||||||
proxiedListenerOf(profileListener, this.context.logger),
|
proxiedListenerOf(profileListener, this.context.logger),
|
||||||
|
@@ -14,7 +14,7 @@ export interface NodeIKernelBuddyService {
|
|||||||
}>
|
}>
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
getBuddyListFromCache(callFrom: string): Promise<Array<
|
getBuddyListFromCache(reqType: BuddyListReqType): Promise<Array<
|
||||||
{
|
{
|
||||||
categoryId: number,//9999应该跳过 那是兜底数据吧
|
categoryId: number,//9999应该跳过 那是兜底数据吧
|
||||||
categorySortId: number,//排序方式
|
categorySortId: number,//排序方式
|
||||||
@@ -23,7 +23,7 @@ export interface NodeIKernelBuddyService {
|
|||||||
onlineCount: number,//在线数目
|
onlineCount: number,//在线数目
|
||||||
buddyUids: Array<string>//Uids
|
buddyUids: Array<string>//Uids
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
|
addKernelBuddyListener(listener: NodeIKernelBuddyListener): number;
|
||||||
|
|
||||||
getAllBuddyCount(): number;
|
getAllBuddyCount(): number;
|
||||||
|
@@ -59,6 +59,7 @@ export interface QuickLoginResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeIKernelLoginService {
|
export interface NodeIKernelLoginService {
|
||||||
|
connect(): boolean;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||||
new(): NodeIKernelLoginService;
|
new(): NodeIKernelLoginService;
|
||||||
|
|
||||||
|
@@ -3,6 +3,11 @@ import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType
|
|||||||
import { GeneralCallResult } from '@/core/services/common';
|
import { GeneralCallResult } from '@/core/services/common';
|
||||||
|
|
||||||
export interface NodeIKernelProfileService {
|
export interface NodeIKernelProfileService {
|
||||||
|
getOtherFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||||
|
|
||||||
|
getVasInfo(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||||
|
|
||||||
|
getRelationFlag(callfrom: string, uids: string[]): Promise<Map<string, any>>;
|
||||||
|
|
||||||
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;
|
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;
|
||||||
|
|
||||||
|
@@ -43,12 +43,12 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
|||||||
const fileName = mixElementInner.fileName ?? '';
|
const fileName = mixElementInner.fileName ?? '';
|
||||||
let url = '';
|
let url = '';
|
||||||
if (mixElement?.picElement && rawMessage) {
|
if (mixElement?.picElement && rawMessage) {
|
||||||
let tempData =
|
const tempData =
|
||||||
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement) as OB11MessageImage | undefined;
|
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement) as OB11MessageImage | undefined;
|
||||||
url = tempData?.data.url ?? '';
|
url = tempData?.data.url ?? '';
|
||||||
}
|
}
|
||||||
if (mixElement?.videoElement && rawMessage) {
|
if (mixElement?.videoElement && rawMessage) {
|
||||||
let tempData =
|
const tempData =
|
||||||
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement) as OB11MessageVideo | undefined;
|
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement) as OB11MessageVideo | undefined;
|
||||||
url = tempData?.data.url ?? '';
|
url = tempData?.data.url ?? '';
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
|
|||||||
...extendData.detail.commonExt,
|
...extendData.detail.commonExt,
|
||||||
...extendData.detail.simpleInfo.baseInfo,
|
...extendData.detail.simpleInfo.baseInfo,
|
||||||
...extendData.detail.simpleInfo.relationFlags,
|
...extendData.detail.simpleInfo.relationFlags,
|
||||||
|
...extendData.detail.simpleInfo.status,
|
||||||
user_id: parseInt(extendData.detail.uin) || 0,
|
user_id: parseInt(extendData.detail.uin) || 0,
|
||||||
nickname: extendData.detail.simpleInfo.coreInfo.nick,
|
nickname: extendData.detail.simpleInfo.coreInfo.nick,
|
||||||
sex: OB11UserSex.unknown,
|
sex: OB11UserSex.unknown,
|
||||||
|
@@ -51,7 +51,7 @@ export class GetGroupEssence extends BaseAction<Payload, any> {
|
|||||||
operator_nick: msg.add_digest_nick,
|
operator_nick: msg.add_digest_nick,
|
||||||
message_id: message_id,
|
message_id: message_id,
|
||||||
operator_time: msg.add_digest_time,
|
operator_time: msg.add_digest_time,
|
||||||
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage, 'array'))?.message
|
content: (await this.obContext.apis.MsgApi.parseMessage(rawMessage))?.message
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const msgTempData = JSON.stringify({
|
const msgTempData = JSON.stringify({
|
||||||
|
@@ -83,6 +83,7 @@ import { DeleteGroupFileFolder } from '@/onebot/action/go-cqhttp/DeleteGroupFile
|
|||||||
import { GetGroupFileSystemInfo } from '@/onebot/action/go-cqhttp/GetGroupFileSystemInfo';
|
import { GetGroupFileSystemInfo } from '@/onebot/action/go-cqhttp/GetGroupFileSystemInfo';
|
||||||
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
|
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
|
||||||
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
|
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
|
||||||
|
import { GetGroupSystemMsg } from './system/GetSystemMsg';
|
||||||
|
|
||||||
export type ActionMap = Map<string, BaseAction<any, any>>;
|
export type ActionMap = Map<string, BaseAction<any, any>>;
|
||||||
|
|
||||||
@@ -176,6 +177,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
|||||||
new DeleteGroupFileFolder(obContext, core),
|
new DeleteGroupFileFolder(obContext, core),
|
||||||
new GetGroupFileSystemInfo(obContext, core),
|
new GetGroupFileSystemInfo(obContext, core),
|
||||||
new GetGroupFilesByFolder(obContext, core),
|
new GetGroupFilesByFolder(obContext, core),
|
||||||
|
new GetGroupSystemMsg(obContext, core),
|
||||||
];
|
];
|
||||||
const actionMap = new Map();
|
const actionMap = new Map();
|
||||||
for (const action of actionHandlers) {
|
for (const action of actionHandlers) {
|
||||||
|
@@ -35,7 +35,7 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
|
|||||||
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(
|
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(
|
||||||
peer,
|
peer,
|
||||||
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
|
[msgIdWithPeer?.MsgId || payload.message_id.toString()]);
|
||||||
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0], 'array');
|
const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0]);
|
||||||
if (!retMsg) throw Error('消息为空');
|
if (!retMsg) throw Error('消息为空');
|
||||||
try {
|
try {
|
||||||
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!;
|
retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!;
|
||||||
|
50
src/onebot/action/system/GetSystemMsg.ts
Normal file
50
src/onebot/action/system/GetSystemMsg.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { GroupNotifyMsgStatus } from '@/core';
|
||||||
|
import BaseAction from '../BaseAction';
|
||||||
|
import { ActionName } from '../types';
|
||||||
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
const SchemaData = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
group_id: { type: ['number', 'string'] },
|
||||||
|
},
|
||||||
|
} as const satisfies JSONSchema;
|
||||||
|
|
||||||
|
type Payload = FromSchema<typeof SchemaData>;
|
||||||
|
|
||||||
|
export class GetGroupSystemMsg extends BaseAction<void, any> {
|
||||||
|
actionName = ActionName.GetGroupSystemMsg;
|
||||||
|
|
||||||
|
async _handle(payload: void) {
|
||||||
|
const NTQQUserApi = this.core.apis.UserApi;
|
||||||
|
const NTQQGroupApi = this.core.apis.GroupApi;
|
||||||
|
// 默认10条 该api未完整实现 包括响应数据规范化 类型规范化
|
||||||
|
const SingleScreenNotifies = await NTQQGroupApi.getSingleScreenNotifies(false,10);
|
||||||
|
const retData: any = { InvitedRequest: [], join_requests: [] };
|
||||||
|
for (const SSNotify of SingleScreenNotifies) {
|
||||||
|
if (SSNotify.type == 1) {
|
||||||
|
retData.InvitedRequest.push({
|
||||||
|
request_id: SSNotify.seq,
|
||||||
|
invitor_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||||
|
invitor_nick: SSNotify.user1?.nickName,
|
||||||
|
group_id: SSNotify.group?.groupCode,
|
||||||
|
group_name: SSNotify.group?.groupName,
|
||||||
|
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
|
||||||
|
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
|
||||||
|
});
|
||||||
|
} else if (SSNotify.type == 7) {
|
||||||
|
retData.join_requests.push({
|
||||||
|
request_id: SSNotify.seq,
|
||||||
|
requester_uin: await NTQQUserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||||
|
requester_nick: SSNotify.user1?.nickName,
|
||||||
|
group_id: SSNotify.group?.groupCode,
|
||||||
|
group_name: SSNotify.group?.groupName,
|
||||||
|
checked: SSNotify.status === GroupNotifyMsgStatus.KUNHANDLE ? false : true,
|
||||||
|
actor: await NTQQUserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retData;
|
||||||
|
}
|
||||||
|
}
|
@@ -116,5 +116,6 @@ export enum ActionName {
|
|||||||
SetInputStatus = 'set_input_status',
|
SetInputStatus = 'set_input_status',
|
||||||
GetCSRF = 'get_csrf_token',
|
GetCSRF = 'get_csrf_token',
|
||||||
DelGroupNotice = '_del_group_notice',
|
DelGroupNotice = '_del_group_notice',
|
||||||
GetGroupInfoEx = "get_group_info_ex"
|
GetGroupInfoEx = "get_group_info_ex",
|
||||||
|
GetGroupSystemMsg = 'get_group_system_msg',
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ export default class GetRecentContact extends BaseAction<Payload, any> {
|
|||||||
const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
|
const FastMsg = await this.core.apis.MsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
|
||||||
if (FastMsg.msgList.length > 0) {
|
if (FastMsg.msgList.length > 0) {
|
||||||
//扩展ret.info.changedList
|
//扩展ret.info.changedList
|
||||||
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0], 'array');
|
const lastestMsg = await this.obContext.apis.MsgApi.parseMessage(FastMsg.msgList[0]);
|
||||||
return {
|
return {
|
||||||
lastestMsg: lastestMsg,
|
lastestMsg: lastestMsg,
|
||||||
peerUin: t.peerUin,
|
peerUin: t.peerUin,
|
||||||
|
@@ -19,6 +19,7 @@ import { OB11GroupPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
|
|||||||
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
|
import { OB11GroupEssenceEvent } from '@/onebot/event/notice/OB11GroupEssenceEvent';
|
||||||
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
|
import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
|
||||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
|
|
||||||
export class OneBotGroupApi {
|
export class OneBotGroupApi {
|
||||||
obContext: NapCatOneBot11Adapter;
|
obContext: NapCatOneBot11Adapter;
|
||||||
@@ -77,7 +78,8 @@ export class OneBotGroupApi {
|
|||||||
id: FileNapCatOneBotUUID.encode({
|
id: FileNapCatOneBotUUID.encode({
|
||||||
chatType: ChatType.KCHATTYPEGROUP,
|
chatType: ChatType.KCHATTYPEGROUP,
|
||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
}, msg.msgId, element.elementId),
|
}, msg.msgId, element.elementId, "." + element.fileElement.fileName),
|
||||||
|
url: pathToFileURL(element.fileElement.filePath).href,
|
||||||
name: element.fileElement.fileName,
|
name: element.fileElement.fileName,
|
||||||
size: parseInt(element.fileElement.fileSize),
|
size: parseInt(element.fileElement.fileSize),
|
||||||
busid: element.fileElement.fileBizId || 0,
|
busid: element.fileElement.fileBizId || 0,
|
||||||
@@ -138,15 +140,25 @@ export class OneBotGroupApi {
|
|||||||
}
|
}
|
||||||
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
|
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
|
||||||
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
|
||||||
const memberUin = json.items[1].param[0];
|
const type = json.items[json.items.length - 1]?.txt;
|
||||||
const title = json.items[3].txt;
|
switch (type) {
|
||||||
logger.logDebug('收到群成员新头衔消息', json);
|
case "头衔": {
|
||||||
return new OB11GroupTitleEvent(
|
const memberUin = json.items[1].param[0];
|
||||||
this.core,
|
const title = json.items[3].txt;
|
||||||
parseInt(msg.peerUid),
|
logger.logDebug('收到群成员新头衔消息', json);
|
||||||
parseInt(memberUin),
|
return new OB11GroupTitleEvent(
|
||||||
title,
|
this.core,
|
||||||
);
|
parseInt(msg.peerUid),
|
||||||
|
parseInt(memberUin),
|
||||||
|
title,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
case "移出":
|
||||||
|
logger.logDebug('收到机器人被踢消息', json);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
logger.logWarn('收到未知的灰条消息', json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
import {
|
import {
|
||||||
AtType,
|
AtType,
|
||||||
ChatType,
|
ChatType,
|
||||||
@@ -106,7 +107,7 @@ export class OneBotMsgApi {
|
|||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId);
|
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName);
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.image,
|
type: OB11MessageDataType.image,
|
||||||
data: {
|
data: {
|
||||||
@@ -114,6 +115,7 @@ export class OneBotMsgApi {
|
|||||||
sub_type: element.picSubType,
|
sub_type: element.picSubType,
|
||||||
file_id: encodedFileId,
|
file_id: encodedFileId,
|
||||||
url: await this.core.apis.FileApi.getImageUrl(element),
|
url: await this.core.apis.FileApi.getImageUrl(element),
|
||||||
|
path: element.filePath,
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName
|
file_unique: element.fileName
|
||||||
},
|
},
|
||||||
@@ -135,8 +137,8 @@ export class OneBotMsgApi {
|
|||||||
data: {
|
data: {
|
||||||
file: element.fileName,
|
file: element.fileName,
|
||||||
path: element.filePath,
|
path: element.filePath,
|
||||||
url: element.filePath,
|
url: pathToFileURL(element.filePath).href,
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName),
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName,
|
file_unique: element.fileName,
|
||||||
},
|
},
|
||||||
@@ -175,13 +177,16 @@ export class OneBotMsgApi {
|
|||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
|
const { emojiId } = _;
|
||||||
|
const dir = emojiId.substring(0, 2);
|
||||||
|
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.image,
|
type: OB11MessageDataType.image,
|
||||||
data: {
|
data: {
|
||||||
file: 'marketface',
|
file: 'marketface',
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + _.key + ".jpg"),
|
||||||
path: elementWrapper.elementId,
|
path: elementWrapper.elementId,
|
||||||
url: elementWrapper.elementId,
|
url: url,
|
||||||
file_unique: _.key
|
file_unique: _.key
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -209,7 +214,7 @@ export class OneBotMsgApi {
|
|||||||
if (records.peerUin === '284840486') {
|
if (records.peerUin === '284840486') {
|
||||||
return createReplyData(records.msgId);
|
return createReplyData(records.msgId);
|
||||||
}
|
}
|
||||||
let replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
|
const replyMsg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, element.replyMsgTime, [element.senderUidStr]))
|
||||||
.msgList.find(msg => msg.msgRandom === records.msgRandom);
|
.msgList.find(msg => msg.msgRandom === records.msgRandom);
|
||||||
|
|
||||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||||
@@ -257,14 +262,14 @@ export class OneBotMsgApi {
|
|||||||
if (!videoDownUrl) {
|
if (!videoDownUrl) {
|
||||||
videoDownUrl = element.filePath;
|
videoDownUrl = element.filePath;
|
||||||
}
|
}
|
||||||
|
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName);
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.video,
|
type: OB11MessageDataType.video,
|
||||||
data: {
|
data: {
|
||||||
file: element.fileName,
|
file: fileCode,
|
||||||
path: videoDownUrl,
|
path: videoDownUrl,
|
||||||
url: videoDownUrl,
|
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
file_id: fileCode,
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
file_unique: element.fileName,
|
file_unique: element.fileName,
|
||||||
},
|
},
|
||||||
@@ -277,13 +282,16 @@ export class OneBotMsgApi {
|
|||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
|
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + element.fileName);
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.voice,
|
type: OB11MessageDataType.voice,
|
||||||
data: {
|
data: {
|
||||||
file: element.fileName,
|
file: fileCode,
|
||||||
path: element.filePath,
|
path: element.filePath,
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
url: pathToFileURL(element.filePath).href,
|
||||||
|
file_id: fileCode,
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
|
file_unique: element.fileName
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -790,20 +798,18 @@ export class OneBotMsgApi {
|
|||||||
{ data: inputdata }: OB11MessageFileBase,
|
{ data: inputdata }: OB11MessageFileBase,
|
||||||
{ deleteAfterSentFiles }: MessageContext,
|
{ deleteAfterSentFiles }: MessageContext,
|
||||||
) {
|
) {
|
||||||
const isBlankUrl = !inputdata.url || inputdata.url === '';
|
const realUri = inputdata.url || inputdata.file || inputdata.path || '';
|
||||||
const isBlankFile = !inputdata.file || inputdata.file === '';
|
if (realUri.length === 0) {
|
||||||
if (isBlankUrl && isBlankFile) {
|
|
||||||
this.core.context.logger.logError('文件消息缺少参数', inputdata);
|
this.core.context.logger.logError('文件消息缺少参数', inputdata);
|
||||||
throw Error('文件消息缺少参数');
|
throw Error('文件消息缺少参数');
|
||||||
}
|
}
|
||||||
const fileOrUrl = (isBlankUrl ? inputdata.file : inputdata.url) ?? '';
|
|
||||||
const {
|
const {
|
||||||
path,
|
path,
|
||||||
isLocal,
|
isLocal,
|
||||||
fileName,
|
fileName,
|
||||||
errMsg,
|
errMsg,
|
||||||
success,
|
success,
|
||||||
} = (await uri2local(this.core.NapCatTempPath, fileOrUrl));
|
} = (await uri2local(this.core.NapCatTempPath, realUri));
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
this.core.context.logger.logError('文件下载失败', errMsg);
|
this.core.context.logger.logError('文件下载失败', errMsg);
|
||||||
|
@@ -109,7 +109,7 @@ export class OB11Entities {
|
|||||||
static file(peerId: string, file: Exclude<GroupFileInfoUpdateParamType['item'][0]['fileInfo'], undefined>): OB11GroupFile {
|
static file(peerId: string, file: Exclude<GroupFileInfoUpdateParamType['item'][0]['fileInfo'], undefined>): OB11GroupFile {
|
||||||
return {
|
return {
|
||||||
group_id: parseInt(peerId),
|
group_id: parseInt(peerId),
|
||||||
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId),
|
file_id: FileNapCatOneBotUUID.encodeModelId({ chatType: 2, peerUid: peerId }, file.fileModelId, file.fileId, file.fileName),
|
||||||
file_name: file.fileName,
|
file_name: file.fileName,
|
||||||
busid: file.busId,
|
busid: file.busId,
|
||||||
size: parseInt(file.fileSize),
|
size: parseInt(file.fileSize),
|
||||||
|
@@ -6,6 +6,7 @@ export interface GroupUploadFile {
|
|||||||
name: string,
|
name: string,
|
||||||
size: number,
|
size: number,
|
||||||
busid: number,
|
busid: number,
|
||||||
|
url:string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OB11GroupUploadNoticeEvent extends OB11GroupNoticeEvent {
|
export class OB11GroupUploadNoticeEvent extends OB11GroupNoticeEvent {
|
||||||
|
@@ -21,6 +21,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
|||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
logger: LogWrapper;
|
logger: LogWrapper;
|
||||||
private heartbeatIntervalId: NodeJS.Timeout | null = null;
|
private heartbeatIntervalId: NodeJS.Timeout | null = null;
|
||||||
|
wsClientWithEvent: WebSocket[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ip: string,
|
ip: string,
|
||||||
@@ -46,7 +47,12 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
|||||||
}
|
}
|
||||||
//鉴权
|
//鉴权
|
||||||
this.authorize(token, wsClient, wsReq);
|
this.authorize(token, wsClient, wsReq);
|
||||||
this.connectEvent(core, wsClient);
|
let paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url;
|
||||||
|
const isEventConnect = paramUrl === '/event' || paramUrl === '' || paramUrl === '/';
|
||||||
|
if (isEventConnect) {
|
||||||
|
this.connectEvent(core, wsClient);
|
||||||
|
}
|
||||||
|
|
||||||
wsClient.on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Client Error:', err.message));
|
wsClient.on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Client Error:', err.message));
|
||||||
wsClient.on('message', (message) => {
|
wsClient.on('message', (message) => {
|
||||||
this.handleMessage(wsClient, message).then().catch(this.logger.logError);
|
this.handleMessage(wsClient, message).then().catch(this.logger.logError);
|
||||||
@@ -59,13 +65,21 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
|||||||
});
|
});
|
||||||
wsClient.once('close', () => {
|
wsClient.once('close', () => {
|
||||||
this.wsClientsMutex.runExclusive(async () => {
|
this.wsClientsMutex.runExclusive(async () => {
|
||||||
const index = this.wsClients.indexOf(wsClient);
|
const NormolIndex = this.wsClients.indexOf(wsClient);
|
||||||
if (index !== -1) {
|
if (NormolIndex !== -1) {
|
||||||
this.wsClients.splice(index, 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 () => {
|
await this.wsClientsMutex.runExclusive(async () => {
|
||||||
|
if(isEventConnect){
|
||||||
|
this.wsClientWithEvent.push(wsClient);
|
||||||
|
}
|
||||||
this.wsClients.push(wsClient);
|
this.wsClients.push(wsClient);
|
||||||
});
|
});
|
||||||
}).on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Server Error:', err.message));
|
}).on('error', (err) => this.logger.log('[OneBot] [WebSocket Server] Server Error:', err.message));
|
||||||
@@ -81,7 +95,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
|||||||
|
|
||||||
onEvent<T extends OB11EmitEventContent>(event: T) {
|
onEvent<T extends OB11EmitEventContent>(event: T) {
|
||||||
this.wsClientsMutex.runExclusive(async () => {
|
this.wsClientsMutex.runExclusive(async () => {
|
||||||
this.wsClients.forEach((wsClient) => {
|
this.wsClientWithEvent.forEach((wsClient) => {
|
||||||
wsClient.send(JSON.stringify(event));
|
wsClient.send(JSON.stringify(event));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -84,6 +84,8 @@ export interface OB11MessageText {
|
|||||||
|
|
||||||
export interface OB11MessageFileBase {
|
export interface OB11MessageFileBase {
|
||||||
data: {
|
data: {
|
||||||
|
file_unique?:string,
|
||||||
|
path?: string;
|
||||||
thumb?: string;
|
thumb?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
file: string,
|
file: string,
|
||||||
|
@@ -150,7 +150,12 @@ export async function NCoreInitShell() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
|
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger) as any);
|
||||||
|
const isConnect = loginService.connect();
|
||||||
|
if (!isConnect) {
|
||||||
|
logger.logError('核心登录服务连接失败!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.log('核心登录服务连接成功!');
|
||||||
// 实现WebUi快速登录
|
// 实现WebUi快速登录
|
||||||
loginService.getLoginList().then((res) => {
|
loginService.getLoginList().then((res) => {
|
||||||
// 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList
|
// 遍历 res.LocalLoginInfoList[x].isQuickLogin是否可以 res.LocalLoginInfoList[x].uin 转为string 加入string[] 最后遍历完成调用WebUiDataRuntime.setQQQuickLoginList
|
||||||
@@ -199,7 +204,7 @@ export async function NCoreInitShell() {
|
|||||||
logger.log(`可用于快速登录的 QQ:\n${historyLoginList
|
logger.log(`可用于快速登录的 QQ:\n${historyLoginList
|
||||||
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
|
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
}`);
|
}`);
|
||||||
}
|
}
|
||||||
loginService.getQRCodePicture();
|
loginService.getQRCodePicture();
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
undefined,
|
undefined,
|
||||||
SettingButton('V2.3.7', 'napcat-update-button', 'secondary'),
|
SettingButton('V2.4.4', 'napcat-update-button', 'secondary'),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
void 0,
|
void 0,
|
||||||
SettingButton("V2.3.7", "napcat-update-button", "secondary")
|
SettingButton("V2.4.4", "napcat-update-button", "secondary")
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
Reference in New Issue
Block a user