Compare commits

...

100 Commits

Author SHA1 Message Date
idranme
0bc6e23343 Merge pull request #459 from LLOneBot/dev
release: 4.0.0
2024-10-07 20:26:59 +08:00
idranme
8e9523602b chore: v4.0.0 2024-10-07 20:23:54 +08:00
idranme
48588817fb chore 2024-10-07 19:10:38 +08:00
idranme
4cd9adde1d feat: satori protocol 2024-10-06 10:37:06 +08:00
idranme
8c0cc8beba refactor 2024-10-06 10:28:52 +08:00
idranme
9ec09c6eee Merge pull request #457 from LLOneBot/dev
release: 3.34.1
2024-10-03 15:18:47 +08:00
idranme
4d816b498a chore: v3.34.1 2024-10-03 15:17:57 +08:00
idranme
464efe819d fix 2024-10-03 15:16:48 +08:00
idranme
0876e4645f Merge pull request #456 from LLOneBot/dev
release: 3.34.0
2024-10-01 21:32:24 +08:00
idranme
a2f9128623 chore: v3.34.0 2024-10-01 21:25:19 +08:00
idranme
e313b2b3e6 feat 2024-10-01 21:16:39 +08:00
idranme
a7d86f8fe0 refactor 2024-10-01 21:09:27 +08:00
idranme
496d56f297 feat 2024-09-30 00:49:58 +08:00
idranme
ed2f554d4e refactor 2024-09-28 22:00:05 +08:00
idranme
36d990e328 Merge pull request #452 from LLOneBot/dev
release: 3.33.10
2024-09-28 14:40:11 +08:00
idranme
0ceef4d4c0 chore: v3.33.10 2024-09-28 14:37:44 +08:00
idranme
35bf4f001b feat: _get_group_notice API 2024-09-28 14:35:06 +08:00
idranme
544682fe41 fix 2024-09-28 12:54:02 +08:00
idranme
3da49fbfba optimize 2024-09-27 18:37:47 +08:00
idranme
d5875c9e5b Merge pull request #451 from LLOneBot/dev
release: 3.33.9
2024-09-27 16:53:44 +08:00
idranme
7895644156 chore: v3.33.9 2024-09-27 16:51:49 +08:00
idranme
f092626ede fix 2024-09-27 16:22:50 +08:00
idranme
a58fb31f8e Merge pull request #448 from LLOneBot/dev
release: 3.33.8
2024-09-26 12:57:16 +08:00
idranme
fe85e277f1 chore: v3.33.8 2024-09-26 12:54:30 +08:00
idranme
5217638b46 feat 2024-09-26 01:52:47 +08:00
idranme
f68b707e1c optimize 2024-09-25 22:34:59 +08:00
idranme
c24ce6ec65 adjustment of get_friends_with_category API returns 2024-09-25 22:04:52 +08:00
idranme
f9270c38cf Merge pull request #444 from LLOneBot/dev
release: 3.33.7
2024-09-25 14:59:34 +08:00
idranme
fd478cdaed chore: v3.33.7 2024-09-25 14:55:05 +08:00
idranme
517b233496 fix 2024-09-25 14:52:04 +08:00
idranme
1045c94a91 feat: get_group_file_url API 2024-09-25 12:13:28 +08:00
idranme
032ac85c04 refactor 2024-09-24 19:59:07 +08:00
idranme
1e35ffd7e6 optimize 2024-09-24 14:18:44 +08:00
idranme
e5ab6134cd Merge pull request #441 from LLOneBot/dev
release: 3.33.6
2024-09-23 23:43:50 +08:00
idranme
a95ae44614 chore: v3.33.6 2024-09-23 23:36:10 +08:00
idranme
3dc9940ac9 feat 2024-09-23 23:34:52 +08:00
idranme
277e418cf3 refactor 2024-09-23 22:10:12 +08:00
idranme
24f09d485e Merge pull request #438 from LLOneBot/dev
release: 3.33.5
2024-09-22 21:31:55 +08:00
idranme
3394823719 chore: v3.33.5 2024-09-22 21:26:37 +08:00
idranme
afa06f0760 fix 2024-09-22 21:18:59 +08:00
idranme
4f9e465fb2 optimize 2024-09-22 20:37:20 +08:00
idranme
f400d43b8a Merge pull request #436 from LLOneBot/dev
release: 3.33.4
2024-09-21 23:29:47 +08:00
idranme
fb2f1a8917 chore: v3.33.4 2024-09-21 23:29:05 +08:00
idranme
c849b9bea2 fix: get_forward_msg API 2024-09-21 23:28:27 +08:00
idranme
1c6364d98f Merge pull request #435 from LLOneBot/dev
release: 3.33.3
2024-09-21 21:52:54 +08:00
idranme
8a268c3968 chore: v3.33.3 2024-09-21 21:39:30 +08:00
idranme
806798bd48 refactor 2024-09-21 21:32:40 +08:00
idranme
0c456e2160 optimize 2024-09-21 20:19:26 +08:00
idranme
08e7e471d6 fix: delete_group_file API 2024-09-21 20:19:10 +08:00
idranme
13299c4631 chore: improve code quality 2024-09-21 09:21:08 +08:00
idranme
390e20c2ef optimize 2024-09-21 03:30:01 +08:00
idranme
d8433e22d2 chore: improve code quality 2024-09-21 01:06:49 +08:00
idranme
ac07c98ae1 Merge pull request #434 from LLOneBot/dev
release: 3.33.2
2024-09-20 23:00:09 +08:00
idranme
6a19d6f234 chore: v3.33.2 2024-09-20 22:57:00 +08:00
idranme
ab0b8ae663 feat: get_essence_msg_list API 2024-09-20 22:55:29 +08:00
idranme
96aa5e264a refactor 2024-09-20 22:13:26 +08:00
idranme
15b85f735d fix: friend_add event 2024-09-20 19:08:22 +08:00
idranme
4dd6d12168 feat 2024-09-20 18:00:32 +08:00
idranme
44febed486 optimize 2024-09-20 03:19:43 +08:00
idranme
6c66dab3dc Merge pull request #433 from LLOneBot/dev
release: 3.33.1
2024-09-19 18:31:01 +08:00
idranme
0f7939fe5e chore: v3.33.1 2024-09-19 18:29:16 +08:00
idranme
73a2b4e35f fix 2024-09-19 18:29:12 +08:00
idranme
936b1d911c Merge pull request #428 from LLOneBot/dev
release: 3.33.0
2024-09-18 20:59:57 +08:00
idranme
58817d1c02 chore: v3.33.0 2024-09-18 20:53:28 +08:00
idranme
2c24422478 feat: support setting remark when agreeing to a friend request 2024-09-18 20:47:45 +08:00
idranme
c2a723380a fix: get_group_member_list API 2024-09-18 19:35:58 +08:00
idranme
156bbaea33 feat: get_group_files_by_folder API 2024-09-18 17:22:09 +08:00
idranme
6c485634e1 feat: get_friend_msg_history API 2024-09-18 16:56:15 +08:00
idranme
f39a9aeafb feat: fetch_custom_face API 2024-09-18 16:11:08 +08:00
idranme
1160cd4b26 feat: fetch_emoji_like API 2024-09-18 15:49:37 +08:00
idranme
9a7ff523dd optimize 2024-09-18 14:07:42 +08:00
idranme
f49995ea97 refactor 2024-09-17 21:04:36 +08:00
idranme
1876dd29ac Merge pull request #423 from LLOneBot/dev
release: 3.32.8
2024-09-17 11:59:57 +08:00
idranme
9944b53266 chore: v3.32.8 2024-09-17 11:55:50 +08:00
idranme
9a791e3a21 fix 2024-09-17 02:17:16 +08:00
idranme
64c5eb6c04 Merge pull request #422 from LLOneBot/dev
release: 3.32.7
2024-09-16 20:48:15 +08:00
idranme
e5750786cb chore: v3.32.7 2024-09-16 20:46:14 +08:00
idranme
18cb46ade5 fix 2024-09-16 20:43:18 +08:00
idranme
e39c89a441 fix 2024-09-16 19:01:59 +08:00
idranme
476d498e44 Merge pull request #417 from LLOneBot/dev
release: 3.32.6
2024-09-15 17:48:35 +08:00
idranme
55446538de chore: v3.32.6 2024-09-15 17:43:10 +08:00
idranme
b965f50653 fix: friend_add event 2024-09-15 16:14:36 +08:00
idranme
2d354c5eda optimize 2024-09-15 14:08:02 +08:00
idranme
536999f296 feat: support for sending contact message segment 2024-09-14 20:13:45 +08:00
idranme
cad09b2ed1 fix 2024-09-14 19:56:46 +08:00
idranme
6be0c11ca2 refactor 2024-09-13 22:58:21 +08:00
idranme
b03bcf9a7c Merge pull request #415 from LLOneBot/dev
release: 3.32.5
2024-09-13 18:59:37 +08:00
idranme
d4f9629af2 chore: v3.32.5 2024-09-13 18:54:56 +08:00
idranme
6d47e2ee80 chore 2024-09-13 18:52:45 +08:00
idranme
506bddb21a chore: style 2024-09-13 17:30:56 +08:00
idranme
91c689baf8 fix 2024-09-13 14:56:30 +08:00
idranme
b7938aaab8 refactor 2024-09-12 22:39:14 +08:00
idranme
b1a892cf4e chore 2024-09-12 18:29:18 +08:00
idranme
9284fc7e8a Merge pull request #413 from LLOneBot/dev
3.32.4
2024-09-12 18:14:23 +08:00
idranme
ceb063143a chore: v3.32.4 2024-09-12 18:11:01 +08:00
idranme
ed55a5a54c optimize 2024-09-12 17:52:21 +08:00
idranme
2c4fdbfa6a fix: upload_group_file API 2024-09-12 16:46:38 +08:00
idranme
1132495eb3 fix: check for updates 2024-09-12 01:11:17 +08:00
idranme
2ac2c68435 chore 2024-09-11 22:13:11 +08:00
idranme
6477366ba6 chore
chore
2024-09-11 21:35:50 +08:00
182 changed files with 5110 additions and 6267 deletions

7
.gitattributes vendored Normal file
View File

@@ -0,0 +1,7 @@
* text eol=lf
*.png -text
*.jpg -text
*.ico -text
*.gif -text
*.webp -text

View File

@@ -7,9 +7,6 @@ body:
attributes: attributes:
value: | value: |
欢迎来到 LLOneBot 的 Issue Tracker请填写以下表格来提交 Bug。 欢迎来到 LLOneBot 的 Issue Tracker请填写以下表格来提交 Bug。
在提交新的 Bug 反馈前,请确保您:
* 已经搜索了现有的 issues并且没有找到可以解决您问题的方法
* 不与现有的某一 issue 重复
- type: input - type: input
id: system-version id: system-version
attributes: attributes:
@@ -40,8 +37,6 @@ body:
label: OneBot 客户端 label: OneBot 客户端
description: 连接至 LLOneBot 的客户端版本信息 description: 连接至 LLOneBot 的客户端版本信息
placeholder: Overflow 2.16.0-2cf7991-SNAPSHOT placeholder: Overflow 2.16.0-2cf7991-SNAPSHOT
validations:
required: true
- type: textarea - type: textarea
id: what-happened id: what-happened
attributes: attributes:

View File

@@ -1,6 +1,6 @@
# LLOneBot # LLOneBot
LiteLoaderQQNT 插件,实现 OneBot 11 协议,用于 QQ 机器人开发 LiteLoaderQQNT 插件,实现 OneBot 11 和 Satori 协议,用于 QQ 机器人开发
> [!CAUTION]\ > [!CAUTION]\
> 请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论任何与本插件存在相关性的信息 > 请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论任何与本插件存在相关性的信息

View File

@@ -31,7 +31,6 @@ const config: ElectronViteConfig = {
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
}, },
}, },
plugins: [ plugins: [

View File

@@ -3,8 +3,8 @@
"type": "extension", "type": "extension",
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发", "description": "实现 OneBot 11 和 Satori 协议,用于 QQ 机器人开发",
"version": "3.32.3", "version": "4.0.0",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {

View File

@@ -12,28 +12,30 @@
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %LITELOADERQQNT_PROFILE%\\plugins\\LLOneBot\\\"", "deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %LITELOADERQQNT_PROFILE%\\plugins\\LLOneBot\\\"",
"format": "prettier -cw .", "format": "prettier -cw .",
"check": "tsc", "check": "tsc",
"compile:proto": "pbjs -t static-module -w es6 -p ./src/ntqqapi/proto -o ./src/ntqqapi/proto/compiled.js systemMessage.proto profileLikeTip.proto && pbts -o ./src/ntqqapi/proto/compiled.d.ts ./src/ntqqapi/proto/compiled.js" "compile:proto": "pbjs --no-create --no-convert --no-encode --no-verify -t static-module -w es6 -p src/ntqqapi/proto -o src/ntqqapi/proto/compiled.js systemMessage.proto profileLikeTip.proto && pbts -o src/ntqqapi/proto/compiled.d.ts src/ntqqapi/proto/compiled.js"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@minatojs/driver-sqlite": "^4.5.0", "@minatojs/driver-sqlite": "^4.6.0",
"compressing": "^1.10.1", "@satorijs/element": "^3.1.7",
"cordis": "^3.18.0", "@satorijs/protocol": "^1.4.2",
"compare-versions": "^6.1.1",
"cordis": "^3.18.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cosmokit": "^1.6.2", "cosmokit": "^1.6.3",
"express": "^5.0.0", "express": "^5.0.0",
"fast-xml-parser": "^4.5.0", "fast-xml-parser": "^4.5.0",
"file-type": "^19.5.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"minato": "^3.5.1", "minato": "^3.6.0",
"protobufjs": "^7.4.0", "protobufjs": "^7.4.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"ts-case-convert": "^2.1.0",
"ws": "^8.18.0" "ws": "^8.18.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^5.0.0",
"@types/fluent-ffmpeg": "^2.1.26", "@types/fluent-ffmpeg": "^2.1.26",
"@types/node": "^20.14.15", "@types/node": "^20.14.15",
"@types/ws": "^8.5.12", "@types/ws": "^8.5.12",
@@ -41,8 +43,8 @@
"electron-vite": "^2.3.0", "electron-vite": "^2.3.0",
"protobufjs-cli": "^1.1.3", "protobufjs-cli": "^1.1.3",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"vite": "^5.4.4", "vite": "^5.4.8",
"vite-plugin-cp": "^4.0.8" "vite-plugin-cp": "^4.0.8"
}, },
"packageManager": "yarn@4.4.1" "packageManager": "yarn@4.5.0"
} }

View File

@@ -6,7 +6,7 @@ const manifest = {
type: 'extension', type: 'extension',
name: 'LLOneBot', name: 'LLOneBot',
slug: 'LLOneBot', slug: 'LLOneBot',
description: '实现 OneBot 11 协议,用于 QQ 机器人开发', description: '实现 OneBot 11 和 Satori 协议,用于 QQ 机器人开发',
version, version,
icon: './icon.webp', icon: './icon.webp',
authors: [ authors: [
@@ -35,4 +35,4 @@ const manifest = {
} }
} }
writeFileSync('manifest.json', JSON.stringify(manifest, null, 2)) writeFileSync('manifest.json', JSON.stringify(manifest, null, 2))

View File

@@ -1,6 +1,6 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { Config, OB11Config } from './types' import { Config, OB11Config, SatoriConfig } from './types'
import { selfInfo, DATA_DIR } from './globalVars' import { selfInfo, DATA_DIR } from './globalVars'
import { mergeNewProperties } from './utils/misc' import { mergeNewProperties } from './utils/misc'
@@ -22,6 +22,7 @@ export class ConfigUtil {
reloadConfig(): Config { reloadConfig(): Config {
const ob11Default: OB11Config = { const ob11Default: OB11Config = {
enable: true,
httpPort: 3000, httpPort: 3000,
httpHosts: [], httpHosts: [],
httpSecret: '', httpSecret: '',
@@ -33,11 +34,16 @@ export class ConfigUtil {
enableWsReverse: false, enableWsReverse: false,
messagePostFormat: 'array', messagePostFormat: 'array',
enableHttpHeart: false, enableHttpHeart: false,
enableQOAutoQuote: false,
listenLocalhost: false listenLocalhost: false
} }
const satoriDefault: SatoriConfig = {
enable: true,
port: 5600,
listen: '0.0.0.0',
token: ''
}
const defaultConfig: Config = { const defaultConfig: Config = {
enableLLOB: true, satori: satoriDefault,
ob11: ob11Default, ob11: ob11Default,
heartInterval: 60000, heartInterval: 60000,
token: '', token: '',

View File

@@ -19,4 +19,4 @@ export const selfInfo: SelfInfo = {
uin: '', uin: '',
nick: '', nick: '',
online: true, online: true,
} }

View File

@@ -1,4 +1,5 @@
export interface OB11Config { export interface OB11Config {
enable: boolean
httpPort: number httpPort: number
httpHosts: string[] httpHosts: string[]
httpSecret?: string httpSecret?: string
@@ -10,17 +11,23 @@ export interface OB11Config {
enableWsReverse?: boolean enableWsReverse?: boolean
messagePostFormat?: 'array' | 'string' messagePostFormat?: 'array' | 'string'
enableHttpHeart?: boolean enableHttpHeart?: boolean
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息 /**
* 快速操作回复自动引用原消息
* @deprecated
*/
enableQOAutoQuote?: boolean
listenLocalhost: boolean listenLocalhost: boolean
} }
export interface CheckVersion { export interface SatoriConfig {
result: boolean enable: boolean
version: string listen: string
port: number
token: string
} }
export interface Config { export interface Config {
enableLLOB: boolean satori: SatoriConfig
ob11: OB11Config ob11: OB11Config
token?: string token?: string
heartInterval: number // ms heartInterval: number // ms
@@ -41,6 +48,13 @@ export interface Config {
hosts?: string[] hosts?: string[]
/** @deprecated */ /** @deprecated */
wsPort?: string wsPort?: string
/** @deprecated */
enableLLOB?: boolean
}
export interface CheckVersion {
result: boolean
version: string
} }
export interface LLOneBotError { export interface LLOneBotError {
@@ -70,4 +84,4 @@ export interface FileCacheV2 {
chatType: number chatType: number
elementId: string elementId: string
elementType: number elementType: number
} }

View File

@@ -101,7 +101,7 @@ export async function encodeSilk(ctx: Context, filePath: string) {
type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
export async function decodeSilk(ctx: Context, inputFilePath: string, outFormat: OutFormat = 'mp3') { export async function decodeSilk(ctx: Context, inputFilePath: string, outFormat: OutFormat) {
const silk = await fsPromise.readFile(inputFilePath) const silk = await fsPromise.readFile(inputFilePath)
const { data } = await decode(silk, 24000) const { data } = await decode(silk, 24000)
const tmpPath = path.join(TEMP_DIR, path.basename(inputFilePath)) const tmpPath = path.join(TEMP_DIR, path.basename(inputFilePath))
@@ -115,4 +115,4 @@ export async function decodeSilk(ctx: Context, inputFilePath: string, outFormat:
'-ac 1' '-ac 1'
] ]
}, outFilePath) }, outFilePath)
} }

View File

@@ -4,15 +4,7 @@ import path from 'node:path'
import { TEMP_DIR } from '../globalVars' import { TEMP_DIR } from '../globalVars'
import { randomUUID, createHash } from 'node:crypto' import { randomUUID, createHash } from 'node:crypto'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { fileTypeFromFile } from 'file-type' import { Context } from 'cordis'
export function isGIF(path: string) {
const buffer = Buffer.alloc(4)
const fd = fs.openSync(path, 'r')
fs.readSync(fd, buffer, 0, 4, 0)
fs.closeSync(fd)
return buffer.toString() === 'GIF8'
}
// 定义一个异步函数来检查文件是否存在 // 定义一个异步函数来检查文件是否存在
export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> { export function checkFileReceived(path: string, timeout: number = 3000): Promise<void> {
@@ -91,17 +83,26 @@ interface FetchFileRes {
} }
export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> { export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> {
const headers: Record<string, string> = { const headers = new Headers({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
'Host': new URL(url).hostname, 'Host': new URL(url).hostname,
...headersInit ...headersInit
} })
const raw = await fetch(url, { headers }).catch((err) => { let raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) { if (err.cause) {
throw err.cause throw err.cause
} }
throw err throw err
}) })
if (raw.status === 403 && !headers.has('Referer')) {
headers.set('Referer', url)
raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause
}
throw err
})
}
if (!raw.ok) throw new Error(`statusText: ${raw.statusText}`) if (!raw.ok) throw new Error(`statusText: ${raw.statusText}`)
return { return {
data: Buffer.from(await raw.arrayBuffer()), data: Buffer.from(await raw.arrayBuffer()),
@@ -117,7 +118,7 @@ type Uri2LocalRes = {
isLocal: boolean isLocal: boolean
} }
export async function uri2local(uri: string, filename?: string, needExt?: boolean): Promise<Uri2LocalRes> { export async function uri2local(ctx: Context, uri: string, needExt?: boolean): Promise<Uri2LocalRes> {
const { type } = checkUriType(uri) const { type } = checkUriType(uri)
if (type === FileUriType.FileURL) { if (type === FileUriType.FileURL) {
@@ -133,17 +134,18 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
if (type === FileUriType.RemoteURL) { if (type === FileUriType.RemoteURL) {
try { try {
const res = await fetchFile(uri, { 'Referer': uri }) const res = await fetchFile(uri)
const match = res.url.match(/.+\/([^/?]*)(?=\?)?/) const match = res.url.match(/.+\/([^/?]*)(?=\?)?/)
let filename: string
if (match?.[1]) { if (match?.[1]) {
filename ??= match[1].replace(/[/\\:*?"<>|]/g, '_') filename = match[1].replace(/[/\\:*?"<>|]/g, '_')
} else { } else {
filename ??= randomUUID() filename = randomUUID()
} }
let filePath = path.join(TEMP_DIR, filename) let filePath = path.join(TEMP_DIR, filename)
await fsPromise.writeFile(filePath, res.data) await fsPromise.writeFile(filePath, res.data)
if (needExt && !path.extname(filePath)) { if (needExt && !path.extname(filePath)) {
const ext = (await fileTypeFromFile(filePath))?.ext const ext = (await ctx.ntFileApi.getFileType(filePath)).ext
filename += `.${ext}` filename += `.${ext}`
await fsPromise.rename(filePath, `${filePath}.${ext}`) await fsPromise.rename(filePath, `${filePath}.${ext}`)
filePath = `${filePath}.${ext}` filePath = `${filePath}.${ext}`
@@ -156,12 +158,12 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
} }
if (type === FileUriType.OneBotBase64) { if (type === FileUriType.OneBotBase64) {
filename ??= randomUUID() let filename = randomUUID()
let filePath = path.join(TEMP_DIR, filename) let filePath = path.join(TEMP_DIR, filename)
const base64 = uri.replace(/^base64:\/\//, '') const base64 = uri.replace(/^base64:\/\//, '')
await fsPromise.writeFile(filePath, base64, 'base64') await fsPromise.writeFile(filePath, base64, 'base64')
if (needExt) { if (needExt) {
const ext = (await fileTypeFromFile(filePath))?.ext const ext = (await ctx.ntFileApi.getFileType(filePath)).ext
filename += `.${ext}` filename += `.${ext}`
await fsPromise.rename(filePath, `${filePath}.${ext}`) await fsPromise.rename(filePath, `${filePath}.${ext}`)
filePath = `${filePath}.${ext}` filePath = `${filePath}.${ext}`
@@ -173,12 +175,12 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
const capture = /^data:([\w/.+-]+);base64,(.*)$/.exec(uri) const capture = /^data:([\w/.+-]+);base64,(.*)$/.exec(uri)
if (capture) { if (capture) {
filename ??= randomUUID() let filename = randomUUID()
const [, _type, base64] = capture const [, _type, base64] = capture
let filePath = path.join(TEMP_DIR, filename) let filePath = path.join(TEMP_DIR, filename)
await fsPromise.writeFile(filePath, base64, 'base64') await fsPromise.writeFile(filePath, base64, 'base64')
if (needExt) { if (needExt) {
const ext = (await fileTypeFromFile(filePath))?.ext const ext = (await ctx.ntFileApi.getFileType(filePath)).ext
filename += `.${ext}` filename += `.${ext}`
await fsPromise.rename(filePath, `${filePath}.${ext}`) await fsPromise.rename(filePath, `${filePath}.${ext}`)
filePath = `${filePath}.${ext}` filePath = `${filePath}.${ext}`
@@ -189,26 +191,3 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
return { success: false, errMsg: '未知文件类型', fileName: '', path: '', isLocal: false } return { success: false, errMsg: '未知文件类型', fileName: '', path: '', isLocal: false }
} }
export async function copyFolder(sourcePath: string, destPath: string) {
try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true })
await fsPromise.mkdir(destPath, { recursive: true })
for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name)
const dstPath = path.join(destPath, entry.name)
if (entry.isDirectory()) {
await copyFolder(srcPath, dstPath)
} else {
try {
await fsPromise.copyFile(srcPath, dstPath)
} catch (error) {
console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`)
// 这里可以决定是否要继续复制其他文件
}
}
}
} catch (error) {
console.error('复制文件夹时出错:', error)
}
}

View File

@@ -2,24 +2,7 @@ import fs from 'fs'
import path from 'node:path' import path from 'node:path'
import { getConfigUtil } from '../config' import { getConfigUtil } from '../config'
import { LOG_DIR } from '../globalVars' import { LOG_DIR } from '../globalVars'
import { Dict } from 'cosmokit' import { inspect } from 'node:util'
function truncateString(obj: Dict | null, maxLength = 500) {
if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') {
// 如果是字符串且超过指定长度,则截断
if (obj[key].length > maxLength) {
obj[key] = obj[key].substring(0, maxLength) + '...'
}
} else if (typeof obj[key] === 'object') {
// 如果是对象或数组,则递归调用
truncateString(obj[key], maxLength)
}
})
}
return obj
}
export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-') export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-')
@@ -29,12 +12,16 @@ export function log(...msg: unknown[]) {
} }
let logMsg = '' let logMsg = ''
for (const msgItem of msg) { for (const msgItem of msg) {
// 判断是否是对象
if (typeof msgItem === 'object') { if (typeof msgItem === 'object') {
logMsg += JSON.stringify(truncateString(msgItem)) + ' ' logMsg += inspect(msgItem, {
continue depth: 10,
compact: true,
breakLength: Infinity,
maxArrayLength: 220
}) + ' '
} else {
logMsg += msgItem + ' '
} }
logMsg += msgItem + ' '
} }
const currentDateTime = new Date().toLocaleString() const currentDateTime = new Date().toLocaleString()
logMsg = `${currentDateTime} ${logMsg}\n\n` logMsg = `${currentDateTime} ${logMsg}\n\n`

View File

@@ -1,163 +0,0 @@
import fsPromise from 'node:fs/promises'
import fs from 'node:fs'
import path from 'node:path'
import Database, { Tables } from 'minato'
import SQLite from '@minatojs/driver-sqlite'
import { Peer } from '@/ntqqapi/types'
import { createHash } from 'node:crypto'
import { LimitedHashTable } from './table'
import { DATA_DIR } from '../globalVars'
import { FileCacheV2 } from '../types'
interface SQLiteTables extends Tables {
message: {
shortId: number
msgId: string
chatType: number
peerUid: string
}
file_v2: FileCacheV2
}
interface MsgIdAndPeerByShortId {
MsgId: string
Peer: Peer
}
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/common/utils/MessageUnique.ts#L84
class MessageUniqueWrapper {
private msgDataMap: LimitedHashTable<string, number>
private msgIdMap: LimitedHashTable<string, number>
private db: Database<SQLiteTables> | undefined
constructor(maxMap: number = 1000) {
this.msgIdMap = new LimitedHashTable<string, number>(maxMap)
this.msgDataMap = new LimitedHashTable<string, number>(maxMap)
}
async init(uin: string) {
const dbDir = path.join(DATA_DIR, 'database')
if (!fs.existsSync(dbDir)) {
await fsPromise.mkdir(dbDir)
}
const database = new Database<SQLiteTables>()
await database.connect(SQLite, {
path: path.join(dbDir, `${uin}.db`)
})
database.extend('message', {
shortId: 'integer(10)',
chatType: 'unsigned',
msgId: 'string(24)',
peerUid: 'string(24)'
}, {
primary: 'shortId'
})
database.extend('file_v2', {
fileName: 'string',
fileSize: 'string',
fileUuid: 'string(128)',
msgId: 'string(24)',
msgTime: 'unsigned(10)',
peerUid: 'string(24)',
chatType: 'unsigned',
elementId: 'string(24)',
elementType: 'unsigned',
}, {
primary: 'fileUuid',
indexes: ['fileName']
})
this.db = database
}
async getRecentMsgIds(Peer: Peer, size: number): Promise<string[]> {
const heads = this.msgIdMap.getHeads(size)
if (!heads) {
return []
}
const data: (MsgIdAndPeerByShortId | undefined)[] = []
for (const t of heads) {
data.push(await MessageUnique.getMsgIdAndPeerByShortId(t.value))
}
const ret = data.filter((t) => t?.Peer.chatType === Peer.chatType && t?.Peer.peerUid === Peer.peerUid)
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined)
}
createMsg(peer: Peer, msgId: string): number {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`
const hash = createHash('md5').update(key).digest()
//设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f
const shortId = hash.readInt32BE(0)
//减少性能损耗
// const isExist = this.msgIdMap.getKey(shortId)
// if (isExist && isExist === msgId) {
// return shortId
// }
this.msgIdMap.set(msgId, shortId)
this.msgDataMap.set(key, shortId)
this.db?.upsert('message', [{
msgId,
shortId,
chatType: peer.chatType,
peerUid: peer.peerUid
}], 'shortId').then()
return shortId
}
async getMsgIdAndPeerByShortId(shortId: number): Promise<MsgIdAndPeerByShortId | undefined> {
const data = this.msgDataMap.getKey(shortId)
if (data) {
const [msgId, chatTypeStr, peerUid] = data.split('|')
const peer: Peer = {
chatType: parseInt(chatTypeStr),
peerUid,
guildId: '',
}
return { MsgId: msgId, Peer: peer }
}
const items = await this.db?.get('message', { shortId })
if (items?.length) {
const { msgId, chatType, peerUid } = items[0]
return {
MsgId: msgId,
Peer: {
chatType,
peerUid,
guildId: '',
}
}
}
return undefined
}
getShortIdByMsgId(msgId: string): number | undefined {
return this.msgIdMap.getValue(msgId)
}
async getPeerByMsgId(msgId: string) {
const shortId = this.msgIdMap.getValue(msgId)
if (!shortId) return undefined
return await this.getMsgIdAndPeerByShortId(shortId)
}
resize(maxSize: number): void {
this.msgIdMap.resize(maxSize)
this.msgDataMap.resize(maxSize)
}
addFileCache(data: FileCacheV2) {
return this.db?.upsert('file_v2', [data], 'fileUuid')
}
getFileCacheByName(fileName: string) {
return this.db?.get('file_v2', { fileName }, {
sort: { msgTime: 'desc' }
})
}
getFileCacheById(fileUuid: string) {
return this.db?.get('file_v2', { fileUuid })
}
}
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper()

View File

@@ -1,5 +1,5 @@
import { QQLevel } from '@/ntqqapi/types' import { QQLevel } from '@/ntqqapi/types'
import { Dict } from 'cosmokit' import { Dict, isNullable } from 'cosmokit'
export function isNumeric(str: string) { export function isNumeric(str: string) {
return /^\d+$/.test(str) return /^\d+$/.test(str)
@@ -12,8 +12,9 @@ export function calcQQLevel(level: QQLevel) {
/** QQ Build Version */ /** QQ Build Version */
export function getBuildVersion(): number { export function getBuildVersion(): number {
const version: string = globalThis.LiteLoader.versions.qqnt //const version: string = globalThis.LiteLoader.versions.qqnt
return +version.split('-')[1] //return +version.split('-')[1]
return +globalThis.LiteLoader.package.qqnt.buildVersion
} }
/** 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 */ /** 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 */
@@ -32,4 +33,15 @@ export function mergeNewProperties(newObj: Dict, oldObj: Dict) {
} }
} }
}) })
} }
export function filterNullable<T>(array: T[]) {
return array.filter(e => !isNullable(e)) as NonNullable<T>[]
}
export function parseBool(value: string) {
if (['', 'true', '1'].includes(value)) {
return true
}
return false
}

View File

@@ -1,98 +1,73 @@
import path from 'node:path' import path from 'node:path'
import compressing from 'compressing'
import { writeFile } from 'node:fs/promises' import { writeFile } from 'node:fs/promises'
import { version } from '../../version' import { version } from '../../version'
import { copyFolder, log, fetchFile } from '.' import { log, fetchFile } from '.'
import { PLUGIN_DIR, TEMP_DIR } from '../globalVars' import { TEMP_DIR } from '../globalVars'
import { compare } from 'compare-versions'
const downloadMirrorHosts = ['https://mirror.ghproxy.com/'] const downloadMirrorHosts = ['https://ghp.ci/']
const checkVersionMirrorHosts = ['https://kkgithub.com'] const releasesMirrorHosts = ['https://kkgithub.com']
export async function checkNewVersion() { export async function checkNewVersion() {
const latestVersionText = await getRemoteVersion() const latestVersion = await getRemoteVersion()
const latestVersion = latestVersionText.split('.') log('LLOneBot latest version', latestVersion)
//log('llonebot last version', latestVersion) if (latestVersion === '') {
const currentVersion: string[] = version.split('.') return { result: false, version: latestVersion }
//log('llonebot current version', currentVersion) }
for (const k of [0, 1, 2]) { if (compare(latestVersion, version, '>')) {
if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) { return { result: true, version: latestVersion }
log('')
return { result: true, version: latestVersionText }
} else if (parseInt(latestVersion[k]) < parseInt(currentVersion[k])) {
break
}
} }
return { result: false, version: version } return { result: false, version: version }
} }
export async function upgradeLLOneBot() { export async function upgradeLLOneBot(): Promise<boolean> {
const latestVersion = await getRemoteVersion() const latestVersion = await getRemoteVersion()
if (latestVersion && latestVersion != '') { if (latestVersion && latestVersion != '') {
const downloadUrl = 'https://github.com/LLOneBot/LLOneBot/releases/download/v' + latestVersion + '/LLOneBot.zip' const downloadUrl = `https://github.com/LLOneBot/LLOneBot/releases/download/v${latestVersion}/LLOneBot.zip`
const filePath = path.join(TEMP_DIR, './update-' + latestVersion + '.zip') const filePath = path.join(TEMP_DIR, './update-' + latestVersion + '.zip')
let downloadSuccess = false
// 多镜像下载 // 多镜像下载
for (const mirrorGithub of downloadMirrorHosts) { for (const mirrorGithub of downloadMirrorHosts) {
try { try {
const res = await fetchFile(mirrorGithub + downloadUrl) const res = await fetchFile(mirrorGithub + downloadUrl)
await writeFile(filePath, res.data) await writeFile(filePath, res.data)
downloadSuccess = true return globalThis.LiteLoader.api.plugin.install(filePath)
break
} catch (e) { } catch (e) {
log('llonebot upgrade error', e) log('llonebot upgrade error', e)
} }
} }
if (!downloadSuccess) {
log('llonebot upgrade error', 'download failed')
return false
}
const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion)
const uncompressedPromise = async function () {
return new Promise<boolean>(resolve => {
compressing.zip
.uncompress(filePath, temp_ver_dir)
.then(() => {
resolve(true)
})
.catch(reason => {
log('llonebot upgrade failed, ', reason)
if (reason?.errno == -4082) {
resolve(true)
}
resolve(false)
})
})
}
const uncompressedResult = await uncompressedPromise()
// 复制文件
await copyFolder(temp_ver_dir, PLUGIN_DIR)
return uncompressedResult
} }
return false return false
} }
export async function getRemoteVersion() { export async function getRemoteVersion() {
let Version = '' for (const mirror of releasesMirrorHosts) {
for (let i = 0; i < checkVersionMirrorHosts.length; i++) { const version = await getRemoteVersionByReleasesMirror(mirror)
const mirrorGithub = checkVersionMirrorHosts[i] if (version) {
const tVersion = await getRemoteVersionByMirror(mirrorGithub) return version
if (tVersion && tVersion != '') { }
Version = tVersion }
break for (const mirror of downloadMirrorHosts) {
const version = await getRemoteVersionByDownloadMirror(mirror)
if (version) {
return version
} }
} }
return Version
}
export async function getRemoteVersionByMirror(mirrorGithub: string) {
let releasePage = 'error'
try {
releasePage = (await fetchFile(mirrorGithub + '/LLOneBot/LLOneBot/releases')).data.toString()
// log("releasePage", releasePage);
if (releasePage === 'error') return ''
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
} catch { }
return '' return ''
} }
export async function getRemoteVersionByDownloadMirror(mirrorGithub: string) {
try {
const source = 'https://raw.githubusercontent.com/LLOneBot/LLOneBot/main/src/version.ts'
const page = (await fetchFile(mirrorGithub + source)).data.toString()
return page.match(/(\d+\.\d+\.\d+)/)?.[0]
} catch (e) {
log(e?.toString())
}
}
export async function getRemoteVersionByReleasesMirror(mirrorGithub: string) {
try {
const page = (await fetchFile(mirrorGithub + '/LLOneBot/LLOneBot/releases')).data.toString()
return page.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
} catch { }
}

2
src/global.d.ts vendored
View File

@@ -6,4 +6,4 @@ declare global {
var LiteLoader: Dict var LiteLoader: Dict
var authData: Dict | undefined var authData: Dict | undefined
var navigation: Dict | undefined var navigation: Dict | undefined
} }

View File

@@ -23,14 +23,13 @@ export default class Log {
return return
} }
const dateTime = new Date(record.timestamp).toLocaleString() const dateTime = new Date(record.timestamp).toLocaleString()
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : '' const content = `${dateTime} [${record.type}] ${selfInfo.nick}(${selfInfo.uin}) | ${record.name} ${record.content}\n\n`
const content = `${dateTime} [${record.type}] ${userInfo} | ${record.name} ${record.content}\n\n`
appendFile(file, content, noop) appendFile(file, content, noop)
}, },
} }
Logger.targets.push(target) Logger.targets.push(target)
ctx.on('llonebot/config-updated', input => { ctx.on('llob/config-updated', input => {
enable = input.log! enable = input.log!
}) })
} }
} }

