mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Merge branch 'main' into pr/604
This commit is contained in:
@@ -36,6 +36,13 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||
## 回家旅途
|
||||
[QQ Group](https://qm.qq.com/q/I6LU87a0Yq)
|
||||
|
||||
## 性能设计/协议标准
|
||||
NapCat 已实现90%+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
|
||||
|
||||
由此设计带来一系列好处,在开发中,获取群员列表通常小于50Ms,单条文本消息发送在320Ms以内,在1k+的群聊流畅运行,同时带来一些副作用,上报数据中大量使用Magic生成字段,消息Id无法持久,无法上报撤回消息原始内容。
|
||||
|
||||
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
|
||||
|
||||
## 感谢他们
|
||||
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||
|
||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.2.9",
|
||||
"version": "4.2.24",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.2.9",
|
||||
"version": "4.2.24",
|
||||
"scripts": {
|
||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import crypto, { randomUUID } from 'crypto';
|
||||
import util from 'util';
|
||||
import path from 'node:path';
|
||||
import * as fileType from 'file-type';
|
||||
import { solveProblem } from '@/common/helper';
|
||||
|
||||
export interface HttpDownloadOptions {
|
||||
@@ -15,7 +13,6 @@ type Uri2LocalRes = {
|
||||
success: boolean,
|
||||
errMsg: string,
|
||||
fileName: string,
|
||||
ext: string,
|
||||
path: string
|
||||
}
|
||||
|
||||
@@ -73,27 +70,6 @@ async function checkFile(path: string): Promise<void> {
|
||||
// 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身
|
||||
}
|
||||
|
||||
export async function file2base64(path: string) {
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
const result = {
|
||||
err: '',
|
||||
data: '',
|
||||
};
|
||||
try {
|
||||
try {
|
||||
await checkFileExist(path, 5000);
|
||||
} catch (e: any) {
|
||||
result.err = e.toString();
|
||||
return result;
|
||||
}
|
||||
const data = await readFile(path);
|
||||
result.data = data.toString('base64');
|
||||
} catch (err: any) {
|
||||
result.err = err.toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function calculateFileMD5(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建一个流式读取器
|
||||
@@ -160,20 +136,6 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
||||
return Buffer.from(buffer);
|
||||
}
|
||||
|
||||
export async function checkFileV2(filePath: string) {
|
||||
try {
|
||||
const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (ext) {
|
||||
fs.renameSync(filePath, filePath + `.${ext}`);
|
||||
filePath += `.${ext}`;
|
||||
return { success: true, ext: ext, path: filePath };
|
||||
}
|
||||
} catch (e) {
|
||||
// log("获取文件类型失败", filePath,e.stack)
|
||||
}
|
||||
return { success: false, ext: '', path: filePath };
|
||||
}
|
||||
|
||||
export enum FileUriType {
|
||||
Unknown = 0,
|
||||
Local = 1,
|
||||
@@ -213,63 +175,35 @@ export async function checkUriType(Uri: string) {
|
||||
return { Uri: Uri, Type: FileUriType.Unknown };
|
||||
}
|
||||
|
||||
export async function uri2local(dir: string, uri: string, filename: string | undefined = undefined): Promise<Uri2LocalRes> {
|
||||
export async function uriToLocalFile(dir: string, uri: string): Promise<Uri2LocalRes> {
|
||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||
|
||||
//解析失败
|
||||
const tempName = randomUUID();
|
||||
if (!filename) filename = randomUUID();
|
||||
const filename = randomUUID();
|
||||
const filePath = path.join(dir, filename);
|
||||
|
||||
//解析Http和Https协议
|
||||
if (UriType == FileUriType.Unknown) {
|
||||
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
||||
}
|
||||
|
||||
//解析File协议和本地文件
|
||||
if (UriType == FileUriType.Local) {
|
||||
switch (UriType) {
|
||||
case FileUriType.Local: {
|
||||
const fileExt = path.extname(HandledUri);
|
||||
let filename = path.basename(HandledUri, fileExt);
|
||||
filename += fileExt;
|
||||
//复制文件到临时文件并保持后缀
|
||||
const filenameTemp = tempName + fileExt;
|
||||
const filePath = path.join(dir, filenameTemp);
|
||||
fs.copyFileSync(HandledUri, filePath);
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
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 };
|
||||
}
|
||||
|
||||
//接下来都要有文件名
|
||||
if (UriType == FileUriType.Remote) {
|
||||
const pathInfo = path.parse(decodeURIComponent(new URL(HandledUri).pathname));
|
||||
if (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).replace(/[/\\:*?"<>|]/g, '_').substring(0, 10);
|
||||
const filePath = path.join(dir, tempName + fileExt);
|
||||
case FileUriType.Remote: {
|
||||
const buffer = await httpDownload(HandledUri);
|
||||
//没有文件就创建
|
||||
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
|
||||
//解析Base64
|
||||
if (UriType == FileUriType.Base64) {
|
||||
case FileUriType.Base64: {
|
||||
const base64 = HandledUri.replace(/^base64:\/\//, '');
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
let filePath = path.join(dir, filename);
|
||||
let fileExt = '';
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
const { success, ext, path: fileTypePath } = await checkFileV2(filePath);
|
||||
if (success) {
|
||||
filePath = fileTypePath;
|
||||
fileExt = ext;
|
||||
filename = filename + '.' + ext;
|
||||
}
|
||||
return { success: true, errMsg: '', fileName: filename, ext: fileExt, path: filePath };
|
||||
const base64Buffer = Buffer.from(base64, 'base64');
|
||||
fs.writeFileSync(filePath, base64Buffer, { flag: 'wx' });
|
||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||
}
|
||||
|
||||
default:
|
||||
return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' };
|
||||
}
|
||||
return { success: false, errMsg: `未知文件类型, uri= ${uri}`, fileName: '', ext: '', path: '' };
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import winston, { format, transports } from 'winston';
|
||||
import { truncateString } from '@/common/helper';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import fs from 'node:fs/promises';
|
||||
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/core';
|
||||
import EventEmitter from 'node:events';
|
||||
export enum LogLevel {
|
||||
@@ -97,26 +97,20 @@ export class LogWrapper {
|
||||
|
||||
cleanOldLogs(logDir: string) {
|
||||
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
||||
fs.readdir(logDir, (err, files) => {
|
||||
if (err) {
|
||||
this.logger.error('Failed to read log directory', err);
|
||||
return;
|
||||
}
|
||||
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, (err, stats) => {
|
||||
if (err) {
|
||||
this.logger.error('Failed to get file stats', err);
|
||||
return;
|
||||
}
|
||||
fs.stat(filePath).then((stats) => {
|
||||
if (stats.mtime.getTime() < oneWeekAgo) {
|
||||
fs.unlink(filePath, (err) => {
|
||||
fs.unlink(filePath).catch((err) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.logger.warn(`File already deleted: ${filePath}`);
|
||||
@@ -128,6 +122,8 @@ export class LogWrapper {
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.logger.error('Failed to get file stats', err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -187,7 +183,7 @@ export class LogWrapper {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, ''));
|
||||
}
|
||||
logSubscription.notify(message);
|
||||
logSubscription.notify(JSON.stringify({ level, message }));
|
||||
}
|
||||
|
||||
log(...args: any[]) {
|
||||
@@ -316,9 +312,8 @@ function textElementToText(textElement: any): string {
|
||||
|
||||
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})`
|
||||
return `[回复消息 ${recordMsgOrNull && recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020'
|
||||
? rawMessageToText(recordMsgOrNull, recursiveLevel + 1)
|
||||
: `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})`
|
||||
}]`;
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import https from 'node:https';
|
||||
import http from 'node:http';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
export class RequestUtil {
|
||||
// 适用于获取服务器下发cookies时获取,仅GET
|
||||
@@ -112,24 +111,4 @@ export class RequestUtil {
|
||||
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string } = {}) {
|
||||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||
}
|
||||
|
||||
static async createFormData(boundary: string, filePath: string): Promise<Buffer> {
|
||||
let type = 'image/png';
|
||||
if (filePath.endsWith('.jpg')) {
|
||||
type = 'image/jpeg';
|
||||
}
|
||||
const formDataParts = [
|
||||
`------${boundary}\r\n`,
|
||||
`Content-Disposition: form-data; name="share_image"; filename="${filePath}"\r\n`,
|
||||
'Content-Type: ' + type + '\r\n\r\n',
|
||||
];
|
||||
|
||||
const fileContent = readFileSync(filePath);
|
||||
const footer = `\r\n------${boundary}--`;
|
||||
return Buffer.concat([
|
||||
Buffer.from(formDataParts.join(''), 'utf8'),
|
||||
fileContent,
|
||||
Buffer.from(footer, 'utf8'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.2.9';
|
||||
export const napCatVersion = '4.2.24';
|
||||
|
@@ -6,7 +6,6 @@ import {
|
||||
Peer,
|
||||
PicElement,
|
||||
PicSubType,
|
||||
PicType,
|
||||
RawMessage,
|
||||
SendFileElement,
|
||||
SendPicElement,
|
||||
@@ -17,7 +16,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import fsPromises from 'fs/promises';
|
||||
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
|
||||
import * as fileType from 'file-type';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import imageSize from 'image-size';
|
||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||
import { RkeyManager } from '@/core/helper/rkey';
|
||||
@@ -62,7 +61,7 @@ export class NTQQFileApi {
|
||||
|
||||
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) {
|
||||
const fileMd5 = await calculateFileMD5(filePath);
|
||||
const extOrEmpty = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
const extOrEmpty = await fileTypeFromFile(filePath).then(e => e?.ext ?? '').catch(e => '');
|
||||
const ext = extOrEmpty ? `.${extOrEmpty}` : '';
|
||||
let fileName = `${path.basename(filePath)}`;
|
||||
if (fileName.indexOf('.') === -1) {
|
||||
@@ -158,7 +157,7 @@ export class NTQQFileApi {
|
||||
|
||||
let fileExt = 'mp4';
|
||||
try {
|
||||
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
const tempExt = (await fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.context.logger.logError('获取文件类型失败', e);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FriendV2 } from '@/core/types';
|
||||
import { FriendRequest, FriendV2 } from '@/core/types';
|
||||
import { BuddyListReqType, InstanceContext, NapCatCore } from '@/core';
|
||||
import { LimitedHashTable } from '@/common/message-unique';
|
||||
|
||||
@@ -79,16 +79,10 @@ export class NTQQFriendApi {
|
||||
return ret;
|
||||
}
|
||||
|
||||
async handleFriendRequest(flag: string, accept: boolean) {
|
||||
const data = flag.split('|');
|
||||
if (data.length < 2) {
|
||||
return;
|
||||
}
|
||||
const friendUid = data[0];
|
||||
const reqTime = data[1];
|
||||
async handleFriendRequest(notify: FriendRequest, accept: boolean) {
|
||||
this.context.session.getBuddyService()?.approvalFriendRequest({
|
||||
friendUid: friendUid,
|
||||
reqTime: reqTime,
|
||||
friendUid: notify.friendUid,
|
||||
reqTime: notify.reqTime,
|
||||
accept,
|
||||
});
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
GeneralCallResult,
|
||||
Group,
|
||||
GroupMember,
|
||||
NTGroupMemberRole,
|
||||
NTGroupRequestOperateTypes,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
KickMemberV2Req,
|
||||
MemberExtSourceType,
|
||||
NapCatCore,
|
||||
GroupNotify,
|
||||
} from '@/core';
|
||||
import { isNumeric, solveAsyncProblem } from '@/common/helper';
|
||||
import { LimitedHashTable } from '@/common/message-unique';
|
||||
@@ -16,34 +16,22 @@ import { NTEventWrapper } from '@/common/event';
|
||||
export class NTQQGroupApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
groupCache: Map<string, Group> = new Map<string, Group>();
|
||||
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
|
||||
groups: Group[] = [];
|
||||
essenceLRU = new LimitedHashTable<number, string>(1000);
|
||||
session: any;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async initApi() {
|
||||
this.initCache().then().catch(e => this.context.logger.logError(e));
|
||||
}
|
||||
async initCache() {
|
||||
this.groups = await this.getGroups();
|
||||
for (const group of this.groups) {
|
||||
this.groupCache.set(group.groupCode, group);
|
||||
}
|
||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||
// process.pid 调试点
|
||||
}
|
||||
|
||||
async getCoreAndBaseInfo(uids: string[]) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
'nodeStore',
|
||||
uids,
|
||||
);
|
||||
async initCache() {
|
||||
for (const group of await this.getGroups(true)) {
|
||||
this.refreshGroupMemberCache(group.groupCode).then().catch();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchGroupEssenceList(groupCode: string) {
|
||||
@@ -54,20 +42,22 @@ export class NTQQGroupApi {
|
||||
pageLimit: 300,
|
||||
}, pskey);
|
||||
}
|
||||
|
||||
async getGroupShutUpMemberList(groupCode: string) {
|
||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', (group_id) => group_id === groupCode, 1, 1000);
|
||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
return (await data)[1];
|
||||
}
|
||||
async clearGroupNotifiesUnreadCount(uk: boolean) {
|
||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(uk);
|
||||
|
||||
async clearGroupNotifiesUnreadCount(doubt: boolean) {
|
||||
return this.context.session.getGroupService().clearGroupNotifiesUnreadCount(doubt);
|
||||
}
|
||||
|
||||
async setGroupAvatar(gc: string, filePath: string) {
|
||||
return this.context.session.getGroupService().setHeader(gc, filePath);
|
||||
async setGroupAvatar(groupCode: string, filePath: string) {
|
||||
return this.context.session.getGroupService().setHeader(groupCode, filePath);
|
||||
}
|
||||
|
||||
async getGroups(forced = false) {
|
||||
async getGroups(forced: boolean = false) {
|
||||
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getGroupList',
|
||||
'NodeIKernelGroupListener/onGroupListUpdate',
|
||||
@@ -76,9 +66,9 @@ export class NTQQGroupApi {
|
||||
return groupList;
|
||||
}
|
||||
|
||||
async getGroupExtFE0Info(groupCode: string[], forced = true) {
|
||||
async getGroupExtFE0Info(groupCodes: Array<string>, forced = true) {
|
||||
return this.context.session.getGroupService().getGroupExt0xEF0Info(
|
||||
groupCode,
|
||||
groupCodes,
|
||||
[],
|
||||
{
|
||||
bindGuildId: 1,
|
||||
@@ -118,53 +108,42 @@ export class NTQQGroupApi {
|
||||
);
|
||||
}
|
||||
|
||||
async getGroup(groupCode: string, forced = false) {
|
||||
let group = this.groupCache.get(groupCode.toString());
|
||||
if (!group) {
|
||||
try {
|
||||
const groupList = await this.getGroups(forced);
|
||||
if (groupList.length) {
|
||||
groupList.forEach(g => {
|
||||
this.groupCache.set(g.groupCode, g);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
group = this.groupCache.get(groupCode.toString());
|
||||
return group;
|
||||
}
|
||||
|
||||
async getGroupMemberAll(groupCode: string, forced = false) {
|
||||
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
|
||||
}
|
||||
|
||||
async refreshGroupMemberCache(groupCode: string) {
|
||||
try {
|
||||
const members = await this.getGroupMemberAll(groupCode, true);
|
||||
this.groupMemberCache.set(groupCode, members.result.infos);
|
||||
} catch (e) {
|
||||
this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`);
|
||||
}
|
||||
return this.groupMemberCache;
|
||||
}
|
||||
|
||||
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
|
||||
const groupCodeStr = groupCode.toString();
|
||||
const memberUinOrUidStr = memberUinOrUid.toString();
|
||||
|
||||
// 获取群成员缓存
|
||||
let members = this.groupMemberCache.get(groupCodeStr);
|
||||
if (!members) {
|
||||
try {
|
||||
members = await this.getGroupMembers(groupCodeStr);
|
||||
this.groupMemberCache.set(groupCodeStr, members);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function getMember() {
|
||||
let member: GroupMember | undefined;
|
||||
if (isNumeric(memberUinOrUidStr)) {
|
||||
member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr);
|
||||
} else {
|
||||
member = members!.get(memberUinOrUidStr);
|
||||
}
|
||||
return member;
|
||||
members = (await this.refreshGroupMemberCache(groupCodeStr)).get(groupCodeStr);
|
||||
}
|
||||
|
||||
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.getGroupMembers(groupCodeStr);
|
||||
members = (await this.refreshGroupMemberCache(groupCodeStr)).get(groupCodeStr);
|
||||
member = getMember();
|
||||
}
|
||||
return member;
|
||||
@@ -174,26 +153,26 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().getGroupRecommendContactArkJson(groupCode);
|
||||
}
|
||||
|
||||
async CreatGroupFileFolder(groupCode: string, folderName: string) {
|
||||
async creatGroupFileFolder(groupCode: string, folderName: string) {
|
||||
return this.context.session.getRichMediaService().createGroupFolder(groupCode, folderName);
|
||||
}
|
||||
|
||||
async DelGroupFile(groupCode: string, files: string[]) {
|
||||
async delGroupFile(groupCode: string, files: Array<string>) {
|
||||
return this.context.session.getRichMediaService().deleteGroupFile(groupCode, [102], files);
|
||||
}
|
||||
|
||||
async DelGroupFileFolder(groupCode: string, folderId: string) {
|
||||
async delGroupFileFolder(groupCode: string, folderId: string) {
|
||||
return this.context.session.getRichMediaService().deleteGroupFolder(groupCode, folderId);
|
||||
}
|
||||
|
||||
async addGroupEssence(GroupCode: string, msgId: string) {
|
||||
async addGroupEssence(groupCode: string, msgId: string) {
|
||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||
chatType: 2,
|
||||
guildId: '',
|
||||
peerUid: GroupCode,
|
||||
peerUid: groupCode,
|
||||
}, msgId, 1, false);
|
||||
const param = {
|
||||
groupCode: GroupCode,
|
||||
groupCode: groupCode,
|
||||
msgRandom: parseInt(MsgData.msgList[0].msgRandom),
|
||||
msgSeq: parseInt(MsgData.msgList[0].msgSeq),
|
||||
};
|
||||
@@ -204,9 +183,9 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().kickMemberV2(param);
|
||||
}
|
||||
|
||||
async deleteGroupBulletin(GroupCode: string, noticeId: string) {
|
||||
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);
|
||||
return this.context.session.getGroupService().deleteGroupBulletin(groupCode, psKey, noticeId);
|
||||
}
|
||||
|
||||
async quitGroupV2(GroupCode: string, needDeleteLocalMsg: boolean) {
|
||||
@@ -217,65 +196,42 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().quitGroupV2(param);
|
||||
}
|
||||
|
||||
async removeGroupEssenceBySeq(GroupCode: string, msgRandom: string, msgSeq: string) {
|
||||
async removeGroupEssenceBySeq(groupCode: string, msgRandom: string, msgSeq: string) {
|
||||
const param = {
|
||||
groupCode: GroupCode,
|
||||
groupCode: groupCode,
|
||||
msgRandom: parseInt(msgRandom),
|
||||
msgSeq: parseInt(msgSeq),
|
||||
};
|
||||
return this.context.session.getGroupService().removeGroupEssence(param);
|
||||
}
|
||||
|
||||
async removeGroupEssence(GroupCode: string, msgId: string) {
|
||||
async removeGroupEssence(groupCode: string, msgId: string) {
|
||||
const MsgData = await this.context.session.getMsgService().getMsgsIncludeSelf({
|
||||
chatType: 2,
|
||||
guildId: '',
|
||||
peerUid: GroupCode,
|
||||
peerUid: groupCode,
|
||||
}, msgId, 1, false);
|
||||
const param = {
|
||||
groupCode: GroupCode,
|
||||
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, num: number) {
|
||||
async getSingleScreenNotifies(doubt: boolean, count: number) {
|
||||
const [, , , notifies] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getSingleScreenNotifies',
|
||||
'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
|
||||
[
|
||||
doubt,
|
||||
'',
|
||||
num,
|
||||
count,
|
||||
],
|
||||
);
|
||||
return notifies;
|
||||
}
|
||||
|
||||
async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
|
||||
const Listener = this.core.eventWrapper.registerListen(
|
||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||
(params, _, members) => params === GroupCode && members.size > 0,
|
||||
1,
|
||||
forced ? 5000 : 250,
|
||||
);
|
||||
const retData = await (
|
||||
this.core.eventWrapper
|
||||
.createEventFunction('NodeIKernelGroupService/getMemberInfo')
|
||||
)!(GroupCode, [uid], forced);
|
||||
if (retData.result !== 0) {
|
||||
throw new Error(`${retData.errMsg}`);
|
||||
}
|
||||
const result = await Listener as unknown;
|
||||
let member: GroupMember | undefined;
|
||||
if (Array.isArray(result) && result?.[2] instanceof Map) {
|
||||
const members = result[2] as Map<string, GroupMember>;
|
||||
member = members.get(uid);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
async searchGroup(groupCode: string) {
|
||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelSearchService/searchGroup',
|
||||
@@ -294,178 +250,89 @@ export class NTQQGroupApi {
|
||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||
}
|
||||
|
||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||
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],
|
||||
[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);
|
||||
}, 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;
|
||||
const trydata = await this.getGroupMemberEx(groupCode, uid, true, retry - 1) as GroupMember | undefined;
|
||||
if (trydata) return trydata;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async tryGetGroupMembersV2(groupQQ: string, modeListener = false, num = 30, timeout = 100): Promise<{
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
|
||||
.catch(() => { });
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
}
|
||||
let resMode2;
|
||||
if (modeListener) {
|
||||
const ret = (await once)?.[0];
|
||||
if (ret) {
|
||||
resMode2 = ret;
|
||||
}
|
||||
}
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
return {
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
};
|
||||
async getGroupFileCount(groupCodes: Array<string>) {
|
||||
return this.context.session.getRichMediaService().batchGetGroupFileCount(groupCodes);
|
||||
}
|
||||
|
||||
async GetGroupMembersV3(groupQQ: string, num = 3000, timeout = 2500): Promise<{
|
||||
infos: Map<string, GroupMember>;
|
||||
finish: boolean;
|
||||
hasNext: boolean | undefined;
|
||||
listenerMode: boolean;
|
||||
}> {
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1');
|
||||
const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout)
|
||||
.catch(() => { });
|
||||
const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
}
|
||||
let resMode2;
|
||||
if (result.result.finish && result.result.infos.size === 0) {
|
||||
const ret = (await once)?.[0];
|
||||
if (ret) {
|
||||
resMode2 = ret;
|
||||
}
|
||||
}
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
return {
|
||||
infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]),
|
||||
finish: result.result.finish,
|
||||
hasNext: resMode2?.hasNext,
|
||||
listenerMode: resMode2?.hasNext !== undefined
|
||||
};
|
||||
}
|
||||
|
||||
async getGroupMembersV2(groupQQ: string, num = 3000, no_cache: boolean = false): Promise<Map<string, GroupMember>> {
|
||||
if (no_cache) {
|
||||
return (await this.getGroupMemberAll(groupQQ, true)).result.infos;
|
||||
}
|
||||
let res = await this.GetGroupMembersV3(groupQQ, num);
|
||||
let ret = res.infos;
|
||||
if (res.infos.size === 0 && !res.listenerMode) {
|
||||
res = await this.GetGroupMembersV3(groupQQ, num);
|
||||
ret = res.infos;
|
||||
}
|
||||
if (res.infos.size === 0) {
|
||||
ret = (await this.getGroupMemberAll(groupQQ)).result.infos;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
|
||||
const groupService = this.context.session.getGroupService();
|
||||
const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow');
|
||||
const result = await groupService.getNextMemberList(sceneId, undefined, num);
|
||||
if (result.errCode !== 0) {
|
||||
throw new Error('获取群成员列表出错,' + result.errMsg);
|
||||
}
|
||||
this.context.logger.logDebug(`获取群(${groupQQ})成员列表结果:`, `members: ${result.result.infos.size}`);
|
||||
return result.result.infos;
|
||||
}
|
||||
|
||||
async getGroupFileCount(group_ids: Array<string>) {
|
||||
return this.context.session.getRichMediaService().batchGetGroupFileCount(group_ids);
|
||||
}
|
||||
|
||||
async getArkJsonGroupShare(GroupCode: string) {
|
||||
async getArkJsonGroupShare(groupCode: string) {
|
||||
const ret = await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelGroupService/getGroupRecommendContactArkJson',
|
||||
GroupCode,
|
||||
groupCode,
|
||||
) as GeneralCallResult & { arkJson: string };
|
||||
return ret.arkJson;
|
||||
}
|
||||
|
||||
//需要异常处理
|
||||
async uploadGroupBulletinPic(GroupCode: string, imageurl: string) {
|
||||
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);
|
||||
return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl);
|
||||
}
|
||||
|
||||
async handleGroupRequest(flag: string, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||||
const flagitem = flag.split('|');
|
||||
const groupCode = flagitem[0];
|
||||
const seq = flagitem[1];
|
||||
const type = parseInt(flagitem[2]);
|
||||
|
||||
async handleGroupRequest(notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
|
||||
return this.context.session.getGroupService().operateSysNotify(
|
||||
false,
|
||||
{
|
||||
operateType: operateType,
|
||||
targetMsg: {
|
||||
seq: seq, // 通知序列号
|
||||
type: type,
|
||||
groupCode: groupCode,
|
||||
seq: notify.seq, // 通知序列号
|
||||
type: notify.type,
|
||||
groupCode: notify.group.groupCode,
|
||||
postscript: reason ?? ' ', // 仅传空值可能导致处理失败,故默认给个空格
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async quitGroup(groupQQ: string) {
|
||||
return this.context.session.getGroupService().quitGroup(groupQQ);
|
||||
async quitGroup(groupCode: string) {
|
||||
return this.context.session.getGroupService().quitGroup(groupCode);
|
||||
}
|
||||
|
||||
async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
||||
return this.context.session.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason);
|
||||
async kickMember(groupCode: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
|
||||
return this.context.session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason);
|
||||
}
|
||||
|
||||
async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
||||
async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) {
|
||||
// timeStamp为秒数, 0为解除禁言
|
||||
return this.context.session.getGroupService().setMemberShutUp(groupQQ, memList);
|
||||
return this.context.session.getGroupService().setMemberShutUp(groupCode, memList);
|
||||
}
|
||||
|
||||
async banGroup(groupQQ: string, shutUp: boolean) {
|
||||
return this.context.session.getGroupService().setGroupShutUp(groupQQ, shutUp);
|
||||
async banGroup(groupCode: string, shutUp: boolean) {
|
||||
return this.context.session.getGroupService().setGroupShutUp(groupCode, shutUp);
|
||||
}
|
||||
|
||||
async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
|
||||
return this.context.session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName);
|
||||
async setMemberCard(groupCode: string, memberUid: string, cardName: string) {
|
||||
return this.context.session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName);
|
||||
}
|
||||
|
||||
async setMemberRole(groupQQ: string, memberUid: string, role: NTGroupMemberRole) {
|
||||
return this.context.session.getGroupService().modifyMemberRole(groupQQ, memberUid, role);
|
||||
async setMemberRole(groupCode: string, memberUid: string, role: NTGroupMemberRole) {
|
||||
return this.context.session.getGroupService().modifyMemberRole(groupCode, memberUid, role);
|
||||
}
|
||||
|
||||
async setGroupName(groupQQ: string, groupName: string) {
|
||||
return this.context.session.getGroupService().modifyGroupName(groupQQ, groupName, false);
|
||||
async setGroupName(groupCode: string, groupName: string) {
|
||||
return this.context.session.getGroupService().modifyGroupName(groupCode, groupName, false);
|
||||
}
|
||||
|
||||
async publishGroupBulletin(groupQQ: string, content: string, picInfo: {
|
||||
async publishGroupBulletin(groupCode: string, content: string, picInfo: {
|
||||
id: string,
|
||||
width: number,
|
||||
height: number
|
||||
@@ -479,11 +346,11 @@ export class NTQQGroupApi {
|
||||
pinned: pinned,
|
||||
confirmRequired: confirmRequired,
|
||||
};
|
||||
return this.context.session.getGroupService().publishGroupBulletin(groupQQ, psKey!, data);
|
||||
return this.context.session.getGroupService().publishGroupBulletin(groupCode, psKey!, data);
|
||||
}
|
||||
|
||||
async getGroupRemainAtTimes(GroupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupRemainAtTimes(GroupCode);
|
||||
async getGroupRemainAtTimes(groupCode: string) {
|
||||
return this.context.session.getGroupService().getGroupRemainAtTimes(groupCode);
|
||||
}
|
||||
|
||||
async getMemberExtInfo(groupCode: string, uin: string) {
|
||||
|
@@ -1,25 +0,0 @@
|
||||
import { InstanceContext, NapCatCore } from '..';
|
||||
|
||||
export class NTQQMusicSignApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
|
||||
constructor(context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
//转换外域名为 https://qq.ugcimg.cn/v1/cpqcbu4b8870i61bde6k7cbmjgejq8mr3in82qir4qi7ielffv5slv8ck8g42novtmev26i233ujtuab6tvu2l2sjgtupfr389191v00s1j5oh5325j5eqi40774jv1i/khovifoh7jrqd6eahoiv7koh8o
|
||||
//https://cgi.connect.qq.com/qqconnectopen/openapi/change_image_url?url=https://th.bing.com/th?id=OSK.b8ed36f1fb1889de6dc84fd81c187773&w=46&h=46&c=11&rs=1&qlt=80&o=6&dpr=2&pid=SANGAM
|
||||
|
||||
//外域名不行得走qgroup中转
|
||||
//https://proxy.gtimg.cn/tx_tls_gate=y.qq.com/music/photo_new/T002R800x800M000000y5gq7449K9I.jpg
|
||||
|
||||
//可外域名
|
||||
//https://pic.ugcimg.cn/500955bdd6657ecc8e82e02d2df06800/jpg1
|
||||
|
||||
//QQ音乐gtimg接口
|
||||
//https://y.gtimg.cn/music/photo_new/T002R800x800M000000y5gq7449K9I.jpg?max_age=2592000
|
||||
|
||||
//还有一处公告上传可以上传高质量图片 持久为qq域名
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ import { ModifyProfileParams, User, UserDetailSource } from '@/core/types';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
|
||||
import { solveAsyncProblem } from '@/common/helper';
|
||||
import { promisify } from 'node:util';
|
||||
import { LRUCache } from '@/common/lru-cache';
|
||||
|
||||
export class NTQQUserApi {
|
||||
context: InstanceContext;
|
||||
@@ -11,13 +13,15 @@ export class NTQQUserApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
//self_tind格式
|
||||
async createUidFromTinyId(tinyId: string) {
|
||||
return this.context.session.getMsgService().createUidFromTinyId(this.core.selfInfo.uin, tinyId);
|
||||
}
|
||||
async getStatusByUid(uid: string) {
|
||||
return this.context.session.getProfileService().getStatus(uid);
|
||||
|
||||
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({
|
||||
@@ -161,35 +165,51 @@ export class NTQQUserApi {
|
||||
if (!skey) {
|
||||
throw new Error('SKey is Empty');
|
||||
}
|
||||
|
||||
return skey;
|
||||
}
|
||||
|
||||
//后期改成流水线处理
|
||||
async getUidByUinV2(Uin: string) {
|
||||
let uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
|
||||
if (uid) return uid;
|
||||
uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
|
||||
if (uid) return uid;
|
||||
uid = (await this.context.session.getUixConvertService().getUid([Uin])).uidInfo.get(Uin);
|
||||
if (uid) return uid;
|
||||
const unverifiedUid = (await this.getUserDetailInfoByUin(Uin)).detail.uid;//从QQ Native 特殊转换
|
||||
if (unverifiedUid.indexOf('*') == -1) uid = unverifiedUid;
|
||||
//if (uid) return uid;
|
||||
return uid;
|
||||
if (!Uin) {
|
||||
return '';
|
||||
}
|
||||
const services = [
|
||||
() => this.context.session.getUixConvertService().getUid([Uin]).then((data) => data.uidInfo.get(Uin)).catch(() => undefined),
|
||||
() => promisify<string, string[], Map<string, string>>
|
||||
(this.context.session.getProfileService().getUidByUin)('FriendsServiceImpl', [Uin]).then((data) => data.get(Uin)).catch(() => undefined),
|
||||
() => this.context.session.getGroupService().getUidByUins([Uin]).then((data) => data.uids.get(Uin)).catch(() => undefined),
|
||||
() => this.getUserDetailInfoByUin(Uin).then((data) => data.detail.uid).catch(() => undefined),
|
||||
];
|
||||
let uid: string | undefined = undefined;
|
||||
for (const service of services) {
|
||||
uid = await service();
|
||||
if (uid && uid.indexOf('*') == -1 && uid !== '') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return uid ?? '';
|
||||
}
|
||||
|
||||
//后期改成流水线处理
|
||||
async getUinByUidV2(Uid: string) {
|
||||
let uin = (await this.context.session.getGroupService().getUinByUids([Uid])).uins.get(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [Uid])).get(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.context.session.getUixConvertService().getUin([Uid])).uinInfo.get(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.core.apis.FriendApi.getBuddyIdMap(true)).getKey(Uid);
|
||||
if (uin) return uin;
|
||||
uin = (await this.getUserDetailInfo(Uid)).uin; //从QQ Native 转换
|
||||
return uin;
|
||||
if (!Uid) {
|
||||
return '0';
|
||||
}
|
||||
const services = [
|
||||
() => this.context.session.getUixConvertService().getUin([Uid]).then((data) => data.uinInfo.get(Uid)).catch(() => undefined),
|
||||
() => this.context.session.getGroupService().getUinByUids([Uid]).then((data) => data.uins.get(Uid)).catch(() => undefined),
|
||||
() => promisify<string, string[], Map<string, string>>
|
||||
(this.context.session.getProfileService().getUinByUid)('FriendsServiceImpl', [Uid]).then((data) => data.get(Uid)).catch(() => undefined),
|
||||
() => this.core.apis.FriendApi.getBuddyIdMap(true).then((data) => data.getKey(Uid)).catch(() => undefined),
|
||||
() => this.getUserDetailInfo(Uid).then((data) => data.uin).catch(() => undefined),
|
||||
];
|
||||
let uin: string | undefined = undefined;
|
||||
for (const service of services) {
|
||||
uin = await service();
|
||||
if (uin && uin !== '0' && uin !== '') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return uin ?? '0';
|
||||
}
|
||||
|
||||
async getRecentContactListSnapShot(count: number) {
|
||||
|
@@ -366,50 +366,4 @@ export class NTQQWebApi {
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
async uploadQunAlbumSlice(path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) {
|
||||
const img_size = statSync(path).size;
|
||||
const img_name = basename(path);
|
||||
let seq = 0;
|
||||
let offset = 0;
|
||||
const GTK = this.getBknFromSKey(pskey);
|
||||
const cookie = `p_uin=${uin}; p_skey=${pskey}; skey=${skey}; uin=${uin}`;
|
||||
|
||||
const stream = createReadStream(path, { highWaterMark: slice_size });
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const end = Math.min(offset + chunk.length, img_size);
|
||||
const boundary = `----WebKitFormBoundary${Math.random().toString(36).substring(2)}`;
|
||||
const formData = await RequestUtil.createFormData(boundary, path);
|
||||
|
||||
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 body = {
|
||||
uin: uin,
|
||||
appid: "qun",
|
||||
session: session,
|
||||
offset: offset,
|
||||
data: formData,
|
||||
checksum: "",
|
||||
check_type: 0,
|
||||
retry: 0,
|
||||
seq: seq,
|
||||
end: end,
|
||||
cmd: "FileUpload",
|
||||
slice_size: slice_size,
|
||||
"biz_req.iUploadType": 0
|
||||
};
|
||||
|
||||
const post = await RequestUtil.HttpGetJson(api, 'POST', body, {
|
||||
"Cookie": cookie,
|
||||
"Content-Type": `multipart/form-data; boundary=${boundary}`
|
||||
});
|
||||
|
||||
offset += chunk.length;
|
||||
seq++;
|
||||
}
|
||||
}
|
||||
async uploadQunAlbum(path: string, albumId: string, group: string, skey: string, pskey: string, uin: string) {
|
||||
const session = (await this.createQunAlbumSession(group, albumId, group, path, skey, pskey, uin) as { data: { session: string } }).data.session;
|
||||
return await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
12
src/core/external/appid.json
vendored
12
src/core/external/appid.json
vendored
@@ -98,5 +98,17 @@
|
||||
"6.9.61-29927": {
|
||||
"appid": 537255836,
|
||||
"qua": "V1_MAC_NQ_6.9.61_29927_GW_B"
|
||||
},
|
||||
"9.9.17-30366": {
|
||||
"appid": 537258389,
|
||||
"qua": "V1_WIN_NQ_9.9.17_30366_GW_B"
|
||||
},
|
||||
"3.2.15-30366": {
|
||||
"appid": 537258413,
|
||||
"qua": "V1_LNX_NQ_3.2.15_30366_GW_B"
|
||||
},
|
||||
"6.9.62-30366": {
|
||||
"appid": 537258401,
|
||||
"qua": "V1_MAC_NQ_6.9.62_30366_GW_B"
|
||||
}
|
||||
}
|
20
src/core/external/offset.json
vendored
20
src/core/external/offset.json
vendored
@@ -102,5 +102,25 @@
|
||||
"6.9.61-29927-arm64": {
|
||||
"send": "4038740",
|
||||
"recv": "403AF58"
|
||||
},
|
||||
"9.9.17-30366-x64": {
|
||||
"send": "39AB0B0",
|
||||
"recv": "39AF4E4"
|
||||
},
|
||||
"3.2.15-30366-x64": {
|
||||
"send": "A402380",
|
||||
"recv": "A405C80"
|
||||
},
|
||||
"3.2.15-30366-arm64": {
|
||||
"send": "70C3FA8",
|
||||
"recv": "70C77E0"
|
||||
},
|
||||
"6.9.62-30366-x64": {
|
||||
"send": "4669760",
|
||||
"recv": "466BFCC"
|
||||
},
|
||||
"6.9.62-30366-arm64": {
|
||||
"send": "4189770",
|
||||
"recv": "418BF88"
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import * as fileType from 'file-type';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import { PicType } from '../types';
|
||||
export async function getFileTypeForSendType(picPath: string): Promise<PicType> {
|
||||
const fileTypeResult = (await fileType.fileTypeFromFile(picPath))?.ext ?? 'jpg';
|
||||
const fileTypeResult = (await fileTypeFromFile(picPath))?.ext ?? 'jpg';
|
||||
const picTypeMap: { [key: string]: PicType } = {
|
||||
//'webp': PicType.NEWPIC_WEBP,
|
||||
'gif': PicType.NEWPIC_GIF,
|
||||
|
@@ -24,10 +24,10 @@ import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/types';
|
||||
import { GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/types';
|
||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||
import os from 'node:os';
|
||||
import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
import { NTQQPacketApi } from './apis/packet';
|
||||
export * from './wrapper';
|
||||
@@ -163,7 +163,6 @@ export class NapCatCore {
|
||||
msgListener.onAddSendMsg = (msg) => {
|
||||
this.context.logger.logMessage(msg, this.selfInfo);
|
||||
};
|
||||
//await sleep(2500);
|
||||
this.context.session.getMsgService().addKernelMsgListener(
|
||||
proxiedListenerOf(msgListener, this.context.logger),
|
||||
);
|
||||
@@ -185,92 +184,6 @@ export class NapCatCore {
|
||||
this.context.session.getProfileService().addKernelProfileListener(
|
||||
proxiedListenerOf(profileListener, this.context.logger),
|
||||
);
|
||||
|
||||
// 群相关
|
||||
const groupListener = new NodeIKernelGroupListener();
|
||||
groupListener.onGroupListUpdate = (updateType, groupList) => {
|
||||
// console.log("onGroupListUpdate", updateType, groupList)
|
||||
groupList.map(g => {
|
||||
const existGroup = this.apis.GroupApi.groupCache.get(g.groupCode);
|
||||
//群成员数量变化 应该刷新缓存
|
||||
if (existGroup && g.memberCount === existGroup.memberCount) {
|
||||
Object.assign(existGroup, g);
|
||||
} else {
|
||||
this.apis.GroupApi.groupCache.set(g.groupCode, g);
|
||||
// 获取群成员
|
||||
}
|
||||
const sceneId = this.context.session.getGroupService().createMemberListScene(g.groupCode, 'groupMemberList_MainWindow');
|
||||
this.context.session.getGroupService().getNextMemberList(sceneId, undefined, 3000).then( /* r => {
|
||||
// console.log(`get group ${g.groupCode} members`, r);
|
||||
// r.result.infos.forEach(member => {
|
||||
// });
|
||||
// groupMembers.set(g.groupCode, r.result.infos);
|
||||
} */);
|
||||
this.context.session.getGroupService().destroyMemberListScene(sceneId);
|
||||
});
|
||||
};
|
||||
groupListener.onMemberListChange = (arg) => {
|
||||
// TODO: 应该加一个内部自己维护的成员变动callback,用于判断成员变化通知
|
||||
const groupCode = arg.sceneId.split('_')[0];
|
||||
if (this.apis.GroupApi.groupMemberCache.has(groupCode)) {
|
||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode)!;
|
||||
arg.infos.forEach((member, uid) => {
|
||||
//console.log('onMemberListChange', member);
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
Object.assign(existMember, member);
|
||||
} else {
|
||||
existMembers.set(uid, member);
|
||||
}
|
||||
//移除成员
|
||||
if (member.isDelete) {
|
||||
existMembers.delete(uid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.apis.GroupApi.groupMemberCache.set(groupCode, arg.infos);
|
||||
}
|
||||
};
|
||||
groupListener.onMemberInfoChange = (groupCode, dataSource, members) => {
|
||||
if (dataSource === DataSource.LOCAL && members.get(this.selfInfo.uid)?.isDelete) {
|
||||
// 自身退群或者被踢退群 5s用于Api操作 之后不再出现
|
||||
setTimeout(() => {
|
||||
this.apis.GroupApi.groupCache.delete(groupCode);
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
const existMembers = this.apis.GroupApi.groupMemberCache.get(groupCode);
|
||||
if (existMembers) {
|
||||
members.forEach((member, uid) => {
|
||||
const existMember = existMembers.get(uid);
|
||||
if (existMember) {
|
||||
// 检查管理变动
|
||||
member.isChangeRole = this.checkAdminEvent(groupCode, member, existMember);
|
||||
// 更新成员信息
|
||||
Object.assign(existMember, member);
|
||||
} else {
|
||||
existMembers.set(uid, member);
|
||||
}
|
||||
//移除成员
|
||||
if (member.isDelete) {
|
||||
existMembers.delete(uid);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.apis.GroupApi.groupMemberCache.set(groupCode, members);
|
||||
}
|
||||
};
|
||||
this.context.session.getGroupService().addKernelGroupListener(
|
||||
proxiedListenerOf(groupListener, this.context.logger),
|
||||
);
|
||||
}
|
||||
|
||||
checkAdminEvent(groupCode: string, memberNew: GroupMember, memberOld: GroupMember | undefined): boolean {
|
||||
if (memberNew.role !== memberOld?.role) {
|
||||
this.context.logger.logDebug(`群 ${groupCode} ${memberNew.nick} 角色变更为 ${memberNew.role === 3 ? '管理员' : '群员'}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/core/packet/transformer/proto/message/groupAdmin.ts
Normal file
18
src/core/packet/transformer/proto/message/groupAdmin.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||
|
||||
export const GroupAdminExtra = {
|
||||
adminUid: ProtoField(1, ScalarType.STRING),
|
||||
isPromote: ProtoField(2, ScalarType.BOOL),
|
||||
};
|
||||
|
||||
export const GroupAdminBody = {
|
||||
extraDisable: ProtoField(1, () => GroupAdminExtra),
|
||||
extraEnable: ProtoField(2, () => GroupAdminExtra),
|
||||
};
|
||||
|
||||
export const GroupAdmin = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
flag: ProtoField(2, ScalarType.UINT32),
|
||||
isPromote: ProtoField(3, ScalarType.BOOL),
|
||||
body: ProtoField(4, () => GroupAdminBody),
|
||||
};
|
@@ -187,13 +187,13 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
destroyGroup(groupCode: string): void;
|
||||
|
||||
getSingleScreenNotifies(doubted: boolean, start_seq: string, num: number): Promise<GeneralCallResult>;
|
||||
getSingleScreenNotifies(doubt: boolean, startSeq: string, count: number): Promise<GeneralCallResult>;
|
||||
|
||||
clearGroupNotifies(groupCode: string): void;
|
||||
|
||||
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>;
|
||||
getGroupNotifiesUnreadCount(doubt: boolean): Promise<GeneralCallResult>;
|
||||
|
||||
clearGroupNotifiesUnreadCount(unknown: boolean): void;
|
||||
clearGroupNotifiesUnreadCount(doubt: boolean): void;
|
||||
|
||||
operateSysNotify(
|
||||
doubt: boolean,
|
||||
|
@@ -9,9 +9,9 @@ export interface NodeIKernelProfileService {
|
||||
|
||||
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>): Map<string, string>;
|
||||
|
||||
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>>;
|
||||
getUinByUid(callfrom: string, uid: Array<string>): Map<string, string>;
|
||||
|
||||
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>;
|
||||
|
||||
|
@@ -29,6 +29,7 @@ export interface TextElement {
|
||||
}
|
||||
|
||||
export interface FaceElement {
|
||||
pokeType?: number;
|
||||
faceIndex: number;
|
||||
faceType: FaceType;
|
||||
faceText?: string;
|
||||
|
@@ -14,6 +14,6 @@ export class GetCollectionList extends OneBotAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
return await this.core.apis.CollectionApi.getAllCollection(parseInt(payload.category.toString()), +payload.count);
|
||||
return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count);
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +1,36 @@
|
||||
import { GroupNotifyMsgStatus } from '@/core';
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Notify } from '@/onebot/types';
|
||||
|
||||
interface OB11GroupRequestNotify {
|
||||
group_id: number,
|
||||
user_id: number,
|
||||
flag: string
|
||||
}
|
||||
|
||||
export default class GetGroupAddRequest extends OneBotAction<null, OB11GroupRequestNotify[] | null> {
|
||||
export default class GetGroupAddRequest extends OneBotAction<null, Notify[] | null> {
|
||||
actionName = ActionName.GetGroupIgnoreAddRequest;
|
||||
|
||||
async _handle(payload: null): Promise<OB11GroupRequestNotify[] | null> {
|
||||
const ignoredNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(true, 10);
|
||||
const retData: any = {
|
||||
join_requests: await Promise.all(
|
||||
ignoredNotifies
|
||||
.filter(notify => notify.type === 7)
|
||||
.map(async SSNotify => ({
|
||||
request_id: SSNotify.seq,
|
||||
requester_uin: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||
requester_nick: SSNotify.user1?.nickName,
|
||||
group_id: SSNotify.group?.groupCode,
|
||||
group_name: SSNotify.group?.groupName,
|
||||
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
|
||||
actor: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
|
||||
}))),
|
||||
};
|
||||
async _handle(payload: null): Promise<Notify[] | null> {
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
return retData;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { checkFileExist, uri2local } from '@/common/file';
|
||||
import { checkFileExist, uriToLocalFile } from '@/common/file';
|
||||
import fs from 'fs';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
@@ -15,7 +15,7 @@ export class OCRImage extends OneBotAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.image));
|
||||
const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.image));
|
||||
if (!success) {
|
||||
throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import * as fs from 'node:fs';
|
||||
import { checkFileExist, uri2local } from '@/common/file';
|
||||
import fs from 'node:fs/promises';
|
||||
import { checkFileExist, uriToLocalFile } from '@/common/file';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
@@ -14,16 +14,14 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
|
||||
actionName = ActionName.SetQQAvatar;
|
||||
payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
|
||||
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, () => {
|
||||
});
|
||||
|
||||
fs.unlink(path).catch(() => { });
|
||||
if (!ret) {
|
||||
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
||||
}
|
||||
@@ -34,7 +32,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
|
||||
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
} else {
|
||||
fs.unlink(path, () => { });
|
||||
fs.unlink(path).catch(() => { });
|
||||
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
|
||||
}
|
||||
return null;
|
||||
|
@@ -13,6 +13,6 @@ export class CreateGroupFileFolder extends OneBotAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
|
||||
payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
return (await this.core.apis.GroupApi.CreatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
|
||||
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,6 @@ export class DeleteGroupFile extends OneBotAction<Payload, any> {
|
||||
async _handle(payload: Payload) {
|
||||
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (!data) throw new Error('Invalid file_id');
|
||||
return await this.core.apis.GroupApi.DelGroupFile(payload.group_id.toString(), [data.fileId]);
|
||||
return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]);
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ export class DeleteGroupFileFolder extends OneBotAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder;
|
||||
payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
return (await this.core.apis.GroupApi.DelGroupFileFolder(
|
||||
return (await this.core.apis.GroupApi.delGroupFileFolder(
|
||||
payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { checkFileExist, uri2local } from '@/common/file';
|
||||
import { checkFileExist, uriToLocalFile } from '@/common/file';
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { unlink } from 'node:fs';
|
||||
import { unlink } from 'node:fs/promises';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
@@ -28,7 +28,7 @@ export class SendGroupNotice extends OneBotAction<Payload, null> {
|
||||
const {
|
||||
path,
|
||||
success,
|
||||
} = (await uri2local(this.core.NapCatTempPath, payload.image));
|
||||
} = (await uriToLocalFile(this.core.NapCatTempPath, payload.image));
|
||||
if (!success) {
|
||||
throw new Error(`群公告${payload.image}设置失败,image字段可能格式不正确`);
|
||||
}
|
||||
@@ -41,8 +41,7 @@ export class SendGroupNotice extends OneBotAction<Payload, null> {
|
||||
throw new Error(`群公告${payload.image}设置失败,图片上传失败`);
|
||||
}
|
||||
|
||||
unlink(path, () => {
|
||||
});
|
||||
unlink(path).catch(() => { });
|
||||
|
||||
UploadImage = ImageUploadResult.picInfo;
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
||||
import * as fs from 'node:fs';
|
||||
import { checkFileExistV2, uri2local } from '@/common/file';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { checkFileExistV2, uriToLocalFile } from '@/common/file';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import fs from 'node:fs/promises';
|
||||
const SchemaData = Type.Object({
|
||||
file: Type.String(),
|
||||
group_id: Type.Union([Type.Number(), Type.String()])
|
||||
@@ -16,14 +15,14 @@ export default class SetGroupPortrait extends OneBotAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload): Promise<any> {
|
||||
const { path, success } = (await uri2local(this.core.NapCatTempPath, payload.file));
|
||||
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, () => { });
|
||||
fs.unlink(path).catch(() => { });
|
||||
if (!ret) {
|
||||
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
||||
}
|
||||
@@ -34,7 +33,7 @@ export default class SetGroupPortrait extends OneBotAction<Payload, any> {
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
fs.unlink(path, () => { });
|
||||
fs.unlink(path).catch(() => { });
|
||||
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { ChatType, Peer } from '@/core/types';
|
||||
import fs from 'fs';
|
||||
import { uri2local } from '@/common/file';
|
||||
import { uriToLocalFile } from '@/common/file';
|
||||
import { SendMessageContext } from '@/onebot/api';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class GoCQHTTPUploadGroupFile extends OneBotAction<Payload, null>
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`;
|
||||
}
|
||||
const downloadResult = await uri2local(this.core.NapCatTempPath, file);
|
||||
const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, file);
|
||||
const peer: Peer = {
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
peerUid: payload.group_id.toString(),
|
||||
|
@@ -2,7 +2,7 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { ChatType, Peer, SendFileElement } from '@/core/types';
|
||||
import fs from 'fs';
|
||||
import { uri2local } from '@/common/file';
|
||||
import { uriToLocalFile } from '@/common/file';
|
||||
import { SendMessageContext } from '@/onebot/api';
|
||||
import { ContextMode, createContext } from '@/onebot/action/msg/SendMsg';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
@@ -36,7 +36,7 @@ export default class GoCQHTTPUploadPrivateFile extends OneBotAction<Payload, nul
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`;
|
||||
}
|
||||
const downloadResult = await uri2local(this.core.NapCatTempPath, file);
|
||||
const downloadResult = await uriToLocalFile(this.core.NapCatTempPath, file);
|
||||
if (!downloadResult.success) {
|
||||
throw new Error(downloadResult.errMsg);
|
||||
}
|
||||
|
@@ -1,25 +1,43 @@
|
||||
import { GroupNotifyMsgStatus } from '@/core';
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
export class GetGroupIgnoredNotifies extends OneBotAction<void, any> {
|
||||
import { Notify } from '@/onebot/types';
|
||||
|
||||
interface RetData {
|
||||
InvitedRequest: Notify[];
|
||||
join_requests: Notify[];
|
||||
}
|
||||
|
||||
export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
|
||||
actionName = ActionName.GetGroupIgnoredNotifies;
|
||||
|
||||
async _handle(payload: void) {
|
||||
const ignoredNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(true, 10);
|
||||
const retData: any = {
|
||||
join_requests: await Promise.all(
|
||||
ignoredNotifies
|
||||
.filter(notify => notify.type === 7)
|
||||
.map(async SSNotify => ({
|
||||
request_id: SSNotify.seq,
|
||||
requester_uin: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1?.uid),
|
||||
requester_nick: SSNotify.user1?.nickName,
|
||||
group_id: SSNotify.group?.groupCode,
|
||||
group_name: SSNotify.group?.groupName,
|
||||
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
|
||||
actor: await this.core.apis.UserApi.getUinByUidV2(SSNotify.user2?.uid) || 0,
|
||||
}))),
|
||||
};
|
||||
async _handle(): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
|
||||
const retData: RetData = { 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,
|
||||
};
|
||||
|
||||
if (SSNotify.type === 1) {
|
||||
retData.InvitedRequest.push(commonData);
|
||||
} else if (SSNotify.type === 7) {
|
||||
retData.join_requests.push(commonData);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
return retData;
|
||||
}
|
||||
|
@@ -19,11 +19,14 @@ export class GetGroupMemberList extends OneBotAction<Payload, OB11GroupMember[]>
|
||||
const groupIdStr = payload.group_id.toString();
|
||||
const noCache = payload.no_cache ? this.stringToBoolean(payload.no_cache) : false;
|
||||
const memberCache = this.core.apis.GroupApi.groupMemberCache;
|
||||
let groupMembers;
|
||||
try {
|
||||
groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr, 3000, noCache);
|
||||
} catch (error) {
|
||||
groupMembers = memberCache.get(groupIdStr) ?? await this.core.apis.GroupApi.getGroupMembersV2(groupIdStr);
|
||||
let groupMembers = memberCache.get(groupIdStr);
|
||||
if (noCache || !groupMembers) {
|
||||
this.core.apis.GroupApi.refreshGroupMemberCache(groupIdStr).then().catch();
|
||||
//下次刷新
|
||||
groupMembers = memberCache.get(groupIdStr);
|
||||
if (!groupMembers) {
|
||||
throw new Error(`Failed to get group member list for group ${groupIdStr}`);
|
||||
}
|
||||
}
|
||||
const memberPromises = Array.from(groupMembers.values()).map(item =>
|
||||
OB11Construct.groupMember(groupIdStr, item)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { uri2local } from "@/common/file";
|
||||
import { uriToLocalFile } from "@/common/file";
|
||||
import { ChatType, Peer } from "@/core";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
@@ -23,7 +23,7 @@ export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
|
||||
async _handle(payload: Payload) {
|
||||
const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||
const url = await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||
const { path, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
|
||||
const { path, errMsg, success } = (await uriToLocalFile(this.core.NapCatTempPath, url));
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
@@ -4,9 +4,9 @@ import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
flag: Type.String(),
|
||||
flag: Type.Union([Type.String(), Type.Number()]),
|
||||
approve: Type.Optional(Type.Union([Type.Boolean(), Type.String()])),
|
||||
reason: Type.Union([Type.String({ default: ' ' }), Type.Null()]),
|
||||
reason: Type.Optional(Type.Union([Type.String({ default: ' ' }), Type.Null()])),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -18,10 +18,26 @@ export default class SetGroupAddRequest extends OneBotAction<Payload, null> {
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
const flag = payload.flag.toString();
|
||||
const approve = payload.approve?.toString() !== 'false';
|
||||
await this.core.apis.GroupApi.handleGroupRequest(flag,
|
||||
const reason = payload.reason ?? ' ';
|
||||
|
||||
const notify = await this.findNotify(flag);
|
||||
if (!notify) {
|
||||
throw new Error('No such request');
|
||||
}
|
||||
|
||||
await this.core.apis.GroupApi.handleGroupRequest(
|
||||
notify,
|
||||
approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
|
||||
payload.reason ?? ' ',
|
||||
reason,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
private 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 notify;
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ export default class SetGroupBan extends OneBotAction<Payload, null> {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('uid error');
|
||||
await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
|
||||
[{ uid: uid, timeStamp: parseInt(payload.duration.toString()) }]);
|
||||
[{ uid: uid, timeStamp: +payload.duration }]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ class ForwardSingleMsg extends OneBotAction<Payload, null> {
|
||||
}
|
||||
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||
if (!msg) {
|
||||
throw new Error(`无法找到消息${payload.message_id}`);
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ export class SetMsgEmojiLike extends OneBotAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
|
||||
const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||
if (!msg) {
|
||||
throw new Error('msg not found');
|
||||
}
|
||||
|
@@ -1,38 +1,43 @@
|
||||
import { GroupNotifyMsgStatus } from '@/core';
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
export class GetGroupSystemMsg extends OneBotAction<void, any> {
|
||||
import { Notify } from '@/onebot/types';
|
||||
|
||||
interface RetData {
|
||||
InvitedRequest: Notify[];
|
||||
join_requests: Notify[];
|
||||
}
|
||||
|
||||
export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
|
||||
actionName = ActionName.GetGroupSystemMsg;
|
||||
|
||||
async _handle() {
|
||||
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,
|
||||
});
|
||||
async _handle(): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
|
||||
const retData: RetData = { 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,
|
||||
};
|
||||
|
||||
if (SSNotify.type === 1) {
|
||||
retData.InvitedRequest.push(commonData);
|
||||
} else if (SSNotify.type === 7) {
|
||||
retData.join_requests.push(commonData);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
return retData;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
flag: Type.String(),
|
||||
flag: Type.Union([Type.String(), Type.Number()]),
|
||||
approve: Type.Optional(Type.Union([Type.String(), Type.Boolean()])),
|
||||
remark: Type.Optional(Type.String())
|
||||
});
|
||||
@@ -16,14 +16,13 @@ export default class SetFriendAddRequest extends OneBotAction<Payload, null> {
|
||||
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
const approve = payload.approve?.toString() !== 'false';
|
||||
await this.core.apis.FriendApi.handleFriendRequest(payload.flag, approve);
|
||||
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) {
|
||||
const data = payload.flag.split('|');
|
||||
if (data.length < 2) {
|
||||
throw new Error('Invalid flag');
|
||||
}
|
||||
const friendUid = data[0];
|
||||
await this.core.apis.FriendApi.setBuddyRemark(friendUid, payload.remark);
|
||||
await this.core.apis.FriendApi.setBuddyRemark(notify.friendUid, payload.remark);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@@ -7,15 +7,10 @@ import {
|
||||
MessageElement,
|
||||
NapCatCore,
|
||||
NTGrayTipElementSubTypeV2,
|
||||
NTMsgType,
|
||||
RawMessage,
|
||||
TipGroupElement,
|
||||
TipGroupElementType,
|
||||
} from '@/core';
|
||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||
import { OB11GroupBanEvent } from '@/onebot/event/notice/OB11GroupBanEvent';
|
||||
import { OB11GroupIncreaseEvent } from '@/onebot/event/notice/OB11GroupIncreaseEvent';
|
||||
import { OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
|
||||
import fastXmlParser from 'fast-xml-parser';
|
||||
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot/event/notice/OB11MsgEmojiLikeEvent';
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
@@ -56,9 +51,9 @@ export class OneBotGroupApi {
|
||||
if (memberUin && adminUin) {
|
||||
return new OB11GroupBanEvent(
|
||||
this.core,
|
||||
parseInt(GroupCode),
|
||||
parseInt(memberUin),
|
||||
parseInt(adminUin),
|
||||
+GroupCode,
|
||||
+memberUin,
|
||||
+adminUin,
|
||||
duration,
|
||||
subType,
|
||||
);
|
||||
@@ -66,67 +61,6 @@ export class OneBotGroupApi {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// async parseGroupIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// this.core.context.logger.logDebug('收到新人被邀请进群消息', grayTipElement);
|
||||
// const xmlElement = grayTipElement.xmlElement;
|
||||
// if (xmlElement?.content) {
|
||||
// const regex = /jp="(\d+)"/g;
|
||||
|
||||
// const matches = [];
|
||||
// let match = null;
|
||||
|
||||
// while ((match = regex.exec(xmlElement.content)) !== null) {
|
||||
// matches.push(match[1]);
|
||||
// }
|
||||
// if (matches.length === 2) {
|
||||
// const [inviter, invitee] = matches;
|
||||
// return new OB11GroupIncreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(GroupCode),
|
||||
// parseInt(invitee),
|
||||
// parseInt(inviter),
|
||||
// 'invite',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
// async parseGroupMemberIncreaseEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// const groupElement = grayTipElement?.groupElement;
|
||||
// if (!groupElement) return undefined;
|
||||
// const member = await this.core.apis.UserApi.getUserDetailInfo(groupElement.memberUid);
|
||||
// const memberUin = member?.uin;
|
||||
// const adminMember = await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid);
|
||||
// if (memberUin) {
|
||||
// const operatorUin = adminMember?.uin ?? memberUin;
|
||||
// return new OB11GroupIncreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(GroupCode),
|
||||
// parseInt(memberUin),
|
||||
// parseInt(operatorUin),
|
||||
// );
|
||||
// } else {
|
||||
// return undefined;
|
||||
// }
|
||||
// }
|
||||
|
||||
// async parseGroupKickEvent(GroupCode: string, grayTipElement: GrayTipElement) {
|
||||
// const groupElement = grayTipElement?.groupElement;
|
||||
// if (!groupElement) return undefined;
|
||||
// const adminUin = (await this.core.apis.GroupApi.getGroupMember(GroupCode, groupElement.adminUid))?.uin ?? (await this.core.apis.UserApi.getUidByUinV2(groupElement.adminUid));
|
||||
// if (adminUin) {
|
||||
// return new OB11GroupDecreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(GroupCode),
|
||||
// parseInt(this.core.selfInfo.uin),
|
||||
// parseInt(adminUin),
|
||||
// 'kick_me',
|
||||
// );
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
async parseGroupEmojiLikeEventByGrayTip(
|
||||
groupCode: string,
|
||||
grayTipElement: GrayTipElement
|
||||
@@ -164,8 +98,8 @@ export class OneBotGroupApi {
|
||||
}
|
||||
return new OB11GroupMsgEmojiLikeEvent(
|
||||
this.core,
|
||||
parseInt(groupCode),
|
||||
parseInt(senderUin),
|
||||
+groupCode,
|
||||
+senderUin,
|
||||
MessageUnique.getShortIdByMsgId(replyMsg.msgId)!,
|
||||
[{
|
||||
emoji_id: emojiId,
|
||||
@@ -177,9 +111,10 @@ export class OneBotGroupApi {
|
||||
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 oldName = member?.cardName || member?.nick || '';
|
||||
if (member && oldName !== msg.sendMemberName) {
|
||||
const newCardName = msg.sendMemberName ?? '';
|
||||
const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, member.cardName);
|
||||
const event = new OB11GroupCardEvent(this.core, parseInt(msg.peerUid), parseInt(msg.senderUin), newCardName, oldName);
|
||||
member.cardName = newCardName;
|
||||
return event;
|
||||
}
|
||||
@@ -187,31 +122,6 @@ export class OneBotGroupApi {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// async parseGroupElement(msg: RawMessage, groupElement: TipGroupElement, elementWrapper: GrayTipElement) {
|
||||
// if (groupElement.type == TipGroupElementType.KMEMBERADD) {
|
||||
// const MemberIncreaseEvent = await this.obContext.apis.GroupApi.parseGroupMemberIncreaseEvent(msg.peerUid, elementWrapper);
|
||||
// if (MemberIncreaseEvent) return MemberIncreaseEvent;
|
||||
// } else if (groupElement.type === TipGroupElementType.KSHUTUP) {
|
||||
// const BanEvent = await this.obContext.apis.GroupApi.parseGroupBanEvent(msg.peerUid, elementWrapper);
|
||||
// if (BanEvent) return BanEvent;
|
||||
// } else if (groupElement.type == TipGroupElementType.KQUITTE) {
|
||||
// this.core.apis.GroupApi.quitGroup(msg.peerUid).then();
|
||||
// try {
|
||||
// const KickEvent = await this.obContext.apis.GroupApi.parseGroupKickEvent(msg.peerUid, elementWrapper);
|
||||
// if (KickEvent) return KickEvent;
|
||||
// } catch (e) {
|
||||
// return new OB11GroupDecreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(msg.peerUid),
|
||||
// parseInt(this.core.selfInfo.uin),
|
||||
// 0,
|
||||
// 'leave',
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
async parsePaiYiPai(msg: RawMessage, jsonStr: string) {
|
||||
const json = JSON.parse(jsonStr);
|
||||
|
||||
|
@@ -15,7 +15,6 @@ import {
|
||||
RawMessage,
|
||||
SendMessageElement,
|
||||
SendTextElement,
|
||||
BaseEmojiType,
|
||||
FaceType,
|
||||
GrayTipElement,
|
||||
} from '@/core';
|
||||
@@ -24,18 +23,17 @@ import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataTyp
|
||||
import { OB11Construct } from '@/onebot/helper/data';
|
||||
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||
import { encodeCQCode } from '@/onebot/helper/cqcode';
|
||||
import { uri2local } from '@/common/file';
|
||||
import { uriToLocalFile } from '@/common/file';
|
||||
import { RequestUtil } from '@/common/request';
|
||||
import fs from 'node:fs';
|
||||
import fsPromise from 'node:fs/promises';
|
||||
import fsPromise, { constants } from 'node:fs/promises';
|
||||
import { OB11FriendAddNoticeEvent } from '@/onebot/event/notice/OB11FriendAddNoticeEvent';
|
||||
// import { decodeSysMessage } from '@/core/packet/proto/old/ProfileLike';
|
||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||
import { decodeSysMessage } from "@/core/helper/adaptDecoder";
|
||||
import { GroupChange, PushMsgBody } from "@/core/packet/transformer/proto";
|
||||
import { NapProtoMsg } from '@napneko/nap-proto-core';
|
||||
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||
import { OB11GroupDecreaseEvent, GroupDecreaseSubType } from '../event/notice/OB11GroupDecreaseEvent';
|
||||
import { GroupAdmin } from '@/core/packet/transformer/proto/message/groupAdmin';
|
||||
import { OB11GroupAdminNoticeEvent } from '../event/notice/OB11GroupAdminNoticeEvent';
|
||||
|
||||
type RawToOb11Converters = {
|
||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||
@@ -155,6 +153,17 @@ export class OneBotMsgApi {
|
||||
|
||||
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,
|
||||
@@ -452,7 +461,7 @@ export class OneBotMsgApi {
|
||||
},
|
||||
|
||||
[OB11MessageDataType.face]: async ({ data: { id } }) => {
|
||||
let parsedFaceId = parseInt(id);
|
||||
const parsedFaceId = +id;
|
||||
// 从face_config.json中获取表情名称
|
||||
const sysFaces = faceConfig.sysface;
|
||||
const face: any = sysFaces.find((systemFace) => systemFace.QSid === parsedFaceId.toString());
|
||||
@@ -460,7 +469,6 @@ export class OneBotMsgApi {
|
||||
this.core.context.logger.logError('不支持的ID', id);
|
||||
return undefined;
|
||||
}
|
||||
parsedFaceId = parseInt(parsedFaceId.toString());
|
||||
let faceType = 1;
|
||||
if (parsedFaceId >= 222) {
|
||||
faceType = 2;
|
||||
@@ -517,7 +525,7 @@ export class OneBotMsgApi {
|
||||
|
||||
let thumb = sendMsg.data.thumb;
|
||||
if (thumb) {
|
||||
const uri2LocalRes = await uri2local(this.core.NapCatTempPath, thumb);
|
||||
const uri2LocalRes = await uriToLocalFile(this.core.NapCatTempPath, thumb);
|
||||
if (uri2LocalRes.success) thumb = uri2LocalRes.path;
|
||||
}
|
||||
return await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb);
|
||||
@@ -883,49 +891,53 @@ export class OneBotMsgApi {
|
||||
if (!sendElements.length) {
|
||||
throw new Error('消息体无法解析, 请检查是否发送了不支持的消息类型');
|
||||
}
|
||||
let totalSize = 0;
|
||||
let timeout = 10000;
|
||||
try {
|
||||
for (const fileElement of sendElements) {
|
||||
if (fileElement.elementType === ElementType.PTT) {
|
||||
totalSize += fs.statSync(fileElement.pttElement.filePath).size;
|
||||
|
||||
const calculateTotalSize = async (elements: SendMessageElement[]): Promise<number> => {
|
||||
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;
|
||||
}
|
||||
if (fileElement.elementType === ElementType.FILE) {
|
||||
totalSize += fs.statSync(fileElement.fileElement.filePath).size;
|
||||
}
|
||||
if (fileElement.elementType === ElementType.VIDEO) {
|
||||
totalSize += fs.statSync(fileElement.videoElement.filePath).size;
|
||||
}
|
||||
if (fileElement.elementType === ElementType.PIC) {
|
||||
totalSize += fs.statSync(fileElement.picElement.sourcePath).size;
|
||||
}
|
||||
}
|
||||
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
|
||||
const PredictTime = totalSize / 1024 / 256 * 1000;
|
||||
if (!Number.isNaN(PredictTime)) {
|
||||
timeout += PredictTime;// 10S Basic Timeout + PredictTime( For File 512kb/s )
|
||||
}
|
||||
} catch (e) {
|
||||
});
|
||||
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);
|
||||
|
||||
const returnMsg = await this.core.apis.MsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
|
||||
if (!returnMsg) throw new Error('发送消息失败');
|
||||
|
||||
returnMsg.id = MessageUnique.createUniqueMsgId({
|
||||
chatType: peer.chatType,
|
||||
guildId: '',
|
||||
peerUid: peer.peerUid,
|
||||
}, returnMsg.msgId);
|
||||
|
||||
setTimeout(() => {
|
||||
deleteAfterSentFiles.forEach(file => {
|
||||
setTimeout(async () => {
|
||||
const deletePromises = deleteAfterSentFiles.map(async file => {
|
||||
try {
|
||||
if (fs.existsSync(file)) {
|
||||
fsPromise.unlink(file).then().catch(e => this.core.context.logger.logError('发送消息删除文件失败', e));
|
||||
if (await fsPromise.access(file, constants.W_OK).then(() => true).catch(() => false)) {
|
||||
await fsPromise.unlink(file);
|
||||
}
|
||||
} catch (error) {
|
||||
this.core.context.logger.logError('发送消息删除文件失败', (error as Error).message);
|
||||
} catch (e) {
|
||||
this.core.context.logger.logError('发送消息删除文件失败', e);
|
||||
}
|
||||
});
|
||||
await Promise.all(deletePromises);
|
||||
}, 60000);
|
||||
|
||||
return returnMsg;
|
||||
@@ -935,7 +947,7 @@ export class OneBotMsgApi {
|
||||
{ data: inputdata }: OB11MessageFileBase,
|
||||
{ deleteAfterSentFiles }: SendMessageContext,
|
||||
) {
|
||||
const realUri = inputdata.url || inputdata.file || inputdata.path || '';
|
||||
const realUri = inputdata.url ?? inputdata.file ?? inputdata.path ?? '';
|
||||
if (realUri.length === 0) {
|
||||
this.core.context.logger.logError('文件消息缺少参数', inputdata);
|
||||
throw Error('文件消息缺少参数');
|
||||
@@ -945,7 +957,7 @@ export class OneBotMsgApi {
|
||||
fileName,
|
||||
errMsg,
|
||||
success,
|
||||
} = (await uri2local(this.core.NapCatTempPath, realUri));
|
||||
} = (await uriToLocalFile(this.core.NapCatTempPath, realUri));
|
||||
|
||||
if (!success) {
|
||||
this.core.context.logger.logError('文件下载失败', errMsg);
|
||||
@@ -970,11 +982,10 @@ export class OneBotMsgApi {
|
||||
}
|
||||
|
||||
async parseSysMessage(msg: number[]) {
|
||||
// Todo Refactor
|
||||
const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
|
||||
if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) {
|
||||
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||
console.log(JSON.stringify(groupChange));
|
||||
this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString()).then().catch();
|
||||
return new OB11GroupIncreaseEvent(
|
||||
this.core,
|
||||
groupChange.groupUin,
|
||||
@@ -984,6 +995,14 @@ export class OneBotMsgApi {
|
||||
);
|
||||
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
|
||||
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||
if (groupChange.memberUid === this.core.selfInfo.uid) {
|
||||
setTimeout(() => {
|
||||
this.core.apis.GroupApi.groupMemberCache.delete(groupChange.groupUin.toString());
|
||||
}, 5000);
|
||||
// 自己被踢了 5S后回收
|
||||
} else {
|
||||
this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString()).then().catch();
|
||||
}
|
||||
return new OB11GroupDecreaseEvent(
|
||||
this.core,
|
||||
groupChange.groupUin,
|
||||
@@ -991,31 +1010,26 @@ export class OneBotMsgApi {
|
||||
groupChange.operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(groupChange.operatorUid) : 0,
|
||||
this.groupChangDecreseType2String(groupChange.decreaseType),
|
||||
);
|
||||
} else if (SysMessage.contentHead.type == 44 && SysMessage.body?.msgContent) {
|
||||
const groupAmin = new NapProtoMsg(GroupAdmin).decode(SysMessage.body.msgContent);
|
||||
this.core.apis.GroupApi.refreshGroupMemberCache(groupAmin.groupUin.toString()).then().catch();
|
||||
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 == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) {
|
||||
return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent);
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
BuddyReqType,
|
||||
ChatType,
|
||||
DataSource,
|
||||
NTGroupMemberRole,
|
||||
GroupNotifyMsgStatus,
|
||||
GroupNotifyMsgType,
|
||||
InstanceContext,
|
||||
@@ -41,8 +39,6 @@ import { OB11InputStatusEvent } from '@/onebot/event/notice/OB11InputStatusEvent
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
import { proxiedListenerOf } from '@/common/proxy-handler';
|
||||
import { OB11FriendRequestEvent } from '@/onebot/event/request/OB11FriendRequest';
|
||||
import { OB11GroupAdminNoticeEvent } from '@/onebot/event/notice/OB11GroupAdminNoticeEvent';
|
||||
// import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
|
||||
import { OB11GroupRequestEvent } from '@/onebot/event/request/OB11GroupRequest';
|
||||
import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent';
|
||||
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
||||
@@ -337,7 +333,7 @@ export class NapCatOneBot11Adapter {
|
||||
this.core,
|
||||
+requesterUin,
|
||||
req.extWords,
|
||||
req.friendUid + '|' + req.reqTime
|
||||
req.reqTime
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -355,7 +351,6 @@ export class NapCatOneBot11Adapter {
|
||||
const groupListener = new NodeIKernelGroupListener();
|
||||
|
||||
groupListener.onGroupNotifiesUpdated = async (_, notifies) => {
|
||||
//console.log('ob11 onGroupNotifiesUpdated', notifies[0]);
|
||||
await this.core.apis.GroupApi.clearGroupNotifiesUnreadCount(false);
|
||||
if (
|
||||
![
|
||||
@@ -370,177 +365,78 @@ export class NapCatOneBot11Adapter {
|
||||
if (notifyTime < this.bootTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type;
|
||||
const flag = notify.seq;
|
||||
this.context.logger.logDebug('收到群通知', notify);
|
||||
|
||||
if (
|
||||
[
|
||||
GroupNotifyMsgType.SET_ADMIN,
|
||||
GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_CANCELED,
|
||||
GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_ADMIN,
|
||||
].includes(notify.type)
|
||||
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
|
||||
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
||||
) {
|
||||
const member1 = await this.core.apis.GroupApi.getGroupMember(
|
||||
notify.group.groupCode,
|
||||
notify.user1.uid
|
||||
);
|
||||
this.context.logger.logDebug('有管理员变动通知');
|
||||
// refreshGroupMembers(notify.group.groupCode).then();
|
||||
this.context.logger.logDebug('开始获取变动的管理员');
|
||||
if (member1) {
|
||||
this.context.logger.logDebug('变动管理员获取成功');
|
||||
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
|
||||
this.core,
|
||||
parseInt(notify.group.groupCode),
|
||||
parseInt(member1.uin),
|
||||
[
|
||||
GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_CANCELED,
|
||||
GroupNotifyMsgType.CANCEL_ADMIN_NOTIFY_ADMIN,
|
||||
].includes(notify.type)
|
||||
? 'unset'
|
||||
: 'set'
|
||||
);
|
||||
this.networkManager
|
||||
.emitEvent(groupAdminNoticeEvent)
|
||||
.catch((e) =>
|
||||
this.context.logger.logError('处理群管理员变动失败', e)
|
||||
);
|
||||
} else {
|
||||
this.context.logger.logDebug(
|
||||
'获取群通知的成员信息失败',
|
||||
notify,
|
||||
this.core.apis.GroupApi.getGroup(notify.group.groupCode)
|
||||
);
|
||||
}
|
||||
} else
|
||||
// if (
|
||||
// notify.type == GroupNotifyMsgType.MEMBER_LEAVE_NOTIFY_ADMIN ||
|
||||
// notify.type == GroupNotifyMsgType.KICK_MEMBER_NOTIFY_ADMIN
|
||||
// ) {
|
||||
// this.context.logger.logDebug('有成员退出通知', notify);
|
||||
// const member1Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
||||
// let operatorId = member1Uin;
|
||||
// let subType: GroupDecreaseSubType = 'leave';
|
||||
// if (notify.user2.uid) {
|
||||
// // 是被踢的
|
||||
// const member2Uin = await this.core.apis.UserApi.getUinByUidV2(notify.user2.uid);
|
||||
// if (member2Uin) {
|
||||
// operatorId = member2Uin;
|
||||
// }
|
||||
// subType = 'kick';
|
||||
// }
|
||||
// const groupDecreaseEvent = new OB11GroupDecreaseEvent(
|
||||
// this.core,
|
||||
// parseInt(notify.group.groupCode),
|
||||
// parseInt(member1Uin),
|
||||
// parseInt(operatorId),
|
||||
// subType
|
||||
// );
|
||||
// this.networkManager
|
||||
// .emitEvent(groupDecreaseEvent)
|
||||
// .catch((e) =>
|
||||
// this.context.logger.logError('处理群成员退出失败', e)
|
||||
// );
|
||||
// // notify.status == 1 表示未处理 2表示处理完成
|
||||
// } else
|
||||
if (
|
||||
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
|
||||
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
||||
) {
|
||||
this.context.logger.logDebug('有加群请求');
|
||||
try {
|
||||
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
||||
if (isNaN(parseInt(requestUin))) {
|
||||
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
|
||||
}
|
||||
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
|
||||
);
|
||||
this.context.logger.logDebug('有加群请求');
|
||||
try {
|
||||
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
|
||||
if (isNaN(parseInt(requestUin))) {
|
||||
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
|
||||
}
|
||||
} else if (
|
||||
notify.type == GroupNotifyMsgType.INVITED_BY_MEMBER &&
|
||||
notify.status == GroupNotifyMsgStatus.KUNHANDLE
|
||||
) {
|
||||
this.context.logger.logDebug(`收到邀请我加群通知:${notify}`);
|
||||
const groupInviteEvent = new OB11GroupRequestEvent(
|
||||
const groupRequestEvent = new OB11GroupRequestEvent(
|
||||
this.core,
|
||||
parseInt(notify.group.groupCode),
|
||||
parseInt(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,
|
||||
parseInt(notify.group.groupCode),
|
||||
parseInt(await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid)),
|
||||
parseInt(requestUin),
|
||||
'add',
|
||||
notify.postscript,
|
||||
flag
|
||||
);
|
||||
this.networkManager
|
||||
.emitEvent(groupInviteEvent)
|
||||
.emitEvent(groupRequestEvent)
|
||||
.catch((e) =>
|
||||
this.context.logger.logError('处理邀请本人加群失败', 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
groupListener.onMemberInfoChange = async (groupCode, dataSource, members) => {
|
||||
//this.context.logger.logDebug('收到群成员信息变动通知', groupCode, changeType);
|
||||
if (dataSource === DataSource.LOCAL) {
|
||||
const existMembers = this.core.apis.GroupApi.groupMemberCache.get(groupCode);
|
||||
if (!existMembers) return;
|
||||
members.forEach((member) => {
|
||||
const existMember = existMembers.get(member.uid);
|
||||
if (!existMember?.isChangeRole) return;
|
||||
this.context.logger.logDebug('变动管理员获取成功');
|
||||
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
|
||||
this.core,
|
||||
parseInt(groupCode),
|
||||
parseInt(member.uin),
|
||||
member.role === NTGroupMemberRole.KADMIN ? 'set' : 'unset'
|
||||
);
|
||||
this.networkManager
|
||||
.emitEvent(groupAdminNoticeEvent)
|
||||
.catch((e) =>
|
||||
this.context.logger.logError('处理群管理员变动失败', e)
|
||||
);
|
||||
existMember.isChangeRole = false;
|
||||
this.context.logger.logDebug('群管理员变动处理完毕');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.context.session
|
||||
.getGroupService()
|
||||
.addKernelGroupListener(proxiedListenerOf(groupListener, this.context.logger));
|
||||
@@ -625,21 +521,27 @@ export class NapCatOneBot11Adapter {
|
||||
// 群名片修改事件解析 任何都该判断
|
||||
if (message.senderUin && message.senderUin !== '0') {
|
||||
const cardChangedEvent = await this.apis.GroupApi.parseCardChangedEvent(message);
|
||||
cardChangedEvent && await this.networkManager.emitEvent(cardChangedEvent);
|
||||
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);
|
||||
uploadGroupFileEvent && await this.networkManager.emitEvent(uploadGroupFileEvent);
|
||||
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);
|
||||
event && await this.networkManager.emitEvent(event);
|
||||
if (event) {
|
||||
await this.networkManager.emitEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -654,7 +556,10 @@ export class NapCatOneBot11Adapter {
|
||||
const grayTipElement = message.elements[0].grayTipElement;
|
||||
if (grayTipElement) {
|
||||
const event = await this.apis.MsgApi.parsePrivateMsgEvent(message, grayTipElement);
|
||||
event && await this.networkManager.emitEvent(event);
|
||||
if (event) {
|
||||
await this.networkManager.emitEvent(event);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -674,6 +579,8 @@ export class NapCatOneBot11Adapter {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -684,7 +591,7 @@ export class NapCatOneBot11Adapter {
|
||||
private async emitGroupRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) {
|
||||
const operatorUid = element.grayTipElement?.revokeElement.operatorUid;
|
||||
if (!operatorUid) return undefined;
|
||||
const operatorId = message.senderUin ?? await this.core.apis.UserApi.getUinByUidV2(operatorUid);
|
||||
const operatorId = await this.core.apis.UserApi.getUinByUidV2(operatorUid);
|
||||
return new OB11GroupRecallNoticeEvent(
|
||||
this.core,
|
||||
+message.peerUin,
|
||||
|
@@ -11,6 +11,17 @@ export interface OB11User {
|
||||
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;
|
||||
}
|
||||
|
||||
export enum OB11UserSex {
|
||||
male = 'male', // 男性
|
||||
|
@@ -71,6 +71,14 @@ export enum OB11MessageDataType {
|
||||
location = 'location'
|
||||
}
|
||||
|
||||
export interface OB11MessagePoke {
|
||||
type: OB11MessageDataType.poke;
|
||||
data: {
|
||||
type: string;
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 商城表情消息接口定义
|
||||
export interface OB11MessageMFace {
|
||||
type: OB11MessageDataType.mface;
|
||||
@@ -247,7 +255,7 @@ export type OB11MessageData =
|
||||
OB11MessageAt | OB11MessageReply |
|
||||
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
|
||||
OB11MessageNode | OB11MessageIdMusic | OB11MessageCustomMusic | OB11MessageJson |
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward | OB11MessageContext;
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward | OB11MessageContext | OB11MessagePoke;
|
||||
|
||||
// 发送消息接口定义
|
||||
export interface OB11PostSendMsg {
|
||||
|
@@ -9,13 +9,13 @@ export const LogHandler: RequestHandler = async (req, res) => {
|
||||
if (filename.includes('..')) {
|
||||
return sendError(res, 'ID不合法');
|
||||
}
|
||||
const logContent = WebUiConfigWrapper.GetLogContent(filename);
|
||||
const logContent = await WebUiConfigWrapper.GetLogContent(filename);
|
||||
return sendSuccess(res, logContent);
|
||||
};
|
||||
|
||||
// 日志列表
|
||||
export const LogListHandler: RequestHandler = async (_, res) => {
|
||||
const logList = WebUiConfigWrapper.GetLogsList();
|
||||
const logList = await WebUiConfigWrapper.GetLogsList();
|
||||
return sendSuccess(res, logList);
|
||||
};
|
||||
// 实时日志(SSE)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { webUiPathWrapper } from '@/webui';
|
||||
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
||||
import fs, { constants } from 'node:fs/promises';
|
||||
import * as net from 'node:net';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
@@ -90,18 +90,18 @@ export class WebUiConfigWrapper {
|
||||
try {
|
||||
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
writeFileSync(configPath, JSON.stringify(defaultconfig, null, 4));
|
||||
if (!await fs.access(configPath, constants.R_OK | constants.W_OK).then(() => true).catch(() => false)) {
|
||||
await fs.writeFile(configPath, JSON.stringify(defaultconfig, null, 4));
|
||||
}
|
||||
|
||||
const fileContent = readFileSync(configPath, 'utf-8');
|
||||
const fileContent = await fs.readFile(configPath, 'utf-8');
|
||||
// 更新配置字段后新增字段可能会缺失,同步一下
|
||||
const parsedConfig = this.applyDefaults(JSON.parse(fileContent) as Partial<WebUiConfigType>, defaultconfig);
|
||||
|
||||
if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix;
|
||||
if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1);
|
||||
// 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里
|
||||
writeFileSync(configPath, JSON.stringify(parsedConfig, null, 4));
|
||||
await fs.writeFile(configPath, JSON.stringify(parsedConfig, null, 4));
|
||||
// 不希望回写的配置放后面
|
||||
|
||||
// 查询主机地址是否可用
|
||||
@@ -137,19 +137,19 @@ export class WebUiConfigWrapper {
|
||||
return resolve(webUiPathWrapper.logsPath);
|
||||
}
|
||||
// 获取日志列表
|
||||
public static GetLogsList(): string[] {
|
||||
if (existsSync(webUiPathWrapper.logsPath)) {
|
||||
return readdirSync(webUiPathWrapper.logsPath)
|
||||
public static async GetLogsList(): Promise<string[]> {
|
||||
if (await fs.access(webUiPathWrapper.logsPath, constants.F_OK).then(() => true).catch(() => false)) {
|
||||
return (await fs.readdir(webUiPathWrapper.logsPath))
|
||||
.filter((file) => file.endsWith('.log'))
|
||||
.map((file) => file.replace('.log', ''));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
// 获取指定日志文件内容
|
||||
public static GetLogContent(filename: string): string {
|
||||
public static async GetLogContent(filename: string): Promise<string> {
|
||||
const logPath = resolve(webUiPathWrapper.logsPath, `${filename}.log`);
|
||||
if (existsSync(logPath)) {
|
||||
return readFileSync(logPath, 'utf-8');
|
||||
if (await fs.access(logPath, constants.R_OK).then(() => true).catch(() => false)) {
|
||||
return await fs.readFile(logPath, 'utf-8');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
Reference in New Issue
Block a user