Compare commits

...

17 Commits

Author SHA1 Message Date
手瓜一十雪
40b2f6bfd6 release: 2.4.7 2024-09-12 18:31:11 +08:00
手瓜一十雪
911e4921e2 fix: 删除旧文件 2024-09-12 18:15:38 +08:00
手瓜一十雪
1db9bb419d fix: 一处异常字段 2024-09-12 10:55:18 +08:00
手瓜一十雪
c6241a94e3 style: lint 2024-09-12 09:28:41 +08:00
手瓜一十雪
1cbf75ca36 style: lint 2024-09-12 09:28:26 +08:00
手瓜一十雪
8f85c897c8 refactor: SysMsg 2024-09-12 09:20:10 +08:00
手瓜一十雪
29c31b7aba release: 2.4.6 2024-09-12 09:01:13 +08:00
手瓜一十雪
402919d6f2 feat: qucikLogin 2024-09-12 09:00:53 +08:00
手瓜一十雪
82608dd5ff fix: build 2024-09-12 00:17:35 +08:00
手瓜一十雪
f312368df2 build: fix2 2024-09-11 23:40:05 +08:00
手瓜一十雪
374fc64427 feat: delFile 2024-09-11 23:29:26 +08:00
手瓜一十雪
95bd74bb0d BUILD: TEST 2024-09-11 23:18:38 +08:00
手瓜一十雪
a9f5069649 Revert "build: debug"
This reverts commit 957f7ffd8d.
2024-09-11 22:41:59 +08:00
手瓜一十雪
957f7ffd8d build: debug 2024-09-11 22:28:28 +08:00
手瓜一十雪
336dd3ce10 chore: 扩展 2024-09-11 22:13:45 +08:00
手瓜一十雪
47a7295477 fix: typo
copilot
2024-09-11 20:24:50 +08:00
手瓜一十雪
341a0e1c2a chore: code 2024-09-11 20:10:52 +08:00
29 changed files with 199 additions and 168 deletions

View File