View File

@@ -1,8 +1,11 @@
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs'
import Log from './log' import Log from './log'
import Core from '../ntqqapi/core' import Core from '../ntqqapi/core'
import OneBot11Adapter from '../onebot11/adapter' import OneBot11Adapter from '../onebot11/adapter'
import SatoriAdapter from '../satori/adapter'
import Database from 'minato'
import SQLiteDriver from '@minatojs/driver-sqlite'
import Store from './store'
import { BrowserWindow, dialog, ipcMain } from 'electron' import { BrowserWindow, dialog, ipcMain } from 'electron'
import { Config as LLOBConfig } from '../common/types' import { Config as LLOBConfig } from '../common/types'
import { import {
@@ -15,12 +18,10 @@ import {
CHANNEL_UPDATE, CHANNEL_UPDATE,
CHANNEL_SET_CONFIG_CONFIRMED CHANNEL_SET_CONFIG_CONFIRMED
} from '../common/channels' } from '../common/channels'
import { getBuildVersion } from '../common/utils'
import { hookNTQQApiCall, hookNTQQApiReceive } from '../ntqqapi/hook' import { hookNTQQApiCall, hookNTQQApiReceive } from '../ntqqapi/hook'
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade' import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
import { getConfigUtil } from '../common/config' import { getConfigUtil } from '../common/config'
import { checkFfmpeg } from '../common/utils/video' import { checkFfmpeg } from '../common/utils/video'
import { getSession } from '../ntqqapi/wrapper'
import { Context } from 'cordis' import { Context } from 'cordis'
import { llonebotError, selfInfo, LOG_DIR, DATA_DIR, TEMP_DIR } from '../common/globalVars' import { llonebotError, selfInfo, LOG_DIR, DATA_DIR, TEMP_DIR } from '../common/globalVars'
import { log, logFileName } from '../common/utils/legacyLog' import { log, logFileName } from '../common/utils/legacyLog'
@@ -34,10 +35,12 @@ import {
NTQQWebApi, NTQQWebApi,
NTQQWindowApi NTQQWindowApi
} from '../ntqqapi/api' } from '../ntqqapi/api'
import { mkdir } from 'node:fs/promises'
import { existsSync, mkdirSync } from 'node:fs'
declare module 'cordis' { declare module 'cordis' {
interface Events { interface Events {
'llonebot/config-updated': (input: LLOBConfig) => void 'llob/config-updated': (input: LLOBConfig) => void
} }
} }
@@ -45,12 +48,12 @@ let mainWindow: BrowserWindow | null = null
// 加载插件时触发 // 加载插件时触发
function onLoad() { function onLoad() {
if (!fs.existsSync(DATA_DIR)) { if (!existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true }) mkdirSync(DATA_DIR, { recursive: true })
} }
if (!fs.existsSync(LOG_DIR)) { if (!existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR) mkdirSync(LOG_DIR)
} }
ipcMain.handle(CHANNEL_CHECK_VERSION, async () => { ipcMain.handle(CHANNEL_CHECK_VERSION, async () => {
@@ -146,13 +149,12 @@ function onLoad() {
async function start() { async function start() {
log('process pid', process.pid) log('process pid', process.pid)
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
if (!config.enableLLOB) { if (!existsSync(TEMP_DIR)) {
llonebotError.otherError = 'LLOneBot 未启动' await mkdir(TEMP_DIR)
log('LLOneBot 开关设置为关闭不启动LLOneBot')
return
} }
if (!fs.existsSync(TEMP_DIR)) { const dbDir = path.join(DATA_DIR, 'database')
fs.mkdirSync(TEMP_DIR) if (!existsSync(dbDir)) {
await mkdir(dbDir)
} }
const ctx = new Context() const ctx = new Context()
ctx.plugin(Log, { ctx.plugin(Log, {
@@ -168,32 +170,45 @@ function onLoad() {
ctx.plugin(NTQQWebApi) ctx.plugin(NTQQWebApi)
ctx.plugin(NTQQWindowApi) ctx.plugin(NTQQWindowApi)
ctx.plugin(Core, config) ctx.plugin(Core, config)
ctx.plugin(OneBot11Adapter, { ctx.plugin(Database)
...config.ob11, ctx.plugin(SQLiteDriver, {
heartInterval: config.heartInterval, path: path.join(dbDir, `${selfInfo.uin}.db`)
token: config.token!,
debug: config.debug!,
reportSelfMessage: config.reportSelfMessage!,
msgCacheExpire: config.msgCacheExpire!,
musicSignUrl: config.musicSignUrl,
enableLocalFile2Url: config.enableLocalFile2Url!,
ffmpeg: config.ffmpeg,
}) })
ctx.plugin(Store, {
msgCacheExpire: config.msgCacheExpire! * 1000
})
if (config.ob11.enable) {
ctx.plugin(OneBot11Adapter, {
...config.ob11,
heartInterval: config.heartInterval,
token: config.token!,
debug: config.debug!,
reportSelfMessage: config.reportSelfMessage!,
musicSignUrl: config.musicSignUrl,
enableLocalFile2Url: config.enableLocalFile2Url!,
ffmpeg: config.ffmpeg,
})
}
if (config.satori.enable) {
ctx.plugin(SatoriAdapter, {
...config.satori,
ffmpeg: config.ffmpeg,
})
}
ctx.start() ctx.start()
llonebotError.otherError = ''
ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => { ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => {
ctx.parallel('llonebot/config-updated', config) ctx.parallel('llob/config-updated', config)
}) })
} }
const buildVersion = getBuildVersion()
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
const self = Object.assign(selfInfo, { const self = Object.assign(selfInfo, {
uin: globalThis.authData?.uin, uin: globalThis.authData?.uin,
uid: globalThis.authData?.uid, uid: globalThis.authData?.uid,
online: true online: true
}) })
if (self.uin && (buildVersion >= 27187 || getSession())) { if (self.uin) {
clearInterval(intervalId) clearInterval(intervalId)
start() start()
} }

153
src/main/store.ts Normal file
View File

@@ -0,0 +1,153 @@
import { Peer, RawMessage } from '@/ntqqapi/types'
import { createHash } from 'node:crypto'
import { LimitedHashTable } from '@/common/utils/table'
import { FileCacheV2 } from '@/common/types'
import { Context, Service } from 'cordis'
declare module 'cordis' {
interface Context {
store: Store
}
interface Tables {
message: {
shortId: number
msgId: string
chatType: number
peerUid: string
}
file_v2: FileCacheV2
}
}
interface MsgInfo {
msgId: string
peer: Peer
}
class Store extends Service {
static inject = ['database', 'model']
private cache: LimitedHashTable<string, number>
private messages: Map<string, RawMessage>
constructor(protected ctx: Context, public config: Store.Config) {
super(ctx, 'store', true)
this.cache = new LimitedHashTable(1000)
this.messages = new Map()
this.initDatabase()
}
private async initDatabase() {
this.ctx.model.extend('message', {
shortId: 'integer(10)',
chatType: 'unsigned',
msgId: 'string(24)',
peerUid: 'string(24)'
}, {
primary: 'shortId'
})
this.ctx.model.extend('file_v2', {
fileName: 'string',
fileSize: 'string',
fileUuid: 'string(128)',
msgId: 'string(24)',
msgTime: 'unsigned(10)',
peerUid: 'string(24)',
chatType: 'unsigned',
elementId: 'string(24)',
elementType: 'unsigned',
}, {
primary: 'fileUuid',
indexes: ['fileName']
})
}
createMsgShortId(peer: Peer, msgId: string): number {
const cacheKey = `${msgId}|${peer.chatType}|${peer.peerUid}`
const hash = createHash('md5').update(cacheKey).digest()
hash[0] &= 0x7f //设置第一个bit为0 保证shortId为正数
const shortId = hash.readInt32BE()
this.cache.set(cacheKey, shortId)
this.ctx.database.upsert('message', [{
msgId,
shortId,
chatType: peer.chatType,
peerUid: peer.peerUid
}], 'shortId').then()
return shortId
}
async getMsgInfoByShortId(shortId: number): Promise<MsgInfo | undefined> {
const data = this.cache.getKey(shortId)
if (data) {
const [msgId, chatTypeStr, peerUid] = data.split('|')
return {
msgId,
peer: {
chatType: +chatTypeStr,
peerUid,
guildId: ''
}
}
}
const items = await this.ctx.database.get('message', { shortId })
if (items?.length) {
const { msgId, chatType, peerUid } = items[0]
return {
msgId,
peer: {
chatType,
peerUid,
guildId: ''
}
}
}
}
async getShortIdByMsgId(msgId: string): Promise<number | undefined> {
return (await this.ctx.database.get('message', { msgId }))[0]?.shortId
}
getShortIdByMsgInfo(peer: Peer, msgId: string) {
const cacheKey = `${msgId}|${peer.chatType}|${peer.peerUid}`
return this.cache.getValue(cacheKey)
}
addFileCache(data: FileCacheV2) {
return this.ctx.database.upsert('file_v2', [data], 'fileUuid')
}
getFileCacheByName(fileName: string) {
return this.ctx.database.get('file_v2', { fileName }, {
sort: { msgTime: 'desc' }
})
}
getFileCacheById(fileUuid: string) {
return this.ctx.database.get('file_v2', { fileUuid })
}
async addMsgCache(msg: RawMessage) {
const expire = this.config.msgCacheExpire
if (expire === 0) {
return
}
const id = msg.msgId
this.messages.set(id, msg)
setTimeout(() => {
this.messages.delete(id)
}, expire)
}
getMsgCache(msgId: string) {
return this.messages.get(msgId)
}
}
namespace Store {
export interface Config {
/** 单位为毫秒 */
msgCacheExpire: number
}
}
export default Store

View File

@@ -13,18 +13,14 @@ import {
PicElement, PicElement,
} from '../types' } from '../types'
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import { existsSync } from 'node:fs'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { RkeyManager } from '@/ntqqapi/helper/rkey' import { RkeyManager } from '@/ntqqapi/helper/rkey'
import { getSession } from '@/ntqqapi/wrapper' import { OnRichMediaDownloadCompleteParams, Peer } from '@/ntqqapi/types/msg'
import { Peer } from '@/ntqqapi/types/msg'
import { calculateFileMD5 } from '@/common/utils/file' import { calculateFileMD5 } from '@/common/utils/file'
import { fileTypeFromFile } from 'file-type' import { copyFile, stat, unlink } from 'node:fs/promises'
import fsPromise from 'node:fs/promises'
import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { TEMP_DIR } from '@/common/globalVars'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@@ -41,53 +37,42 @@ export class NTQQFileApi extends Service {
this.rkeyManager = new RkeyManager(ctx, 'https://llob.linyuchen.net/rkey') this.rkeyManager = new RkeyManager(ctx, 'https://llob.linyuchen.net/rkey')
} }
async getVideoUrl(peer: Peer, msgId: string, elementId: string) { async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string | undefined> {
const session = getSession() const data = await invoke('nodeIKernelRichMediaService/getVideoPlayUrlV2', [{
if (session) { peer,
return (await session.getRichMediaService().getVideoPlayUrlV2( msgId,
peer, elemId: elementId,
msgId, videoCodecFormat: 0,
elementId, params: {
0, downSourceType: 1,
{ downSourceType: 1, triggerType: 1 } triggerType: 1
)).urlResult.domainUrl[0]?.url
} else {
const data = await invoke('nodeIKernelRichMediaService/getVideoPlayUrlV2', [{
peer,
msgId,
elemId: elementId,
videoCodecFormat: 0,
exParams: {
downSourceType: 1,
triggerType: 1
},
}, null])
if (data.result !== 0) {
this.ctx.logger.warn('getVideoUrl', data)
} }
return data.urlResult.domainUrl[0]?.url }])
if (data.result !== 0) {
this.ctx.logger.warn('getVideoUrl', data)
} }
return data.urlResult.domainUrl[0]?.url
} }
async getFileType(filePath: string) { async getFileType(filePath: string) {
return fileTypeFromFile(filePath) return await invoke<{
ext: string
mime: string
}>(NTMethod.FILE_TYPE, [filePath], {
className: NTClass.FS_API
})
} }
// 上传文件到QQ的文件夹 /** 上传文件到 QQ 的文件夹 */
async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType = 0) { async uploadFile(filePath: string, elementType = ElementType.Pic, elementSubType = 0) {
const fileMd5 = await calculateFileMD5(filePath) const fileMd5 = await calculateFileMD5(filePath)
let ext = (await this.getFileType(filePath))?.ext || '' let fileName = path.basename(filePath)
if (ext) { if (!fileName.includes('.')) {
ext = '.' + ext const ext = (await this.getFileType(filePath))?.ext
fileName += ext ? '.' + ext : ''
} }
let fileName = `${path.basename(filePath)}` const mediaPath = await invoke(NTMethod.MEDIA_FILE_PATH, [{
if (fileName.indexOf('.') === -1) { path_info: {
fileName += ext
}
const session = getSession()
let mediaPath: string
if (session) {
mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({
md5HexStr: fileMd5, md5HexStr: fileMd5,
fileName: fileName, fileName: fileName,
elementType: elementType, elementType: elementType,
@@ -95,30 +80,16 @@ export class NTQQFileApi extends Service {
thumbSize: 0, thumbSize: 0,
needCreate: true, needCreate: true,
downloadType: 1, downloadType: 1,
file_uuid: '' file_uuid: '',
}) },
} else { }])
mediaPath = await invoke(NTMethod.MEDIA_FILE_PATH, [{ await copyFile(filePath, mediaPath)
path_info: { const fileSize = (await stat(filePath)).size
md5HexStr: fileMd5,
fileName: fileName,
elementType: elementType,
elementSubType,
thumbSize: 0,
needCreate: true,
downloadType: 1,
file_uuid: '',
},
}])
}
await fsPromise.copyFile(filePath, mediaPath)
const fileSize = (await fsPromise.stat(filePath)).size
return { return {
md5: fileMd5, md5: fileMd5,
fileName, fileName,
path: mediaPath, path: mediaPath,
fileSize, fileSize,
ext
} }
} }
@@ -127,16 +98,16 @@ export class NTQQFileApi extends Service {
chatType: ChatType, chatType: ChatType,
peerUid: string, peerUid: string,
elementId: string, elementId: string,
thumbPath: string, thumbPath = '',
sourcePath: string, sourcePath = '',
timeout = 1000 * 60 * 2, timeout = 1000 * 60 * 2,
force = false force = false
) { ) {
// 用于下载收到的消息中的图片等 // 用于下载收到的消息中的图片等
if (sourcePath && fs.existsSync(sourcePath)) { if (sourcePath && existsSync(sourcePath)) {
if (force) { if (force) {
try { try {
await fsPromise.unlink(sourcePath) await unlink(sourcePath)
} catch { } } catch { }
} else { } else {
return sourcePath return sourcePath
@@ -144,40 +115,35 @@ export class NTQQFileApi extends Service {
} }
const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>( const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>(
'nodeIKernelMsgService/downloadRichMedia', 'nodeIKernelMsgService/downloadRichMedia',
[ [{
{ getReq: {
getReq: { fileModelId: '0',
fileModelId: '0', downloadSourceType: 0,
downloadSourceType: 0, triggerType: 1,
triggerType: 1, msgId: msgId,
msgId: msgId, chatType: chatType,
chatType: chatType, peerUid: peerUid,
peerUid: peerUid, elementId: elementId,
elementId: elementId, thumbSize: 0,
thumbSize: 0, downloadType: 1,
downloadType: 1, filePath: thumbPath,
filePath: thumbPath,
},
}, },
null, }],
],
{ {
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: payload => payload.notifyInfo.msgId === msgId, cmdCB: payload => payload.notifyInfo.msgId === msgId,
timeout timeout
} }
) )
let filePath = data.notifyInfo.filePath return data.notifyInfo.filePath
if (filePath.startsWith('\\')) {
const downloadPath = TEMP_DIR
filePath = path.join(downloadPath, filePath)
// 下载路径是下载文件夹的相对路径
}
return filePath
} }
async getImageSize(filePath: string) { async getImageSize(filePath: string) {
return await invoke<{ width: number; height: number }>( return await invoke<{
width: number
height: number
type: string
}>(
NTMethod.IMAGE_SIZE, NTMethod.IMAGE_SIZE,
[filePath], [filePath],
{ {
@@ -197,8 +163,8 @@ export class NTQQFileApi extends Service {
if (url) { if (url) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接 const parsedUrl = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接
const imageAppid = parsedUrl.searchParams.get('appid') const imageAppid = parsedUrl.searchParams.get('appid')
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid) const isNTPic = imageAppid && ['1406', '1407'].includes(imageAppid)
if (isNewPic) { if (isNTPic) {
let rkey = parsedUrl.searchParams.get('rkey') let rkey = parsedUrl.searchParams.get('rkey')
if (rkey) { if (rkey) {
return IMAGE_HTTP_HOST_NT + url return IMAGE_HTTP_HOST_NT + url
@@ -217,6 +183,24 @@ export class NTQQFileApi extends Service {
this.ctx.logger.error('图片url获取失败', element) this.ctx.logger.error('图片url获取失败', element)
return '' return ''
} }
async downloadFileForModelId(peer: Peer, fileModelId: string, timeout = 2 * Time.minute) {
const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>(
'nodeIKernelRichMediaService/downloadFileForModelId',
[{
peer,
fileModelIdList: [fileModelId],
save_path: ''
}],
{
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: payload => payload.notifyInfo.fileModelId === fileModelId,
timeout,
afterFirstCmd: false
}
)
return data.notifyInfo.filePath
}
} }
export class NTQQFileCacheApi extends Service { export class NTQQFileCacheApi extends Service {
@@ -225,21 +209,19 @@ export class NTQQFileCacheApi extends Service {
} }
async setCacheSilentScan(isSilent: boolean = true) { async setCacheSilentScan(isSilent: boolean = true) {
return await invoke<GeneralCallResult>(NTMethod.CACHE_SET_SILENCE, [{ isSilent }, null]) return await invoke<GeneralCallResult>(NTMethod.CACHE_SET_SILENCE, [{ isSilent }])
} }
getCacheSessionPathList() { getCacheSessionPathList() {
return invoke< return invoke<Array<{
{ key: string
key: string value: string
value: string }>>(NTMethod.CACHE_PATH_SESSION, [], { className: NTClass.OS_API })
}[]
>(NTMethod.CACHE_PATH_SESSION, [], { className: NTClass.OS_API })
} }
scanCache() { scanCache() {
invoke<GeneralCallResult>(ReceiveCmdS.CACHE_SCAN_FINISH, [], { classNameIsRegister: true }) invoke<GeneralCallResult>(ReceiveCmdS.CACHE_SCAN_FINISH, [], { registerEvent: true })
return invoke<CacheScanResult>(NTMethod.CACHE_SCAN, [null, null], { timeout: 300 * Time.second }) return invoke<CacheScanResult>(NTMethod.CACHE_SCAN, [], { timeout: 300 * Time.second })
} }
getHotUpdateCachePath() { getHotUpdateCachePath() {
@@ -259,13 +241,13 @@ export class NTQQFileCacheApi extends Service {
pageSize: pageSize, pageSize: pageSize,
order: 1, order: 1,
lastRecord: _lastRecord, lastRecord: _lastRecord,
}, null]) }])
} }
async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
return await invoke<GeneralCallResult>(NTMethod.CACHE_CHAT_CLEAR, [{ return await invoke<GeneralCallResult>(NTMethod.CACHE_CHAT_CLEAR, [{
chats, chats,
fileKeys, fileKeys,
}, null]) }])
} }
} }

View File

@@ -1,9 +1,6 @@
import { Friend, FriendV2, SimpleInfo, CategoryFriend } from '../types' import { Friend, SimpleInfo, CategoryFriend } from '../types'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { invoke, NTMethod, NTClass } from '../ntcall' import { invoke, NTMethod, NTClass } from '../ntcall'
import { getSession } from '@/ntqqapi/wrapper'
import { BuddyListReqType } from '../services'
import { Dict, pick } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
declare module 'cordis' { declare module 'cordis' {
@@ -26,15 +23,11 @@ export class NTQQFriendApi extends Service {
categroyMbCount: number categroyMbCount: number
buddyList: Friend[] buddyList: Friend[]
}[] }[]
}>( }>('getBuddyList', [], {
'getBuddyList', className: NTClass.NODE_STORE_API,
[], cbCmd: ReceiveCmdS.FRIENDS,
{ afterFirstCmd: false
className: NTClass.NODE_STORE_API, })
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}
)
const _friends: Friend[] = [] const _friends: Friend[] = []
for (const item of data.data) { for (const item of data.data) {
_friends.push(...item.buddyList) _friends.push(...item.buddyList)
@@ -42,151 +35,85 @@ export class NTQQFriendApi extends Service {
return _friends return _friends
} }
async handleFriendRequest(flag: string, accept: boolean) { async handleFriendRequest(friendUid: string, reqTime: string, accept: boolean) {
const data = flag.split('|') return await invoke(NTMethod.HANDLE_FRIEND_REQUEST, [{
if (data.length < 2) { approvalInfo: {
return
}
const friendUid = data[0]
const reqTime = data[1]
const session = getSession()
if (session) {
return session.getBuddyService().approvalFriendRequest({
friendUid, friendUid,
reqTime, reqTime,
accept accept,
}) },
} else { }])
return await invoke(NTMethod.HANDLE_FRIEND_REQUEST, [{
approvalInfo: {
friendUid,
reqTime,
accept,
},
}])
}
} }
async getBuddyV2(refresh = false): Promise<FriendV2[]> { async getBuddyV2(refresh = false): Promise<SimpleInfo[]> {
const session = getSession() const data = await invoke<{
if (session) { buddyCategory: CategoryFriend[]
const uids: string[] = [] userSimpleInfos: Record<string, SimpleInfo>
const buddyService = session.getBuddyService() }>(
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) 'getBuddyList',
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) [refresh],
const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids) {
return Array.from(data.values()) className: NTClass.NODE_STORE_API,
} else { cbCmd: ReceiveCmdS.FRIENDS,
const data = await invoke<{ afterFirstCmd: false,
buddyCategory: CategoryFriend[] }
userSimpleInfos: Record<string, SimpleInfo> )
}>( const uids = data.buddyCategory.flatMap(item => item.buddyUids)
'getBuddyList', return Object.values(data.userSimpleInfos).filter(v => uids.includes(v.uid!))
[refresh],
{
className: NTClass.NODE_STORE_API,
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}
)
const uids = data.buddyCategory.flatMap(item => item.buddyUids)
return Object.values(data.userSimpleInfos).filter(v => uids.includes(v.uid!))
}
} }
/** uid => uin */ /** uid => uin */
async getBuddyIdMap(refresh = false): Promise<Map<string, string>> { async getBuddyIdMap(refresh = false): Promise<Map<string, string>> {
const retMap: Map<string, string> = new Map() const retMap: Map<string, string> = new Map()
const session = getSession() const data = await invoke<{
if (session) { buddyCategory: CategoryFriend[]
const uids: string[] = [] userSimpleInfos: Record<string, SimpleInfo>
const buddyService = session.getBuddyService() }>(
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) 'getBuddyList',
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) [refresh],
const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids) {
for (const [, item] of data) { className: NTClass.NODE_STORE_API,
if (retMap.size > 5000) { cbCmd: ReceiveCmdS.FRIENDS,
break afterFirstCmd: false,
}
retMap.set(item.uid!, item.uin!)
} }
} else { )
const data = await invoke<{ for (const item of Object.values(data.userSimpleInfos)) {
buddyCategory: CategoryFriend[] if (retMap.size > 5000) {
userSimpleInfos: Record<string, SimpleInfo> break
}>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API,
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}
)
for (const item of Object.values(data.userSimpleInfos)) {
if (retMap.size > 5000) {
break
}
retMap.set(item.uid!, item.uin!)
} }
retMap.set(item.uid!, item.uin!)
} }
return retMap return retMap
} }
async getBuddyV2ExWithCate(refresh = false) { async getBuddyV2WithCate(refresh = false) {
const session = getSession() const data = await invoke<{
if (session) { buddyCategory: CategoryFriend[]
const uids: string[] = [] userSimpleInfos: Record<string, SimpleInfo>
const categoryMap: Map<string, Dict> = new Map() }>(
const buddyService = session.getBuddyService() 'getBuddyList',
const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data [refresh],
uids.push( {
...buddyListV2.flatMap(item => { className: NTClass.NODE_STORE_API,
item.buddyUids.forEach(uid => { cbCmd: ReceiveCmdS.FRIENDS,
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName }) afterFirstCmd: false,
})
return item.buddyUids
}))
const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key)
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value
})
} else {
const data = await invoke<{
buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo>
}>(
'getBuddyList',
[refresh],
{
className: NTClass.NODE_STORE_API,
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
}
)
const category: Map<number, Pick<CategoryFriend, 'buddyUids' | 'categroyName'>> = new Map()
for (const item of data.buddyCategory) {
category.set(item.categoryId, pick(item, ['buddyUids', 'categroyName']))
} }
return Object.values(data.userSimpleInfos) )
.filter(v => v.baseInfo && category.get(v.baseInfo.categoryId)?.buddyUids.includes(v.uid!)) return data
.map(value => {
return {
...value,
categoryId: value.baseInfo.categoryId,
categroyName: category.get(value.baseInfo.categoryId)?.categroyName
}
})
}
} }
async isBuddy(uid: string): Promise<boolean> { async isBuddy(uid: string): Promise<boolean> {
const session = getSession() return await invoke('nodeIKernelBuddyService/isBuddy', [{ uid }])
if (session) { }
return session.getBuddyService().isBuddy(uid)
} else { async getBuddyRecommendContact(uin: string) {
return await invoke('nodeIKernelBuddyService/isBuddy', [{ uid }, null]) const ret = await invoke('nodeIKernelBuddyService/getBuddyRecommendContactArkJson', [{ uin }])
} return ret.arkMsg
}
async setBuddyRemark(uid: string, remark: string) {
return await invoke('nodeIKernelBuddyService/setBuddyRemark', [{
remarkParams: { uid, remark }
}])
} }
} }

View File

