mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7330a05c78 | ||
![]() |
a39c932868 | ||
![]() |
a2c24c9197 | ||
![]() |
5c3efc681f | ||
![]() |
e70d2bd708 | ||
![]() |
cf75a961fb | ||
![]() |
159f317071 | ||
![]() |
713eef592a | ||
![]() |
cf03ad8fd9 | ||
![]() |
0c0b27901a | ||
![]() |
137fe3c8f2 | ||
![]() |
d96174076a | ||
![]() |
6d5662d96e | ||
![]() |
57abd47d99 | ||
![]() |
5092b3d791 | ||
![]() |
649409d1be | ||
![]() |
8f549d896a | ||
![]() |
a1359ddbb5 | ||
![]() |
304a0dda3e | ||
![]() |
fff9c4a4d8 | ||
![]() |
2c76102fc4 | ||
![]() |
f576cd9417 | ||
![]() |
9cfd224b74 | ||
![]() |
c12f8de8b4 | ||
![]() |
ed9a7c52e2 | ||
![]() |
38fcaaa28b | ||
![]() |
5317a1c1a9 | ||
![]() |
4bc5933ea2 | ||
![]() |
6a6bd33fe5 | ||
![]() |
8256942a3d | ||
![]() |
697632eee8 | ||
![]() |
6bbf5b254d | ||
![]() |
5831898c4a | ||
![]() |
2cc413bec1 | ||
![]() |
0af36e89d9 | ||
![]() |
b2c0f5d2e5 | ||
![]() |
80b74c7da9 | ||
![]() |
f14f13b158 | ||
![]() |
9dda00b6fa | ||
![]() |
a29debb738 | ||
![]() |
b990fc43df | ||
![]() |
915e9552ee | ||
![]() |
c522e0a386 | ||
![]() |
c9cc08a9ba | ||
![]() |
66e1b1662f | ||
![]() |
9372e83bd8 | ||
![]() |
b38a240dbb | ||
![]() |
76b9506395 | ||
![]() |
f1cf636aa2 | ||
![]() |
312dcd0e13 | ||
![]() |
42c2419613 | ||
![]() |
8f7f748e82 | ||
![]() |
7ad3bad1be | ||
![]() |
5cd682e69f | ||
![]() |
5d57780e84 | ||
![]() |
f399955204 | ||
![]() |
770652fe6b | ||
![]() |
9ed5fa8c67 | ||
![]() |
5a4ad29727 | ||
![]() |
1eda3f2e33 | ||
![]() |
95cb95ef96 | ||
![]() |
4e7c96634c | ||
![]() |
58587b8aea | ||
![]() |
3fbf6239db | ||
![]() |
faec53d497 | ||
![]() |
482dcc534e | ||
![]() |
854f61dda6 | ||
![]() |
fca38713a1 | ||
![]() |
5dd3bade53 | ||
![]() |
665360f48d | ||
![]() |
65719cb56a | ||
![]() |
bdb76d4639 | ||
![]() |
15634412ef | ||
![]() |
bbcf9649fa | ||
![]() |
e845d7314e | ||
![]() |
6927b1c94f | ||
![]() |
a09c6acd0d | ||
![]() |
0963650ccb | ||
![]() |
380688b353 | ||
![]() |
ad5466bff8 | ||
![]() |
a83652bf3f | ||
![]() |
c632de314d | ||
![]() |
259c9610d5 | ||
![]() |
e9936c5524 | ||
![]() |
3f60440e72 | ||
![]() |
71a15f92fb | ||
![]() |
32bc0dd820 | ||
![]() |
20d1ac9d01 | ||
![]() |
18baf89e0e | ||
![]() |
3a1d1f2e59 | ||
![]() |
e9a048721d | ||
![]() |
68f0c7ff1a | ||
![]() |
2875fe94ea | ||
![]() |
1870427c0f | ||
![]() |
636568fd30 | ||
![]() |
bbc2391bf8 | ||
![]() |
401684542a | ||
![]() |
870edb2513 | ||
![]() |
7ad09169ea | ||
![]() |
c1a0f8915b | ||
![]() |
dcdab8e5a1 | ||
![]() |
eb3278fdab | ||
![]() |
34db3af48d | ||
![]() |
198da960dd | ||
![]() |
cb83918fb3 | ||
![]() |
f59a48540b | ||
![]() |
ccf9c1a5fb | ||
![]() |
ba6a85142a | ||
![]() |
440baccd2a | ||
![]() |
690c073328 | ||
![]() |
3f0730ed4f | ||
![]() |
01d5663bc8 | ||
![]() |
49806cd00e | ||
![]() |
935b0848e5 | ||
![]() |
5ca20a89a2 | ||
![]() |
e89a2266ec |
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -150,3 +150,15 @@ jobs:
|
||||
NapCat.Shell.zip
|
||||
NapCat.Framework.Windows.Once.zip
|
||||
draft: true
|
||||
|
||||
build-docker:
|
||||
needs: release-napcat
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dispatch Docker Build
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.NAPCAT_BUILD }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/NapNeko/NapCat-Docker/actions/workflows/docker-publish.yml/dispatches \
|
||||
-d '{"ref": "main"}'
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
# Develop
|
||||
node_modules/
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
out/
|
||||
dist/
|
||||
|
@@ -53,6 +53,8 @@ _Modern protocol-side framework implemented based on NTQQ._
|
||||
|
||||
+ [AstrBot](https://github.com/AstrBotDevs/AstrBot) 是完美适配本项目的LLM Bot框架 在此推荐一下
|
||||
|
||||
+ [MaiBot](https://github.com/MaiM-with-u/MaiBot) 一只赛博群友 麦麦 Bot框架 在此推荐一下
|
||||
|
||||
+ 不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||
|
||||
---
|
||||
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "4.7.58",
|
||||
"version": "4.8.93",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
15995
napcat.webui/package-lock.json
generated
Normal file
15995
napcat.webui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,7 +92,9 @@ const MusicInsert = () => {
|
||||
className="w-96"
|
||||
fullWidth
|
||||
selectedKey={mode}
|
||||
onSelectionChange={setMode}
|
||||
onSelectionChange={(key) => {
|
||||
if (key !== null) setMode(key)
|
||||
}}
|
||||
>
|
||||
<Tab title="主流平台" key="default" className="flex flex-col gap-2">
|
||||
<Select
|
||||
|
@@ -26,7 +26,7 @@ const itemVariants = {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
y: 0,
|
||||
transition: { type: 'spring', stiffness: 300, damping: 20 }
|
||||
transition: { type: 'spring' as const, stiffness: 300, damping: 20 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,9 +24,7 @@ const oneBotHttpApiGroup = {
|
||||
},
|
||||
'/get_group_system_msg': {
|
||||
description: '获取群系统消息',
|
||||
request: z.object({
|
||||
group_id: z.union([z.string(), z.number()]).describe('群号')
|
||||
}),
|
||||
request: z.object({}),
|
||||
response: baseResponseSchema.extend({
|
||||
data: z.object({
|
||||
InvitedRequest: z
|
||||
@@ -37,6 +35,7 @@ const oneBotHttpApiGroup = {
|
||||
invitor_uin: z.string().describe('邀请人 QQ 号'),
|
||||
invitor_nick: z.string().describe('邀请人昵称'),
|
||||
group_id: z.string().describe('群号'),
|
||||
message: z.string().describe('入群回答'),
|
||||
group_name: z.string().describe('群名称'),
|
||||
checked: z.boolean().describe('是否已处理'),
|
||||
actor: z.string().describe('处理人 QQ 号')
|
||||
@@ -50,6 +49,7 @@ const oneBotHttpApiGroup = {
|
||||
requester_uin: z.string().describe('请求人 QQ 号'),
|
||||
requester_nick: z.string().describe('请求人昵称'),
|
||||
group_id: z.string().describe('群号'),
|
||||
message: z.string().describe('入群回答'),
|
||||
group_name: z.string().describe('群名称'),
|
||||
checked: z.boolean().describe('是否已处理'),
|
||||
actor: z.string().describe('处理人 QQ 号')
|
||||
@@ -604,7 +604,7 @@ const oneBotHttpApiGroup = {
|
||||
response: baseResponseSchema.extend({
|
||||
data: z
|
||||
.object({
|
||||
group_id: z.string().describe('群号'),
|
||||
group_id: z.number().describe('群号'),
|
||||
current_talkative: z
|
||||
.object({
|
||||
user_id: z.number().describe('QQ 号'),
|
||||
|
@@ -56,9 +56,9 @@ export default function TerminalPage() {
|
||||
|
||||
setTabs((prev) => [...prev, newTab])
|
||||
setSelectedTab(id)
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to create terminal:', error)
|
||||
toast.error('创建终端失败')
|
||||
toast.error((error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
|
8301
package-lock.json
generated
Normal file
8301
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "4.7.58",
|
||||
"version": "4.8.93",
|
||||
"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",
|
||||
@@ -18,14 +18,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@eslint/compat": "^1.2.2",
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@eslint/js": "^9.30.1",
|
||||
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@napneko/nap-proto-core": "^0.0.4",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-typescript": "^12.1.4",
|
||||
"@sinclair/typebox": "^0.34.9",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
@@ -42,19 +42,18 @@
|
||||
"async-mutex": "^0.5.0",
|
||||
"commander": "^13.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"esbuild": "0.25.0",
|
||||
"esbuild": "0.25.5",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-import-resolver-typescript": "^4.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fast-xml-parser": "^4.3.6",
|
||||
"file-type": "^20.0.0",
|
||||
"file-type": "^21.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"image-size": "^1.1.1",
|
||||
"json5": "^2.2.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer": "^2.0.1",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"typescript-eslint": "^8.35.1",
|
||||
"vite": "^6.0.1",
|
||||
"vite-plugin-cp": "^6.0.0",
|
||||
"vite-tsconfig-paths": "^5.1.0",
|
||||
|
@@ -10,10 +10,14 @@ import { LogWrapper } from './log';
|
||||
|
||||
const downloadOri = "https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip"
|
||||
const urls = [
|
||||
"https://github.moeyy.xyz/" + downloadOri,
|
||||
"https://ghp.ci/" + downloadOri,
|
||||
"https://gh.api.99988866.xyz/" + downloadOri,
|
||||
"https://gh.api.99988866.xyz/" + downloadOri,
|
||||
"https://j.1win.ggff.net/" + downloadOri,
|
||||
"https://git.yylx.win/" + downloadOri,
|
||||
"https://ghfile.geekertao.top/" + downloadOri,
|
||||
"https://gh-proxy.net/" + downloadOri,
|
||||
"https://ghm.078465.xyz/" + downloadOri,
|
||||
"https://gitproxy.127731.xyz/" + downloadOri,
|
||||
"https://jiashu.1win.eu.org/" + downloadOri,
|
||||
"https://github.tbedu.top/" + downloadOri,
|
||||
downloadOri
|
||||
];
|
||||
|
||||
|
@@ -4,10 +4,10 @@ import { execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import type { VideoInfo } from './video';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import imageSize from 'image-size';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { platform } from 'node:os';
|
||||
import { LogWrapper } from './log';
|
||||
import { imageSizeFallBack } from '@/image-size';
|
||||
const currentPath = dirname(fileURLToPath(import.meta.url));
|
||||
const execFileAsync = promisify(execFile);
|
||||
const getFFmpegPath = (tool: string): string => {
|
||||
@@ -157,7 +157,7 @@ export class FFmpegService {
|
||||
try {
|
||||
await this.extractThumbnail(videoPath, thumbnailPath);
|
||||
// 获取图片尺寸
|
||||
const dimensions = imageSize(thumbnailPath);
|
||||
const dimensions = await imageSizeFallBack(thumbnailPath);
|
||||
|
||||
return {
|
||||
format: fileType?.ext ?? 'mp4',
|
||||
|
@@ -13,11 +13,15 @@ export class NapCatPathWrapper {
|
||||
constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) {
|
||||
this.binaryPath = mainPath;
|
||||
let writePath: string;
|
||||
if (os.platform() === 'darwin') {
|
||||
|
||||
if (process.env['NAPCAT_WORKDIR']) {
|
||||
writePath = process.env['NAPCAT_WORKDIR'];
|
||||
} else if (os.platform() === 'darwin') {
|
||||
writePath = path.join(os.homedir(), 'Library', 'Application Support', 'QQ', 'NapCat');
|
||||
} else {
|
||||
writePath = this.binaryPath;
|
||||
}
|
||||
|
||||
this.logsPath = path.join(writePath, 'logs');
|
||||
this.configPath = path.join(writePath, 'config');
|
||||
this.cachePath = path.join(writePath, 'cache');
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '4.7.58';
|
||||
export const napCatVersion = '4.8.93';
|
||||
|
@@ -17,8 +17,6 @@ import fs from 'fs';
|
||||
import fsPromises from 'fs/promises';
|
||||
import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import imageSize from 'image-size';
|
||||
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
|
||||
import { RkeyManager } from '@/core/helper/rkey';
|
||||
import { calculateFileMD5 } from '@/common/file';
|
||||
import pathLib from 'node:path';
|
||||
@@ -30,6 +28,7 @@ import { FFmpegService } from '@/common/ffmpeg';
|
||||
import { rkeyDataType } from '../types/file';
|
||||
import { NapProtoMsg } from '@napneko/nap-proto-core';
|
||||
import { FileId } from '../packet/transformer/proto/misc/fileid';
|
||||
import { imageSizeFallBack } from '@/image-size';
|
||||
|
||||
export class NTQQFileApi {
|
||||
context: InstanceContext;
|
||||
@@ -209,7 +208,7 @@ export class NTQQFileApi {
|
||||
if (fileSize === 0) {
|
||||
throw new Error('文件异常,大小为0');
|
||||
}
|
||||
const imageSize = await this.core.apis.FileApi.getImageSize(picPath);
|
||||
const imageSize = await imageSizeFallBack(picPath);
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
return {
|
||||
elementType: ElementType.PIC,
|
||||
@@ -437,19 +436,6 @@ export class NTQQFileApi {
|
||||
return completeRetData.filePath;
|
||||
}
|
||||
|
||||
async getImageSize(filePath: string): Promise<ISizeCalculationResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
imageSize(filePath, (err: Error | null, dimensions) => {
|
||||
if (err) {
|
||||
reject(new Error(err.message));
|
||||
} else if (!dimensions) {
|
||||
reject(new Error('获取图片尺寸失败'));
|
||||
} else {
|
||||
resolve(dimensions);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
|
||||
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
|
||||
|
@@ -10,11 +10,14 @@ import {
|
||||
GroupNotify,
|
||||
GroupInfoSource,
|
||||
ShutUpGroupMember,
|
||||
Peer,
|
||||
ChatType,
|
||||
} from '@/core';
|
||||
import { isNumeric, solveAsyncProblem } from '@/common/helper';
|
||||
import { LimitedHashTable } from '@/common/message-unique';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { CancelableTask, TaskExecutor } from '@/common/cancel-task';
|
||||
import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data';
|
||||
|
||||
export class NTQQGroupApi {
|
||||
context: InstanceContext;
|
||||
@@ -47,6 +50,22 @@ export class NTQQGroupApi {
|
||||
this.initCache().then().catch(e => this.context.logger.logError(e));
|
||||
}
|
||||
|
||||
async createGrayTip(groupCode: string, tip: string) {
|
||||
return this.context.session.getMsgService().addLocalJsonGrayTipMsg(
|
||||
{
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
peerUid: groupCode,
|
||||
} as Peer,
|
||||
{
|
||||
busiId: 2201,
|
||||
jsonStr: JSON.stringify({ "align": "center", "items": [{ "txt": tip, "type": "nor" }] }),
|
||||
recentAbstract: tip,
|
||||
isServer: false
|
||||
},
|
||||
true,
|
||||
true
|
||||
)
|
||||
}
|
||||
async initCache() {
|
||||
for (const group of await this.getGroups(true)) {
|
||||
this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e));
|
||||
@@ -95,6 +114,58 @@ export class NTQQGroupApi {
|
||||
return this.context.session.getGroupService().setHeader(groupCode, filePath);
|
||||
}
|
||||
|
||||
// 0 0 无需管理员审核
|
||||
// 0 2 需要管理员审核
|
||||
// 1 2 禁止Bot入群( 最好只传一个1 ?)
|
||||
async setGroupRobotAddOption(groupCode: string, robotMemberSwitch?: number, robotMemberExamine?: number) {
|
||||
let extInfo = createGroupExtInfo(groupCode);
|
||||
let groupExtFilter = createGroupExtFilter();
|
||||
if (robotMemberSwitch !== undefined) {
|
||||
extInfo.extInfo.inviteRobotMemberSwitch = robotMemberSwitch;
|
||||
groupExtFilter.inviteRobotMemberSwitch = 1;
|
||||
}
|
||||
if (robotMemberExamine !== undefined) {
|
||||
extInfo.extInfo.inviteRobotMemberExamine = robotMemberExamine;
|
||||
groupExtFilter.inviteRobotMemberExamine = 1;
|
||||
}
|
||||
return this.context.session.getGroupService().modifyGroupExtInfoV2(extInfo, groupExtFilter);
|
||||
}
|
||||
|
||||
async setGroupAddOption(groupCode: string, option: {
|
||||
addOption: number;
|
||||
groupQuestion?: string;
|
||||
groupAnswer?: string;
|
||||
}) {
|
||||
let param = createGroupDetailInfoV2Param(groupCode);
|
||||
// 设置要修改的目标
|
||||
param.filter.addOption = 1;
|
||||
if (option.addOption == 4 || option.addOption == 5) {
|
||||
// 4 问题进入答案 5 问题管理员批准
|
||||
param.filter.groupQuestion = 1;
|
||||
param.filter.groupAnswer = option.addOption == 4 ? 1 : 0;
|
||||
param.modifyInfo.groupQuestion = option.groupQuestion || '';
|
||||
param.modifyInfo.groupAnswer = option.addOption == 4 ? option.groupAnswer || '' : '';
|
||||
}
|
||||
param.modifyInfo.addOption = option.addOption;
|
||||
return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0);
|
||||
}
|
||||
|
||||
async setGroupSearch(groupCode: string, option: {
|
||||
noCodeFingerOpenFlag?: number;
|
||||
noFingerOpenFlag?: number;
|
||||
}) {
|
||||
let param = createGroupDetailInfoV2Param(groupCode);
|
||||
if (option.noCodeFingerOpenFlag) {
|
||||
param.filter.noCodeFingerOpenFlag = 1;
|
||||
param.modifyInfo.noCodeFingerOpenFlag = option.noCodeFingerOpenFlag;
|
||||
}
|
||||
if (option.noFingerOpenFlag) {
|
||||
param.filter.noFingerOpenFlag = 1;
|
||||
param.modifyInfo.noFingerOpenFlag = option.noFingerOpenFlag;
|
||||
}
|
||||
return this.context.session.getGroupService().modifyGroupDetailInfoV2(param, 0);
|
||||
}
|
||||
|
||||
async getGroups(forced: boolean = false) {
|
||||
const [, , groupList] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelGroupService/getGroupList',
|
||||
|
@@ -264,7 +264,7 @@ export class NTQQWebApi {
|
||||
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
let HonorInfo = {
|
||||
group_id: groupCode,
|
||||
group_id: Number(groupCode),
|
||||
current_talkative: {},
|
||||
talkative_list: [],
|
||||
performer_list: [],
|
||||
|
245
src/core/data/group.ts
Normal file
245
src/core/data/group.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { GroupDetailInfoV2Param, GroupExtInfo, GroupExtFilter } from "../types";
|
||||
|
||||
export function createGroupDetailInfoV2Param(group_code: string): GroupDetailInfoV2Param {
|
||||
return {
|
||||
groupCode: group_code,
|
||||
filter:
|
||||
{
|
||||
noCodeFingerOpenFlag: 0,
|
||||
noFingerOpenFlag: 0,
|
||||
groupName: 0,
|
||||
classExt: 0,
|
||||
classText: 0,
|
||||
fingerMemo: 0,
|
||||
richFingerMemo: 0,
|
||||
tagRecord: 0,
|
||||
groupGeoInfo:
|
||||
{
|
||||
ownerUid: 0,
|
||||
setTime: 0,
|
||||
cityId: 0,
|
||||
longitude: 0,
|
||||
latitude: 0,
|
||||
geoContent: 0,
|
||||
poiId: 0
|
||||
},
|
||||
groupExtAdminNum: 0,
|
||||
flag: 0,
|
||||
groupMemo: 0,
|
||||
groupAioSkinUrl: 0,
|
||||
groupBoardSkinUrl: 0,
|
||||
groupCoverSkinUrl: 0,
|
||||
groupGrade: 0,
|
||||
activeMemberNum: 0,
|
||||
certificationType: 0,
|
||||
certificationText: 0,
|
||||
groupNewGuideLines:
|
||||
{
|
||||
enabled: 0,
|
||||
content: 0
|
||||
},
|
||||
groupFace: 0,
|
||||
addOption: 0,
|
||||
shutUpTime: 0,
|
||||
groupTypeFlag: 0,
|
||||
appPrivilegeFlag: 0,
|
||||
appPrivilegeMask: 0,
|
||||
groupExtOnly:
|
||||
{
|
||||
tribeId: 0,
|
||||
moneyForAddGroup: 0
|
||||
}, groupSecLevel: 0,
|
||||
groupSecLevelInfo: 0,
|
||||
subscriptionUin: 0,
|
||||
subscriptionUid: "",
|
||||
allowMemberInvite: 0,
|
||||
groupQuestion: 0,
|
||||
groupAnswer: 0,
|
||||
groupFlagExt3: 0,
|
||||
groupFlagExt3Mask: 0,
|
||||
groupOpenAppid: 0,
|
||||
rootId: 0,
|
||||
msgLimitFrequency: 0,
|
||||
hlGuildAppid: 0,
|
||||
hlGuildSubType: 0,
|
||||
hlGuildOrgId: 0,
|
||||
groupFlagExt4: 0,
|
||||
groupFlagExt4Mask: 0,
|
||||
groupSchoolInfo: {
|
||||
location: 0,
|
||||
grade: 0,
|
||||
school: 0
|
||||
},
|
||||
groupCardPrefix:
|
||||
{
|
||||
introduction: 0,
|
||||
rptPrefix: 0
|
||||
}, allianceId: 0,
|
||||
groupFlagPro1: 0,
|
||||
groupFlagPro1Mask: 0
|
||||
},
|
||||
modifyInfo: {
|
||||
noCodeFingerOpenFlag: 0,
|
||||
noFingerOpenFlag: 0,
|
||||
groupName: "",
|
||||
classExt: 0,
|
||||
classText: "",
|
||||
fingerMemo: "",
|
||||
richFingerMemo: "",
|
||||
tagRecord: [],
|
||||
groupGeoInfo: {
|
||||
ownerUid: "",
|
||||
SetTime: 0,
|
||||
CityId: 0,
|
||||
Longitude: "",
|
||||
Latitude: "",
|
||||
GeoContent: "",
|
||||
poiId: ""
|
||||
},
|
||||
groupExtAdminNum: 0,
|
||||
flag: 0,
|
||||
groupMemo: "",
|
||||
groupAioSkinUrl: "",
|
||||
groupBoardSkinUrl: "",
|
||||
groupCoverSkinUrl: "",
|
||||
groupGrade: 0,
|
||||
activeMemberNum: 0,
|
||||
certificationType: 0,
|
||||
certificationText: "",
|
||||
groupNewGuideLines: {
|
||||
enabled: false,
|
||||
content: ""
|
||||
}, groupFace: 0,
|
||||
addOption: 0,
|
||||
shutUpTime: 0,
|
||||
groupTypeFlag: 0,
|
||||
appPrivilegeFlag: 0,
|
||||
appPrivilegeMask: 0,
|
||||
groupExtOnly: {
|
||||
tribeId: 0,
|
||||
moneyForAddGroup: 0
|
||||
},
|
||||
groupSecLevel: 0,
|
||||
groupSecLevelInfo: 0,
|
||||
subscriptionUin: "",
|
||||
subscriptionUid: "",
|
||||
allowMemberInvite: 0,
|
||||
groupQuestion: "",
|
||||
groupAnswer: "",
|
||||
groupFlagExt3: 0,
|
||||
groupFlagExt3Mask: 0,
|
||||
groupOpenAppid: 0,
|
||||
rootId: "",
|
||||
msgLimitFrequency: 0,
|
||||
hlGuildAppid: 0,
|
||||
hlGuildSubType: 0,
|
||||
hlGuildOrgId: 0,
|
||||
groupFlagExt4: 0,
|
||||
groupFlagExt4Mask: 0,
|
||||
groupSchoolInfo: {
|
||||
location: "",
|
||||
grade: 0,
|
||||
school: ""
|
||||
},
|
||||
groupCardPrefix:
|
||||
{
|
||||
introduction: "",
|
||||
rptPrefix: []
|
||||
},
|
||||
allianceId: "",
|
||||
groupFlagPro1: 0,
|
||||
groupFlagPro1Mask: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
export function createGroupExtInfo(group_code: string): GroupExtInfo {
|
||||
return {
|
||||
groupCode: group_code,
|
||||
resultCode: 0,
|
||||
extInfo: {
|
||||
groupInfoExtSeq: 0,
|
||||
reserve: 0,
|
||||
luckyWordId: '',
|
||||
lightCharNum: 0,
|
||||
luckyWord: '',
|
||||
starId: 0,
|
||||
essentialMsgSwitch: 0,
|
||||
todoSeq: 0,
|
||||
blacklistExpireTime: 0,
|
||||
isLimitGroupRtc: 0,
|
||||
companyId: 0,
|
||||
hasGroupCustomPortrait: 0,
|
||||
bindGuildId: '',
|
||||
groupOwnerId: {
|
||||
memberUin: '',
|
||||
memberUid: '',
|
||||
memberQid: '',
|
||||
},
|
||||
essentialMsgPrivilege: 0,
|
||||
msgEventSeq: '',
|
||||
inviteRobotSwitch: 0,
|
||||
gangUpId: '',
|
||||
qqMusicMedalSwitch: 0,
|
||||
showPlayTogetherSwitch: 0,
|
||||
groupFlagPro1: '',
|
||||
groupBindGuildIds: {
|
||||
guildIds: [],
|
||||
},
|
||||
viewedMsgDisappearTime: '',
|
||||
groupExtFlameData: {
|
||||
switchState: 0,
|
||||
state: 0,
|
||||
dayNums: [],
|
||||
version: 0,
|
||||
updateTime: '',
|
||||
isDisplayDayNum: false,
|
||||
},
|
||||
groupBindGuildSwitch: 0,
|
||||
groupAioBindGuildId: '',
|
||||
groupExcludeGuildIds: {
|
||||
guildIds: [],
|
||||
},
|
||||
fullGroupExpansionSwitch: 0,
|
||||
fullGroupExpansionSeq: '',
|
||||
inviteRobotMemberSwitch: 0,
|
||||
inviteRobotMemberExamine: 0,
|
||||
groupSquareSwitch: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
export function createGroupExtFilter(): GroupExtFilter {
|
||||
return {
|
||||
groupInfoExtSeq: 0,
|
||||
reserve: 0,
|
||||
luckyWordId: 0,
|
||||
lightCharNum: 0,
|
||||
luckyWord: 0,
|
||||
starId: 0,
|
||||
essentialMsgSwitch: 0,
|
||||
todoSeq: 0,
|
||||
blacklistExpireTime: 0,
|
||||
isLimitGroupRtc: 0,
|
||||
companyId: 0,
|
||||
hasGroupCustomPortrait: 0,
|
||||
bindGuildId: 0,
|
||||
groupOwnerId: 0,
|
||||
essentialMsgPrivilege: 0,
|
||||
msgEventSeq: 0,
|
||||
inviteRobotSwitch: 0,
|
||||
gangUpId: 0,
|
||||
qqMusicMedalSwitch: 0,
|
||||
showPlayTogetherSwitch: 0,
|
||||
groupFlagPro1: 0,
|
||||
groupBindGuildIds: 0,
|
||||
viewedMsgDisappearTime: 0,
|
||||
groupExtFlameData: 0,
|
||||
groupBindGuildSwitch: 0,
|
||||
groupAioBindGuildId: 0,
|
||||
groupExcludeGuildIds: 0,
|
||||
fullGroupExpansionSwitch: 0,
|
||||
fullGroupExpansionSeq: 0,
|
||||
inviteRobotMemberSwitch: 0,
|
||||
inviteRobotMemberExamine: 0,
|
||||
groupSquareSwitch: 0,
|
||||
}
|
||||
};
|
1
src/core/data/index.ts
Normal file
1
src/core/data/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./group";
|
52
src/core/external/appid.json
vendored
52
src/core/external/appid.json
vendored
@@ -286,5 +286,57 @@
|
||||
"9.9.19-34958": {
|
||||
"appid": 537290742,
|
||||
"qua": "V1_WIN_NQ_9.9.19_34958_GW_B"
|
||||
},
|
||||
"3.2.17-35184": {
|
||||
"appid": 537291084,
|
||||
"qua": "V1_LNX_NQ_3.2.17_35184_GW_B"
|
||||
},
|
||||
"9.9.19-35184": {
|
||||
"appid": 537291048,
|
||||
"qua": "V1_WIN_NQ_9.9.19_35184_GW_B"
|
||||
},
|
||||
"3.2.17-35341": {
|
||||
"appid": 537291383,
|
||||
"qua": "V1_LNX_NQ_3.2.17_35341_GW_B"
|
||||
},
|
||||
"9.9.19-35341": {
|
||||
"appid": 537291347,
|
||||
"qua": "V1_WIN_NQ_9.9.19_35341_GW_B"
|
||||
},
|
||||
"9.9.19-35469": {
|
||||
"appid": 537291398,
|
||||
"qua": "V1_WIN_NQ_9.9.19_35469_GW_B"
|
||||
},
|
||||
"3.2.18-35951": {
|
||||
"appid": 537296013,
|
||||
"qua": "V1_LNX_NQ_3.2.18_35951_GW_B"
|
||||
},
|
||||
"9.9.20-35951": {
|
||||
"appid": 537295977,
|
||||
"qua": "V1_WIN_NQ_9.9.20_35951_GW_B"
|
||||
},
|
||||
"3.2.18-36580": {
|
||||
"appid": 537298509,
|
||||
"qua": "V1_LNX_NQ_3.2.18_36580_GW_B"
|
||||
},
|
||||
"9.9.20-36580": {
|
||||
"appid": 537298473,
|
||||
"qua": "V1_WIN_NQ_9.9.20_36580_GW_B"
|
||||
},
|
||||
"9.9.20-37012": {
|
||||
"appid": 537304071,
|
||||
"qua": "V1_WIN_NQ_9.9.20_37012_GW_B"
|
||||
},
|
||||
"3.2.18-37012": {
|
||||
"appid": 537304107,
|
||||
"qua": "V1_LNX_NQ_3.2.18_37012_GW_B"
|
||||
},
|
||||
"3.2.18-37051": {
|
||||
"appid": 537304158,
|
||||
"qua": "V1_LNX_NQ_3.2.18_37051_GW_B"
|
||||
},
|
||||
"9.9.20-37051": {
|
||||
"appid": 537304122,
|
||||
"qua": "V1_WIN_NQ_9.9.20_37051_GW_B"
|
||||
}
|
||||
}
|
72
src/core/external/offset.json
vendored
72
src/core/external/offset.json
vendored
@@ -366,5 +366,77 @@
|
||||
"9.9.19-34958-x64": {
|
||||
"send": "3BDD8D0",
|
||||
"recv": "3BE20D0"
|
||||
},
|
||||
"3.2.17-35184-x64": {
|
||||
"send": "AE0DDE0",
|
||||
"recv": "AE11800"
|
||||
},
|
||||
"3.2.17-35184-arm64": {
|
||||
"send": "7776028",
|
||||
"recv": "7779958"
|
||||
},
|
||||
"9.9.19-35184-x64": {
|
||||
"send": "3BE5A10",
|
||||
"recv": "3BEA210"
|
||||
},
|
||||
"9.9.19-35341-x64": {
|
||||
"send": "3BF1D50",
|
||||
"recv": "3BF6550"
|
||||
},
|
||||
"9.9.19-35469-x64": {
|
||||
"send": "3BF1D50",
|
||||
"recv": "3BF6550"
|
||||
},
|
||||
"3.2.17-35341-x64": {
|
||||
"send": "AE2F700",
|
||||
"recv": "AE33120"
|
||||
},
|
||||
"3.2.17-35341-arm64": {
|
||||
"send": "778D840",
|
||||
"recv": "7791170"
|
||||
},
|
||||
"9.9.20-35951-x64": {
|
||||
"send": "3034BAC",
|
||||
"recv": "3038354"
|
||||
},
|
||||
"3.2.18-35951-x64": {
|
||||
"send": "AFBBB00",
|
||||
"recv": "AFBF520"
|
||||
},
|
||||
"9.9.20-36580-x64": {
|
||||
"send": "30824B8",
|
||||
"recv": "3085C5C"
|
||||
},
|
||||
"3.2.18-36580-x64": {
|
||||
"send": "B0853E0",
|
||||
"recv": "B088E60"
|
||||
},
|
||||
"3.2.18-36580-arm64": {
|
||||
"send": "793DAC8",
|
||||
"recv": "7941458"
|
||||
},
|
||||
"3.2.18-37012-x64": {
|
||||
"send": "B20F960",
|
||||
"recv": "B2133E0"
|
||||
},
|
||||
"3.2.18-37012-arm64": {
|
||||
"send": "7A19E00",
|
||||
"recv": "7A1D790"
|
||||
},
|
||||
"9.9.20-37012-x64": {
|
||||
"send": "30CC958",
|
||||
"recv": "30D00FC"
|
||||
},
|
||||
"3.2.18-37051-x64": {
|
||||
"send": "B20F960",
|
||||
"recv": "B2133E0"
|
||||
},
|
||||
"3.2.18-37051-arm64": {
|
||||
"send": "7A19E00",
|
||||
"recv": "7A1D790"
|
||||
},
|
||||
"9.9.20-37051-x64": {
|
||||
"send": "30CC958",
|
||||
"recv": "30D00FC"
|
||||
}
|
||||
}
|
@@ -40,7 +40,8 @@ export class NativePacketClient extends IPacketClient {
|
||||
|
||||
async init(_pid: number, recv: string, send: string): Promise<void> {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||
const isNewQQ = this.napcore.basicInfo.requireMinNTQQBuild("36580");
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + (isNewQQ ? '.new' : '') + '.node');
|
||||
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
|
||||
|
||||
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, _uin: string, cmd: string, seq: number, hex_data: string) => {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { NapCatCore } from '@/core';
|
||||
|
||||
export interface NapCoreCompatBasicInfo {
|
||||
readonly requireMinNTQQBuild: (buildVer: string) => boolean;
|
||||
readonly uin: number;
|
||||
readonly uid: string;
|
||||
readonly uin2uid: (uin: number) => Promise<string>;
|
||||
@@ -21,6 +22,7 @@ export class NapCoreContext {
|
||||
|
||||
get basicInfo() {
|
||||
return {
|
||||
requireMinNTQQBuild: (buildVer: string) => this.core.context.basicInfoWrapper.requireMinNTQQBuild(buildVer),
|
||||
uin: +this.core.selfInfo.uin,
|
||||
uid: this.core.selfInfo.uid,
|
||||
uin2uid: (uin: number) => this.core.apis.UserApi.getUidByUinV2(String(uin)).then(res => res ?? ''),
|
||||
|
@@ -30,13 +30,8 @@ export class PacketOperationContext {
|
||||
return await this.context.client.sendOidbPacket(pkt, rsp);
|
||||
}
|
||||
|
||||
async GroupPoke(groupUin: number, uin: number) {
|
||||
const req = trans.SendPoke.build(uin, groupUin);
|
||||
await this.context.client.sendOidbPacket(req);
|
||||
}
|
||||
|
||||
async FriendPoke(uin: number) {
|
||||
const req = trans.SendPoke.build(uin);
|
||||
async SendPoke(is_group: boolean, peer: number, target?: number) {
|
||||
const req = trans.SendPoke.build(is_group, peer, target ?? peer);
|
||||
await this.context.client.sendOidbPacket(req);
|
||||
}
|
||||
|
||||
|
@@ -8,13 +8,13 @@ class SendPoke extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
|
||||
super();
|
||||
}
|
||||
|
||||
build(peer: number, group?: number): OidbPacket {
|
||||
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode({
|
||||
uin: peer,
|
||||
groupUin: group,
|
||||
friendUin: group ?? peer,
|
||||
ext: 0
|
||||
});
|
||||
build(is_group: boolean, peer: number, target: number): OidbPacket {
|
||||
const payload = {
|
||||
uin: target,
|
||||
ext: 0,
|
||||
...(is_group ? { groupUin: peer } : { friendUin: peer })
|
||||
};
|
||||
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode(payload);
|
||||
return OidbBase.build(0xED3, 1, data);
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ class OidbBase extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
|
||||
super();
|
||||
}
|
||||
|
||||
build(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): OidbPacket {
|
||||
build(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, _isLafter: boolean = false): OidbPacket {
|
||||
const data = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).encode({
|
||||
command: cmd,
|
||||
subCommand: subCmd,
|
||||
|
@@ -8,10 +8,22 @@ import {
|
||||
GroupNotifyMsgType,
|
||||
NTGroupRequestOperateTypes,
|
||||
KickMemberV2Req,
|
||||
GroupDetailInfoV2Param,
|
||||
GroupExtInfo,
|
||||
GroupExtFilter,
|
||||
} from '@/core/types';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
|
||||
export interface NodeIKernelGroupService {
|
||||
|
||||
modifyGroupExtInfoV2(groupExtInfo: GroupExtInfo, groupExtFilter: GroupExtFilter): Promise<GeneralCallResult &
|
||||
{
|
||||
result: {
|
||||
groupCode: string,
|
||||
result: number
|
||||
}
|
||||
}>;
|
||||
|
||||
// --->
|
||||
// 待启用 For Next Version 3.2.0
|
||||
// isTroopMember ? 0 : 111
|
||||
@@ -169,6 +181,9 @@ export interface NodeIKernelGroupService {
|
||||
|
||||
modifyGroupDetailInfo(groupCode: string, arg: unknown): void;
|
||||
|
||||
// 第二个参数在大多数情况为0 设置群成员权限 例如上传群文件权限和群成员付费/加入邀请加入时为8
|
||||
modifyGroupDetailInfoV2(param: GroupDetailInfoV2Param, arg: number): Promise<GeneralCallResult>;
|
||||
|
||||
setGroupMsgMask(groupCode: string, arg: unknown): void;
|
||||
|
||||
changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void;
|
||||
|
@@ -58,6 +58,7 @@ export interface GrayTipRovokeElement {
|
||||
operatorUid: string;
|
||||
operatorNick: string;
|
||||
operatorRemark: string;
|
||||
isSelfOperate: boolean; // 是否是自己撤回的
|
||||
operatorMemRemark?: string;
|
||||
wording: string; // 自定义的撤回提示语
|
||||
}
|
||||
|
@@ -1,4 +1,97 @@
|
||||
import { QQLevel, NTSex } from './user';
|
||||
export interface GroupExtInfo {
|
||||
groupCode: string;
|
||||
resultCode: number;
|
||||
extInfo: EXTInfo;
|
||||
}
|
||||
export interface GroupExtFilter {
|
||||
groupInfoExtSeq: number;
|
||||
reserve: number;
|
||||
luckyWordId: number;
|
||||
lightCharNum: number;
|
||||
luckyWord: number;
|
||||
starId: number;
|
||||
essentialMsgSwitch: number;
|
||||
todoSeq: number;
|
||||
blacklistExpireTime: number;
|
||||
isLimitGroupRtc: number;
|
||||
companyId: number;
|
||||
hasGroupCustomPortrait: number;
|
||||
bindGuildId: number;
|
||||
groupOwnerId: number;
|
||||
essentialMsgPrivilege: number;
|
||||
msgEventSeq: number;
|
||||
inviteRobotSwitch: number;
|
||||
gangUpId: number;
|
||||
qqMusicMedalSwitch: number;
|
||||
showPlayTogetherSwitch: number;
|
||||
groupFlagPro1: number;
|
||||
groupBindGuildIds: number;
|
||||
viewedMsgDisappearTime: number;
|
||||
groupExtFlameData: number;
|
||||
groupBindGuildSwitch: number;
|
||||
groupAioBindGuildId: number;
|
||||
groupExcludeGuildIds: number;
|
||||
fullGroupExpansionSwitch: number;
|
||||
fullGroupExpansionSeq: number;
|
||||
inviteRobotMemberSwitch: number;
|
||||
inviteRobotMemberExamine: number;
|
||||
groupSquareSwitch: number;
|
||||
};
|
||||
|
||||
export interface EXTInfo {
|
||||
groupInfoExtSeq: number;
|
||||
reserve: number;
|
||||
luckyWordId: string;
|
||||
lightCharNum: number;
|
||||
luckyWord: string;
|
||||
starId: number;
|
||||
essentialMsgSwitch: number;
|
||||
todoSeq: number;
|
||||
blacklistExpireTime: number;
|
||||
isLimitGroupRtc: number;
|
||||
companyId: number;
|
||||
hasGroupCustomPortrait: number;
|
||||
bindGuildId: string;
|
||||
groupOwnerId: GroupOwnerID;
|
||||
essentialMsgPrivilege: number;
|
||||
msgEventSeq: string;
|
||||
inviteRobotSwitch: number;
|
||||
gangUpId: string;
|
||||
qqMusicMedalSwitch: number;
|
||||
showPlayTogetherSwitch: number;
|
||||
groupFlagPro1: string;
|
||||
groupBindGuildIds: GroupGuildIDS;
|
||||
viewedMsgDisappearTime: string;
|
||||
groupExtFlameData: GroupEXTFlameData;
|
||||
groupBindGuildSwitch: number;
|
||||
groupAioBindGuildId: string;
|
||||
groupExcludeGuildIds: GroupGuildIDS;
|
||||
fullGroupExpansionSwitch: number;
|
||||
fullGroupExpansionSeq: string;
|
||||
inviteRobotMemberSwitch: number;
|
||||
inviteRobotMemberExamine: number;
|
||||
groupSquareSwitch: number;
|
||||
}
|
||||
|
||||
export interface GroupGuildIDS {
|
||||
guildIds: any[];
|
||||
}
|
||||
|
||||
export interface GroupEXTFlameData {
|
||||
switchState: number;
|
||||
state: number;
|
||||
dayNums: any[];
|
||||
version: number;
|
||||
updateTime: string;
|
||||
isDisplayDayNum: boolean;
|
||||
}
|
||||
|
||||
export interface GroupOwnerID {
|
||||
memberUin: string;
|
||||
memberUid: string;
|
||||
memberQid: string;
|
||||
}
|
||||
|
||||
export interface KickMemberInfo {
|
||||
optFlag: number;
|
||||
@@ -7,6 +100,185 @@ export interface KickMemberInfo {
|
||||
optBytesMsg: string;
|
||||
}
|
||||
|
||||
|
||||
export interface GroupDetailInfoV2Param {
|
||||
groupCode: string;
|
||||
filter: Filter;
|
||||
modifyInfo: ModifyInfo;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
noCodeFingerOpenFlag: number;
|
||||
noFingerOpenFlag: number;
|
||||
groupName: number;
|
||||
classExt: number;
|
||||
classText: number;
|
||||
fingerMemo: number;
|
||||
richFingerMemo: number;
|
||||
tagRecord: number;
|
||||
groupGeoInfo: FilterGroupGeoInfo;
|
||||
groupExtAdminNum: number;
|
||||
flag: number;
|
||||
groupMemo: number;
|
||||
groupAioSkinUrl: number;
|
||||
groupBoardSkinUrl: number;
|
||||
groupCoverSkinUrl: number;
|
||||
groupGrade: number;
|
||||
activeMemberNum: number;
|
||||
certificationType: number;
|
||||
certificationText: number;
|
||||
groupNewGuideLines: FilterGroupNewGuideLines;
|
||||
groupFace: number;
|
||||
addOption: number;
|
||||
shutUpTime: number;
|
||||
groupTypeFlag: number;
|
||||
appPrivilegeFlag: number;
|
||||
appPrivilegeMask: number;
|
||||
groupExtOnly: GroupEXTOnly;
|
||||
groupSecLevel: number;
|
||||
groupSecLevelInfo: number;
|
||||
subscriptionUin: number;
|
||||
subscriptionUid: string;
|
||||
allowMemberInvite: number;
|
||||
groupQuestion: number;
|
||||
groupAnswer: number;
|
||||
groupFlagExt3: number;
|
||||
groupFlagExt3Mask: number;
|
||||
groupOpenAppid: number;
|
||||
rootId: number;
|
||||
msgLimitFrequency: number;
|
||||
hlGuildAppid: number;
|
||||
hlGuildSubType: number;
|
||||
hlGuildOrgId: number;
|
||||
groupFlagExt4: number;
|
||||
groupFlagExt4Mask: number;
|
||||
groupSchoolInfo: FilterGroupSchoolInfo;
|
||||
groupCardPrefix: FilterGroupCardPrefix;
|
||||
allianceId: number;
|
||||
groupFlagPro1: number;
|
||||
groupFlagPro1Mask: number;
|
||||
}
|
||||
|
||||
export interface FilterGroupCardPrefix {
|
||||
introduction: number;
|
||||
rptPrefix: number;
|
||||
}
|
||||
|
||||
export interface GroupEXTOnly {
|
||||
tribeId: number;
|
||||
moneyForAddGroup: number;
|
||||
}
|
||||
|
||||
export interface FilterGroupGeoInfo {
|
||||
ownerUid: number;
|
||||
setTime: number;
|
||||
cityId: number;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
geoContent: number;
|
||||
poiId: number;
|
||||
}
|
||||
|
||||
export interface FilterGroupNewGuideLines {
|
||||
enabled: number;
|
||||
content: number;
|
||||
}
|
||||
|
||||
export interface FilterGroupSchoolInfo {
|
||||
location: number;
|
||||
grade: number;
|
||||
school: number;
|
||||
}
|
||||
|
||||
export interface ModifyInfo {
|
||||
noCodeFingerOpenFlag: number;
|
||||
noFingerOpenFlag: number;
|
||||
groupName: string;
|
||||
classExt: number;
|
||||
classText: string;
|
||||
fingerMemo: string;
|
||||
richFingerMemo: string;
|
||||
tagRecord: any[];
|
||||
groupGeoInfo: ModifyInfoGroupGeoInfo;
|
||||
groupExtAdminNum: number;
|
||||
flag: number;
|
||||
groupMemo: string;
|
||||
groupAioSkinUrl: string;
|
||||
groupBoardSkinUrl: string;
|
||||
groupCoverSkinUrl: string;
|
||||
groupGrade: number;
|
||||
activeMemberNum: number;
|
||||
certificationType: number;
|
||||
certificationText: string;
|
||||
groupNewGuideLines: ModifyInfoGroupNewGuideLines;
|
||||
groupFace: number;
|
||||
addOption: number;// 0 空设置 1 任何人都可以进入 2 需要管理员批准 3 不允许任何人入群 4 问题进入答案 5 问题管理员批准
|
||||
shutUpTime: number;
|
||||
groupTypeFlag: number;
|
||||
appPrivilegeFlag: number;
|
||||
// 需要管理员审核
|
||||
// 0000 0000 0000 0000 0000 0000 0000
|
||||
// 无需审核入群
|
||||
// 0000 0001 0000 0000 0000 0000 0000
|
||||
// 成员数100内无审核
|
||||
// 0100 0000 0000 0000 0000 0000 0000
|
||||
// 禁用 群成员邀请好友
|
||||
// 0100 0000 0000 0000 0000 0000 0000
|
||||
|
||||
appPrivilegeMask: number;
|
||||
// 0110 0001 0000 0000 0000 0000 0000
|
||||
// 101711872
|
||||
groupExtOnly: GroupEXTOnly;
|
||||
groupSecLevel: number;
|
||||
groupSecLevelInfo: number;
|
||||
subscriptionUin: string;
|
||||
subscriptionUid: string;
|
||||
allowMemberInvite: number;
|
||||
groupQuestion: string;
|
||||
groupAnswer: string;
|
||||
groupFlagExt3: number;
|
||||
groupFlagExt3Mask: number;
|
||||
groupOpenAppid: number;
|
||||
rootId: string;
|
||||
msgLimitFrequency: number;
|
||||
hlGuildAppid: number;
|
||||
hlGuildSubType: number;
|
||||
hlGuildOrgId: number;
|
||||
groupFlagExt4: number;
|
||||
groupFlagExt4Mask: number;
|
||||
groupSchoolInfo: ModifyInfoGroupSchoolInfo;
|
||||
groupCardPrefix: ModifyInfoGroupCardPrefix;
|
||||
allianceId: string;
|
||||
groupFlagPro1: number;
|
||||
groupFlagPro1Mask: number;
|
||||
}
|
||||
|
||||
export interface ModifyInfoGroupCardPrefix {
|
||||
introduction: string;
|
||||
rptPrefix: any[];
|
||||
}
|
||||
|
||||
export interface ModifyInfoGroupGeoInfo {
|
||||
ownerUid: string;
|
||||
SetTime: number;
|
||||
CityId: number;
|
||||
Longitude: string;
|
||||
Latitude: string;
|
||||
GeoContent: string;
|
||||
poiId: string;
|
||||
}
|
||||
|
||||
export interface ModifyInfoGroupNewGuideLines {
|
||||
enabled: boolean;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ModifyInfoGroupSchoolInfo {
|
||||
location: string;
|
||||
grade: number;
|
||||
school: string;
|
||||
}
|
||||
|
||||
// 获取群详细信息的来源类型
|
||||
export enum GroupInfoSource {
|
||||
KUNSPECIFIED,
|
||||
|
@@ -48,6 +48,12 @@ export async function NCoreInitFramework(
|
||||
});
|
||||
}
|
||||
//直到登录成功后,执行下一步
|
||||
// const selfInfo = {
|
||||
// uid: 'u_FUSS0_x06S_9Tf4na_WpUg',
|
||||
// uin: '3684714082',
|
||||
// nick: '',
|
||||
// online: true
|
||||
// }
|
||||
const selfInfo = await new Promise<SelfInfo>((resolveSelfInfo) => {
|
||||
const loginListener = new NodeIKernelLoginListener();
|
||||
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
|
||||
|
26
src/framework/nativeLoader.cjs
Normal file
26
src/framework/nativeLoader.cjs
Normal file
@@ -0,0 +1,26 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function initializeNapCat(session, loginService, registerCallback) {
|
||||
//const logFile = path.join(currentPath, 'napcat.log');
|
||||
|
||||
console.log('[NapCat] [Info] 开始初始化NapCat');
|
||||
|
||||
//fs.writeFileSync(logFile, '', { flag: 'w' });
|
||||
|
||||
//fs.writeFileSync(logFile, '[NapCat] [Info] NapCat 初始化成功\n', { flag: 'a' });
|
||||
|
||||
try {
|
||||
const currentPath = path.dirname(__filename);
|
||||
const { NCoreInitFramework } = await import('file://' + path.join(currentPath, './napcat.mjs'));
|
||||
await NCoreInitFramework(session, loginService, (callback) => { registerCallback(callback) });
|
||||
|
||||
} catch (error) {
|
||||
console.log('[NapCat] [Error] 初始化NapCat', error);
|
||||
//fs.writeFileSync(logFile, `[NapCat] [Error] 初始化NapCat失败: ${error.message}\n`, { flag: 'a' });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initializeNapCat: initializeNapCat
|
||||
};
|
426
src/image-size/index.ts
Normal file
426
src/image-size/index.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
import * as fs from 'fs';
|
||||
import { ReadStream } from 'fs';
|
||||
|
||||
export interface ImageSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export enum ImageType {
|
||||
JPEG = 'jpeg',
|
||||
PNG = 'png',
|
||||
BMP = 'bmp',
|
||||
GIF = 'gif',
|
||||
WEBP = 'webp',
|
||||
UNKNOWN = 'unknown',
|
||||
}
|
||||
|
||||
interface ImageParser {
|
||||
readonly type: ImageType;
|
||||
canParse(buffer: Buffer): boolean;
|
||||
parseSize(stream: ReadStream): Promise<ImageSize | undefined>;
|
||||
}
|
||||
|
||||
// 魔术匹配
|
||||
function matchMagic(buffer: Buffer, magic: number[], offset = 0): boolean {
|
||||
if (buffer.length < offset + magic.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < magic.length; i++) {
|
||||
if (buffer[offset + i] !== magic[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// PNG解析器
|
||||
class PngParser implements ImageParser {
|
||||
readonly type = ImageType.PNG;
|
||||
// PNG 魔术头:89 50 4E 47 0D 0A 1A 0A
|
||||
private readonly PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
|
||||
canParse(buffer: Buffer): boolean {
|
||||
return matchMagic(buffer, this.PNG_SIGNATURE);
|
||||
}
|
||||
|
||||
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.once('error', reject);
|
||||
stream.once('readable', () => {
|
||||
const buf = stream.read(24) as Buffer;
|
||||
if (!buf || buf.length < 24) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
if (this.canParse(buf)) {
|
||||
const width = buf.readUInt32BE(16);
|
||||
const height = buf.readUInt32BE(20);
|
||||
resolve({ width, height });
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// JPEG解析器
|
||||
class JpegParser implements ImageParser {
|
||||
readonly type = ImageType.JPEG;
|
||||
// JPEG 魔术头:FF D8
|
||||
private readonly JPEG_SIGNATURE = [0xFF, 0xD8];
|
||||
|
||||
// JPEG标记常量
|
||||
private readonly SOF_MARKERS = {
|
||||
SOF0: 0xC0, // 基线DCT
|
||||
SOF1: 0xC1, // 扩展顺序DCT
|
||||
SOF2: 0xC2, // 渐进式DCT
|
||||
SOF3: 0xC3, // 无损
|
||||
} as const;
|
||||
|
||||
// 非SOF标记
|
||||
private readonly NON_SOF_MARKERS: number[] = [
|
||||
0xC4, // DHT
|
||||
0xC8, // JPEG扩展
|
||||
0xCC, // DAC
|
||||
] as const;
|
||||
|
||||
canParse(buffer: Buffer): boolean {
|
||||
return matchMagic(buffer, this.JPEG_SIGNATURE);
|
||||
}
|
||||
|
||||
isSOFMarker(marker: number): boolean {
|
||||
return (
|
||||
marker === this.SOF_MARKERS.SOF0 ||
|
||||
marker === this.SOF_MARKERS.SOF1 ||
|
||||
marker === this.SOF_MARKERS.SOF2 ||
|
||||
marker === this.SOF_MARKERS.SOF3
|
||||
);
|
||||
}
|
||||
|
||||
isNonSOFMarker(marker: number): boolean {
|
||||
return this.NON_SOF_MARKERS.includes(marker);
|
||||
}
|
||||
|
||||
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
|
||||
return new Promise<ImageSize | undefined>((resolve, reject) => {
|
||||
const BUFFER_SIZE = 1024; // 读取块大小,可以根据需要调整
|
||||
let buffer = Buffer.alloc(0);
|
||||
let offset = 0;
|
||||
let found = false;
|
||||
|
||||
// 处理错误
|
||||
stream.on('error', (err) => {
|
||||
stream.destroy();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// 处理数据块
|
||||
stream.on('data', (chunk: Buffer | string) => {
|
||||
// 追加新数据到缓冲区
|
||||
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
buffer = Buffer.concat([buffer.subarray(offset), chunkBuffer]);
|
||||
offset = 0;
|
||||
|
||||
// 保持缓冲区在合理大小内,只保留最后的部分用于跨块匹配
|
||||
const bufferSize = buffer.length;
|
||||
const MIN_REQUIRED_BYTES = 10; // SOF段最低字节数
|
||||
|
||||
// 从JPEG头部后开始扫描
|
||||
while (offset < bufferSize - MIN_REQUIRED_BYTES) {
|
||||
// 寻找FF标记
|
||||
if (buffer[offset] === 0xFF && buffer[offset + 1]! >= 0xC0 && buffer[offset + 1]! <= 0xCF) {
|
||||
const marker = buffer[offset + 1];
|
||||
if (!marker) {
|
||||
break;
|
||||
}
|
||||
// 跳过非SOF标记
|
||||
if (this.isNonSOFMarker(marker)) {
|
||||
offset += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理SOF标记 (包含尺寸信息)
|
||||
if (this.isSOFMarker(marker)) {
|
||||
// 确保缓冲区中有足够数据读取尺寸
|
||||
if (offset + 9 < bufferSize) {
|
||||
// 解析尺寸: FF XX YY YY PP HH HH WW WW ...
|
||||
// XX = 标记, YY YY = 段长度, PP = 精度, HH HH = 高, WW WW = 宽
|
||||
const height = buffer.readUInt16BE(offset + 5);
|
||||
const width = buffer.readUInt16BE(offset + 7);
|
||||
|
||||
found = true;
|
||||
stream.destroy();
|
||||
resolve({ width, height });
|
||||
return;
|
||||
} else {
|
||||
// 如果缓冲区内数据不够,保留当前位置等待更多数据
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
// 缓冲区管理: 如果处理了许多数据但没找到标记,
|
||||
// 保留最后N字节用于跨块匹配,丢弃之前的数据
|
||||
if (offset > BUFFER_SIZE) {
|
||||
const KEEP_BYTES = 20; // 保留足够数据以处理跨块边界的情况
|
||||
if (offset > KEEP_BYTES) {
|
||||
buffer = buffer.subarray(offset - KEEP_BYTES);
|
||||
offset = KEEP_BYTES;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 处理流结束
|
||||
stream.on('end', () => {
|
||||
if (!found) {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// BMP解析器
|
||||
class BmpParser implements ImageParser {
|
||||
readonly type = ImageType.BMP;
|
||||
// BMP 魔术头:42 4D (BM)
|
||||
private readonly BMP_SIGNATURE = [0x42, 0x4D];
|
||||
|
||||
canParse(buffer: Buffer): boolean {
|
||||
return matchMagic(buffer, this.BMP_SIGNATURE);
|
||||
}
|
||||
|
||||
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.once('error', reject);
|
||||
stream.once('readable', () => {
|
||||
const buf = stream.read(26) as Buffer;
|
||||
if (!buf || buf.length < 26) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
if (this.canParse(buf)) {
|
||||
const width = buf.readUInt32LE(18);
|
||||
const height = buf.readUInt32LE(22);
|
||||
resolve({ width, height });
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// GIF解析器
|
||||
class GifParser implements ImageParser {
|
||||
readonly type = ImageType.GIF;
|
||||
// GIF87a 魔术头:47 49 46 38 37 61
|
||||
private readonly GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];
|
||||
// GIF89a 魔术头:47 49 46 38 39 61
|
||||
private readonly GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
|
||||
|
||||
canParse(buffer: Buffer): boolean {
|
||||
return (
|
||||
matchMagic(buffer, this.GIF87A_SIGNATURE) ||
|
||||
matchMagic(buffer, this.GIF89A_SIGNATURE)
|
||||
);
|
||||
}
|
||||
|
||||
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.once('error', reject);
|
||||
stream.once('readable', () => {
|
||||
const buf = stream.read(10) as Buffer;
|
||||
if (!buf || buf.length < 10) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
if (this.canParse(buf)) {
|
||||
const width = buf.readUInt16LE(6);
|
||||
const height = buf.readUInt16LE(8);
|
||||
resolve({ width, height });
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// WEBP解析器 - 完整支持VP8, VP8L, VP8X格式
|
||||
class WebpParser implements ImageParser {
|
||||
readonly type = ImageType.WEBP;
|
||||
// WEBP RIFF 头:52 49 46 46 (RIFF)
|
||||
private readonly RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46];
|
||||
// WEBP 魔术头:57 45 42 50 (WEBP)
|
||||
private readonly WEBP_SIGNATURE = [0x57, 0x45, 0x42, 0x50];
|
||||
|
||||
// WEBP 块头
|
||||
private readonly CHUNK_VP8 = [0x56, 0x50, 0x38, 0x20]; // "VP8 "
|
||||
private readonly CHUNK_VP8L = [0x56, 0x50, 0x38, 0x4C]; // "VP8L"
|
||||
private readonly CHUNK_VP8X = [0x56, 0x50, 0x38, 0x58]; // "VP8X"
|
||||
|
||||
canParse(buffer: Buffer): boolean {
|
||||
return (
|
||||
buffer.length >= 12 &&
|
||||
matchMagic(buffer, this.RIFF_SIGNATURE, 0) &&
|
||||
matchMagic(buffer, this.WEBP_SIGNATURE, 8)
|
||||
);
|
||||
}
|
||||
|
||||
isChunkType(buffer: Buffer, offset: number, chunkType: number[]): boolean {
|
||||
return buffer.length >= offset + 4 && matchMagic(buffer, chunkType, offset);
|
||||
}
|
||||
|
||||
async parseSize(stream: ReadStream): Promise<ImageSize | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 需要读取足够的字节来检测所有三种格式
|
||||
const MAX_HEADER_SIZE = 32;
|
||||
let totalBytes = 0;
|
||||
let buffer = Buffer.alloc(0);
|
||||
|
||||
stream.on('error', reject);
|
||||
|
||||
stream.on('data', (chunk: Buffer | string) => {
|
||||
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
buffer = Buffer.concat([buffer, chunkBuffer]);
|
||||
totalBytes += chunk.length;
|
||||
|
||||
// 检查是否有足够的字节进行格式检测
|
||||
if (totalBytes >= MAX_HEADER_SIZE) {
|
||||
stream.destroy();
|
||||
|
||||
// 检查基本的WEBP签名
|
||||
if (!this.canParse(buffer)) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
|
||||
// 检查chunk头部,位于字节12-15
|
||||
if (this.isChunkType(buffer, 12, this.CHUNK_VP8)) {
|
||||
// VP8格式 - 标准WebP
|
||||
// 宽度和高度在帧头中
|
||||
const width = buffer.readUInt16LE(26) & 0x3FFF;
|
||||
const height = buffer.readUInt16LE(28) & 0x3FFF;
|
||||
return resolve({ width, height });
|
||||
|
||||
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8L)) {
|
||||
// VP8L格式 - 无损WebP
|
||||
// 1字节标记后是14位宽度和14位高度
|
||||
const bits = buffer.readUInt32LE(21);
|
||||
const width = 1 + (bits & 0x3FFF);
|
||||
const height = 1 + ((bits >> 14) & 0x3FFF);
|
||||
return resolve({ width, height });
|
||||
|
||||
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8X)) {
|
||||
// VP8X格式 - 扩展WebP
|
||||
// 24位宽度和高度(减去1)
|
||||
if (!buffer[24] || !buffer[25] || !buffer[26] || !buffer[27] || !buffer[28] || !buffer[29]) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
const width = 1 + ((buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) & 0xFFFFFF);
|
||||
const height = 1 + ((buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) & 0xFFFFFF);
|
||||
return resolve({ width, height });
|
||||
} else {
|
||||
// 未知的WebP子格式
|
||||
return resolve(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
// 如果没有读到足够的字节
|
||||
if (totalBytes < MAX_HEADER_SIZE) {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const parsers: ReadonlyArray<ImageParser> = [
|
||||
new PngParser(),
|
||||
new JpegParser(),
|
||||
new BmpParser(),
|
||||
new GifParser(),
|
||||
new WebpParser(),
|
||||
];
|
||||
|
||||
export async function detectImageType(filePath: string): Promise<ImageType> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stream = fs.createReadStream(filePath, {
|
||||
highWaterMark: 64, // 优化读取buffer大小
|
||||
start: 0,
|
||||
end: 63
|
||||
});
|
||||
|
||||
let buffer: Buffer | null = null;
|
||||
|
||||
stream.once('error', (err) => {
|
||||
stream.destroy();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
stream.once('readable', () => {
|
||||
buffer = stream.read(64) as Buffer;
|
||||
stream.destroy();
|
||||
|
||||
if (!buffer) {
|
||||
return resolve(ImageType.UNKNOWN);
|
||||
}
|
||||
|
||||
for (const parser of parsers) {
|
||||
if (parser.canParse(buffer)) {
|
||||
return resolve(parser.type);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(ImageType.UNKNOWN);
|
||||
});
|
||||
|
||||
stream.once('end', () => {
|
||||
if (!buffer) {
|
||||
resolve(ImageType.UNKNOWN);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function imageSizeFromFile(filePath: string): Promise<ImageSize | undefined> {
|
||||
try {
|
||||
// 先检测类型
|
||||
const type = await detectImageType(filePath);
|
||||
const parser = parsers.find(p => p.type === type);
|
||||
if (!parser) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 用流式方式解析尺寸
|
||||
const stream = fs.createReadStream(filePath);
|
||||
try {
|
||||
return await parser.parseSize(stream);
|
||||
} catch (err) {
|
||||
console.error(`解析图片尺寸出错: ${err}`);
|
||||
return undefined;
|
||||
} finally {
|
||||
if (!stream.destroyed) {
|
||||
stream.destroy();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`检测图片类型出错: ${err}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function imageSizeFallBack(
|
||||
filePath: string,
|
||||
fallback: ImageSize = {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
}
|
||||
): Promise<ImageSize> {
|
||||
return await imageSizeFromFile(filePath) ?? fallback;
|
||||
}
|
BIN
src/native/packet/MoeHoo.linux.arm64.new.node
Normal file
BIN
src/native/packet/MoeHoo.linux.arm64.new.node
Normal file
Binary file not shown.
BIN
src/native/packet/MoeHoo.linux.x64.new.node
Normal file
BIN
src/native/packet/MoeHoo.linux.x64.new.node
Normal file
Binary file not shown.
BIN
src/native/packet/MoeHoo.win32.x64.new.node
Normal file
BIN
src/native/packet/MoeHoo.win32.x64.new.node
Normal file
Binary file not shown.
28
src/onebot/action/extends/SetGroupAddOption.ts
Normal file
28
src/onebot/action/extends/SetGroupAddOption.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
add_type: Type.Number(),
|
||||
group_question: Type.Optional(Type.String()),
|
||||
group_answer: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export default class SetGroupAddOption extends OneBotAction<Payload, null> {
|
||||
override actionName = ActionName.SetGroupAddOption;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
let ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, {
|
||||
addOption: payload.add_type,
|
||||
groupQuestion: payload.group_question,
|
||||
groupAnswer: payload.group_answer,
|
||||
});
|
||||
if (ret.result != 0) {
|
||||
throw new Error(`设置群添加选项失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
23
src/onebot/action/extends/SetGroupKickMembers.ts
Normal file
23
src/onebot/action/extends/SetGroupKickMembers.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
user_id: Type.Array(Type.String()),
|
||||
reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export default class SetGroupKickMembers extends OneBotAction<Payload, null> {
|
||||
override actionName = ActionName.SetGroupKickMembers;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
const rejectReq = payload.reject_add_request?.toString() == 'true';
|
||||
const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin)));
|
||||
await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq);
|
||||
return null;
|
||||
}
|
||||
}
|
27
src/onebot/action/extends/SetGroupRobotAddOption.ts
Normal file
27
src/onebot/action/extends/SetGroupRobotAddOption.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
robot_member_switch: Type.Optional(Type.Number()),
|
||||
robot_member_examine: Type.Optional(Type.Number()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export default class SetGroupRobotAddOption extends OneBotAction<Payload, null> {
|
||||
override actionName = ActionName.SetGroupRobotAddOption;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
let ret = await this.core.apis.GroupApi.setGroupRobotAddOption(
|
||||
payload.group_id,
|
||||
payload.robot_member_switch,
|
||||
payload.robot_member_examine,
|
||||
);
|
||||
if (ret.result != 0) {
|
||||
throw new Error(`设置群机器人添加选项失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
26
src/onebot/action/extends/SetGroupSearch.ts
Normal file
26
src/onebot/action/extends/SetGroupSearch.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
no_code_finger_open: Type.Optional(Type.Number()),
|
||||
no_finger_open: Type.Optional(Type.Number()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export default class SetGroupSearch extends OneBotAction<Payload, null> {
|
||||
override actionName = ActionName.SetGroupSearch;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload): Promise<null> {
|
||||
let ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, {
|
||||
noCodeFingerOpenFlag: payload.no_code_finger_open,
|
||||
noFingerOpenFlag: payload.no_finger_open,
|
||||
});
|
||||
if (ret.result != 0) {
|
||||
throw new Error(`设置群搜索失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -4,7 +4,10 @@ import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
folder_name: Type.String(),
|
||||
// 兼容gocq 与name二选一
|
||||
folder_name: Type.Optional(Type.String()),
|
||||
// 兼容gocq 与folder_name二选一
|
||||
name: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
@@ -16,6 +19,7 @@ export class CreateGroupFileFolder extends OneBotAction<Payload, ResponseType>
|
||||
override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), payload.folder_name)).resultWithGroupItem;
|
||||
const folderName = payload.folder_name || payload.name;
|
||||
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), folderName!)).resultWithGroupItem;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { normalize, SendMsgBase } from '@/onebot/action/msg/SendMsg';
|
||||
import { ContextMode, normalize, ReturnDataType, SendMsgBase } from '@/onebot/action/msg/SendMsg';
|
||||
import { OB11PostSendMsg } from '@/onebot/types';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
|
||||
@@ -19,8 +19,14 @@ export class GoCQHTTPSendForwardMsg extends GoCQHTTPSendForwardMsgBase {
|
||||
}
|
||||
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsgBase {
|
||||
override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg;
|
||||
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
||||
return this.base_handle(payload, ContextMode.Private);
|
||||
}
|
||||
}
|
||||
|
||||
export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsgBase {
|
||||
override actionName = ActionName.GoCQHTTP_SendGroupForwardMsg;
|
||||
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
||||
return this.base_handle(payload, ContextMode.Group);
|
||||
}
|
||||
}
|
||||
|
27
src/onebot/action/group/GetGroupDetailInfo.ts
Normal file
27
src/onebot/action/group/GetGroupDetailInfo.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetGroupDetailInfo extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetGroupDetailInfo;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const data = await this.core.apis.GroupApi.fetchGroupDetail(payload.group_id.toString());
|
||||
return {
|
||||
...data,
|
||||
group_all_shut: data.shutUpAllTimestamp > 0 ? -1 : 0,
|
||||
group_remark: '',
|
||||
group_id: +payload.group_id,
|
||||
group_name: data.groupName,
|
||||
member_count: data.memberNum,
|
||||
max_member_count: data.maxMemberNum,
|
||||
};
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ import { ActionName } from '@/onebot/action/router';
|
||||
import { Notify } from '@/onebot/types';
|
||||
|
||||
interface RetData {
|
||||
invited_requests: Notify[];
|
||||
InvitedRequest: Notify[];
|
||||
join_requests: Notify[];
|
||||
}
|
||||
@@ -13,7 +14,7 @@ export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
|
||||
|
||||
async _handle(): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
|
||||
const retData: RetData = { InvitedRequest: [], join_requests: [] };
|
||||
const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] };
|
||||
|
||||
const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => {
|
||||
const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
|
||||
@@ -38,7 +39,7 @@ export class GetGroupIgnoredNotifies extends OneBotAction<void, RetData> {
|
||||
});
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
retData.invited_requests = retData.InvitedRequest;
|
||||
return retData;
|
||||
}
|
||||
}
|
@@ -8,10 +8,16 @@ interface GroupNotice {
|
||||
notice_id: string;
|
||||
message: {
|
||||
text: string
|
||||
// 保持一段时间兼容性 防止以往版本出现问题 后续版本可考虑移除
|
||||
image: Array<{
|
||||
height: string
|
||||
width: string
|
||||
id: string
|
||||
}>,
|
||||
images: Array<{
|
||||
height: string
|
||||
width: string
|
||||
id: string
|
||||
}>
|
||||
};
|
||||
}
|
||||
@@ -40,15 +46,18 @@ export class GetGroupNotice extends OneBotAction<Payload, GroupNotice[]> {
|
||||
continue;
|
||||
}
|
||||
const retApiNotice: WebApiGroupNoticeFeed = ret.feeds[key];
|
||||
const image = retApiNotice.msg.pics?.map((pic) => {
|
||||
return { id: pic.id, height: pic.h, width: pic.w };
|
||||
}) || [];
|
||||
|
||||
const retNotice: GroupNotice = {
|
||||
notice_id: retApiNotice.fid,
|
||||
sender_id: retApiNotice.u,
|
||||
publish_time: retApiNotice.pubt,
|
||||
message: {
|
||||
text: retApiNotice.msg.text,
|
||||
image: retApiNotice.msg.pics?.map((pic) => {
|
||||
return { id: pic.id, height: pic.h, width: pic.w };
|
||||
}) || [],
|
||||
image,
|
||||
images: image,
|
||||
},
|
||||
};
|
||||
retNotices.push(retNotice);
|
||||
|
@@ -1,19 +0,0 @@
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GroupPoke extends GetPacketStatusDepends<Payload, void> {
|
||||
override actionName = ActionName.GroupPoke;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
|
||||
}
|
||||
}
|
@@ -1,17 +1,19 @@
|
||||
import { ContextMode, SendMsgBase } from '@/onebot/action/msg/SendMsg';
|
||||
import { ContextMode, ReturnDataType, SendMsgBase } from '@/onebot/action/msg/SendMsg';
|
||||
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
||||
import { OB11PostSendMsg } from '@/onebot/types';
|
||||
|
||||
// 未检测参数
|
||||
class SendGroupMsg extends SendMsgBase {
|
||||
override actionName = ActionName.SendGroupMsg;
|
||||
override contextMode: ContextMode = ContextMode.Group;
|
||||
|
||||
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
delete payload.user_id;
|
||||
payload.message_type = 'group';
|
||||
return super.check(payload);
|
||||
}
|
||||
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
||||
return this.base_handle(payload, ContextMode.Group);
|
||||
}
|
||||
}
|
||||
|
||||
export default SendGroupMsg;
|
||||
|
@@ -78,7 +78,6 @@ import { GetGroupFileSystemInfo } from '@/onebot/action/go-cqhttp/GetGroupFileSy
|
||||
import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
|
||||
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
|
||||
import { GetGroupSystemMsg } from './system/GetSystemMsg';
|
||||
import { GroupPoke } from './group/GroupPoke';
|
||||
import { GetUserStatus } from './extends/GetUserStatus';
|
||||
import { GetRkey } from './extends/GetRkey';
|
||||
import { SetSpecialTitle } from './extends/SetSpecialTitle';
|
||||
@@ -86,7 +85,6 @@ import { GetGroupShutList } from './group/GetGroupShutList';
|
||||
import { GetGroupMemberList } from './group/GetGroupMemberList';
|
||||
import { GetGroupFileUrl } from '@/onebot/action/file/GetGroupFileUrl';
|
||||
import { GetPacketStatus } from '@/onebot/action/packet/GetPacketStatus';
|
||||
import { FriendPoke } from '@/onebot/action/user/FriendPoke';
|
||||
import { GetCredentials } from './system/GetCredentials';
|
||||
import { SendGroupSign, SetGroupSign } from './extends/SetGroupSign';
|
||||
import { GoCQHTTPGetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain';
|
||||
@@ -102,7 +100,7 @@ import { GetGuildList } from './guild/GetGuildList';
|
||||
import { GetGuildProfile } from './guild/GetGuildProfile';
|
||||
import { GetClientkey } from './extends/GetClientkey';
|
||||
import { SendPacket } from './extends/SendPacket';
|
||||
import { SendPoke } from '@/onebot/action/packet/SendPoke';
|
||||
import { FriendPoke, GroupPoke, SendPoke } from '@/onebot/action/packet/SendPoke';
|
||||
import { SetDiyOnlineStatus } from './extends/SetDiyOnlineStatus';
|
||||
import { BotExit } from './extends/BotExit';
|
||||
import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton';
|
||||
@@ -118,10 +116,22 @@ import { CleanCache } from './system/CleanCache';
|
||||
import SetFriendRemark from './user/SetFriendRemark';
|
||||
import { SetDoubtFriendsAddRequest } from './new/SetDoubtFriendsAddRequest';
|
||||
import { GetDoubtFriendsAddRequest } from './new/GetDoubtFriendsAddRequest';
|
||||
import SetGroupAddOption from './extends/SetGroupAddOption';
|
||||
import SetGroupSearch from './extends/SetGroupSearch';
|
||||
import SetGroupRobotAddOption from './extends/SetGroupRobotAddOption';
|
||||
import SetGroupKickMembers from './extends/SetGroupKickMembers';
|
||||
import { GetGroupDetailInfo } from './group/GetGroupDetailInfo';
|
||||
import GetGroupAddRequest from './extends/GetGroupAddRequest';
|
||||
import { GetCollectionList } from './extends/GetCollectionList';
|
||||
|
||||
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
|
||||
const actionHandlers = [
|
||||
new GetGroupDetailInfo(obContext, core),
|
||||
new SetGroupKickMembers(obContext, core),
|
||||
new SetGroupAddOption(obContext, core),
|
||||
new SetGroupRobotAddOption(obContext, core),
|
||||
new SetGroupSearch(obContext, core),
|
||||
new SetDoubtFriendsAddRequest(obContext, core),
|
||||
new GetDoubtFriendsAddRequest(obContext, core),
|
||||
new SetFriendRemark(obContext, core),
|
||||
@@ -249,6 +259,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
new GetPrivateFileUrl(obContext, core),
|
||||
new GetUnidirectionalFriendList(obContext, core),
|
||||
new CleanCache(obContext, core),
|
||||
new GetGroupAddRequest(obContext, core),
|
||||
new GetCollectionList(obContext, core),
|
||||
];
|
||||
|
||||
type HandlerUnion = typeof actionHandlers[number];
|
||||
|
@@ -19,6 +19,7 @@ import { rawMsgWithSendMsg } from '@/core/packet/message/converter';
|
||||
export interface ReturnDataType {
|
||||
message_id: number;
|
||||
res_id?: string;
|
||||
forward_id?: string;
|
||||
}
|
||||
|
||||
export enum ContextMode {
|
||||
@@ -104,8 +105,6 @@ function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType
|
||||
}
|
||||
|
||||
export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
||||
contextMode = ContextMode.Normal;
|
||||
|
||||
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
const messages = normalize(payload.message);
|
||||
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
|
||||
@@ -117,12 +116,13 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
||||
this.contextMode = ContextMode.Normal;
|
||||
if (payload.message_type === 'group') this.contextMode = ContextMode.Group;
|
||||
if (payload.message_type === 'private') this.contextMode = ContextMode.Private;
|
||||
const peer = await createContext(this.core, payload, this.contextMode);
|
||||
return this.base_handle(payload);
|
||||
}
|
||||
async base_handle(payload: OB11PostSendMsg, contextMode: ContextMode = ContextMode.Normal): Promise<ReturnDataType> {
|
||||
if (payload.message_type === 'group') contextMode = ContextMode.Group;
|
||||
if (payload.message_type === 'private') contextMode = ContextMode.Private;
|
||||
const peer = await createContext(this.core, payload, contextMode);
|
||||
|
||||
const messages = normalize(
|
||||
payload.message,
|
||||
@@ -148,7 +148,10 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
||||
peerUid: peer.peerUid,
|
||||
chatType: peer.chatType,
|
||||
}, (returnMsgAndResId.message).msgId);
|
||||
return { message_id: msgShortId!, res_id: returnMsgAndResId.res_id! };
|
||||
|
||||
// 对gocq的forward_id进行兼容
|
||||
const resId = returnMsgAndResId.res_id!;
|
||||
return { message_id: msgShortId!, res_id: resId, forward_id: resId };
|
||||
} else if (returnMsgAndResId.res_id && !returnMsgAndResId.message) {
|
||||
throw Error(`发送转发消息(res_id:${returnMsgAndResId.res_id} 失败`);
|
||||
}
|
||||
|
@@ -1,16 +1,18 @@
|
||||
import { ContextMode, SendMsgBase } from './SendMsg';
|
||||
import { ContextMode, ReturnDataType, SendMsgBase } from './SendMsg';
|
||||
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
||||
import { OB11PostSendMsg } from '@/onebot/types';
|
||||
|
||||
// 未检测参数
|
||||
class SendPrivateMsg extends SendMsgBase {
|
||||
override actionName = ActionName.SendPrivateMsg;
|
||||
override contextMode: ContextMode = ContextMode.Private;
|
||||
|
||||
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
payload.message_type = 'private';
|
||||
return super.check(payload);
|
||||
}
|
||||
override async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
||||
return this.base_handle(payload, ContextMode.Private);
|
||||
}
|
||||
}
|
||||
|
||||
export default SendPrivateMsg;
|
||||
|
@@ -3,21 +3,36 @@ import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
group_id: Type.Optional(Type.String()),
|
||||
user_id: Type.Optional(Type.String()),
|
||||
target_id: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class SendPoke extends GetPacketStatusDepends<Payload, void> {
|
||||
override actionName = ActionName.SendPoke;
|
||||
export class SendPokeBase extends GetPacketStatusDepends<Payload, void> {
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
if (payload.group_id) {
|
||||
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
|
||||
} else {
|
||||
await this.core.apis.PacketApi.pkt.operation.FriendPoke(+payload.user_id);
|
||||
// 这里的 !! 可以传入空字符串 忽略这些数据有利用接口统一接口
|
||||
const target_id = !!payload.target_id ? payload.target_id : payload.user_id;
|
||||
const peer_id = !!payload.group_id ? payload.group_id : payload.user_id;
|
||||
|
||||
const is_group = !!payload.group_id;
|
||||
if (!target_id || !peer_id) {
|
||||
throw new Error('请检查参数,缺少 user_id 或 group_id');
|
||||
}
|
||||
|
||||
await this.core.apis.PacketApi.pkt.operation.SendPoke(is_group, +peer_id, +target_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SendPoke extends SendPokeBase {
|
||||
override actionName = ActionName.SendPoke;
|
||||
}
|
||||
export class GroupPoke extends SendPokeBase {
|
||||
override actionName = ActionName.GroupPoke;
|
||||
}
|
||||
export class FriendPoke extends SendPokeBase {
|
||||
override actionName = ActionName.FriendPoke;
|
||||
}
|
||||
|
@@ -10,6 +10,10 @@ export interface InvalidCheckResult {
|
||||
}
|
||||
|
||||
export const ActionName = {
|
||||
SetGroupKickMembers: 'set_group_kick_members',
|
||||
SetGroupRobotAddOption: 'set_group_robot_add_option',
|
||||
SetGroupAddOption: 'set_group_add_option',
|
||||
SetGroupSearch: 'set_group_search',
|
||||
// new extends 完全差异OneBot类别
|
||||
GetDoubtFriendsAddRequest: 'get_doubt_friends_add_request',
|
||||
SetDoubtFriendsAddRequest: 'set_doubt_friends_add_request',
|
||||
@@ -59,7 +63,7 @@ export const ActionName = {
|
||||
GetStatus: 'get_status',
|
||||
GetVersionInfo: 'get_version_info',
|
||||
// Reboot : 'set_restart',
|
||||
CleanCache : 'clean_cache',
|
||||
CleanCache: 'clean_cache',
|
||||
Exit: 'bot_exit',
|
||||
// go-cqhttp
|
||||
SetQQProfile: 'set_qq_profile',
|
||||
@@ -128,6 +132,7 @@ export const ActionName = {
|
||||
FetchEmojiLike: 'fetch_emoji_like',
|
||||
SetInputStatus: 'set_input_status',
|
||||
GetGroupInfoEx: 'get_group_info_ex',
|
||||
GetGroupDetailInfo: 'get_group_detail_info',
|
||||
GetGroupIgnoreAddRequest: 'get_group_ignore_add_request',
|
||||
DelGroupNotice: '_del_group_notice',
|
||||
FriendPoke: 'friend_poke',
|
||||
|
@@ -2,18 +2,27 @@ import { GroupNotifyMsgStatus } from '@/core';
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { Notify } from '@/onebot/types';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
interface RetData {
|
||||
invited_requests: Notify[];
|
||||
InvitedRequest: Notify[];
|
||||
join_requests: Notify[];
|
||||
}
|
||||
|
||||
export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
|
||||
override actionName = ActionName.GetGroupSystemMsg;
|
||||
const SchemaData = Type.Object({
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 50 }),
|
||||
});
|
||||
|
||||
async _handle(): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, 50);
|
||||
const retData: RetData = { InvitedRequest: [], join_requests: [] };
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetGroupSystemMsg extends OneBotAction<Payload, RetData> {
|
||||
override actionName = ActionName.GetGroupSystemMsg;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(params: Payload): Promise<RetData> {
|
||||
const SingleScreenNotifies = await this.core.apis.GroupApi.getSingleScreenNotifies(false, +params.count);
|
||||
const retData: RetData = { invited_requests: [], InvitedRequest: [], join_requests: [] };
|
||||
|
||||
const notifyPromises = SingleScreenNotifies.map(async (SSNotify) => {
|
||||
const invitorUin = SSNotify.user1?.uid ? +await this.core.apis.UserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
|
||||
@@ -39,6 +48,7 @@ export class GetGroupSystemMsg extends OneBotAction<void, RetData> {
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
retData.invited_requests = retData.InvitedRequest;
|
||||
return retData;
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()])
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class FriendPoke extends GetPacketStatusDepends<Payload, void> {
|
||||
override actionName = ActionName.FriendPoke;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
await this.core.apis.PacketApi.pkt.operation.FriendPoke(+payload.user_id);
|
||||
}
|
||||
}
|
@@ -1209,7 +1209,6 @@ export class OneBotMsgApi {
|
||||
async waitGroupNotify(groupUin: string, memberUid?: string, operatorUid?: string) {
|
||||
const groupRole = this.core.apis.GroupApi.groupMemberCache.get(groupUin)?.get(this.core.selfInfo.uid.toString())?.role;
|
||||
const isAdminOrOwner = groupRole === 3 || groupRole === 4;
|
||||
|
||||
if (isAdminOrOwner && !operatorUid) {
|
||||
let dataNotify: GroupNotify | undefined;
|
||||
await this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onGroupNotifiesUpdated',
|
||||
@@ -1239,7 +1238,7 @@ export class OneBotMsgApi {
|
||||
const operatorUid = await this.waitGroupNotify(
|
||||
groupChange.groupUin.toString(),
|
||||
groupChange.memberUid,
|
||||
groupChange.operatorInfo ? Buffer.from(groupChange.operatorInfo).toString() : ''
|
||||
groupChange.operatorInfo ? new TextDecoder('utf-8').decode(groupChange.operatorInfo) : undefined
|
||||
);
|
||||
return new OB11GroupIncreaseEvent(
|
||||
this.core,
|
||||
@@ -1251,13 +1250,42 @@ export class OneBotMsgApi {
|
||||
|
||||
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
|
||||
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||
// 自身被踢出时operatorInfo会是一个protobuf 否则大多数情况为一个string
|
||||
|
||||
let operator_uid_parse: string | undefined = undefined;
|
||||
if (groupChange.operatorInfo) {
|
||||
// 先判断是否可能是protobuf(自身被踢出或以0a开头)
|
||||
if (groupChange.decreaseType === 3 || Buffer.from(groupChange.operatorInfo).toString('hex').startsWith('0a')) {
|
||||
// 可能是protobuf,尝试解析
|
||||
try {
|
||||
operator_uid_parse = new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid;
|
||||
} catch (error) {
|
||||
// protobuf解析失败,fallback到字符串解析
|
||||
try {
|
||||
const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo);
|
||||
// 检查是否包含非ASCII字符,如果包含则丢弃
|
||||
const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126);
|
||||
operator_uid_parse = isAsciiOnly ? decoded : '';
|
||||
} catch (e2) {
|
||||
operator_uid_parse = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 直接进行字符串解析
|
||||
try {
|
||||
const decoded = new TextDecoder('utf-8').decode(groupChange.operatorInfo);
|
||||
// 检查是否包含非ASCII字符,如果包含则丢弃
|
||||
const isAsciiOnly = [...decoded].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126);
|
||||
operator_uid_parse = isAsciiOnly ? decoded : '';
|
||||
} catch (e) {
|
||||
operator_uid_parse = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const operatorUid = await this.waitGroupNotify(
|
||||
groupChange.groupUin.toString(),
|
||||
groupChange.memberUid,
|
||||
groupChange.decreaseType === 3 && groupChange.operatorInfo ?
|
||||
new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid :
|
||||
groupChange.operatorInfo?.toString()
|
||||
operator_uid_parse
|
||||
);
|
||||
if (groupChange.memberUid === this.core.selfInfo.uid) {
|
||||
setTimeout(() => {
|
||||
|
@@ -2,4 +2,5 @@ import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent';
|
||||
|
||||
export abstract class OB11BaseNoticeEvent extends OneBotEvent {
|
||||
post_type = EventType.NOTICE;
|
||||
abstract notice_type: string;
|
||||
}
|
6
src/onebot/event/request/OB11BaseRequestEvent.ts
Normal file
6
src/onebot/event/request/OB11BaseRequestEvent.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent';
|
||||
|
||||
export abstract class OB11BaseRequestEvent extends OneBotEvent {
|
||||
readonly post_type = EventType.REQUEST;
|
||||
abstract request_type: string;
|
||||
}
|
@@ -1,10 +1,8 @@
|
||||
import { OB11BaseNoticeEvent } from '@/onebot/event/notice/OB11BaseNoticeEvent';
|
||||
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||
import { NapCatCore } from '@/core';
|
||||
import { OB11BaseRequestEvent } from './OB11BaseRequestEvent';
|
||||
|
||||
export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {
|
||||
override post_type = EventType.REQUEST;
|
||||
request_type = 'friend';
|
||||
export class OB11FriendRequestEvent extends OB11BaseRequestEvent {
|
||||
override request_type = 'friend';
|
||||
|
||||
user_id: number;
|
||||
comment: string;
|
||||
|
@@ -1,18 +1,18 @@
|
||||
import { OB11GroupNoticeEvent } from '@/onebot/event/notice/OB11GroupNoticeEvent';
|
||||
import { EventType } from '@/onebot/event/OneBotEvent';
|
||||
import { NapCatCore } from '@/core';
|
||||
import { OB11BaseRequestEvent } from './OB11BaseRequestEvent';
|
||||
|
||||
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent {
|
||||
override post_type = EventType.REQUEST;
|
||||
request_type = 'group';
|
||||
export class OB11GroupRequestEvent extends OB11BaseRequestEvent {
|
||||
override readonly request_type = 'group' as const;
|
||||
|
||||
override user_id: number;
|
||||
group_id: number;
|
||||
user_id: number;
|
||||
comment: string;
|
||||
flag: string;
|
||||
sub_type: string;
|
||||
|
||||
constructor(core: NapCatCore, groupId: number, userId: number, sub_type: string, comment: string, flag: string) {
|
||||
super(core, groupId, userId);
|
||||
super(core);
|
||||
this.group_id = groupId;
|
||||
this.user_id = userId;
|
||||
this.sub_type = sub_type;
|
||||
this.comment = comment;
|
||||
|
@@ -270,7 +270,6 @@ export class NapCatOneBot11Adapter {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
msgListener.onAddSendMsg = async (msg) => {
|
||||
try {
|
||||
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
|
||||
@@ -282,7 +281,8 @@ export class NapCatOneBot11Adapter {
|
||||
}, 1, 10 * 60 * 1000);
|
||||
// 10分钟 超时
|
||||
const updatemsg = updatemsgs.find((e) => e.msgId === msg.msgId);
|
||||
if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS || updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ) {
|
||||
// updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS_NOSEQ NOSEQ一般是服务器未下发SEQ 这意味着这条消息不应该推送network
|
||||
if (updatemsg?.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS) {
|
||||
updatemsg.id = MessageUnique.createUniqueMsgId(
|
||||
{
|
||||
chatType: updatemsg.chatType,
|
||||
@@ -304,8 +304,18 @@ export class NapCatOneBot11Adapter {
|
||||
peerUid: uid,
|
||||
guildId: ''
|
||||
};
|
||||
const msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
|
||||
let msg = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, msgSeq)).msgList.find(e => e.msgType == NTMsgType.KMSGTYPEGRAYTIPS);
|
||||
const element = msg?.elements.find(e => !!e.grayTipElement?.revokeElement);
|
||||
if (element?.grayTipElement?.revokeElement.isSelfOperate && msg) {
|
||||
await this.core.eventWrapper.registerListen('NodeIKernelMsgListener/onMsgRecall',
|
||||
(chatType: ChatType, uid: string, msgSeq: string) => {
|
||||
return chatType === msg?.chatType && uid === msg?.peerUid && msgSeq === msg?.msgSeq;
|
||||
}
|
||||
).catch(() => {
|
||||
msg = undefined;
|
||||
this.context.logger.logDebug('自操作消息撤回事件');
|
||||
});
|
||||
}
|
||||
if (msg && element) {
|
||||
const recallEvent = await this.emitRecallMsg(msg, element);
|
||||
try {
|
||||
@@ -316,6 +326,7 @@ export class NapCatOneBot11Adapter {
|
||||
this.context.logger.logError('处理消息撤回失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
msgListener.onKickedOffLine = async (kick) => {
|
||||
const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc);
|
||||
|
@@ -87,8 +87,8 @@ export class OB11HttpServerAdapter extends IOB11NetworkAdapter<HttpServerConfig>
|
||||
this.app.use(async (req, res) => {
|
||||
await this.handleRequest(req, res);
|
||||
});
|
||||
this.server.listen(this.config.port, () => {
|
||||
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.config.port}`);
|
||||
this.server.listen(this.config.port, this.config.host, () => {
|
||||
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On ${this.config.host}:${this.config.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -122,7 +122,9 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
|
||||
|
||||
// ------------挂载路由------------
|
||||
// 挂载静态路由(前端),路径为 /webui
|
||||
app.use('/webui', express.static(pathWrapper.staticPath));
|
||||
app.use('/webui', express.static(pathWrapper.staticPath, {
|
||||
maxAge: '1d'
|
||||
}));
|
||||
// 初始化WebSocket服务器
|
||||
const sslCerts = await checkCertificates(logger);
|
||||
const isHttps = !!sslCerts;
|
||||
|
@@ -47,6 +47,9 @@ export const CreateTerminalHandler: RequestHandler = async (req, res) => {
|
||||
if (isMacOS) {
|
||||
return sendError(res, 'MacOS不支持终端');
|
||||
}
|
||||
if ((await WebUiConfig.GetWebUIConfig()).token === 'napcat') {
|
||||
return sendError(res, '默认密码禁止创建终端');
|
||||
}
|
||||
try {
|
||||
const { cols, rows } = req.body;
|
||||
const { id } = terminalManager.createTerminal(cols, rows);
|
||||
|
@@ -9,7 +9,7 @@ Object.defineProperty(global, '__dirname', {
|
||||
// 注意:堆栈格式可能不同,请根据实际环境调整索引及正则表达式
|
||||
for (const line of stack) {
|
||||
const match = line.match(/\((.*):\d+:\d+\)/);
|
||||
if (match) {
|
||||
if (match?.[1]) {
|
||||
callerFile = match[1];
|
||||
if (!callerFile.includes('init-dynamic-dirname.ts')) {
|
||||
break;
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import multer from 'multer';
|
||||
import { WebUiConfigWrapper } from '../helper/config';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import type { Request, Response } from 'express';
|
||||
import { WebUiConfig } from '@/webui';
|
||||
|
||||
export const webUIFontStorage = multer.diskStorage({
|
||||
destination: (_, __, cb) => {
|
||||
try {
|
||||
const fontsPath = path.dirname(WebUiConfigWrapper.GetWebUIFontPath());
|
||||
const fontsPath = path.dirname(WebUiConfig.GetWebUIFontPath());
|
||||
// 确保字体目录存在
|
||||
fs.mkdirSync(fontsPath, { recursive: true });
|
||||
cb(null, fontsPath);
|
||||
|
@@ -53,6 +53,7 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
|
||||
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
|
||||
{ src: './src/framework/napcat.cjs', dest: 'dist' },
|
||||
{ src: './src/framework/nativeLoader.cjs', dest: 'dist' },
|
||||
{ src: './src/framework/preload.cjs', dest: 'dist' },
|
||||
{ src: './src/framework/renderer.js', dest: 'dist' },
|
||||
{ src: './package.json', dest: 'dist' },
|
||||
|
Reference in New Issue
Block a user