@@ -5,7 +5,7 @@ if %errorLevel% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
exit
)
@@ -35,6 +35,6 @@ if not exist "%QQpath%" (
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456

View File

@@ -5,7 +5,7 @@ if %errorLevel% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\" %1' -Verb runAs"
exit
)
@@ -36,6 +36,4 @@ if not exist "%QQpath%" (
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %1

View File

@@ -0,0 +1,4 @@
@echo off
REM ./launcher.bat 123456
REM ./launcher-win10.bat 123456
REM 带有REM的为注释 删掉你需要的系统的那行REM这三个单词 修改QQ本脚本启动即可

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "2.4.5",
"version": "2.4.7",
"icon": "./logo.png",
"authors": [
{

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.4.5",
"version": "2.4.7",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",

View File

@@ -1,4 +1,3 @@
import fs from 'fs';
import fsPromise from 'fs/promises';
import path from 'node:path';
import { randomUUID } from 'crypto';
@@ -12,7 +11,7 @@ const FFMPEG_PATH = process.env.FFMPEG_PATH || 'ffmpeg';
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
const duration = Math.max(1, Math.floor(pttFileInfo.size / 1024 / 3)); // 3kb/s
logger.log('通过文件大小估算语音的时长:', duration);
return duration;
}
@@ -20,7 +19,7 @@ async function guessDuration(pttPath: string, logger: LogWrapper) {
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 => {
cp.on('error', (err: Error) => {
logger.log('FFmpeg处理转换出错: ', err.message);
reject(err);
});

View File

@@ -160,8 +160,7 @@ type Uri2LocalRes = {
errMsg: string,
fileName: string,
ext: string,
path: string,
isLocal: boolean
path: string
}
export async function checkFileV2(filePath: string) {
@@ -194,7 +193,6 @@ export async function checkUriType(Uri: string) {
return undefined;
}, Uri);
if (LocalFileRet) return LocalFileRet;
const OtherFileRet = await solveProblem((uri: string) => {
//再判断是否是Http
if (uri.startsWith('http://') || uri.startsWith('https://')) {
@@ -206,13 +204,13 @@ export async function checkUriType(Uri: string) {
}
if (uri.startsWith('file://')) {
let filePath: string;
// await fs.copyFile(url.pathname, filePath);
const pathname = decodeURIComponent(new URL(uri).pathname);
if (process.platform === 'win32') {
filePath = pathname.slice(1);
} else {
filePath = pathname;
}
return { Uri: filePath, Type: FileUriType.Local };
}
if (uri.startsWith('data:')) {
@@ -228,35 +226,42 @@ export async function checkUriType(Uri: string) {
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
//解析失败
const tempName = randomUUID();
if (!filename) filename = randomUUID();
//解析Http和Https协议
if (UriType == FileUriType.Unknown) {
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
}
//解析File协议和本地文件
if (UriType == FileUriType.Local) {
const fileExt = path.extname(HandledUri);
let filename = path.basename(HandledUri, fileExt);
filename += fileExt;
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: HandledUri, isLocal: true };
//复制文件到临时文件并保持后缀
const filenameTemp = tempName + fileExt;
const filePath = path.join(dir, filenameTemp);
fs.copyFileSync(HandledUri, filePath);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
//接下来都要有文件名
if (!filename) filename = randomUUID();
//解析Http和Https协议
if (UriType == FileUriType.Remote) {
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
if (pathInfo.name) {
filename = pathInfo.name;
const pathlen = 200 - dir.length - pathInfo.name.length;
filename = pathlen > 0 ? pathInfo.name.substring(0, pathlen) : pathInfo.name.substring(pathInfo.name.length, pathInfo.name.length - 10);//过长截断
if (pathInfo.ext) {
filename += pathInfo.ext;
}
}
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
const fileExt = path.extname(HandledUri);
const filePath = path.join(dir, filename);
const fileExt = path.extname(HandledUri).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
const filePath = path.join(dir, tempName + fileExt);
const buffer = await httpDownload(HandledUri);
fs.writeFileSync(filePath, buffer);
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: false };
//没有文件就创建
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
//解析Base64
if (UriType == FileUriType.Base64) {
@@ -271,7 +276,7 @@ export async function uri2local(dir: string, uri: string, filename: string | und
fileExt = ext;
filename = filename + '.' + ext;
}
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath, isLocal: false };
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
}
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '', isLocal: false };
return { success: false, errMsg: '未知文件类型', fileName: '', ext: '', path: '' };
}

View File

@@ -1 +1 @@
export const napCatVersion = '2.4.5';
export const napCatVersion = '2.4.7';

View File

@@ -24,6 +24,7 @@ import pathLib from 'node:path';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
import ffmpeg from 'fluent-ffmpeg';
import { encodeSilk } from '@/common/audio';
import { MessageContext } from '@/onebot/api';
export class NTQQFileApi {
context: InstanceContext;
@@ -33,7 +34,7 @@ export class NTQQFileApi {
constructor(context: InstanceContext, core: NapCatCore) {
this.context = context;
this.core = core;
this.rkeyManager = new RkeyManager('https://llob.linyuchen.net/rkey', this.context.logger);
this.rkeyManager = new RkeyManager(['https://llob.linyuchen.net/rkey', 'http://napcat-sign.wumiao.wang:2082/rkey'], this.context.logger);
}
async copyFile(filePath: string, destPath: string) {
@@ -71,7 +72,7 @@ export class NTQQFileApi {
file_uuid: '',
});
await this.copyFile(filePath, mediaPath!);
await this.copyFile(filePath, mediaPath);
const fileSize = await this.getFileSize(filePath);
return {
md5: fileMd5,
@@ -82,7 +83,7 @@ export class NTQQFileApi {
};
}
async createValidSendFileElement(filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
async createValidSendFileElement(context: MessageContext, filePath: string, fileName: string = '', folderId: string = '',): Promise<SendFileElement> {
const {
fileName: _fileName,
path,
@@ -91,6 +92,7 @@ export class NTQQFileApi {
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.FILE,
elementId: '',
@@ -103,12 +105,13 @@ export class NTQQFileApi {
};
}
async createValidSendPicElement(picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
async createValidSendPicElement(context: MessageContext, picPath: string, summary: string = '', subType: 0 | 1 = 0,): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.PIC,
elementId: '',
@@ -130,26 +133,32 @@ export class NTQQFileApi {
};
}
async createValidSendVideoElement(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
async createValidSendVideoElement(context: MessageContext, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
const logger = this.core.context.logger;
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
thumb = pathLib.dirname(thumb);
let videoInfo = {
width: 1920, height: 1080,
time: 15,
format: 'mp4',
size: fileSize,
size: 0,
filePath,
};
try {
videoInfo = await getVideoInfo(path, logger);
videoInfo = await getVideoInfo(filePath, logger);
} catch (e) {
logger.logError('获取视频信息失败,将使用默认值', e);
}
const newFilePath = filePath + '.mp4';
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);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
videoInfo.size = fileSize;
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
thumb = pathLib.dirname(thumb);
const thumbPath = new Map();
const _thumbPath = await new Promise<string | undefined>((resolve, reject) => {
const thumbFileName = `${md5}_0.png`;
@@ -179,6 +188,7 @@ export class NTQQFileApi {
const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0;
thumbPath.set(0, _thumbPath);
const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : '';
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.VIDEO,
elementId: '',

View File

@@ -32,10 +32,6 @@ export class NTQQGroupApi {
for (const group of this.groups) {
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}个群组缓存完成`);
}

View File

@@ -1,4 +1,4 @@
import { GroupMemberRole, Peer } from '@/core';
import { GroupMemberRole } from '@/core';
export interface Peer {
chatType: ChatType;

View File

@@ -8,7 +8,7 @@ interface ServerRkeyData {
}
export class RkeyManager {
serverUrl: string = '';
serverUrl: string[] = [];
logger: LogWrapper;
private rkeyData: ServerRkeyData = {
group_rkey: '',
@@ -16,7 +16,7 @@ export class RkeyManager {
expired_time: 0,
};
constructor(serverUrl: string, logger: LogWrapper) {
constructor(serverUrl: string[], logger: LogWrapper) {
this.logger = logger;
this.serverUrl = serverUrl;
}
@@ -40,6 +40,13 @@ export class RkeyManager {
async refreshRkey(): Promise<any> {
//刷新rkey
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(this.serverUrl, 'GET');
for (const url of this.serverUrl) {
try {
this.rkeyData = await RequestUtil.HttpGetJson<ServerRkeyData>(url, 'GET');
} catch (e) {
this.logger.logError(`[Rkey] Get Rkey ${url} Error `, e);
}
}
}
}

View File

@@ -145,7 +145,6 @@ export class NapCatCore {
if (Info.status == 20) {
this.selfInfo.online = false;
this.context.logger.log("账号状态变更为离线");
return;
} else {
this.selfInfo.online = true;
}

View File

@@ -1,7 +1,7 @@
import * as pb from 'protobufjs';
// Proto: from src/core/proto/ProfileLike.proto
// Auther: Mlikiowa
// Author: Mlikiowa
export interface LikeDetailType {
txt: string;
@@ -80,4 +80,4 @@ export const likeMsg = new pb.Type("likeMsg")
export const profileLikeTip = new pb.Type("profileLikeTip")
.add(likeMsg)
.add(new pb.Field("msg", 14, "likeMsg"));
.add(new pb.Field("msg", 14, "likeMsg"));

View File

@@ -19,26 +19,21 @@ export class OCRImage extends BaseAction<Payload, any> {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const { path, isLocal, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
if (!success) {
throw `OCR ${payload.image}失败,image字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.SystemApi.ocrImage(path);
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
if (!ret) {
throw `OCR ${payload.file}失败`;
}
return ret.result;
}
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
throw `OCR ${payload.file}失败,文件可能不存在`;
}
}

View File

@@ -24,17 +24,16 @@ export default class SetAvatar extends BaseAction<Payload, null> {
}
async _handle(payload: Payload): Promise<null> {
const { path, isLocal, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.UserApi.setQQAvatar(path);
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => {
});
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
}
@@ -45,10 +44,8 @@ export default class SetAvatar extends BaseAction<Payload, null> {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`;
}
} else {
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
}
return null;

View File

@@ -31,7 +31,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
//公告图逻辑
const {
path,
isLocal,
success,
} = (await uri2local(this.core.NapCatTempPath, payload.image));
if (!success) {
@@ -45,10 +44,10 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
if (ImageUploadResult.errCode != 0) {
throw `群公告${payload.image}设置失败,图片上传失败`;
}
if (!isLocal) {
unlink(path, () => {
});
}
unlink(path, () => {
});
UploadImage = ImageUploadResult.picInfo;
}
@@ -58,7 +57,6 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
const noticeShowEditCard = +(payload.is_show_edit_card ?? 0);
const noticeTipWindowType = +(payload.tip_window_type ?? 0);
const noticeConfirmRequired = +(payload.confirm_required ?? 1);
//const publishGroupBulletinResult = await this.core.apis.GroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
const publishGroupBulletinResult = await this.core.apis.WebApi.setGroupNotice(
payload.group_id.toString(),
payload.content,
@@ -72,7 +70,7 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
UploadImage?.height
);
if (!publishGroupBulletinResult || publishGroupBulletinResult.ec != 0) {
throw `设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`;
throw new Error(`设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`);
}
return null;
}

View File

@@ -25,17 +25,14 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
}
async _handle(payload: Payload): Promise<any> {
const { path, isLocal, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`;
}
if (path) {
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃需要提前判断
const ret = await this.core.apis.GroupApi.setGroupAvatar(payload.group_id.toString(), path) as any;
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => { });
if (!ret) {
throw `头像${payload.file}设置失败,api无返回`;
}
@@ -46,10 +43,7 @@ export default class SetGroupPortrait extends BaseAction<Payload, any> {
}
return ret;
} else {
if (!isLocal) {
fs.unlink(path, () => {
});
}
fs.unlink(path, () => {});
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`;
}
}

View File

@@ -1,9 +1,10 @@
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { ChatType } from '@/core/entities';
import { ChatType, Peer } from '@/core/entities';
import fs from 'fs';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageContext } from '@/onebot/api';
const SchemaData = {
type: 'object',
@@ -29,14 +30,19 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
file = `file://${file}`;
}
const downloadResult = await uri2local(this.core.NapCatTempPath, file);
const peer: Peer = {
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
};
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(downloadResult.path, payload.name, payload.folder_id);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId({
chatType: ChatType.KCHATTYPEGROUP,
peerUid: payload.group_id.toString(),
}, [sendFileEle], [], true);
const msgContext: MessageContext = {
peer: peer,
deleteAfterSentFiles: []
};
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder_id);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], [], true);
return null;
}
}

View File

@@ -4,6 +4,8 @@ import { ChatType, Peer, SendFileElement } from '@/core/entities';
import fs from 'fs';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageContext } from '@/onebot/api';
import { ContextMode, createContext } from '../msg/SendMsg';
const SchemaData = {
type: 'object',
@@ -42,7 +44,15 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
if (!downloadResult.success) {
throw new Error(downloadResult.errMsg);
}
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(downloadResult.path, payload.name);
const msgContext: MessageContext = {
peer: await createContext(this.core, {
user_id: payload.user_id.toString(),
group_id: undefined,
}, ContextMode.Private),
deleteAfterSentFiles: []
};
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name);
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], [], true);
return null;
}

View File

@@ -3,6 +3,7 @@ import {
OB11MessageDataType,
OB11MessageMixType,
OB11MessageNode,
OB11PostContext,
OB11PostSendMsg,
} from '@/onebot/types';
import { ActionName, BaseCheckResult } from '@/onebot/action/types';
@@ -30,7 +31,7 @@ export function normalize(message: OB11MessageMixType, autoEscape = false): OB11
) : Array.isArray(message) ? message : [message];
}
export async function createContext(core: NapCatCore, payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
export async function createContext(core: NapCatCore, payload: OB11PostContext, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.

View File

@@ -142,22 +142,22 @@ export class OneBotGroupApi {
//下面得改 上面也是错的grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE
const type = json.items[json.items.length - 1]?.txt;
switch (type) {
case "头衔": {
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
this.core,
parseInt(msg.peerUid),
parseInt(memberUin),
title,
);
};
case "移出":
logger.logDebug('收到机器人被踢消息', json);
return;
default:
logger.logWarn('收到未知的灰条消息', json);
case "头衔": {
const memberUin = json.items[1].param[0];
const title = json.items[3].txt;
logger.logDebug('收到群成员新头衔消息', json);
return new OB11GroupTitleEvent(
this.core,
parseInt(msg.peerUid),
parseInt(memberUin),
title,
);
}
case "移出":
logger.logDebug('收到机器人被踢消息', json);
return;
default:
logger.logWarn('收到未知的灰条消息', json);
}
}
}

View File

@@ -34,6 +34,7 @@ import { RequestUtil } from '@/common/request';
import fs from 'node:fs';
import fsPromise from 'node:fs/promises';
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
import { SysMessage, SysMessageType } from '@/core/proto/ProfileLike';
type RawToOb11Converters = {
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
@@ -185,7 +186,7 @@ export class OneBotMsgApi {
data: {
file: 'marketface',
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "." + _.key + ".jpg"),
path: elementWrapper.elementId,
path: url,
url: url,
file_unique: _.key
},
@@ -460,6 +461,7 @@ export class OneBotMsgApi {
// File service
[OB11MessageDataType.image]: async (sendMsg, context) => {
const sendPicElement = await this.core.apis.FileApi.createValidSendPicElement(
context,
(await this.handleOb11FileLikeMessage(sendMsg, context)).path,
sendMsg.data.summary,
sendMsg.data.sub_type,
@@ -470,7 +472,7 @@ export class OneBotMsgApi {
[OB11MessageDataType.file]: async (sendMsg, context) => {
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
return await this.core.apis.FileApi.createValidSendFileElement(path, fileName);
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName);
},
[OB11MessageDataType.video]: async (sendMsg, context) => {
@@ -481,9 +483,7 @@ export class OneBotMsgApi {
const uri2LocalRes = await uri2local(this.core.NapCatTempPath, thumb);
if (uri2LocalRes.success) thumb = uri2LocalRes.path;
}
const videoEle = await this.core.apis.FileApi.createValidSendVideoElement(path, fileName, thumb);
context.deleteAfterSentFiles.push(videoEle.videoElement.filePath);
const videoEle = await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb);
return videoEle;
},
@@ -805,7 +805,6 @@ export class OneBotMsgApi {
}
const {
path,
isLocal,
fileName,
errMsg,
success,
@@ -816,10 +815,42 @@ export class OneBotMsgApi {
throw Error('文件下载失败' + errMsg);
}
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path);
}
deleteAfterSentFiles.push(path);
return { path, fileName: inputdata.name ?? fileName };
}
async parseSysMessage(msg: number[]) {
const sysMsg = SysMessage.decode(Uint8Array.from(msg)) as unknown as SysMessageType;
if (sysMsg.msgSpec.length === 0) {
return;
}
const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
if (msgType === 528 && subType === 39 && subSubType === 39) {
if (!sysMsg.bodyWrapper) return;
const event = await this.obContext.apis.UserApi.parseLikeEvent(sysMsg.bodyWrapper.wrappedBody);
return event;
}
/*
if (msgType === 732 && subType === 16 && subSubType === 16) {
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
if (greyTip.subTypeId === 36) {
const emojiLikeToOthers = EmojiLikeToOthersWrapper1
.fromBinary(greyTip.rest)
.wrapper!
.body!;
if (emojiLikeToOthers.attributes?.operation !== 1) { // Un-like
return;
}
const eventOrEmpty = await this.apis.GroupApi.createGroupEmojiLikeEvent(
greyTip.groupCode.toString(),
await this.core.apis.UserApi.getUinByUidV2(emojiLikeToOthers.attributes!.senderUid),
emojiLikeToOthers.msgSpec!.msgSeq.toString(),
emojiLikeToOthers.attributes!.emojiId,
);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
eventOrEmpty && await this.networkManager.emitEvent(eventOrEmpty);
}
}
*/
}
}

View File

@@ -1,6 +1,8 @@
import { NapCatCore } from '@/core';
import { profileLikeTip, ProfileLikeTipType } from '@/core/proto/ProfileLike';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11ProfileLikeEvent } from '../event/notice/OB11ProfileLikeEvent';
export class OneBotUserApi {
obContext: NapCatOneBot11Adapter;
@@ -10,4 +12,20 @@ export class OneBotUserApi {
this.obContext = obContext;
this.core = core;
}
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
const likeTip = profileLikeTip.decode(Uint8Array.from(wrappedBody.slice(12))) as unknown as ProfileLikeTipType;
this.core.context.logger.logDebug("收到点赞通知消息");
const likeMsg = likeTip.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,
);
}
}