@@ -1,13 +1,20 @@
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GetFileListParam, PublishGroupBulletinReq } from '../types' import {
Group,
GroupMember,
GroupMemberRole,
GroupNotifies,
GroupRequestOperateTypes,
GetFileListParam,
PublishGroupBulletinReq,
GroupAllInfo,
GroupFileInfo,
GroupBulletinListResult
} from '../types'
import { invoke, NTClass, NTMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { NTQQWindows } from './window' import { NTQQWindows } from './window'
import { getSession } from '../wrapper'
import { OnGroupFileInfoUpdateParams } from '../listeners'
import { NodeIKernelGroupService } from '../services'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { isNumeric } from '@/common/utils/misc'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@@ -18,8 +25,6 @@ declare module 'cordis' {
export class NTQQGroupApi extends Service { export class NTQQGroupApi extends Service {
static inject = ['ntWindowApi'] static inject = ['ntWindowApi']
public groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>()
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntGroupApi', true) super(ctx, 'ntGroupApi', true)
} }
@@ -41,51 +46,37 @@ export class NTQQGroupApi extends Service {
} }
async getGroupMembers(groupCode: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembers(groupCode: string, num = 3000): Promise<Map<string, GroupMember>> {
const session = getSession() const sceneId = await invoke(NTMethod.GROUP_MEMBER_SCENE, [{ groupCode, scene: 'groupMemberList_MainWindow' }])
let result: Awaited<ReturnType<NodeIKernelGroupService['getNextMemberList']>> const data = await invoke(NTMethod.GROUP_MEMBERS, [{ sceneId, num }])
if (session) { if (data.errCode !== 0) {
const groupService = session.getGroupService() throw new Error('获取群成员列表出错,' + data.errMsg)
const sceneId = groupService.createMemberListScene(groupCode, 'groupMemberList_MainWindow')
result = await groupService.getNextMemberList(sceneId, undefined, num)
} else {
const sceneId = await invoke(NTMethod.GROUP_MEMBER_SCENE, [{ groupCode, scene: 'groupMemberList_MainWindow' }])
result = await invoke(NTMethod.GROUP_MEMBERS, [{ sceneId, num }, null])
} }
if (result.errCode !== 0) { return data.result.infos
throw ('获取群成员列表出错,' + result.errMsg)
}
return result.result.infos
} }
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) { async getGroupMember(groupCode: string, uid: string, forceUpdate = false) {
const groupCodeStr = groupCode.toString() invoke('nodeIKernelGroupListener/onMemberInfoChange', [], {
const memberUinOrUidStr = memberUinOrUid.toString() registerEvent: true
if (!this.groupMembers.has(groupCodeStr)) { })
try {
// 更新群成员列表 const data = await invoke<{
this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr)) groupCode: string
members: Map<string, GroupMember>
}>(
'nodeIKernelGroupService/getMemberInfo',
[{
groupCode,
uids: [uid],
forceUpdate
}],
{
cbCmd: 'nodeIKernelGroupListener/onMemberInfoChange',
afterFirstCmd: false,
cmdCB: payload => payload.members.has(uid),
timeout: 2000
} }
catch (e) { )
return null return data.members.get(uid)!
}
}
let members = this.groupMembers.get(groupCodeStr)!
const getMember = () => {
let member: GroupMember | undefined = undefined
if (isNumeric(memberUinOrUidStr)) {
member = Array.from(members.values()).find(member => member.uin === memberUinOrUidStr)
} else {
member = members.get(memberUinOrUidStr)
}
return member
}
let member = getMember()
if (!member) {
this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
members = this.groupMembers.get(groupCodeStr)!
member = getMember()
}
return member
} }
async getGroupIgnoreNotifies() { async getGroupIgnoreNotifies() {
@@ -97,13 +88,13 @@ export class NTQQGroupApi extends Service {
) )
} }
async getSingleScreenNotifies(num: number) { async getSingleScreenNotifies(number: number, startSeq = '') {
invoke(ReceiveCmdS.GROUP_NOTIFY, [], { classNameIsRegister: true }) invoke(ReceiveCmdS.GROUP_NOTIFY, [], { registerEvent: true })
return (await invoke<GroupNotifies>( return (await invoke<GroupNotifies>(
'nodeIKernelGroupService/getSingleScreenNotifies', 'nodeIKernelGroupService/getSingleScreenNotifies',
[{ doubt: false, startSeq: '', number: num }, null], [{ doubt: false, startSeq, number }],
{ {
cbCmd: ReceiveCmdS.GROUP_NOTIFY, cbCmd: ReceiveCmdS.GROUP_NOTIFY,
afterFirstCmd: false, afterFirstCmd: false,
} }
@@ -115,189 +106,194 @@ export class NTQQGroupApi extends Service {
const groupCode = flagitem[0] const groupCode = flagitem[0]
const seq = flagitem[1] const seq = flagitem[1]
const type = parseInt(flagitem[2]) const type = parseInt(flagitem[2])
const session = getSession() return await invoke(NTMethod.HANDLE_GROUP_REQUEST, [{
if (session) { doubt: false,
return session.getGroupService().operateSysNotify( operateMsg: {
false, operateType,
{ targetMsg: {
'operateType': operateType, // 2 拒绝 seq,
'targetMsg': { type,
'seq': seq, // 通知序列号 groupCode,
'type': type, postscript: reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
'groupCode': groupCode,
'postscript': reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
}
})
} else {
return await invoke(NTMethod.HANDLE_GROUP_REQUEST, [{
doubt: false,
operateMsg: {
operateType,
targetMsg: {
seq,
type,
groupCode,
postscript: reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
},
}, },
}, null]) },
} }])
} }
async quitGroup(groupCode: string) { async quitGroup(groupCode: string) {
const session = getSession() return await invoke(NTMethod.QUIT_GROUP, [{ groupCode }])
if (session) {
return session.getGroupService().quitGroup(groupCode)
} else {
return await invoke(NTMethod.QUIT_GROUP, [{ groupCode }, null])
}
} }
async kickMember( async kickMember(groupCode: string, kickUids: string[], refuseForever = false, kickReason = '') {
groupCode: string, return await invoke(NTMethod.KICK_MEMBER, [{ groupCode, kickUids, refuseForever, kickReason }])
kickUids: string[],
refuseForever = false,
kickReason = '',
) {
const session = getSession()
if (session) {
return session.getGroupService().kickMember(groupCode, kickUids, refuseForever, kickReason)
} else {
return await invoke(NTMethod.KICK_MEMBER, [{ groupCode, kickUids, refuseForever, kickReason }])
}
} }
/** timeStamp为秒数, 0为解除禁言 */
async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) { async banMember(groupCode: string, memList: Array<{ uid: string, timeStamp: number }>) {
// timeStamp为秒数, 0为解除禁言 return await invoke(NTMethod.MUTE_MEMBER, [{ groupCode, memList }])
const session = getSession()
if (session) {
return session.getGroupService().setMemberShutUp(groupCode, memList)
} else {
return await invoke(NTMethod.MUTE_MEMBER, [{ groupCode, memList }])
}
} }
async banGroup(groupCode: string, shutUp: boolean) { async banGroup(groupCode: string, shutUp: boolean) {
const session = getSession() return await invoke(NTMethod.MUTE_GROUP, [{ groupCode, shutUp }])
if (session) {
return session.getGroupService().setGroupShutUp(groupCode, shutUp)
} else {
return await invoke(NTMethod.MUTE_GROUP, [{ groupCode, shutUp }, null])
}
} }
async setMemberCard(groupCode: string, memberUid: string, cardName: string) { async setMemberCard(groupCode: string, memberUid: string, cardName: string) {
const session = getSession() return await invoke(NTMethod.SET_MEMBER_CARD, [{ groupCode, uid: memberUid, cardName }])
if (session) {
return session.getGroupService().modifyMemberCardName(groupCode, memberUid, cardName)
} else {
return await invoke(NTMethod.SET_MEMBER_CARD, [{ groupCode, uid: memberUid, cardName }, null])
}
} }
async setMemberRole(groupCode: string, memberUid: string, role: GroupMemberRole) { async setMemberRole(groupCode: string, memberUid: string, role: GroupMemberRole) {
const session = getSession() return await invoke(NTMethod.SET_MEMBER_ROLE, [{ groupCode, uid: memberUid, role }])
if (session) {
return session.getGroupService().modifyMemberRole(groupCode, memberUid, role)
} else {
return await invoke(NTMethod.SET_MEMBER_ROLE, [{ groupCode, uid: memberUid, role }, null])
}
} }
async setGroupName(groupCode: string, groupName: string) { async setGroupName(groupCode: string, groupName: string) {
const session = getSession() return await invoke(NTMethod.SET_GROUP_NAME, [{ groupCode, groupName }])
if (session) {
return session.getGroupService().modifyGroupName(groupCode, groupName, false)
} else {
return await invoke(NTMethod.SET_GROUP_NAME, [{ groupCode, groupName }, null])
}
} }
async getGroupRemainAtTimes(groupCode: string) { async getGroupRemainAtTimes(groupCode: string) {
return await invoke< return await invoke(NTMethod.GROUP_AT_ALL_REMAIN_COUNT, [{ groupCode }])
GeneralCallResult & {
atInfo: {
canAtAll: boolean
RemainAtAllCountForUin: number
RemainAtAllCountForGroup: number
atTimesMsg: string
canNotAtAllMsg: ''
}
}
>(NTMethod.GROUP_AT_ALL_REMAIN_COUNT, [{ groupCode }, null])
} }
/** 27187 TODO */
async removeGroupEssence(groupCode: string, msgId: string) { async removeGroupEssence(groupCode: string, msgId: string) {
const session = getSession() const ntMsgApi = this.ctx.get('ntMsgApi')!
// 代码没测过 const data = await ntMsgApi.getMsgHistory({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false)
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom return await invoke('nodeIKernelGroupService/removeGroupEssence', [{
const data = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false) req: {
const param = { groupCode: groupCode,
groupCode: groupCode, msgRandom: Number(data?.msgList[0].msgRandom),
msgRandom: Number(data?.msgList[0].msgRandom), msgSeq: Number(data?.msgList[0].msgSeq)
msgSeq: Number(data?.msgList[0].msgSeq) }
} }])
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
return session?.getGroupService().removeGroupEssence(param)
} }
/** 27187 TODO */
async addGroupEssence(groupCode: string, msgId: string) { async addGroupEssence(groupCode: string, msgId: string) {
const session = getSession() const ntMsgApi = this.ctx.get('ntMsgApi')!
// 代码没测过 const data = await ntMsgApi.getMsgHistory({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false)
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom return await invoke('nodeIKernelGroupService/addGroupEssence', [{
const data = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false) req: {
const param = { groupCode: groupCode,
groupCode: groupCode, msgRandom: Number(data?.msgList[0].msgRandom),
msgRandom: Number(data?.msgList[0].msgRandom), msgSeq: Number(data?.msgList[0].msgSeq)
msgSeq: Number(data?.msgList[0].msgSeq) }
} }])
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
return session?.getGroupService().addGroupEssence(param)
} }
async createGroupFileFolder(groupId: string, folderName: string) { async createGroupFileFolder(groupId: string, folderName: string) {
return await invoke('nodeIKernelRichMediaService/createGroupFolder', [{ groupId, folderName }, null]) return await invoke('nodeIKernelRichMediaService/createGroupFolder', [{ groupId, folderName }])
} }
async deleteGroupFileFolder(groupId: string, folderId: string) { async deleteGroupFileFolder(groupId: string, folderId: string) {
return await invoke('nodeIKernelRichMediaService/deleteGroupFolder', [{ groupId, folderId }, null]) return await invoke('nodeIKernelRichMediaService/deleteGroupFolder', [{ groupId, folderId }])
} }
async deleteGroupFile(groupId: string, fileIdList: string[]) { async deleteGroupFile(groupId: string, fileIdList: string[], busIdList: number[]) {
return await invoke('nodeIKernelRichMediaService/deleteGroupFile', [{ groupId, busIdList: [102], fileIdList }, null]) return await invoke('nodeIKernelRichMediaService/deleteGroupFile', [{ groupId, busIdList, fileIdList }])
} }
async getGroupFileList(groupId: string, fileListForm: GetFileListParam) { async getGroupFileList(groupId: string, fileListForm: GetFileListParam) {
invoke('nodeIKernelMsgListener/onGroupFileInfoUpdate', [], { classNameIsRegister: true }) invoke('nodeIKernelMsgListener/onGroupFileInfoUpdate', [], { registerEvent: true })
const data = await invoke<{ fileInfo: OnGroupFileInfoUpdateParams }>( const data = await invoke<{ fileInfo: GroupFileInfo }>(
'nodeIKernelRichMediaService/getGroupFileList', 'nodeIKernelRichMediaService/getGroupFileList',
[ [{
{ groupId,
groupId, fileListForm
fileListForm }],
},
null,
],
{ {
cbCmd: 'nodeIKernelMsgListener/onGroupFileInfoUpdate', cbCmd: 'nodeIKernelMsgListener/onGroupFileInfoUpdate',
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: (payload, result) => payload.fileInfo.reqId === result cmdCB: (payload, result) => payload.fileInfo.reqId === result
} }
) )
return data.fileInfo.item return data.fileInfo
} }
async publishGroupBulletin(groupCode: string, req: PublishGroupBulletinReq) { async publishGroupBulletin(groupCode: string, req: PublishGroupBulletinReq) {
const ntUserApi = this.ctx.get('ntUserApi')! const ntUserApi = this.ctx.get('ntUserApi')!
const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')! const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!
return await invoke('nodeIKernelGroupService/publishGroupBulletin', [{ groupCode, psKey, req }, null]) return await invoke('nodeIKernelGroupService/publishGroupBulletin', [{ groupCode, psKey, req }])
} }
async uploadGroupBulletinPic(groupCode: string, path: string) { async uploadGroupBulletinPic(groupCode: string, path: string) {
const ntUserApi = this.ctx.get('ntUserApi')! const ntUserApi = this.ctx.get('ntUserApi')!
const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')! const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!
return await invoke('nodeIKernelGroupService/uploadGroupBulletinPic', [{ groupCode, psKey, path }, null]) return await invoke('nodeIKernelGroupService/uploadGroupBulletinPic', [{ groupCode, psKey, path }])
}
async getGroupRecommendContact(groupCode: string) {
const ret = await invoke('nodeIKernelGroupService/getGroupRecommendContactArkJson', [{ groupCode }])
return ret.arkJson
}
async queryCachedEssenceMsg(groupCode: string, msgSeq = '0', msgRandom = '0') {
return await invoke('nodeIKernelGroupService/queryCachedEssenceMsg', [{
key: {
groupCode,
msgSeq: +msgSeq,
msgRandom: +msgRandom
}
}])
}
async getGroupHonorList(groupCode: string) {
// 还缺点东西
return await invoke('nodeIKernelGroupService/getGroupHonorList', [{
req: {
groupCode: [+groupCode]
}
}])
}
async getGroupAllInfo(groupCode: string) {
invoke('nodeIKernelGroupListener/onGroupAllInfoChange', [], {
registerEvent: true
})
return await invoke<{ groupAll: GroupAllInfo }>(
'nodeIKernelGroupService/getGroupAllInfo',
[{
groupCode,
source: 4
}],
{
cbCmd: 'nodeIKernelGroupListener/onGroupAllInfoChange',
afterFirstCmd: false,
cmdCB: payload => payload.groupAll.groupCode === groupCode
}
)
}
async getGroupBulletinList(groupCode: string) {
invoke('nodeIKernelGroupListener/onGetGroupBulletinListResult', [], {
registerEvent: true
})
const ntUserApi = this.ctx.get('ntUserApi')!
const psKey = (await ntUserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!
return await invoke<{
groupCode: string
context: string
result: GroupBulletinListResult
}>(
'nodeIKernelGroupService/getGroupBulletinList',
[{
groupCode,
psKey,
context: '',
req: {
startIndex: -1,
num: 20,
needInstructionsForJoinGroup: 1,
needPublisherInfo: 1
}
}],
{
cbCmd: 'nodeIKernelGroupListener/onGetGroupBulletinListResult',
cmdCB: payload => payload.groupCode === groupCode,
afterFirstCmd: false
}
)
}
async setGroupAvatar(groupCode: string, path: string) {
return await invoke('nodeIKernelGroupService/setHeader', [{ path, groupCode }])
} }
} }

View File

@@ -1,7 +1,5 @@
import { invoke, NTMethod } from '../ntcall' import { invoke, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { RawMessage, SendMessageElement, Peer, ChatType } from '../types'
import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types'
import { getSession } from '@/ntqqapi/wrapper'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -11,16 +9,6 @@ declare module 'cordis' {
} }
} }
function generateMsgId() {
const timestamp = Math.floor(Date.now() / 1000)
const random = Math.floor(Math.random() * Math.pow(2, 32))
const buffer = Buffer.alloc(8)
buffer.writeUInt32BE(timestamp, 0)
buffer.writeUInt32BE(random, 4)
const msgId = BigInt('0x' + buffer.toString('hex')).toString()
return msgId
}
export class NTQQMsgApi extends Service { export class NTQQMsgApi extends Service {
static inject = ['ntUserApi'] static inject = ['ntUserApi']
@@ -28,99 +16,66 @@ export class NTQQMsgApi extends Service {
super(ctx, 'ntMsgApi', true) super(ctx, 'ntMsgApi', true)
} }
async getTempChatInfo(chatType: ChatType2, peerUid: string) { async getTempChatInfo(chatType: ChatType, peerUid: string) {
const session = getSession() return await invoke('nodeIKernelMsgService/getTempChatInfo', [{ chatType, peerUid }])
if (session) {
return session.getMsgService().getTempChatInfo(chatType, peerUid)
} else {
return await invoke('nodeIKernelMsgService/getTempChatInfo', [{ chatType, peerUid }, null])
}
} }
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, setEmoji: boolean = true) { async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, setEmoji: boolean) {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 // nt_qq/global/nt_data/Emoji/emoji-resource/sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid // nt_qq/global/nt_data/Emoji/emoji-resource/face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType // 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
const session = getSession()
const emojiType = emojiId.length > 3 ? '2' : '1' const emojiType = emojiId.length > 3 ? '2' : '1'
if (session) { return await invoke(NTMethod.EMOJI_LIKE, [{ peer, msgSeq, emojiId, emojiType, setEmoji }])
return session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiType, setEmoji)
} else {
return await invoke(NTMethod.EMOJI_LIKE, [{ peer, msgSeq, emojiId, emojiType, setEmoji }, null])
}
} }
async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) { async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
const session = getSession() return await invoke(NTMethod.GET_MULTI_MSG, [{ peer, rootMsgId, parentMsgId }])
if (session) {
return session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId)
} else {
return await invoke(NTMethod.GET_MULTI_MSG, [{ peer, rootMsgId, parentMsgId }, null])
}
} }
async activateChat(peer: Peer) { async activateChat(peer: Peer) {
return await invoke<GeneralCallResult>(NTMethod.ACTIVE_CHAT_PREVIEW, [{ peer, cnt: 20 }, null]) return await invoke(NTMethod.ACTIVE_CHAT_PREVIEW, [{ peer, cnt: 1 }])
} }
async activateChatAndGetHistory(peer: Peer) { async activateChatAndGetHistory(peer: Peer, cnt: number) {
return await invoke<GeneralCallResult>(NTMethod.ACTIVE_CHAT_HISTORY, [{ peer, cnt: 20 }, null]) return await invoke(NTMethod.ACTIVE_CHAT_HISTORY, [{ peer, cnt, msgId: '0', queryOrder: true }])
} }
async getAioFirstViewLatestMsgs(peer: Peer, cnt: number) { async getAioFirstViewLatestMsgs(peer: Peer, cnt: number) {
return await invoke('nodeIKernelMsgService/getAioFirstViewLatestMsgs', [{ peer, cnt }, null]) return await invoke('nodeIKernelMsgService/getAioFirstViewLatestMsgs', [{ peer, cnt }])
} }
async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) { async getMsgsByMsgId(peer: Peer, msgIds: string[]) {
if (!peer) throw new Error('peer is not allowed') if (!peer) throw new Error('peer is not allowed')
if (!msgIds) throw new Error('msgIds is not allowed') if (!msgIds) throw new Error('msgIds is not allowed')
const session = getSession() return await invoke('nodeIKernelMsgService/getMsgsByMsgId', [{ peer, msgIds }])
if (session) {
return session.getMsgService().getMsgsByMsgId(peer, msgIds)
} else {
return await invoke('nodeIKernelMsgService/getMsgsByMsgId', [{ peer, msgIds }, null])
}
} }
async getMsgHistory(peer: Peer, msgId: string, cnt: number, isReverseOrder: boolean = false) { async getMsgHistory(peer: Peer, msgId: string, cnt: number, queryOrder = false) {
const session = getSession() // 默认情况下消息时间从旧到新
// 消息时间从旧到新 return await invoke(NTMethod.HISTORY_MSG, [{ peer, msgId, cnt, queryOrder }])
if (session) {
return session.getMsgService().getMsgsIncludeSelf(peer, msgId, cnt, isReverseOrder)
} else {
return await invoke(NTMethod.HISTORY_MSG, [{ peer, msgId, cnt, queryOrder: isReverseOrder }, null])
}
} }
async recallMsg(peer: Peer, msgIds: string[]) { async recallMsg(peer: Peer, msgIds: string[]) {
const session = getSession() return await invoke(NTMethod.RECALL_MSG, [{ peer, msgIds }])
if (session) {
return session.getMsgService().recallMsg(peer, msgIds)
} else {
return await invoke(NTMethod.RECALL_MSG, [{ peer, msgIds }, null])
}
} }
async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) { async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) {
const msgId = generateMsgId() const uniqueId = await this.generateMsgUniqueId(peer.chatType)
peer.guildId = msgId peer.guildId = uniqueId
const data = await invoke<{ msgList: RawMessage[] }>( const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/sendMsg', 'nodeIKernelMsgService/sendMsg',
[ [{
{ msgId: '0',
msgId: '0', peer,
peer, msgElements,
msgElements, msgAttributeInfos: new Map()
msgAttributeInfos: new Map() }],
},
null
],
{ {
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate', cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: payload => { cmdCB: payload => {
for (const msgRecord of payload.msgList) { for (const msgRecord of payload.msgList) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) { if (msgRecord.guildId === uniqueId && msgRecord.sendStatus === 2) {
return true return true
} }
} }
@@ -129,22 +84,37 @@ export class NTQQMsgApi extends Service {
timeout timeout
} }
) )
return data.msgList.find(msgRecord => msgRecord.guildId === msgId) delete peer.guildId
return data.msgList.find(msgRecord => msgRecord.guildId === uniqueId)
} }
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
const session = getSession() const uniqueId = await this.generateMsgUniqueId(destPeer.chatType)
if (session) { destPeer.guildId = uniqueId
return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], []) const data = await invoke<{ msgList: RawMessage[] }>(
} else { 'nodeIKernelMsgService/forwardMsg',
return await invoke(NTMethod.FORWARD_MSG, [{ [{
msgIds, msgIds,
srcContact: srcPeer, srcContact: srcPeer,
dstContacts: [destPeer], dstContacts: [destPeer],
commentElements: [], commentElements: [],
msgAttributeInfos: new Map(), msgAttributeInfos: new Map(),
}, null]) }],
} {
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false,
cmdCB: payload => {
for (const msgRecord of payload.msgList) {
if (msgRecord.guildId === uniqueId && msgRecord.sendStatus === 2) {
return true
}
}
return false
}
}
)
delete destPeer.guildId
return data.msgList.filter(msgRecord => msgRecord.guildId === uniqueId)
} }
async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> { async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
@@ -155,27 +125,24 @@ export class NTQQMsgApi extends Service {
const selfUid = selfInfo.uid const selfUid = selfInfo.uid
const data = await invoke<{ msgList: RawMessage[] }>( const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/multiForwardMsgWithComment', 'nodeIKernelMsgService/multiForwardMsgWithComment',
[ [{
{ msgInfos,
msgInfos, srcContact: srcPeer,
srcContact: srcPeer, dstContact: destPeer,
dstContact: destPeer, commentElements: [],
commentElements: [], msgAttributeInfos: new Map(),
msgAttributeInfos: new Map(), }],
},
null,
],
{ {
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate', cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: payload => { cmdCB: payload => {
for (const msgRecord of payload.msgList) { for (const msgRecord of payload.msgList) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) { if (msgRecord.peerUid === destPeer.peerUid && msgRecord.senderUid === selfUid) {
return true return true
} }
} }
return false return false
}, }
} }
) )
for (const msg of data.msgList) { for (const msg of data.msgList) {
@@ -183,38 +150,19 @@ export class NTQQMsgApi extends Service {
if (!arkElement) { if (!arkElement) {
continue continue
} }
const forwardData = JSON.parse(arkElement.arkElement.bytesData) const forwardData = JSON.parse(arkElement.arkElement!.bytesData)
if (forwardData.app != 'com.tencent.multimsg') { if (forwardData.app !== 'com.tencent.multimsg') {
continue continue
} }
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfUid) { if (msg.peerUid === destPeer.peerUid && msg.senderUid === selfUid) {
return msg return msg
} }
} }
throw new Error('转发消息超时') throw new Error('转发消息超时')
} }
async getMsgsBySeqAndCount(peer: Peer, msgSeq: string, count: number, desc: boolean, z: boolean) {
const session = getSession()
if (session) {
return await session.getMsgService().getMsgsBySeqAndCount(peer, msgSeq, count, desc, z)
} else {
return await invoke('nodeIKernelMsgService/getMsgsBySeqAndCount', [{
peer,
cnt: count,
msgSeq,
queryOrder: desc
}, null])
}
}
async getSingleMsg(peer: Peer, msgSeq: string) { async getSingleMsg(peer: Peer, msgSeq: string) {
const session = getSession() return await invoke('nodeIKernelMsgService/getSingleMsg', [{ peer, msgSeq }])
if (session) {
return await session.getMsgService().getSingleMsg(peer, msgSeq)
} else {
return await invoke('nodeIKernelMsgService/getSingleMsg', [{ peer, msgSeq }, null])
}
} }
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) { async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
@@ -232,10 +180,10 @@ export class NTQQMsgApi extends Service {
isIncludeCurrent: true, isIncludeCurrent: true,
pageLimit: 1, pageLimit: 1,
} }
}, null]) }])
} }
async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[]) { async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[] = []) {
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{ return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId: '0', msgId: '0',
msgTime: '0', msgTime: '0',
@@ -250,10 +198,68 @@ export class NTQQMsgApi extends Service {
isIncludeCurrent: true, isIncludeCurrent: true,
pageLimit: 1, pageLimit: 1,
} }
}, null]) }])
} }
async setMsgRead(peer: Peer) { async setMsgRead(peer: Peer) {
return await invoke('nodeIKernelMsgService/setMsgRead', [{ peer }, null]) return await invoke('nodeIKernelMsgService/setMsgRead', [{ peer }])
}
async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number) {
return await invoke('nodeIKernelMsgService/getMsgEmojiLikesList', [{
peer,
msgSeq,
emojiId,
emojiType,
cnt: count
}])
}
async fetchFavEmojiList(count: number) {
return await invoke('nodeIKernelMsgService/fetchFavEmojiList', [{
resId: '',
count,
backwardFetch: true,
forceRefresh: true
}])
}
async generateMsgUniqueId(chatType: number) {
const time = await this.getServerTime()
const uniqueId = await invoke('nodeIKernelMsgService/generateMsgUniqueId', [{ chatType, time }])
if (typeof uniqueId === 'string') {
return uniqueId
} else {
const random = Math.trunc(Math.random() * 100)
return `${Date.now()}${random}`
}
}
async queryMsgsById(chatType: ChatType, msgId: string) {
const msgTime = this.getMsgTimeFromId(msgId)
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId,
msgTime: '0',
msgSeq: '0',
params: {
chatInfo: {
peerUid: '',
chatType
},
filterMsgToTime: msgTime,
filterMsgFromTime: msgTime,
isIncludeCurrent: true,
pageLimit: 1,
}
}])
}
getMsgTimeFromId(msgId: string) {
// 小概率相差1毫秒
return String(BigInt(msgId) >> 32n)
}
async getServerTime() {
return await invoke('nodeIKernelMSFService/getServerTime', [])
} }
} }

View File

@@ -1,10 +1,8 @@
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfo, UserDetailSource, ProfileBizType, SimpleInfo } from '../types'
import { invoke } from '../ntcall' import { invoke } from '../ntcall'
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types'
import { getBuildVersion } from '@/common/utils' import { getBuildVersion } from '@/common/utils'
import { getSession } from '@/ntqqapi/wrapper'
import { RequestUtil } from '@/common/utils/request' import { RequestUtil } from '@/common/utils/request'
import { UserDetailSource, ProfileBizType } from '../services' import { isNullable, pick, Time } from 'cosmokit'
import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -21,31 +19,25 @@ export class NTQQUserApi extends Service {
super(ctx, 'ntUserApi', true) super(ctx, 'ntUserApi', true)
} }
async setQQAvatar(path: string) { async setSelfAvatar(path: string) {
return await invoke( return await invoke(
'nodeIKernelProfileService/setHeader', 'nodeIKernelProfileService/setHeader',
[ [{ path }],
{ path },
null,
],
{ {
timeout: 10 * Time.second, // 10秒不一定够 timeout: 10 * Time.second // 10秒不一定够
} }
) )
} }
async fetchUserDetailInfo(uid: string) { async fetchUserDetailInfo(uid: string) {
const result = await invoke<{ info: UserDetailInfoListenerArg }>( const result = await invoke<{ info: UserDetailInfo }>(
'nodeIKernelProfileService/fetchUserDetailInfo', 'nodeIKernelProfileService/fetchUserDetailInfo',
[ [{
{ callFrom: 'BuddyProfileStore',
callFrom: 'BuddyProfileStore', uid: [uid],
uid: [uid], source: UserDetailSource.KSERVER,
source: UserDetailSource.KSERVER, bizList: [ProfileBizType.KALL]
bizList: [ProfileBizType.KALL] }],
},
null
],
{ {
cbCmd: 'nodeIKernelProfileListener/onUserDetailInfoChanged', cbCmd: 'nodeIKernelProfileListener/onUserDetailInfoChanged',
afterFirstCmd: false, afterFirstCmd: false,
@@ -71,13 +63,10 @@ export class NTQQUserApi extends Service {
} }
const result = await invoke<{ info: User }>( const result = await invoke<{ info: User }>(
'nodeIKernelProfileService/getUserDetailInfoWithBizInfo', 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
[ [{
{ uid,
uid, bizList: [0]
bizList: [0] }],
},
null,
],
{ {
cbCmd: 'nodeIKernelProfileListener/onProfileDetailInfoChanged', cbCmd: 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
afterFirstCmd: false, afterFirstCmd: false,
@@ -87,17 +76,6 @@ export class NTQQUserApi extends Service {
return result.info return result.info
} }
async getSkey(): Promise<string> {
const clientKeyData = await this.forceFetchClientKey()
if (clientKeyData?.result !== 0) {
throw new Error('获取clientKey失败')
}
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
+ '&clientkey=' + clientKeyData.clientKey
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
return (await RequestUtil.HttpsGetCookies(url))?.skey
}
async getCookies(domain: string) { async getCookies(domain: string) {
const clientKeyData = await this.forceFetchClientKey() const clientKeyData = await this.forceFetchClientKey()
if (clientKeyData?.result !== 0) { if (clientKeyData?.result !== 0) {
@@ -110,63 +88,25 @@ export class NTQQUserApi extends Service {
} }
async getPSkey(domains: string[]) { async getPSkey(domains: string[]) {
return await invoke('nodeIKernelTipOffService/getPskey', [{ domains, isForNewPCQQ: true }, null]) return await invoke('nodeIKernelTipOffService/getPskey', [{ domains, isForNewPCQQ: true }])
}
genBkn(sKey: string) {
sKey = sKey || ''
let hash = 5381
for (let i = 0; i < sKey.length; i++) {
const code = sKey.charCodeAt(i)
hash = hash + (hash << 5) + code
}
return (hash & 0x7fffffff).toString()
} }
async like(uid: string, count = 1) { async like(uid: string, count = 1) {
const session = getSession() return await invoke(
if (session) { 'nodeIKernelProfileLikeService/setBuddyProfileLike',
return session.getProfileLikeService().setBuddyProfileLike({ [{
friendUid: uid, doLikeUserInfo: {
sourceId: 71, friendUid: uid,
doLikeCount: count, sourceId: 71,
doLikeTollCount: 0 doLikeCount: count,
}) doLikeTollCount: 0
} else { }
return await invoke( }]
'nodeIKernelProfileLikeService/setBuddyProfileLike', )
[
{
doLikeUserInfo: {
friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0
}
},
null,
],
)
}
} }
async getUidByUinV1(uin: string) { async getUidByUinV1(uin: string, groupCode?: string) {
const session = getSession() let uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
// 通用转换开始尝试
let uid = (await session?.getUixConvertService().getUid([uin]))?.uidInfo.get(uin)
if (!uid) {
for (const membersList of this.ctx.ntGroupApi.groupMembers.values()) { //从群友列表转
for (const member of membersList.values()) {
if (member.uin === uin) {
uid = member.uid
break
}
}
if (uid) break
}
}
if (!uid) { if (!uid) {
const unveifyUid = (await this.getUserDetailInfoByUin(uin)).info.uid //特殊转换 const unveifyUid = (await this.getUserDetailInfoByUin(uin)).info.uid //特殊转换
if (unveifyUid.indexOf('*') === -1) { if (unveifyUid.indexOf('*') === -1) {
@@ -177,54 +117,43 @@ export class NTQQUserApi extends Service {
const friends = await this.ctx.ntFriendApi.getFriends() //从好友列表转 const friends = await this.ctx.ntFriendApi.getFriends() //从好友列表转
uid = friends.find(item => item.uin === uin)?.uid uid = friends.find(item => item.uin === uin)?.uid
} }
if (!uid && groupCode) {
const members = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
uid = Array.from(members.values()).find(e => e.uin === uin)?.uid
}
return uid return uid
} }
async getUidByUinV2(uin: string) { async getUidByUinV2(uin: string) {
const session = getSession() let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uinList: [uin] }])).uids.get(uin)
if (session) { if (uid) return uid
let uid = (await session.getGroupService().getUidByUins([uin])).uids.get(uin) uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }])).get(uin)
if (uid) return uid if (uid) return uid
uid = (await session.getProfileService().getUidByUin('FriendsServiceImpl', [uin])).get(uin) uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
if (uid) return uid if (uid) return uid
uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin) const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid
if (uid) return uid //if (!unveifyUid.includes('*')) return unveifyUid
} else { return unveifyUid
let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uin: [uin] }])).uids.get(uin)
if (uid) return uid
uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }])).get(uin)
if (uid) return uid
uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
if (uid) return uid
}
const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) return unveifyUid
} }
async getUidByUin(uin: string) { async getUidByUin(uin: string, groupCode?: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return this.getUidByUinV2(uin) return this.getUidByUinV2(uin)
} }
return this.getUidByUinV1(uin) return this.getUidByUinV1(uin, groupCode)
} }
async getUserDetailInfoByUinV2(uin: string) { async getUserDetailInfoByUinV2(uin: string) {
return await invoke<UserDetailInfoByUinV2>( return await invoke<UserDetailInfoByUinV2>(
'nodeIKernelProfileService/getUserDetailInfoByUin', 'nodeIKernelProfileService/getUserDetailInfoByUin',
[ [{ uin }]
{ uin },
null,
],
) )
} }
async getUserDetailInfoByUin(uin: string) { async getUserDetailInfoByUin(uin: string) {
return await invoke<UserDetailInfoByUin>( return await invoke<UserDetailInfoByUin>(
'nodeIKernelProfileService/getUserDetailInfoByUin', 'nodeIKernelProfileService/getUserDetailInfoByUin',
[ [{ uin }]
{ uin },
null,
],
) )
} }
@@ -232,31 +161,21 @@ export class NTQQUserApi extends Service {
const ret = await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }]) const ret = await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])
let uin = ret.uinInfo.get(uid) let uin = ret.uinInfo.get(uid)
if (!uin) { if (!uin) {
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换 uin = (await this.getUserDetailInfo(uid)).uin
} }
return uin return uin
} }
async getUinByUidV2(uid: string) { async getUinByUidV2(uid: string) {
const session = getSession() let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uidList: [uid] }])).uins.get(uid)
if (session) {
let uin = (await session.getGroupService().getUinByUids([uid])).uins.get(uid)
if (uin) return uin
uin = (await session.getProfileService().getUinByUid('FriendsServiceImpl', [uid])).get(uid)
if (uin) return uin
uin = (await session.getUixConvertService().getUin([uid])).uinInfo.get(uid)
if (uin) return uin
} else {
let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uid: [uid] }])).uins.get(uid)
if (uin) return uin
uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }])).get(uid)
if (uin) return uin
uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])).uinInfo.get(uid)
if (uin) return uin
}
let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).get(uid)
if (uin) return uin if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换 uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }])).get(uid)
if (uin) return uin
uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])).uinInfo.get(uid)
if (uin) return uin
uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).get(uid)
if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin
return uin return uin
} }
@@ -268,21 +187,13 @@ export class NTQQUserApi extends Service {
} }
async forceFetchClientKey() { async forceFetchClientKey() {
const session = getSession() return await invoke('nodeIKernelTicketService/forceFetchClientKey', [{ url: '' }])
if (session) {
return await session.getTicketService().forceFetchClientKey('')
} else {
return await invoke('nodeIKernelTicketService/forceFetchClientKey', [{ domain: '' }, null])
}
} }
async getSelfNick(refresh = false) { async getSelfNick(refresh = true) {
if ((refresh || !selfInfo.nick) && selfInfo.uid) { if ((refresh || !selfInfo.nick) && selfInfo.uid) {
const userInfo = await this.getUserDetailInfo(selfInfo.uid) const data = await this.getUserSimpleInfo(selfInfo.uid)
if (userInfo) { selfInfo.nick = data.nick
Object.assign(selfInfo, { nick: userInfo.nick })
return userInfo.nick
}
} }
return selfInfo.nick return selfInfo.nick
} }
@@ -294,7 +205,7 @@ export class NTQQUserApi extends Service {
extStatus, extStatus,
batteryStatus, batteryStatus,
} }
}, null]) }])
} }
async getProfileLike(uid: string) { async getProfileLike(uid: string) {
@@ -309,6 +220,67 @@ export class NTQQUserApi extends Service {
start: 0, start: 0,
limit: 20, limit: 20,
} }
}, null]) }])
}
async getUserSimpleInfoV2(uid: string, force = true) {
const data = await invoke<{ profiles: Record<string, SimpleInfo> }>(
'nodeIKernelProfileService/getUserSimpleInfo',
[{
uids: [uid],
force
}],
{
cbCmd: 'onProfileSimpleChanged',
afterFirstCmd: false,
cmdCB: payload => !isNullable(payload.profiles[uid]),
}
)
return data.profiles[uid].coreInfo
}
async getUserSimpleInfo(uid: string, force = true) {
if (getBuildVersion() >= 26702) {
return this.getUserSimpleInfoV2(uid, force)
}
const data = await invoke<{ profiles: Map<string, User> }>(
'nodeIKernelProfileService/getUserSimpleInfo',
[{
uids: [uid],
force
}],
{
cbCmd: 'nodeIKernelProfileListener/onProfileSimpleChanged',
afterFirstCmd: false,
cmdCB: payload => payload.profiles.has(uid),
}
)
const profile = data.profiles.get(uid)!
return pick(profile, ['nick', 'remark', 'uid', 'uin'])
}
async getCoreAndBaseInfo(uids: string[]) {
return await invoke(
'nodeIKernelProfileService/getCoreAndBaseInfo',
[{
uids,
callFrom: 'nodeStore'
}]
)
}
async getRobotUinRange() {
const data = await invoke(
'nodeIKernelRobotService/getRobotUinRange',
[{
req: {
justFetchMsgConfig: '1',
type: 1,
version: 0,
aioKeywordVersion: 0
}
}]
)
return data.response.robotUinRanges
} }
} }

View File