View File

@@ -44,9 +44,7 @@ import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecal
import { LRUCache } from '@/common/lru-cache';
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
import { OB11ProfileLikeEvent } from './event/notice/OB11ProfileLikeEvent';
import { profileLikeTip, ProfileLikeTipType, SysMessage, SysMessageMsgSpecType, SysMessageType } from '@/core/proto/ProfileLike';
import { Message } from 'protobufjs';
import { profileLikeTip, ProfileLikeTipType, SysMessage, SysMessageType } from '@/core/proto/ProfileLike';
//OneBot实现类
export class NapCatOneBot11Adapter {
readonly core: NapCatCore;
@@ -240,50 +238,10 @@ export class NapCatOneBot11Adapter {
private initMsgListener() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onRecvSysMsg = async (msg) => {
const sysMsg = SysMessage.decode(Uint8Array.from(msg)) as unknown as SysMessageType;
if (sysMsg.msgSpec.length === 0) {
return;
}
const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
if (msgType === 528 && subType === 39 && subSubType === 39) {
const likeTip = profileLikeTip.decode(Uint8Array.from(sysMsg.bodyWrapper.wrappedBody.slice(12))) as unknown as ProfileLikeTipType;
this.core.context.logger.logDebug("收到点赞通知消息");
const likeMsg = likeTip.msg;
if (!likeMsg) return;
const detail = likeMsg.detail;
if (!detail) return;
const times = detail.txt.match(/\d+/) ?? "0";
await this.networkManager.emitEvent(new OB11ProfileLikeEvent(
this.core,
Number(detail.uin),
detail.nickname,
parseInt(times[0], 10),
likeMsg.time,
)).catch(e => this.context.logger.logError('处理被点赞事件失败', e));
};
/*
if (msgType === 732 && subType === 16 && subSubType === 16) {
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
if (greyTip.subTypeId === 36) {
const emojiLikeToOthers = EmojiLikeToOthersWrapper1
.fromBinary(greyTip.rest)
.wrapper!
.body!;
if (emojiLikeToOthers.attributes?.operation !== 1) { // Un-like
return;
}
const eventOrEmpty = await this.apis.GroupApi.createGroupEmojiLikeEvent(
greyTip.groupCode.toString(),
await this.core.apis.UserApi.getUinByUidV2(emojiLikeToOthers.attributes!.senderUid),
emojiLikeToOthers.msgSpec!.msgSeq.toString(),
emojiLikeToOthers.attributes!.emojiId,
);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
eventOrEmpty && await this.networkManager.emitEvent(eventOrEmpty);
}
}
*/
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));
};
msgListener.onInputStatusPush = async data => {

View File

@@ -47,7 +47,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
}
//鉴权
this.authorize(token, wsClient, wsReq);
let paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url;
const 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);

View File

@@ -208,3 +208,8 @@ export interface OB11PostSendMsg {
messages?: OB11MessageMixType; // 兼容 go-cqhttp
auto_escape?: boolean | string
}
export interface OB11PostContext {
message_type?: 'private' | 'group'
user_id?: string,
group_id?: string,
}

View File

@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V2.4.5', 'napcat-update-button', 'secondary'),
SettingButton('V2.4.7', 'napcat-update-button', 'secondary'),
),
]),
SettingList([

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V2.4.5", "napcat-update-button", "secondary")
SettingButton("V2.4.7", "napcat-update-button", "secondary")
)
]),
SettingList([