@@ -17,88 +17,6 @@ export enum WebHonorType {
EMOTION = 'emotion' EMOTION = 'emotion'
} }
export interface WebApiGroupMember {
uin: number
role: number
g: number
join_time: number
last_speak_time: number
lv: {
point: number
level: number
}
card: string
tags: string
flag: number
nick: string
qage: number
rm: number
}
interface WebApiGroupMemberRet {
ec: number
errcode: number
em: string
cache: number
adm_num: number
levelname: unknown
mems: WebApiGroupMember[]
count: number
svr_time: number
max_count: number
search_count: number
extmode: number
}
interface GroupEssenceMsg {
group_code: string
msg_seq: number
msg_random: number
sender_uin: string
sender_nick: string
sender_time: number
add_digest_uin: string
add_digest_nick: string
add_digest_time: number
msg_content: unknown[]
can_be_removed: true
}
export interface GroupEssenceMsgRet {
retcode: number
retmsg: string
data: {
msg_list: GroupEssenceMsg[]
is_end: boolean
group_role: number
config_page_url: string
}
}
interface SetGroupNoticeParams {
groupCode: string
content: string
pinned: number
type: number
isShowEditCard: number
tipWindowType: number
confirmRequired: number
picId: string
imgWidth?: number
imgHeight?: number
}
interface SetGroupNoticeRet {
ec: number
em: string
id: number
ltsm: number
new_fid: string
read_only: number
role: number
srv_code: number
}
export class NTQQWebApi extends Service { export class NTQQWebApi extends Service {
static inject = ['ntUserApi'] static inject = ['ntUserApi']
@@ -106,47 +24,6 @@ export class NTQQWebApi extends Service {
super(ctx, 'ntWebApi', true) super(ctx, 'ntWebApi', true)
} }
async getGroupMembers(groupCode: string): Promise<WebApiGroupMember[]> {
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const cookieStr = this.cookieToString(cookieObject)
const retList: Promise<WebApiGroupMemberRet>[] = []
const params = new URLSearchParams({
st: '0',
end: '40',
sort: '1',
gc: groupCode,
bkn: this.genBkn(cookieObject.skey)
})
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${params}`, 'POST', '', { 'Cookie': cookieStr })
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
return []
} else {
for (const member of fastRet.mems) {
memberData.push(member)
}
}
const pageNum = Math.ceil(fastRet.count / 40)
//遍历批量请求
for (let i = 2; i <= pageNum; i++) {
params.set('st', String((i - 1) * 40))
params.set('end', String(i * 40))
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${params}`, 'POST', '', { 'Cookie': cookieStr })
retList.push(ret)
}
//批量等待
for (let i = 1; i <= pageNum; i++) {
const ret = await (retList[i])
if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) {
continue
}
for (const member of ret.mems) {
memberData.push(member)
}
}
return memberData
}
genBkn(sKey: string) { genBkn(sKey: string) {
sKey = sKey || '' sKey = sKey || ''
let hash = 5381 let hash = 5381
@@ -157,10 +34,9 @@ export class NTQQWebApi extends Service {
return (hash & 0x7FFFFFFF).toString() return (hash & 0x7FFFFFFF).toString()
} }
//实现未缓存 考虑2h缓存 async getGroupHonorInfo(groupCode: string, getType: string) {
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { const getDataInternal = async (groupCode: string, type: number) => {
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => { const url = 'https://qun.qq.com/interactive/honorlist?gc=' + groupCode + '&type=' + type
const url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString()
let resJson let resJson
try { try {
const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr }) const res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr })
@@ -168,7 +44,7 @@ export class NTQQWebApi extends Service {
if (match) { if (match) {
resJson = JSON.parse(match[1].trim()) resJson = JSON.parse(match[1].trim())
} }
if (Internal_type === 1) { if (type === 1) {
return resJson?.talkativeList return resJson?.talkativeList
} else { } else {
return resJson?.actorList return resJson?.actorList
@@ -274,34 +150,6 @@ export class NTQQWebApi extends Service {
return honorInfo return honorInfo
} }
async setGroupNotice(params: SetGroupNoticeParams): Promise<SetGroupNoticeRet> {
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const settings = JSON.stringify({
is_show_edit_card: params.isShowEditCard,
tip_window_type: params.tipWindowType,
confirm_required: params.confirmRequired
})
return await RequestUtil.HttpGetJson<SetGroupNoticeRet>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
bkn: this.genBkn(cookieObject.skey),
qid: params.groupCode,
text: params.content,
pinned: params.pinned.toString(),
type: params.type.toString(),
settings: settings,
...(params.picId !== '' && {
pic: params.picId,
imgWidth: params.imgWidth?.toString(),
imgHeight: params.imgHeight?.toString(),
})
})}`,
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
)
}
private cookieToString(cookieObject: Dict) { private cookieToString(cookieObject: Dict) {
return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ') return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
} }

View File

@@ -35,7 +35,7 @@ export class NTQQWindowApi extends Service {
super(ctx, 'ntWindowApi', true) super(ctx, 'ntWindowApi', true)
} }
// 打开窗口并获取对应的下发事件 /** 打开窗口并获取对应的下发事件 */
async openWindow<R = GeneralCallResult>( async openWindow<R = GeneralCallResult>(
ntQQWindow: NTQQWindow, ntQQWindow: NTQQWindow,
args: unknown[], args: unknown[],
@@ -53,7 +53,6 @@ export class NTQQWindowApi extends Service {
) )
setTimeout(() => { setTimeout(() => {
for (const w of BrowserWindow.getAllWindows()) { for (const w of BrowserWindow.getAllWindows()) {
// log("close window", w.webContents.getURL())
if (w.webContents.getURL().indexOf(ntQQWindow.windowUrlHash) != -1) { if (w.webContents.getURL().indexOf(ntQQWindow.windowUrlHash) != -1) {
w.close() w.close()
} }

View File

@@ -1,9 +1,7 @@
import fs from 'node:fs' import { unlink } from 'node:fs/promises'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook' import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook'
import { MessageUnique } from '../common/utils/messageUnique'
import { Config as LLOBConfig } from '../common/types' import { Config as LLOBConfig } from '../common/types'
import { llonebotError } from '../common/globalVars'
import { isNumeric } from '../common/utils/misc' import { isNumeric } from '../common/utils/misc'
import { NTMethod } from './ntcall' import { NTMethod } from './ntcall'
import { import {
@@ -14,10 +12,11 @@ import {
GroupMember, GroupMember,
CategoryFriend, CategoryFriend,
SimpleInfo, SimpleInfo,
User, ChatType,
ChatType BuddyReqType,
GrayTipElementSubType
} from './types' } from './types'
import { selfInfo } from '../common/globalVars' import { selfInfo, llonebotError } from '../common/globalVars'
import { version } from '../version' import { version } from '../version'
import { invoke } from './ntcall' import { invoke } from './ntcall'
@@ -26,58 +25,58 @@ declare module 'cordis' {
app: Core app: Core
} }
interface Events { interface Events {
'nt/message-created': (input: RawMessage[]) => void 'nt/message-created': (input: RawMessage) => void
'nt/message-deleted': (input: RawMessage[]) => void 'nt/message-deleted': (input: RawMessage) => void
'nt/message-sent': (input: RawMessage[]) => void 'nt/message-sent': (input: RawMessage) => void
'nt/group-notify': (input: GroupNotify[]) => void 'nt/group-notify': (input: GroupNotify) => void
'nt/friend-request': (input: FriendRequest[]) => void 'nt/friend-request': (input: FriendRequest) => void
'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void 'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void
'nt/system-message-created': (input: Uint8Array) => void 'nt/system-message-created': (input: Uint8Array) => void
} }
} }
class Core extends Service { class Core extends Service {
static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi'] static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi', 'store']
public startTime = 0
constructor(protected ctx: Context, public config: Core.Config) { constructor(protected ctx: Context, public config: Core.Config) {
super(ctx, 'app', true) super(ctx, 'app', true)
} }
public start() { public start() {
llonebotError.otherError = '' if (!this.config.ob11.enable && !this.config.satori.enable) {
MessageUnique.init(selfInfo.uin) llonebotError.otherError = 'LLOneBot 未启动'
this.ctx.logger.info('LLOneBot 开关设置为关闭,不启动 LLOneBot')
return
}
this.startTime = Date.now()
this.registerListener() this.registerListener()
this.ctx.logger.info(`LLOneBot/${version}`) this.ctx.logger.info(`LLOneBot/${version}`)
this.ctx.on('llonebot/config-updated', input => { this.ctx.on('llob/config-updated', input => {
Object.assign(this.config, input) Object.assign(this.config, input)
}) })
} }
private registerListener() { private registerListener() {
registerReceiveHook<{ registerReceiveHook<{
data: CategoryFriend[] data?: CategoryFriend[]
userSimpleInfos?: Map<string, SimpleInfo> //V2
buddyCategory?: CategoryFriend[] //V2
}>(ReceiveCmdS.FRIENDS, (payload) => { }>(ReceiveCmdS.FRIENDS, (payload) => {
type V2data = { userSimpleInfos: Map<string, SimpleInfo> } let uids: string[] = []
let friendList: User[] = [] if (payload.buddyCategory) {
if ('userSimpleInfos' in payload) { uids = payload.buddyCategory.flatMap(item => item.buddyUids)
friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => { } else if (payload.data) {
return { uids = payload.data.flatMap(item => item.buddyList.map(e => e.uid))
...v.coreInfo,
}
})
} else {
for (const fData of payload.data) {
friendList.push(...fData.buddyList)
}
} }
this.ctx.logger.info('好友列表变动', friendList.length) for (const uid of uids) {
for (const friend of friendList) { this.ctx.ntMsgApi.activateChat({ peerUid: uid, chatType: ChatType.C2C })
this.ctx.ntMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend })
} }
this.ctx.logger.info('好友列表变动', uids.length)
}) })
// 自动清理新消息文件 // 自动清理新消息文件
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => { registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
if (!this.config.autoDeleteFile) { if (!this.config.autoDeleteFile) {
return return
} }
@@ -96,9 +95,7 @@ class Core extends Service {
} }
for (const path of pathList) { for (const path of pathList) {
if (path) { if (path) {
fs.unlink(picPath, () => { unlink(path).then(() => this.ctx.logger.info('删除文件成功', path))
this.ctx.logger.info('删除文件成功', path)
})
} }
} }
}, this.config.autoDeleteFileSecond! * 1000) }, this.config.autoDeleteFileSecond! * 1000)
@@ -126,17 +123,14 @@ class Core extends Service {
if (activatedPeerUids.includes(contact.id)) continue if (activatedPeerUids.includes(contact.id)) continue
activatedPeerUids.push(contact.id) activatedPeerUids.push(contact.id)
const peer = { peerUid: contact.id, chatType: contact.chatType } const peer = { peerUid: contact.id, chatType: contact.chatType }
if (contact.chatType === ChatType.temp) { if (contact.chatType === ChatType.TempC2CFromGroup) {
this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => { this.ctx.ntMsgApi.activateChatAndGetHistory(peer, 1).then(res => {
this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => { const lastTempMsg = res.msgList[0]
const lastTempMsg = msgList.at(-1) if (Date.now() / 1000 - Number(lastTempMsg?.msgTime) < 5) {
if (Date.now() / 1000 - Number(lastTempMsg?.msgTime) < 5) { this.ctx.parallel('nt/message-created', lastTempMsg!)
this.ctx.parallel('nt/message-created', [lastTempMsg!]) }
}
})
}) })
} } else {
else {
this.ctx.ntMsgApi.activateChat(peer) this.ctx.ntMsgApi.activateChat(peer)
} }
} }
@@ -146,12 +140,12 @@ class Core extends Service {
registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => { registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => {
const peerUid = payload[0] as string const peerUid = payload[0] as string
this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid) this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid)
let chatType = ChatType.friend let chatType = ChatType.C2C
if (isNumeric(peerUid)) { if (isNumeric(peerUid)) {
chatType = ChatType.group chatType = ChatType.Group
} }
else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) { else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) {
chatType = ChatType.temp chatType = ChatType.TempC2CFromGroup
} }
const peer = { peerUid, chatType } const peer = { peerUid, chatType }
await this.ctx.sleep(1000) await this.ctx.sleep(1000)
@@ -171,26 +165,45 @@ class Core extends Service {
}) })
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => { registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => {
this.ctx.parallel('nt/message-created', payload.msgList) for (const message of payload.msgList) {
// 过滤启动之前的消息
if (parseInt(message.msgTime) < this.startTime / 1000) {
continue
}
if (message.senderUin && message.senderUin !== '0') {
this.ctx.store.addMsgCache(message)
}
this.ctx.parallel('nt/message-created', message)
}
}) })
const sentMsgIds = new Map<string, boolean>()
const recallMsgIds: string[] = [] // 避免重复上报 const recallMsgIds: string[] = [] // 避免重复上报
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => { registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => {
const list = payload.msgList.filter(v => { for (const msg of payload.msgList) {
if (recallMsgIds.includes(v.msgId)) { if (
return false msg.recallTime !== '0' &&
msg.msgType === 5 &&
msg.subMsgType === 4 &&
msg.elements[0]?.grayTipElement?.subElementType === GrayTipElementSubType.Revoke &&
!recallMsgIds.includes(msg.msgId)
) {
recallMsgIds.shift()
recallMsgIds.push(msg.msgId)
this.ctx.parallel('nt/message-deleted', msg)
} else if (sentMsgIds.get(msg.msgId)) {
sentMsgIds.delete(msg.msgId)
this.ctx.parallel('nt/message-sent', msg)
} }
recallMsgIds.push(v.msgId) }
return true
})
this.ctx.parallel('nt/message-deleted', list)
}) })
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => { registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => {
if (!this.config.reportSelfMessage) { if (!this.config.reportSelfMessage) {
return return
} }
this.ctx.parallel('nt/message-sent', [payload.msgRecord]) sentMsgIds.set(payload.msgRecord.msgId, true)
}) })
const groupNotifyFlags: string[] = [] const groupNotifyFlags: string[] = []
@@ -202,27 +215,36 @@ class Core extends Service {
if (payload.unreadCount) { if (payload.unreadCount) {
let notifies: GroupNotify[] let notifies: GroupNotify[]
try { try {
notifies = (await this.ctx.ntGroupApi.getSingleScreenNotifies(14)).slice(0, payload.unreadCount) notifies = await this.ctx.ntGroupApi.getSingleScreenNotifies(payload.unreadCount)
} catch (e) { } catch (e) {
return return
} }
const list = notifies.filter(v => { for (const notify of notifies) {
const flag = v.group.groupCode + '|' + v.seq + '|' + v.type const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
if (groupNotifyFlags.includes(flag)) { const notifyTime = parseInt(notify.seq) / 1000
return false if (groupNotifyFlags.includes(flag) || notifyTime < this.startTime) {
continue
} }
groupNotifyFlags.shift()
groupNotifyFlags.push(flag) groupNotifyFlags.push(flag)
return true this.ctx.parallel('nt/group-notify', notify)
}) }
this.ctx.parallel('nt/group-notify', list)
} }
}) })
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => { registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => {
this.ctx.parallel('nt/friend-request', payload.data.buddyReqs) for (const req of payload.data.buddyReqs) {
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.MeInitiatorWaitPeerConfirm)) {
continue
}
if (+req.reqTime < this.startTime / 1000) {
continue
}
this.ctx.parallel('nt/friend-request', req)
}
}) })
invoke('nodeIKernelMsgListener/onRecvSysMsg', [], { classNameIsRegister: true }) invoke('nodeIKernelMsgListener/onRecvSysMsg', [], { registerEvent: true })
registerReceiveHook<{ registerReceiveHook<{
msgBuf: number[] msgBuf: number[]
@@ -237,4 +259,4 @@ namespace Core {
} }
} }
export default Core export default Core

View File

@@ -1,5 +1,6 @@
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import faceConfig from './helper/face_config.json' import faceConfig from './helper/face_config.json'
import pathLib from 'node:path'
import { import {
AtType, AtType,
ElementType, ElementType,
@@ -16,22 +17,20 @@ import {
SendVideoElement, SendVideoElement,
} from './types' } from './types'
import { stat, writeFile, copyFile, unlink } from 'node:fs/promises' import { stat, writeFile, copyFile, unlink } from 'node:fs/promises'
import { calculateFileMD5, isGIF } from '../common/utils/file' import { calculateFileMD5 } from '../common/utils/file'
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video' import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
import { encodeSilk } from '../common/utils/audio' import { encodeSilk } from '../common/utils/audio'
import { Context } from 'cordis' import { Context } from 'cordis'
import { isNullable } from 'cosmokit' import { isNullable } from 'cosmokit'
//export const mFaceCache = new Map<string, string>() // emojiId -> faceName export namespace SendElement {
export namespace SendElementEntities {
export function text(content: string): SendTextElement { export function text(content: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.Text,
elementId: '', elementId: '',
textElement: { textElement: {
content, content,
atType: AtType.notAt, atType: AtType.Unknown,
atUid: '', atUid: '',
atTinyId: '', atTinyId: '',
atNtUid: '', atNtUid: '',
@@ -41,7 +40,7 @@ export namespace SendElementEntities {
export function at(atUid: string, atNtUid: string, atType: AtType, display: string): SendTextElement { export function at(atUid: string, atNtUid: string, atType: AtType, display: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.Text,
elementId: '', elementId: '',
textElement: { textElement: {
content: display, content: display,
@@ -53,27 +52,23 @@ export namespace SendElementEntities {
} }
} }
export function reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { export function reply(msgSeq: string, msgId: string, senderUin: string): SendReplyElement {
return { return {
elementType: ElementType.REPLY, elementType: ElementType.Reply,
elementId: '', elementId: '',
replyElement: { replyElement: {
replayMsgSeq: msgSeq, // raw.msgSeq replayMsgSeq: msgSeq,
replayMsgId: msgId, // raw.msgId replayMsgId: msgId,
senderUin: senderUin, senderUin: senderUin,
senderUinStr: senderUinStr, senderUinStr: senderUin,
}, },
} }
} }
export async function pic(ctx: Context, picPath: string, summary: string = '', subType: 0 | 1 = 0): Promise<SendPicElement> { export async function pic(ctx: Context, picPath: string, summary = '', subType: 0 | 1 = 0, isFlashPic?: boolean): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(picPath, ElementType.PIC, subType) const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(picPath, ElementType.Pic, subType)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常,大小为 0'
}
const maxMB = 30;
if (fileSize > 1024 * 1024 * 30) {
throw `图片过大,最大支持${maxMB}MB当前文件大小${fileSize}B`
} }
const imageSize = await ctx.ntFileApi.getImageSize(picPath) const imageSize = await ctx.ntFileApi.getImageSize(picPath)
const picElement = { const picElement = {
@@ -84,16 +79,17 @@ export namespace SendElementEntities {
fileName: fileName, fileName: fileName,
sourcePath: path, sourcePath: path,
original: true, original: true,
picType: isGIF(picPath) ? PicType.gif : PicType.jpg, picType: imageSize.type === 'gif' ? PicType.GIF : PicType.JPEG,
picSubType: subType, picSubType: subType,
fileUuid: '', fileUuid: '',
fileSubId: '', fileSubId: '',
thumbFileSize: 0, thumbFileSize: 0,
summary, summary,
isFlashPic,
} }
ctx.logger.info('图片信息', picElement) ctx.logger.info('图片信息', picElement)
return { return {
elementType: ElementType.PIC, elementType: ElementType.Pic,
elementId: '', elementId: '',
picElement, picElement,
} }
@@ -106,7 +102,7 @@ export namespace SendElementEntities {
throw new Error('文件异常,大小为 0') throw new Error('文件异常,大小为 0')
} }
const element: SendFileElement = { const element: SendFileElement = {
elementType: ElementType.FILE, elementType: ElementType.File,
elementId: '', elementId: '',
fileElement: { fileElement: {
fileName, fileName,
@@ -125,7 +121,7 @@ export namespace SendElementEntities {
throw `文件${filePath}异常,不存在` throw `文件${filePath}异常,不存在`
} }
ctx.logger.info('复制视频到QQ目录', filePath) ctx.logger.info('复制视频到QQ目录', filePath)
const { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO) const { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.Video)
ctx.logger.info('复制视频到QQ目录完成', path) ctx.logger.info('复制视频到QQ目录完成', path)
if (fileSize === 0) { if (fileSize === 0) {
@@ -135,7 +131,6 @@ export namespace SendElementEntities {
if (fileSize > 1024 * 1024 * maxMB) { if (fileSize > 1024 * 1024 * maxMB) {
throw `视频过大,最大支持${maxMB}MB当前文件大小${fileSize}B` throw `视频过大,最大支持${maxMB}MB当前文件大小${fileSize}B`
} }
const pathLib = require('path')
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`) let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
thumbDir = pathLib.dirname(thumbDir) thumbDir = pathLib.dirname(thumbDir)
// log("thumb 目录", thumb) // log("thumb 目录", thumb)
@@ -203,7 +198,7 @@ export namespace SendElementEntities {
thumbPath.set(0, _thumbPath) thumbPath.set(0, _thumbPath)
const thumbMd5 = await calculateFileMD5(_thumbPath) const thumbMd5 = await calculateFileMD5(_thumbPath)
const element: SendVideoElement = { const element: SendVideoElement = {
elementType: ElementType.VIDEO, elementType: ElementType.Video,
elementId: '', elementId: '',
videoElement: { videoElement: {
fileName: fileName || _fileName, fileName: fileName || _fileName,
@@ -215,17 +210,7 @@ export namespace SendElementEntities {
thumbSize, thumbSize,
thumbWidth: videoInfo.width, thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height, thumbHeight: videoInfo.height,
fileSize: '' + fileSize, fileSize: String(fileSize),
// fileUuid: "",
// transferStatus: 0,
// progress: 0,
// invalidState: 0,
// fileSubId: "",
// fileBizId: null,
// originVideoMd5: "",
// fileFormat: 2,
// import_rich_media_context: null,
// sourceVideoCodecFormat: 2
}, },
} }
ctx.logger.info('videoElement', element) ctx.logger.info('videoElement', element)
@@ -238,7 +223,7 @@ export namespace SendElementEntities {
throw '语音转换失败, 请检查语音文件是否正常' throw '语音转换失败, 请检查语音文件是否正常'
} }
// log("生成语音", silkPath, duration); // log("生成语音", silkPath, duration);
const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(silkPath, ElementType.PTT) const { md5, fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(silkPath, ElementType.Ptt)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
@@ -246,14 +231,13 @@ export namespace SendElementEntities {
unlink(silkPath) unlink(silkPath)
} }
return { return {
elementType: ElementType.PTT, elementType: ElementType.Ptt,
elementId: '', elementId: '',
pttElement: { pttElement: {
fileName: fileName, fileName: fileName,
filePath: path, filePath: path,
md5HexStr: md5, md5HexStr: md5,
fileSize: fileSize, fileSize: String(fileSize),
// duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算
duration: duration, duration: duration,
formatType: 1, formatType: 1,
voiceType: 1, voiceType: 1,
@@ -267,22 +251,22 @@ export namespace SendElementEntities {
} }
} }
export function face(faceId: number): SendFaceElement { export function face(faceId: number, faceType?: number): SendFaceElement {
// 从face_config.json中获取表情名称 // 从face_config.json中获取表情名称
const sysFaces = faceConfig.sysface const sysFaces = faceConfig.sysface
const emojiFaces = faceConfig.emoji const face = sysFaces.find(face => face.QSid === String(faceId))
const face = sysFaces.find((face) => face.QSid === faceId.toString()) if (!faceType) {
faceId = parseInt(faceId.toString()) if (faceId < 222) {
// let faceType = parseInt(faceId.toString().substring(0, 1)); faceType = 1
let faceType = 1 } else {
if (faceId >= 222) { faceType = 2
faceType = 2 }
} if (face?.AniStickerType) {
if (face?.AniStickerType) { faceType = 3
faceType = 3; }
} }
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: faceId, faceIndex: faceId,
@@ -298,7 +282,8 @@ export namespace SendElementEntities {
export function mface(emojiPackageId: number, emojiId: string, key: string, summary?: string): SendMarketFaceElement { export function mface(emojiPackageId: number, emojiId: string, key: string, summary?: string): SendMarketFaceElement {
return { return {
elementType: ElementType.MFACE, elementType: ElementType.MarketFace,
elementId: '',
marketFaceElement: { marketFaceElement: {
imageWidth: 300, imageWidth: 300,
imageHeight: 300, imageHeight: 300,
@@ -315,10 +300,10 @@ export namespace SendElementEntities {
// 随机1到6 // 随机1到6
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: FaceIndex.dice, faceIndex: FaceIndex.Dice,
faceType: 3, faceType: 3,
faceText: '[骰子]', faceText: '[骰子]',
packId: '1', packId: '1',
@@ -337,7 +322,7 @@ export namespace SendElementEntities {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1
return { return {
elementType: ElementType.FACE, elementType: ElementType.Face,
elementId: '', elementId: '',
faceElement: { faceElement: {
faceIndex: FaceIndex.RPS, faceIndex: FaceIndex.RPS,
@@ -356,7 +341,7 @@ export namespace SendElementEntities {
export function ark(data: string): SendArkElement { export function ark(data: string): SendArkElement {
return { return {
elementType: ElementType.ARK, elementType: ElementType.Ark,
elementId: '', elementId: '',
arkElement: { arkElement: {
bytesData: data, bytesData: data,
@@ -365,4 +350,16 @@ export namespace SendElementEntities {
}, },
} }
} }
export function shake(): SendFaceElement {
return {
elementType: ElementType.Face,
elementId: '',
faceElement: {
faceIndex: 1,
faceType: 5,
pokeType: 1,
},
}
}
} }

View File

@@ -1,5 +1,15 @@
{ {
"sysface": [ "sysface": [
{
"QSid": "419",
"QDes": "/火车",
"IQLid": "419",
"AQLid": "419",
"EMCode": "10419",
"AniStickerType": 3,
"AniStickerPackId": "1",
"AniStickerId": "47"
},
{ {
"QSid": "392", "QSid": "392",
"QDes": "/龙年快乐", "QDes": "/龙年快乐",
@@ -3662,4 +3672,4 @@
"EMCode": "401016" "EMCode": "401016"
} }
] ]
} }

View File

@@ -31,30 +31,18 @@ export class RkeyManager {
isExpired(): boolean { isExpired(): boolean {
const now = new Date().getTime() / 1000 const now = new Date().getTime() / 1000
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`)
return now > this.rkeyData.expired_time return now > this.rkeyData.expired_time
} }
async refreshRkey() { async refreshRkey() {
//刷新rkey
this.rkeyData = await this.fetchServerRkey() this.rkeyData = await this.fetchServerRkey()
} }
async fetchServerRkey() { async fetchServerRkey(): Promise<ServerRkeyData> {
return new Promise<ServerRkeyData>((resolve, reject) => { const response = await fetch(this.serverUrl)
fetch(this.serverUrl) if (!response.ok) {
.then(response => { throw new Error(response.statusText)
if (!response.ok) { }
return reject(response.statusText) // 请求失败,返回错误信息 return response.json()
}
return response.json() // 解析 JSON 格式的响应体
})
.then(data => {
resolve(data)
})
.catch(error => {
reject(error)
})
})
} }
} }

View File

@@ -26,7 +26,6 @@ export enum ReceiveCmdS {
SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged', SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged',
CACHE_SCAN_FINISH = 'nodeIKernelStorageCleanListener/onFinishScan', CACHE_SCAN_FINISH = 'nodeIKernelStorageCleanListener/onFinishScan',
MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete', MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete',
SKEY_UPDATE = 'onSkeyUpdate',
} }
type NTReturnData = [ type NTReturnData = [
@@ -96,7 +95,7 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
const isLogger = args[3]?.[0]?.eventName?.startsWith('ns-LoggerApi') const isLogger = args[3]?.[0]?.eventName?.startsWith('ns-LoggerApi')
if (!isLogger) { if (!isLogger) {
try { try {
logHook && log('call NTQQ api', thisArg, args) logHook && log('call NTQQ api', args)
} catch (e) { } } catch (e) { }
if (!onlyLog) { if (!onlyLog) {
try { try {
@@ -173,4 +172,4 @@ export function registerCallHook(
export function removeReceiveHook(id: string) { export function removeReceiveHook(id: string) {
const index = receiveHooks.findIndex((h) => h.id === id) const index = receiveHooks.findIndex((h) => h.id === id)
receiveHooks.splice(index, 1) receiveHooks.splice(index, 1)
} }

View File

@@ -1,58 +0,0 @@
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/ntqqapi/types'
export interface IGroupListener {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void
onGroupExtListUpdate(...args: unknown[]): void
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void
onGroupDetailInfoChange(...args: unknown[]): void
onGroupAllInfoChange(...args: unknown[]): void
onGroupsMsgMaskResult(...args: unknown[]): void
onGroupConfMemberChange(...args: unknown[]): void
onGroupBulletinChange(...args: unknown[]): void
onGetGroupBulletinListResult(...args: unknown[]): void
onMemberListChange(arg: {
sceneId: string,
ids: string[],
infos: Map<string, GroupMember>,
finish: boolean,
hasRobot: boolean
}): void
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): void
onSearchMemberChange(...args: unknown[]): void
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void
onGroupStatisticInfoChange(...args: unknown[]): void
onJoinGroupNotify(...args: unknown[]): void
onShutUpMemberListChanged(...args: unknown[]): void
onGroupBulletinRemindNotify(...args: unknown[]): void
onGroupFirstBulletinNotify(...args: unknown[]): void
onJoinGroupNoVerifyFlag(...args: unknown[]): void
onGroupArkInviteStateResult(...args: unknown[]): void
// 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void
}

View File

@@ -1,268 +0,0 @@
import { ChatType, RawMessage } from '@/ntqqapi/types'
export interface OnRichMediaDownloadCompleteParams {
fileModelId: string,
msgElementId: string,
msgId: string,
fileId: string,
fileProgress: string, // '0'
fileSpeed: string, // '0'
fileErrCode: string, // '0'
fileErrMsg: string,
fileDownType: number, // 暂时未知
thumbSize: number,
filePath: string,
totalSize: string,
trasferStatus: number,
step: number,
commonFileInfo: unknown | null,
fileSrvErrCode: string,
clientMsg: string,
businessId: number,
userTotalSpacePerDay: unknown | null,
userUsedSpacePerDay: unknown | null
}
export interface OnGroupFileInfoUpdateParams {
retCode: number
retMsg: string
clientWording: string
isEnd: boolean
item: {
peerId: string
type: number
folderInfo?: {
folderId: string
parentFolderId: string
folderName: string
createTime: number
modifyTime: number
createUin: string
creatorName: string
totalFileCount: number
modifyUin: string
modifyName: string
usedSpace: string
}
fileInfo?: {
fileModelId: string
fileId: string
fileName: string
fileSize: string
busId: number
uploadedSize: string
uploadTime: number
deadTime: number
modifyTime: number
downloadTimes: number
sha: string
sha3: string
md5: string
uploaderLocalPath: string
uploaderName: string
uploaderUin: string
parentFolderId: string
localPath: string
transStatus: number
transType: number
elementId: string
isFolder: boolean
}
}[]
allFileCount: number
nextIndex: number
reqId: number
}
// {
// sessionType: 1,
// chatType: 100,
// peerUid: 'u_PVQ3tl6K78xxxx',
// groupCode: '809079648',
// fromNick: '拾xxxx,
// sig: '0x'
// }
export interface TempOnRecvParams {
sessionType: number,//1
chatType: ChatType,//100
peerUid: string,//uid
groupCode: string,//gc
fromNick: string,//gc name
sig: string,
}
export interface IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage): void
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void
onContactUnreadCntUpdate(hashMap: unknown): void
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void
onEmojiDownloadComplete(emojiNotifyInfo: unknown): void
onEmojiResourceUpdate(emojiResourceInfo: unknown): void
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void
onFileMsgCome(arrayList: unknown): void
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void
onFirstViewGroupGuildMapping(arrayList: unknown): void
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void
onGroupFileInfoAdd(groupItem: unknown): void
onGroupFileInfoUpdate(groupFileListResult: OnGroupFileInfoUpdateParams): void
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void
onGroupTransferInfoAdd(groupItem: unknown): void
onGroupTransferInfoUpdate(groupFileListResult: unknown): void
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void
onInputStatusPush(inputStatusInfo: unknown): void
onKickedOffLine(kickedInfo: unknown): void
onLineDev(arrayList: unknown): void
onLogLevelChanged(j2: unknown): void
onMsgAbstractUpdate(arrayList: unknown): void
onMsgBoxChanged(arrayList: unknown): void
onMsgDelete(contact: unknown, arrayList: unknown): void
onMsgEventListUpdate(hashMap: unknown): void
onMsgInfoListAdd(arrayList: unknown): void
onMsgInfoListUpdate(msgList: RawMessage[]): void
onMsgQRCodeStatusChanged(i2: unknown): void
onMsgRecall(i2: unknown, str: unknown, j2: unknown): void
onMsgSecurityNotify(msgRecord: unknown): void
onMsgSettingUpdate(msgSetting: unknown): void
onNtFirstViewMsgSyncEnd(): void
onNtMsgSyncEnd(): void
onNtMsgSyncStart(): void
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void
onRecvGroupGuildFlag(i2: unknown): void
onRecvMsg(...arrayList: unknown[]): void
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void
onRecvOnlineFileMsg(arrayList: unknown): void
onRecvS2CMsg(arrayList: unknown): void
onRecvSysMsg(arrayList: unknown): void
onRecvUDCFlag(i2: unknown): void
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void
onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void
onSearchGroupFileInfoUpdate(searchGroupFileResult:
{
result: {
retCode: number,
retMsg: string,
clientWording: string
},
syncCookie: string,
totalMatchCount: number,
ownerMatchCount: number,
isEnd: boolean,
reqId: number,
item: Array<{
groupCode: string,
groupName: string,
uploaderUin: string,
uploaderName: string,
matchUin: string,
matchWords: Array<unknown>,
fileNameHits: Array<{
start: number,
end: number
}>,
fileModelId: string,
fileId: string,
fileName: string,
fileSize: string,
busId: number,
uploadTime: number,
modifyTime: number,
deadTime: number,
downloadTimes: number,
localPath: string
}>
}): void
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void
onUnreadCntAfterFirstView(hashMap: unknown): void
onUnreadCntUpdate(hashMap: unknown): void
onUserChannelTabStatusChanged(z: unknown): void
onUserOnlineStatusChanged(z: unknown): void
onUserTabStatusChanged(arrayList: unknown): void
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]): void
onMsgWithRichLinkInfoUpdate(...args: unknown[]): void
onRedTouchChanged(...args: unknown[]): void
// 第一次发现于Win 9.9.9 23159
onBroadcastHelperProgerssUpdate(...args: unknown[]): void
}

View File

@@ -1,15 +0,0 @@
import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types'
export interface IProfileListener {
onProfileSimpleChanged(...args: unknown[]): void
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void
onProfileDetailInfoChanged(profile: User): void
onStatusUpdate(...args: unknown[]): void
onSelfStatusChanged(...args: unknown[]): void
onStrangerRemarkChanged(...args: unknown[]): void
}

View File

@@ -1,3 +0,0 @@
export * from './NodeIKernelProfileListener'
export * from './NodeIKernelGroupListener'
export * from './NodeIKernelMsgListener'

View File

@@ -14,7 +14,7 @@ import {
NodeIKernelRichMediaService, NodeIKernelRichMediaService,
NodeIKernelTicketService, NodeIKernelTicketService,
NodeIKernelTipOffService, NodeIKernelTipOffService,
NodeIKernelSearchService, NodeIKernelRobotService
} from './services' } from './services'
export enum NTClass { export enum NTClass {
@@ -40,7 +40,6 @@ export enum NTMethod {
MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild', MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild',
RECALL_MSG = 'nodeIKernelMsgService/recallMsg', RECALL_MSG = 'nodeIKernelMsgService/recallMsg',
EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes', EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes',
FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment',
SELF_INFO = 'fetchAuthData', SELF_INFO = 'fetchAuthData',
FILE_TYPE = 'getFileType', FILE_TYPE = 'getFileType',
@@ -94,13 +93,13 @@ interface NTService {
nodeIKernelRichMediaService: NodeIKernelRichMediaService nodeIKernelRichMediaService: NodeIKernelRichMediaService
nodeIKernelTicketService: NodeIKernelTicketService nodeIKernelTicketService: NodeIKernelTicketService
nodeIKernelTipOffService: NodeIKernelTipOffService nodeIKernelTipOffService: NodeIKernelTipOffService
nodeIKernelSearchService: NodeIKernelSearchService nodeIKernelRobotService: NodeIKernelRobotService
} }
interface InvokeOptions<ReturnType> { interface InvokeOptions<ReturnType> {
className?: NTClass className?: NTClass
channel?: NTChannel channel?: NTChannel
classNameIsRegister?: boolean registerEvent?: boolean
cbCmd?: string | string[] cbCmd?: string | string[]
cmdCB?: (payload: ReturnType, result: unknown) => boolean cmdCB?: (payload: ReturnType, result: unknown) => boolean
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
@@ -117,17 +116,21 @@ export function invoke<
const timeout = options.timeout ?? 5000 const timeout = options.timeout ?? 5000
const afterFirstCmd = options.afterFirstCmd ?? true const afterFirstCmd = options.afterFirstCmd ?? true
let eventName = className + '-' + channel[channel.length - 1] let eventName = className + '-' + channel[channel.length - 1]
if (options.classNameIsRegister) { if (options.registerEvent) {
eventName += '-register' eventName += '-register'
} }
return new Promise<R>((resolve, reject) => { return new Promise<R>((resolve, reject) => {
const apiArgs = [method, ...args] const apiArgs = [method, ...args]
const callbackId = randomUUID() const callbackId = randomUUID()
let success = false const timeoutId = setTimeout(() => {
log(`ntqq api timeout ${channel}, ${eventName}, ${method}`, apiArgs)
reject(`ntqq api timeout ${channel}, ${eventName}, ${method}, ${apiArgs}`)
}, timeout)
if (!options.cbCmd) { if (!options.cbCmd) {
// QQ后端会返回结果并且可以根据uuid识别 // QQ后端会返回结果并且可以根据uuid识别
hookApiCallbacks[callbackId] = res => { hookApiCallbacks[callbackId] = res => {
success = true clearTimeout(timeoutId)
resolve(res) resolve(res)
} }
} }
@@ -139,13 +142,13 @@ export function invoke<
if (options.cmdCB) { if (options.cmdCB) {
if (options.cmdCB(payload, result)) { if (options.cmdCB(payload, result)) {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true clearTimeout(timeoutId)
resolve(payload) resolve(payload)
} }
} }
else { else {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true clearTimeout(timeoutId)
resolve(payload) resolve(payload)
} }
}) })
@@ -157,17 +160,12 @@ export function invoke<
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback()
} }
else { else {
log('ntqq api call failed,', method, res) log('ntqq api call failed,', method, args, res)
reject(`ntqq api call failed, ${method}, ${res.errMsg}`) clearTimeout(timeoutId)
reject(`ntqq api call failed, ${method}, ${res?.errMsg}`)
} }
} }
} }
setTimeout(() => {
if (!success) {
log(`ntqq api timeout ${channel}, ${eventName}, ${method}`, apiArgs)
reject(`ntqq api timeout ${channel}, ${eventName}, ${method}, ${apiArgs}`)
}
}, timeout)
ipcMain.emit( ipcMain.emit(
channel, channel,
@@ -181,4 +179,4 @@ export function invoke<
apiArgs, apiArgs,
) )
}) })
} }

View File

@@ -34,29 +34,6 @@ export namespace SysMsg {
/** SystemMessage bodyWrapper. */ /** SystemMessage bodyWrapper. */
public bodyWrapper?: (SysMsg.ISystemMessageBodyWrapper|null); public bodyWrapper?: (SysMsg.ISystemMessageBodyWrapper|null);
/**
* Creates a new SystemMessage instance using the specified properties.
* @param [properties] Properties to set
* @returns SystemMessage instance
*/
public static create(properties?: SysMsg.ISystemMessage): SysMsg.SystemMessage;
/**
* Encodes the specified SystemMessage message. Does not implicitly {@link SysMsg.SystemMessage.verify|verify} messages.
* @param message SystemMessage message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.ISystemMessage, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified SystemMessage message, length delimited. Does not implicitly {@link SysMsg.SystemMessage.verify|verify} messages.
* @param message SystemMessage message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.ISystemMessage, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a SystemMessage message from the specified reader or buffer. * Decodes a SystemMessage message from the specified reader or buffer.
* @param reader Reader or buffer to decode from * @param reader Reader or buffer to decode from
@@ -76,34 +53,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessage; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessage;
/**
* Verifies a SystemMessage message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a SystemMessage message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns SystemMessage
*/
public static fromObject(object: { [k: string]: any }): SysMsg.SystemMessage;
/**
* Creates a plain object from a SystemMessage message. Also converts values to other types if specified.
* @param message SystemMessage
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.SystemMessage, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this SystemMessage to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for SystemMessage * Gets the default type url for SystemMessage
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
@@ -149,29 +98,6 @@ export namespace SysMsg {
/** SystemMessageHeader uid. */ /** SystemMessageHeader uid. */
public uid?: (string|null); public uid?: (string|null);
/**
* Creates a new SystemMessageHeader instance using the specified properties.
* @param [properties] Properties to set
* @returns SystemMessageHeader instance
*/
public static create(properties?: SysMsg.ISystemMessageHeader): SysMsg.SystemMessageHeader;
/**
* Encodes the specified SystemMessageHeader message. Does not implicitly {@link SysMsg.SystemMessageHeader.verify|verify} messages.
* @param message SystemMessageHeader message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.ISystemMessageHeader, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified SystemMessageHeader message, length delimited. Does not implicitly {@link SysMsg.SystemMessageHeader.verify|verify} messages.
* @param message SystemMessageHeader message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.ISystemMessageHeader, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a SystemMessageHeader message from the specified reader or buffer. * Decodes a SystemMessageHeader message from the specified reader or buffer.
* @param reader Reader or buffer to decode from * @param reader Reader or buffer to decode from
@@ -191,34 +117,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageHeader; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageHeader;
/**
* Verifies a SystemMessageHeader message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a SystemMessageHeader message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns SystemMessageHeader
*/
public static fromObject(object: { [k: string]: any }): SysMsg.SystemMessageHeader;
/**
* Creates a plain object from a SystemMessageHeader message. Also converts values to other types if specified.
* @param message SystemMessageHeader
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.SystemMessageHeader, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this SystemMessageHeader to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for SystemMessageHeader * Gets the default type url for SystemMessageHeader
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
@@ -276,29 +174,6 @@ export namespace SysMsg {
/** SystemMessageMsgSpec other. */ /** SystemMessageMsgSpec other. */
public other: number; public other: number;
/**
* Creates a new SystemMessageMsgSpec instance using the specified properties.
* @param [properties] Properties to set
* @returns SystemMessageMsgSpec instance
*/
public static create(properties?: SysMsg.ISystemMessageMsgSpec): SysMsg.SystemMessageMsgSpec;
/**
* Encodes the specified SystemMessageMsgSpec message. Does not implicitly {@link SysMsg.SystemMessageMsgSpec.verify|verify} messages.
* @param message SystemMessageMsgSpec message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.ISystemMessageMsgSpec, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified SystemMessageMsgSpec message, length delimited. Does not implicitly {@link SysMsg.SystemMessageMsgSpec.verify|verify} messages.
* @param message SystemMessageMsgSpec message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.ISystemMessageMsgSpec, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a SystemMessageMsgSpec message from the specified reader or buffer. * Decodes a SystemMessageMsgSpec message from the specified reader or buffer.
* @param reader Reader or buffer to decode from * @param reader Reader or buffer to decode from
@@ -318,34 +193,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageMsgSpec; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageMsgSpec;
/**
* Verifies a SystemMessageMsgSpec message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a SystemMessageMsgSpec message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns SystemMessageMsgSpec
*/
public static fromObject(object: { [k: string]: any }): SysMsg.SystemMessageMsgSpec;
/**
* Creates a plain object from a SystemMessageMsgSpec message. Also converts values to other types if specified.
* @param message SystemMessageMsgSpec
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.SystemMessageMsgSpec, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this SystemMessageMsgSpec to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for SystemMessageMsgSpec * Gets the default type url for SystemMessageMsgSpec
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
@@ -373,29 +220,6 @@ export namespace SysMsg {
/** SystemMessageBodyWrapper body. */ /** SystemMessageBodyWrapper body. */
public body: Uint8Array; public body: Uint8Array;
/**
* Creates a new SystemMessageBodyWrapper instance using the specified properties.
* @param [properties] Properties to set
* @returns SystemMessageBodyWrapper instance
*/
public static create(properties?: SysMsg.ISystemMessageBodyWrapper): SysMsg.SystemMessageBodyWrapper;
/**
* Encodes the specified SystemMessageBodyWrapper message. Does not implicitly {@link SysMsg.SystemMessageBodyWrapper.verify|verify} messages.
* @param message SystemMessageBodyWrapper message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.ISystemMessageBodyWrapper, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified SystemMessageBodyWrapper message, length delimited. Does not implicitly {@link SysMsg.SystemMessageBodyWrapper.verify|verify} messages.
* @param message SystemMessageBodyWrapper message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.ISystemMessageBodyWrapper, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a SystemMessageBodyWrapper message from the specified reader or buffer. * Decodes a SystemMessageBodyWrapper message from the specified reader or buffer.
* @param reader Reader or buffer to decode from * @param reader Reader or buffer to decode from
@@ -415,34 +239,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageBodyWrapper; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.SystemMessageBodyWrapper;
/**
* Verifies a SystemMessageBodyWrapper message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a SystemMessageBodyWrapper message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns SystemMessageBodyWrapper
*/
public static fromObject(object: { [k: string]: any }): SysMsg.SystemMessageBodyWrapper;
/**
* Creates a plain object from a SystemMessageBodyWrapper message. Also converts values to other types if specified.
* @param message SystemMessageBodyWrapper
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.SystemMessageBodyWrapper, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this SystemMessageBodyWrapper to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for SystemMessageBodyWrapper * Gets the default type url for SystemMessageBodyWrapper
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
@@ -482,29 +278,6 @@ export namespace SysMsg {
/** LikeDetail nickname. */ /** LikeDetail nickname. */
public nickname: string; public nickname: string;
/**
* Creates a new LikeDetail instance using the specified properties.
* @param [properties] Properties to set
* @returns LikeDetail instance
*/
public static create(properties?: SysMsg.ILikeDetail): SysMsg.LikeDetail;
/**
* Encodes the specified LikeDetail message. Does not implicitly {@link SysMsg.LikeDetail.verify|verify} messages.
* @param message LikeDetail message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.ILikeDetail, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified LikeDetail message, length delimited. Does not implicitly {@link SysMsg.LikeDetail.verify|verify} messages.
* @param message LikeDetail message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.ILikeDetail, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a LikeDetail message from the specified reader or buffer. * Decodes a LikeDetail message from the specified reader or buffer.
* @param reader Reader or buffer to decode from * @param reader Reader or buffer to decode from
@@ -524,34 +297,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.LikeDetail; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.LikeDetail;
/**
* Verifies a LikeDetail message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a LikeDetail message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns LikeDetail
*/
public static fromObject(object: { [k: string]: any }): SysMsg.LikeDetail;
/**
* Creates a plain object from a LikeDetail message. Also converts values to other types if specified.
* @param message LikeDetail
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.LikeDetail, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this LikeDetail to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for LikeDetail * Gets the default type url for LikeDetail
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
@@ -591,29 +336,6 @@ export namespace SysMsg {
/** LikeMsg detail. */ /** LikeMsg detail. */
public detail?: (SysMsg.ILikeDetail|null); public detail?: (SysMsg.ILikeDetail|null);
/**
* Creates a new LikeMsg instance using the specified properties.
* @param [properties] Properties to set
* @returns LikeMsg instance
*/
public static create(properties?: SysMsg.ILikeMsg): SysMsg.LikeMsg;
/**
* Encodes the specified LikeMsg message. Does not implicitly {@link SysMsg.LikeMsg.verify|verify} messages.
* @param message LikeMsg message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.ILikeMsg, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified LikeMsg message, length delimited. Does not implicitly {@link SysMsg.LikeMsg.verify|verify} messages.
* @param message LikeMsg message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.ILikeMsg, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a LikeMsg message from the specified reader or buffer. * Decodes a LikeMsg message from the specified reader or buffer.
* @param reader Reader or buffer to decode from * @param reader Reader or buffer to decode from
@@ -633,34 +355,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.LikeMsg; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.LikeMsg;
/**
* Verifies a LikeMsg message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a LikeMsg message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns LikeMsg
*/
public static fromObject(object: { [k: string]: any }): SysMsg.LikeMsg;
/**
* Creates a plain object from a LikeMsg message. Also converts values to other types if specified.
* @param message LikeMsg
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.LikeMsg, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this LikeMsg to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for LikeMsg * Gets the default type url for LikeMsg
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
@@ -669,11 +363,63 @@ export namespace SysMsg {
public static getTypeUrl(typeUrlPrefix?: string): string; public static getTypeUrl(typeUrlPrefix?: string): string;
} }
/** Properties of a ProfileLikeSubTip. */
interface IProfileLikeSubTip {
/** ProfileLikeSubTip msg */
msg?: (SysMsg.ILikeMsg|null);
}
/** Represents a ProfileLikeSubTip. */
class ProfileLikeSubTip implements IProfileLikeSubTip {
/**
* Constructs a new ProfileLikeSubTip.
* @param [properties] Properties to set
*/
constructor(properties?: SysMsg.IProfileLikeSubTip);
/** ProfileLikeSubTip msg. */
public msg?: (SysMsg.ILikeMsg|null);
/**
* Decodes a ProfileLikeSubTip message from the specified reader or buffer.
* @param reader Reader or buffer to decode from
* @param [length] Message length if known beforehand
* @returns ProfileLikeSubTip
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): SysMsg.ProfileLikeSubTip;
/**
* Decodes a ProfileLikeSubTip message from the specified reader or buffer, length delimited.
* @param reader Reader or buffer to decode from
* @returns ProfileLikeSubTip
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.ProfileLikeSubTip;
/**
* Gets the default type url for ProfileLikeSubTip
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
* @returns The default type url
*/
public static getTypeUrl(typeUrlPrefix?: string): string;
}
/** Properties of a ProfileLikeTip. */ /** Properties of a ProfileLikeTip. */
interface IProfileLikeTip { interface IProfileLikeTip {
/** ProfileLikeTip msg */ /** ProfileLikeTip msgType */
msg?: (SysMsg.ILikeMsg|null); msgType?: (number|null);
/** ProfileLikeTip subType */
subType?: (number|null);
/** ProfileLikeTip content */
content?: (SysMsg.IProfileLikeSubTip|null);
} }
/** Represents a ProfileLikeTip. */ /** Represents a ProfileLikeTip. */
@@ -685,31 +431,14 @@ export namespace SysMsg {
*/ */
constructor(properties?: SysMsg.IProfileLikeTip); constructor(properties?: SysMsg.IProfileLikeTip);
/** ProfileLikeTip msg. */ /** ProfileLikeTip msgType. */
public msg?: (SysMsg.ILikeMsg|null); public msgType: number;
/** /** ProfileLikeTip subType. */
* Creates a new ProfileLikeTip instance using the specified properties. public subType: number;
* @param [properties] Properties to set
* @returns ProfileLikeTip instance
*/
public static create(properties?: SysMsg.IProfileLikeTip): SysMsg.ProfileLikeTip;
/** /** ProfileLikeTip content. */
* Encodes the specified ProfileLikeTip message. Does not implicitly {@link SysMsg.ProfileLikeTip.verify|verify} messages. public content?: (SysMsg.IProfileLikeSubTip|null);
* @param message ProfileLikeTip message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encode(message: SysMsg.IProfileLikeTip, writer?: $protobuf.Writer): $protobuf.Writer;
/**
* Encodes the specified ProfileLikeTip message, length delimited. Does not implicitly {@link SysMsg.ProfileLikeTip.verify|verify} messages.
* @param message ProfileLikeTip message or plain object to encode
* @param [writer] Writer to encode to
* @returns Writer
*/
public static encodeDelimited(message: SysMsg.IProfileLikeTip, writer?: $protobuf.Writer): $protobuf.Writer;
/** /**
* Decodes a ProfileLikeTip message from the specified reader or buffer. * Decodes a ProfileLikeTip message from the specified reader or buffer.
@@ -730,34 +459,6 @@ export namespace SysMsg {
*/ */
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.ProfileLikeTip; public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): SysMsg.ProfileLikeTip;
/**
* Verifies a ProfileLikeTip message.
* @param message Plain object to verify
* @returns `null` if valid, otherwise the reason why it is not
*/
public static verify(message: { [k: string]: any }): (string|null);
/**
* Creates a ProfileLikeTip message from a plain object. Also converts values to their respective internal types.
* @param object Plain object
* @returns ProfileLikeTip
*/
public static fromObject(object: { [k: string]: any }): SysMsg.ProfileLikeTip;
/**
* Creates a plain object from a ProfileLikeTip message. Also converts values to other types if specified.
* @param message ProfileLikeTip
* @param [options] Conversion options
* @returns Plain object
*/
public static toObject(message: SysMsg.ProfileLikeTip, options?: $protobuf.IConversionOptions): { [k: string]: any };
/**
* Converts this ProfileLikeTip to JSON.
* @returns JSON object
*/
public toJSON(): { [k: string]: any };
/** /**
* Gets the default type url for ProfileLikeTip * Gets the default type url for ProfileLikeTip
* @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,23 @@ syntax = "proto3";
package SysMsg; package SysMsg;
message LikeDetail { message LikeDetail {
string txt = 1; string txt = 1;
uint32 uin = 3; uint32 uin = 3;
string nickname = 5; string nickname = 5;
} }
message LikeMsg { message LikeMsg {
uint32 count = 1; uint32 count = 1;
uint32 time = 2; uint32 time = 2;
LikeDetail detail = 3; LikeDetail detail = 3;
}
message ProfileLikeSubTip {
LikeMsg msg = 14;
} }
message ProfileLikeTip { message ProfileLikeTip {
LikeMsg msg = 14; uint32 msgType = 1;
} uint32 subType = 2;
ProfileLikeSubTip content = 203;
}

View File

@@ -1,125 +1,27 @@
import { BuddyListReqType } from '@/ntqqapi/types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
export interface NodeIKernelBuddyService { export interface NodeIKernelBuddyService {
// 26702 以上
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & { getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
data: Array<{ data: {
categoryId: number, categoryId: number
categorySortId: number, categorySortId: number
categroyName: string, categroyName: string
categroyMbCount: number, categroyMbCount: number
onlineCount: number, onlineCount: number
buddyUids: Array<string> buddyUids: string[]
}> }[]
}> }>
//26702 以上
getBuddyListFromCache(callFrom: string): Promise<Array<
{
categoryId: number,//9999应该跳过 那是兜底数据吧
categorySortId: number,//排序方式
categroyName: string,//分类名
categroyMbCount: number,//不懂
onlineCount: number,//在线数目
buddyUids: Array<string>//Uids
}>>
addKernelBuddyListener(listener: unknown): number
getAllBuddyCount(): number
removeKernelBuddyListener(listener: unknown): void
getBuddyList(nocache: boolean): Promise<GeneralCallResult>
getBuddyNick(uid: number): string
getBuddyRemark(uid: number): string
setBuddyRemark(uid: number, remark: string): void setBuddyRemark(uid: number, remark: string): void
getAvatarUrl(uid: number): string
isBuddy(uid: string): boolean isBuddy(uid: string): boolean
getCategoryNameWithUid(uid: number): string
getTargetBuddySetting(uid: number): unknown
getTargetBuddySettingByType(uid: number, type: number): unknown
getBuddyReqUnreadCnt(): number
getBuddyReq(): unknown
delBuddyReq(uid: number): void
clearBuddyReqUnreadCnt(): void
reqToAddFriends(uid: number, msg: string): void
setSpacePermission(uid: number, permission: number): void
approvalFriendRequest(arg: { approvalFriendRequest(arg: {
friendUid: string friendUid: string
reqTime: string reqTime: string
accept: boolean accept: boolean
}): Promise<void> }): Promise<void>
delBuddy(uid: number): void getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<GeneralCallResult & { arkMsg: string }>
}
delBatchBuddy(uids: number[]): void
getSmartInfos(uid: number): unknown
setBuddyCategory(uid: number, category: number): void
setBatchBuddyCategory(uids: number[], category: number): void
addCategory(category: string): void
delCategory(category: string): void
renameCategory(oldCategory: string, newCategory: string): void
resortCategory(categorys: string[]): void
pullCategory(uid: number, category: string): void
setTop(uid: number, isTop: boolean): void
SetSpecialCare(uid: number, isSpecialCare: boolean): void
setMsgNotify(uid: number, isNotify: boolean): void
hasBuddyList(): boolean
setBlock(uid: number, isBlock: boolean): void
isBlocked(uid: number): boolean
modifyAddMeSetting(setting: unknown): void
getAddMeSetting(): unknown
getDoubtBuddyReq(): unknown
getDoubtBuddyUnreadNum(): number
approvalDoubtBuddyReq(uid: number, isAgree: boolean): void
delDoubtBuddyReq(uid: number): void
delAllDoubtBuddyReq(): void
reportDoubtBuddyReqUnread(): void
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown>
isNull(): boolean
}

View File

@@ -1,207 +1,99 @@
import { import {
GroupExtParam,
GroupMember, GroupMember,
GroupMemberRole, GroupMemberRole,
GroupNotifyType, GroupNotifyType,
GroupRequestOperateTypes, GroupRequestOperateTypes,
} from '@/ntqqapi/types' } from '@/ntqqapi/types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
getMemberCommonInfo(Req: { getGroupHonorList(req: { groupCode: number[] }): Promise<{
groupCode: string, errCode: number
startUin: string, errMsg: string
identifyFlag: string, groupMemberHonorList: {
uinList: string[], honorList: {
memberCommonFilter: { groupCode: string
memberUin: number, id: number[]
uinFlag: number, isGray: number
uinFlagExt: number, }[]
uinMobileFlag: number, cacheTs: number
shutUpTime: number, honorInfos: unknown[]
privilege: number, joinTime: number
}, }
memberNum: number, }>
filterMethod: string,
onlineFlag: string,
realSpecialTitleFlag: number
}): Promise<unknown>
//26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown>
//26702
getGroupHonorList(groupCodes: Array<string>): unknown
getUinByUids(uins: string[]): Promise<{ getUinByUids(uins: string[]): Promise<{
errCode: number, errCode: number
errMsg: string, errMsg: string
uins: Map<string, string> uins: Map<string, string>
}> }>
getUidByUins(uins: string[]): Promise<{ getUidByUins(uins: string[]): Promise<{
errCode: number, errCode: number
errMsg: string, errMsg: string
uids: Map<string, string> uids: Map<string, string>
}> }>
//26702(其实更早 但是我不知道) queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<{
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown> items: {
groupCode: string
//26702(其实更早 但是我不知道) msgSeq: number
getGroupLatestEssenceList(groupCode: string): Promise<unknown> msgRandom: number
msgSenderUin: string
//26702(其实更早 但是我不知道) msgSenderNick: string
shareDigest(Req: { opType: number
appId: string, opUin: string
appType: number, opNick: string
msgStyle: number, opTime: number
recvUin: string, grayTipSeq: string
sendType: number, }[]
clientInfo: {
platform: number
},
richMsg: {
usingArk: boolean,
title: string,
summary: string,
url: string,
pictureUrl: string,
brief: string
}
}): Promise<unknown>
//26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>
//26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number,
errMsg: string,
result: {
ids: Array<{
uid: string,
index: number//0
}>,
infos: Dict,
finish: true,
hasRobot: false
}
}> }>
setHeader(uid: string, path: string): unknown setHeader(uid: string, path: string): unknown
addKernelGroupListener(listener: unknown): number
removeKernelGroupListener(listenerId: unknown): void
createMemberListScene(groupCode: string, scene: string): string createMemberListScene(groupCode: string, scene: string): string
destroyMemberListScene(SceneId: string): void
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string, errCode: number
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean } errMsg: string
result: {
ids: {
uid: string
index: number
}[]
infos: Map<string, GroupMember>
finish: boolean
hasRobot: boolean
}
}> }>
getPrevMemberList(): unknown
monitorMemberList(): unknown
searchMember(sceneId: string, keywords: string[]): unknown
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult>
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void> kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void>
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void
modifyMemberCardName(groupCode: string, uid: string, cardName: string): void modifyMemberCardName(groupCode: string, uid: string, cardName: string): void
getTransferableMemberInfo(groupCode: string): unknown//获取整个群的
transferGroup(uid: string): void
getGroupList(force: boolean): Promise<GeneralCallResult>
getGroupExtList(force: boolean): Promise<GeneralCallResult>
getGroupDetailInfo(groupCode: string): unknown
getMemberExtInfo(param: GroupExtParam): Promise<unknown>//req
getGroupAllInfo(): unknown
getDiscussExistInfo(): unknown
getGroupConfMember(): unknown
getGroupMsgMask(): unknown
getGroupPortrait(): void
modifyGroupName(groupCode: string, groupName: string, arg: false): void modifyGroupName(groupCode: string, groupName: string, arg: false): void
modifyGroupRemark(groupCode: string, remark: string): void
modifyGroupDetailInfo(groupCode: string, arg: unknown): void
setGroupMsgMask(groupCode: string, arg: unknown): void
changeGroupShieldSettingTemp(groupCode: string, arg: unknown): void
inviteToGroup(arg: unknown): void
inviteMembersToGroup(args: unknown[]): void
inviteMembersToGroupWithMsg(args: unknown): void
createGroup(arg: unknown): void
createGroupWithMembers(arg: unknown): void
quitGroup(groupCode: string): void quitGroup(groupCode: string): void
destroyGroup(groupCode: string): void getSingleScreenNotifies(force: boolean, startSeq: string, num: number): Promise<GeneralCallResult>
//获取单屏群通知列表
getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise<GeneralCallResult>
clearGroupNotifies(groupCode: string): void
getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>
clearGroupNotifiesUnreadCount(groupCode: string): void
operateSysNotify( operateSysNotify(
doubt: boolean, doubt: boolean,
operateMsg: { operateMsg: {
operateType: GroupRequestOperateTypes, // 2 拒绝 operateType: GroupRequestOperateTypes // 2 拒绝
targetMsg: { targetMsg: {
seq: string, // 通知序列号 seq: string // 通知序列号
type: GroupNotifyType, type: GroupNotifyType
groupCode: string, groupCode: string
postscript: string postscript: string
} }
}): Promise<void> }
): Promise<void>
setTop(groupCode: string, isTop: boolean): void
getGroupBulletin(groupCode: string): unknown
deleteGroupBulletin(groupCode: string, seq: string): void
publishGroupBulletin(groupCode: string, pskey: string, data: unknown): Promise<GeneralCallResult> publishGroupBulletin(groupCode: string, pskey: string, data: unknown): Promise<GeneralCallResult>
publishInstructionForNewcomers(groupCode: string, arg: unknown): void
uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<{ uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<{
errCode: number errCode: number
errMsg: string errMsg: string
@@ -212,44 +104,25 @@ export interface NodeIKernelGroupService {
} }
}> }>
downloadGroupBulletinRichMedia(groupCode: string): unknown getGroupRemainAtTimes(groupCode: string): Promise<GeneralCallResult & {
atInfo: {
getGroupBulletinList(groupCode: string): unknown canAtAll: boolean
RemainAtAllCountForUin: number
getGroupStatisticInfo(groupCode: string): unknown RemainAtAllCountForGroup: number
atTimesMsg: string
getGroupRemainAtTimes(groupCode: string): number canNotAtAllMsg: ''
}
getJoinGroupNoVerifyFlag(groupCode: string): unknown }>
getGroupArkInviteState(groupCode: string): unknown
reqToJoinGroup(groupCode: string, arg: unknown): void
setGroupShutUp(groupCode: string, shutUp: boolean): void setGroupShutUp(groupCode: string, shutUp: boolean): void
getGroupShutUpMemberList(groupCode: string): unknown[]
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void> setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>
getGroupRecommendContactArkJson(groupCode: string): unknown getGroupRecommendContactArkJson(groupCode: string): Promise<GeneralCallResult & { arkJson: string }>
getJoinGroupLink(groupCode: string): unknown addGroupEssence(param: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
modifyGroupExtInfo(groupCode: string, arg: unknown): void removeGroupEssence(param: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//需要提前判断是否存在 高版本新增 setHeader(args: unknown[]): Promise<GeneralCallResult>
addGroupEssence(param: { }
groupCode: string
msgRandom: number,
msgSeq: number
}): Promise<unknown>
//需要提前判断是否存在 高版本新增
removeGroupEssence(param: {
groupCode: string
msgRandom: number,
msgSeq: number
}): Promise<unknown>
isNull(): boolean
}

View File

@@ -1,743 +1,98 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types' import { ElementType, MessageElement, Peer, RawMessage, QueryMsgsParams, TmpChatInfoApi } from '@/ntqqapi/types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
export interface QueryMsgsParams {
chatInfo: Peer,
filterMsgType: [],
filterSendersUid: string[],
filterMsgFromTime: string,
filterMsgToTime: string,
pageLimit: number,
isReverseOrder: boolean,
isIncludeCurrent: boolean
}
export interface TmpChatInfoApi {
errMsg: string
result: number
tmpChatInfo?: TmpChatInfo
}
export interface TmpChatInfo {
chatType: number
fromNick: string
groupCode: string
peerUid: string
sessionType: number
sig: string
}
export interface NodeIKernelMsgService { export interface NodeIKernelMsgService {
generateMsgUniqueId(chatType: number, time: string): string generateMsgUniqueId(chatType: number, time: string): string
addKernelMsgListener(nodeIKernelMsgListener: unknown): number sendMsg(msgId: string, peer: Peer, msgElements: MessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult> recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>
addKernelMsgImportToolListener(arg: Dict): unknown
removeKernelMsgListener(args: unknown): unknown
addKernelTempChatSigListener(...args: unknown[]): unknown
removeKernelTempChatSigListener(...args: unknown[]): unknown
setAutoReplyTextList(AutoReplyText: Array<unknown>, i2: number): unknown
getAutoReplyTextList(...args: unknown[]): unknown
getOnLineDev(): void
kickOffLine(DevInfo: Dict): unknown
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult> setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>
fetchStatusMgrInfo(): unknown forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult & {
detailErr: Map<unknown, unknown>
fetchStatusUnitedConfigInfo(): unknown }>
getOnlineStatusSmallIconBasePath(): unknown
getOnlineStatusSmallIconFileNameByUrl(Url: string): unknown
downloadOnlineStatusSmallIconByUrl(arg0: number, arg1: string): unknown
getOnlineStatusBigIconBasePath(): unknown
downloadOnlineStatusBigIconByUrl(arg0: number, arg1: string): unknown
getOnlineStatusCommonPath(arg: string): unknown
getOnlineStatusCommonFileNameByUrl(Url: string): unknown
downloadOnlineStatusCommonByUrl(arg0: string, arg1: string): unknown
// this.tokenType = i2
// this.apnsToken = bArr
// this.voipToken = bArr2
// this.profileId = str
setToken(arg: Dict): unknown
switchForeGround(): unknown
switchBackGround(arg: Dict): unknown
//hex
setTokenForMqq(token: string): unknown
switchForeGroundForMqq(...args: unknown[]): unknown
switchBackGroundForMqq(...args: unknown[]): unknown
getMsgSetting(...args: unknown[]): unknown
setMsgSetting(...args: unknown[]): unknown
addSendMsg(...args: unknown[]): unknown
cancelSendMsg(...args: unknown[]): unknown
switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown
reqToOfflineSendMsg(...args: unknown[]): unknown
refuseReceiveOnlineFileMsg(peer: Peer, MsgId: string): unknown
resendMsg(...args: unknown[]): unknown
reeditRecallMsg(...args: unknown[]): unknown
//调用请检查除开commentElements其余参数不能为null
forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult>
forwardMsgWithComment(...args: unknown[]): Promise<GeneralCallResult> forwardMsgWithComment(...args: unknown[]): Promise<GeneralCallResult>
forwardSubMsgWithComment(...args: unknown[]): unknown
forwardRichMsgInVist(...args: unknown[]): unknown
forwardFile(...args: unknown[]): unknown
//Array<Msg>, Peer from, Peer to
multiForwardMsg(...args: unknown[]): unknown
multiForwardMsgWithComment(...args: unknown[]): unknown multiForwardMsgWithComment(...args: unknown[]): unknown
deleteRecallMsg(...args: unknown[]): unknown getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & { msgList: RawMessage[] }>
deleteRecallMsgForLocal(...args: unknown[]): unknown getAioFirstViewLatestMsgsAndAddActiveChat(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
addLocalGrayTipMsg(...args: unknown[]): unknown getMsgsIncludeSelfAndAddActiveChat(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
addLocalJsonGrayTipMsg(...args: unknown[]): unknown getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }>
addLocalJsonGrayTipMsgExt(...args: unknown[]): unknown
IsLocalJsonTipValid(...args: unknown[]): unknown
addLocalAVRecordMsg(...args: unknown[]): unknown
addLocalTofuRecordMsg(...args: unknown[]): unknown
addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<unknown> | number, front: boolean): Promise<unknown>
deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<unknown>
updateElementExtBufForUI(...args: unknown[]): unknown
updateMsgRecordExtPbBufForUI(...args: unknown[]): unknown
startMsgSync(...args: unknown[]): unknown
startGuildMsgSync(...args: unknown[]): unknown
isGuildChannelSync(...args: unknown[]): unknown
getMsgUniqueId(UniqueId: string): string
isMsgMatched(...args: unknown[]): unknown
getOnlineFileMsgs(...args: unknown[]): unknown
getAllOnlineFileMsgs(...args: unknown[]): unknown
getLatestDbMsgs(peer: Peer, cnt: number): Promise<unknown>
getLastMessageList(peer: Peer[]): Promise<unknown>
getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>
// this.$peer = contact
// this.$msgTime = j2
// this.$clientSeq = j3
// this.$cnt = i2
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getMsgsWithStatus(params: {
peer: Peer
msgId: string
msgTime: unknown
cnt: unknown
queryOrder: boolean
isIncludeSelf: boolean
appid: unknown
}): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }> getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getMsgsByMsgId(peer: Peer, ids: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }> getMsgsByMsgId(peer: Peer, ids: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getRecallMsgsByMsgId(peer: Peer, MsgId: string[]): Promise<unknown>
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }> getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }> getSingleMsg(peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown
getSourceOfReplyMsgV2(peer: Peer, RootMsgId: string, ReplyMsgId: string): unknown
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown
getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown
//cnt clientSeq?并不是吧
getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown
getMsgWithAbstractByFilterParam(...args: unknown[]): unknown
queryMsgsWithFilter(...args: unknown[]): unknown
/**
* @deprecated 该函数已被标记为废弃,请使用新的替代方法。
* 使用过滤条件查询消息列表的版本2接口。
*
* 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。
* 函数返回一个Promise解析为查询结果的未知类型对象。
*
* @param MsgId 消息ID用于特定消息的查询。
* @param MsgTime 消息时间,用于指定消息的时间范围。
* @param param 查询参数对象,包含详细的过滤条件和分页信息。
* @param param.chatInfo 聊天信息包括聊天类型和对方用户ID。
* @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。
* @param param.filterSendersUid 需要过滤的发送者用户ID数组。
* @param param.filterMsgFromTime 查询消息的起始时间。
* @param param.filterMsgToTime 查询消息的结束时间。
* @param param.pageLimit 每页的消息数量限制。
* @param param.isReverseOrder 是否按时间顺序倒序返回消息。
* @param param.isIncludeCurrent 是否包含当前页码。
* @returns 返回一个Promise解析为查询结果的未知类型对象。
*/
queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise<unknown>
// this.chatType = i2
// this.peerUid = str
// this.chatInfo = new ChatInfo()
// this.filterMsgType = new ArrayList<>()
// this.filterSendersUid = new ArrayList<>()
// this.chatInfo = chatInfo
// this.filterMsgType = arrayList
// this.filterSendersUid = arrayList2
// this.filterMsgFromTime = j2
// this.filterMsgToTime = j3
// this.pageLimit = i2
// this.isReverseOrder = z
// this.isIncludeCurrent = z2
//queryMsgsWithFilterEx(0L, 0L, 0L, new QueryMsgsParams(new ChatInfo(2, str), new ArrayList(), new ArrayList(), 0L, 0L, 250, false, true))
queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & { queryMsgsWithFilterEx(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<GeneralCallResult & {
msgList: RawMessage[] msgList: RawMessage[]
}> }>
//queryMsgsWithFilterEx(this.$msgId, this.$msgTime, this.$msgSeq, this.$param)
queryFileMsgsDesktop(...args: unknown[]): unknown
setMsgRichInfoFlag(...args: unknown[]): unknown
queryPicOrVideoMsgs(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): Promise<unknown>
queryPicOrVideoMsgsDesktop(...args: unknown[]): unknown
queryEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>
queryTroopEmoticonMsgs(msgId: string, msgTime: string, msgSeq: string, Params: QueryMsgsParams): Promise<unknown>
queryMsgsAndAbstractsWithFilter(msgId: string, msgTime: string, megSeq: string, param: QueryMsgsParams): unknown
setFocusOnGuild(...args: unknown[]): unknown
setFocusSession(...args: unknown[]): unknown
enableFilterUnreadInfoNotify(...args: unknown[]): unknown
enableFilterMsgAbstractNotify(...args: unknown[]): unknown
onScenesChangeForSilenceMode(...args: unknown[]): unknown
getContactUnreadCnt(...args: unknown[]): unknown
getUnreadCntInfo(...args: unknown[]): unknown
getGuildUnreadCntInfo(...args: unknown[]): unknown
getGuildUnreadCntTabInfo(...args: unknown[]): unknown
getAllGuildUnreadCntInfo(...args: unknown[]): unknown
getAllJoinGuildCnt(...args: unknown[]): unknown
getAllDirectSessionUnreadCntInfo(...args: unknown[]): unknown
getCategoryUnreadCntInfo(...args: unknown[]): unknown
getGuildFeedsUnreadCntInfo(...args: unknown[]): unknown
setUnVisibleChannelCntInfo(...args: unknown[]): unknown
setUnVisibleChannelTypeCntInfo(...args: unknown[]): unknown
setVisibleGuildCntInfo(...args: unknown[]): unknown
setMsgRead(peer: Peer): Promise<GeneralCallResult> setMsgRead(peer: Peer): Promise<GeneralCallResult>
setAllC2CAndGroupMsgRead(): Promise<unknown>
setGuildMsgRead(...args: unknown[]): unknown
setAllGuildMsgRead(...args: unknown[]): unknown
setMsgReadAndReport(...args: unknown[]): unknown
setSpecificMsgReadAndReport(...args: unknown[]): unknown
setLocalMsgRead(...args: unknown[]): unknown
setGroupGuildMsgRead(...args: unknown[]): unknown
getGuildGroupTransData(...args: unknown[]): unknown
setGroupGuildBubbleRead(...args: unknown[]): unknown
getGuildGroupBubble(...args: unknown[]): unknown
fetchGroupGuildUnread(...args: unknown[]): unknown
setGroupGuildFlag(...args: unknown[]): unknown
setGuildUDCFlag(...args: unknown[]): unknown
setGuildTabUserFlag(...args: unknown[]): unknown
setBuildMode(flag: number/*0 1 3*/): unknown
setConfigurationServiceData(...args: unknown[]): unknown
setMarkUnreadFlag(...args: unknown[]): unknown
getChannelEventFlow(...args: unknown[]): unknown
getMsgEventFlow(...args: unknown[]): unknown
getRichMediaFilePathForMobileQQSend(...args: unknown[]): unknown
getRichMediaFilePathForGuild(arg: { getRichMediaFilePathForGuild(arg: {
md5HexStr: string, md5HexStr: string
fileName: string, fileName: string
elementType: ElementType, elementType: ElementType
elementSubType: number, elementSubType: number
thumbSize: 0, thumbSize: 0
needCreate: true, needCreate: true
downloadType: 1, downloadType: 1
file_uuid: '' file_uuid: ''
}): string }): string
assembleMobileQQRichMediaFilePath(...args: unknown[]): unknown
getFileThumbSavePathForSend(...args: unknown[]): unknown
getFileThumbSavePath(...args: unknown[]): unknown
//猜测居多
translatePtt2Text(MsgId: string, Peer: Dict, MsgElement: Dict): unknown
setPttPlayedState(...args: unknown[]): unknown
// NodeIQQNTWrapperSession fetchFavEmojiList [
// "",
// 48,
// true,
// true
// ]
fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & { fetchFavEmojiList(str: string, num: number, uk1: boolean, uk2: boolean): Promise<GeneralCallResult & {
emojiInfoList: Array<{ emojiInfoList: {
uin: string, uin: string
emoId: number, emoId: number
emoPath: string, emoPath: string
isExist: boolean, isExist: boolean
resId: string, resId: string
url: string, url: string
md5: string, md5: string
emoOriginalPath: string, emoOriginalPath: string
thumbPath: string, thumbPath: string
RomaingType: string, RomaingType: string
isAPNG: false, isAPNG: false
isMarkFace: false, isMarkFace: false
eId: string, eId: string
epId: string, epId: string
ocrWord: string, ocrWord: string
modifyWord: string, modifyWord: string
exposeNum: number, exposeNum: number
clickNum: number, clickNum: number
desc: string desc: string
}> }[]
}> }>
addFavEmoji(...args: unknown[]): unknown
fetchMarketEmoticonList(...args: unknown[]): unknown
fetchMarketEmoticonShowImage(...args: unknown[]): unknown
fetchMarketEmoticonAioImage(...args: unknown[]): unknown
fetchMarketEmotionJsonFile(...args: unknown[]): unknown
getMarketEmoticonPath(...args: unknown[]): unknown
getMarketEmoticonPathBySync(...args: unknown[]): unknown
fetchMarketEmoticonFaceImages(...args: unknown[]): unknown
fetchMarketEmoticonAuthDetail(...args: unknown[]): unknown
getFavMarketEmoticonInfo(...args: unknown[]): unknown
addRecentUsedFace(...args: unknown[]): unknown
getRecentUsedFaceList(...args: unknown[]): unknown
getMarketEmoticonEncryptKeys(...args: unknown[]): unknown
downloadEmojiPic(...args: unknown[]): unknown
deleteFavEmoji(...args: unknown[]): unknown
modifyFavEmojiDesc(...args: unknown[]): unknown
queryFavEmojiByDesc(...args: unknown[]): unknown
getHotPicInfoListSearchString(...args: unknown[]): unknown
getHotPicSearchResult(...args: unknown[]): unknown
getHotPicHotWords(...args: unknown[]): unknown
getHotPicJumpInfo(...args: unknown[]): unknown
getEmojiResourcePath(...args: unknown[]): unknown
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: unknown): unknown
getMsgAbstracts(...args: unknown[]): unknown
getMsgAbstract(...args: unknown[]): unknown
getMsgAbstractList(...args: unknown[]): unknown
getMsgAbstractListBySeqRange(...args: unknown[]): unknown
refreshMsgAbstracts(...args: unknown[]): unknown
refreshMsgAbstractsByGuildIds(...args: unknown[]): unknown
getRichMediaElement(...args: unknown[]): unknown
cancelGetRichMediaElement(...args: unknown[]): unknown
refuseGetRichMediaElement(...args: unknown[]): unknown
switchToOfflineGetRichMediaElement(...args: unknown[]): unknown
downloadRichMedia(...args: unknown[]): unknown downloadRichMedia(...args: unknown[]): unknown
getFirstUnreadMsgSeq(args: { setMsgEmojiLikes(...args: unknown[]): Promise<GeneralCallResult>
peerUid: string
guildId: string
}): unknown
getFirstUnreadCommonMsg(...args: unknown[]): unknown
getFirstUnreadAtmeMsg(...args: unknown[]): unknown
getFirstUnreadAtallMsg(...args: unknown[]): unknown
getNavigateInfo(...args: unknown[]): unknown
getChannelFreqLimitInfo(...args: unknown[]): unknown
getRecentUseEmojiList(...args: unknown[]): unknown
getRecentEmojiList(...args: unknown[]): unknown
setMsgEmojiLikes(...args: unknown[]): unknown
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{
result: number, result: number
errMsg: string, errMsg: string
emojiLikesList: emojiLikesList: {
Array<{ tinyId: string
tinyId: string, nickName: string
nickName: string,
headUrl: string headUrl: string
}>, }[]
cookie: string, cookie: string
isLastPage: boolean, isLastPage: boolean
isFirstPage: boolean isFirstPage: boolean
}> }>
setMsgEmojiLikesForRole(...args: unknown[]): unknown getMultiMsg(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>
clickInlineKeyboardButton(...args: unknown[]): unknown getTempChatInfo(chatType: number, uid: string): Promise<TmpChatInfoApi>
}
setCurOnScreenMsg(...args: unknown[]): unknown
setCurOnScreenMsgForMsgEvent(...args: unknown[]): unknown
getMiscData(key: string): unknown
setMiscData(key: string, value: string): unknown
getBookmarkData(...args: unknown[]): unknown
setBookmarkData(...args: unknown[]): unknown
sendShowInputStatusReq(ChatType: number, EventType: number, toUid: string): Promise<unknown>
queryCalendar(...args: unknown[]): unknown
queryFirstMsgSeq(peer: Peer, ...args: unknown[]): unknown
queryRoamCalendar(...args: unknown[]): unknown
queryFirstRoamMsg(...args: unknown[]): unknown
fetchLongMsg(peer: Peer, msgId: string): unknown
fetchLongMsgWithCb(...args: unknown[]): unknown
setIsStopKernelFetchLongMsg(...args: unknown[]): unknown
insertGameResultAsMsgToDb(...args: unknown[]): unknown
getMultiMsg(...args: unknown[]): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>
setDraft(...args: unknown[]): unknown
getDraft(...args: unknown[]): unknown
deleteDraft(...args: unknown[]): unknown
getRecentHiddenSesionList(...args: unknown[]): unknown
setRecentHiddenSession(...args: unknown[]): unknown
delRecentHiddenSession(...args: unknown[]): unknown
getCurHiddenSession(...args: unknown[]): unknown
setCurHiddenSession(...args: unknown[]): unknown
setReplyDraft(...args: unknown[]): unknown
getReplyDraft(...args: unknown[]): unknown
deleteReplyDraft(...args: unknown[]): unknown
getFirstUnreadAtMsg(peer: Peer): unknown
clearMsgRecords(...args: unknown[]): unknown//设置已读后调用我觉得比较好 清理记录 现在别了
IsExistOldDb(...args: unknown[]): unknown
canImportOldDbMsg(...args: unknown[]): unknown
setPowerStatus(z: boolean): unknown
canProcessDataMigration(...args: unknown[]): unknown
importOldDbMsg(...args: unknown[]): unknown
stopImportOldDbMsgAndroid(...args: unknown[]): unknown
isMqqDataImportFinished(...args: unknown[]): unknown
getMqqDataImportTableNames(...args: unknown[]): unknown
getCurChatImportStatusByUin(...args: unknown[]): unknown
getDataImportUserLevel(): unknown
getMsgQRCode(...args: unknown[]): unknown
getGuestMsgAbstracts(...args: unknown[]): unknown
getGuestMsgByRange(...args: unknown[]): unknown
getGuestMsgAbstractByRange(...args: unknown[]): unknown
registerSysMsgNotification(...args: unknown[]): unknown
unregisterSysMsgNotification(...args: unknown[]): unknown
enterOrExitAio(...args: unknown[]): unknown
// this.peerUid = ""
// this.peerNickname = ""
// this.fromGroupCode = ""
// this.sig = new byte[0]
// this.selfUid = ""
// this.selfPhone = ""
// this.chatType = i2
// this.peerUid = str
// this.peerNickname = str2
// this.fromGroupCode = str3
// this.sig = bArr
// this.selfUid = str4
// this.selfPhone = str5
// this.gameSession = tempChatGameSession
prepareTempChat(args: unknown): unknown//主动临时消息 不做
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>
setContactLocalTop(...args: unknown[]): unknown
switchAnonymousChat(...args: unknown[]): unknown
renameAnonyChatNick(...args: unknown[]): unknown
getAnonymousInfo(...args: unknown[]): unknown
updateAnonymousInfo(...args: unknown[]): unknown
sendSummonMsg(peer: Peer, MsgElement: unknown, MsgAttributeInfo: unknown): Promise<unknown>//频道的东西
outputGuildUnreadInfo(...args: unknown[]): unknown
checkMsgWithUrl(...args: unknown[]): unknown
checkTabListStatus(...args: unknown[]): unknown
getABatchOfContactMsgBoxInfo(...args: unknown[]): unknown
insertMsgToMsgBox(peer: Peer, msgId: string, arg: 2006): unknown
isHitEmojiKeyword(...args: unknown[]): unknown
getKeyWordRelatedEmoji(...args: unknown[]): unknown
recordEmoji(...args: unknown[]): unknown
fetchGetHitEmotionsByWord(args: Dict): Promise<unknown>//表情推荐?
deleteAllRoamMsgs(...args: unknown[]): unknown//漫游消息?
packRedBag(...args: unknown[]): unknown
grabRedBag(...args: unknown[]): unknown
pullDetail(...args: unknown[]): unknown
selectPasswordRedBag(...args: unknown[]): unknown
pullRedBagPasswordList(...args: unknown[]): unknown
requestTianshuAdv(...args: unknown[]): unknown
tianshuReport(...args: unknown[]): unknown
tianshuMultiReport(...args: unknown[]): unknown
GetMsgSubType(a0: number, a1: number): unknown
setIKernelPublicAccountAdapter(...args: unknown[]): unknown
//tempChatGameSession有关
createUidFromTinyId(fromTinyId: string, toTinyId: string): unknown
dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown
dataMigrationGetMsgList(...args: unknown[]): unknown
dataMigrationStopOperation(...args: unknown[]): unknown
//新的希望
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
extensionData: string//"Hex"
extraData: string //""
chatType: number
chatUin: string
msgType: number
msgTime: string
msgSeq: string
msgRandom: string
}>, DataMigrationResourceInfo: {
extraData: string
filePath: string
fileSize: string
msgRandom: string
msgSeq: string
msgSubType: number
msgType: number
}): unknown
dataMigrationGetResourceLocalDestinyPath(...args: unknown[]): unknown
dataMigrationSetIOSPathPrefix(...args: unknown[]): unknown
getServiceAssistantSwitch(...args: unknown[]): unknown
setServiceAssistantSwitch(...args: unknown[]): unknown
setSubscribeFolderUsingSmallRedPoint(...args: unknown[]): unknown
clearGuildNoticeRedPoint(...args: unknown[]): unknown
clearFeedNoticeRedPoint(...args: unknown[]): unknown
clearFeedSquareRead(...args: unknown[]): unknown
IsC2CStyleChatType(...args: unknown[]): unknown
IsTempChatType(uin: number): unknown//猜的
getGuildInteractiveNotification(...args: unknown[]): unknown
getGuildNotificationAbstract(...args: unknown[]): unknown
setFocusOnBase(...args: unknown[]): unknown
queryArkInfo(...args: unknown[]): unknown
queryUserSecQuality(...args: unknown[]): unknown
getGuildMsgAbFlag(...args: unknown[]): unknown
getGroupMsgStorageTime(): unknown//这是嘛啊
}

View File

@@ -3,11 +3,7 @@ import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit' import { Dict } from 'cosmokit'
export interface NodeIKernelProfileLikeService { export interface NodeIKernelProfileLikeService {
addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void setBuddyProfileLike(...args: unknown[]): GeneralCallResult & { succCounts: number }
removeKernelProfileLikeListener(listener: unknown): void
setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number }
getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & { getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & {
info: { info: {
@@ -26,8 +22,4 @@ export interface NodeIKernelProfileLikeService {
start: number start: number
} }
}> }>
}
getProfileLikeScidResourceInfo(...args: unknown[]): void
isNull(): boolean
}

View File

@@ -1,106 +1,18 @@
import { AnyCnameRecord } from 'node:dns'
import { SimpleInfo } from '../types' import { SimpleInfo } from '../types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}
export interface NodeIKernelProfileService { export interface NodeIKernelProfileService {
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>//uin->uid getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>> getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>>
// {
// coreInfo: CoreInfo,
// baseInfo: BaseInfo,
// status: null,
// vasInfo: null,
// relationFlags: null,
// otherFlags: null,
// intimate: null
// }
getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>> getCoreAndBaseInfo(callfrom: string, uids: string[]): Promise<Map<string, SimpleInfo>>
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown> fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>
addKernelProfileListener(listener: unknown): number
removeKernelProfileListener(listenerId: number): void
prepareRegionConfig(...args: unknown[]): unknown
getLocalStrangerRemark(): Promise<AnyCnameRecord>
enumCountryOptions(): Array<string>
enumProvinceOptions(Country: string): Array<string>
enumCityOptions(Country: string, Province: string): unknown
enumAreaOptions(...args: unknown[]): unknown
//SimpleInfo
// this.uid = ""
// this.uid = str
// this.uin = j2
// this.isBuddy = z
// this.coreInfo = coreInfo
// this.baseInfo = baseInfo
// this.status = statusInfo
// this.vasInfo = vasInfo
// this.relationFlags = relationFlag
// this.otherFlags = otherFlag
// this.intimate = intimate
modifySelfProfile(...args: unknown[]): Promise<unknown>
modifyDesktopMiniProfile(param: unknown): Promise<GeneralCallResult>
setNickName(NickName: string): Promise<unknown>
setLongNick(longNick: string): Promise<unknown>
setBirthday(...args: unknown[]): Promise<unknown>
setGander(...args: unknown[]): Promise<unknown>
setHeader(arg: string): Promise<GeneralCallResult> setHeader(arg: string): Promise<GeneralCallResult>
setRecommendImgFlag(...args: unknown[]): Promise<unknown> getUserDetailInfoWithBizInfo(uid: string, biz: unknown[]): Promise<GeneralCallResult>
getUserSimpleInfo(force: boolean, uids: string[],): Promise<unknown>
getUserDetailInfo(uid: string): Promise<unknown>
getUserDetailInfoWithBizInfo(uid: string, Biz: unknown[]): Promise<GeneralCallResult>
getUserDetailInfoByUin(uin: string): Promise<unknown> getUserDetailInfoByUin(uin: string): Promise<unknown>
}
getZplanAvatarInfos(args: string[]): Promise<unknown>
getStatus(uid: string): Promise<unknown>
startStatusPolling(isForceReset: boolean): Promise<unknown>
getSelfStatus(): Promise<unknown>
setdisableEmojiShortCuts(...args: unknown[]): unknown
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList)
getCoreInfo(name: string, arg: unknown[]): unknown
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>())
isNull(): boolean
}

View File

@@ -1,191 +1,23 @@
import { GetFileListParam, MessageElement, Peer } from '../types' import { GetFileListParam, Peer } from '../types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
export enum UrlFileDownloadType {
KUNKNOWN,
KURLFILEDOWNLOADPRIVILEGEICON,
KURLFILEDOWNLOADPHOTOWALL,
KURLFILEDOWNLOADQZONE,
KURLFILEDOWNLOADCOMMON,
KURLFILEDOWNLOADINSTALLAPP
}
export enum RMBizTypeEnum {
KUNKNOWN,
KC2CFILE,
KGROUPFILE,
KC2CPIC,
KGROUPPIC,
KDISCPIC,
KC2CVIDEO,
KGROUPVIDEO,
KC2CPTT,
KGROUPPTT,
KFEEDCOMMENTPIC,
KGUILDFILE,
KGUILDPIC,
KGUILDPTT,
KGUILDVIDEO
}
export interface CommonFileInfo {
bizType: number
chatType: number
elemId: string
favId: string
fileModelId: string
fileName: string
fileSize: string
md5: string
md510m: string
msgId: string
msgTime: string
parent: string
peerUid: string
picThumbPath: Array<string>
sha: string
sha3: string
subId: string
uuid: string
}
export interface NodeIKernelRichMediaService { export interface NodeIKernelRichMediaService {
//getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb)
// public enum VideoCodecFormatType {
// KCODECFORMATH264,
// KCODECFORMATH265,
// KCODECFORMATH266,
// KCODECFORMATAV1
// }
// public enum VideoRequestWay {
// KUNKNOW,
// KHAND,
// KAUTO
// }
getVideoPlayUrl(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, VideoRequestWay: number): Promise<unknown>
//exParams (RMReqExParams)
// this.downSourceType = i2
// this.triggerType = i3
//peer, msgId, elemId, videoCodecFormat, exParams
// 1 0 频道在用
// 1 1
// 0 2
// public static final int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007
// public static final int KDOWNSOURCETYPEAIOINNER = 1
// public static final int KDOWNSOURCETYPEBIGSCREEN = 2
// public static final int KDOWNSOURCETYPEHISTORY = 3
// public static final int KDOWNSOURCETYPEUNKNOWN = 0
// public static final int KTRIGGERTYPEAUTO = 1
// public static final int KTRIGGERTYPEMANUAL = 0
getVideoPlayUrlV2(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, exParams: { downSourceType: number, triggerType: number }): Promise<GeneralCallResult & { getVideoPlayUrlV2(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, exParams: { downSourceType: number, triggerType: number }): Promise<GeneralCallResult & {
urlResult: { urlResult: {
v4IpUrl: [], v4IpUrl: []
v6IpUrl: [], v6IpUrl: []
domainUrl: Array<{ domainUrl: {
url: string, url: string
isHttps: boolean, isHttps: boolean
httpsDomain: string httpsDomain: string
}>, }[]
videoCodecFormat: number videoCodecFormat: number
} }
}> }>
getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown deleteGroupFolder(groupCode: string, folderId: string): Promise<GeneralCallResult & { groupFileCommonResult: { retCode: number, retMsg: string, clientWording: string } }>
// this.senderUid = "" createGroupFolder(groupCode: string, folderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: unknown, groupItem: unknown[] } }>
// this.peerUid = ""
// this.guildId = ""
// this.elem = new MsgElement()
// this.downloadType = i2
// this.thumbSize = i3
// this.msgId = j2
// this.msgRandom = j3
// this.msgSeq = j4
// this.msgTime = j5
// this.chatType = i4
// this.senderUid = str
// this.peerUid = str2
// this.guildId = str3
// this.elem = msgElement
// this.useHttps = num
getVideoPlayUrlInVisit(arg: {
downloadType: number,
thumbSize: number,
msgId: string,
msgRandom: string,
msgSeq: string,
msgTime: string,
chatType: number,
senderUid: string,
peerUid: string,
guildId: string,
ele: MessageElement,
useHttps: boolean
}): Promise<unknown>
//arg双端number
isFileExpired(arg: number): unknown
deleteGroupFolder(GroupCode: string, FolderId: string): Promise<GeneralCallResult & { groupFileCommonResult: { retCode: number, retMsg: string, clientWording: string } }>
//参数与getVideoPlayUrlInVisit一样
downloadRichMediaInVisit(arg: {
downloadType: number,
thumbSize: number,
msgId: string,
msgRandom: string,
msgSeq: string,
msgTime: string,
chatType: number,
senderUid: string,
peerUid: string,
guildId: string,
ele: MessageElement,
useHttps: boolean
}): unknown
//arg3为“”
downloadFileForModelId(peer: Peer, ModelId: string[], arg3: string): unknown
//第三个参数 Array<Type>
// this.fileId = ""
// this.fileName = ""
// this.fileId = str
// this.fileName = str2
// this.fileSize = j2
// this.fileModelId = j3
downloadFileForFileUuid(peer: Peer, uuid: string, arg3: {
fileId: string,
fileName: string,
fileSize: string,
fileModelId: string
}[]): Promise<unknown>
downloadFileByUrlList(fileDownloadTyp: UrlFileDownloadType, urlList: Array<string>): unknown
downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown
createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: unknown, groupItem: Array<unknown> } }>
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
deleteTransferInfo(arg1: unknown, arg2: unknown): unknown
cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown
cancelUrlDownload(arg: unknown): unknown
updateOnlineVideoElemStatus(arg: unknown): unknown
getGroupSpace(arg: unknown): unknown
getGroupFileList(groupCode: string, params: GetFileListParam): Promise<GeneralCallResult & { getGroupFileList(groupCode: string, params: GetFileListParam): Promise<GeneralCallResult & {
groupSpaceResult: { groupSpaceResult: {
@@ -198,69 +30,14 @@ export interface NodeIKernelRichMediaService {
} }
}> }>
getGroupFileInfo(arg1: unknown, arg2: unknown): unknown
getGroupTransferList(arg1: unknown, arg2: unknown): unknown
renameGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
moveGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown moveGroupFile(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
transGroupFile(arg1: unknown, arg2: unknown): unknown deleteGroupFile(groupCode: string, params: Array<number>, files: Array<string>): Promise<GeneralCallResult & {
searchGroupFile(
keywords: Array<string>,
param: {
groupIds: Array<string>,
fileType: number,
context: string,
count: number,
sortType: number,
groupNames: Array<string>
}): Promise<unknown>
searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
transGroupFileResult: { transGroupFileResult: {
result: unknown result: unknown
successFileIdList: Array<unknown> successFileIdList: Array<unknown>
failFileIdList: Array<unknown> failFileIdList: Array<unknown>
} }
}> }>
}
translateEnWordToZn(words: string[]): Promise<GeneralCallResult & { words: string[] }>
getScreenOCR(path: string): Promise<unknown>
batchGetGroupFileCount(Gids: Array<string>): Promise<GeneralCallResult & { groupCodes: Array<string>, groupFileCounts: Array<number> }>
queryPicDownloadSize(arg: unknown): unknown
searchGroupFile(arg1: unknown, arg2: unknown): unknown
searchMoreGroupFile(arg: unknown): unknown
cancelSearcheGroupFile(arg1: unknown, arg2: unknown, arg3: unknown): unknown
onlyDownloadFile(peer: Peer, arg2: unknown, arg3: Array<{
fileId: string,
fileName: string,
fileSize: string,
fileModelId: string
}
>): unknown
onlyUploadFile(arg1: unknown, arg2: unknown): unknown
isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown
uploadRMFileWithoutMsg(arg: {
bizType: RMBizTypeEnum,
filePath: string,
peerUid: string,
transferId: string
useNTV2: string
}): Promise<unknown>
isNull(): boolean
}

View File

@@ -0,0 +1,13 @@
import { GeneralCallResult } from './common'
export interface NodeIKernelRobotService {
getRobotUinRange(req: unknown): Promise<GeneralCallResult & {
response: {
version: number
robotUinRanges: {
minUin: string
maxUin: string
}[]
}
}>
}

View File

@@ -1,128 +0,0 @@
import { ChatType } from '../types'
export interface NodeIKernelSearchService {
addKernelSearchListener(...args: unknown[]): unknown// needs 1 arguments
removeKernelSearchListener(...args: unknown[]): unknown// needs 1 arguments
searchStranger(...args: unknown[]): unknown// needs 3 arguments
searchGroup(...args: unknown[]): unknown// needs 1 arguments
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown
cancelSearchLocalInfo(...args: unknown[]): unknown// needs 3 arguments
searchBuddyChatInfo(...args: unknown[]): unknown// needs 2 arguments
searchMoreBuddyChatInfo(...args: unknown[]): unknown// needs 1 arguments
cancelSearchBuddyChatInfo(...args: unknown[]): unknown// needs 3 arguments
searchContact(...args: unknown[]): unknown// needs 2 arguments
searchMoreContact(...args: unknown[]): unknown// needs 1 arguments
cancelSearchContact(...args: unknown[]): unknown// needs 3 arguments
searchGroupChatInfo(...args: unknown[]): unknown// needs 3 arguments
resetSearchGroupChatInfoSortType(...args: unknown[]): unknown// needs 3 arguments
resetSearchGroupChatInfoFilterMembers(...args: unknown[]): unknown// needs 3 arguments
searchMoreGroupChatInfo(...args: unknown[]): unknown// needs 1 arguments
cancelSearchGroupChatInfo(...args: unknown[]): unknown// needs 3 arguments
searchChatsWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchMoreChatsWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatsWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchChatMsgs(...args: unknown[]): unknown// needs 2 arguments
searchMoreChatMsgs(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatMsgs(...args: unknown[]): unknown// needs 3 arguments
searchMsgWithKeywords(...args: unknown[]): unknown// needs 2 arguments
searchMoreMsgWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchMsgWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchFileWithKeywords(keywords: string[], source: number): Promise<string>// needs 2 arguments
searchMoreFileWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchFileWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchAtMeChats(...args: unknown[]): unknown// needs 3 arguments
searchMoreAtMeChats(...args: unknown[]): unknown// needs 1 arguments
cancelSearchAtMeChats(...args: unknown[]): unknown// needs 3 arguments
searchChatAtMeMsgs(...args: unknown[]): unknown// needs 1 arguments
searchMoreChatAtMeMsgs(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatAtMeMsgs(...args: unknown[]): unknown// needs 3 arguments
addSearchHistory(param: {
type: number,//4
contactList: [],
id: number,//-1
groupInfos: [],
msgs: [],
fileInfos: [
{
chatType: ChatType,
buddyChatInfo: Array<{ category_name: string, peerUid: string, peerUin: string, remark: string }>,
discussChatInfo: [],
groupChatInfo: Array<
{
groupCode: string,
isConf: boolean,
hasModifyConfGroupFace: boolean,
hasModifyConfGroupName: boolean,
groupName: string,
remark: string
}>,
dataLineChatInfo: [],
tmpChatInfo: [],
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderNick: string,
senderRemark: string,
senderCard: string,
elemId: string,
elemType: string,//3
fileSize: string,
filePath: string,
fileName: string,
hits: Array<
{
start: 12,
end: 14
}
>
}
]
}): Promise<{
result: number,
errMsg: string,
id?: number
}>
removeSearchHistory(...args: unknown[]): unknown// needs 1 arguments
searchCache(...args: unknown[]): unknown// needs 3 arguments
clearSearchCache(...args: unknown[]): unknown// needs 1 arguments
}

View File

@@ -1,11 +1,10 @@
import { forceFetchClientKeyRetType } from './common' import { GeneralCallResult } from './common'
export interface NodeIKernelTicketService { export interface NodeIKernelTicketService {
addKernelTicketListener(listener: unknown): void forceFetchClientKey(arg: string): Promise<GeneralCallResult & {
url: string
removeKernelTicketListener(listenerId: unknown): void keyIndex: string
clientKey: string
forceFetchClientKey(arg: string): Promise<forceFetchClientKeyRetType> expireTime: string
}>
isNull(): boolean }
}

View File

@@ -1,19 +1,5 @@
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
export interface NodeIKernelTipOffService { export interface NodeIKernelTipOffService {
addKernelTipOffListener(listener: unknown): void getPskey(domainList: string[], nocache: boolean): Promise<GeneralCallResult & { domainPskeyMap: Map<string, string> }>
}
removeKernelTipOffListener(listenerId: unknown): void
tipOffSendJsData(args: unknown[]): Promise<unknown> //2
getPskey(domainList: string[], nocache: boolean): Promise<GeneralCallResult & { domainPskeyMap: Map<string, string> }> //2
tipOffSendJsData(args: unknown[]): Promise<unknown> //2
tipOffMsgs(args: unknown[]): Promise<unknown> //1
encodeUinAesInfo(args: unknown[]): Promise<unknown> //2
isNull(): boolean
}

View File

@@ -1,16 +1,8 @@
export enum GeneralCallResultStatus { export enum GeneralCallResultStatus {
OK = 0 OK = 0
// ERROR = 1
} }
export interface GeneralCallResult { export interface GeneralCallResult {
result: GeneralCallResultStatus result: GeneralCallResultStatus
errMsg: string errMsg: string
} }
export interface forceFetchClientKeyRetType extends GeneralCallResult {
url: string
keyIndex: string
clientKey: string
expireTime: string
}

View File

@@ -9,4 +9,4 @@ export * from './NodeIKernelUixConvertService'
export * from './NodeIKernelRichMediaService' export * from './NodeIKernelRichMediaService'
export * from './NodeIKernelTicketService' export * from './NodeIKernelTicketService'
export * from './NodeIKernelTipOffService' export * from './NodeIKernelTipOffService'
export * from './NodeIKernelSearchService' export * from './NodeIKernelRobotService'

View File

@@ -22,9 +22,9 @@ export interface Group {
hasModifyConfGroupName: boolean hasModifyConfGroupName: boolean
remarkName: string remarkName: string
hasMemo: boolean hasMemo: boolean
groupShutupExpireTime: string //"0", groupShutupExpireTime: string
personShutupExpireTime: string //"0", personShutupExpireTime: string
discussToGroupUin: string //"0", discussToGroupUin: string
discussToGroupMaxMsgSeq: number discussToGroupMaxMsgSeq: number
discussToGroupTime: number discussToGroupTime: number
groupFlagExt: number //1073938496, groupFlagExt: number //1073938496,
@@ -32,8 +32,8 @@ export interface Group {
groupCreditLevel: number //0, groupCreditLevel: number //0,
groupFlagExt3: number //0, groupFlagExt3: number //0,
groupOwnerId: { groupOwnerId: {
memberUin: string //"0", memberUin: string
memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q" memberUid: string
} }
members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段 members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段
createTime: string createTime: string
@@ -62,8 +62,9 @@ export interface GroupMember {
sex?: Sex sex?: Sex
qqLevel?: QQLevel qqLevel?: QQLevel
isChangeRole: boolean isChangeRole: boolean
joinTime: string joinTime: number
lastSpeakTime: string lastSpeakTime: number
memberLevel: number
} }
export interface PublishGroupBulletinReq { export interface PublishGroupBulletinReq {
@@ -76,4 +77,98 @@ export interface PublishGroupBulletinReq {
oldFeedsId: '' oldFeedsId: ''
pinned: number pinned: number
confirmRequired: number confirmRequired: number
} }
export interface GroupAllInfo {
groupCode: string
ownerUid: string
groupFlag: number
groupFlagExt: number
maxMemberNum: number
memberNum: number
groupOption: number
classExt: number
groupName: string
fingerMemo: string
groupQuestion: string
certType: number
shutUpAllTimestamp: number
shutUpMeTimestamp: number //解除禁言时间
groupTypeFlag: number
privilegeFlag: number
groupSecLevel: number
groupFlagExt3: number
isConfGroup: number
isModifyConfGroupFace: number
isModifyConfGroupName: number
noFigerOpenFlag: number
noCodeFingerOpenFlag: number
groupFlagExt4: number
groupMemo: string
cmdUinMsgSeq: number
cmdUinJoinTime: number
cmdUinUinFlag: number
cmdUinMsgMask: number
groupSecLevelInfo: number
cmdUinPrivilege: number
cmdUinFlagEx2: number
appealDeadline: number
remarkName: number
isTop: boolean
richFingerMemo: string
groupAnswer: string
joinGroupAuth: string
isAllowModifyConfGroupName: number
}
export interface GroupBulletinListResult {
groupCode: string
srvCode: number
readOnly: number
role: number
inst: unknown[]
feeds: {
uin: string
feedId: string
publishTime: string
msg: {
text: string
textFace: string
pics: {
id: string
width: number
height: number
}[]
title: string
}
type: number
fn: number
cn: number
vn: number
settings: {
isShowEditCard: number
remindTs: number
tipWindowType: number
confirmRequired: number
}
pinned: number
readNum: number
is_read: number
is_all_confirm: number
}[]
groupInfo: {
groupCode: string
classId: number
}
gln: number
tst: number
publisherInfos: {
uin: string
nick: string
avatar: string
}[]
server_time: string
svrt: string
nextIndex: number
jointime: string
}

View File

@@ -1,124 +1,108 @@
import { GroupMemberRole } from './group' import { GroupMemberRole } from './group'
import { GeneralCallResult } from '../services'
export interface GetFileListParam {
sortType: number
fileCount: number
startIndex: number
sortOrder: number
showOnlinedocFolder: number
}
export enum ElementType { export enum ElementType {
UNKNOWN = 0, Text = 1,
TEXT = 1, Pic = 2,
PIC = 2, File = 3,
FILE = 3, Ptt = 4,
PTT = 4, Video = 5,
VIDEO = 5, Face = 6,
FACE = 6, Reply = 7,
REPLY = 7, GrayTip = 8,
WALLET = 9, Ark = 10,
GreyTip = 8, //Poke别叫戳一搓了 官方名字拍一拍 戳一戳是另一个名字 MarketFace = 11,
ARK = 10, LiveGift = 12,
MFACE = 11, StructLongMsg = 13,
LIVEGIFT = 12, Markdown = 14,
STRUCTLONGMSG = 13, Giphy = 15,
MARKDOWN = 14, MultiForward = 16,
GIPHY = 15, InlineKeyboard = 17,
MULTIFORWARD = 16, Calendar = 19,
INLINEKEYBOARD = 17, YoloGameResult = 20,
INTEXTGIFT = 18, AvRecord = 21,
CALENDAR = 19, TofuRecord = 23,
YOLOGAMERESULT = 20, FaceBubble = 27,
AVRECORD = 21, ShareLocation = 28,
FEED = 22, TaskTopMsg = 29,
TOFURECORD = 23, RecommendedMsg = 43,
ACEBUBBLE = 24, ActionBar = 44
ACTIVITY = 25,
TOFU = 26,
FACEBUBBLE = 27,
SHARELOCATION = 28,
TASKTOPMSG = 29,
RECOMMENDEDMSG = 43,
ACTIONBAR = 44
} }
export interface SendTextElement { export interface SendTextElement {
elementType: ElementType.TEXT elementType: ElementType.Text
elementId: '' elementId: ''
textElement: TextElement textElement: TextElement
} }
export interface SendPttElement { export interface SendPttElement {
elementType: ElementType.PTT elementType: ElementType.Ptt
elementId: '' elementId: ''
pttElement: { pttElement: Partial<PttElement>
fileName: string
filePath: string
md5HexStr: string
fileSize: number
duration: number // 单位是秒
formatType: number
voiceType: number
voiceChangeType: number
canConvert2Text: boolean
waveAmplitudes: number[]
fileSubId: ''
playState: number
autoConvertText: number
}
}
export enum PicType {
gif = 2000,
jpg = 1000,
}
export enum PicSubType {
normal = 0, // 普通图片,大图
face = 1, // 表情包小图
} }
export interface SendPicElement { export interface SendPicElement {
elementType: ElementType.PIC elementType: ElementType.Pic
elementId: '' elementId: ''
picElement: { picElement: Partial<PicElement>
md5HexStr: string
fileSize: number | string
picWidth: number
picHeight: number
fileName: string
sourcePath: string
original: boolean
picType: PicType
picSubType: PicSubType
fileUuid: string
fileSubId: string
thumbFileSize: number
summary: string
}
} }
export interface SendReplyElement { export interface SendReplyElement {
elementType: ElementType.REPLY elementType: ElementType.Reply
elementId: '' elementId: ''
replyElement: ReplyElement replyElement: Partial<ReplyElement>
} }
export interface SendFaceElement { export interface SendFaceElement {
elementType: ElementType.FACE elementType: ElementType.Face
elementId: '' elementId: ''
faceElement: FaceElement faceElement: FaceElement
} }
export interface SendMarketFaceElement { export interface SendMarketFaceElement {
elementType: ElementType.MFACE elementType: ElementType.MarketFace
elementId: ''
marketFaceElement: MarketFaceElement marketFaceElement: MarketFaceElement
} }
export interface SendFileElement {
elementType: ElementType.File
elementId: ''
fileElement: FileElement
}
export interface SendVideoElement {
elementType: ElementType.Video
elementId: ''
videoElement: VideoElement
}
export interface SendArkElement {
elementType: ElementType.Ark
elementId: ''
arkElement: ArkElement
}
export type SendMessageElement =
| SendTextElement
| SendPttElement
| SendPicElement
| SendReplyElement
| SendFaceElement
| SendMarketFaceElement
| SendFileElement
| SendVideoElement
| SendArkElement
export enum AtType {
Unknown,
All,
One,
}
export interface TextElement { export interface TextElement {
content: string content: string
atType: number atType: AtType
atUid: string atUid: string
atTinyId: string atTinyId: string
atNtUid: string atNtUid: string
@@ -129,6 +113,12 @@ export interface ReplyElement {
replayMsgId: string replayMsgId: string
senderUin: string senderUin: string
senderUinStr: string senderUinStr: string
sourceMsgIdInRecords: string
senderUid: string
senderUidStr: string
sourceMsgIsIncPic: boolean // 原消息是否有图片
sourceMsgText: string
replyMsgTime: string
} }
export interface FileElement { export interface FileElement {
@@ -149,91 +139,6 @@ export interface FileElement {
fileBizId?: number fileBizId?: number
} }
export interface SendFileElement {
elementType: ElementType.FILE
elementId: ''
fileElement: FileElement
}
export interface SendVideoElement {
elementType: ElementType.VIDEO
elementId: ''
videoElement: VideoElement
}
export interface SendArkElement {
elementType: ElementType.ARK
elementId: ''
arkElement: ArkElement
}
export type SendMessageElement =
| SendTextElement
| SendPttElement
| SendPicElement
| SendReplyElement
| SendFaceElement
| SendMarketFaceElement
| SendFileElement
| SendVideoElement
| SendArkElement
export enum AtType {
notAt = 0,
atAll = 1,
atUser = 2,
}
export enum ChatType {
friend = 1,
group = 2,
temp = 100,
}
// 来自Android分析
export enum ChatType2 {
KCHATTYPEADELIE = 42,
KCHATTYPEBUDDYNOTIFY = 5,
KCHATTYPEC2C = 1,
KCHATTYPECIRCLE = 113,
KCHATTYPEDATALINE = 8,
KCHATTYPEDATALINEMQQ = 134,
KCHATTYPEDISC = 3,
KCHATTYPEFAV = 41,
KCHATTYPEGAMEMESSAGE = 105,
KCHATTYPEGAMEMESSAGEFOLDER = 116,
KCHATTYPEGROUP = 2,
KCHATTYPEGROUPBLESS = 133,
KCHATTYPEGROUPGUILD = 9,
KCHATTYPEGROUPHELPER = 7,
KCHATTYPEGROUPNOTIFY = 6,
KCHATTYPEGUILD = 4,
KCHATTYPEGUILDMETA = 16,
KCHATTYPEMATCHFRIEND = 104,
KCHATTYPEMATCHFRIENDFOLDER = 109,
KCHATTYPENEARBY = 106,
KCHATTYPENEARBYASSISTANT = 107,
KCHATTYPENEARBYFOLDER = 110,
KCHATTYPENEARBYHELLOFOLDER = 112,
KCHATTYPENEARBYINTERACT = 108,
KCHATTYPEQQNOTIFY = 132,
KCHATTYPERELATEACCOUNT = 131,
KCHATTYPESERVICEASSISTANT = 118,
KCHATTYPESERVICEASSISTANTSUB = 201,
KCHATTYPESQUAREPUBLIC = 115,
KCHATTYPESUBSCRIBEFOLDER = 30,
KCHATTYPETEMPADDRESSBOOK = 111,
KCHATTYPETEMPBUSSINESSCRM = 102,
KCHATTYPETEMPC2CFROMGROUP = 100,
KCHATTYPETEMPC2CFROMUNKNOWN = 99,
KCHATTYPETEMPFRIENDVERIFY = 101,
KCHATTYPETEMPNEARBYPRO = 119,
KCHATTYPETEMPPUBLICACCOUNT = 103,
KCHATTYPETEMPWPA = 117,
KCHATTYPEUNKNOWN = 0,
KCHATTYPEWEIYUN = 40,
}
export interface PttElement { export interface PttElement {
canConvert2Text: boolean canConvert2Text: boolean
duration: number // 秒数 duration: number // 秒数
@@ -244,7 +149,7 @@ export interface PttElement {
fileSize: string // "4261" fileSize: string // "4261"
fileSubId: string // "0" fileSubId: string // "0"
fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV"
formatType: string // 1 formatType: number // 1
invalidState: number // 0 invalidState: number // 0
md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6" md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6"
playState: number // 0 playState: number // 0
@@ -255,6 +160,7 @@ export interface PttElement {
voiceChangeType: number // 0 voiceChangeType: number // 0
voiceType: number // 0 voiceType: number // 0
waveAmplitudes: number[] waveAmplitudes: number[]
autoConvertText: number
} }
export interface ArkElement { export interface ArkElement {
@@ -266,6 +172,16 @@ export interface ArkElement {
export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn' export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn' export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
export enum PicType {
GIF = 2000,
JPEG = 1000,
}
export enum PicSubType {
Normal = 0, // 普通图片,大图
Face = 1, // 表情包小图
}
export interface PicElement { export interface PicElement {
picSubType: PicSubType picSubType: PicSubType
picType: PicType // 有这玩意儿吗 picType: PicType // 有这玩意儿吗
@@ -281,15 +197,83 @@ export interface PicElement {
md5HexStr?: string md5HexStr?: string
} }
export interface TipAioOpGrayTipElement {
operateType: number
peerUid: string
fromGrpCodeOfTmpChat: string
}
export enum TipGroupElementType {
MemberIncrease = 1,
Kicked = 3, // 被移出群
Ban = 8,
}
export interface TipGroupElement {
type: TipGroupElementType // 1是表示有人加入群, 自己加入群也会收到这个
role: number
groupName: string // 暂时获取不到
memberUid: string
memberNick: string
memberRemark: string
adminUid: string
adminNick: string
adminRemark: string
createGroup: null
memberAdd?: {
showType: number
otherAdd?: {
uid: string
name: string
}
otherAddByOtherQRCode?: unknown
otherAddByYourQRCode?: unknown
youAddByOtherQRCode?: unknown
otherInviteOther?: unknown
otherInviteYou?: unknown
youInviteOther?: unknown
}
shutUp?: {
curTime: string
duration: string // 禁言时间,秒
admin: {
uid: string
card: string
name: string
role: GroupMemberRole
}
member: {
uid: string
card: string
name: string
role: GroupMemberRole
}
}
}
export enum GrayTipElementSubType { export enum GrayTipElementSubType {
RECALL = 1, Revoke = 1,
INVITE_NEW_MEMBER = 12, Proclamation = 2,
MEMBER_NEW_TITLE = 17, EmojiReply = 3,
Group = 4,
Buddy = 5,
Feed = 6,
Essence = 7,
GroupNotify = 8,
BuddyNotify = 9,
File = 10,
FeedChannelMsg = 11,
XmlMsg = 12,
LocalMsg = 13,
Block = 14,
AioOp = 15,
Wallet = 16,
JSON = 17,
} }
export interface GrayTipElement { export interface GrayTipElement {
subElementType: GrayTipElementSubType subElementType: GrayTipElementSubType
revokeElement: { revokeElement?: {
operatorRole: string operatorRole: string
operatorUid: string operatorUid: string
operatorNick: string operatorNick: string
@@ -299,21 +283,22 @@ export interface GrayTipElement {
isSelfOperate?: boolean isSelfOperate?: boolean
wording: string // 自定义的撤回提示语 wording: string // 自定义的撤回提示语
} }
aioOpGrayTipElement: TipAioOpGrayTipElement aioOpGrayTipElement?: TipAioOpGrayTipElement
groupElement: TipGroupElement groupElement?: TipGroupElement
xmlElement: { xmlElement?: {
templId: string templId: string
content: string content: string
templParam: Map<string, string>
members: Map<string, string> // uid -> remark
} }
jsonGrayTipElement: { jsonGrayTipElement?: {
busiId: number busiId: string
jsonStr: string jsonStr: string
} }
} }
export enum FaceIndex { export enum FaceIndex {
dice = 358, Dice = 358,
RPS = 359, // 石头剪刀布 RPS = 359, // 石头剪刀布
} }
@@ -328,6 +313,7 @@ export interface FaceElement {
resultId?: string resultId?: string
surpriseId?: string surpriseId?: string
randomType?: number randomType?: number
pokeType?: number
} }
export interface MarketFaceElement { export interface MarketFaceElement {
@@ -337,6 +323,10 @@ export interface MarketFaceElement {
key: string key: string
imageWidth?: number imageWidth?: number
imageHeight?: number imageHeight?: number
supportSize?: {
width: number
height: number
}[]
} }
export interface VideoElement { export interface VideoElement {
@@ -386,6 +376,7 @@ export interface InlineKeyboardElementRowButton {
enter: false enter: false
subscribeDataTemplateIds: [] subscribeDataTemplateIds: []
} }
export interface InlineKeyboardElement { export interface InlineKeyboardElement {
rows: [ rows: [
{ {
@@ -394,56 +385,9 @@ export interface InlineKeyboardElement {
] ]
} }
export interface TipAioOpGrayTipElement { export interface StructLongMsgElement {
// 这是什么提示来着? xmlContent: string
operateType: number resId: string
peerUid: string
fromGrpCodeOfTmpChat: string
}
export enum TipGroupElementType {
memberIncrease = 1,
kicked = 3, // 被移出群
ban = 8,
}
export interface TipGroupElement {
type: TipGroupElementType // 1是表示有人加入群, 自己加入群也会收到这个
role: 0 // 暂时不知
groupName: string // 暂时获取不到
memberUid: string
memberNick: string
memberRemark: string
adminUid: string
adminNick: string
adminRemark: string
createGroup: null
memberAdd?: {
showType: 1
otherAdd: null
otherAddByOtherQRCode: null
otherAddByYourQRCode: null
youAddByOtherQRCode: null
otherInviteOther: null
otherInviteYou: null
youInviteOther: null
}
shutUp?: {
curTime: string
duration: string // 禁言时间,秒
admin: {
uid: string
card: string
name: string
role: GroupMemberRole
}
member: {
uid: string
card: string
name: string
role: GroupMemberRole
}
}
} }
export interface MultiForwardMsgElement { export interface MultiForwardMsgElement {
@@ -452,54 +396,44 @@ export interface MultiForwardMsgElement {
fileName: string fileName: string
} }
export enum ChatType {
C2C = 1,
Group = 2,
TempC2CFromGroup = 100,
}
export interface RawMessage { export interface RawMessage {
msgId: string msgId: string
msgType: number msgType: number
subMsgType: number subMsgType: number
msgShortId?: number // 自己维护的消息id
msgTime: string // 时间戳,秒 msgTime: string // 时间戳,秒
msgSeq: string msgSeq: string
msgRandom: string msgRandom: string
senderUid: string senderUid: string
senderUin?: string // 发送者QQ号 senderUin: string // 发送者QQ号
peerUid: string // 群号 或者 QQ uid peerUid: string // 群号 或者 QQ uid
peerUin: string // 群号 或者 发送者QQ号 peerUin: string // 群号 或者 发送者QQ号
guildId: string guildId: string
sendNickName: string sendNickName: string
sendMemberName?: string // 发送者群名片 sendMemberName?: string // 发送者群名片
sendRemarkName?: string // 发送者好友备注
chatType: ChatType chatType: ChatType
sendStatus?: number // 消息状态别人发的2是已撤回自己发的2是已发送 sendStatus?: number // 消息状态别人发的2是已撤回自己发的2是已发送
recallTime: string // 撤回时间, "0"是没有撤回 recallTime: string // 撤回时间, "0"是没有撤回
records: RawMessage[] records: RawMessage[]
elements: { elements: MessageElement[]
elementId: string peerName: string
elementType: ElementType multiTransInfo?: {
replyElement: { status: number
sourceMsgIdInRecords: string msgId: number
senderUid: string // 原消息发送者QQ号 friendFlag: number
sourceMsgIsIncPic: boolean // 原消息是否有图片 fromFaceUrl: string
sourceMsgText: string }
replayMsgSeq: string // 源消息的msgSeq可以通过这个找到源消息的msgId emojiLikesList: {
senderUidStr: string emojiId: string
replyMsgTime: string emojiType: string
} likesCnt: string
textElement: { isClicked: boolean
atType: AtType
atUid: string // QQ号
content: string
atNtUid: string // uid号
}
picElement: PicElement
pttElement: PttElement
arkElement: ArkElement
grayTipElement: GrayTipElement
faceElement: FaceElement
videoElement: VideoElement
fileElement: FileElement
marketFaceElement: MarketFaceElement
inlineKeyboardElement: InlineKeyboardElement
markdownElement: MarkdownElement
multiForwardMsgElement: MultiForwardMsgElement
}[] }[]
} }
@@ -515,7 +449,7 @@ export interface MessageElement {
extBufForUI: string //"0x" extBufForUI: string //"0x"
textElement?: TextElement textElement?: TextElement
faceElement?: FaceElement faceElement?: FaceElement
marketFaceElement?: MarkdownElement marketFaceElement?: MarketFaceElement
replyElement?: ReplyElement replyElement?: ReplyElement
picElement?: PicElement picElement?: PicElement
pttElement?: PttElement pttElement?: PttElement
@@ -525,12 +459,11 @@ export interface MessageElement {
fileElement?: FileElement fileElement?: FileElement
liveGiftElement?: unknown liveGiftElement?: unknown
markdownElement?: MarkdownElement markdownElement?: MarkdownElement
structLongMsgElement?: unknown structLongMsgElement?: StructLongMsgElement
multiForwardMsgElement?: MultiForwardMsgElement multiForwardMsgElement?: MultiForwardMsgElement
giphyElement?: unknown giphyElement?: unknown
walletElement?: unknown
inlineKeyboardElement?: InlineKeyboardElement inlineKeyboardElement?: InlineKeyboardElement
textGiftElement?: unknown //???? textGiftElement?: unknown
calendarElement?: unknown calendarElement?: unknown
yoloGameResultElement?: unknown yoloGameResultElement?: unknown
avRecordElement?: unknown avRecordElement?: unknown
@@ -541,4 +474,109 @@ export interface MessageElement {
taskTopMsgElement?: unknown taskTopMsgElement?: unknown
recommendedMsgElement?: unknown recommendedMsgElement?: unknown
actionBarElement?: unknown actionBarElement?: unknown
} }
export interface OnRichMediaDownloadCompleteParams {
fileModelId: string
msgElementId: string
msgId: string
fileId: string
fileProgress: string // '0'
fileSpeed: string // '0'
fileErrCode: string // '0'
fileErrMsg: string
fileDownType: number // 暂时未知
thumbSize: number
filePath: string
totalSize: string
trasferStatus: number
step: number
commonFileInfo: unknown
fileSrvErrCode: string
clientMsg: string
businessId: number
userTotalSpacePerDay: unknown
userUsedSpacePerDay: unknown
}
export interface GroupFileInfo {
retCode: number
retMsg: string
clientWording: string
isEnd: boolean
item: {
peerId: string
type: number
folderInfo?: {
folderId: string
parentFolderId: string
folderName: string
createTime: number
modifyTime: number
createUin: string
creatorName: string
totalFileCount: number
modifyUin: string
modifyName: string
usedSpace: string
}
fileInfo?: {
fileModelId: string
fileId: string
fileName: string
fileSize: string
busId: number
uploadedSize: string
uploadTime: number
deadTime: number
modifyTime: number
downloadTimes: number
sha: string
sha3: string
md5: string
uploaderLocalPath: string
uploaderName: string
uploaderUin: string
parentFolderId: string
localPath: string
transStatus: number
transType: number
elementId: string
isFolder: boolean
}
}[]
allFileCount: number
nextIndex: number
reqId: number
}
export interface QueryMsgsParams {
chatInfo: Peer
filterMsgType: []
filterSendersUid: string[]
filterMsgFromTime: string
filterMsgToTime: string
pageLimit: number
isReverseOrder: boolean
isIncludeCurrent: boolean
}
export interface TmpChatInfoApi extends GeneralCallResult {
tmpChatInfo?: {
chatType: number
fromNick: string
groupCode: string
peerUid: string
sessionType: number
sig: string
}
}
export interface GetFileListParam {
sortType: number
fileCount: number
startIndex: number
sortOrder: number
showOnlinedocFolder: number
folderId?: string
}

View File

@@ -1,19 +1,19 @@
export enum GroupNotifyType { export enum GroupNotifyType {
INVITED_BY_MEMBER = 1, InvitedByMember = 1,
REFUSE_INVITED, RefuseInvited,
REFUSED_BY_ADMINI_STRATOR, RefusedByAdminiStrator,
AGREED_TOJOIN_DIRECT, // 有人接受了邀请入群 AgreedTojoinDirect, // 有人接受了邀请入群
INVITED_NEED_ADMINI_STRATOR_PASS, // 有人邀请了别人入群 InvitedNeedAdminiStratorPass, // 有人邀请了别人入群
AGREED_TO_JOIN_BY_ADMINI_STRATOR, AgreedToJoinByAdminiStrator,
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS, RequestJoinNeedAdminiStratorPass,
SET_ADMIN, SetAdmin,
KICK_MEMBER_NOTIFY_ADMIN, KickMemberNotifyAdmin,
KICK_MEMBER_NOTIFY_KICKED, KickMemberNotifyKicked,
MEMBER_LEAVE_NOTIFY_ADMIN, // 主动退出 MemberLeaveNotifyAdmin, // 主动退出
CANCEL_ADMIN_NOTIFY_CANCELED, // 我被取消管理员 CancelAdminNotifyCanceled, // 我被取消管理员
CANCEL_ADMIN_NOTIFY_ADMIN, // 其他人取消管理员 CancelAdminNotifyAdmin, // 其他人取消管理员
TRANSFER_GROUP_NOTIFY_OLDOWNER, TransferGroupNotifyOldowner,
TRANSFER_GROUP_NOTIFY_ADMIN TransferGroupNotifyAdmin
} }
export interface GroupNotifies { export interface GroupNotifies {
@@ -23,11 +23,11 @@ export interface GroupNotifies {
} }
export enum GroupNotifyStatus { export enum GroupNotifyStatus {
KINIT, // 初始化 Init, // 初始化
KUNHANDLE, // 未处理 Unhandle, // 未处理
KAGREED, // 同意 Agreed, // 同意
KREFUSED, // 拒绝 Refused, // 拒绝
KIGNORED // 忽略 Ignored // 忽略
} }
export interface GroupNotify { export interface GroupNotify {
@@ -56,20 +56,8 @@ export enum GroupRequestOperateTypes {
} }
export enum BuddyReqType { export enum BuddyReqType {
KMEINITIATOR, MsgInfo = 12,
KPEERINITIATOR, MeInitiatorWaitPeerConfirm = 13,
KMEAGREED,
KMEAGREEDANDADDED,
KPEERAGREED,
KPEERAGREEDANDADDED,
KPEERREFUSED,
KMEREFUSED,
KMEIGNORED,
KMEAGREEANYONE,
KMESETQUESTION,
KMEAGREEANDADDFAILED,
KMSGINFO,
KMEINITIATORWAITPEERCONFIRM
} }
export interface FriendRequest { export interface FriendRequest {
@@ -91,41 +79,3 @@ export interface FriendRequestNotify {
buddyReqs: FriendRequest[] buddyReqs: FriendRequest[]
} }
} }
export enum MemberExtSourceType {
DEFAULTTYPE = 0,
TITLETYPE = 1,
NEWGROUPTYPE = 2,
}
export interface GroupExtParam {
groupCode: string
seq: string
beginUin: string
dataTime: string
uinList: Array<string>
uinNum: string
groupType: string
richCardNameVer: string
sourceType: MemberExtSourceType
memberExtFilter: {
memberLevelInfoUin: number
memberLevelInfoPoint: number
memberLevelInfoActiveDay: number
memberLevelInfoLevel: number
memberLevelInfoName: number
levelName: number
dataTime: number
userShowFlag: number
sysShowFlag: number
timeToUpdate: number
nickName: number
specialTitle: number
levelNameNew: number
userShowFlagNew: number
msgNeedField: number
cmdUinFlagExt3Grocery: number
memberIcon: number
memberInfoSeq: number
}
}

View File

@@ -105,7 +105,7 @@ export interface BaseInfo {
phoneNum: string phoneNum: string
categoryId: number categoryId: number
richTime: number richTime: number
richBuffer: string richBuffer: Uint8Array
} }
interface MusicInfo { interface MusicInfo {
@@ -221,11 +221,6 @@ interface RelationFlags {
isHidePrivilegeIcon: number isHidePrivilegeIcon: number
} }
export interface FriendV2 extends SimpleInfo {
categoryId?: number
categroyName?: string
}
interface CommonExt { interface CommonExt {
constellation: number constellation: number
shengXiao: number shengXiao: number
@@ -255,7 +250,7 @@ interface PhotoWall {
picList: Pic[] picList: Pic[]
} }
export interface UserDetailInfoListenerArg { export interface UserDetailInfo {
uid: string uid: string
uin: string uin: string
simpleInfo: SimpleInfo simpleInfo: SimpleInfo
@@ -344,3 +339,21 @@ export interface UserDetailInfoByUin {
vipNameColorId: string vipNameColorId: string
} }
} }
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}

View File

@@ -1,81 +0,0 @@
import {
NodeIKernelBuddyService,
NodeIKernelGroupService,
NodeIKernelProfileService,
NodeIKernelProfileLikeService,
NodeIKernelMSFService,
NodeIKernelMsgService,
NodeIKernelUixConvertService,
NodeIKernelRichMediaService,
NodeIKernelTicketService,
NodeIKernelTipOffService,
NodeIKernelSearchService
} from './services'
import { constants } from 'node:os'
import { Dict } from 'cosmokit'
const Process = require('node:process')
export interface NodeIQQNTWrapperSession {
getBuddyService(): NodeIKernelBuddyService
getGroupService(): NodeIKernelGroupService
getProfileService(): NodeIKernelProfileService
getProfileLikeService(): NodeIKernelProfileLikeService
getMsgService(): NodeIKernelMsgService
getMSFService(): NodeIKernelMSFService
getUixConvertService(): NodeIKernelUixConvertService
getRichMediaService(): NodeIKernelRichMediaService
getTicketService(): NodeIKernelTicketService
getTipOffService(): NodeIKernelTipOffService
getSearchService(): NodeIKernelSearchService
}
export interface WrapperApi {
NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession
}
export interface WrapperConstructor {
[key: string]: unknown
}
const wrapperApi: WrapperApi = {}
export const wrapperConstructor: WrapperConstructor = {}
const constructor = [
'NodeIKernelBuddyListener',
'NodeIKernelGroupListener',
'NodeQQNTWrapperUtil',
'NodeIKernelMsgListener',
'NodeIQQNTWrapperEngine',
'NodeIGlobalAdapter',
'NodeIDependsAdapter',
'NodeIDispatcherAdapter',
'NodeIKernelSessionListener',
'NodeIKernelLoginService',
'NodeIKernelLoginListener',
'NodeIKernelProfileService',
'NodeIKernelProfileListener',
]
Process.dlopenOrig = Process.dlopen
Process.dlopen = function (module: Dict, filename: string, flags = constants.dlopen.RTLD_LAZY) {
const dlopenRet = this.dlopenOrig(module, filename, flags)
for (const export_name in module.exports) {
module.exports[export_name] = new Proxy(module.exports[export_name], {
construct: (target, args) => {
const ret = new target(...args)
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
return ret
}
})
if (constructor.includes(export_name)) {
wrapperConstructor[export_name] = module.exports[export_name]
}
}
return dlopenRet
}
export function getSession() {
return wrapperApi['NodeIQQNTWrapperSession']
}

View File

@@ -1,44 +1,43 @@
import { ActionName, BaseCheckResult } from './types' import { ActionName } from './types'
import { OB11Response } from './OB11Response' import { OB11Response } from './OB11Response'
import { OB11Return } from '../types' import { OB11Return } from '../types'
import { Context } from 'cordis' import { Context, Schema } from 'cordis'
import type Adapter from '../adapter' import type Adapter from '../adapter'
abstract class BaseAction<PayloadType, ReturnDataType> { abstract class BaseAction<PayloadType, ReturnDataType> {
abstract actionName: ActionName abstract actionName: ActionName
protected ctx: Context protected ctx: Context
payloadSchema?: Schema<PayloadType>
constructor(protected adapter: Adapter) { constructor(protected adapter: Adapter) {
this.ctx = adapter.ctx this.ctx = adapter.ctx
} }
protected async check(payload: PayloadType): Promise<BaseCheckResult> {
return {
valid: true,
}
}
public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> { public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload) let params: PayloadType
if (!result.valid) { try {
return OB11Response.error(result.message, 400) params = this.payloadSchema ? new this.payloadSchema(payload) : payload
} catch (e) {
return OB11Response.error((e as Error).message, 400)
} }
try { try {
const resData = await this._handle(payload) const resData = await this._handle(params)
return OB11Response.ok(resData) return OB11Response.ok(resData)
} catch (e) { } catch (e) {
this.ctx.logger.error('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e?.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200) return OB11Response.error((e as Error)?.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200)
} }
} }
public async websocketHandle(payload: PayloadType, echo: unknown): Promise<OB11Return<ReturnDataType | null>> { public async websocketHandle(payload: PayloadType, echo: unknown): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload) let params: PayloadType
if (!result.valid) { try {
return OB11Response.error(result.message, 1400) params = this.payloadSchema ? new this.payloadSchema(payload) : payload
} catch (e) {
return OB11Response.error((e as Error).message, 1400)
} }
try { try {
const resData = await this._handle(payload) const resData = await this._handle(params)
return OB11Response.ok(resData, echo) return OB11Response.ok(resData, echo)
} catch (e) { } catch (e) {
this.ctx.logger.error('发生错误', e) this.ctx.logger.error('发生错误', e)
@@ -46,9 +45,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
} }
} }
protected async _handle(payload: PayloadType): Promise<ReturnDataType> { protected abstract _handle(payload: PayloadType): Promise<ReturnDataType>
throw `pleas override ${this.actionName} _handle`
}
} }
export default BaseAction export { BaseAction, Schema }

View File

@@ -1,8 +1,7 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import fsPromise from 'node:fs/promises' import { readFile } from 'node:fs/promises'
import { ActionName } from '../types' import { ActionName } from '../types'
import { Peer, ElementType } from '@/ntqqapi/types' import { Peer, ElementType } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/messageUnique'
export interface GetFilePayload { export interface GetFilePayload {
file: string // 文件名或者fileUuid file: string // 文件名或者fileUuid
@@ -17,13 +16,16 @@ export interface GetFileResponse {
} }
export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> { export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/onebot11/action/file/GetFile.ts#L44 payloadSchema = Schema.object({
file: Schema.string().required()
})
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> { protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const { enableLocalFile2Url } = this.adapter.config const { enableLocalFile2Url } = this.adapter.config
let fileCache = await MessageUnique.getFileCacheById(String(payload.file)) let fileCache = await this.ctx.store.getFileCacheById(payload.file)
if (!fileCache?.length) { if (!fileCache?.length) {
fileCache = await MessageUnique.getFileCacheByName(String(payload.file)) fileCache = await this.ctx.store.getFileCacheByName(payload.file)
} }
if (fileCache?.length) { if (fileCache?.length) {
@@ -46,7 +48,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
peerUid: fileCache[0].peerUid, peerUid: fileCache[0].peerUid,
guildId: '' guildId: ''
} }
if (fileCache[0].elementType === ElementType.PIC) { if (fileCache[0].elementType === ElementType.Pic) {
const msgList = await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [fileCache[0].msgId]) const msgList = await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [fileCache[0].msgId])
if (msgList.msgList.length === 0) { if (msgList.msgList.length === 0) {
throw new Error('msg not found') throw new Error('msg not found')
@@ -56,13 +58,13 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
if (!findEle) { if (!findEle) {
throw new Error('element not found') throw new Error('element not found')
} }
res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement) res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement!)
} else if (fileCache[0].elementType === ElementType.VIDEO) { } else if (fileCache[0].elementType === ElementType.Video) {
res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId) res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId)
} }
if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) { if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) {
try { try {
res.base64 = await fsPromise.readFile(downloadPath, 'base64') res.base64 = await readFile(downloadPath, 'base64')
} catch (e) { } catch (e) {
throw new Error('文件下载失败. ' + e) throw new Error('文件下载失败. ' + e)
} }
@@ -76,11 +78,12 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
export default class GetFile extends GetFileBase { export default class GetFile extends GetFileBase {
actionName = ActionName.GetFile actionName = ActionName.GetFile
payloadSchema = Schema.object({
file: Schema.string(),
file_id: Schema.string().required()
})
protected async _handle(payload: { file_id: string; file: string }): Promise<GetFileResponse> { protected async _handle(payload: { file_id: string, file: string }): Promise<GetFileResponse> {
if (!payload.file_id) {
throw new Error('file_id 不能为空')
}
payload.file = payload.file_id payload.file = payload.file_id
return super._handle(payload) return super._handle(payload)
} }

View File

@@ -3,11 +3,4 @@ import { ActionName } from '../types'
export default class GetImage extends GetFileBase { export default class GetImage extends GetFileBase {
actionName = ActionName.GetImage actionName = ActionName.GetImage
protected async _handle(payload: { file: string }) {
if (!payload.file) {
throw new Error('参数 file 不能为空')
}
return super._handle(payload)
}
} }

View File

@@ -1,8 +1,9 @@
import path from 'node:path'
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile' import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
import { ActionName } from '../types' import { ActionName } from '../types'
import { decodeSilk } from '@/common/utils/audio' import { decodeSilk } from '@/common/utils/audio'
import path from 'node:path' import { Schema } from '../BaseAction'
import fs from 'node:fs' import { stat, readFile } from 'node:fs/promises'
interface Payload extends GetFilePayload { interface Payload extends GetFilePayload {
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
@@ -10,14 +11,18 @@ interface Payload extends GetFilePayload {
export default class GetRecord extends GetFileBase { export default class GetRecord extends GetFileBase {
actionName = ActionName.GetRecord actionName = ActionName.GetRecord
payloadSchema = Schema.object({
file: Schema.string().required(),
out_format: Schema.string().default('mp3')
})
protected async _handle(payload: Payload): Promise<GetFileResponse> { protected async _handle(payload: Payload): Promise<GetFileResponse> {
const res = await super._handle(payload) const res = await super._handle(payload)
res.file = await decodeSilk(this.ctx, res.file!, payload.out_format) res.file = await decodeSilk(this.ctx, res.file!, payload.out_format)
res.file_name = path.basename(res.file) res.file_name = path.basename(res.file)
res.file_size = fs.statSync(res.file).size.toString() res.file_size = (await stat(res.file)).size.toString()
if (this.adapter.config.enableLocalFile2Url) { if (this.adapter.config.enableLocalFile2Url) {
res.base64 = fs.readFileSync(res.file, 'base64') res.base64 = await readFile(res.file, 'base64')
} }
return res return res
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -9,9 +9,13 @@ interface Payload {
export class CreateGroupFileFolder extends BaseAction<Payload, null> { export class CreateGroupFileFolder extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder actionName = ActionName.GoCQHTTP_CreateGroupFileFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
name: Schema.string().required(),
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
await this.ctx.ntGroupApi.createGroupFileFolder(payload.group_id.toString(), payload.name) await this.ctx.ntGroupApi.createGroupFileFolder(payload.group_id.toString(), payload.name)
return null return null
} }
} }

View File

@@ -1,25 +1,24 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
} }
export class DelEssenceMsg extends BaseAction<Payload, unknown> { export class DeleteEssenceMsg extends BaseAction<Payload, unknown> {
actionName = ActionName.GoCQHTTP_DelEssenceMsg actionName = ActionName.GoCQHTTP_DelEssenceMsg
payloadSchema = Schema.object({
message_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (!payload.message_id) { const msg = await this.ctx.store.getMsgInfoByShortId(+payload.message_id)
throw Error('message_id不能为空')
}
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')
} }
return await this.ctx.ntGroupApi.removeGroupEssence( return await this.ctx.ntGroupApi.removeGroupEssence(
msg.Peer.peerUid, msg.peer.peerUid,
msg.MsgId, msg.msgId,
) )
} }
} }

View File

@@ -1,17 +1,22 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
group_id: string | number group_id: number | string
file_id: string file_id: string
busid?: 102 busid: number | string
} }
export class DelGroupFile extends BaseAction<Payload, null> { export class DelGroupFile extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_DelGroupFile actionName = ActionName.GoCQHTTP_DelGroupFile
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file_id: Schema.string().required(),
busid: Schema.union([Number, String]).default(102)
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
await this.ctx.ntGroupApi.deleteGroupFile(payload.group_id.toString(), [payload.file_id]) await this.ctx.ntGroupApi.deleteGroupFile(payload.group_id.toString(), [payload.file_id], [+payload.busid])
return null return null
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -8,9 +8,13 @@ interface Payload {
export class DelGroupFolder extends BaseAction<Payload, null> { export class DelGroupFolder extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_DelGroupFolder actionName = ActionName.GoCQHTTP_DelGroupFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
folder_id: Schema.string().required()
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
await this.ctx.ntGroupApi.deleteGroupFileFolder(payload.group_id.toString(), payload.folder_id) await this.ctx.ntGroupApi.deleteGroupFileFolder(payload.group_id.toString(), payload.folder_id)
return null return null
} }
} }

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction'
import fs from 'fs' import fs from 'fs'
import fsPromise from 'fs/promises' import fsPromise from 'fs/promises'
import path from 'node:path' import path from 'node:path'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { calculateFileMD5, fetchFile } from '@/common/utils' import { calculateFileMD5, fetchFile } from '@/common/utils'
import { TEMP_DIR } from '@/common/globalVars' import { TEMP_DIR } from '@/common/globalVars'
@@ -22,6 +22,11 @@ interface FileResponse {
export class DownloadFile extends BaseAction<Payload, FileResponse> { export class DownloadFile extends BaseAction<Payload, FileResponse> {
actionName = ActionName.GoCQHTTP_DownloadFile actionName = ActionName.GoCQHTTP_DownloadFile
payloadSchema = Schema.object({
url: String,
base64: String,
headers: Schema.union([String, Schema.array(String)])
})
protected async _handle(payload: Payload): Promise<FileResponse> { protected async _handle(payload: Payload): Promise<FileResponse> {
const isRandomName = !payload.name const isRandomName = !payload.name

View File

@@ -1,8 +1,8 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11ForwardMessage } from '../../types' import { OB11ForwardMessage } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique' import { filterNullable } from '@/common/utils/misc'
interface Payload { interface Payload {
message_id: string // long msg idgocq message_id: string // long msg idgocq
@@ -15,37 +15,42 @@ interface Response {
export class GetForwardMsg extends BaseAction<Payload, Response> { export class GetForwardMsg extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetForwardMsg actionName = ActionName.GoCQHTTP_GetForwardMsg
payloadSchema = Schema.object({
message_id: Schema.string(),
id: Schema.string()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const msgId = payload.id || payload.message_id const msgId = payload.id || payload.message_id
if (!msgId) { if (!msgId) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
} }
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId) const rootMsgId = await this.ctx.store.getShortIdByMsgId(msgId)
const rootMsg = await MessageUnique.getMsgIdAndPeerByShortId(rootMsgId || +msgId) const rootMsg = await this.ctx.store.getMsgInfoByShortId(rootMsgId || +msgId)
if (!rootMsg) { if (!rootMsg) {
throw Error('msg not found') throw Error('msg not found')
} }
const data = await this.ctx.ntMsgApi.getMultiMsg(rootMsg.Peer, rootMsg.MsgId, rootMsg.MsgId) const data = await this.ctx.ntMsgApi.getMultiMsg(rootMsg.peer, rootMsg.msgId, rootMsg.msgId)
if (data?.result !== 0) { if (data?.result !== 0) {
throw Error('找不到相关的聊天记录' + data?.errMsg) throw Error('找不到相关的聊天记录' + data?.errMsg)
} }
const msgList = data.msgList const messages: (OB11ForwardMessage | undefined)[] = await Promise.all(
const messages = await Promise.all( data.msgList.map(async (msg) => {
msgList.map(async (msg) => { const res = await OB11Entities.message(this.ctx, msg)
const resMsg = await OB11Entities.message(this.ctx, msg) if (res) {
resMsg.message_id = MessageUnique.createMsg({ return {
chatType: msg.chatType, content: res.message,
peerUid: msg.peerUid, sender: {
}, msg.msgId) nickname: res.sender.nickname,
return resMsg user_id: res.sender.user_id
}), },
time: res.time,
message_format: res.message_format,
message_type: res.message_type
}
}
})
) )
const forwardMessages = messages.map(v => { return { messages: filterNullable(messages) }
const msg = v as Partial<OB11ForwardMessage>
msg.content = msg.message
delete msg.message
return msg as OB11ForwardMessage
})
return { messages: forwardMessages }
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -13,6 +13,9 @@ interface Response {
export class GetGroupAtAllRemain extends BaseAction<Payload, Response> { export class GetGroupAtAllRemain extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString()) const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString())
@@ -22,4 +25,4 @@ export class GetGroupAtAllRemain extends BaseAction<Payload, Response> {
remain_at_all_count_for_uin: data.atInfo.RemainAtAllCountForUin remain_at_all_count_for_uin: data.atInfo.RemainAtAllCountForUin
} }
} }
} }

View File

@@ -0,0 +1,50 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { ChatType } from '@/ntqqapi/types'
interface Payload {
group_id: number | string
}
interface EssenceMsg {
sender_id: number
sender_nick: string
sender_time: number
operator_id: number
operator_nick: string
operator_time: number
message_id: number
}
export class GetEssenceMsgList extends BaseAction<Payload, EssenceMsg[]> {
actionName = ActionName.GoCQHTTP_GetEssenceMsgList
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) {
const groupCode = payload.group_id.toString()
const peer = {
guildId: '',
chatType: ChatType.Group,
peerUid: groupCode
}
const essence = await this.ctx.ntGroupApi.queryCachedEssenceMsg(groupCode)
const data: EssenceMsg[] = []
for (const item of essence.items) {
const { msgList } = await this.ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, String(item.msgSeq), '0')
const sourceMsg = msgList.find(e => e.msgRandom === String(item.msgRandom))
if (!sourceMsg) continue
data.push({
sender_id: +item.msgSenderUin,
sender_nick: item.msgSenderNick,
sender_time: +sourceMsg.msgTime,
operator_id: +item.opUin,
operator_nick: item.opNick,
operator_time: item.opTime,
message_id: this.ctx.store.createMsgShortId(peer, sourceMsg.msgId)
})
}
return data
}
}

View File

@@ -0,0 +1,82 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { pathToFileURL } from 'node:url'
import { ChatType } from '@/ntqqapi/types'
import { GroupFileInfo } from '@/ntqqapi/types'
export interface Payload {
group_id: number | string
file_id: string
busid?: number
}
export interface Response {
url: string
}
export class GetGroupFileUrl extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupFileUrl
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file_id: Schema.string().required()
})
protected async _handle(payload: Payload) {
const file = await this.ctx.store.getFileCacheById(payload.file_id)
if (file.length > 0) {
const { msgId, chatType, peerUid, elementId } = file[0]
const path = await this.ctx.ntFileApi.downloadMedia(msgId, chatType, peerUid, elementId)
return {
url: pathToFileURL(path).href
}
} else {
const groupId = payload.group_id.toString()
const modelId = await this.search(groupId, payload.file_id)
if (modelId) {
const peer = {
chatType: ChatType.Group,
peerUid: groupId,
guildId: ''
}
const path = await this.ctx.ntFileApi.downloadFileForModelId(peer, modelId)
return {
url: pathToFileURL(path).href
}
}
throw new Error('file not found')
}
}
private async search(groupId: string, fileId: string, folderId?: string) {
let modelId: string | undefined
let nextIndex: number | undefined
const folders: GroupFileInfo['item'] = []
while (nextIndex !== 0) {
const res = await this.ctx.ntGroupApi.getGroupFileList(groupId, {
sortType: 1,
fileCount: 100,
startIndex: nextIndex ?? 0,
sortOrder: 2,
showOnlinedocFolder: 0,
folderId
})
const file = res.item.find(item => item.fileInfo?.fileId === fileId)
if (file) {
modelId = file.fileInfo?.fileModelId
break
}
folders.push(...res.item.filter(item => item.folderInfo?.totalFileCount))
nextIndex = res.nextIndex
}
if (!modelId) {
for (const item of folders) {
const res = await this.search(groupId, fileId, item.folderInfo?.folderId)
if (res) {
modelId = res
break
}
}
}
return modelId
}
}

View File

@@ -0,0 +1,62 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '@/onebot11/types'
import { GroupFileInfo } from '@/ntqqapi/types'
interface Payload {
group_id: number | string
folder_id: string
}
interface Response {
files: OB11GroupFile[]
folders: OB11GroupFileFolder[]
}
export class GetGroupFilesByFolder extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
folder_id: Schema.string().required()
})
async _handle(payload: Payload) {
const groupId = payload.group_id.toString()
const data: GroupFileInfo['item'] = []
let nextIndex: number | undefined
while (nextIndex !== 0) {
const res = await this.ctx.ntGroupApi.getGroupFileList(groupId, {
sortType: 1,
fileCount: 100,
startIndex: nextIndex ?? 0,
sortOrder: 2,
showOnlinedocFolder: 0,
folderId: payload.folder_id
})
data.push(...res.item)
nextIndex = res.nextIndex
}
return {
files: data.filter(item => item.fileInfo)
.map(item => {
const file = item.fileInfo!
return {
group_id: +item.peerId,
file_id: file.fileId,
file_name: file.fileName,
busid: file.busId,
file_size: +file.fileSize,
upload_time: file.uploadTime,
dead_time: file.deadTime,
modify_time: file.modifyTime,
download_times: file.downloadTimes,
uploader: +file.uploaderUin,
uploader_name: file.uploaderName
}
}),
folders: []
}
}
}

View File

@@ -1,16 +1,16 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11Message } from '../../types' import { OB11Message } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { ChatType } from '@/ntqqapi/types' import { ChatType } from '@/ntqqapi/types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { RawMessage } from '@/ntqqapi/types' import { RawMessage } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/messageUnique' import { filterNullable, parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
message_seq?: number | string message_seq?: number | string
count?: number | string count: number | string
reverseOrder?: boolean reverseOrder: boolean
} }
interface Response { interface Response {
@@ -19,28 +19,27 @@ interface Response {
export class GetGroupMsgHistory extends BaseAction<Payload, Response> { export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory actionName = ActionName.GoCQHTTP_GetGroupMsgHistory
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
message_seq: Schema.union([Number, String]),
count: Schema.union([Number, String]).default(20),
reverseOrder: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(false)
})
protected async _handle(payload: Payload): Promise<Response> { protected async _handle(payload: Payload): Promise<Response> {
const count = payload.count || 20 const { count, reverseOrder } = payload
const isReverseOrder = payload.reverseOrder || true const peer = { chatType: ChatType.Group, peerUid: payload.group_id.toString() }
const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() } let msgList: RawMessage[]
let msgList: RawMessage[] | undefined if (!payload.message_seq || payload.message_seq === '0') {
// 包含 message_seq 0
if (!payload.message_seq) {
msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList
} else { } else {
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq))?.MsgId const startMsgId = (await this.ctx.store.getMsgInfoByShortId(+payload.message_seq))?.msgId
if (!startMsgId) throw new Error(`消息${payload.message_seq}不存在`) if (!startMsgId) throw new Error(`消息${payload.message_seq}不存在`)
msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList
} }
if (!msgList?.length) throw new Error('未找到消息') if (!msgList?.length) throw new Error('未找到消息')
if (isReverseOrder) msgList.reverse() if (reverseOrder) msgList.reverse()
await Promise.all( const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg)))
msgList.map(async msg => { return { messages: filterNullable(ob11MsgList) }
msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
})
)
const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Entities.message(this.ctx, msg)))
return { messages: ob11MsgList }
} }
} }

View File

@@ -0,0 +1,48 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number | string
}
interface Notice {
sender_id: number
publish_time: number
message: {
text: string
images: {
height: string
width: string
id: string
}[]
}
}
export class GetGroupNotice extends BaseAction<Payload, Notice[]> {
actionName = ActionName.GoCQHTTP_GetGroupNotice
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupBulletinList(payload.group_id.toString())
const result: Notice[] = []
for (const feed of data.result.feeds) {
result.push({
sender_id: +feed.uin,
publish_time: +feed.publishTime,
message: {
text: feed.msg.text,
images: feed.msg.pics.map(image => {
return {
height: String(image.height),
width: String(image.width),
id: image.id
}
})
}
})
}
return result
}
}

View File

@@ -1,10 +1,10 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '../../types' import { OB11GroupFile, OB11GroupFileFolder } from '../../types'
import { GroupFileInfo } from '@/ntqqapi/types'
interface Payload { interface Payload {
group_id: string | number group_id: number | string
file_count: string | number
} }
interface Response { interface Response {
@@ -14,17 +14,26 @@ interface Response {
export class GetGroupRootFiles extends BaseAction<Payload, Response> { export class GetGroupRootFiles extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupRootFiles actionName = ActionName.GoCQHTTP_GetGroupRootFiles
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), { const groupId = payload.group_id.toString()
sortType: 1, const data: GroupFileInfo['item'] = []
fileCount: +(payload.file_count ?? 50),
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
this.ctx.logger.info(data) let nextIndex: number | undefined
while (nextIndex !== 0) {
const res = await this.ctx.ntGroupApi.getGroupFileList(groupId, {
sortType: 1,
fileCount: 100,
startIndex: nextIndex ?? 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
data.push(...res.item)
nextIndex = res.nextIndex
}
return { return {
files: data.filter(item => item.fileInfo) files: data.filter(item => item.fileInfo)
@@ -59,4 +68,4 @@ export class GetGroupRootFiles extends BaseAction<Payload, Response> {
}) })
} }
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { GroupNotifyStatus } from '@/ntqqapi/types' import { GroupNotifyStatus } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -38,7 +38,7 @@ export class GetGroupSystemMsg extends BaseAction<void, Response> {
invitor_nick: notify.user1.nickName, invitor_nick: notify.user1.nickName,
group_id: +notify.group.groupCode, group_id: +notify.group.groupCode,
group_name: notify.group.groupName, group_name: notify.group.groupName,
checked: notify.status !== GroupNotifyStatus.KUNHANDLE, checked: notify.status !== GroupNotifyStatus.Unhandle,
actor: notify.user2?.uid ? Number(await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)) : 0 actor: notify.user2?.uid ? Number(await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)) : 0
}) })
} else if (notify.type == 7) { } else if (notify.type == 7) {
@@ -49,11 +49,11 @@ export class GetGroupSystemMsg extends BaseAction<void, Response> {
message: notify.postscript, message: notify.postscript,
group_id: +notify.group.groupCode, group_id: +notify.group.groupCode,
group_name: notify.group.groupName, group_name: notify.group.groupName,
checked: notify.status !== GroupNotifyStatus.KUNHANDLE, checked: notify.status !== GroupNotifyStatus.Unhandle,
actor: notify.user2?.uid ? Number(await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)) : 0 actor: notify.user2?.uid ? Number(await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)) : 0
}) })
} }
} }
return data return data
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11User } from '../../types' import { OB11User } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -12,6 +12,9 @@ interface Payload {
export class GetStrangerInfo extends BaseAction<Payload, OB11User> { export class GetStrangerInfo extends BaseAction<Payload, OB11User> {
actionName = ActionName.GoCQHTTP_GetStrangerInfo actionName = ActionName.GoCQHTTP_GetStrangerInfo
payloadSchema = Schema.object({
user_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload): Promise<OB11User> {
if (!(getBuildVersion() >= 26702)) { if (!(getBuildVersion() >= 26702)) {

View File

@@ -1,6 +1,5 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
@@ -8,16 +7,16 @@ interface Payload {
export class MarkMsgAsRead extends BaseAction<Payload, null> { export class MarkMsgAsRead extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_MarkMsgAsRead actionName = ActionName.GoCQHTTP_MarkMsgAsRead
payloadSchema = Schema.object({
message_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (!payload.message_id) { const msg = await this.ctx.store.getMsgInfoByShortId(+payload.message_id)
throw new Error('参数 message_id 不能为空')
}
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')
} }
await this.ctx.ntMsgApi.setMsgRead(msg.Peer) await this.ctx.ntMsgApi.setMsgRead(msg.peer)
return null return null
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../../helper/quickOperation' import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../../helper/quickOperation'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -9,8 +9,9 @@ interface Payload {
export class HandleQuickOperation extends BaseAction<Payload, null> { export class HandleQuickOperation extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_HandleQuickOperation actionName = ActionName.GoCQHTTP_HandleQuickOperation
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e)) handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e))
return null return null
} }
} }

View File

@@ -1,21 +1,187 @@
import SendMsg from '../msg/SendMsg' import { unlink } from 'node:fs/promises'
import { OB11PostSendMsg } from '../../types' import { OB11MessageNode } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { convertMessage2List } from '../../helper/createMessage' import { BaseAction, Schema } from '../BaseAction'
import { Peer } from '@/ntqqapi/types/msg'
import { ChatType, ElementType, RawMessage, SendMessageElement } from '@/ntqqapi/types'
import { selfInfo } from '@/common/globalVars'
import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
export class SendForwardMsg extends SendMsg { interface Payload {
actionName = ActionName.GoCQHTTP_SendForwardMsg user_id?: string | number
group_id?: string | number
messages?: OB11MessageNode[]
message?: OB11MessageNode[]
message_type?: 'group' | 'private'
}
protected async check(payload: OB11PostSendMsg) { interface Response {
if (payload.messages) payload.message = convertMessage2List(payload.messages) message_id: number
return super.check(payload) forward_id?: string
}
export class SendForwardMsg extends BaseAction<Payload, Response> {
actionName = ActionName.SendForwardMsg
payloadSchema = Schema.object({
user_id: Schema.union([Number, String]),
group_id: Schema.union([Number, String]),
messages: Schema.array(Schema.any()),
message: Schema.array(Schema.any()),
message_type: Schema.union(['group', 'private'])
})
protected async _handle(payload: Payload) {
const messages = payload.messages ?? payload.message
if (!messages) {
throw new Error('未指定消息内容')
}
let contextMode = CreatePeerMode.Normal
if (payload.message_type === 'group') {
contextMode = CreatePeerMode.Group
} else if (payload.message_type === 'private') {
contextMode = CreatePeerMode.Private
}
const peer = await createPeer(this.ctx, payload, contextMode)
const msg = await this.handleForwardNode(peer, messages)
const msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
return { message_id: msgShortId }
}
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
this.ctx.logger.info('克隆的目标消息', msg)
const sendElements: SendMessageElement[] = []
for (const ele of msg.elements) {
sendElements.push(ele as SendMessageElement)
}
if (sendElements.length === 0) {
this.ctx.logger.warn('需要clone的消息无法解析将会忽略掉', msg)
}
this.ctx.logger.info('克隆消息', sendElements)
try {
const peer = {
chatType: ChatType.C2C,
peerUid: selfInfo.uid
}
const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements)
await this.ctx.sleep(300)
return nodeMsg
} catch (e) {
this.ctx.logger.warn(e, '克隆转发消息失败,将忽略本条消息', msg)
}
}
// 返回一个合并转发的消息id
private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) {
const selfPeer = {
chatType: ChatType.C2C,
peerUid: selfInfo.uid,
}
const nodeMsgIds: { msgId: string, peer: Peer }[] = []
// 先判断一遍是不是id和自定义混用
for (const messageNode of messageNodes) {
// 一个node表示一个人的消息
const nodeId = messageNode.data.id
// 有nodeId表示一个子转发消息卡片
if (nodeId) {
const nodeMsg = await this.ctx.store.getMsgInfoByShortId(+nodeId)
if (!nodeMsg) {
this.ctx.logger.warn('转发消息失败,未找到消息', nodeId)
continue
}
nodeMsgIds.push(nodeMsg)
}
else {
// 自定义的消息
// 提取消息段发给自己生成消息id
try {
const { sendElements, deleteAfterSentFiles } = await createSendElements(
this.ctx,
convertMessage2List(messageNode.data.content),
destPeer
)
this.ctx.logger.info('开始生成转发节点', sendElements)
const sendElementsSplit: SendMessageElement[][] = []
let splitIndex = 0
for (const ele of sendElements) {
if (!sendElementsSplit[splitIndex]) {
sendElementsSplit[splitIndex] = []
}
if (ele.elementType === ElementType.File || ele.elementType === ElementType.Video) {
if (sendElementsSplit[splitIndex].length > 0) {
splitIndex++
}
sendElementsSplit[splitIndex] = [ele]
splitIndex++
}
else {
sendElementsSplit[splitIndex].push(ele)
}
}
this.ctx.logger.info('分割后的转发节点', sendElementsSplit)
for (const eles of sendElementsSplit) {
const nodeMsg = await sendMsg(this.ctx, selfPeer, eles, [])
if (!nodeMsg) {
this.ctx.logger.warn('转发节点生成失败', eles)
continue
}
nodeMsgIds.push({ msgId: nodeMsg.msgId, peer: selfPeer })
await this.ctx.sleep(300)
}
deleteAfterSentFiles.map(path => unlink(path))
} catch (e) {
this.ctx.logger.error('生成转发消息节点失败', e)
}
}
}
// 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发
const nodeMsgArray: RawMessage[] = []
let srcPeer: Peer
let needSendSelf = false
for (const { msgId, peer } of nodeMsgIds) {
const nodeMsg = (await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [msgId])).msgList[0]
srcPeer ??= { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true
}
nodeMsgArray.push(nodeMsg)
}
let retMsgIds: string[] = []
if (needSendSelf) {
for (const msg of nodeMsgArray) {
if (msg.peerUid === selfPeer.peerUid) {
retMsgIds.push(msg.msgId)
continue
}
const clonedMsg = await this.cloneMsg(msg)
if (clonedMsg) retMsgIds.push(clonedMsg.msgId)
}
} else {
retMsgIds = nodeMsgArray.map(msg => msg.msgId)
}
if (retMsgIds.length === 0) {
throw Error('转发消息失败,节点为空')
}
const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds)
return returnMsg
} }
} }
export class SendPrivateForwardMsg extends SendForwardMsg { export class SendPrivateForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg
protected _handle(payload: Payload) {
payload.message_type = 'private'
return super._handle(payload)
}
} }
export class SendGroupForwardMsg extends SendForwardMsg { export class SendGroupForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendGroupForwardMsg actionName = ActionName.GoCQHTTP_SendGroupForwardMsg
protected _handle(payload: Payload) {
payload.message_type = 'group'
return super._handle(payload)
}
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { unlink } from 'fs/promises' import { unlink } from 'fs/promises'
import { checkFileReceived, uri2local } from '@/common/utils/file' import { checkFileReceived, uri2local } from '@/common/utils/file'
@@ -7,24 +7,28 @@ interface Payload {
group_id: number | string group_id: number | string
content: string content: string
image?: string image?: string
pinned?: number | string //扩展 pinned: number | string //扩展
confirm_required?: number | string //扩展 confirm_required: number | string //扩展
} }
export class SendGroupNotice extends BaseAction<Payload, null> { export class SendGroupNotice extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SendGroupNotice actionName = ActionName.GoCQHTTP_SendGroupNotice
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
content: Schema.string().required(),
image: Schema.string(),
pinned: Schema.union([Number, String]).default(0),
confirm_required: Schema.union([Number, String]).default(1)
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
if(!payload.content){
throw new Error('参数 content 不能为空')
}
const groupCode = payload.group_id.toString() const groupCode = payload.group_id.toString()
const pinned = Number(payload.pinned ?? 0) const pinned = +payload.pinned
const confirmRequired = Number(payload.confirm_required ?? 1) const confirmRequired = +payload.confirm_required
let picInfo: { id: string, width: number, height: number } | undefined let picInfo: { id: string, width: number, height: number } | undefined
if (payload.image) { if (payload.image) {
const { path, isLocal, success, errMsg } = await uri2local(payload.image, undefined, true) const { path, isLocal, success, errMsg } = await uri2local(this.ctx, payload.image, true)
if (!success) { if (!success) {
throw new Error(`设置群公告失败, 错误信息: uri2local: ${errMsg}`) throw new Error(`设置群公告失败, 错误信息: uri2local: ${errMsg}`)
} }
@@ -51,4 +55,4 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
} }
return null return null
} }
} }

View File

@@ -1,25 +1,22 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
} }
export class SetEssenceMsg extends BaseAction<Payload, unknown> { export class SetEssenceMsg extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SetEssenceMsg actionName = ActionName.GoCQHTTP_SetEssenceMsg
payloadSchema = Schema.object({
message_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (!payload.message_id) { const msg = await this.ctx.store.getMsgInfoByShortId(+payload.message_id)
throw Error('message_id不能为空')
}
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')
} }
return await this.ctx.ntGroupApi.addGroupEssence( await this.ctx.ntGroupApi.addGroupEssence(msg.peer.peerUid, msg.msgId)
msg.Peer.peerUid, return null
msg.MsgId
)
} }
} }

View File

@@ -1,49 +0,0 @@
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
import { SendElementEntities } from '@/ntqqapi/entities'
import { uri2local } from '@/common/utils'
import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
interface UploadGroupFilePayload {
group_id: number | string
file: string
name: string
folder?: string
folder_id?: string
}
export class UploadGroupFile extends BaseAction<UploadGroupFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadGroupFile
protected async _handle(payload: UploadGroupFilePayload): Promise<null> {
const { success, errMsg, path, fileName } = await uri2local(payload.file)
if (!success) {
throw new Error(errMsg)
}
const sendFileEle = await SendElementEntities.file(this.ctx, path, payload.name || fileName, payload.folder_id)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
await sendMsg(this.ctx, peer, [sendFileEle], [])
return null
}
}
interface UploadPrivateFilePayload {
user_id: number | string
file: string
name: string
}
export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadPrivateFile
protected async _handle(payload: UploadPrivateFilePayload): Promise<null> {
const { success, errMsg, path, fileName } = await uri2local(payload.file)
if (!success) {
throw new Error(errMsg)
}
const sendFileEle = await SendElementEntities.file(this.ctx, path, payload.name || fileName)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
await sendMsg(this.ctx, peer, [sendFileEle], [])
return null
}
}

View File

@@ -0,0 +1,35 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { SendElement } from '@/ntqqapi/entities'
import { uri2local } from '@/common/utils'
import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
interface Payload {
group_id: number | string
file: string
name: string
folder?: string
folder_id?: string
}
export class UploadGroupFile extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_UploadGroupFile
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file: Schema.string().required(),
name: Schema.string(),
folder: Schema.string(),
folder_id: Schema.string()
})
protected async _handle(payload: Payload): Promise<null> {
const { success, errMsg, path, fileName } = await uri2local(this.ctx, payload.file)
if (!success) {
throw new Error(errMsg)
}
const file = await SendElement.file(this.ctx, path, payload.name || fileName, payload.folder ?? payload.folder_id)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
await sendMsg(this.ctx, peer, [file], [])
return null
}
}

View File

@@ -0,0 +1,31 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { SendElement } from '@/ntqqapi/entities'
import { uri2local } from '@/common/utils'
import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
interface UploadPrivateFilePayload {
user_id: number | string
file: string
name: string
}
export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadPrivateFile
payloadSchema = Schema.object({
user_id: Schema.union([Number, String]).required(),
file: Schema.string().required(),
name: Schema.string()
})
protected async _handle(payload: UploadPrivateFilePayload): Promise<null> {
const { success, errMsg, path, fileName } = await uri2local(this.ctx, payload.file)
if (!success) {
throw new Error(errMsg)
}
const sendFileEle = await SendElement.file(this.ctx, path, payload.name || fileName)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
await sendMsg(this.ctx, peer, [sendFileEle], [])
return null
}
}

View File

@@ -1,16 +0,0 @@
import { GroupEssenceMsgRet } from '@/ntqqapi/api'
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
interface PayloadType {
group_id: number
pages?: number
}
export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet | void> {
actionName = ActionName.GoCQHTTP_GetEssenceMsg
protected async _handle() {
throw '此 api 暂不支持'
}
}

View File

@@ -1,22 +1,19 @@
import { WebHonorType } from '@/ntqqapi/api'
import { ActionName } from '../types' import { ActionName } from '../types'
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
interface Payload { interface Payload {
group_id: number group_id: number | string
type?: WebHonorType type: 'talkative' | 'performer' | 'legend' | 'strong_newbie' | 'emotion' | 'all'
} }
export class GetGroupHonorInfo extends BaseAction<Payload, unknown> { export class GetGroupHonorInfo extends BaseAction<Payload, unknown> {
actionName = ActionName.GetGroupHonorInfo actionName = ActionName.GetGroupHonorInfo
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
type: Schema.union(['talkative', 'performer', 'legend', 'strong_newbie', 'emotion', 'all']).default('all')
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (!payload.group_id) {
throw '缺少参数group_id'
}
if (!payload.type) {
payload.type = WebHonorType.ALL
}
return await this.ctx.ntWebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type) return await this.ctx.ntWebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type)
} }
} }

View File

@@ -1,6 +1,6 @@
import { OB11Group } from '../../types' import { OB11Group } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -9,14 +9,17 @@ interface Payload {
class GetGroupInfo extends BaseAction<Payload, OB11Group> { class GetGroupInfo extends BaseAction<Payload, OB11Group> {
actionName = ActionName.GetGroupInfo actionName = ActionName.GetGroupInfo
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const group = (await this.ctx.ntGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString()) const groupCode = payload.group_id.toString()
const group = (await this.ctx.ntGroupApi.getGroups()).find(e => e.groupCode === groupCode)
if (group) { if (group) {
return OB11Entities.group(group) return OB11Entities.group(group)
} else {
throw `${payload.group_id}不存在`
} }
throw new Error(`${payload.group_id}不存在`)
} }
} }

View File

@@ -1,6 +1,6 @@
import { OB11Group } from '../../types' import { OB11Group } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {

View File

@@ -1,8 +1,7 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11GroupMember } from '../../types' import { OB11GroupMember } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars'
import { isNullable } from 'cosmokit' import { isNullable } from 'cosmokit'
interface Payload { interface Payload {
@@ -12,35 +11,28 @@ interface Payload {
class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> { class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo actionName = ActionName.GetGroupMemberInfo
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), payload.user_id.toString()) const groupCode = payload.group_id.toString()
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!uid) throw new Error('无法获取用户信息')
const member = await this.ctx.ntGroupApi.getGroupMember(groupCode, uid)
if (member) { if (member) {
if (isNullable(member.sex)) { if (isNullable(member.sex)) {
//log('获取群成员详细信息')
const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid) const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid)
//log('群成员详细信息结果', info)
Object.assign(member, info) Object.assign(member, info)
} }
const ret = OB11Entities.groupMember(payload.group_id.toString(), member) const ret = OB11Entities.groupMember(groupCode, member)
const self = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), selfInfo.uid)
if (self?.role === 3 || self?.role === 4) {
const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString())
const target = webGroupMembers.find(e => e?.uin && e.uin === ret.user_id)
if (target) {
ret.join_time = target.join_time
ret.last_sent_time = target.last_speak_time
ret.qage = target.qage
ret.level = target.lv.level.toString()
}
}
const date = Math.round(Date.now() / 1000) const date = Math.round(Date.now() / 1000)
ret.last_sent_time ||= Number(member.lastSpeakTime || date) ret.last_sent_time ??= date
ret.join_time ||= Number(member.joinTime || date) ret.join_time ??= date
return ret return ret
} else {
throw `群成员${payload.user_id}不存在`
} }
throw new Error(`群成员${payload.user_id}不存在`)
} }
} }

View File

@@ -1,57 +1,35 @@
import { OB11GroupMember } from '../../types' import { OB11GroupMember } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
no_cache: boolean | string no_cache?: boolean | string
} }
class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> { class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList actionName = ActionName.GetGroupMemberList
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const groupMembers = await this.ctx.ntGroupApi.getGroupMembers(payload.group_id.toString()) const groupCode = payload.group_id.toString()
let groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
if (groupMembers.size === 0) {
await this.ctx.sleep(100)
groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
}
const groupMembersArr = Array.from(groupMembers.values()) const groupMembersArr = Array.from(groupMembers.values())
let _groupMembers = groupMembersArr.map(item => {
return OB11Entities.groupMember(payload.group_id.toString(), item)
})
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>()
const date = Math.round(Date.now() / 1000) const date = Math.round(Date.now() / 1000)
for (let i = 0, len = _groupMembers.length; i < len; i++) { return groupMembersArr.map(item => {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了 const member = OB11Entities.groupMember(groupCode, item)
_groupMembers[i].join_time = date member.join_time ??= date
_groupMembers[i].last_sent_time = date member.last_sent_time ??= date
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]) return member
} })
const selfRole = groupMembers.get(selfInfo.uid)?.role
const isPrivilege = selfRole === 3 || selfRole === 4
if (isPrivilege) {
const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString())
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) {
continue
}
const MemberData = MemberMap.get(webGroupMembers[i]?.uin)
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time
MemberData.qage = webGroupMembers[i]?.qage
MemberData.level = webGroupMembers[i]?.lv.level.toString()
MemberMap.set(webGroupMembers[i]?.uin, MemberData)
}
}
}
_groupMembers = Array.from(MemberMap.values())
return _groupMembers
} }
} }

View File

@@ -1,4 +1,4 @@
import BaseAction from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
export default class GetGuildList extends BaseAction<null, null> { export default class GetGuildList extends BaseAction<null, null> {

View File

@@ -1,14 +1,13 @@
import SendMsg from '../msg/SendMsg' import SendMsg from '../msg/SendMsg'
import { ActionName, BaseCheckResult } from '../types' import { ActionName } from '../types'
import { OB11PostSendMsg } from '../../types' import { OB11PostSendMsg } from '../../types'
class SendGroupMsg extends SendMsg { class SendGroupMsg extends SendMsg {
actionName = ActionName.SendGroupMsg actionName = ActionName.SendGroupMsg
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected _handle(payload: OB11PostSendMsg) {
delete (payload as Partial<OB11PostSendMsg>).user_id
payload.message_type = 'group' payload.message_type = 'group'
return super.check(payload) return super._handle(payload)
} }
} }

View File

@@ -1,22 +1,27 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { GroupRequestOperateTypes } from '@/ntqqapi/types' import { GroupRequestOperateTypes } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
flag: string flag: string
approve?: boolean | string approve: boolean
reason?: string reason?: string
} }
export default class SetGroupAddRequest extends BaseAction<Payload, null> { export default class SetGroupAddRequest extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupAddRequest actionName = ActionName.SetGroupAddRequest
payloadSchema = Schema.object({
flag: Schema.string().required(),
approve: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(true),
reason: Schema.string()
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const flag = payload.flag.toString() await this.ctx.ntGroupApi.handleGroupRequest(
const approve = payload.approve?.toString() !== 'false' payload.flag,
await this.ctx.ntGroupApi.handleGroupRequest(flag, payload.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, payload.reason
payload.reason || ''
) )
return null return null
} }

View File

@@ -1,26 +1,31 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { GroupMemberRole } from '@/ntqqapi/types' import { GroupMemberRole } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number group_id: number | string
user_id: number user_id: number | string
enable: boolean enable: boolean
} }
export default class SetGroupAdmin extends BaseAction<Payload, null> { export default class SetGroupAdmin extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupAdmin actionName = ActionName.SetGroupAdmin
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
enable: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(true)
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id) const groupCode = payload.group_id.toString()
const enable = payload.enable.toString() === 'true' const uin = payload.user_id.toString()
if (!member) { const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
throw `群成员${payload.user_id}不存在` if (!uid) throw new Error('无法获取用户信息')
}
await this.ctx.ntGroupApi.setMemberRole( await this.ctx.ntGroupApi.setMemberRole(
payload.group_id.toString(), groupCode,
member.uid, uid,
enable ? GroupMemberRole.admin : GroupMemberRole.normal, payload.enable ? GroupMemberRole.admin : GroupMemberRole.normal
) )
return null return null
} }

View File

@@ -1,22 +1,27 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
group_id: number group_id: number | string
user_id: number user_id: number | string
duration: number duration: number | string
} }
export default class SetGroupBan extends BaseAction<Payload, null> { export default class SetGroupBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupBan actionName = ActionName.SetGroupBan
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
duration: Schema.union([Number, String]).default(30 * 60)
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id) const groupCode = payload.group_id.toString()
if (!member) { const uin = payload.user_id.toString()
throw `群成员${payload.user_id}不存在` const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
} if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.banMember(payload.group_id.toString(), [ await this.ctx.ntGroupApi.banMember(groupCode, [
{ uid: member.uid, timeStamp: parseInt(payload.duration.toString()) }, { uid, timeStamp: +payload.duration },
]) ])
return null return null
} }

View File

@@ -1,21 +1,26 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
group_id: number group_id: number | string
user_id: number user_id: number | string
card: string card: string
} }
export default class SetGroupCard extends BaseAction<Payload, null> { export default class SetGroupCard extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupCard actionName = ActionName.SetGroupCard
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
card: Schema.string().default('')
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id) const groupCode = payload.group_id.toString()
if (!member) { const uin = payload.user_id.toString()
throw `群成员${payload.user_id}不存在` const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
} if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || '') await this.ctx.ntGroupApi.setMemberCard(groupCode, uid, payload.card)
return null return null
} }
} }

View File

@@ -1,21 +1,27 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number group_id: number | string
user_id: number user_id: number | string
reject_add_request: boolean reject_add_request: boolean
} }
export default class SetGroupKick extends BaseAction<Payload, null> { export default class SetGroupKick extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupKick actionName = ActionName.SetGroupKick
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
reject_add_request: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(false)
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id) const groupCode = payload.group_id.toString()
if (!member) { const uin = payload.user_id.toString()
throw `群成员${payload.user_id}不存在` const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
} if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request) await this.ctx.ntGroupApi.kickMember(groupCode, [uid], payload.reject_add_request)
return null return null
} }
} }

View File

@@ -1,20 +1,19 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
group_id: number group_id: number | string
is_dismiss: boolean is_dismiss?: boolean
} }
export default class SetGroupLeave extends BaseAction<Payload, void> { export default class SetGroupLeave extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupLeave actionName = ActionName.SetGroupLeave
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
try { await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString())
await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString()) return null
} catch (e) {
this.ctx.logger.error('退群失败', e)
throw e
}
} }
} }

View File

@@ -1,13 +1,17 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
group_id: number group_id: number | string
group_name: string group_name: string
} }
export default class SetGroupName extends BaseAction<Payload, null> { export default class SetGroupName extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupName actionName = ActionName.SetGroupName
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
group_name: Schema.string().required()
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
await this.ctx.ntGroupApi.setGroupName(payload.group_id.toString(), payload.group_name) await this.ctx.ntGroupApi.setGroupName(payload.group_id.toString(), payload.group_name)

View File

@@ -1,17 +1,21 @@
import BaseAction from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { parseBool } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number group_id: number | string
enable: boolean enable: boolean
} }
export default class SetGroupWholeBan extends BaseAction<Payload, null> { export default class SetGroupWholeBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupWholeBan actionName = ActionName.SetGroupWholeBan
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
enable: Schema.union([Boolean, Schema.transform(String, parseBool)]).default(true)
})
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const enable = payload.enable.toString() === 'true' await this.ctx.ntGroupApi.banGroup(payload.group_id.toString(), payload.enable)
await this.ctx.ntGroupApi.banGroup(payload.group_id.toString(), enable)
return null return null
} }
} }

Some files were not shown because too many files have changed in this diff Show More