Compare commits

...

57 Commits

Author SHA1 Message Date
idranme
c0b682606c Merge pull request #378 from LLOneBot/dev
3.31.1
2024-08-28 16:09:35 +08:00
idranme
8564630c4d Update manifest.json 2024-08-28 16:07:58 +08:00
idranme
abd5a12708 chore: v3.31.1 2024-08-28 16:07:31 +08:00
idranme
234167f305 fix 2024-08-28 16:06:40 +08:00
idranme
da75f59d0d fix 2024-08-28 15:40:08 +08:00
idranme
eaf96ac3fc Merge pull request #376 from LLOneBot/dev
fix
2024-08-28 10:45:50 +08:00
idranme
2491de9af8 fix 2024-08-28 02:45:17 +00:00
idranme
01f8987e1e Merge pull request #375 from LLOneBot/dev
3.31.0
2024-08-28 10:28:27 +08:00
idranme
4a9bebbc9c chore: v3.31.0 2024-08-28 10:27:05 +08:00
idranme
6be6151d73 fix 2024-08-28 10:25:17 +08:00
idranme
738b0a96a0 chore 2024-08-28 06:52:29 +08:00
idranme
7cb94cb8b8 refactor 2024-08-28 06:49:46 +08:00
idranme
5501980ab3 refactor 2024-08-28 04:48:07 +08:00
idranme
bc3c8b1259 Merge pull request #374 from LLOneBot/main
merge
2024-08-28 04:45:33 +08:00
idranme
61e63efbd8 Merge pull request #373 from itzdrli/main
Fix typo in LICENSE file
2024-08-27 22:01:30 +08:00
itzdrli
28770d5995 Fix typo in LICENSE file 2024-08-27 13:01:14 +00:00
idranme
67d3dfb3cf Merge pull request #367 from LLOneBot/dev
3.30.5
2024-08-25 23:09:44 +08:00
idranme
afe8392a1e chore: v3.30.5 2024-08-25 23:07:33 +08:00
idranme
c1f5c5cd58 fix 2024-08-25 20:00:13 +08:00
idranme
85001a40da Merge pull request #366 from LLOneBot/dev
3.30.4
2024-08-23 17:05:03 +08:00
idranme
867a05c85a chore: v3.30.4 2024-08-23 17:03:58 +08:00
idranme
d8a63f6561 fix 2024-08-23 17:02:31 +08:00
idranme
e9fb9d1b30 Update publish.yml 2024-08-23 16:08:59 +08:00
idranme
b4fc987537 Merge pull request #365 from LLOneBot/dev
3.30.3
2024-08-23 13:40:59 +08:00
idranme
d0ccf53d88 chore: v3.30.3 2024-08-23 13:39:26 +08:00
idranme
d5ca94569d fix 2024-08-23 13:32:58 +08:00
idranme
bf72685501 Merge pull request #363 from LLOneBot/dev
3.30.2
2024-08-23 00:30:48 +08:00
idranme
c07467b670 chore: v3.30.2 2024-08-23 00:08:52 +08:00
idranme
ea164fb048 fix: friend list 2024-08-22 23:47:15 +08:00
idranme
0c0ad9a616 Merge pull request #362 from LLOneBot/dev
3.30.1
2024-08-22 20:41:32 +08:00
idranme
7bb4808e2d chore: v3.30.1 2024-08-22 20:18:16 +08:00
idranme
3f7592d06d opt 2024-08-22 20:17:28 +08:00
idranme
2f341fcf43 fix 2024-08-22 18:16:08 +08:00
idranme
9c59e5903e Merge pull request #360 from LLOneBot/dev
3.30.0
2024-08-22 12:41:06 +08:00
idranme
339ba409ee chore: v3.30.0 2024-08-22 12:37:43 +08:00
idranme
099da66661 fix: poke event 2024-08-22 12:32:09 +08:00
idranme
adcde6e49e fix 2024-08-22 06:37:28 +08:00
idranme
b3b8f9cd72 fix 2024-08-22 06:23:35 +08:00
idranme
8b57ebd7de fix: adaptation 27187 2024-08-22 05:45:02 +08:00
idranme
1afaeb0396 fix: adaptation 27187 2024-08-22 03:34:42 +08:00
idranme
235a986253 fix: adaptation 27187 2024-08-22 02:48:01 +08:00
idranme
b16bea9548 fix: adaptation 27187 2024-08-22 02:01:44 +08:00
idranme
7897034d13 opt 2024-08-22 00:42:12 +08:00
idranme
eabe891838 opt 2024-08-21 23:36:35 +08:00
idranme
75d3fc27f0 chore: remove unused methods 2024-08-21 22:51:00 +08:00
idranme
111bb4dd88 fix: adaptation 27187 2024-08-21 22:14:52 +08:00
idranme
f8bf60a3a0 Merge pull request #357 from cnxysoft/dev
fix: Linux上报
2024-08-21 17:50:42 +08:00
Alen
7c22eb3376 fix: Linux上报 2024-08-21 17:42:33 +08:00
Alen
7e1f7ac7f5 Merge remote-tracking branch 'upstream/dev' into dev 2024-08-21 10:56:00 +08:00
Alen
4efcf5b520 Merge remote-tracking branch 'upstream/dev' into dev 2024-08-12 19:56:48 +08:00
Alen
9ff6ff7cab Merge remote-tracking branch 'upstream/dev' into dev 2024-08-12 16:09:24 +08:00
Alen
594a421163 Merge remote-tracking branch 'upstream/dev' into dev 2024-08-09 22:15:54 +08:00
Alen
b748d84e8a Merge branch 'dev' of https://github.com/cnxysoft/LLOneBot into dev 2024-08-07 15:06:19 +08:00
Alen
e8d83d2958 Merge remote-tracking branch 'upstream/dev' into dev 2024-08-07 15:06:11 +08:00
Alen
cdb34ffe61 Merge remote-tracking branch 'upstream/dev' into dev 2024-08-05 22:15:48 +08:00
Alen
a45c56bd85 Merge remote-tracking branch 'upstream/dev' into dev 2024-08-05 10:09:12 +08:00
Alen
bb07ebd5d7 Merge branch 'main' into dev 2024-08-05 10:07:28 +08:00
127 changed files with 5193 additions and 5390 deletions

View File

@@ -14,7 +14,7 @@ jobs:
- name: setup node - name: setup node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 20
- name: install dependenies - name: install dependenies
run: | run: |

View File

@@ -1,4 +1,4 @@
MIT Without Public Sicial Media Promotion License MIT Without Public Social Media Promotion License
Copyright (c) 2024 LLOneBot Copyright (c) 2024 LLOneBot

View File

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

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发", "description": "实现 OneBot 11 协议,用于 QQ 机器人开发",
"version": "3.29.6", "version": "3.31.1",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {

View File

@@ -20,18 +20,19 @@
"compressing": "^1.10.1", "compressing": "^1.10.1",
"cordis": "^3.18.0", "cordis": "^3.18.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"cosmokit": "^1.6.2",
"express": "^4.19.2", "express": "^4.19.2",
"fast-xml-parser": "^4.4.1", "fast-xml-parser": "^4.4.1",
"file-type": "^19.4.1", "file-type": "^19.4.1",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"minato": "^3.5.0", "minato": "^3.5.1",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"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": "^4.17.21",
"@types/fluent-ffmpeg": "^2.1.25", "@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",
"electron": "^31.4.0", "electron": "^31.4.0",
@@ -40,5 +41,5 @@
"vite": "^5.4.2", "vite": "^5.4.2",
"vite-plugin-cp": "^4.0.8" "vite-plugin-cp": "^4.0.8"
}, },
"packageManager": "yarn@4.4.0" "packageManager": "yarn@4.4.1"
} }

View File

@@ -1,5 +1,6 @@
export const CHANNEL_GET_CONFIG = 'llonebot_get_config' export const CHANNEL_GET_CONFIG = 'llonebot_get_config'
export const CHANNEL_SET_CONFIG = 'llonebot_set_config' export const CHANNEL_SET_CONFIG = 'llonebot_set_config'
export const CHANNEL_SET_CONFIG_CONFIRMED = 'llonebot_set_config_confirmed'
export const CHANNEL_LOG = 'llonebot_log' export const CHANNEL_LOG = 'llonebot_log'
export const CHANNEL_ERROR = 'llonebot_error' export const CHANNEL_ERROR = 'llonebot_error'
export const CHANNEL_UPDATE = 'llonebot_update' export const CHANNEL_UPDATE = 'llonebot_update'

View File

@@ -1,11 +1,25 @@
import fs from 'node:fs' import fs from 'node:fs'
import { Config, OB11Config } from './types' import { Config, OB11Config } from './types'
import { mergeNewProperties } from './utils/helper'
import path from 'node:path' import path from 'node:path'
import { getSelfUin } from './data' import { selfInfo, DATA_DIR } from './globalVars'
import { DATA_DIR } from './utils'
//export const HOOK_LOG = false // 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
function mergeNewProperties(newObj: any, oldObj: any) {
Object.keys(newObj).forEach((key) => {
// 如果老对象不存在当前属性,则直接复制
if (!oldObj.hasOwnProperty(key)) {
oldObj[key] = newObj[key]
} else {
// 如果老对象和新对象的当前属性都是对象,则递归合并
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
mergeNewProperties(newObj[key], oldObj[key])
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
// 属性冲突,有一方不是对象,直接覆盖
oldObj[key] = newObj[key]
}
}
})
}
export class ConfigUtil { export class ConfigUtil {
private readonly configPath: string private readonly configPath: string
@@ -96,6 +110,6 @@ export class ConfigUtil {
} }
export function getConfigUtil() { export function getConfigUtil() {
const configFilePath = path.join(DATA_DIR, `config_${getSelfUin()}.json`) const configFilePath = path.join(DATA_DIR, `config_${selfInfo.uin}.json`)
return new ConfigUtil(configFilePath) return new ConfigUtil(configFilePath)
} }

View File

@@ -1,128 +0,0 @@
import {
type Friend,
type GroupMember,
type SelfInfo,
} from '../ntqqapi/types'
import { type LLOneBotError } from './types'
import { NTQQGroupApi } from '../ntqqapi/api/group'
import { log } from './utils/log'
import { isNumeric } from './utils/helper'
import { NTQQFriendApi, NTQQUserApi } from '../ntqqapi/api'
import { RawMessage } from '../ntqqapi/types'
import { getConfigUtil } from './config'
import { getBuildVersion } from './utils/QQBasicInfo'
export let friends: Friend[] = []
export const llonebotError: LLOneBotError = {
ffmpegError: '',
httpServerError: '',
wsServerError: '',
otherError: 'LLOneBot 未能正常启动,请检查日志查看错误',
}
// 群号 -> 群成员map(uid=>GroupMember)
export const groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>()
export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
const filterKey: 'uin' | 'uid' = isNumeric(uinOrUid.toString()) ? 'uin' : 'uid'
const filterValue = uinOrUid
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
if (!friend && getBuildVersion() < 26702) {
try {
const _friends = await NTQQFriendApi.getFriends(true)
friend = _friends.find((friend) => friend[filterKey] === filterValue.toString())
if (friend) {
friends.push(friend)
}
} catch (e: any) {
log('刷新好友列表失败', e.stack.toString())
}
}
return friend
}
export async function getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
const groupCodeStr = groupCode.toString()
const memberUinOrUidStr = memberUinOrUid.toString()
let members = groupMembers.get(groupCodeStr)
if (!members) {
try {
members = await NTQQGroupApi.getGroupMembers(groupCodeStr)
// 更新群成员列表
groupMembers.set(groupCodeStr, members)
}
catch (e) {
return null
}
}
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) {
members = await NTQQGroupApi.getGroupMembers(groupCodeStr)
groupMembers.set(groupCodeStr, members)
member = getMember()
}
return member
}
const selfInfo: SelfInfo = {
uid: '',
uin: '',
nick: '',
online: true,
}
export async function getSelfNick(force = false): Promise<string> {
if ((!selfInfo.nick || force) && selfInfo.uid) {
const userInfo = await NTQQUserApi.getUserDetailInfo(selfInfo.uid)
if (userInfo) {
selfInfo.nick = userInfo.nick
return userInfo.nick
}
}
return selfInfo.nick
}
export function getSelfInfo() {
return selfInfo
}
export function setSelfInfo(data: Partial<SelfInfo>) {
Object.assign(selfInfo, data)
}
export function getSelfUid() {
return selfInfo['uid']
}
export function getSelfUin() {
return selfInfo['uin']
}
const messages: Map<string, RawMessage> = new Map()
/** 缓存近期消息内容 */
export async function addMsgCache(msg: RawMessage) {
const expire = getConfigUtil().getConfig().msgCacheExpire! * 1000
if (expire === 0) {
return
}
const id = msg.msgId
messages.set(id, msg)
setTimeout(() => {
messages.delete(id)
}, expire)
}
/** 获取近期消息内容 */
export function getMsgCache(msgId: string) {
return messages.get(msgId)
}

22
src/common/globalVars.ts Normal file
View File

@@ -0,0 +1,22 @@
import { LLOneBotError } from './types'
import { SelfInfo } from '../ntqqapi/types'
import path from 'node:path'
export const llonebotError: LLOneBotError = {
ffmpegError: '',
httpServerError: '',
wsServerError: '',
otherError: 'LLOneBot 未能正常启动,请检查日志查看错误',
}
export const DATA_DIR: string = global.LiteLoader.plugins['LLOneBot'].path.data
export const TEMP_DIR: string = path.join(DATA_DIR, 'temp')
export const PLUGIN_DIR: string = global.LiteLoader.plugins['LLOneBot'].path.plugin
export const LOG_DIR = path.join(DATA_DIR, 'logs')
export const selfInfo: SelfInfo = {
uid: '',
uin: '',
nick: '',
online: true,
}

View File

@@ -1,119 +0,0 @@
import express, { Express, Request, Response } from 'express'
import http from 'node:http'
import cors from 'cors'
import { log } from '../utils/log'
import { getConfigUtil } from '../config'
import { llonebotError } from '../data'
type RegisterHandler = (res: Response, payload: any) => Promise<any>
export abstract class HttpServerBase {
name: string = 'LLOneBot'
private readonly expressAPP: Express
private server: http.Server | null = null
constructor() {
this.expressAPP = express()
// 添加 CORS 中间件
this.expressAPP.use(cors())
this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' }))
this.expressAPP.use((req, res, next) => {
// 兼容处理没有带content-type的请求
// log("req.headers['content-type']", req.headers['content-type'])
req.headers['content-type'] = 'application/json'
const originalJson = express.json({ limit: '5000mb' })
// 调用原始的express.json()处理器
originalJson(req, res, (err) => {
if (err) {
log('Error parsing JSON:', err)
return res.status(400).send('Invalid JSON')
}
next()
})
})
}
authorize(req: Request, res: Response, next: () => void) {
let serverToken = getConfigUtil().getConfig().token
let clientToken = ''
const authHeader = req.get('authorization')
if (authHeader) {
clientToken = authHeader.split('Bearer ').pop()!
log('receive http header token', clientToken)
} else if (req.query.access_token) {
if (Array.isArray(req.query.access_token)) {
clientToken = req.query.access_token[0].toString()
} else {
clientToken = req.query.access_token.toString()
}
log('receive http url token', clientToken)
}
if (serverToken && clientToken != serverToken) {
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }))
}
next()
}
start(port: number) {
try {
this.expressAPP.get('/', (req: Request, res: Response) => {
res.send(`${this.name} 已启动`)
})
this.listen(port)
llonebotError.httpServerError = ''
} catch (e: any) {
log('HTTP服务启动失败', e.toString())
llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString()
}
}
stop() {
llonebotError.httpServerError = ''
if (this.server) {
this.server.close()
this.server = null
}
}
restart(port: number) {
this.stop()
this.start(port)
}
abstract handleFailed(res: Response, payload: any, err: any): void
registerRouter(method: 'post' | 'get' | string, url: string, handler: RegisterHandler) {
if (!url.startsWith('/')) {
url = '/' + url
}
if (!this.expressAPP[method]) {
const err = `${this.name} register router failed${method} not exist`
log(err)
throw err
}
this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => {
let payload = req.body
if (method == 'get') {
payload = req.query
} else if (req.query) {
payload = { ...req.query, ...req.body }
}
log('收到 HTTP 请求', url, payload)
try {
res.send(await handler(res, payload))
} catch (e: any) {
this.handleFailed(res, payload, e.stack.toString())
}
})
}
protected listen(port: number) {
this.server = this.expressAPP.listen(port, '0.0.0.0', () => {
const info = `${this.name} started 0.0.0.0:${port}`
console.log(info)
log(info)
})
}
}

View File

@@ -12,15 +12,17 @@ export interface OB11Config {
enableHttpHeart?: boolean enableHttpHeart?: boolean
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息 enableQOAutoQuote: boolean // 快速操作回复自动引用原消息
} }
export interface CheckVersion { export interface CheckVersion {
result: boolean result: boolean
version: string version: string
} }
export interface Config { export interface Config {
enableLLOB: boolean enableLLOB: boolean
ob11: OB11Config ob11: OB11Config
token?: string token?: string
heartInterval?: number // ms heartInterval: number // ms
enableLocalFile2Url?: boolean // 开启后本地文件路径图片会转成http链接, 语音会转成base64 enableLocalFile2Url?: boolean // 开启后本地文件路径图片会转成http链接, 语音会转成base64
debug?: boolean debug?: boolean
reportSelfMessage?: boolean reportSelfMessage?: boolean

View File

@@ -1,52 +0,0 @@
import path from 'node:path'
import os from 'node:os'
import { systemPlatform } from './system'
export const exePath = process.execPath
function getPKGPath() {
let p = path.join(path.dirname(exePath), 'resources', 'app', 'package.json')
if (systemPlatform === 'darwin') {
p = path.join(path.dirname(path.dirname(exePath)), 'Resources', 'app', 'package.json')
}
return p
}
export const pkgInfoPath = getPKGPath()
let configVersionInfoPath: string
if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json')
}
else {
const userPath = os.homedir()
const appDataPath = path.resolve(userPath, './.config/QQ')
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json')
}
if (typeof configVersionInfoPath !== 'string') {
throw new Error('Something went wrong when load QQ info path')
}
export { configVersionInfoPath }
type QQPkgInfo = {
version: string
buildVersion: string
platform: string
eleArch: string
}
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath)
// platform_type: 3,
// app_type: 4,
// app_version: '9.9.9-23159',
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
// appid: '537213764',
// platVer: '10.0.26100',
// clientVer: '9.9.9-23159',
export function getBuildVersion(): number {
return +qqPkgInfo.buildVersion
}

View File

@@ -2,11 +2,11 @@ import path from 'node:path'
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk, EncodeResult } from 'silk-wasm' import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk, EncodeResult } from 'silk-wasm'
import { log } from './log' import { TEMP_DIR } from '../globalVars'
import { TEMP_DIR } from './index'
import { getConfigUtil } from '../config' import { getConfigUtil } from '../config'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Readable } from 'node:stream' import { Readable } from 'node:stream'
import { Context } from 'cordis'
interface FFmpegOptions { interface FFmpegOptions {
input?: string[] input?: string[]
@@ -15,14 +15,14 @@ interface FFmpegOptions {
type Input = string | Readable type Input = string | Readable
function convert(input: Input, options: FFmpegOptions): Promise<Buffer> function convert(ctx: Context, input: Input, options: FFmpegOptions): Promise<Buffer>
function convert(input: Input, options: FFmpegOptions, outputPath: string): Promise<string> function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath: string): Promise<string>
function convert(input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer> | Promise<string> { function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer> | Promise<string> {
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
const chunks: Buffer[] = [] const chunks: Buffer[] = []
let command = ffmpeg(input) let command = ffmpeg(input)
.on('error', err => { .on('error', err => {
log(`FFmpeg处理转换出错: `, err.message) ctx.logger.error(`FFmpeg处理转换出错: `, err.message)
reject(err) reject(err)
}) })
.on('end', () => { .on('end', () => {
@@ -53,17 +53,17 @@ function convert(input: Input, options: FFmpegOptions, outputPath?: string): Pro
}) })
} }
export async function encodeSilk(filePath: string) { export async function encodeSilk(ctx: Context, filePath: string) {
try { try {
const file = await fsPromise.readFile(filePath) const file = await fsPromise.readFile(filePath)
if (!isSilk(file)) { if (!isSilk(file)) {
log(`语音文件${filePath}需要转换成silk`) ctx.logger.info(`语音文件${filePath}需要转换成silk`)
let result: EncodeResult let result: EncodeResult
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000] const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
if (isWav(file) && allowSampleRate.includes(getWavFileInfo(file).fmt.sampleRate)) { if (isWav(file) && allowSampleRate.includes(getWavFileInfo(file).fmt.sampleRate)) {
result = await encode(file, 0) result = await encode(file, 0)
} else { } else {
const input = await convert(filePath, { const input = await convert(ctx, filePath, {
output: [ output: [
'-ar 24000', '-ar 24000',
'-ac 1', '-ac 1',
@@ -74,7 +74,7 @@ export async function encodeSilk(filePath: string) {
} }
const pttPath = path.join(TEMP_DIR, randomUUID()) const pttPath = path.join(TEMP_DIR, randomUUID())
await fsPromise.writeFile(pttPath, result.data) await fsPromise.writeFile(pttPath, result.data)
log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, result.duration) ctx.logger.info(`语音文件${filePath}转换成功!`, pttPath, `时长:`, result.duration)
return { return {
converted: true, converted: true,
path: pttPath, path: pttPath,
@@ -86,7 +86,7 @@ export async function encodeSilk(filePath: string) {
try { try {
duration = getDuration(silk) / 1000 duration = getDuration(silk) / 1000
} catch (e: any) { } catch (e: any) {
log('获取语音文件时长失败, 默认为1秒', filePath, e.stack) ctx.logger.warn('获取语音文件时长失败, 默认为1秒', filePath, e.stack)
} }
return { return {
converted: false, converted: false,
@@ -95,21 +95,21 @@ export async function encodeSilk(filePath: string) {
} }
} }
} catch (error: any) { } catch (error: any) {
log('convert silk failed', error.stack) ctx.logger.error('convert silk failed', error.stack)
return {} return {}
} }
} }
type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' type OutFormat = 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
export async function decodeSilk(inputFilePath: string, outFormat: OutFormat = 'mp3') { export async function decodeSilk(ctx: Context, inputFilePath: string, outFormat: OutFormat = 'mp3') {
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))
const outFilePath = tmpPath + `.${outFormat}` const outFilePath = tmpPath + `.${outFormat}`
const pcmFilePath = tmpPath + '.pcm' const pcmFilePath = tmpPath + '.pcm'
await fsPromise.writeFile(pcmFilePath, data) await fsPromise.writeFile(pcmFilePath, data)
return convert(pcmFilePath, { return convert(ctx, pcmFilePath, {
input: [ input: [
'-f s16le', '-f s16le',
'-ar 24000', '-ar 24000',

View File

@@ -22,6 +22,7 @@ export class NTEventWrapper {
private WrapperSession: NodeIQQNTWrapperSession | undefined//WrapperSession private WrapperSession: NodeIQQNTWrapperSession | undefined//WrapperSession
private ListenerManger: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>() //ListenerName-Unique -> Listener实例 private ListenerManger: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>() //ListenerName-Unique -> Listener实例
private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>()//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} private EventTask = new Map<string, Map<string, Map<string, Internal_MapKey>>>()//tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
public initialised = false
constructor() { constructor() {
} }
@@ -46,6 +47,7 @@ export class NTEventWrapper {
init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) { init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) {
this.ListenerMap = ListenerMap this.ListenerMap = ListenerMap
this.WrapperSession = WrapperSession this.WrapperSession = WrapperSession
this.initialised = true
} }
createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined { createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {

View File

@@ -1,7 +1,7 @@
import fs from 'node:fs' import fs from 'node:fs'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import path from 'node:path' import path from 'node:path'
import { TEMP_DIR } from './index' 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'
@@ -56,34 +56,6 @@ export function calculateFileMD5(filePath: string): Promise<string> {
}) })
} }
export interface HttpDownloadOptions {
url: string
headers?: Record<string, string> | string
}
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
let url: string
let headers: Record<string, string> = {
'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',
}
if (typeof options === 'string') {
url = options
} else {
url = options.url
if (options.headers) {
if (typeof options.headers === 'string') {
headers = JSON.parse(options.headers)
} else {
headers = options.headers
}
}
}
const fetchRes = await fetch(url, { headers })
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
return Buffer.from(await fetchRes.arrayBuffer())
}
export enum FileUriType { export enum FileUriType {
Unknown = 0, Unknown = 0,
FileURL = 1, FileURL = 1,
@@ -117,10 +89,11 @@ interface FetchFileRes {
url: string url: string
} }
async function fetchFile(url: string): Promise<FetchFileRes> { export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> {
const headers: Record<string, string> = { const headers: Record<string, string> = {
'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
} }
const raw = await fetch(url, { headers }).catch((err) => { const raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) { if (err.cause) {
@@ -170,7 +143,7 @@ export async function uri2local(uri: string, filename?: string): Promise<Uri2Loc
await fsPromise.writeFile(filePath, res.data) await fsPromise.writeFile(filePath, res.data)
return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false } return { success: true, errMsg: '', fileName: filename, path: filePath, isLocal: false }
} catch (e: any) { } catch (e: any) {
const errMsg = `${uri}下载失败,` + e.toString() const errMsg = `${uri} 下载失败, ${e.message}`
return { success: false, errMsg, fileName: '', path: '', isLocal: false } return { success: false, errMsg, fileName: '', path: '', isLocal: false }
} }
} }

View File

@@ -1,169 +0,0 @@
export function truncateString(obj: any, 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 function isNumeric(str: string) {
return /^\d+$/.test(str)
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
export function mergeNewProperties(newObj: any, oldObj: any) {
Object.keys(newObj).forEach((key) => {
// 如果老对象不存在当前属性,则直接复制
if (!oldObj.hasOwnProperty(key)) {
oldObj[key] = newObj[key]
} else {
// 如果老对象和新对象的当前属性都是对象,则递归合并
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
mergeNewProperties(newObj[key], oldObj[key])
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
// 属性冲突,有一方不是对象,直接覆盖
oldObj[key] = newObj[key]
}
}
})
}
export function isNull(value: unknown) {
return value === undefined || value === null
}
/**
* 将字符串按最大长度分割并添加换行符
* @param str 原始字符串
* @param maxLength 每行的最大字符数
* @returns 处理后的字符串,超过长度的地方将会换行
*/
export function wrapText(str: string, maxLength: number): string {
// 初始化一个空字符串用于存放结果
let result: string = ''
// 循环遍历字符串每次步进maxLength个字符
for (let i = 0; i < str.length; i += maxLength) {
// 从i开始截取长度为maxLength的字符串段并添加到结果字符串
// 如果不是第一段,先添加一个换行符
if (i > 0) result += '\n'
result += str.substring(i, i + maxLength)
}
return result
}
/**
* 函数缓存装饰器根据方法名、参数、自定义key生成缓存键在一定时间内返回缓存结果
* @param ttl 超时时间,单位毫秒
* @param customKey 自定义缓存键前缀,可为空,防止方法名参数名一致时导致缓存键冲突
* @returns 处理后缓存或调用原方法的结果
*/
export function cacheFunc(ttl: number, customKey: string = '') {
const cache = new Map<string, { expiry: number; value: any }>()
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
const originalMethod = descriptor.value
const className = target.constructor.name // 获取类名
const methodName = propertyKey // 获取方法名
descriptor.value = async function (...args: any[]) {
const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`
const cached = cache.get(cacheKey)
if (cached && cached.expiry > Date.now()) {
return cached.value
} else {
const result = await originalMethod.apply(this, args)
cache.set(cacheKey, { value: result, expiry: Date.now() + ttl })
return result
}
}
return descriptor
}
}
export function CacheClassFuncAsync(ttl = 3600 * 1000, customKey = '') {
function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) {
const cache = new Map<string, { expiry: number; value: any }>()
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`
cache.forEach((value, key) => {
if (value.expiry < Date.now()) {
cache.delete(key)
}
})
const cachedValue = cache.get(key)
if (cachedValue && cachedValue.expiry > Date.now()) {
return cachedValue.value
}
const result = await originalMethod.apply(this, args)
cache.set(key, { expiry: Date.now() + ttl, value: result })
return result
}
}
return logExecutionTime
}
export function CacheClassFuncAsyncExtend(ttl: number = 3600 * 1000, customKey: string = '', checker: any = (...data: any[]) => { return true }) {
function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) {
const cache = new Map<string, { expiry: number; value: any }>()
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`
cache.forEach((value, key) => {
if (value.expiry < Date.now()) {
cache.delete(key)
}
})
const cachedValue = cache.get(key)
if (cachedValue && cachedValue.expiry > Date.now()) {
return cachedValue.value
}
const result = await originalMethod.apply(this, args)
if (!checker(...args, result)) {
return result //丢弃缓存
}
cache.set(key, { expiry: Date.now() + ttl, value: result })
return result
}
}
return logExecutionTime
}
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/common/utils/helper.ts#L14
export class UUIDConverter {
static encode(highStr: string, lowStr: string): string {
const high = BigInt(highStr)
const low = BigInt(lowStr)
const highHex = high.toString(16).padStart(16, '0')
const lowHex = low.toString(16).padStart(16, '0')
const combinedHex = highHex + lowHex
const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(
12,
16,
)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`
return uuid
}
static decode(uuid: string): { high: string; low: string } {
const hex = uuid.replace(/-/g, '')
const high = BigInt('0x' + hex.substring(0, 16))
const low = BigInt('0x' + hex.substring(16))
return { high: high.toString(), low: low.toString() }
}
}

View File

@@ -1,14 +1,7 @@
import path from 'node:path'
export * from './file' export * from './file'
export * from './helper' export * from './misc'
export * from './log' export * from './legacyLog'
export * from './qqlevel' export * from './misc'
export * from './QQBasicInfo'
export * from './upgrade' export * from './upgrade'
export const DATA_DIR: string = global.LiteLoader.plugins['LLOneBot'].path.data export { getVideoInfo, checkFfmpeg } from './video'
export const TEMP_DIR: string = path.join(DATA_DIR, 'temp')
export const PLUGIN_DIR: string = global.LiteLoader.plugins['LLOneBot'].path.plugin
export { getVideoInfo } from './video'
export { checkFfmpeg } from './video'
export { encodeSilk } from './audio' export { encodeSilk } from './audio'

View File

@@ -0,0 +1,41 @@
import fs from 'fs'
import path from 'node:path'
import { getConfigUtil } from '../config'
import { LOG_DIR } from '../globalVars'
function truncateString(obj: any, 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 function log(...msg: any[]) {
if (!getConfigUtil().getConfig().log) {
return
}
let logMsg = ''
for (const msgItem of msg) {
// 判断是否是对象
if (typeof msgItem === 'object') {
logMsg += JSON.stringify(truncateString(msgItem)) + ' '
continue
}
logMsg += msgItem + ' '
}
const currentDateTime = new Date().toLocaleString()
logMsg = `${currentDateTime} ${logMsg}\n\n`
fs.appendFile(path.join(LOG_DIR, logFileName), logMsg, () => { })
}

View File

@@ -1,35 +0,0 @@
import { getSelfInfo } from '../data'
import fs from 'fs'
import path from 'node:path'
import { DATA_DIR, truncateString } from './index'
import { getConfigUtil } from '../config'
const date = new Date()
const logFileName = `llonebot-${date.toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-')
const logDir = path.join(DATA_DIR, 'logs')
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
export function log(...msg: any[]) {
if (!getConfigUtil().getConfig().log) {
return //console.log(...msg);
}
const selfInfo = getSelfInfo()
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : ''
let logMsg = ''
for (let msgItem of msg) {
// 判断是否是对象
if (typeof msgItem === 'object') {
let obj = JSON.parse(JSON.stringify(msgItem))
logMsg += JSON.stringify(truncateString(obj)) + ' '
continue
}
logMsg += msgItem + ' '
}
let currentDateTime = new Date().toLocaleString()
logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n`
// sendLog(...msg);
// console.log(msg)
fs.appendFile(path.join(logDir, logFileName), logMsg, () => {})
}

View File

@@ -1,12 +1,12 @@
import { Peer } from '@/ntqqapi/types'
import { createHash } from 'node:crypto'
import { LimitedHashTable } from './table'
import { DATA_DIR } from './index'
import Database, { Tables } from 'minato'
import SQLite from '@minatojs/driver-sqlite'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' 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' import { FileCacheV2 } from '../types'
interface SQLiteTables extends Tables { interface SQLiteTables extends Tables {

16
src/common/utils/misc.ts Normal file
View File

@@ -0,0 +1,16 @@
import { QQLevel } from '@/ntqqapi/types'
export function isNumeric(str: string) {
return /^\d+$/.test(str)
}
export function calcQQLevel(level: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum
}
/** QQ Build Version */
export function getBuildVersion(): number {
const version: string = globalThis.LiteLoader.versions.qqnt
return +version.split('-')[1]
}

View File

@@ -1,7 +0,0 @@
// QQ等级换算
import { QQLevel } from '../../ntqqapi/types'
export function calcQQLevel(level: QQLevel) {
const { crownNum, sunNum, moonNum, starNum } = level
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum
}

View File

@@ -1,6 +1,5 @@
import https from 'node:https'; import https from 'node:https'
import http from 'node:http'; import http from 'node:http'
import { log } from '@/common/utils/log'
export class RequestUtil { export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET // 适用于获取服务器下发cookies时获取仅GET

View File

@@ -1,4 +1,4 @@
import { log } from './log' import { Context } from 'cordis'
export interface IdMusicSignPostData { export interface IdMusicSignPostData {
type: 'qq' | '163' type: 'qq' | '163'
@@ -19,7 +19,7 @@ export type MusicSignPostData = IdMusicSignPostData | CustomMusicSignPostData
export class MusicSign { export class MusicSign {
private readonly url: string private readonly url: string
constructor(url: string) { constructor(protected ctx: Context, url: string) {
this.url = url this.url = url
} }
@@ -31,7 +31,7 @@ export class MusicSign {
}) })
if (!resp.ok) throw new Error(resp.statusText) if (!resp.ok) throw new Error(resp.statusText)
const data = await resp.text() const data = await resp.text()
log('音乐消息生成成功', data) this.ctx.logger.info('音乐消息生成成功', data)
return data return data
} }
} }

View File

@@ -1,10 +0,0 @@
import os from 'node:os';
import path from 'node:path';
export const systemPlatform = os.platform();
export const cpuArch = os.arch();
export const systemVersion = os.release();
// export const hostname = os.hostname(); // win7不支持
const homeDir = os.homedir();
export const downloadsPath = path.join(homeDir, 'Downloads');
export const systemName = os.type();

View File

@@ -1,8 +1,9 @@
import { version } from '../../version' import path from 'node:path'
import * as path from 'node:path'
import * as fs from 'node:fs'
import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from '.'
import compressing from 'compressing' import compressing from 'compressing'
import { writeFile } from 'node:fs/promises'
import { version } from '../../version'
import { copyFolder, log, fetchFile } from '.'
import { PLUGIN_DIR, TEMP_DIR } from '../globalVars'
const downloadMirrorHosts = ['https://mirror.ghproxy.com/'] const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
const checkVersionMirrorHosts = ['https://kkgithub.com'] const checkVersionMirrorHosts = ['https://kkgithub.com']
@@ -10,9 +11,9 @@ const checkVersionMirrorHosts = ['https://kkgithub.com']
export async function checkNewVersion() { export async function checkNewVersion() {
const latestVersionText = await getRemoteVersion() const latestVersionText = await getRemoteVersion()
const latestVersion = latestVersionText.split('.') const latestVersion = latestVersionText.split('.')
log('llonebot last version', latestVersion) //log('llonebot last version', latestVersion)
const currentVersion: string[] = version.split('.') const currentVersion: string[] = version.split('.')
log('llonebot current version', currentVersion) //log('llonebot current version', currentVersion)
for (let k of [0, 1, 2]) { for (let k of [0, 1, 2]) {
if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) { if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) {
log('') log('')
@@ -33,8 +34,8 @@ export async function upgradeLLOneBot() {
// 多镜像下载 // 多镜像下载
for (const mirrorGithub of downloadMirrorHosts) { for (const mirrorGithub of downloadMirrorHosts) {
try { try {
const buffer = await httpDownload(mirrorGithub + downloadUrl) const res = await fetchFile(mirrorGithub + downloadUrl)
fs.writeFileSync(filePath, buffer) await writeFile(filePath, res.data)
downloadSuccess = true downloadSuccess = true
break break
} catch (e) { } catch (e) {
@@ -88,10 +89,10 @@ export async function getRemoteVersionByMirror(mirrorGithub: string) {
let releasePage = 'error' let releasePage = 'error'
try { try {
releasePage = (await httpDownload(mirrorGithub + '/LLOneBot/LLOneBot/releases')).toString() releasePage = (await fetchFile(mirrorGithub + '/LLOneBot/LLOneBot/releases')).data.toString()
// log("releasePage", releasePage); // log("releasePage", releasePage);
if (releasePage === 'error') return '' if (releasePage === 'error') return ''
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0] return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))?.[0]
} catch {} } catch { }
return '' return ''
} }

View File

@@ -1,6 +1,6 @@
import { log } from './log'
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import fs from 'fs' import fs from 'node:fs'
import { log } from './legacyLog'
import { getConfigUtil } from '../config' import { getConfigUtil } from '../config'
const defaultVideoThumbB64 = const defaultVideoThumbB64 =
@@ -43,43 +43,19 @@ export async function getVideoInfo(filePath: string) {
}) })
} }
export async function encodeMp4(filePath: string) { export function checkFfmpeg(newPath?: string): Promise<boolean> {
let videoInfo = await getVideoInfo(filePath)
log('视频信息', videoInfo)
if (videoInfo.format.indexOf('mp4') === -1) {
log('视频需要转换为MP4格式', filePath)
// 转成mp4
const newPath: string = await new Promise<string>((resolve, reject) => {
const newPath = filePath + '.mp4'
ffmpeg(filePath)
.toFormat('mp4')
.on('error', (err) => {
reject(`转换视频格式失败: ${err.message}`)
})
.on('end', () => {
log('视频转换为MP4格式完成')
resolve(newPath) // 返回转换后的文件路径
})
.save(newPath)
})
return await getVideoInfo(newPath)
}
return videoInfo
}
export function checkFfmpeg(newPath: string | null = null): Promise<boolean> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
log('开始检查ffmpeg', newPath) log(`开始检查 FFmpeg ${newPath ?? ''}`)
if (newPath) { if (newPath) {
ffmpeg.setFfmpegPath(newPath) ffmpeg.setFfmpegPath(newPath)
} }
try { try {
ffmpeg.getAvailableFormats((err, formats) => { ffmpeg.getAvailableFormats((err, formats) => {
if (err) { if (err) {
log('ffmpeg is not installed or not found in PATH:', err) log('FFmpeg is not installed or not found in PATH:', err)
resolve(false) resolve(false)
} else { } else {
log('ffmpeg is installed.') log('FFmpeg is installed.')
resolve(true) resolve(true)
} }
}) })

8
src/global.d.ts vendored
View File

@@ -1,8 +1,10 @@
import { type LLOneBot } from './preload' import type { LLOneBot } from './preload'
import { Dict } from 'cosmokit'
declare global { declare global {
interface Window { interface Window {
llonebot: LLOneBot llonebot: LLOneBot
LiteLoader: Record<string, any> LiteLoader: Dict
} }
} var LiteLoader: Dict
}

View File

@@ -1,12 +0,0 @@
import { webContents } from 'electron'
function sendIPCMsg(channel: string, ...data: any) {
let contents = webContents.getAllWebContents()
for (const content of contents) {
try {
content.send(channel, ...data)
} catch (e) {
console.log('llonebot send ipc msg to render error:', e)
}
}
}

41
src/main/log.ts Normal file
View File

@@ -0,0 +1,41 @@
import path from 'node:path'
import { Context, Logger } from 'cordis'
import { appendFile } from 'node:fs'
import { LOG_DIR, selfInfo } from '@/common/globalVars'
import { noop } from 'cosmokit'
interface Config {
enable: boolean
filename: string
}
export default class Log {
static name = 'logger'
constructor(ctx: Context, cfg: Config) {
Logger.targets.splice(0, Logger.targets.length)
if (!cfg.enable) {
return
}
const file = path.join(LOG_DIR, cfg.filename)
const refreshNick = ctx.debounce(() => {
const ntUserApi = ctx.get('ntUserApi')
if (ntUserApi && !selfInfo.nick) {
ntUserApi.getSelfNick(true)
}
}, 1000)
const target: Logger.Target = {
colors: 0,
record: (record: Logger.Record) => {
if (!selfInfo.nick) {
refreshNick()
}
const dateTime = new Date(record.timestamp).toLocaleString()
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : ''
const content = `${dateTime} [${record.type}] ${userInfo} | ${record.name} ${record.content}\n\n`
appendFile(file, content, noop)
},
}
Logger.targets.push(target)
}
}

View File

@@ -1,9 +1,10 @@
// 运行在 Electron 主进程 下的插件入口
import { BrowserWindow, dialog, ipcMain } from 'electron'
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import fs from 'node:fs'
import { Config } from '../common/types' import Log from './log'
import Core from '../ntqqapi/core'
import OneBot11Adapter from '../onebot11/adapter'
import { BrowserWindow, dialog, ipcMain } from 'electron'
import { Config as LLOBConfig } from '../common/types'
import { import {
CHANNEL_CHECK_VERSION, CHANNEL_CHECK_VERSION,
CHANNEL_ERROR, CHANNEL_ERROR,
@@ -12,54 +13,54 @@ import {
CHANNEL_SELECT_FILE, CHANNEL_SELECT_FILE,
CHANNEL_SET_CONFIG, CHANNEL_SET_CONFIG,
CHANNEL_UPDATE, CHANNEL_UPDATE,
CHANNEL_SET_CONFIG_CONFIRMED
} from '../common/channels' } from '../common/channels'
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer' import { getBuildVersion } from '../common/utils'
import { DATA_DIR, TEMP_DIR } from '../common/utils' import { hookNTQQApiCall, hookNTQQApiReceive } from '../ntqqapi/hook'
import {
llonebotError,
setSelfInfo,
getSelfInfo,
getSelfUid,
getSelfUin,
addMsgCache
} from '../common/data'
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, startHook } from '../ntqqapi/hook'
import { OB11Constructor } from '../onebot11/constructor'
import {
FriendRequestNotify,
GroupNotify,
GroupNotifyTypes,
RawMessage,
BuddyReqType,
} from '../ntqqapi/types'
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
import { postOb11Event } from '../onebot11/server/post-ob11-event'
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequest'
import { MessageUnique } from '../common/utils/MessageUnique'
import { setConfig } from './setConfig'
import { NTQQUserApi, NTQQGroupApi } from '../ntqqapi/api'
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade' import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
import { log } from '../common/utils/log'
import { getConfigUtil } from '../common/config' import { getConfigUtil } from '../common/config'
import { checkFfmpeg } from '../common/utils/video' import { checkFfmpeg } from '../common/utils/video'
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' import { getSession } from '../ntqqapi/wrapper'
import '../ntqqapi/wrapper' import { Context } from 'cordis'
import { NTEventDispatch } from '../common/utils/EventTask' import { llonebotError, selfInfo, LOG_DIR, DATA_DIR, TEMP_DIR } from '../common/globalVars'
import { wrapperConstructor, getSession } from '../ntqqapi/wrapper' import { log, logFileName } from '../common/utils/legacyLog'
import { Peer } from '../ntqqapi/types' import {
NTQQFileApi,
NTQQFileCacheApi,
NTQQFriendApi,
NTQQGroupApi,
NTQQMsgApi,
NTQQUserApi,
NTQQWebApi,
NTQQWindowApi
} from '../ntqqapi/api'
declare module 'cordis' {
interface Events {
'llonebot/config-updated': (input: LLOBConfig) => void
}
}
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
// 加载插件时触发 // 加载插件时触发
function onLoad() { function onLoad() {
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true })
}
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR)
}
ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => { ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => {
return checkNewVersion() return checkNewVersion()
}) })
ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => { ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => {
return upgradeLLOneBot() return upgradeLLOneBot()
}) })
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => { ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => {
const selectPath = new Promise<string>((resolve, reject) => { const selectPath = new Promise<string>((resolve, reject) => {
dialog dialog
@@ -73,11 +74,9 @@ function onLoad() {
if (!result.canceled) { if (!result.canceled) {
const _selectPath = path.join(result.filePaths[0]) const _selectPath = path.join(result.filePaths[0])
resolve(_selectPath) resolve(_selectPath)
// let config = getConfigUtil().getConfig() } else {
// config.ffmpeg = path.join(result.filePaths[0]); resolve('')
// getConfigUtil().setConfig(config);
} }
resolve('')
}) })
.catch((err) => { .catch((err) => {
reject(err) reject(err)
@@ -90,9 +89,7 @@ function onLoad() {
return '' return ''
} }
}) })
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true })
}
ipcMain.handle(CHANNEL_ERROR, async (event, arg) => { ipcMain.handle(CHANNEL_ERROR, async (event, arg) => {
const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg) const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg)
llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到 FFmpeg, 音频只能发送 WAV 和 SILK, 视频尺寸可能异常' llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到 FFmpeg, 音频只能发送 WAV 和 SILK, 视频尺寸可能异常'
@@ -100,274 +97,53 @@ function onLoad() {
let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}` let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}`
error = error.replace('\n\n', '\n') error = error.replace('\n\n', '\n')
error = error.trim() error = error.trim()
log('查询llonebot错误信息', error) log('查询 LLOneBot 错误信息', error)
return error return error
}) })
ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => { ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => {
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
return config return config
}) })
ipcMain.on(CHANNEL_SET_CONFIG, (event, ask: boolean, config: Config) => {
if (!ask) { ipcMain.handle(CHANNEL_SET_CONFIG, (event, ask: boolean, config: LLOBConfig) => {
setConfig(config) return new Promise<boolean>(resolve => {
.then() if (!ask) {
.catch((e) => { getConfigUtil().setConfig(config)
log('保存设置失败', e.stack) log('配置已更新', config)
checkFfmpeg(config.ffmpeg).then()
resolve(true)
return
}
dialog
.showMessageBox(mainWindow!, {
type: 'question',
buttons: ['确认', '取消'],
defaultId: 0, // 默认选中的按钮0 代表第一个按钮,即 "确认"
title: '确认保存',
message: '是否保存?',
detail: 'LLOneBot配置已更改是否保存',
}) })
return .then((result) => {
} if (result.response === 0) {
dialog getConfigUtil().setConfig(config)
.showMessageBox(mainWindow!, { log('配置已更新', config)
type: 'question', checkFfmpeg(config.ffmpeg).then()
buttons: ['确认', '取消'], resolve(true)
defaultId: 0, // 默认选中的按钮0 代表第一个按钮,即 "确认" }
title: '确认保存', })
message: '是否保存?', .catch((err) => {
detail: 'LLOneBot配置已更改是否保存', log('保存设置询问弹窗错误', err)
}) resolve(false)
.then((result) => { })
if (result.response === 0) { })
setConfig(config)
.then()
.catch((e) => {
log('保存设置失败', e.stack)
})
}
else {
}
})
.catch((err) => {
log('保存设置询问弹窗错误', err)
})
}) })
ipcMain.on(CHANNEL_LOG, (event, arg) => { ipcMain.on(CHANNEL_LOG, (event, arg) => {
log(arg) log(arg)
}) })
async function postReceiveMsg(msgList: RawMessage[]) { async function start() {
const { debug, reportSelfMessage } = getConfigUtil().getConfig()
for (let message of msgList) {
// 过滤启动之前的消息
if (parseInt(message.msgTime) < startTime / 1000) {
continue
}
// log("收到新消息", message.msgId, message.msgSeq)
const peer: Peer = {
chatType: message.chatType,
peerUid: message.peerUid
}
message.msgShortId = MessageUnique.createMsg(peer, message.msgId)
addMsgCache(message)
OB11Constructor.message(message)
.then((msg) => {
if (!debug && msg.message.length === 0) {
return
}
const isSelfMsg = msg.user_id.toString() === getSelfUin()
if (isSelfMsg && !reportSelfMessage) {
return
}
if (isSelfMsg) {
msg.target_id = parseInt(message.peerUin)
}
postOb11Event(msg)
// log("post msg", msg)
})
.catch((e) => log('constructMessage error: ', e.stack.toString()))
OB11Constructor.GroupEvent(message).then((groupEvent) => {
if (groupEvent) {
// log("post group event", groupEvent);
postOb11Event(groupEvent)
}
})
OB11Constructor.PrivateEvent(message).then((privateEvent) => {
//log(message)
if (privateEvent) {
// log("post private event", privateEvent);
postOb11Event(privateEvent)
}
})
}
}
async function startReceiveHook() {
startHook()
registerReceiveHook<{
msgList: Array<RawMessage>
}>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => {
try {
await postReceiveMsg(payload.msgList)
} catch (e: any) {
log('report message error: ', e.stack.toString())
}
})
const recallMsgIds: string[] = [] // 避免重复上报
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
for (const message of payload.msgList) {
if (message.recallTime != '0') {
if (recallMsgIds.includes(message.msgId)) {
continue
}
recallMsgIds.push(message.msgId)
const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId)
if (!oriMessageId) {
continue
}
OB11Constructor.RecallEvent(message, oriMessageId).then((recallEvent) => {
if (recallEvent) {
//log('post recall event', recallEvent)
postOb11Event(recallEvent)
}
})
}
}
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, async (payload) => {
const { reportSelfMessage } = getConfigUtil().getConfig()
if (!reportSelfMessage) {
return
}
// log("reportSelfMessage", payload)
try {
await postReceiveMsg([payload.msgRecord])
} catch (e: any) {
log('report self message error: ', e.stack.toString())
}
})
const processedGroupNotify: string[] = []
registerReceiveHook<{
doubt: boolean
oldestUnreadSeq: string
unreadCount: number
}>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => {
if (payload.unreadCount) {
// log("开始获取群通知详情")
let notifies: GroupNotify[]
try {
notifies = (await NTQQGroupApi.getSingleScreenNotifies(14)).slice(0, payload.unreadCount)
} catch (e) {
// log("获取群通知详情失败", e);
return
}
for (const notify of notifies) {
try {
notify.time = Date.now()
const notifyTime = parseInt(notify.seq) / 1000
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
if (notifyTime < startTime || processedGroupNotify.includes(flag)) {
continue
}
processedGroupNotify.push(flag)
if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
log('有成员退出通知', notify)
const member1Uin = (await NTQQUserApi.getUinByUid(notify.user1.uid))!
let operatorId = member1Uin
let subType: GroupDecreaseSubType = 'leave'
if (notify.user2.uid) {
// 是被踢的
const member2Uin = await NTQQUserApi.getUinByUid(notify.user2.uid)
if (member2Uin) {
operatorId = member2Uin
}
subType = 'kick'
}
const groupDecreaseEvent = new OB11GroupDecreaseEvent(
parseInt(notify.group.groupCode),
parseInt(member1Uin),
parseInt(operatorId),
subType,
)
postOb11Event(groupDecreaseEvent, true)
}
else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) {
log('有加群请求')
let requestQQ = ''
try {
// uid-->uin
requestQQ = (await NTQQUserApi.getUinByUid(notify.user1.uid))
if (isNaN(parseInt(requestQQ))) {
requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin
}
} catch (e) {
log('获取加群人QQ号失败 Uid:', notify.user1.uid, e)
}
let invitorId: string
if (notify.type == GroupNotifyTypes.JOIN_REQUEST_BY_INVITED) {
// groupRequestEvent.sub_type = 'invite'
try {
// uid-->uin
invitorId = (await NTQQUserApi.getUinByUid(notify.user2.uid))
if (isNaN(parseInt(invitorId))) {
invitorId = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid)).uin
}
} catch (e) {
invitorId = ''
log('获取邀请人QQ号失败 Uid:', notify.user2.uid, e)
}
}
const groupRequestEvent = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(requestQQ) || 0,
flag,
notify.postscript,
invitorId! === undefined ? undefined : +invitorId,
'add'
)
postOb11Event(groupRequestEvent)
}
else if (notify.type == GroupNotifyTypes.INVITE_ME) {
log('收到邀请我加群通知')
const userId = (await NTQQUserApi.getUinByUid(notify.user2.uid)) || ''
const groupInviteEvent = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(userId),
flag,
undefined,
undefined,
'invite'
)
postOb11Event(groupInviteEvent)
}
} catch (e: any) {
log('解析群通知失败', e.stack.toString())
}
}
}
else if (payload.doubt) {
// 可能有群管理员变动
}
})
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, async (payload) => {
for (const req of payload.data.buddyReqs) {
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
continue
}
let userId = 0
try {
const requesterUin = await NTQQUserApi.getUinByUid(req.friendUid)
userId = parseInt(requesterUin!)
} catch (e) {
log('获取加好友者QQ号失败', e)
}
const flag = req.friendUid + '|' + req.reqTime
const comment = req.extWords
const friendRequestEvent = new OB11FriendRequestEvent(
userId,
comment,
flag
)
postOb11Event(friendRequestEvent)
}
})
}
let startTime = 0 // 毫秒
async function start(uid: string, uin: string) {
log('process pid', process.pid) log('process pid', process.pid)
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
if (!config.enableLLOB) { if (!config.enableLLOB) {
@@ -376,54 +152,54 @@ function onLoad() {
return return
} }
if (!fs.existsSync(TEMP_DIR)) { if (!fs.existsSync(TEMP_DIR)) {
fs.mkdirSync(TEMP_DIR, { recursive: true }) fs.mkdirSync(TEMP_DIR)
} }
llonebotError.otherError = '' const ctx = new Context()
startTime = Date.now() ctx.plugin(Log, {
NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession: getSession()! }) enable: config.log!,
MessageUnique.init(uin) filename: logFileName
})
//log('start activate group member info') ctx.plugin(NTQQFileApi)
// 下面两个会导致CPU占用过高QQ卡死 ctx.plugin(NTQQFileCacheApi)
// NTQQGroupApi.activateMemberInfoChange().then().catch(log) ctx.plugin(NTQQFriendApi)
// NTQQGroupApi.activateMemberListChange().then().catch(log) ctx.plugin(NTQQGroupApi)
startReceiveHook().then() ctx.plugin(NTQQMsgApi)
ctx.plugin(NTQQUserApi)
if (config.ob11.enableHttp) { ctx.plugin(NTQQWebApi)
ob11HTTPServer.start(config.ob11.httpPort) ctx.plugin(NTQQWindowApi)
} ctx.plugin(Core, config)
if (config.ob11.enableWs) { ctx.plugin(OneBot11Adapter, {
ob11WebsocketServer.start(config.ob11.wsPort) ...config.ob11,
} heartInterval: config.heartInterval,
if (config.ob11.enableWsReverse) { token: config.token!,
ob11ReverseWebsockets.start() debug: config.debug!,
} reportSelfMessage: config.reportSelfMessage!,
if (config.ob11.enableHttpHeart) { msgCacheExpire: config.msgCacheExpire!,
httpHeart.start() })
} ctx.start()
ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => {
log('LLOneBot start') ctx.parallel('llonebot/config-updated', config)
})
} }
const buildVersion = getBuildVersion()
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
const current = getSelfInfo() const self = Object.assign(selfInfo, {
if (!current.uin) { uin: globalThis.authData?.uin,
setSelfInfo({ uid: globalThis.authData?.uid,
uin: globalThis.authData?.uin, online: true
uid: globalThis.authData?.uid, })
nick: current.uin, if (self.uin && (buildVersion >= 27187 || getSession())) {
})
}
if (current.uin && getSession()) {
clearInterval(intervalId) clearInterval(intervalId)
start(current.uid, current.uin) start()
} }
}, 600) }, 600)
} }
// 创建窗口时触发 // 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) { function onBrowserWindowCreated(window: BrowserWindow) {
if (getSelfUid()) { if (window.id !== 2) {
return return
} }
mainWindow = window mainWindow = window

View File

@@ -1,67 +0,0 @@
import { Config } from '../common/types'
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
import { llonebotError } from '../common/data'
import { getConfigUtil } from '../common/config'
import { checkFfmpeg, log } from '../common/utils'
export async function setConfig(config: Config) {
let oldConfig = { ...getConfigUtil().getConfig() }
getConfigUtil().setConfig(config)
if (config.ob11.httpPort != oldConfig.ob11.httpPort && config.ob11.enableHttp) {
ob11HTTPServer.restart(config.ob11.httpPort)
}
// 判断是否启用或关闭HTTP服务
if (!config.ob11.enableHttp) {
ob11HTTPServer.stop()
} else {
ob11HTTPServer.start(config.ob11.httpPort)
}
// 正向ws端口变化重启服务
if (config.ob11.wsPort != oldConfig.ob11.wsPort) {
ob11WebsocketServer.restart(config.ob11.wsPort)
llonebotError.wsServerError = ''
}
// 判断是否启用或关闭正向ws
if (config.ob11.enableWs != oldConfig.ob11.enableWs) {
if (config.ob11.enableWs) {
ob11WebsocketServer.start(config.ob11.wsPort)
} else {
ob11WebsocketServer.stop()
}
}
// 判断是否启用或关闭反向ws
if (config.ob11.enableWsReverse != oldConfig.ob11.enableWsReverse) {
if (config.ob11.enableWsReverse) {
ob11ReverseWebsockets.start()
} else {
ob11ReverseWebsockets.stop()
}
}
if (config.ob11.enableWsReverse) {
// 判断反向ws地址有变化
if (config.ob11.wsHosts.length != oldConfig.ob11.wsHosts.length) {
log('反向ws地址有变化, 重启反向ws服务')
ob11ReverseWebsockets.restart()
} else {
for (const newHost of config.ob11.wsHosts) {
if (!oldConfig.ob11.wsHosts.includes(newHost)) {
log('反向ws地址有变化, 重启反向ws服务')
ob11ReverseWebsockets.restart()
break
}
}
}
}
if (config.ob11.enableHttpHeart) {
// 启动http心跳
httpHeart.start()
} else {
// 关闭http心跳
httpHeart.stop()
}
log('old config', oldConfig)
log('配置已更新', config)
checkFfmpeg(config.ffmpeg).then()
}

View File

@@ -1,4 +1,5 @@
import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services'
import { import {
CacheFileList, CacheFileList,
CacheFileListItem, CacheFileListItem,
@@ -15,56 +16,51 @@ import {
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import fs from 'node:fs'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { log, TEMP_DIR } from '@/common/utils' import { RkeyManager } from '@/ntqqapi/helper/rkey'
import { rkeyManager } from '@/ntqqapi/api/rkey'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { 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 { fileTypeFromFile } from 'file-type'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import { NTEventDispatch } from '@/common/utils/EventTask' import { NTEventDispatch } from '@/common/utils/eventTask'
import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners' import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners'
import { NodeIKernelSearchService } from '@/ntqqapi/services' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis'
import { TEMP_DIR } from '@/common/globalVars'
export class NTQQFileApi { declare module 'cordis' {
static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string> { interface Context {
ntFileApi: NTQQFileApi
ntFileCacheApi: NTQQFileCacheApi
}
}
export class NTQQFileApi extends Service {
private rkeyManager: RkeyManager
constructor(protected ctx: Context) {
super(ctx, 'ntFileApi', true)
this.rkeyManager = new RkeyManager(ctx, 'http://napcat-sign.wumiao.wang:2082/rkey')
}
/** 27187 TODO */
async getVideoUrl(peer: Peer, msgId: string, elementId: string) {
const session = getSession() const session = getSession()
return (await session?.getRichMediaService().getVideoPlayUrlV2(peer, return (await session?.getRichMediaService().getVideoPlayUrlV2(peer,
msgId, msgId,
elementId, elementId,
0, 0,
{ downSourceType: 1, triggerType: 1 }))?.urlResult?.domainUrl[0]?.url! { downSourceType: 1, triggerType: 1 }))?.urlResult.domainUrl[0].url
} }
static async getFileType(filePath: string) { async getFileType(filePath: string) {
return fileTypeFromFile(filePath) return fileTypeFromFile(filePath)
} }
static async copyFile(filePath: string, destPath: string) {
return await callNTQQApi<string>({
className: NTQQApiClass.FS_API,
methodName: NTQQApiMethod.FILE_COPY,
args: [
{
fromPath: filePath,
toPath: destPath,
},
],
})
}
static async getFileSize(filePath: string) {
return await callNTQQApi<number>({
className: NTQQApiClass.FS_API,
methodName: NTQQApiMethod.FILE_SIZE,
args: [filePath],
})
}
// 上传文件到QQ的文件夹 // 上传文件到QQ的文件夹
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType = 0) { async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType = 0) {
const fileMd5 = await calculateFileMD5(filePath) const fileMd5 = await calculateFileMD5(filePath)
let ext = (await NTQQFileApi.getFileType(filePath))?.ext || '' let ext = (await this.getFileType(filePath))?.ext || ''
if (ext) { if (ext) {
ext = '.' + ext ext = '.' + ext
} }
@@ -73,28 +69,49 @@ export class NTQQFileApi {
fileName += ext fileName += ext
} }
const session = getSession() const session = getSession()
const mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({ let mediaPath: string
md5HexStr: fileMd5, if (session) {
fileName: fileName, mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({
elementType: elementType, md5HexStr: fileMd5,
elementSubType, fileName: fileName,
thumbSize: 0, elementType: elementType,
needCreate: true, elementSubType,
downloadType: 1, thumbSize: 0,
file_uuid: '' needCreate: true,
}) downloadType: 1,
await fsPromise.copyFile(filePath, mediaPath!) file_uuid: ''
})
} else {
mediaPath = await invoke<string>({
methodName: NTMethod.MEDIA_FILE_PATH,
args: [
{
path_info: {
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 const fileSize = (await fsPromise.stat(filePath)).size
return { return {
md5: fileMd5, md5: fileMd5,
fileName, fileName,
path: mediaPath!, path: mediaPath,
fileSize, fileSize,
ext ext
} }
} }
static async downloadMedia( async downloadMedia(
msgId: string, msgId: string,
chatType: ChatType, chatType: ChatType,
peerUid: string, peerUid: string,
@@ -109,53 +126,79 @@ export class NTQQFileApi {
if (force) { if (force) {
try { try {
await fsPromise.unlink(sourcePath) await fsPromise.unlink(sourcePath)
} catch (e) { } catch { }
//
}
} else { } else {
return sourcePath return sourcePath
} }
} }
const data = await NTEventDispatch.CallNormalEvent< let filePath: string
( if (NTEventDispatch.initialised) {
params: { const data = await NTEventDispatch.CallNormalEvent<
fileModelId: string, (
downloadSourceType: number, params: {
triggerType: number, fileModelId: string,
msgId: string, downloadSourceType: number,
chatType: ChatType, triggerType: number,
peerUid: string, msgId: string,
elementId: string, chatType: ChatType,
thumbSize: number, peerUid: string,
downloadType: number, elementId: string,
filePath: string thumbSize: number,
}) => Promise<unknown>, downloadType: number,
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void filePath: string
>( }) => Promise<unknown>,
'NodeIKernelMsgService/downloadRichMedia', (fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
'NodeIKernelMsgListener/onRichMediaDownloadComplete', >(
1, 'NodeIKernelMsgService/downloadRichMedia',
timeout, 'NodeIKernelMsgListener/onRichMediaDownloadComplete',
(arg: OnRichMediaDownloadCompleteParams) => { 1,
if (arg.msgId === msgId) { timeout,
return true (arg: OnRichMediaDownloadCompleteParams) => {
if (arg.msgId === msgId) {
return true
}
return false
},
{
fileModelId: '0',
downloadSourceType: 0,
triggerType: 1,
msgId: msgId,
chatType: chatType,
peerUid: peerUid,
elementId: elementId,
thumbSize: 0,
downloadType: 1,
filePath: thumbPath
} }
return false )
}, filePath = data[1].filePath
{ } else {
fileModelId: '0', const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>({
downloadSourceType: 0, methodName: NTMethod.DOWNLOAD_MEDIA,
triggerType: 1, args: [
msgId: msgId, {
chatType: chatType, getReq: {
peerUid: peerUid, fileModelId: '0',
elementId: elementId, downloadSourceType: 0,
thumbSize: 0, triggerType: 1,
downloadType: 1, msgId: msgId,
filePath: thumbPath chatType: chatType,
} peerUid: peerUid,
) elementId: elementId,
let filePath = data[1].filePath thumbSize: 0,
downloadType: 1,
filePath: thumbPath,
},
},
null,
],
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: payload => payload.notifyInfo.msgId === msgId,
timeout
})
filePath = data.notifyInfo.filePath
}
if (filePath.startsWith('\\')) { if (filePath.startsWith('\\')) {
const downloadPath = TEMP_DIR const downloadPath = TEMP_DIR
filePath = path.join(downloadPath, filePath) filePath = path.join(downloadPath, filePath)
@@ -164,15 +207,15 @@ export class NTQQFileApi {
return filePath return filePath
} }
static async getImageSize(filePath: string) { async getImageSize(filePath: string) {
return await callNTQQApi<{ width: number; height: number }>({ return await invoke<{ width: number; height: number }>({
className: NTQQApiClass.FS_API, className: NTClass.FS_API,
methodName: NTQQApiMethod.IMAGE_SIZE, methodName: NTMethod.IMAGE_SIZE,
args: [filePath], args: [filePath],
}) })
} }
static async getImageUrl(element: PicElement) { async getImageUrl(element: PicElement) {
if (!element) { if (!element) {
return '' return ''
} }
@@ -189,7 +232,7 @@ export class NTQQFileApi {
if (UrlRkey) { if (UrlRkey) {
return IMAGE_HTTP_HOST_NT + url return IMAGE_HTTP_HOST_NT + url
} }
const rkeyData = await rkeyManager.getRkey() const rkeyData = await this.rkeyManager.getRkey()
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}` return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`
} else { } else {
@@ -200,131 +243,19 @@ export class NTQQFileApi {
// 没有url需要自己拼接 // 没有url需要自己拼接
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0` return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
} }
log('图片url获取失败', element) this.ctx.logger.error('图片url获取失败', element)
return '' return ''
} }
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/core/src/apis/file.ts#L149
static async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
let GroupData: any[] | undefined
let BuddyData: any[] | undefined
if (peer.chatType === ChatType.group) {
GroupData =
[{
groupCode: peer.peerUid,
isConf: false,
hasModifyConfGroupFace: true,
hasModifyConfGroupName: true,
groupName: 'LLOneBot.Cached',
remark: 'LLOneBot.Cached',
}];
} else if (peer.chatType === ChatType.friend) {
BuddyData = [{
category_name: 'LLOneBot.Cached',
peerUid: peer.peerUid,
peerUin: peer.peerUid,
remark: 'LLOneBot.Cached',
}]
} else {
return undefined
}
const session = getSession()
return session?.getSearchService().addSearchHistory({
type: 4,
contactList: [],
id: -1,
groupInfos: [],
msgs: [],
fileInfos: [
{
chatType: peer.chatType,
buddyChatInfo: BuddyData || [],
discussChatInfo: [],
groupChatInfo: GroupData || [],
dataLineChatInfo: [],
tmpChatInfo: [],
msgId: msgId,
msgSeq: msgSeq,
msgTime: Math.floor(Date.now() / 1000).toString(),
senderUid: senderUid,
senderNick: 'LLOneBot.Cached',
senderRemark: 'LLOneBot.Cached',
senderCard: 'LLOneBot.Cached',
elemId: elemId,
elemType: elemType,
fileSize: fileSize,
filePath: '',
fileName: fileName,
hits: [{
start: 12,
end: 14,
}],
},
],
})
}
static async searchfile(keys: string[]) {
type EventType = NodeIKernelSearchService['searchFileWithKeywords']
interface OnListener {
searchId: string,
hasMore: boolean,
resultItems: {
chatType: ChatType,
buddyChatInfo: any[],
discussChatInfo: any[],
groupChatInfo:
{
groupCode: string,
isConf: boolean,
hasModifyConfGroupFace: boolean,
hasModifyConfGroupName: boolean,
groupName: string,
remark: string
}[],
dataLineChatInfo: any[],
tmpChatInfo: any[],
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderNick: string,
senderRemark: string,
senderCard: string,
elemId: string,
elemType: number,
fileSize: string,
filePath: string,
fileName: string,
hits:
{
start: number,
end: number
}[]
}[]
}
const Event = NTEventDispatch.createEventFunction<EventType>('NodeIKernelSearchService/searchFileWithKeywords')
let id = ''
const Listener = NTEventDispatch.RegisterListen<(params: OnListener) => void>
(
'NodeIKernelSearchListener/onSearchFileKeywordsResult',
1,
20000,
(params) => id !== '' && params.searchId == id,
)
id = await Event!(keys, 12)
const [ret] = await Listener
return ret
}
} }
export class NTQQFileCacheApi { export class NTQQFileCacheApi extends Service {
static async setCacheSilentScan(isSilent: boolean = true) { constructor(protected ctx: Context) {
return await callNTQQApi<GeneralCallResult>({ super(ctx, 'ntFileCacheApi', true)
methodName: NTQQApiMethod.CACHE_SET_SILENCE, }
async setCacheSilentScan(isSilent: boolean = true) {
return await invoke<GeneralCallResult>({
methodName: NTMethod.CACHE_SET_SILENCE,
args: [ args: [
{ {
isSilent, isSilent,
@@ -334,22 +265,22 @@ export class NTQQFileCacheApi {
}) })
} }
static getCacheSessionPathList() { getCacheSessionPathList() {
return callNTQQApi< return invoke<
{ {
key: string key: string
value: string value: string
}[] }[]
>({ >({
className: NTQQApiClass.OS_API, className: NTClass.OS_API,
methodName: NTQQApiMethod.CACHE_PATH_SESSION, methodName: NTMethod.CACHE_PATH_SESSION,
}) })
} }
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) { clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
return callNTQQApi<any>({ return invoke<any>({
// TODO: 目前还不知道真正的返回值是什么 // TODO: 目前还不知道真正的返回值是什么
methodName: NTQQApiMethod.CACHE_CLEAR, methodName: NTMethod.CACHE_CLEAR,
args: [ args: [
{ {
keys: cacheKeys, keys: cacheKeys,
@@ -359,9 +290,9 @@ export class NTQQFileCacheApi {
}) })
} }
static addCacheScannedPaths(pathMap: object = {}) { addCacheScannedPaths(pathMap: object = {}) {
return callNTQQApi<GeneralCallResult>({ return invoke<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH, methodName: NTMethod.CACHE_ADD_SCANNED_PATH,
args: [ args: [
{ {
pathMap: { ...pathMap }, pathMap: { ...pathMap },
@@ -371,36 +302,36 @@ export class NTQQFileCacheApi {
}) })
} }
static scanCache() { scanCache() {
callNTQQApi<GeneralCallResult>({ invoke<GeneralCallResult>({
methodName: ReceiveCmdS.CACHE_SCAN_FINISH, methodName: ReceiveCmdS.CACHE_SCAN_FINISH,
classNameIsRegister: true, classNameIsRegister: true,
}).then() }).then()
return callNTQQApi<CacheScanResult>({ return invoke<CacheScanResult>({
methodName: NTQQApiMethod.CACHE_SCAN, methodName: NTMethod.CACHE_SCAN,
args: [null, null], args: [null, null],
timeoutSecond: 300, timeout: 300 * Time.second,
}) })
} }
static getHotUpdateCachePath() { getHotUpdateCachePath() {
return callNTQQApi<string>({ return invoke<string>({
className: NTQQApiClass.HOTUPDATE_API, className: NTClass.HOTUPDATE_API,
methodName: NTQQApiMethod.CACHE_PATH_HOT_UPDATE, methodName: NTMethod.CACHE_PATH_HOT_UPDATE,
}) })
} }
static getDesktopTmpPath() { getDesktopTmpPath() {
return callNTQQApi<string>({ return invoke<string>({
className: NTQQApiClass.BUSINESS_API, className: NTClass.BUSINESS_API,
methodName: NTQQApiMethod.CACHE_PATH_DESKTOP_TEMP, methodName: NTMethod.CACHE_PATH_DESKTOP_TEMP,
}) })
} }
static getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) { getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
return new Promise<ChatCacheList>((res, rej) => { return new Promise<ChatCacheList>((res, rej) => {
callNTQQApi<ChatCacheList>({ invoke<ChatCacheList>({
methodName: NTQQApiMethod.CACHE_CHAT_GET, methodName: NTMethod.CACHE_CHAT_GET,
args: [ args: [
{ {
chatType: type, chatType: type,
@@ -416,11 +347,11 @@ export class NTQQFileCacheApi {
}) })
} }
static getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) { getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
const _lastRecord = lastRecord ? lastRecord : { fileType: fileType } const _lastRecord = lastRecord ? lastRecord : { fileType: fileType }
return callNTQQApi<CacheFileList>({ return invoke<CacheFileList>({
methodName: NTQQApiMethod.CACHE_FILE_GET, methodName: NTMethod.CACHE_FILE_GET,
args: [ args: [
{ {
fileType: fileType, fileType: fileType,
@@ -434,9 +365,9 @@ export class NTQQFileCacheApi {
}) })
} }
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
return await callNTQQApi<GeneralCallResult>({ return await invoke<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR, methodName: NTMethod.CACHE_CHAT_CLEAR,
args: [ args: [
{ {
chats, chats,

View File

@@ -1,16 +1,27 @@
import { Friend, FriendV2 } from '../types' import { Friend, FriendV2, SimpleInfo, CategoryFriend } from '../types'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { invoke, NTMethod, NTClass } from '../ntcall'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { BuddyListReqType, NodeIKernelProfileService } from '../services' import { BuddyListReqType, NodeIKernelProfileService } from '../services'
import { NTEventDispatch } from '@/common/utils/EventTask' import { NTEventDispatch } from '@/common/utils/eventTask'
import { CacheClassFuncAsyncExtend } from '@/common/utils/helper'
import { LimitedHashTable } from '@/common/utils/table' import { LimitedHashTable } from '@/common/utils/table'
import { pick } from 'cosmokit'
import { Service, Context } from 'cordis'
declare module 'cordis' {
interface Context {
ntFriendApi: NTQQFriendApi
}
}
export class NTQQFriendApi extends Service {
constructor(protected ctx: Context) {
super(ctx, 'ntFriendApi', true)
}
export class NTQQFriendApi {
/** 大于或等于 26702 应使用 getBuddyV2 */ /** 大于或等于 26702 应使用 getBuddyV2 */
static async getFriends(forced = false) { async getFriends(forced = false) {
const data = await callNTQQApi<{ const data = await invoke<{
data: { data: {
categoryId: number categoryId: number
categroyName: string categroyName: string
@@ -18,37 +29,19 @@ export class NTQQFriendApi {
buddyList: Friend[] buddyList: Friend[]
}[] }[]
}>({ }>({
methodName: NTQQApiMethod.FRIENDS, className: NTClass.NODE_STORE_API,
args: [{ force_update: forced }, undefined], methodName: 'getBuddyList',
cbCmd: ReceiveCmdS.FRIENDS, cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false, afterFirstCmd: false,
}) })
// log('获取好友列表', data) const _friends: Friend[] = []
let _friends: Friend[] = [] for (const item of data.data) {
for (const fData of data.data) { _friends.push(...item.buddyList)
_friends.push(...fData.buddyList)
} }
return _friends return _friends
} }
static async likeFriend(uid: string, count = 1) { async handleFriendRequest(flag: string, accept: boolean) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.LIKE_FRIEND,
args: [
{
doLikeUserInfo: {
friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0,
},
},
null,
],
})
}
static async handleFriendRequest(flag: string, accept: boolean) {
const data = flag.split('|') const data = flag.split('|')
if (data.length < 2) { if (data.length < 2) {
return return
@@ -56,71 +49,150 @@ export class NTQQFriendApi {
const friendUid = data[0] const friendUid = data[0]
const reqTime = data[1] const reqTime = data[1]
const session = getSession() const session = getSession()
return session?.getBuddyService().approvalFriendRequest({ if (session) {
friendUid, return session.getBuddyService().approvalFriendRequest({
reqTime, friendUid,
accept reqTime,
}) accept
})
} else {
return await invoke({
methodName: NTMethod.HANDLE_FRIEND_REQUEST,
args: [
{
approvalInfo: {
friendUid,
reqTime,
accept,
},
},
],
})
}
} }
static async getBuddyV2(refresh = false): Promise<FriendV2[]> { async getBuddyV2(refresh = false): Promise<FriendV2[]> {
const uids: string[] = []
const session = getSession() const session = getSession()
const buddyService = session?.getBuddyService() if (session) {
const buddyListV2 = refresh ? await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) const uids: string[] = []
uids.push(...buddyListV2?.data.flatMap(item => item.buddyUids)!) const buddyService = session.getBuddyService()
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids uids.push(...buddyListV2.data.flatMap(item => item.buddyUids))
) const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
return Array.from(data.values()) 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
)
return Array.from(data.values())
} else {
const data = await invoke<{
buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo>
}>({
className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
args: [refresh],
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
})
const categoryUids: Map<number, string[]> = new Map()
for (const item of data.buddyCategory) {
categoryUids.set(item.categoryId, item.buddyUids)
}
return Object.values(data.userSimpleInfos).filter(v => v.baseInfo && categoryUids.get(v.baseInfo.categoryId)?.includes(v.uid!))
}
} }
@CacheClassFuncAsyncExtend(3600 * 1000, 'getBuddyIdMap', () => true) async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
static async getBuddyIdMapCache(refresh = false): Promise<LimitedHashTable<string, string>> {
return await NTQQFriendApi.getBuddyIdMap(refresh)
}
static async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> {
const uids: string[] = []
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000) const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000)
const session = getSession() const session = getSession()
const buddyService = session?.getBuddyService() if (session) {
const buddyListV2 = refresh ? await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) const uids: string[] = []
uids.push(...buddyListV2?.data.flatMap(item => item.buddyUids)!) const buddyService = session?.getBuddyService()
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids uids.push(...buddyListV2.data.flatMap(item => item.buddyUids))
); const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
data.forEach((value, key) => { 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
retMap.set(value.uin!, value.uid!) )
}) data.forEach((value, key) => {
//console.log('getBuddyIdMap', retMap.getValue) retMap.set(value.uin!, value.uid!)
})
} else {
const data = await invoke<{
buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo>
}>({
className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
args: [refresh],
cbCmd: ReceiveCmdS.FRIENDS,
afterFirstCmd: false,
})
for (const item of Object.values(data.userSimpleInfos)) {
retMap.set(item.uin!, item.uid!)
}
}
return retMap return retMap
} }
static async getBuddyV2ExWithCate(refresh = false) { async getBuddyV2ExWithCate(refresh = false) {
const uids: string[] = []
const categoryMap: Map<string, any> = new Map()
const session = getSession() const session = getSession()
const buddyService = session?.getBuddyService() if (session) {
const buddyListV2 = refresh ? (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data : (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data const uids: string[] = []
uids.push( const categoryMap: Map<string, any> = new Map()
...buddyListV2?.flatMap(item => { const buddyService = session.getBuddyService()
item.buddyUids.forEach(uid => { const buddyListV2 = (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName }) uids.push(
...buddyListV2?.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName })
})
return item.buddyUids
})!)
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, '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>
}>({
className: NTClass.NODE_STORE_API,
methodName: 'getBuddyList',
args: [refresh],
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!))
.map(value => {
return {
...value,
categoryId: value.baseInfo.categoryId,
categroyName: category.get(value.baseInfo.categoryId)?.categroyName
}
}) })
return item.buddyUids }
})!)
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
)
return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key)
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value
})
} }
static async isBuddy(uid: string): Promise<boolean> { async isBuddy(uid: string): Promise<boolean> {
const session = getSession() const session = getSession()
return session?.getBuddyService().isBuddy(uid)! if (session) {
return session.getBuddyService().isBuddy(uid)
} else {
return await invoke<boolean>({
methodName: 'nodeIKernelBuddyService/isBuddy',
args: [
{ uid },
null,
],
})
}
} }
} }

View File

@@ -1,215 +1,338 @@
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify } from '../types' import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify } from '../types'
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { NTQQWindowApi, NTQQWindows } from './window' import { GeneralCallResult } from '../services'
import { NTQQWindows } from './window'
import { getSession } from '../wrapper' import { getSession } from '../wrapper'
import { NTEventDispatch } from '@/common/utils/EventTask' import { NTEventDispatch } from '@/common/utils/eventTask'
import { NodeIKernelGroupListener } from '../listeners' import { NodeIKernelGroupListener } from '../listeners'
import { NodeIKernelGroupService } from '../services' import { NodeIKernelGroupService } from '../services'
import { Service, Context } from 'cordis'
import { isNumeric } from '@/common/utils/misc'
export class NTQQGroupApi { declare module 'cordis' {
static async activateMemberListChange() { interface Context {
return await callNTQQApi<GeneralCallResult>({ ntGroupApi: NTQQGroupApi
methodName: NTQQApiMethod.ACTIVATE_MEMBER_LIST_CHANGE, }
classNameIsRegister: true, }
args: [],
}) export class NTQQGroupApi extends Service {
private groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>()
constructor(protected ctx: Context) {
super(ctx, 'ntGroupApi', true)
} }
static async activateMemberInfoChange() { async getGroups(forced = false): Promise<Group[]> {
return await callNTQQApi<GeneralCallResult>({ if (NTEventDispatch.initialised) {
methodName: NTQQApiMethod.ACTIVATE_MEMBER_INFO_CHANGE, type ListenerType = NodeIKernelGroupListener['onGroupListUpdate']
classNameIsRegister: true, const [, , groupList] = await NTEventDispatch.CallNormalEvent
args: [], <(force: boolean) => Promise<any>, ListenerType>
}) (
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
forced
)
return groupList
} else {
const result = await invoke<{
updateType: number
groupList: Group[]
}>({
className: NTClass.NODE_STORE_API,
methodName: 'getGroupList',
cbCmd: ReceiveCmdS.GROUPS_STORE,
afterFirstCmd: false,
})
return result.groupList
}
} }
static async getGroupAllInfo(groupCode: string, source: number = 4) { async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
return await callNTQQApi<GeneralCallResult & Group>({
methodName: NTQQApiMethod.GET_GROUP_ALL_INFO,
args: [
{
groupCode,
source
},
null,
],
})
}
static async getGroups(forced = false): Promise<Group[]> {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate']
const [, , groupList] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, ListenerType>
(
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
forced
)
return groupList
}
static async getGroupMemberV2(GroupCode: string, uid: string, forced = false) {
type ListenerType = NodeIKernelGroupListener['onMemberInfoChange']
type EventType = NodeIKernelGroupService['getMemberInfo']
const [, , , _members] = await NTEventDispatch.CallNormalEvent<EventType, ListenerType>
(
'NodeIKernelGroupService/getMemberInfo',
'NodeIKernelGroupListener/onMemberInfoChange',
1,
5000,
(groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
return groupCode == GroupCode && members.has(uid)
},
GroupCode, [uid], forced,
)
return _members.get(uid)
}
static async getGroupMembers(groupQQ: string, num = 3000): Promise<Map<string, GroupMember>> {
const session = getSession() const session = getSession()
const groupService = session?.getGroupService() let result: Awaited<ReturnType<NodeIKernelGroupService['getNextMemberList']>>
const sceneId = groupService?.createMemberListScene(groupQQ, 'groupMemberList_MainWindow') if (session) {
const result = await groupService?.getNextMemberList(sceneId!, undefined, num) const groupService = session.getGroupService()
if (result?.errCode !== 0) { const sceneId = groupService.createMemberListScene(groupQQ, 'groupMemberList_MainWindow')
throw ('获取群成员列表出错,' + result?.errMsg) result = await groupService.getNextMemberList(sceneId, undefined, num)
} else {
const sceneId = await invoke<string>({
methodName: NTMethod.GROUP_MEMBER_SCENE,
args: [
{
groupCode: groupQQ,
scene: 'groupMemberList_MainWindow',
},
],
})
result = await invoke<
ReturnType<NodeIKernelGroupService['getNextMemberList']>
>({
methodName: NTMethod.GROUP_MEMBERS,
args: [
{
sceneId,
num,
},
null,
],
})
}
if (result.errCode !== 0) {
throw ('获取群成员列表出错,' + result.errMsg)
} }
return result.result.infos return result.result.infos
} }
static async getGroupMembersInfo(groupCode: string, uids: string[], forceUpdate: boolean = false) { async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
return await callNTQQApi<GeneralCallResult>({ const groupCodeStr = groupCode.toString()
methodName: NTQQApiMethod.GROUP_MEMBERS_INFO, const memberUinOrUidStr = memberUinOrUid.toString()
args: [ let members = this.groupMembers.get(groupCodeStr)
{ if (!members) {
forceUpdate, try {
groupCode, members = await this.getGroupMembers(groupCodeStr)
uids // 更新群成员列表
}, this.groupMembers.set(groupCodeStr, members)
null, }
], catch (e) {
}) return null
}
}
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) {
members = await this.getGroupMembers(groupCodeStr)
this.groupMembers.set(groupCodeStr, members)
member = getMember()
}
return member
} }
static async getGroupNotifies() { async getGroupIgnoreNotifies() {
// 获取管理员变更 await this.getSingleScreenNotifies(14)
// 加群通知,退出通知,需要管理员权限 return await this.ctx.ntWindowApi.openWindow<GeneralCallResult & GroupNotifies>(
callNTQQApi<GeneralCallResult>({
methodName: ReceiveCmdS.GROUP_NOTIFY,
classNameIsRegister: true,
}).then()
return await callNTQQApi<GroupNotifies>({
methodName: NTQQApiMethod.GET_GROUP_NOTICE,
cbCmd: ReceiveCmdS.GROUP_NOTIFY,
afterFirstCmd: false,
args: [{ doubt: false, startSeq: '', number: 14 }, null],
})
}
static async getGroupIgnoreNotifies() {
await NTQQGroupApi.getGroupNotifies()
return await NTQQWindowApi.openWindow<GeneralCallResult & GroupNotifies>(
NTQQWindows.GroupNotifyFilterWindow, NTQQWindows.GroupNotifyFilterWindow,
[], [],
ReceiveCmdS.GROUP_NOTIFY, ReceiveCmdS.GROUP_NOTIFY,
) )
} }
static async getSingleScreenNotifies(num: number) { async getSingleScreenNotifies(num: number) {
const [_retData, _doubt, _seq, notifies] = await NTEventDispatch.CallNormalEvent if (NTEventDispatch.initialised) {
<(arg1: boolean, arg2: string, arg3: number) => Promise<any>, (doubt: boolean, seq: string, notifies: GroupNotify[]) => void> const [_retData, _doubt, _seq, notifies] = await NTEventDispatch.CallNormalEvent
( <(arg1: boolean, arg2: string, arg3: number) => Promise<any>, (doubt: boolean, seq: string, notifies: GroupNotify[]) => void>
'NodeIKernelGroupService/getSingleScreenNotifies', (
'NodeIKernelGroupListener/onGroupSingleScreenNotifies', 'NodeIKernelGroupService/getSingleScreenNotifies',
1, 'NodeIKernelGroupListener/onGroupSingleScreenNotifies',
5000, 1,
() => true, 5000,
false, () => true,
'', false,
num, '',
) num,
return notifies )
return notifies
} else {
invoke({
methodName: ReceiveCmdS.GROUP_NOTIFY,
classNameIsRegister: true,
})
return (await invoke<GroupNotifies>({
methodName: NTMethod.GET_GROUP_NOTICE,
cbCmd: ReceiveCmdS.GROUP_NOTIFY,
afterFirstCmd: false,
args: [{ doubt: false, startSeq: '', number: num }, null],
})).notifies
}
} }
static async delGroupFile(groupCode: string, files: string[]) { /** 27187 TODO */
async delGroupFile(groupCode: string, files: string[]) {
const session = getSession() const session = getSession()
return session?.getRichMediaService().deleteGroupFile(groupCode, [102], files)! return session?.getRichMediaService().deleteGroupFile(groupCode, [102], files)
} }
static DelGroupFile = NTQQGroupApi.delGroupFile async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
static async delGroupFileFolder(groupCode: string, folderId: string) {
const session = getSession()
return session?.getRichMediaService().deleteGroupFolder(groupCode, folderId)!
}
static DelGroupFileFolder = NTQQGroupApi.delGroupFileFolder
static async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
const flagitem = flag.split('|') const flagitem = flag.split('|')
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() const session = getSession()
return session?.getGroupService().operateSysNotify( if (session) {
false, return session.getGroupService().operateSysNotify(
{ false,
'operateType': operateType, // 2 拒绝 {
'targetMsg': { 'operateType': operateType, // 2 拒绝
'seq': seq, // 通知序列号 'targetMsg': {
'type': type, 'seq': seq, // 通知序列号
'groupCode': groupCode, 'type': type,
'postscript': reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格 'groupCode': groupCode,
} 'postscript': reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
}
})
} else {
return await invoke({
methodName: NTMethod.HANDLE_GROUP_REQUEST,
args: [
{
doubt: false,
operateMsg: {
operateType,
targetMsg: {
seq,
type,
groupCode,
postscript: reason || ' ' // 仅传空值可能导致处理失败,故默认给个空格
},
},
},
null,
],
}) })
}
} }
static async quitGroup(groupQQ: string) { async quitGroup(groupQQ: string) {
const session = getSession() const session = getSession()
return session?.getGroupService().quitGroup(groupQQ) if (session) {
return session.getGroupService().quitGroup(groupQQ)
} else {
return await invoke({
methodName: NTMethod.QUIT_GROUP,
args: [{ groupCode: groupQQ }, null],
})
}
} }
static async kickMember( async kickMember(
groupQQ: string, groupQQ: string,
kickUids: string[], kickUids: string[],
refuseForever = false, refuseForever = false,
kickReason = '', kickReason = '',
) { ) {
const session = getSession() const session = getSession()
return session?.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason) if (session) {
return session.getGroupService().kickMember(groupQQ, kickUids, refuseForever, kickReason)
} else {
return await invoke({
methodName: NTMethod.KICK_MEMBER,
args: [
{
groupCode: groupQQ,
kickUids,
refuseForever,
kickReason,
},
],
})
}
} }
static async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) { async banMember(groupQQ: string, memList: Array<{ uid: string, timeStamp: number }>) {
// timeStamp为秒数, 0为解除禁言 // timeStamp为秒数, 0为解除禁言
const session = getSession() const session = getSession()
return session?.getGroupService().setMemberShutUp(groupQQ, memList) if (session) {
return session.getGroupService().setMemberShutUp(groupQQ, memList)
} else {
return await invoke({
methodName: NTMethod.MUTE_MEMBER,
args: [
{
groupCode: groupQQ,
memList,
},
],
})
}
} }
static async banGroup(groupQQ: string, shutUp: boolean) { async banGroup(groupQQ: string, shutUp: boolean) {
const session = getSession() const session = getSession()
return session?.getGroupService().setGroupShutUp(groupQQ, shutUp) if (session) {
return session.getGroupService().setGroupShutUp(groupQQ, shutUp)
} else {
return await invoke({
methodName: NTMethod.MUTE_GROUP,
args: [
{
groupCode: groupQQ,
shutUp,
},
null,
],
})
}
} }
static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) { async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
const session = getSession() const session = getSession()
return session?.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName) if (session) {
return session.getGroupService().modifyMemberCardName(groupQQ, memberUid, cardName)
} else {
return await invoke({
methodName: NTMethod.SET_MEMBER_CARD,
args: [
{
groupCode: groupQQ,
uid: memberUid,
cardName,
},
null,
],
})
}
} }
static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
const session = getSession() const session = getSession()
return session?.getGroupService().modifyMemberRole(groupQQ, memberUid, role) if (session) {
return session.getGroupService().modifyMemberRole(groupQQ, memberUid, role)
} else {
return await invoke({
methodName: NTMethod.SET_MEMBER_ROLE,
args: [
{
groupCode: groupQQ,
uid: memberUid,
role,
},
null,
],
})
}
} }
static async setGroupName(groupQQ: string, groupName: string) { async setGroupName(groupQQ: string, groupName: string) {
const session = getSession() const session = getSession()
return session?.getGroupService().modifyGroupName(groupQQ, groupName, false) if (session) {
return session.getGroupService().modifyGroupName(groupQQ, groupName, false)
} else {
return await invoke({
methodName: NTMethod.SET_GROUP_NAME,
args: [
{
groupCode: groupQQ,
groupName,
},
null,
],
})
}
} }
static async getGroupAtAllRemainCount(groupCode: string) { async getGroupAtAllRemainCount(groupCode: string) {
return await callNTQQApi< return await invoke<
GeneralCallResult & { GeneralCallResult & {
atInfo: { atInfo: {
canAtAll: boolean canAtAll: boolean
@@ -220,7 +343,7 @@ export class NTQQGroupApi {
} }
} }
>({ >({
methodName: NTQQApiMethod.GROUP_AT_ALL_REMAIN_COUNT, methodName: NTMethod.GROUP_AT_ALL_REMAIN_COUNT,
args: [ args: [
{ {
groupCode, groupCode,
@@ -230,18 +353,8 @@ export class NTQQGroupApi {
}) })
} }
static async getGroupRemainAtTimes(GroupCode: string) { /** 27187 TODO */
const session = getSession() async removeGroupEssence(GroupCode: string, msgId: string) {
return session?.getGroupService().getGroupRemainAtTimes(GroupCode)!
}
// 头衔不可用
static async setGroupTitle(groupQQ: string, uid: string, title: string) {
}
static publishGroupBulletin(groupQQ: string, title: string, content: string) { }
static async removeGroupEssence(GroupCode: string, msgId: string) {
const session = getSession() const session = getSession()
// 代码没测过 // 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
@@ -255,7 +368,8 @@ export class NTQQGroupApi {
return session?.getGroupService().removeGroupEssence(param) return session?.getGroupService().removeGroupEssence(param)
} }
static async addGroupEssence(GroupCode: string, msgId: string) { /** 27187 TODO */
async addGroupEssence(GroupCode: string, msgId: string) {
const session = getSession() const session = getSession()
// 代码没测过 // 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom

View File

@@ -1,137 +1,227 @@
import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' import { invoke, NTMethod } from '../ntcall'
import { GeneralCallResult, TmpChatInfoApi } from '../services'
import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types' import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types'
import { getSelfNick, getSelfUid } from '../../common/data'
import { getBuildVersion } from '../../common/utils'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { NTEventDispatch } from '@/common/utils/EventTask' import { NTEventDispatch } from '@/common/utils/eventTask'
import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars'
export class NTQQMsgApi { declare module 'cordis' {
static async getTempChatInfo(chatType: ChatType2, peerUid: string) { interface Context {
const session = getSession() ntMsgApi: NTQQMsgApi
return session?.getMsgService().getTempChatInfo(chatType, peerUid)! }
}
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 {
constructor(protected ctx: Context) {
super(ctx, 'ntMsgApi', true)
} }
static async prepareTempChat(toUserUid: string, GroupCode: string, nickname: string) { async getTempChatInfo(chatType: ChatType2, peerUid: string) {
//By Jadx/Ida Mlikiowa const session = getSession()
let TempGameSession = { if (session) {
nickname: '', return session.getMsgService().getTempChatInfo(chatType, peerUid)
gameAppId: '', } else {
selfTinyId: '', return await invoke<TmpChatInfoApi>({
peerRoleId: '', methodName: 'nodeIKernelMsgService/getTempChatInfo',
peerOpenId: '', args: [
{
chatType,
peerUid,
},
null,
],
})
} }
const session = getSession()
return session?.getMsgService().prepareTempChat({
chatType: ChatType2.KCHATTYPETEMPC2CFROMGROUP,
peerUid: toUserUid,
peerNickname: nickname,
fromGroupCode: GroupCode,
sig: '',
selfPhone: '',
selfUid: getSelfUid(),
gameSession: TempGameSession
})
} }
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
// 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
emojiId = emojiId.toString() emojiId = emojiId.toString()
const session = getSession() const session = getSession()
return session?.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set) if (session) {
return session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set)
} else {
return await invoke({
methodName: NTMethod.EMOJI_LIKE,
args: [
{
peer,
msgSeq,
emojiId,
emojiType: emojiId.length > 3 ? '2' : '1',
setEmoji: set,
},
null,
],
})
}
} }
static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) { async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
const session = getSession() const session = getSession()
return session?.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId)! if (session) {
return session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId)
} else {
return await invoke<GeneralCallResult & { msgList: RawMessage[] }>({
methodName: NTMethod.GET_MULTI_MSG,
args: [
{
peer,
rootMsgId,
parentMsgId,
},
null,
],
})
}
} }
static async activateChat(peer: Peer) { async activateChat(peer: Peer) {
// await this.fetchRecentContact(); return await invoke<GeneralCallResult>({
// await sleep(500); methodName: NTMethod.ACTIVE_CHAT_PREVIEW,
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.ACTIVE_CHAT_PREVIEW,
args: [{ peer, cnt: 20 }, null], args: [{ peer, cnt: 20 }, null],
}) })
} }
static async activateChatAndGetHistory(peer: Peer) { async activateChatAndGetHistory(peer: Peer) {
// await this.fetchRecentContact(); return await invoke<GeneralCallResult>({
// await sleep(500); methodName: NTMethod.ACTIVE_CHAT_HISTORY,
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.ACTIVE_CHAT_HISTORY,
// 参数似乎不是这样 // 参数似乎不是这样
args: [{ peer, cnt: 20 }, null], args: [{ peer, cnt: 20 }, null],
}) })
} }
static async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) { async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
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() const session = getSession()
//Mlikiowa 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得 if (session) {
return await session?.getMsgService().getMsgsByMsgId(peer, msgIds)! return session.getMsgService().getMsgsByMsgId(peer, msgIds)
} else {
return await invoke<GeneralCallResult & {
msgList: RawMessage[]
}>({
methodName: 'nodeIKernelMsgService/getMsgsByMsgId',
args: [
{
peer,
msgIds,
},
null,
],
})
}
} }
static async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) { async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {
const session = getSession() const session = getSession()
// 消息时间从旧到新 // 消息时间从旧到新
return session?.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder)! if (session) {
return session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder)
} else {
return await invoke<GeneralCallResult & { msgList: RawMessage[] }>({
methodName: NTMethod.HISTORY_MSG,
args: [
{
peer,
msgId,
cnt: count,
queryOrder: isReverseOrder,
},
null,
],
})
}
} }
static async recallMsg(peer: Peer, msgIds: string[]) { async recallMsg(peer: Peer, msgIds: string[]) {
const session = getSession() const session = getSession()
return await session?.getMsgService().recallMsg({ if (session) {
chatType: peer.chatType, return session.getMsgService().recallMsg({
peerUid: peer.peerUid chatType: peer.chatType,
}, msgIds) peerUid: peer.peerUid
}, msgIds)
} else {
return await invoke({
methodName: NTMethod.RECALL_MSG,
args: [
{
peer,
msgIds,
},
null,
],
})
}
} }
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
function generateMsgId() { const msgId = 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
}
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId: string
try {
msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime())
} catch (error) {
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
//兜底识别策略V2
msgId = generateMsgId()
}
peer.guildId = msgId peer.guildId = msgId
const data = await NTEventDispatch.CallNormalEvent< let msgList: RawMessage[]
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>, if (NTEventDispatch.initialised) {
(msgList: RawMessage[]) => void const data = await NTEventDispatch.CallNormalEvent<
>( (msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
'NodeIKernelMsgService/sendMsg', (msgList: RawMessage[]) => void
'NodeIKernelMsgListener/onMsgInfoListUpdate', >(
1, 'NodeIKernelMsgService/sendMsg',
timeout, 'NodeIKernelMsgListener/onMsgInfoListUpdate',
(msgRecords: RawMessage[]) => { 1,
for (let msgRecord of msgRecords) { timeout,
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) { (msgRecords: RawMessage[]) => {
return true for (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
return true
}
} }
} return false
return false },
}, '0',
'0', peer,
peer, msgElements,
msgElements, new Map()
new Map() )
) msgList = data[1]
const retMsg = data[1].find(msgRecord => { } else {
const data = await invoke<{ msgList: RawMessage[] }>({
methodName: 'nodeIKernelMsgService/sendMsg',
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false,
cmdCB: payload => {
for (const msgRecord of payload.msgList) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
return true
}
}
return false
},
args: [
{
msgId: '0',
peer,
msgElements,
msgAttributeInfos: new Map()
},
null
],
timeout
})
msgList = data.msgList
}
const retMsg = msgList.find(msgRecord => {
if (msgRecord.guildId === msgId) { if (msgRecord.guildId === msgId) {
return true return true
} }
@@ -139,103 +229,85 @@ export class NTQQMsgApi {
return retMsg! return retMsg!
} }
static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
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
}
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId: string
try {
msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime())
} catch (error) {
//if (!napCatCore.session.getMsgService()['generateMsgUniqueId'])
//兜底识别策略V2
msgId = generateMsgId().toString()
}
let data = await NTEventDispatch.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/sendMsg',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
timeout,
(msgRecords: RawMessage[]) => {
for (let msgRecord of msgRecords) {
if (msgRecord.msgId === msgId && msgRecord.sendStatus === 2) {
return true
}
}
return false
},
msgId,
peer,
msgElements,
new Map()
)
const retMsg = data[1].find(msgRecord => {
if (msgRecord.msgId === msgId) {
return true
}
})
return retMsg!
}
static async getMsgUnique(chatType: number, time: string) {
const session = getSession() const session = getSession()
if (getBuildVersion() >= 26702) { if (session) {
return session?.getMsgService().generateMsgUniqueId(chatType, time)! return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])
} else {
return await invoke<GeneralCallResult>({
methodName: NTMethod.FORWARD_MSG,
args: [
{
msgIds: msgIds,
srcContact: srcPeer,
dstContacts: [destPeer],
commentElements: [],
msgAttributeInfos: new Map(),
},
null,
],
})
} }
return session?.getMsgService().getMsgUniqueId(time)!
} }
static async getServerTime() { async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
const session = getSession() const senderShowName = await this.ctx.ntUserApi.getSelfNick(true)
return session?.getMSFService().getServerTime()!
}
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
const session = getSession()
return session?.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])!
}
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
const senderShowName = await getSelfNick()
const msgInfos = msgIds.map(id => { const msgInfos = msgIds.map(id => {
return { msgId: id, senderShowName } return { msgId: id, senderShowName }
}) })
const selfUid = getSelfUid() const selfUid = selfInfo.uid
let data = await NTEventDispatch.CallNormalEvent< let msgList: RawMessage[]
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>, if (NTEventDispatch.initialised) {
(msgList: RawMessage[]) => void const data = await NTEventDispatch.CallNormalEvent<
>( (msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>,
'NodeIKernelMsgService/multiForwardMsgWithComment', (msgList: RawMessage[]) => void
'NodeIKernelMsgListener/onMsgInfoListUpdate', >(
1, 'NodeIKernelMsgService/multiForwardMsgWithComment',
5000, 'NodeIKernelMsgListener/onMsgInfoListUpdate',
(msgRecords: RawMessage[]) => { 1,
for (let msgRecord of msgRecords) { 5000,
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) { (msgRecords: RawMessage[]) => {
return true for (let msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) {
return true
}
} }
} return false
return false },
}, msgInfos,
msgInfos, srcPeer,
srcPeer, destPeer,
destPeer, [],
[], new Map()
new Map() )
) msgList = data[1]
for (let msg of data[1]) { } else {
const data = await invoke<{ msgList: RawMessage[] }>({
methodName: 'nodeIKernelMsgService/multiForwardMsgWithComment',
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false,
cmdCB: payload => {
for (const msgRecord of payload.msgList) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) {
return true
}
}
return false
},
args: [
{
msgInfos,
srcContact: srcPeer,
dstContact: destPeer,
commentElements: [],
msgAttributeInfos: new Map(),
},
null,
],
})
msgList = data.msgList
}
for (const msg of msgList) {
const arkElement = msg.elements.find(ele => ele.arkElement) const arkElement = msg.elements.find(ele => ele.arkElement)
if (!arkElement) { if (!arkElement) {
continue continue
@@ -251,27 +323,30 @@ export class NTQQMsgApi {
throw new Error('转发消息超时') throw new Error('转发消息超时')
} }
static async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) { async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
const session = getSession() const session = getSession()
const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { if (session) {
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa return await session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z)
filterMsgType: [], } else {
filterSendersUid: [], return await invoke<GeneralCallResult & {
filterMsgToTime: '0', msgList: RawMessage[]
filterMsgFromTime: '0', }>({
isReverseOrder: false, methodName: 'nodeIKernelMsgService/getMsgsBySeqAndCount',
isIncludeCurrent: true, args: [
pageLimit: 1, {
}) peer,
return ret! cnt: count,
msgSeq: seq,
queryOrder: desc
},
null,
],
})
}
} }
static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { /** 27187 TODO */
const session = getSession() async getLastestMsgByUids(peer: Peer, count = 20, isReverseOrder = false) {
return await session?.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z)!
}
static async getLastestMsgByUids(peer: Peer, count = 20, isReverseOrder = false) {
const session = getSession() const session = getSession()
const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', '0', { const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer, chatInfo: peer,
@@ -283,11 +358,26 @@ export class NTQQMsgApi {
isIncludeCurrent: true, isIncludeCurrent: true,
pageLimit: count, pageLimit: count,
}) })
return ret! return ret
} }
static async getSingleMsg(peer: Peer, seq: string) { async getSingleMsg(peer: Peer, seq: string) {
const session = getSession() const session = getSession()
return await session?.getMsgService().getSingleMsg(peer, seq)! if (session) {
return await session.getMsgService().getSingleMsg(peer, seq)
} else {
return await invoke<GeneralCallResult & {
msgList: RawMessage[]
}>({
methodName: 'nodeIKernelMsgService/getSingleMsg',
args: [
{
peer,
msgSeq: seq,
},
null,
],
})
}
} }
} }

View File

@@ -1,142 +1,149 @@
import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' import { invoke, NTMethod } from '../ntcall'
import { SelfInfo, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '../types' import { GeneralCallResult } from '../services'
import { ReceiveCmdS } from '../hook' import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types'
import { friends, groupMembers, getSelfUin } from '@/common/data' import { getBuildVersion } from '@/common/utils'
import { CacheClassFuncAsync, log, getBuildVersion } from '@/common/utils'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { RequestUtil } from '@/common/utils/request' import { RequestUtil } from '@/common/utils/request'
import { NodeIKernelProfileService, UserDetailSource, ProfileBizType } from '../services' import { NodeIKernelProfileService, UserDetailSource, ProfileBizType, forceFetchClientKeyRetType } from '../services'
import { NodeIKernelProfileListener } from '../listeners' import { NodeIKernelProfileListener } from '../listeners'
import { NTEventDispatch } from '@/common/utils/EventTask' import { NTEventDispatch } from '@/common/utils/eventTask'
import { NTQQFriendApi } from './friend' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars'
export class NTQQUserApi { declare module 'cordis' {
static async setQQAvatar(filePath: string) { interface Context {
return await callNTQQApi<GeneralCallResult>({ ntUserApi: NTQQUserApi
methodName: NTQQApiMethod.SET_QQ_AVATAR, }
}
export class NTQQUserApi extends Service {
constructor(protected ctx: Context) {
super(ctx, 'ntUserApi', true)
}
async setQQAvatar(filePath: string) {
return await invoke<GeneralCallResult>({
methodName: NTMethod.SET_QQ_AVATAR,
args: [ args: [
{ {
path: filePath, path: filePath,
}, },
null, null,
], ],
timeoutSecond: 10, // 10秒不一定够 timeout: 10 * Time.second, // 10秒不一定够
}) })
} }
static async getSelfInfo() { async fetchUserDetailInfo(uid: string) {
return await callNTQQApi<SelfInfo>({ let info: UserDetailInfoListenerArg
className: NTQQApiClass.GLOBAL_DATA, if (NTEventDispatch.initialised) {
methodName: NTQQApiMethod.SELF_INFO, type EventService = NodeIKernelProfileService['fetchUserDetailInfo']
timeoutSecond: 2, type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged']
}) const [_retData, profile] = await NTEventDispatch.CallNormalEvent
} <EventService, EventListener>
(
static async getUserInfo(uid: string) { 'NodeIKernelProfileService/fetchUserDetailInfo',
const result = await callNTQQApi<{ profiles: Map<string, User> }>({ 'NodeIKernelProfileListener/onUserDetailInfoChanged',
methodName: NTQQApiMethod.USER_INFO, 1,
args: [{ force: true, uids: [uid] }, undefined], 5000,
cbCmd: ReceiveCmdS.USER_INFO, (profile) => profile.uid === uid,
}) 'BuddyProfileStore',
return result.profiles.get(uid) [uid],
} UserDetailSource.KSERVER,
[ProfileBizType.KALL]
/** 26702 */ )
static async fetchUserDetailInfo(uid: string) { info = profile
type EventService = NodeIKernelProfileService['fetchUserDetailInfo'] } else {
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged'] const result = await invoke<{ info: UserDetailInfoListenerArg }>({
const [_retData, profile] = await NTEventDispatch.CallNormalEvent methodName: 'nodeIKernelProfileService/fetchUserDetailInfo',
<EventService, EventListener> cbCmd: 'nodeIKernelProfileListener/onUserDetailInfoChanged',
( afterFirstCmd: false,
'NodeIKernelProfileService/fetchUserDetailInfo', cmdCB: payload => payload.info.uid === uid,
'NodeIKernelProfileListener/onUserDetailInfoChanged', args: [
1, {
5000, callFrom: 'BuddyProfileStore',
(profile) => profile.uid === uid, uid: [uid],
'BuddyProfileStore', source: UserDetailSource.KSERVER,
[uid], bizList: [ProfileBizType.KALL]
UserDetailSource.KSERVER, },
[ProfileBizType.KALL] null
) ],
const RetUser: User = { })
...profile.simpleInfo.coreInfo, info = result.info
...profile.simpleInfo.status, }
...profile.simpleInfo.vasInfo, const ret: User = {
...profile.commonExt, ...info.simpleInfo.coreInfo,
...profile.simpleInfo.baseInfo, ...info.simpleInfo.status,
qqLevel: profile.commonExt.qqLevel, ...info.simpleInfo.vasInfo,
...info.commonExt,
...info.simpleInfo.baseInfo,
qqLevel: info.commonExt?.qqLevel,
pendantId: '' pendantId: ''
} }
return RetUser return ret
} }
static async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) { async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return NTQQUserApi.fetchUserDetailInfo(uid) return this.fetchUserDetailInfo(uid)
}
if (NTEventDispatch.initialised) {
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo']
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged']
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
2,
5000,
(profile) => profile.uid === uid,
uid,
[0]
)
return profile
} else {
const result = await invoke<{ info: User }>({
methodName: 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
cbCmd: 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
afterFirstCmd: false,
cmdCB: (payload) => payload.info.uid === uid,
args: [
{
uid,
bizList: [0]
},
null,
],
})
return result.info
} }
type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo']
type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged']
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/getUserDetailInfoWithBizInfo',
'NodeIKernelProfileListener/onProfileDetailInfoChanged',
2,
5000,
(profile) => profile.uid === uid,
uid,
[0]
)
return profile
} }
// return 'p_uin=o0xxx; p_skey=orXDssiGF8axxxxxxxxxxxxxx_; skey=' async getSkey(): Promise<string> {
static async getCookieWithoutSkey() { const clientKeyData = await this.forceFetchClientKey()
return await callNTQQApi<string>({ if (clientKeyData?.result !== 0) {
className: NTQQApiClass.GROUP_HOME_WORK,
methodName: NTQQApiMethod.UPDATE_SKEY,
args: [
{
domain: 'qun.qq.com',
},
],
})
}
static async getQzoneCookies() {
const uin = getSelfUin()
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + (await NTQQUserApi.getClientKey()).clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + uin + '%2Finfocenter&keyindex=19%27'
let cookies: { [key: string]: string } = {}
try {
cookies = await RequestUtil.HttpsGetCookies(requestUrl)
} catch (e: any) {
log('获取QZone Cookies失败', e)
cookies = {}
}
return cookies
}
static async getSkey(): Promise<string> {
const clientKeyData = await NTQQUserApi.getClientKey()
if (clientKeyData.result !== 0) {
throw new Error('获取clientKey失败') throw new Error('获取clientKey失败')
} }
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + getSelfUin() const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
+ '&clientkey=' + clientKeyData.clientKey + '&clientkey=' + clientKeyData.clientKey
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex + '&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 return (await RequestUtil.HttpsGetCookies(url))?.skey
} }
@CacheClassFuncAsync(1800 * 1000) async getCookies(domain: string) {
static async getCookies(domain: string) { const clientKeyData = await this.forceFetchClientKey()
const ClientKeyData = await NTQQUserApi.forceFetchClientKey() if (clientKeyData?.result !== 0) {
const uin = getSelfUin() throw new Error('获取clientKey失败')
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + ClientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + uin + '%2Finfocenter&keyindex=19%27' }
const uin = selfInfo.uin
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + uin + '&clientkey=' + clientKeyData.clientKey + '&u1=https%3A%2F%2F' + domain + '%2F' + uin + '%2Finfocenter&keyindex=19%27'
const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl) const cookies: { [key: string]: string; } = await RequestUtil.HttpsGetCookies(requestUrl)
return cookies return cookies
} }
static genBkn(sKey: string) { genBkn(sKey: string) {
sKey = sKey || '' sKey = sKey || ''
let hash = 5381 let hash = 5381
@@ -148,54 +155,39 @@ export class NTQQUserApi {
return (hash & 0x7fffffff).toString() return (hash & 0x7fffffff).toString()
} }
static async getPSkey(domains: string[]): Promise<Map<string, string>> { async like(uid: string, count = 1) {
const session = getSession() const session = getSession()
const res = await session?.getTipOffService().getPskey(domains, true) if (session) {
if (res?.result !== 0) { return session.getProfileLikeService().setBuddyProfileLike({
throw new Error(`获取Pskey失败: ${res?.errMsg}`) friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0
})
} else {
return await invoke<GeneralCallResult & { succCounts: number }>({
methodName: 'nodeIKernelProfileLikeService/setBuddyProfileLike',
args: [
{
doLikeUserInfo: {
friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0
}
},
null,
],
})
} }
return res.domainPskeyMap
} }
static async getClientKey() { async getUidByUinV1(Uin: string) {
const session = getSession()
return await session?.getTicketService().forceFetchClientKey('')!
}
static async like(uid: string, count = 1): Promise<{ result: number, errMsg: string, succCounts: number }> {
const session = getSession()
return session?.getProfileLikeService().setBuddyProfileLike({
friendUid: uid,
sourceId: 71,
doLikeCount: count,
doLikeTollCount: 0
})!
}
static async getUidByUinV1(Uin: string) {
const session = getSession() const session = getSession()
// 通用转换开始尝试 // 通用转换开始尝试
let uid = (await session?.getUixConvertService().getUid([Uin]))?.uidInfo.get(Uin) let uid = (await session?.getUixConvertService().getUid([Uin]))?.uidInfo.get(Uin)
// Uid 好友转
if (!uid) { if (!uid) {
friends.forEach((t) => { let unveifyUid = (await this.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
if (t.uin == Uin) {
uid = t.uid
}
})
}
//Uid 群友列表转
if (!uid) {
for (let groupMembersList of groupMembers.values()) {
for (let GroupMember of groupMembersList.values()) {
if (GroupMember.uin == Uin) {
uid = GroupMember.uid
}
}
}
}
if (!uid) {
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
if (unveifyUid.indexOf('*') == -1) { if (unveifyUid.indexOf('*') == -1) {
uid = unveifyUid uid = unveifyUid
} }
@@ -203,41 +195,75 @@ export class NTQQUserApi {
return uid return uid
} }
static async getUidByUinV2(Uin: string) { async getUidByUinV2(uin: string) {
const session = getSession() const session = getSession()
let uid = (await session?.getProfileService().getUidByUin('FriendsServiceImpl', [Uin]))?.get(Uin) if (session) {
if (uid) return uid let uid = (await session.getGroupService().getUidByUins([uin])).uids.get(uin)
uid = (await session?.getGroupService().getUidByUins([Uin]))?.uids.get(Uin) if (uid) return uid
if (uid) return uid uid = (await session.getProfileService().getUidByUin('FriendsServiceImpl', [uin])).get(uin)
uid = (await session?.getUixConvertService().getUid([Uin]))?.uidInfo.get(Uin) if (uid) return uid
if (uid) return uid uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin)
console.log((await NTQQFriendApi.getBuddyIdMapCache(true))) if (uid) return uid
uid = (await NTQQFriendApi.getBuddyIdMapCache(true)).getValue(Uin)//从Buddy缓存获取Uid } else {
if (uid) return uid let uid = (await invoke<{ uids: Map<string, string> }>({
uid = (await NTQQFriendApi.getBuddyIdMap(true)).getValue(Uin) methodName: 'nodeIKernelGroupService/getUidByUins',
if (uid) return uid args: [
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUinV2(Uin)).detail.uid//从QQ Native 特殊转换 { uin: [uin] },
if (unveifyUid.indexOf('*') == -1) uid = unveifyUid null,
//if (uid) return uid ],
return uid })).uids.get(uin)
} if (uid) return uid
uid = (await invoke<Map<string, string>>({
static async getUidByUin(Uin: string) { methodName: 'nodeIKernelProfileService/getUidByUin',
if (getBuildVersion() >= 26702) { args: [
return await NTQQUserApi.getUidByUinV2(Uin) {
callFrom: 'FriendsServiceImpl',
uin: [uin],
},
null,
],
})).get(uin)
if (uid) return uid
uid = (await invoke<{ uidInfo: Map<string, string> }>({
methodName: 'nodeIKernelUixConvertService/getUid',
args: [
{ uin: [uin] },
null,
],
})).uidInfo.get(uin)
if (uid) return uid
} }
return await NTQQUserApi.getUidByUinV1(Uin) const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) return unveifyUid
} }
static async getUserDetailInfoByUinV2(Uin: string) { async getUidByUin(Uin: string) {
return await NTEventDispatch.CallNoListenerEvent if (getBuildVersion() >= 26702) {
<(Uin: string) => Promise<UserDetailInfoByUinV2>>( return await this.getUidByUinV2(Uin)
'NodeIKernelProfileService/getUserDetailInfoByUin', }
5000, return await this.getUidByUinV1(Uin)
Uin
)
} }
static async getUserDetailInfoByUin(Uin: string) {
async getUserDetailInfoByUinV2(uin: string) {
if (NTEventDispatch.initialised) {
return await NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUinV2>>(
'NodeIKernelProfileService/getUserDetailInfoByUin',
5000,
uin
)
} else {
return await invoke<UserDetailInfoByUinV2>({
methodName: 'nodeIKernelProfileService/getUserDetailInfoByUin',
args: [
{ uin },
null,
],
})
}
}
async getUserDetailInfoByUin(Uin: string) {
return NTEventDispatch.CallNoListenerEvent return NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUin>>( <(Uin: string) => Promise<UserDetailInfoByUin>>(
'NodeIKernelProfileService/getUserDetailInfoByUin', 'NodeIKernelProfileService/getUserDetailInfoByUin',
@@ -246,7 +272,7 @@ export class NTQQUserApi {
) )
} }
static async getUinByUidV1(Uid: string) { async getUinByUidV1(Uid: string) {
const ret = await NTEventDispatch.CallNoListenerEvent const ret = await NTEventDispatch.CallNoListenerEvent
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>( <(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>(
'NodeIKernelUixConvertService/getUin', 'NodeIKernelUixConvertService/getUin',
@@ -255,45 +281,84 @@ export class NTQQUserApi {
) )
let uin = ret.uinInfo.get(Uid) let uin = ret.uinInfo.get(Uid)
if (!uin) { if (!uin) {
//从Buddy缓存获取Uin uin = (await this.getUserDetailInfo(Uid)).uin //从QQ Native 转换
friends.forEach((t) => { }
if (t.uid == Uid) { return uin
uin = t.uin }
}
async getUinByUidV2(uid: string) {
const session = getSession()
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
return uin
} else {
let uin = (await invoke<{ uins: Map<string, string> }>({
methodName: 'nodeIKernelGroupService/getUinByUids',
args: [
{ uid: [uid] },
null,
],
})).uins.get(uid)
if (uin) return uin
uin = (await invoke<Map<string, string>>({
methodName: 'nodeIKernelProfileService/getUinByUid',
args: [
{
callFrom: 'FriendsServiceImpl',
uid: [uid],
},
null,
],
})).get(uid)
if (uin) return uin
uin = (await invoke<{ uinInfo: Map<string, string> }>({
methodName: 'nodeIKernelUixConvertService/getUin',
args: [
{ uid: [uid] },
null,
],
})).uinInfo.get(uid)
if (uin) return uin
}
let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).getKey(uid)
if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
}
async getUinByUid(Uid: string) {
if (getBuildVersion() >= 26702) {
return (await this.getUinByUidV2(Uid))!
}
return await this.getUinByUidV1(Uid)
}
async forceFetchClientKey() {
const session = getSession()
if (session) {
return await session.getTicketService().forceFetchClientKey('')
} else {
return await invoke<forceFetchClientKeyRetType>({
methodName: 'nodeIKernelTicketService/forceFetchClientKey',
args: [{
domain: ''
}, null],
}) })
} }
if (!uin) { }
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin //从QQ Native 转换
async getSelfNick(refresh = false) {
if ((refresh || !selfInfo.nick) && selfInfo.uid) {
const userInfo = await this.getUserDetailInfo(selfInfo.uid)
if (userInfo) {
Object.assign(selfInfo, { nick: userInfo.nick })
return userInfo.nick
}
} }
return uin return selfInfo.nick
}
static async getUinByUidV2(Uid: string) {
const session = getSession()
let uin = (await session?.getProfileService().getUinByUid('FriendsServiceImpl', [Uid]))?.get(Uid)
if (uin) return uin
uin = (await session?.getGroupService().getUinByUids([Uid]))?.uins.get(Uid)
if (uin) return uin
uin = (await session?.getUixConvertService().getUin([Uid]))?.uinInfo.get(Uid)
if (uin) return uin
uin = (await NTQQFriendApi.getBuddyIdMapCache(true)).getKey(Uid) //从Buddy缓存获取Uin
if (uin) return uin
uin = (await NTQQFriendApi.getBuddyIdMap(true)).getKey(Uid)
if (uin) return uin
uin = (await NTQQUserApi.getUserDetailInfo(Uid)).uin //从QQ Native 转换
return uin
}
static async getUinByUid(Uid: string) {
if (getBuildVersion() >= 26702) {
return await NTQQUserApi.getUinByUidV2(Uid)
}
return await NTQQUserApi.getUinByUidV1(Uid)
}
@CacheClassFuncAsync(3600 * 1000, 'ClientKey')
static async forceFetchClientKey() {
const session = getSession()
return await session?.getTicketService().forceFetchClientKey('')!
} }
} }

View File

@@ -1,8 +1,11 @@
import { getSelfUin } from '@/common/data'
import { log } from '@/common/utils/log'
import { NTQQUserApi } from './user'
import { RequestUtil } from '@/common/utils/request' import { RequestUtil } from '@/common/utils/request'
import { CacheClassFuncAsync } from '@/common/utils/helper' import { Service, Context } from 'cordis'
declare module 'cordis' {
interface Context {
ntWebApi: NTQQWebApi
}
}
export enum WebHonorType { export enum WebHonorType {
ALL = 'all', ALL = 'all',
@@ -121,26 +124,14 @@ export interface GroupEssenceMsgRet {
} }
} }
export class WebApi { export class NTQQWebApi extends Service {
static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet | undefined> { constructor(protected ctx: Context) {
const { cookies: CookieValue, bkn: Bkn } = (await NTQQUserApi.getCookies('qun.qq.com')) super(ctx, 'ntWebApi', true)
const url = 'https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=' + Bkn + '&group_code=' + GroupCode + '&page_start=' + page_start + '&page_limit=20'
let ret: GroupEssenceMsgRet
try {
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(url, 'GET', '', { 'Cookie': CookieValue })
} catch {
return undefined
}
//console.log(url, CookieValue)
if (ret.retcode !== 0) {
return undefined
}
return ret
} }
static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> { async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>() const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
const cookieObject = await NTQQUserApi.getCookies('qun.qq.com') const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ') const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
const retList: Promise<WebApiGroupMemberRet>[] = [] const retList: Promise<WebApiGroupMemberRet>[] = []
const params = new URLSearchParams({ const params = new URLSearchParams({
@@ -148,7 +139,7 @@ export class WebApi {
end: '40', end: '40',
sort: '1', sort: '1',
gc: GroupCode, gc: GroupCode,
bkn: WebApi.genBkn(cookieObject.skey) 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 }) 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) { if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
@@ -179,65 +170,7 @@ export class WebApi {
return memberData return memberData
} }
// public static async addGroupDigest(groupCode: string, msgSeq: string) { genBkn(sKey: string) {
// const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`;
// const res = await this.request(url);
// return await res.json();
// }
// public async getGroupDigest(groupCode: string) {
// const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`;
// const res = await this.request(url);
// return await res.json();
// }
static async setGroupNotice(GroupCode: string, Content: string = '') {
//https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=${bkn}
//qid=${群号}&bkn=${bkn}&text=${内容}&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com']
const _Skey = await NTQQUserApi.getSkey()
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + getSelfUin()
let ret: any = undefined
//console.log(CookieValue)
if (!_Skey || !_Pskey) {
//获取Cookies失败
return undefined
}
const Bkn = WebApi.genBkn(_Skey)
const data = 'qid=' + GroupCode + '&bkn=' + Bkn + '&text=' + Content + '&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}'
const url = 'https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=' + Bkn
try {
ret = await RequestUtil.HttpGetJson<any>(url, 'GET', '', { 'Cookie': CookieValue })
return ret
} catch (e) {
return undefined
}
}
static async getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com']
const _Skey = await NTQQUserApi.getSkey()
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + getSelfUin()
let ret: WebApiGroupNoticeRet | undefined = undefined
//console.log(CookieValue)
if (!_Skey || !_Pskey) {
//获取Cookies失败
return undefined
}
const Bkn = WebApi.genBkn(_Skey)
const url = 'https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=' + Bkn + '&qid=' + GroupCode + '&ft=23&ni=1&n=1&i=1&log_read=1&platform=1&s=-1&n=20'
try {
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(url, 'GET', '', { 'Cookie': CookieValue })
if (ret?.ec !== 0) {
return undefined
}
return ret
} catch (e) {
return undefined
}
}
static genBkn(sKey: string) {
sKey = sKey || ''; sKey = sKey || '';
let hash = 5381; let hash = 5381;
@@ -250,13 +183,13 @@ export class WebApi {
} }
//实现未缓存 考虑2h缓存 //实现未缓存 考虑2h缓存
static async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
async function getDataInternal(Internal_groupCode: string, Internal_type: number) { const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString(); let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString();
let res = ''; let res = '';
let resJson; let resJson;
try { try {
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': CookieValue }); res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr });
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/); const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
if (match) { if (match) {
resJson = JSON.parse(match[1].trim()); resJson = JSON.parse(match[1].trim());
@@ -267,13 +200,14 @@ export class WebApi {
return resJson?.actorList; return resJson?.actorList;
} }
} catch (e) { } catch (e) {
log('获取当前群荣耀失败', url, e); this.ctx.logger.error('获取当前群荣耀失败', url, e);
} }
return undefined; return undefined;
} }
let HonorInfo: any = { group_id: groupCode }; let HonorInfo: any = { group_id: groupCode };
const CookieValue = (await NTQQUserApi.getCookies('qun.qq.com')).cookies; const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) { if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
try { try {
@@ -299,7 +233,7 @@ export class WebApi {
}); });
} }
} catch (e) { } catch (e) {
log(e); this.ctx.logger.error(e);
} }
} }
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
@@ -318,7 +252,7 @@ export class WebApi {
}); });
} }
} catch (e) { } catch (e) {
log(e); this.ctx.logger.error(e);
} }
} }
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
@@ -337,7 +271,7 @@ export class WebApi {
}); });
} }
} catch (e) { } catch (e) {
log('获取群聊炽焰失败', e); this.ctx.logger.error('获取群聊炽焰失败', e);
} }
} }
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
@@ -356,7 +290,7 @@ export class WebApi {
}); });
} }
} catch (e) { } catch (e) {
log('获取快乐源泉失败', e); this.ctx.logger.error('获取快乐源泉失败', e);
} }
} }
//冒尖小春笋好像已经被tx扬了 //冒尖小春笋好像已经被tx扬了

View File

@@ -1,38 +1,50 @@
import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services'
import { ReceiveCmd } from '../hook' import { ReceiveCmd } from '../hook'
import { BrowserWindow } from 'electron' import { BrowserWindow } from 'electron'
import { Service, Context } from 'cordis'
declare module 'cordis' {
interface Context {
ntWindowApi: NTQQWindowApi
}
}
export interface NTQQWindow { export interface NTQQWindow {
windowName: string windowName: string
windowUrlHash: string windowUrlHash: string
} }
export class NTQQWindows { export namespace NTQQWindows {
static GroupHomeWorkWindow: NTQQWindow = { export const GroupHomeWorkWindow: NTQQWindow = {
windowName: 'GroupHomeWorkWindow', windowName: 'GroupHomeWorkWindow',
windowUrlHash: '#/group-home-work', windowUrlHash: '#/group-home-work',
} }
static GroupNotifyFilterWindow: NTQQWindow = { export const GroupNotifyFilterWindow: NTQQWindow = {
windowName: 'GroupNotifyFilterWindow', windowName: 'GroupNotifyFilterWindow',
windowUrlHash: '#/group-notify-filter', windowUrlHash: '#/group-notify-filter',
} }
static GroupEssenceWindow: NTQQWindow = { export const GroupEssenceWindow: NTQQWindow = {
windowName: 'GroupEssenceWindow', windowName: 'GroupEssenceWindow',
windowUrlHash: '#/group-essence', windowUrlHash: '#/group-essence',
} }
} }
export class NTQQWindowApi { export class NTQQWindowApi extends Service {
constructor(protected ctx: Context) {
super(ctx, 'ntWindowApi', true)
}
// 打开窗口并获取对应的下发事件 // 打开窗口并获取对应的下发事件
static async openWindow<R = GeneralCallResult>( async openWindow<R = GeneralCallResult>(
ntQQWindow: NTQQWindow, ntQQWindow: NTQQWindow,
args: any[], args: any[],
cbCmd: ReceiveCmd | null = null, cbCmd: ReceiveCmd | undefined,
autoCloseSeconds: number = 2, autoCloseSeconds: number = 2,
) { ) {
const result = await callNTQQApi<R>({ const result = await invoke<R>({
className: NTQQApiClass.WINDOW_API, className: NTClass.WINDOW_API,
methodName: NTQQApiMethod.OPEN_EXTRA_WINDOW, methodName: NTMethod.OPEN_EXTRA_WINDOW,
cbCmd, cbCmd,
afterFirstCmd: false, afterFirstCmd: false,
args: [ntQQWindow.windowName, ...args], args: [ntQQWindow.windowName, ...args],

View File

@@ -15,22 +15,21 @@ import {
} from './types' } from './types'
import { promises as fs } from 'node:fs' import { promises as fs } from 'node:fs'
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import { NTQQFileApi } from './api/file'
import { calculateFileMD5, isGIF } from '../common/utils/file' import { calculateFileMD5, isGIF } from '../common/utils/file'
import { log } from '../common/utils/log'
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 { isNull } from '../common/utils' import faceConfig from './helper/face_config.json'
import faceConfig from './face_config.json' import { Context } from 'cordis'
import { isNullable } from 'cosmokit'
export const mFaceCache = new Map<string, string>() // emojiId -> faceName export const mFaceCache = new Map<string, string>() // emojiId -> faceName
export class SendMsgElementConstructor { export namespace SendMsgElementConstructor {
static poke(groupCode: string, uin: string) { export function poke(groupCode: string, uin: string) {
return null return null
} }
static text(content: string): SendTextElement { export function text(content: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.TEXT,
elementId: '', elementId: '',
@@ -44,7 +43,7 @@ export class SendMsgElementConstructor {
} }
} }
static 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: '',
@@ -58,7 +57,7 @@ export class SendMsgElementConstructor {
} }
} }
static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { export function reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement {
return { return {
elementType: ElementType.REPLY, elementType: ElementType.REPLY,
elementId: '', elementId: '',
@@ -71,8 +70,8 @@ export class SendMsgElementConstructor {
} }
} }
static async pic(picPath: string, summary: string = '', subType: 0 | 1 = 0): Promise<SendPicElement> { export async function pic(ctx: Context, picPath: string, summary: string = '', subType: 0 | 1 = 0): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await NTQQFileApi.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'
} }
@@ -80,7 +79,7 @@ export class SendMsgElementConstructor {
if (fileSize > 1024 * 1024 * 30) { if (fileSize > 1024 * 1024 * 30) {
throw `图片过大,最大支持${maxMB}MB当前文件大小${fileSize}B` throw `图片过大,最大支持${maxMB}MB当前文件大小${fileSize}B`
} }
const imageSize = await NTQQFileApi.getImageSize(picPath) const imageSize = await ctx.ntFileApi.getImageSize(picPath)
const picElement = { const picElement = {
md5HexStr: md5, md5HexStr: md5,
fileSize: fileSize.toString(), fileSize: fileSize.toString(),
@@ -96,7 +95,7 @@ export class SendMsgElementConstructor {
thumbFileSize: 0, thumbFileSize: 0,
summary, summary,
} }
log('图片信息', picElement) ctx.logger.info('图片信息', picElement)
return { return {
elementType: ElementType.PIC, elementType: ElementType.PIC,
elementId: '', elementId: '',
@@ -104,8 +103,8 @@ export class SendMsgElementConstructor {
} }
} }
static async file(filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> { export async function file(ctx: Context, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
const { fileName: _fileName, path, fileSize } = await NTQQFileApi.uploadFile(filePath, ElementType.FILE) const { fileName: _fileName, path, fileSize } = await ctx.ntFileApi.uploadFile(filePath, ElementType.FILE)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常,大小为 0' throw '文件异常,大小为 0'
} }
@@ -122,16 +121,16 @@ export class SendMsgElementConstructor {
return element return element
} }
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> { export async function video(ctx: Context, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
try { try {
await fs.stat(filePath) await fs.stat(filePath)
} catch (e) { } catch (e) {
throw `文件${filePath}异常,不存在` throw `文件${filePath}异常,不存在`
} }
log('复制视频到QQ目录', filePath) ctx.logger.info('复制视频到QQ目录', filePath)
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO) let { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO)
log('复制视频到QQ目录完成', path) ctx.logger.info('复制视频到QQ目录完成', path)
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
@@ -153,19 +152,19 @@ export class SendMsgElementConstructor {
} }
try { try {
videoInfo = await getVideoInfo(path) videoInfo = await getVideoInfo(path)
log('视频信息', videoInfo) ctx.logger.info('视频信息', videoInfo)
} catch (e) { } catch (e) {
log('获取视频信息失败', e) ctx.logger.info('获取视频信息失败', e)
} }
const createThumb = new Promise<string>((resolve, reject) => { const createThumb = new Promise<string>((resolve, reject) => {
const thumbFileName = `${md5}_0.png` const thumbFileName = `${md5}_0.png`
const thumbPath = pathLib.join(thumbDir, thumbFileName) const thumbPath = pathLib.join(thumbDir, thumbFileName)
log('开始生成视频缩略图', filePath) ctx.logger.info('开始生成视频缩略图', filePath)
let completed = false let completed = false
function useDefaultThumb() { function useDefaultThumb() {
if (completed) return if (completed) return
log('获取视频封面失败,使用默认封面') ctx.logger.info('获取视频封面失败,使用默认封面')
fs.writeFile(thumbPath, defaultVideoThumb) fs.writeFile(thumbPath, defaultVideoThumb)
.then(() => { .then(() => {
resolve(thumbPath) resolve(thumbPath)
@@ -194,14 +193,14 @@ export class SendMsgElementConstructor {
size: videoInfo.width + 'x' + videoInfo.height, size: videoInfo.width + 'x' + videoInfo.height,
}) })
.on('end', () => { .on('end', () => {
log('生成视频缩略图', thumbPath) ctx.logger.info('生成视频缩略图', thumbPath)
completed = true completed = true
resolve(thumbPath) resolve(thumbPath)
}) })
}) })
let thumbPath = new Map() let thumbPath = new Map()
const _thumbPath = await createThumb const _thumbPath = await createThumb
log('生成视频缩略图', _thumbPath) ctx.logger.info('生成视频缩略图', _thumbPath)
const thumbSize = (await fs.stat(_thumbPath)).size const thumbSize = (await fs.stat(_thumbPath)).size
// log("生成缩略图", _thumbPath) // log("生成缩略图", _thumbPath)
thumbPath.set(0, _thumbPath) thumbPath.set(0, _thumbPath)
@@ -232,17 +231,17 @@ export class SendMsgElementConstructor {
// sourceVideoCodecFormat: 2 // sourceVideoCodecFormat: 2
}, },
} }
log('videoElement', element) ctx.logger.info('videoElement', element)
return element return element
} }
static async ptt(pttPath: string): Promise<SendPttElement> { export async function ptt(ctx: Context, pttPath: string): Promise<SendPttElement> {
const { converted, path: silkPath, duration } = await encodeSilk(pttPath) const { converted, path: silkPath, duration } = await encodeSilk(ctx, pttPath)
if (!silkPath) { if (!silkPath) {
throw '语音转换失败, 请检查语音文件是否正常' throw '语音转换失败, 请检查语音文件是否正常'
} }
// log("生成语音", silkPath, duration); // log("生成语音", silkPath, duration);
const { md5, fileName, path, fileSize } = await NTQQFileApi.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'
} }
@@ -271,7 +270,7 @@ export class SendMsgElementConstructor {
} }
} }
static face(faceId: number): SendFaceElement { export function face(faceId: number): SendFaceElement {
// 从face_config.json中获取表情名称 // 从face_config.json中获取表情名称
const sysFaces = faceConfig.sysface const sysFaces = faceConfig.sysface
const emojiFaces = faceConfig.emoji const emojiFaces = faceConfig.emoji
@@ -300,10 +299,12 @@ export class SendMsgElementConstructor {
} }
} }
static mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement { export function mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement {
return { return {
elementType: ElementType.MFACE, elementType: ElementType.MFACE,
marketFaceElement: { marketFaceElement: {
imageWidth: 300,
imageHeight: 300,
emojiPackageId, emojiPackageId,
emojiId, emojiId,
key, key,
@@ -312,11 +313,11 @@ export class SendMsgElementConstructor {
} }
} }
static dice(resultId: number | null): SendFaceElement { export function dice(resultId: number | null): SendFaceElement {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
// 随机1到6 // 随机1到6
if (isNull(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: '',
@@ -336,9 +337,9 @@ export class SendMsgElementConstructor {
} }
// 猜拳(石头剪刀布)表情 // 猜拳(石头剪刀布)表情
static rps(resultId: number | null): SendFaceElement { export function rps(resultId: number | null): SendFaceElement {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
if (isNull(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: '',
@@ -357,7 +358,7 @@ export class SendMsgElementConstructor {
} }
} }
static ark(data: string): SendArkElement { export function ark(data: string): SendArkElement {
return { return {
elementType: ElementType.ARK, elementType: ElementType.ARK,
elementId: '', elementId: '',

240
src/ntqqapi/core.ts Normal file
View File

@@ -0,0 +1,240 @@
import fs from 'node:fs'
import { Service, Context } from 'cordis'
import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook'
import { MessageUnique } from '../common/utils/messageUnique'
import { NTEventDispatch } from '../common/utils/eventTask'
import { wrapperConstructor, getSession } from './wrapper'
import { Config as LLOBConfig } from '../common/types'
import { llonebotError } from '../common/globalVars'
import { isNumeric } from '../common/utils/misc'
import { NTMethod } from './ntcall'
import {
RawMessage,
GroupNotify,
FriendRequestNotify,
FriendRequest,
GroupMember,
CategoryFriend,
SimpleInfo,
User,
ChatType
} from './types'
import { selfInfo } from '../common/globalVars'
import { version } from '../version'
declare module 'cordis' {
interface Context {
app: Core
}
interface Events {
'nt/message-created': (input: RawMessage[]) => void
'nt/message-deleted': (input: RawMessage[]) => void
'nt/message-sent': (input: RawMessage[]) => void
'nt/group-notify': (input: GroupNotify[]) => void
'nt/friend-request': (input: FriendRequest[]) => void
'nt/group-member-info-updated': (input: { groupCode: string; members: GroupMember[] }) => void
'nt/friend-list-updated': (input: { groupCode: string; members: GroupMember[] }) => void
}
}
class Core extends Service {
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi']
constructor(protected ctx: Context, public config: Core.Config) {
super(ctx, 'app', true)
}
public start() {
llonebotError.otherError = ''
const WrapperSession = getSession()
if (WrapperSession) {
NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession })
}
MessageUnique.init(selfInfo.uin)
this.registerListener()
this.ctx.logger.info(`LLOneBot/${version}`)
}
private registerListener() {
registerReceiveHook<{
data: CategoryFriend[]
}>(ReceiveCmdS.FRIENDS, (payload) => {
type V2data = { userSimpleInfos: Map<string, SimpleInfo> }
let friendList: User[] = [];
if ((payload as any).userSimpleInfos) {
friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => {
return {
...v.coreInfo,
}
})
} else {
for (const fData of payload.data) {
friendList.push(...fData.buddyList)
}
}
this.ctx.logger.info('好友列表变动', friendList.length)
for (const friend of friendList) {
this.ctx.ntMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend })
}
})
// 自动清理新消息文件
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
if (!this.config.autoDeleteFile) {
return
}
for (const message of payload.msgList) {
for (const msgElement of message.elements) {
setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.filePath
const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!]
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath))
}
for (const path of pathList) {
if (path) {
fs.unlink(picPath, () => {
this.ctx.logger.info('删除文件成功', path)
})
}
}
}, this.config.autoDeleteFileSecond! * 1000)
}
}
})
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
Object.assign(selfInfo, { online: info.info.status !== 20 })
})
const activatedPeerUids: string[] = []
registerReceiveHook<{
changedRecentContactLists: {
listType: number
sortedContactList: string[]
changedList: {
id: string // peerUid
chatType: ChatType
}[]
}[]
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
for (const recentContact of payload.changedRecentContactLists) {
for (const changedContact of recentContact.changedList) {
if (activatedPeerUids.includes(changedContact.id)) continue
activatedPeerUids.push(changedContact.id)
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
if (changedContact.chatType === ChatType.temp) {
this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => {
this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
const lastTempMsg = msgList.at(-1)
if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) {
this.ctx.parallel('nt/message-created', [lastTempMsg!])
}
})
})
}
else {
this.ctx.ntMsgApi.activateChat(peer)
}
}
}
})
registerCallHook(NTMethod.DELETE_ACTIVE_CHAT, async (payload) => {
const peerUid = payload[0] as string
this.ctx.logger.info('激活的聊天窗口被删除,准备重新激活', peerUid)
let chatType = ChatType.friend
if (isNumeric(peerUid)) {
chatType = ChatType.group
}
else if (!(await this.ctx.ntFriendApi.isBuddy(peerUid))) {
chatType = ChatType.temp
}
const peer = { peerUid, chatType }
await this.ctx.sleep(1000)
this.ctx.ntMsgApi.activateChat(peer).then((r) => {
this.ctx.logger.info('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
})
})
registerReceiveHook<{
groupCode: string
dataSource: number
members: Set<GroupMember>
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
const groupCode = payload.groupCode
const members = Array.from(payload.members.values())
this.ctx.parallel('nt/group-member-info-updated', { groupCode, members })
})
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => {
this.ctx.parallel('nt/message-created', payload.msgList)
})
const recallMsgIds: string[] = [] // 避免重复上报
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => {
const list = payload.msgList.filter(v => {
if (recallMsgIds.includes(v.msgId)) {
return false
}
recallMsgIds.push(v.msgId)
return true
})
this.ctx.parallel('nt/message-deleted', list)
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => {
const { msgId, chatType, peerUid } = payload.msgRecord
const peer = {
chatType,
peerUid
}
MessageUnique.createMsg(peer, msgId)
if (!this.config.reportSelfMessage) {
return
}
this.ctx.parallel('nt/message-sent', [payload.msgRecord])
})
const groupNotifyFlags: string[] = []
registerReceiveHook<{
doubt: boolean
oldestUnreadSeq: string
unreadCount: number
}>(ReceiveCmdS.UNREAD_GROUP_NOTIFY, async (payload) => {
if (payload.unreadCount) {
let notifies: GroupNotify[]
try {
notifies = (await this.ctx.ntGroupApi.getSingleScreenNotifies(14)).slice(0, payload.unreadCount)
} catch (e) {
return
}
const list = notifies.filter(v => {
const flag = v.group.groupCode + '|' + v.seq + '|' + v.type
if (groupNotifyFlags.includes(flag)) {
return false
}
groupNotifyFlags.push(flag)
return true
})
this.ctx.parallel('nt/group-notify', list)
}
})
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => {
this.ctx.parallel('nt/friend-request', payload.data.buddyReqs)
})
}
}
namespace Core {
export interface Config extends LLOBConfig {
}
}
export default Core

View File

@@ -1,6 +1,4 @@
//远端rkey获取 import { Context } from "cordis"
import { log } from '@/common/utils'
interface ServerRkeyData { interface ServerRkeyData {
group_rkey: string group_rkey: string
@@ -8,15 +6,15 @@ interface ServerRkeyData {
expired_time: number expired_time: number
} }
class RkeyManager { export class RkeyManager {
serverUrl: string = '' private serverUrl: string = ''
private rkeyData: ServerRkeyData = { private rkeyData: ServerRkeyData = {
group_rkey: '', group_rkey: '',
private_rkey: '', private_rkey: '',
expired_time: 0 expired_time: 0
} }
constructor(serverUrl: string) { constructor(protected ctx: Context, serverUrl: string) {
this.serverUrl = serverUrl this.serverUrl = serverUrl
} }
@@ -25,7 +23,7 @@ class RkeyManager {
try { try {
await this.refreshRkey() await this.refreshRkey()
} catch (e) { } catch (e) {
log('获取rkey失败', e) this.ctx.logger.error('获取rkey失败', e)
} }
} }
return this.rkeyData return this.rkeyData
@@ -60,5 +58,3 @@ class RkeyManager {
}) })
} }
} }
export const rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey')

View File

@@ -1,34 +1,11 @@
import type { BrowserWindow } from 'electron' import type { BrowserWindow } from 'electron'
import { NTQQApiClass, NTQQApiMethod } from './ntcall' import { NTClass, NTMethod } from './ntcall'
import { NTQQMsgApi } from './api/msg'
import {
CategoryFriend,
ChatType,
GroupMember,
GroupMemberRole,
RawMessage,
SimpleInfo, User,
} from './types'
import {
friends,
getFriend,
getGroupMember,
setSelfInfo
} from '@/common/data'
import { postOb11Event } from '../onebot11/server/post-ob11-event'
import { getConfigUtil } from '@/common/config'
import fs from 'node:fs'
import { log } from '@/common/utils' import { log } from '@/common/utils'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { MessageUnique } from '../common/utils/MessageUnique'
import { isNumeric, sleep } from '@/common/utils'
import { OB11Constructor } from '../onebot11/constructor'
import { OB11GroupCardEvent } from '../onebot11/event/notice/OB11GroupCardEvent'
import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent'
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export const hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
export let ReceiveCmdS = { export const ReceiveCmdS = {
RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2', RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2',
UPDATE_MSG: 'nodeIKernelMsgListener/onMsgInfoListUpdate', UPDATE_MSG: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
UPDATE_ACTIVE_MSG: 'nodeIKernelMsgListener/onActiveMsgInfoUpdate', UPDATE_ACTIVE_MSG: 'nodeIKernelMsgListener/onActiveMsgInfoUpdate',
@@ -51,41 +28,43 @@ export let ReceiveCmdS = {
SKEY_UPDATE: 'onSkeyUpdate', SKEY_UPDATE: 'onSkeyUpdate',
} }
export type ReceiveCmd = (typeof ReceiveCmdS)[keyof typeof ReceiveCmdS] export type ReceiveCmd = string
interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> { interface NTQQApiReturnData<Payload = unknown> extends Array<any> {
0: { 0: {
type: 'request' type: 'request'
eventName: NTQQApiClass eventName: NTClass
callbackId?: string callbackId?: string
} }
1: { 1: {
cmdName: ReceiveCmd cmdName: ReceiveCmd
cmdType: 'event' cmdType: 'event'
payload: PayloadType payload: Payload
}[] }[]
} }
let receiveHooks: Array<{ const logHook = false
const receiveHooks: Array<{
method: ReceiveCmd[] method: ReceiveCmd[]
hookFunc: (payload: any) => void | Promise<void> hookFunc: (payload: any) => void | Promise<void>
id: string id: string
}> = [] }> = []
let callHooks: Array<{ const callHooks: Array<{
method: NTQQApiMethod[] method: NTMethod[]
hookFunc: (callParams: unknown[]) => void | Promise<void> hookFunc: (callParams: unknown[]) => void | Promise<void>
}> = [] }> = []
export function hookNTQQApiReceive(window: BrowserWindow) { export function hookNTQQApiReceive(window: BrowserWindow) {
const originalSend = window.webContents.send const originalSend = window.webContents.send
const patchSend = (channel: string, ...args: NTQQApiReturnData) => { const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
/*try { try {
const isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi') const isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi')
if (!isLogger) { if (logHook && !isLogger) {
log(`received ntqq api message: ${channel}`, args) log(`received ntqq api message: ${channel}`, args)
} }
} catch { }*/ } catch { }
if (args?.[1] instanceof Array) { if (args?.[1] instanceof Array) {
for (const receiveData of args?.[1]) { for (const receiveData of args?.[1]) {
const ntQQApiMethodName = receiveData.cmdName const ntQQApiMethodName = receiveData.cmdName
@@ -134,24 +113,22 @@ export function hookNTQQApiCall(window: BrowserWindow) {
isLogger = args[3][0].eventName.startsWith('ns-LoggerApi') isLogger = args[3][0].eventName.startsWith('ns-LoggerApi')
} catch (e) { } } catch (e) { }
if (!isLogger) { if (!isLogger) {
/*try { try {
HOOK_LOG && log('call NTQQ api', thisArg, args) logHook && log('call NTQQ api', thisArg, args)
} catch (e) { }*/ } catch (e) { }
try { try {
const _args: unknown[] = args[3][1] const _args: unknown[] = args[3][1]
const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod const cmdName: NTMethod = _args[0] as NTMethod
const callParams = _args.slice(1) const callParams = _args.slice(1)
callHooks.forEach((hook) => { callHooks.forEach((hook) => {
if (hook.method.includes(cmdName)) { if (hook.method.includes(cmdName)) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
try { try {
let _ = hook.hookFunc(callParams) hook.hookFunc(callParams)
if (hook.hookFunc.constructor.name === 'AsyncFunction') { } catch (e: any) {
(_ as Promise<void>).then()
}
} catch (e) {
log('hook call error', e, _args) log('hook call error', e, _args)
} }
resolve(undefined)
}).then() }).then()
} }
}) })
@@ -207,7 +184,7 @@ export function registerReceiveHook<PayloadType>(
} }
export function registerCallHook( export function registerCallHook(
method: NTQQApiMethod | NTQQApiMethod[], method: NTMethod | NTMethod[],
hookFunc: (callParams: unknown[]) => void | Promise<void>, hookFunc: (callParams: unknown[]) => void | Promise<void>,
): void { ): void {
if (!Array.isArray(method)) { if (!Array.isArray(method)) {
@@ -222,300 +199,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)
} }
//let activatedGroups: string[] = []
/*async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
for (let group of _groups) {
log('update group', group.groupCode)
if (group.privilegeFlag === 0) {
deleteGroup(group.groupCode)
continue
}
//log('update group', group)
NTQQMsgApi.activateChat({ peerUid: group.groupCode, chatType: ChatType.group }).then().catch(log)
let existGroup = groups.find((g) => g.groupCode == group.groupCode)
if (existGroup) {
Object.assign(existGroup, group)
} else {
groups.push(group)
existGroup = group
}
if (needUpdate) {
const members = await NTQQGroupApi.getGroupMembers(group.groupCode)
if (members) {
existGroup.members = Array.from(members.values())
}
}
}
}*/
/*async function processGroupEvent(payload: { groupList: Group[] }) {
try {
const newGroupList = payload.groupList
for (const group of newGroupList) {
let existGroup = groups.find((g) => g.groupCode == group.groupCode)
if (existGroup) {
if (existGroup.memberCount > group.memberCount) {
log(`群(${group.groupCode})成员数量减少${existGroup.memberCount} -> ${group.memberCount}`)
const oldMembers = existGroup.members
await sleep(200) // 如果请求QQ API的速度过快通常无法正确拉取到最新的群信息因此这里人为引入一个延时
const newMembers = await NTQQGroupApi.getGroupMembers(group.groupCode)
group.members = Array.from(newMembers.values())
const newMembersSet = new Set<string>() // 建立索引降低时间复杂度
for (const member of newMembers) {
newMembersSet.add(member[1].uin)
}
// 判断bot是否是管理员如果是管理员不需要从这里得知有人退群这里的退群无法得知是主动退群还是被踢
const selfUin = getSelfUin()
const bot = await getGroupMember(group.groupCode, selfUin)
if (bot?.role == GroupMemberRole.admin || bot?.role == GroupMemberRole.owner) {
continue
}
for (const member of oldMembers) {
if (!newMembersSet.has(member.uin) && member.uin != selfUin) {
postOb11Event(
new OB11GroupDecreaseEvent(
parseInt(group.groupCode),
parseInt(member.uin),
parseInt(member.uin),
'leave',
),
)
break
}
}
}
if (group.privilegeFlag === 0) {
deleteGroup(group.groupCode)
}
}
}
updateGroups(newGroupList, false).then()
} catch (e: any) {
updateGroups(payload.groupList).then()
log('更新群信息错误', e.stack.toString())
}
}*/
export async function startHook() {
// 群列表变动
/*registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
// updateType 3是群列表变动2是群成员变动
// log("群列表变动", payload.updateType, payload.groupList)
if (payload.updateType != 2) {
updateGroups(payload.groupList).then()
}
else {
if (process.platform == 'win32') {
processGroupEvent(payload).then()
}
}
})
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
// updateType 3是群列表变动2是群成员变动
// log("群列表变动, store", payload.updateType, payload.groupList)
if (payload.updateType != 2) {
updateGroups(payload.groupList).then()
}
else {
if (process.platform != 'win32') {
processGroupEvent(payload).then()
}
}
})*/
registerReceiveHook<{
groupCode: string
dataSource: number
members: Set<GroupMember>
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
const groupCode = payload.groupCode
const members = Array.from(payload.members.values())
// log("群成员信息变动", groupCode, members)
for (const member of members) {
const existMember = await getGroupMember(groupCode, member.uin)
if (existMember) {
if (member.cardName != existMember.cardName) {
log('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
postOb11Event(
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
)
} else if (member.role != existMember.role) {
log('有管理员变动通知')
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
member.role == GroupMemberRole.admin ? 'set' : 'unset',
parseInt(groupCode),
parseInt(member.uin)
)
postOb11Event(groupAdminNoticeEvent, true)
}
Object.assign(existMember, member)
}
}
// const existGroup = groups.find(g => g.groupCode == groupCode);
// if (existGroup) {
// log("对比群成员", existGroup.members, members)
// for (const member of members) {
// const existMember = existGroup.members.find(m => m.uin == member.uin);
// if (existMember) {
// log("对比群名片", existMember.cardName, member.cardName)
// if (existMember.cardName != member.cardName) {
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName));
// }
// Object.assign(existMember, member);
// }
// }
// }
})
// 好友列表变动
registerReceiveHook<{
data: CategoryFriend[]
}>(ReceiveCmdS.FRIENDS, (payload) => {
// log("onBuddyListChange", payload)
// let friendListV2: {userSimpleInfos: Map<string, SimpleInfo>} = []
type V2data = { userSimpleInfos: Map<string, SimpleInfo> }
let friendList: User[] = [];
if ((payload as any).userSimpleInfos) {
// friendListV2 = payload as any
friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => {
return {
...v.coreInfo,
}
})
}
else {
for (const fData of payload.data) {
friendList.push(...fData.buddyList)
}
}
log('好友列表变动', friendList)
for (let friend of friendList) {
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
let existFriend = friends.find((f) => f.uin == friend.uin)
if (!existFriend) {
friends.push(friend)
}
else {
Object.assign(existFriend, friend)
}
}
})
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
// 自动清理新消息文件
const { autoDeleteFile } = getConfigUtil().getConfig()
if (!autoDeleteFile) {
return
}
for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message.msgId)
// dbUtil.addMsg(message).then()
// 清理文件
for (const msgElement of message.elements) {
setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.filePath
const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!]
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath))
}
// log("需要清理的文件", pathList);
for (const path of pathList) {
if (path) {
fs.unlink(picPath, () => {
log('删除文件成功', path)
})
}
}
}, getConfigUtil().getConfig().autoDeleteFileSecond! * 1000)
}
}
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
const { msgId, chatType, peerUid } = msgRecord
const peer = {
chatType,
peerUid
}
MessageUnique.createMsg(peer, msgId)
})
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
setSelfInfo({
online: info.info.status !== 20
})
})
let activatedPeerUids: string[] = []
registerReceiveHook<{
changedRecentContactLists: {
listType: number
sortedContactList: string[]
changedList: {
id: string // peerUid
chatType: ChatType
}[]
}[]
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
for (const recentContact of payload.changedRecentContactLists) {
for (const changedContact of recentContact.changedList) {
if (activatedPeerUids.includes(changedContact.id)) continue
activatedPeerUids.push(changedContact.id)
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
if (changedContact.chatType === ChatType.temp) {
log('收到临时会话消息', peer)
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
let lastTempMsg = msgList.pop()
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) {
OB11Constructor.message(lastTempMsg!).then((r) => postOb11Event(r))
}
})
})
}
else {
NTQQMsgApi.activateChat(peer).then()
}
}
}
})
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
const peerUid = payload[0] as string
log('激活的聊天窗口被删除,准备重新激活', peerUid)
let chatType = ChatType.friend
if (isNumeric(peerUid)) {
chatType = ChatType.group
}
else {
// 检查是否好友
if (!(await getFriend(peerUid))) {
chatType = ChatType.temp
}
}
const peer = { peerUid, chatType }
await sleep(1000)
NTQQMsgApi.activateChat(peer).then((r) => {
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
})
})
}

View File

@@ -1,240 +1,240 @@
import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/ntqqapi/types' import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/ntqqapi/types'
interface IGroupListener { interface IGroupListener {
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void
onGroupExtListUpdate(...args: unknown[]): void onGroupExtListUpdate(...args: unknown[]): void
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void
onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void
onGroupDetailInfoChange(...args: unknown[]): void onGroupDetailInfoChange(...args: unknown[]): void
onGroupAllInfoChange(...args: unknown[]): void onGroupAllInfoChange(...args: unknown[]): void
onGroupsMsgMaskResult(...args: unknown[]): void onGroupsMsgMaskResult(...args: unknown[]): void
onGroupConfMemberChange(...args: unknown[]): void onGroupConfMemberChange(...args: unknown[]): void
onGroupBulletinChange(...args: unknown[]): void onGroupBulletinChange(...args: unknown[]): void
onGetGroupBulletinListResult(...args: unknown[]): void onGetGroupBulletinListResult(...args: unknown[]): void
onMemberListChange(arg: { onMemberListChange(arg: {
sceneId: string, sceneId: string,
ids: string[], ids: string[],
infos: Map<string, GroupMember>, infos: Map<string, GroupMember>,
finish: boolean, finish: boolean,
hasRobot: boolean hasRobot: boolean
}): void }): void
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): void onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): void
onSearchMemberChange(...args: unknown[]): void onSearchMemberChange(...args: unknown[]): void
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void
onGroupStatisticInfoChange(...args: unknown[]): void onGroupStatisticInfoChange(...args: unknown[]): void
onJoinGroupNotify(...args: unknown[]): void onJoinGroupNotify(...args: unknown[]): void
onShutUpMemberListChanged(...args: unknown[]): void onShutUpMemberListChanged(...args: unknown[]): void
onGroupBulletinRemindNotify(...args: unknown[]): void onGroupBulletinRemindNotify(...args: unknown[]): void
onGroupFirstBulletinNotify(...args: unknown[]): void onGroupFirstBulletinNotify(...args: unknown[]): void
onJoinGroupNoVerifyFlag(...args: unknown[]): void onJoinGroupNoVerifyFlag(...args: unknown[]): void
onGroupArkInviteStateResult(...args: unknown[]): void onGroupArkInviteStateResult(...args: unknown[]): void
// 发现于Win 9.9.9 23159 // 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void onGroupMemberLevelInfoChange(...args: unknown[]): void
} }
export interface NodeIKernelGroupListener extends IGroupListener { export interface NodeIKernelGroupListener extends IGroupListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new // eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IGroupListener): NodeIKernelGroupListener new(listener: IGroupListener): NodeIKernelGroupListener
} }
export class GroupListener implements IGroupListener { export class GroupListener implements IGroupListener {
// 发现于Win 9.9.9 23159 // 发现于Win 9.9.9 23159
onGroupMemberLevelInfoChange(...args: unknown[]): void { onGroupMemberLevelInfoChange(...args: unknown[]): void {
} }
onGetGroupBulletinListResult(...args: unknown[]) { onGetGroupBulletinListResult(...args: unknown[]) {
} }
onGroupAllInfoChange(...args: unknown[]) { onGroupAllInfoChange(...args: unknown[]) {
} }
onGroupBulletinChange(...args: unknown[]) { onGroupBulletinChange(...args: unknown[]) {
} }
onGroupBulletinRemindNotify(...args: unknown[]) { onGroupBulletinRemindNotify(...args: unknown[]) {
} }
onGroupArkInviteStateResult(...args: unknown[]) { onGroupArkInviteStateResult(...args: unknown[]) {
} }
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) { onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
} }
onGroupConfMemberChange(...args: unknown[]) { onGroupConfMemberChange(...args: unknown[]) {
} }
onGroupDetailInfoChange(...args: unknown[]) { onGroupDetailInfoChange(...args: unknown[]) {
} }
onGroupExtListUpdate(...args: unknown[]) { onGroupExtListUpdate(...args: unknown[]) {
} }
onGroupFirstBulletinNotify(...args: unknown[]) { onGroupFirstBulletinNotify(...args: unknown[]) {
} }
onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) { onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]) {
} }
onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) { onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]) {
} }
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) { onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
} }
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) { onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
} }
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) { onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
} }
onGroupsMsgMaskResult(...args: unknown[]) { onGroupsMsgMaskResult(...args: unknown[]) {
} }
onGroupStatisticInfoChange(...args: unknown[]) { onGroupStatisticInfoChange(...args: unknown[]) {
} }
onJoinGroupNotify(...args: unknown[]) { onJoinGroupNotify(...args: unknown[]) {
} }
onJoinGroupNoVerifyFlag(...args: unknown[]) { onJoinGroupNoVerifyFlag(...args: unknown[]) {
} }
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) { onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
} }
onMemberListChange(arg: { onMemberListChange(arg: {
sceneId: string, sceneId: string,
ids: string[], ids: string[],
infos: Map<string, GroupMember>, // uid -> GroupMember infos: Map<string, GroupMember>, // uid -> GroupMember
finish: boolean, finish: boolean,
hasRobot: boolean hasRobot: boolean
}) { }) {
} }
onSearchMemberChange(...args: unknown[]) { onSearchMemberChange(...args: unknown[]) {
} }
onShutUpMemberListChanged(...args: unknown[]) { onShutUpMemberListChanged(...args: unknown[]) {
} }
} }
export class DebugGroupListener implements IGroupListener { export class DebugGroupListener implements IGroupListener {
onGroupMemberLevelInfoChange(...args: unknown[]): void { onGroupMemberLevelInfoChange(...args: unknown[]): void {
console.log('onGroupMemberLevelInfoChange:', ...args) console.log('onGroupMemberLevelInfoChange:', ...args)
} }
onGetGroupBulletinListResult(...args: unknown[]) { onGetGroupBulletinListResult(...args: unknown[]) {
console.log('onGetGroupBulletinListResult:', ...args) console.log('onGetGroupBulletinListResult:', ...args)
} }
onGroupAllInfoChange(...args: unknown[]) { onGroupAllInfoChange(...args: unknown[]) {
console.log('onGroupAllInfoChange:', ...args) console.log('onGroupAllInfoChange:', ...args)
} }
onGroupBulletinChange(...args: unknown[]) { onGroupBulletinChange(...args: unknown[]) {
console.log('onGroupBulletinChange:', ...args) console.log('onGroupBulletinChange:', ...args)
} }
onGroupBulletinRemindNotify(...args: unknown[]) { onGroupBulletinRemindNotify(...args: unknown[]) {
console.log('onGroupBulletinRemindNotify:', ...args) console.log('onGroupBulletinRemindNotify:', ...args)
} }
onGroupArkInviteStateResult(...args: unknown[]) { onGroupArkInviteStateResult(...args: unknown[]) {
console.log('onGroupArkInviteStateResult:', ...args) console.log('onGroupArkInviteStateResult:', ...args)
} }
onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) { onGroupBulletinRichMediaDownloadComplete(...args: unknown[]) {
console.log('onGroupBulletinRichMediaDownloadComplete:', ...args) console.log('onGroupBulletinRichMediaDownloadComplete:', ...args)
} }
onGroupConfMemberChange(...args: unknown[]) { onGroupConfMemberChange(...args: unknown[]) {
console.log('onGroupConfMemberChange:', ...args) console.log('onGroupConfMemberChange:', ...args)
} }
onGroupDetailInfoChange(...args: unknown[]) { onGroupDetailInfoChange(...args: unknown[]) {
console.log('onGroupDetailInfoChange:', ...args) console.log('onGroupDetailInfoChange:', ...args)
} }
onGroupExtListUpdate(...args: unknown[]) { onGroupExtListUpdate(...args: unknown[]) {
console.log('onGroupExtListUpdate:', ...args) console.log('onGroupExtListUpdate:', ...args)
} }
onGroupFirstBulletinNotify(...args: unknown[]) { onGroupFirstBulletinNotify(...args: unknown[]) {
console.log('onGroupFirstBulletinNotify:', ...args) console.log('onGroupFirstBulletinNotify:', ...args)
} }
onGroupListUpdate(...args: unknown[]) { onGroupListUpdate(...args: unknown[]) {
console.log('onGroupListUpdate:', ...args) console.log('onGroupListUpdate:', ...args)
} }
onGroupNotifiesUpdated(...args: unknown[]) { onGroupNotifiesUpdated(...args: unknown[]) {
console.log('onGroupNotifiesUpdated:', ...args) console.log('onGroupNotifiesUpdated:', ...args)
} }
onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) { onGroupBulletinRichMediaProgressUpdate(...args: unknown[]) {
console.log('onGroupBulletinRichMediaProgressUpdate:', ...args) console.log('onGroupBulletinRichMediaProgressUpdate:', ...args)
} }
onGroupNotifiesUnreadCountUpdated(...args: unknown[]) { onGroupNotifiesUnreadCountUpdated(...args: unknown[]) {
console.log('onGroupNotifiesUnreadCountUpdated:', ...args) console.log('onGroupNotifiesUnreadCountUpdated:', ...args)
} }
onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) { onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]) {
console.log('onGroupSingleScreenNotifies:') console.log('onGroupSingleScreenNotifies:')
} }
onGroupsMsgMaskResult(...args: unknown[]) { onGroupsMsgMaskResult(...args: unknown[]) {
console.log('onGroupsMsgMaskResult:', ...args) console.log('onGroupsMsgMaskResult:', ...args)
} }
onGroupStatisticInfoChange(...args: unknown[]) { onGroupStatisticInfoChange(...args: unknown[]) {
console.log('onGroupStatisticInfoChange:', ...args) console.log('onGroupStatisticInfoChange:', ...args)
} }
onJoinGroupNotify(...args: unknown[]) { onJoinGroupNotify(...args: unknown[]) {
console.log('onJoinGroupNotify:', ...args) console.log('onJoinGroupNotify:', ...args)
} }
onJoinGroupNoVerifyFlag(...args: unknown[]) { onJoinGroupNoVerifyFlag(...args: unknown[]) {
console.log('onJoinGroupNoVerifyFlag:', ...args) console.log('onJoinGroupNoVerifyFlag:', ...args)
} }
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) { onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>) {
console.log('onMemberInfoChange:', groupCode, changeType, members) console.log('onMemberInfoChange:', groupCode, changeType, members)
} }
onMemberListChange(...args: unknown[]) { onMemberListChange(...args: unknown[]) {
console.log('onMemberListChange:', ...args) console.log('onMemberListChange:', ...args)
} }
onSearchMemberChange(...args: unknown[]) { onSearchMemberChange(...args: unknown[]) {
console.log('onSearchMemberChange:', ...args) console.log('onSearchMemberChange:', ...args)
} }
onShutUpMemberListChanged(...args: unknown[]) { onShutUpMemberListChanged(...args: unknown[]) {
console.log('onShutUpMemberListChanged:', ...args) console.log('onShutUpMemberListChanged:', ...args)
} }
} }

View File

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

View File

@@ -1,44 +1,44 @@
import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types' import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types'
interface IProfileListener { interface IProfileListener {
onProfileSimpleChanged(...args: unknown[]): void onProfileSimpleChanged(...args: unknown[]): void
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void
onProfileDetailInfoChanged(profile: User): void onProfileDetailInfoChanged(profile: User): void
onStatusUpdate(...args: unknown[]): void onStatusUpdate(...args: unknown[]): void
onSelfStatusChanged(...args: unknown[]): void onSelfStatusChanged(...args: unknown[]): void
onStrangerRemarkChanged(...args: unknown[]): void onStrangerRemarkChanged(...args: unknown[]): void
} }
export interface NodeIKernelProfileListener extends IProfileListener { export interface NodeIKernelProfileListener extends IProfileListener {
new(listener: IProfileListener): NodeIKernelProfileListener new(listener: IProfileListener): NodeIKernelProfileListener
} }
export class ProfileListener implements IProfileListener { export class ProfileListener implements IProfileListener {
onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void { onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void {
} }
onProfileSimpleChanged(...args: unknown[]) { onProfileSimpleChanged(...args: unknown[]) {
} }
onProfileDetailInfoChanged(profile: User) { onProfileDetailInfoChanged(profile: User) {
} }
onStatusUpdate(...args: unknown[]) { onStatusUpdate(...args: unknown[]) {
} }
onSelfStatusChanged(...args: unknown[]) { onSelfStatusChanged(...args: unknown[]) {
} }
onStrangerRemarkChanged(...args: unknown[]) { onStrangerRemarkChanged(...args: unknown[]) {
} }
} }

View File

@@ -1,9 +1,10 @@
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeReceiveHook } from './hook' import { hookApiCallbacks, registerReceiveHook, removeReceiveHook } from './hook'
import { log } from '../common/utils/log' import { log } from '../common/utils/legacyLog'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { GeneralCallResult } from './services'
export enum NTQQApiClass { export enum NTClass {
NT_API = 'ns-ntApi', NT_API = 'ns-ntApi',
FS_API = 'ns-FsApi', FS_API = 'ns-FsApi',
OS_API = 'ns-OsApi', OS_API = 'ns-OsApi',
@@ -17,8 +18,7 @@ export enum NTQQApiClass {
NODE_STORE_API = 'ns-NodeStoreApi' NODE_STORE_API = 'ns-NodeStoreApi'
} }
export enum NTQQApiMethod { export enum NTMethod {
TEST = 'NodeIKernelTipOffService/getPskey',
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact', RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息 ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息 ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
@@ -57,7 +57,6 @@ export enum NTQQApiMethod {
HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify', HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify',
QUIT_GROUP = 'nodeIKernelGroupService/quitGroup', QUIT_GROUP = 'nodeIKernelGroupService/quitGroup',
GROUP_AT_ALL_REMAIN_COUNT = 'nodeIKernelGroupService/getGroupRemainAtTimes', GROUP_AT_ALL_REMAIN_COUNT = 'nodeIKernelGroupService/getGroupRemainAtTimes',
// READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange"
HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest', HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest',
KICK_MEMBER = 'nodeIKernelGroupService/kickMember', KICK_MEMBER = 'nodeIKernelGroupService/kickMember',
MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp', MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp',
@@ -87,59 +86,41 @@ export enum NTQQApiMethod {
OPEN_EXTRA_WINDOW = 'openExternalWindow', OPEN_EXTRA_WINDOW = 'openExternalWindow',
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader', SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
UPDATE_SKEY = 'updatePskey',
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
} }
enum NTQQApiChannel { export enum NTChannel {
IPC_UP_2 = 'IPC_UP_2', IPC_UP_2 = 'IPC_UP_2',
IPC_UP_3 = 'IPC_UP_3', IPC_UP_3 = 'IPC_UP_3',
IPC_UP_1 = 'IPC_UP_1', IPC_UP_1 = 'IPC_UP_1',
} }
interface NTQQApiParams { interface InvokeParams<ReturnType> {
methodName: NTQQApiMethod | string methodName: string
className?: NTQQApiClass className?: NTClass
channel?: NTQQApiChannel channel?: NTChannel
classNameIsRegister?: boolean classNameIsRegister?: boolean
args?: unknown[] args?: unknown[]
cbCmd?: ReceiveCmd | ReceiveCmd[] | null cbCmd?: string | string[]
cmdCB?: (payload: any) => boolean cmdCB?: (payload: ReturnType) => boolean
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
timeoutSecond?: number timeout?: number
} }
export function callNTQQApi<ReturnType>(params: NTQQApiParams) { export function invoke<ReturnType>(params: InvokeParams<ReturnType>) {
let { const className = params.className ?? NTClass.NT_API
className, const channel = params.channel ?? NTChannel.IPC_UP_2
methodName, const timeout = params.timeout ?? 5000
channel, const afterFirstCmd = params.afterFirstCmd ?? true
args,
cbCmd,
timeoutSecond: timeout,
classNameIsRegister,
cmdCB,
afterFirstCmd,
} = params
className = className ?? NTQQApiClass.NT_API
channel = channel ?? NTQQApiChannel.IPC_UP_2
args = args ?? []
timeout = timeout ?? 5
afterFirstCmd = afterFirstCmd ?? true
const uuid = randomUUID() const uuid = randomUUID()
//HOOK_LOG && log('callNTQQApi', channel, className, methodName, args, uuid) let eventName = className + '-' + channel[channel.length - 1]
if (params.classNameIsRegister) {
eventName += '-register'
}
const apiArgs = [params.methodName, ...(params.args ?? [])]
//log('callNTQQApi', channel, eventName, apiArgs, uuid)
return new Promise((resolve: (data: ReturnType) => void, reject) => { return new Promise((resolve: (data: ReturnType) => void, reject) => {
// log("callNTQQApiPromise", channel, className, methodName, args, uuid)
const _timeout = timeout * 1000
let success = false let success = false
let eventName = className + '-' + channel[channel.length - 1] if (!params.cbCmd) {
if (classNameIsRegister) {
eventName += '-register'
}
const apiArgs = [methodName, ...args]
if (!cbCmd) {
// QQ后端会返回结果并且可以根据uuid识别 // QQ后端会返回结果并且可以根据uuid识别
hookApiCallbacks[uuid] = (r: ReturnType) => { hookApiCallbacks[uuid] = (r: ReturnType) => {
success = true success = true
@@ -149,10 +130,10 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
else { else {
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => { const secondCallback = () => {
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => { const hookId = registerReceiveHook<ReturnType>(params.cbCmd!, (payload) => {
// log(methodName, "second callback", cbCmd, payload, cmdCB); // log(methodName, "second callback", cbCmd, payload, cmdCB);
if (!!cmdCB) { if (!!params.cmdCB) {
if (cmdCB(payload)) { if (params.cmdCB(payload)) {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true success = true
resolve(payload) resolve(payload)
@@ -167,23 +148,22 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
} }
!afterFirstCmd && secondCallback() !afterFirstCmd && secondCallback()
hookApiCallbacks[uuid] = (result: GeneralCallResult) => { hookApiCallbacks[uuid] = (result: GeneralCallResult) => {
log(`${methodName} callback`, result) if (result?.result === 0 || result === undefined) {
if (result?.result == 0 || result === undefined) { //log(`${params.methodName} callback`, result)
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback()
} }
else { else {
success = true log('ntqq api call failed', result)
reject(`ntqq api call failed, ${result.errMsg}`) reject(`ntqq api call failed, ${result.errMsg}`)
} }
} }
} }
setTimeout(() => { setTimeout(() => {
// log("ntqq api timeout", success, channel, className, methodName)
if (!success) { if (!success) {
log(`ntqq api timeout ${channel}, ${eventName}, ${methodName}`, apiArgs) log(`ntqq api timeout ${channel}, ${eventName}, ${params.methodName}`, apiArgs)
reject(`ntqq api timeout ${channel}, ${eventName}, ${methodName}, ${apiArgs}`) reject(`ntqq api timeout ${channel}, ${eventName}, ${params.methodName}, ${apiArgs}`)
} }
}, _timeout) }, timeout)
ipcMain.emit( ipcMain.emit(
channel, channel,
@@ -197,9 +177,4 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
apiArgs, apiArgs,
) )
}) })
}
export interface GeneralCallResult {
result: number // 0: success
errMsg: string
} }

View File

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

View File

@@ -1,249 +1,249 @@
import { NodeIKernelGroupListener } from '@/ntqqapi/listeners' import { NodeIKernelGroupListener } from '@/ntqqapi/listeners'
import { import {
GroupExtParam, GroupExtParam,
GroupMember, GroupMember,
GroupMemberRole, GroupMemberRole,
GroupNotifyTypes, GroupNotifyTypes,
GroupRequestOperateTypes, GroupRequestOperateTypes,
} from '@/ntqqapi/types' } from '@/ntqqapi/types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底 //高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
getMemberCommonInfo(Req: { getMemberCommonInfo(Req: {
groupCode: string,
startUin: string,
identifyFlag: string,
uinList: string[],
memberCommonFilter: {
memberUin: number,
uinFlag: number,
uinFlagExt: number,
uinMobileFlag: number,
shutUpTime: number,
privilege: 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<{
errCode: number,
errMsg: string,
uins: Map<string, string>
}>
getUidByUins(uins: string[]): Promise<{
errCode: number,
errMsg: string,
uids: Map<string, string>
}>
//26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>
//26702(其实更早 但是我不知道)
getGroupLatestEssenceList(groupCode: string): Promise<unknown>
//26702(其实更早 但是我不知道)
shareDigest(Req: {
appId: string,
appType: number,
msgStyle: number,
recvUin: 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: {},
finish: true,
hasRobot: false
}
}>
setHeader(uid: string, path: string): unknown
addKernelGroupListener(listener: NodeIKernelGroupListener): number
removeKernelGroupListener(listenerId: unknown): void
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<{
errCode: number, errMsg: string,
result: { ids: string[], 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>
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): 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
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
destroyGroup(groupCode: string): void
//获取单屏群通知列表
getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise<GeneralCallResult>
clearGroupNotifies(groupCode: string): void
getGroupNotifiesUnreadCount(unknown: Boolean): Promise<GeneralCallResult>
clearGroupNotifiesUnreadCount(groupCode: string): void
operateSysNotify(
doubt: boolean,
operateMsg: {
operateType: GroupRequestOperateTypes, // 2 拒绝
targetMsg: {
seq: string, // 通知序列号
type: GroupNotifyTypes,
groupCode: string, groupCode: string,
startUin: string, postscript: string
identifyFlag: string, }
uinList: string[], }): Promise<void>
memberCommonFilter: {
memberUin: number,
uinFlag: number,
uinFlagExt: number,
uinMobileFlag: number,
shutUpTime: number,
privilege: 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<{ setTop(groupCode: string, isTop: boolean): void
errCode: number,
errMsg: string,
uins: Map<string, string>
}>
getUidByUins(uins: string[]): Promise<{ getGroupBulletin(groupCode: string): unknown
errCode: number,
errMsg: string,
uids: Map<string, string>
}>
//26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>
//26702(其实更早 但是我不知道) deleteGroupBulletin(groupCode: string, seq: string): void
getGroupLatestEssenceList(groupCode: string): Promise<unknown>
//26702(其实更早 但是我不知道) publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult>
shareDigest(Req: {
appId: string,
appType: number,
msgStyle: number,
recvUin: 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: {},
finish: true,
hasRobot: false
}
}>
setHeader(uid: string, path: string): unknown publishInstructionForNewcomers(groupCode: string, arg: unknown): void
addKernelGroupListener(listener: NodeIKernelGroupListener): number uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<GeneralCallResult & {
errCode: number
picInfo?: {
id: string,
width: number,
height: number
}
}>
removeKernelGroupListener(listenerId: unknown): void downloadGroupBulletinRichMedia(groupCode: string): unknown
createMemberListScene(groupCode: string, scene: string): string getGroupBulletinList(groupCode: string): unknown
destroyMemberListScene(SceneId: string): void getGroupStatisticInfo(groupCode: string): unknown
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string,
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean }
}>
getPrevMemberList(): unknown getGroupRemainAtTimes(groupCode: string): number
monitorMemberList(): unknown getJoinGroupNoVerifyFlag(groupCode: string): unknown
searchMember(sceneId: string, keywords: string[]): unknown getGroupArkInviteState(groupCode: string): unknown
getMemberInfo(group_id: string, uids: string[], forceFetch: boolean): Promise<GeneralCallResult> reqToJoinGroup(groupCode: string, arg: unknown): void
//getMemberInfo [ '56729xxxx', [ 'u_4Nj08cwW5Hxxxxx' ], true ]
kickMember(groupCode: string, memberUids: string[], refuseForever: boolean, kickReason: string): Promise<void> setGroupShutUp(groupCode: string, shutUp: boolean): void
modifyMemberRole(groupCode: string, uid: string, role: GroupMemberRole): void getGroupShutUpMemberList(groupCode: string): unknown[]
modifyMemberCardName(groupCode: string, uid: string, cardName: string): void setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>
getTransferableMemberInfo(groupCode: string): unknown//获取整个群的 getGroupRecommendContactArkJson(groupCode: string): unknown
transferGroup(uid: string): void getJoinGroupLink(groupCode: string): unknown
getGroupList(force: boolean): Promise<GeneralCallResult> modifyGroupExtInfo(groupCode: string, arg: unknown): void
getGroupExtList(force: boolean): Promise<GeneralCallResult> //需要提前判断是否存在 高版本新增
addGroupEssence(param: {
groupCode: string
msgRandom: number,
msgSeq: number
}): Promise<unknown>
//需要提前判断是否存在 高版本新增
removeGroupEssence(param: {
groupCode: string
msgRandom: number,
msgSeq: number
}): Promise<unknown>
getGroupDetailInfo(groupCode: string): unknown isNull(): boolean
getMemberExtInfo(param: GroupExtParam): Promise<unknown>//req
getGroupAllInfo(): unknown
getDiscussExistInfo(): unknown
getGroupConfMember(): unknown
getGroupMsgMask(): unknown
getGroupPortrait(): 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
destroyGroup(groupCode: string): void
//获取单屏群通知列表
getSingleScreenNotifies(force: boolean, start_seq: string, num: number): Promise<GeneralCallResult>
clearGroupNotifies(groupCode: string): void
getGroupNotifiesUnreadCount(unknown: Boolean): Promise<GeneralCallResult>
clearGroupNotifiesUnreadCount(groupCode: string): void
operateSysNotify(
doubt: boolean,
operateMsg: {
operateType: GroupRequestOperateTypes, // 2 拒绝
targetMsg: {
seq: string, // 通知序列号
type: GroupNotifyTypes,
groupCode: string,
postscript: string
}
}): Promise<void>
setTop(groupCode: string, isTop: boolean): void
getGroupBulletin(groupCode: string): unknown
deleteGroupBulletin(groupCode: string, seq: string): void
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult>
publishInstructionForNewcomers(groupCode: string, arg: unknown): void
uploadGroupBulletinPic(groupCode: string, pskey: string, imagePath: string): Promise<GeneralCallResult & {
errCode: number
picInfo?: {
id: string,
width: number,
height: number
}
}>
downloadGroupBulletinRichMedia(groupCode: string): unknown
getGroupBulletinList(groupCode: string): unknown
getGroupStatisticInfo(groupCode: string): unknown
getGroupRemainAtTimes(groupCode: string): number
getJoinGroupNoVerifyFlag(groupCode: string): unknown
getGroupArkInviteState(groupCode: string): unknown
reqToJoinGroup(groupCode: string, arg: unknown): void
setGroupShutUp(groupCode: string, shutUp: boolean): void
getGroupShutUpMemberList(groupCode: string): unknown[]
setMemberShutUp(groupCode: string, memberTimes: { uid: string, timeStamp: number }[]): Promise<void>
getGroupRecommendContactArkJson(groupCode: string): unknown
getJoinGroupLink(groupCode: string): unknown
modifyGroupExtInfo(groupCode: string, arg: unknown): void
//需要提前判断是否存在 高版本新增
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,3 +1,3 @@
export interface NodeIKernelMSFService { export interface NodeIKernelMSFService {
getServerTime(): string getServerTime(): string
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,21 +2,21 @@ import { BuddyProfileLikeReq } from '../types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
export interface NodeIKernelProfileLikeService { export interface NodeIKernelProfileLikeService {
addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void
removeKernelProfileLikeListener(listener: unknown): void removeKernelProfileLikeListener(listener: unknown): void
setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number } setBuddyProfileLike(...args: unknown[]): { result: number, errMsg: string, succCounts: number }
getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & { getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & {
'info': { 'info': {
'userLikeInfos': Array<any>, 'userLikeInfos': Array<any>,
'friendMaxVotes': number, 'friendMaxVotes': number,
'start': number 'start': number
} }
}> }>
getProfileLikeScidResourceInfo(...args: unknown[]): void getProfileLikeScidResourceInfo(...args: unknown[]): void
isNull(): boolean isNull(): boolean
} }

View File

@@ -16,9 +16,9 @@ export enum ProfileBizType {
} }
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>>//uin->uid
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string,string>> getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>>
// { // {
// coreInfo: CoreInfo, // coreInfo: CoreInfo,

View File

@@ -2,269 +2,269 @@ import { GetFileListParam, MessageElement, Peer } from '../types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
export enum UrlFileDownloadType { export enum UrlFileDownloadType {
KUNKNOWN, KUNKNOWN,
KURLFILEDOWNLOADPRIVILEGEICON, KURLFILEDOWNLOADPRIVILEGEICON,
KURLFILEDOWNLOADPHOTOWALL, KURLFILEDOWNLOADPHOTOWALL,
KURLFILEDOWNLOADQZONE, KURLFILEDOWNLOADQZONE,
KURLFILEDOWNLOADCOMMON, KURLFILEDOWNLOADCOMMON,
KURLFILEDOWNLOADINSTALLAPP KURLFILEDOWNLOADINSTALLAPP
} }
export enum RMBizTypeEnum { export enum RMBizTypeEnum {
KUNKNOWN, KUNKNOWN,
KC2CFILE, KC2CFILE,
KGROUPFILE, KGROUPFILE,
KC2CPIC, KC2CPIC,
KGROUPPIC, KGROUPPIC,
KDISCPIC, KDISCPIC,
KC2CVIDEO, KC2CVIDEO,
KGROUPVIDEO, KGROUPVIDEO,
KC2CPTT, KC2CPTT,
KGROUPPTT, KGROUPPTT,
KFEEDCOMMENTPIC, KFEEDCOMMENTPIC,
KGUILDFILE, KGUILDFILE,
KGUILDPIC, KGUILDPIC,
KGUILDPTT, KGUILDPTT,
KGUILDVIDEO KGUILDVIDEO
} }
export interface CommonFileInfo { export interface CommonFileInfo {
bizType: number bizType: number
chatType: number chatType: number
elemId: string elemId: string
favId: string favId: string
fileModelId: string fileModelId: string
fileName: string fileName: string
fileSize: string fileSize: string
md5: string md5: string
md510m: string md510m: string
msgId: string msgId: string
msgTime: string msgTime: string
parent: string parent: string
peerUid: string peerUid: string
picThumbPath: Array<string> picThumbPath: Array<string>
sha: string sha: string
sha3: string sha3: string
subId: string subId: string
uuid: string uuid: string
} }
export interface NodeIKernelRichMediaService { export interface NodeIKernelRichMediaService {
//getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb) //getVideoPlayUrl(peer, msgId, elemId, videoCodecFormat, VideoRequestWay.KHAND, cb)
// public enum VideoCodecFormatType { // public enum VideoCodecFormatType {
// KCODECFORMATH264, // KCODECFORMATH264,
// KCODECFORMATH265, // KCODECFORMATH265,
// KCODECFORMATH266, // KCODECFORMATH266,
// KCODECFORMATAV1 // KCODECFORMATAV1
// } // }
// public enum VideoRequestWay { // public enum VideoRequestWay {
// KUNKNOW, // KUNKNOW,
// KHAND, // KHAND,
// KAUTO // KAUTO
// } // }
getVideoPlayUrl(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, VideoRequestWay: number): Promise<unknown> getVideoPlayUrl(peer: Peer, msgId: string, elemId: string, videoCodecFormat: number, VideoRequestWay: number): Promise<unknown>
//exParams (RMReqExParams) //exParams (RMReqExParams)
// this.downSourceType = i2 // this.downSourceType = i2
// this.triggerType = i3 // this.triggerType = i3
//peer, msgId, elemId, videoCodecFormat, exParams //peer, msgId, elemId, videoCodecFormat, exParams
// 1 0 频道在用 // 1 0 频道在用
// 1 1 // 1 1
// 0 2 // 0 2
// public static final int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007 // public static final int KCOMMONREDENVELOPEMSGTYPEINMSGBOX = 1007
// public static final int KDOWNSOURCETYPEAIOINNER = 1 // public static final int KDOWNSOURCETYPEAIOINNER = 1
// public static final int KDOWNSOURCETYPEBIGSCREEN = 2 // public static final int KDOWNSOURCETYPEBIGSCREEN = 2
// public static final int KDOWNSOURCETYPEHISTORY = 3 // public static final int KDOWNSOURCETYPEHISTORY = 3
// public static final int KDOWNSOURCETYPEUNKNOWN = 0 // public static final int KDOWNSOURCETYPEUNKNOWN = 0
// public static final int KTRIGGERTYPEAUTO = 1 // public static final int KTRIGGERTYPEAUTO = 1
// public static final int KTRIGGERTYPEMANUAL = 0 // 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: Array<{
url: string, url: string,
isHttps: boolean, isHttps: boolean,
httpsDomain: string httpsDomain: string
}>, }>,
videoCodecFormat: number videoCodecFormat: number
}
}>
getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown
// this.senderUid = ""
// 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: any, groupItem: Array<any> } }>
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown
createGroupFolder(arg1: unknown, arg2: unknown): unknown
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
deleteGroupFolder(arg1: unknown, arg2: 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 & {
groupSpaceResult: {
retCode: number
retMsg: string
clientWording: string
totalSpace: number
usedSpace: number
allUpload: boolean
}
}>
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
transGroupFile(arg1: unknown, arg2: unknown): unknown
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: {
result: any
successFileIdList: Array<any>
failFileIdList: Array<any>
}
}>
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 getRichMediaFileDir(elementType: number, downType: number, isTemp: boolean): unknown
isExtraLargePic(arg1: unknown, arg2: unknown, arg3: unknown): unknown // this.senderUid = ""
// 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
uploadRMFileWithoutMsg(arg: { getVideoPlayUrlInVisit(arg: {
bizType: RMBizTypeEnum, downloadType: number,
filePath: string, thumbSize: number,
peerUid: string, msgId: string,
transferId: string msgRandom: string,
useNTV2: 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: any, groupItem: Array<any> } }>
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown
createGroupFolder(arg1: unknown, arg2: unknown): unknown
downloadGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
renameGroupFolder(arg1: unknown, arg2: unknown, arg3: unknown): unknown
deleteGroupFolder(arg1: unknown, arg2: 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 & {
groupSpaceResult: {
retCode: number
retMsg: string
clientWording: string
totalSpace: number
usedSpace: number
allUpload: boolean
}
}>
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
transGroupFile(arg1: unknown, arg2: unknown): unknown
searchGroupFile(
keywords: Array<string>,
param: {
groupIds: Array<string>,
fileType: number,
context: string,
count: number,
sortType: number,
groupNames: Array<string>
}): Promise<unknown> }): Promise<unknown>
searchGroupFileByWord(arg1: unknown, arg2: unknown, arg3: unknown, arg4: unknown, arg5: unknown): unknown
isNull(): boolean deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
transGroupFileResult: {
result: any
successFileIdList: Array<any>
failFileIdList: Array<any>
}
}>
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

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
export interface NodeIKernelUixConvertService { export interface NodeIKernelUixConvertService {
getUin(uid: string[]): Promise<{ uinInfo: Map<string, string> }> getUin(uid: string[]): Promise<{ uinInfo: Map<string, string> }>
getUid(uin: string[]): Promise<{ uidInfo: Map<string, string> }> getUid(uin: string[]): Promise<{ uidInfo: Map<string, string> }>
} }

View File

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

View File

@@ -1,3 +1,4 @@
export * from './common'
export * from './NodeIKernelBuddyService' export * from './NodeIKernelBuddyService'
export * from './NodeIKernelProfileService' export * from './NodeIKernelProfileService'
export * from './NodeIKernelGroupService' export * from './NodeIKernelGroupService'

View File

@@ -335,6 +335,8 @@ export interface MarketFaceElement {
faceName?: string faceName?: string
emojiId: string emojiId: string
key: string key: string
imageWidth?: number
imageHeight?: number
} }
export interface VideoElement { export interface VideoElement {

View File

@@ -78,9 +78,12 @@ export interface Friend extends User {
export interface CategoryFriend { export interface CategoryFriend {
categoryId: number categoryId: number
categorySortId: number
categroyName: string categroyName: string
categroyMbCount: number categroyMbCount: number
buddyList: User[] onlineCount: number
buddyList: User[] // V1
buddyUids: string[]
} }
export interface CoreInfo { export interface CoreInfo {

View File

@@ -1,11 +1,16 @@
import { ActionName, BaseCheckResult } from './types' import { ActionName, BaseCheckResult } from './types'
import { OB11Response } from './OB11Response' import { OB11Response } from './OB11Response'
import { OB11Return } from '../types' import { OB11Return } from '../types'
import { Context } from 'cordis'
import { log } from '../../common/utils/log' import type Adapter from '../adapter'
abstract class BaseAction<PayloadType, ReturnDataType> { abstract class BaseAction<PayloadType, ReturnDataType> {
abstract actionName: ActionName abstract actionName: ActionName
protected ctx: Context
constructor(protected adapter: Adapter) {
this.ctx = adapter.ctx
}
protected async check(payload: PayloadType): Promise<BaseCheckResult> { protected async check(payload: PayloadType): Promise<BaseCheckResult> {
return { return {
@@ -22,7 +27,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
const resData = await this._handle(payload) const resData = await this._handle(payload)
return OB11Response.ok(resData) return OB11Response.ok(resData)
} catch (e: any) { } catch (e: any) {
log('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200) return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200)
} }
} }
@@ -36,7 +41,7 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
const resData = await this._handle(payload) const resData = await this._handle(payload)
return OB11Response.ok(resData, echo) return OB11Response.ok(resData, echo)
} catch (e: any) { } catch (e: any) {
log('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo) return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo)
} }
} }

View File

@@ -1,6 +1,5 @@
import { OB11Return } from '../types' import { OB11Return } from '../types'
import { isNullable } from 'cosmokit'
import { isNull } from '../../common/utils/helper'
export class OB11Response { export class OB11Response {
static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> { static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {
@@ -16,7 +15,7 @@ export class OB11Response {
static ok<T>(data: T, echo: any = null) { static ok<T>(data: T, echo: any = null) {
let res = OB11Response.res<T>(data, 'ok', 0) let res = OB11Response.res<T>(data, 'ok', 0)
if (!isNull(echo)) { if (!isNullable(echo)) {
res.echo = echo res.echo = echo
} }
return res return res
@@ -24,7 +23,7 @@ export class OB11Response {
static error(err: string, retcode: number, echo: any = null) { static error(err: string, retcode: number, echo: any = null) {
let res = OB11Response.res(null, 'failed', retcode, err) let res = OB11Response.res(null, 'failed', retcode, err)
if (!isNull(echo)) { if (!isNullable(echo)) {
res.echo = echo res.echo = echo
} }
return res return res

View File

@@ -1,11 +1,9 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import { getConfigUtil } from '@/common/config' import { getConfigUtil } from '@/common/config'
import { NTQQFileApi, NTQQGroupApi, NTQQUserApi, NTQQFriendApi, NTQQMsgApi } from '@/ntqqapi/api'
import { ActionName } from '../types' import { ActionName } from '../types'
import { UUIDConverter } from '@/common/utils/helper' import { Peer, ElementType } from '@/ntqqapi/types'
import { Peer, ChatType, ElementType } from '@/ntqqapi/types' import { MessageUnique } from '@/common/utils/messageUnique'
import { MessageUnique } from '@/common/utils/MessageUnique'
export interface GetFilePayload { export interface GetFilePayload {
file: string // 文件名或者fileUuid file: string // 文件名或者fileUuid
@@ -30,7 +28,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
} }
if (fileCache?.length) { if (fileCache?.length) {
const downloadPath = await NTQQFileApi.downloadMedia( const downloadPath = await this.ctx.ntFileApi.downloadMedia(
fileCache[0].msgId, fileCache[0].msgId,
fileCache[0].chatType, fileCache[0].chatType,
fileCache[0].peerUid, fileCache[0].peerUid,
@@ -50,7 +48,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
guildId: '' guildId: ''
} }
if (fileCache[0].elementType === ElementType.PIC) { if (fileCache[0].elementType === ElementType.PIC) {
const msgList = await NTQQMsgApi.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')
} }
@@ -59,11 +57,11 @@ 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 NTQQFileApi.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 NTQQFileApi.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) { if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) {
try { try {
res.base64 = await fsPromise.readFile(downloadPath, 'base64') res.base64 = await fsPromise.readFile(downloadPath, 'base64')
} catch (e) { } catch (e) {

View File

@@ -1,6 +1,6 @@
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 { getConfigUtil } from '@/common/config' import { getConfigUtil } from '@/common/config'
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import fs from 'node:fs'
@@ -14,10 +14,10 @@ export default class GetRecord extends GetFileBase {
protected async _handle(payload: Payload): Promise<GetFileResponse> { protected async _handle(payload: Payload): Promise<GetFileResponse> {
let res = await super._handle(payload) let res = await super._handle(payload)
res.file = await decodeSilk(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 = fs.statSync(res.file).size.toString()
if (getConfigUtil().getConfig().enableLocalFile2Url){ if (getConfigUtil().getConfig().enableLocalFile2Url) {
res.base64 = fs.readFileSync(res.file, 'base64') res.base64 = fs.readFileSync(res.file, 'base64')
} }
return res return res

View File

@@ -1,8 +1,6 @@
import BaseAction from '../BaseAction'
import BaseAction from '../BaseAction'; import { ActionName } from '../types'
import { ActionName } from '../types'; import { MessageUnique } from '@/common/utils/messageUnique'
import { NTQQGroupApi } from '@/ntqqapi/api/group'
import { MessageUnique } from '@/common/utils/MessageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
@@ -19,7 +17,7 @@ export default class GoCQHTTPDelEssenceMsg extends BaseAction<Payload, any> {
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')
} }
return await NTQQGroupApi.removeGroupEssence( return await this.ctx.ntGroupApi.removeGroupEssence(
msg.Peer.peerUid, msg.Peer.peerUid,
msg.MsgId, msg.MsgId,
) )

View File

@@ -1,6 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '@/ntqqapi/api'
interface Payload { interface Payload {
group_id: string | number group_id: string | number
@@ -12,6 +11,6 @@ export class GoCQHTTPDelGroupFile extends BaseAction<Payload, void> {
actionName = ActionName.GoCQHTTP_DelGroupFile actionName = ActionName.GoCQHTTP_DelGroupFile
async _handle(payload: Payload) { async _handle(payload: Payload) {
await NTQQGroupApi.delGroupFile(payload.group_id.toString(), [payload.file_id]) await this.ctx.ntGroupApi.delGroupFile(payload.group_id.toString(), [payload.file_id])
} }
} }

View File

@@ -1,9 +1,10 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types'
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 { calculateFileMD5, httpDownload, TEMP_DIR } from '@/common/utils' import { ActionName } from '../types'
import { calculateFileMD5, fetchFile } from '@/common/utils'
import { TEMP_DIR } from '@/common/globalVars'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
interface Payload { interface Payload {
@@ -30,8 +31,8 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
await fsPromise.writeFile(filePath, payload.base64, 'base64') await fsPromise.writeFile(filePath, payload.base64, 'base64')
} else if (payload.url) { } else if (payload.url) {
const headers = this.getHeaders(payload.headers) const headers = this.getHeaders(payload.headers)
const buffer = await httpDownload({ url: payload.url, headers: headers }) const res = await fetchFile(payload.url, headers)
await fsPromise.writeFile(filePath, buffer) await fsPromise.writeFile(filePath, res.data)
} else { } else {
throw new Error('不存在任何文件, 无法下载') throw new Error('不存在任何文件, 无法下载')
} }

View File

@@ -1,9 +1,8 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types' import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types'
import { NTQQMsgApi } from '@/ntqqapi/api'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/MessageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
message_id: string // long msg idgocq message_id: string // long msg idgocq
@@ -26,14 +25,14 @@ export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, Response> {
if (!rootMsg) { if (!rootMsg) {
throw Error('msg not found') throw Error('msg not found')
} }
const data = await NTQQMsgApi.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 msgList = data.msgList
const messages = await Promise.all( const messages = await Promise.all(
msgList.map(async (msg) => { msgList.map(async (msg) => {
const resMsg = await OB11Constructor.message(msg) const resMsg = await OB11Constructor.message(this.ctx, msg)
resMsg.message_id = MessageUnique.createMsg({ resMsg.message_id = MessageUnique.createMsg({
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,

View File

@@ -2,10 +2,9 @@ import BaseAction 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 { NTQQMsgApi } from '@/ntqqapi/api/msg'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import { RawMessage } from '@/ntqqapi/types' import { RawMessage } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/MessageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@@ -25,22 +24,23 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
const count = payload.count || 20 const count = payload.count || 20
const isReverseOrder = payload.reverseOrder || true 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
// 包含 message_seq 0 // 包含 message_seq 0
if (!payload.message_seq) { if (!payload.message_seq) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, count)).msgList msgList = (await this.ctx.ntMsgApi.getLastestMsgByUids(peer, count))?.msgList
} else { } else {
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId
if (!startMsgId) throw `消息${payload.message_seq}不存在` if (!startMsgId) throw `消息${payload.message_seq}不存在`
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, count)).msgList msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, count)).msgList
} }
if (!msgList?.length) throw '未找到消息'
if (isReverseOrder) msgList.reverse() if (isReverseOrder) msgList.reverse()
await Promise.all( await Promise.all(
msgList.map(async msg => { msgList.map(async msg => {
msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId) msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
}) })
) )
const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Constructor.message(msg))) const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Constructor.message(this.ctx, msg)))
return { messages: ob11MsgList } return { messages: ob11MsgList }
} }
} }

View File

@@ -2,10 +2,9 @@ import BaseAction from '../BaseAction'
import { OB11User } from '../../types' import { OB11User } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQUserApi } from '../../../ntqqapi/api/user' import { getBuildVersion } from '@/common/utils'
import { getBuildVersion } from '@/common/utils/QQBasicInfo'
import { OB11UserSex } from '../../types' import { OB11UserSex } from '../../types'
import { calcQQLevel } from '@/common/utils/qqlevel' import { calcQQLevel } from '@/common/utils/misc'
interface Payload { interface Payload {
user_id: number | string user_id: number | string
@@ -17,8 +16,8 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload): Promise<OB11User> {
if (!(getBuildVersion() >= 26702)) { if (!(getBuildVersion() >= 26702)) {
const user_id = payload.user_id.toString() const user_id = payload.user_id.toString()
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id) const extendData = await this.ctx.ntUserApi.getUserDetailInfoByUin(user_id)
const uid = (await NTQQUserApi.getUidByUin(user_id))! const uid = (await this.ctx.ntUserApi.getUidByUin(user_id))!
if (!uid || uid.indexOf('*') != -1) { if (!uid || uid.indexOf('*') != -1) {
const ret = { const ret = {
...extendData, ...extendData,
@@ -33,12 +32,12 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
} }
return ret return ret
} }
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) } const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) }
return OB11Constructor.stranger(data) return OB11Constructor.stranger(data)
} else { } else {
const user_id = payload.user_id.toString() const user_id = payload.user_id.toString()
const extendData = await NTQQUserApi.getUserDetailInfoByUinV2(user_id) const extendData = await this.ctx.ntUserApi.getUserDetailInfoByUinV2(user_id)
const uid = (await NTQQUserApi.getUidByUin(user_id))! const uid = (await this.ctx.ntUserApi.getUidByUin(user_id))!
if (!uid || uid.indexOf('*') != -1) { if (!uid || uid.indexOf('*') != -1) {
const ret = { const ret = {
...extendData, ...extendData,
@@ -52,7 +51,7 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
} }
return ret return ret
} }
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) } const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) }
return OB11Constructor.stranger(data) return OB11Constructor.stranger(data)
} }
} }

View File

@@ -1,17 +1,16 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../quick-operation' import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../../helper/quickOperation'
import { log } from '@/common/utils'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload{ interface Payload {
context: QuickOperationEvent, context: QuickOperationEvent,
operation: QuickOperation operation: QuickOperation
} }
export class GoCQHTTHandleQuickOperation extends BaseAction<Payload, null>{ export class GoCQHTTHandleQuickOperation 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(payload.context, payload.operation).then().catch(log); handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e))
return null return null
} }
} }

View File

@@ -1,6 +1,7 @@
import SendMsg, { convertMessage2List } from '../msg/SendMsg' import SendMsg from '../msg/SendMsg'
import { OB11PostSendMsg } from '../../types' import { OB11PostSendMsg } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { convertMessage2List } from '../../helper/createMessage'
export class GoCQHTTPSendForwardMsg extends SendMsg { export class GoCQHTTPSendForwardMsg extends SendMsg {
actionName = ActionName.GoCQHTTP_SendForwardMsg actionName = ActionName.GoCQHTTP_SendForwardMsg

View File

@@ -1,7 +1,6 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '@/ntqqapi/api/group' import { MessageUnique } from '@/common/utils/messageUnique'
import { MessageUnique } from '@/common/utils/MessageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
@@ -18,7 +17,7 @@ export default class GoCQHTTPSetEssenceMsg extends BaseAction<Payload, any> {
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')
} }
return await NTQQGroupApi.addGroupEssence( return await this.ctx.ntGroupApi.addGroupEssence(
msg.Peer.peerUid, msg.Peer.peerUid,
msg.MsgId msg.MsgId
) )

View File

@@ -5,8 +5,7 @@ import { SendMsgElementConstructor } from '@/ntqqapi/constructor'
import { ChatType, SendFileElement } from '@/ntqqapi/types' import { ChatType, SendFileElement } from '@/ntqqapi/types'
import { uri2local } from '@/common/utils' import { uri2local } from '@/common/utils'
import { Peer } from '@/ntqqapi/types' import { Peer } from '@/ntqqapi/types'
import { sendMsg } from '../msg/SendMsg' import { sendMsg } from '../../helper/createMessage'
import { NTQQUserApi, NTQQFriendApi } from '@/ntqqapi/api'
interface Payload { interface Payload {
user_id: number | string user_id: number | string
@@ -29,8 +28,8 @@ export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
if (!downloadResult.success) { if (!downloadResult.success) {
throw new Error(downloadResult.errMsg) throw new Error(downloadResult.errMsg)
} }
const sendFileEle = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id) const sendFileEle = await SendMsgElementConstructor.file(this.ctx, downloadResult.path, payload.name, payload.folder_id)
await sendMsg({ await sendMsg(this.ctx, {
chatType: ChatType.group, chatType: ChatType.group,
peerUid: payload.group_id?.toString()!, peerUid: payload.group_id?.toString()!,
}, [sendFileEle], [], true) }, [sendFileEle], [], true)
@@ -43,11 +42,11 @@ export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> {
async getPeer(payload: Payload): Promise<Peer> { async getPeer(payload: Payload): Promise<Peer> {
if (payload.user_id) { if (payload.user_id) {
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString()) const peerUid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!peerUid) { if (!peerUid) {
throw `私聊${payload.user_id}不存在` throw `私聊${payload.user_id}不存在`
} }
const isBuddy = await NTQQFriendApi.isBuddy(peerUid) const isBuddy = await this.ctx.ntFriendApi.isBuddy(peerUid)
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid } return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid }
} }
throw '缺少参数 user_id' throw '缺少参数 user_id'
@@ -63,8 +62,8 @@ export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> {
if (!downloadResult.success) { if (!downloadResult.success) {
throw new Error(downloadResult.errMsg) throw new Error(downloadResult.errMsg)
} }
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name) const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(this.ctx, downloadResult.path, payload.name)
await sendMsg(peer, [sendFileEle], [], true) await sendMsg(this.ctx, peer, [sendFileEle], [], true)
return null return null
} }
} }

View File

@@ -1,4 +1,4 @@
import { GroupEssenceMsgRet, WebApi } from '@/ntqqapi/api' import { GroupEssenceMsgRet } from '@/ntqqapi/api'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -12,13 +12,5 @@ export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet
protected async _handle(payload: PayloadType) { protected async _handle(payload: PayloadType) {
throw '此 api 暂不支持' throw '此 api 暂不支持'
const ret = await WebApi.getGroupEssenceMsg(payload.group_id.toString(), payload.pages?.toString() || '0')
if (!ret) {
throw new Error('获取失败')
}
// ret.map((item) => {
//
// })
return ret
} }
} }

View File

@@ -1,4 +1,4 @@
import { WebApi, WebHonorType } from '@/ntqqapi/api' import { WebHonorType } from '@/ntqqapi/api'
import { ActionName } from '../types' import { ActionName } from '../types'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
@@ -18,6 +18,6 @@ export class GetGroupHonorInfo extends BaseAction<Payload, Array<any>> {
if (!payload.type) { if (!payload.type) {
payload.type = WebHonorType.ALL payload.type = WebHonorType.ALL
} }
return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type) return await this.ctx.ntWebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type)
} }
} }

View File

@@ -2,7 +2,6 @@ import { OB11Group } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '@/ntqqapi/api'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@@ -12,7 +11,7 @@ class GetGroupInfo extends BaseAction<Payload, OB11Group> {
actionName = ActionName.GetGroupInfo actionName = ActionName.GetGroupInfo
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const group = (await NTQQGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString()) const group = (await this.ctx.ntGroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString())
if (group) { if (group) {
return OB11Constructor.group(group) return OB11Constructor.group(group)
} else { } else {

View File

@@ -2,7 +2,6 @@ import { OB11Group } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api'
interface Payload { interface Payload {
no_cache: boolean | string no_cache: boolean | string
@@ -12,7 +11,7 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
actionName = ActionName.GetGroupList actionName = ActionName.GetGroupList
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const groupList = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload?.no_cache === 'true') const groupList = await this.ctx.ntGroupApi.getGroups(payload?.no_cache === true || payload?.no_cache === 'true')
return OB11Constructor.groups(groupList) return OB11Constructor.groups(groupList)
} }
} }

View File

@@ -1,10 +1,9 @@
import { OB11GroupMember } from '../../types'
import { getGroupMember, getSelfUid } from '@/common/data'
import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { OB11GroupMember } from '../../types'
import { OB11Constructor } from '../../constructor'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQUserApi, WebApi } from '@/ntqqapi/api' import { selfInfo } from '@/common/globalVars'
import { isNull } from '@/common/utils/helper' import { isNullable } from 'cosmokit'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@@ -15,18 +14,18 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo actionName = ActionName.GetGroupMemberInfo
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), payload.user_id.toString())
if (member) { if (member) {
if (isNull(member.sex)) { if (isNullable(member.sex)) {
//log('获取群成员详细信息') //log('获取群成员详细信息')
const info = await NTQQUserApi.getUserDetailInfo(member.uid, true) const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid, true)
//log('群成员详细信息结果', info) //log('群成员详细信息结果', info)
Object.assign(member, info) Object.assign(member, info)
} }
const ret = OB11Constructor.groupMember(payload.group_id.toString(), member) const ret = OB11Constructor.groupMember(payload.group_id.toString(), member)
const self = await getGroupMember(payload.group_id.toString(), getSelfUid()) const self = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), selfInfo.uid)
if (self?.role === 3 || self?.role === 4) { if (self?.role === 3 || self?.role === 4) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()) const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString())
const target = webGroupMembers.find(e => e?.uin && e.uin === ret.user_id) const target = webGroupMembers.find(e => e?.uin && e.uin === ret.user_id)
if (target) { if (target) {
ret.join_time = target.join_time ret.join_time = target.join_time

View File

@@ -2,8 +2,7 @@ import { OB11GroupMember } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi, WebApi } from '@/ntqqapi/api' import { selfInfo } from '@/common/globalVars'
import { getSelfUid } from '@/common/data'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@@ -14,7 +13,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList actionName = ActionName.GetGroupMemberList
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()) const groupMembers = await this.ctx.ntGroupApi.getGroupMembers(payload.group_id.toString())
const groupMembersArr = Array.from(groupMembers.values()) const groupMembersArr = Array.from(groupMembers.values())
let _groupMembers = groupMembersArr.map(item => { let _groupMembers = groupMembersArr.map(item => {
@@ -31,11 +30,11 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]) MemberMap.set(_groupMembers[i].user_id, _groupMembers[i])
} }
const selfRole = groupMembers.get(getSelfUid())?.role const selfRole = groupMembers.get(selfInfo.uid)?.role
const isPrivilege = selfRole === 3 || selfRole === 4 const isPrivilege = selfRole === 3 || selfRole === 4
if (isPrivilege) { if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()) const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString())
for (let i = 0, len = webGroupMembers.length; i < len; i++) { for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) { if (!webGroupMembers[i]?.uin) {
continue continue

View File

@@ -1,7 +1,6 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { GroupRequestOperateTypes } from '../../../ntqqapi/types' import { GroupRequestOperateTypes } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
flag: string flag: string
@@ -15,7 +14,7 @@ export default class SetGroupAddRequest extends BaseAction<Payload, null> {
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const flag = payload.flag.toString() const flag = payload.flag.toString()
const approve = payload.approve?.toString() !== 'false' const approve = payload.approve?.toString() !== 'false'
await NTQQGroupApi.handleGroupRequest(flag, await this.ctx.ntGroupApi.handleGroupRequest(flag,
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject, approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
payload.reason || '' payload.reason || ''
) )

View File

@@ -1,8 +1,6 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { getGroupMember } from '../../../common/data' import { GroupMemberRole } from '@/ntqqapi/types'
import { GroupMemberRole } from '../../../ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -14,12 +12,12 @@ export default class SetGroupAdmin extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupAdmin actionName = ActionName.SetGroupAdmin
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await getGroupMember(payload.group_id, payload.user_id) const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
const enable = payload.enable.toString() === 'true' const enable = payload.enable.toString() === 'true'
if (!member) { if (!member) {
throw `群成员${payload.user_id}不存在` throw `群成员${payload.user_id}不存在`
} }
await NTQQGroupApi.setMemberRole( await this.ctx.ntGroupApi.setMemberRole(
payload.group_id.toString(), payload.group_id.toString(),
member.uid, member.uid,
enable ? GroupMemberRole.admin : GroupMemberRole.normal, enable ? GroupMemberRole.admin : GroupMemberRole.normal,

View File

@@ -1,7 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { getGroupMember } from '../../../common/data'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -13,11 +11,11 @@ export default class SetGroupBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupBan actionName = ActionName.SetGroupBan
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await getGroupMember(payload.group_id, payload.user_id) const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
if (!member) { if (!member) {
throw `群成员${payload.user_id}不存在` throw `群成员${payload.user_id}不存在`
} }
await NTQQGroupApi.banMember(payload.group_id.toString(), [ await this.ctx.ntGroupApi.banMember(payload.group_id.toString(), [
{ uid: member.uid, timeStamp: parseInt(payload.duration.toString()) }, { uid: member.uid, timeStamp: parseInt(payload.duration.toString()) },
]) ])
return null return null

View File

@@ -1,7 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { getGroupMember } from '../../../common/data'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -13,11 +11,11 @@ export default class SetGroupCard extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupCard actionName = ActionName.SetGroupCard
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await getGroupMember(payload.group_id, payload.user_id) const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
if (!member) { if (!member) {
throw `群成员${payload.user_id}不存在` throw `群成员${payload.user_id}不存在`
} }
await NTQQGroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || '') await this.ctx.ntGroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || '')
return null return null
} }
} }

View File

@@ -1,7 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { getGroupMember } from '../../../common/data'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -13,11 +11,11 @@ export default class SetGroupKick extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupKick actionName = ActionName.SetGroupKick
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const member = await getGroupMember(payload.group_id, payload.user_id) const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
if (!member) { if (!member) {
throw `群成员${payload.user_id}不存在` throw `群成员${payload.user_id}不存在`
} }
await NTQQGroupApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request) await this.ctx.ntGroupApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request)
return null return null
} }
} }

View File

@@ -1,7 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
import { log } from '../../../common/utils/log'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -13,9 +11,9 @@ export default class SetGroupLeave extends BaseAction<Payload, any> {
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {
try { try {
await NTQQGroupApi.quitGroup(payload.group_id.toString()) await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString())
} catch (e) { } catch (e) {
log('退群失败', e) this.ctx.logger.error('退群失败', e)
throw e throw e
} }
} }

View File

@@ -1,6 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -11,7 +10,7 @@ export default class SetGroupName extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupName actionName = ActionName.SetGroupName
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
await NTQQGroupApi.setGroupName(payload.group_id.toString(), payload.group_name) await this.ctx.ntGroupApi.setGroupName(payload.group_id.toString(), payload.group_name)
return null return null
} }
} }

View File

@@ -1,6 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface Payload { interface Payload {
group_id: number group_id: number
@@ -12,7 +11,7 @@ export default class SetGroupWholeBan extends BaseAction<Payload, null> {
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const enable = payload.enable.toString() === 'true' const enable = payload.enable.toString() === 'true'
await NTQQGroupApi.banGroup(payload.group_id.toString(), enable) await this.ctx.ntGroupApi.banGroup(payload.group_id.toString(), enable)
return null return null
} }
} }

View File

@@ -54,71 +54,70 @@ import GoCQHTTPSetEssenceMsg from './go-cqhttp/SetEssenceMsg'
import GoCQHTTPDelEssenceMsg from './go-cqhttp/DelEssenceMsg' import GoCQHTTPDelEssenceMsg from './go-cqhttp/DelEssenceMsg'
import GetEvent from './llonebot/GetEvent' import GetEvent from './llonebot/GetEvent'
import { GoCQHTTPDelGroupFile } from './go-cqhttp/DelGroupFile' import { GoCQHTTPDelGroupFile } from './go-cqhttp/DelGroupFile'
import type Adapter from '../adapter'
export function initActionMap(adapter: Adapter) {
export const actionHandlers = [ const actionHandlers = [
new GetFile(), new GetFile(adapter),
new Debug(), new Debug(adapter),
new GetConfigAction(), new GetConfigAction(adapter),
new SetConfigAction(), new SetConfigAction(adapter),
new GetGroupAddRequest(), new GetGroupAddRequest(adapter),
new SetQQAvatar(), new SetQQAvatar(adapter),
new GetFriendWithCategory(), new GetFriendWithCategory(adapter),
new GetEvent(), new GetEvent(adapter),
// onebot11 // onebot11
new SendLike(), new SendLike(adapter),
new GetMsg(), new GetMsg(adapter),
new GetLoginInfo(), new GetLoginInfo(adapter),
new GetFriendList(), new GetFriendList(adapter),
new GetGroupList(), new GetGroupList(adapter),
new GetGroupInfo(), new GetGroupInfo(adapter),
new GetGroupMemberList(), new GetGroupMemberList(adapter),
new GetGroupMemberInfo(), new GetGroupMemberInfo(adapter),
new SendGroupMsg(), new SendGroupMsg(adapter),
new SendPrivateMsg(), new SendPrivateMsg(adapter),
new SendMsg(), new SendMsg(adapter),
new DeleteMsg(), new DeleteMsg(adapter),
new SetGroupAddRequest(), new SetGroupAddRequest(adapter),
new SetFriendAddRequest(), new SetFriendAddRequest(adapter),
new SetGroupLeave(), new SetGroupLeave(adapter),
new GetVersionInfo(), new GetVersionInfo(adapter),
new CanSendRecord(), new CanSendRecord(adapter),
new CanSendImage(), new CanSendImage(adapter),
new GetStatus(), new GetStatus(adapter),
new SetGroupWholeBan(), new SetGroupWholeBan(adapter),
new SetGroupBan(), new SetGroupBan(adapter),
new SetGroupKick(), new SetGroupKick(adapter),
new SetGroupAdmin(), new SetGroupAdmin(adapter),
new SetGroupName(), new SetGroupName(adapter),
new SetGroupCard(), new SetGroupCard(adapter),
new GetImage(), new GetImage(adapter),
new GetRecord(), new GetRecord(adapter),
new CleanCache(), new CleanCache(adapter),
new GetCookies(), new GetCookies(adapter),
new SetMsgEmojiLike(), new SetMsgEmojiLike(adapter),
new ForwardFriendSingleMsg(), new ForwardFriendSingleMsg(adapter),
new ForwardGroupSingleMsg(), new ForwardGroupSingleMsg(adapter),
//以下为go-cqhttp api //以下为go-cqhttp api
new GetGroupEssence(), new GetGroupEssence(adapter),
new GetGroupHonorInfo(), new GetGroupHonorInfo(adapter),
new GoCQHTTPSendForwardMsg(), new GoCQHTTPSendForwardMsg(adapter),
new GoCQHTTPSendGroupForwardMsg(), new GoCQHTTPSendGroupForwardMsg(adapter),
new GoCQHTTPSendPrivateForwardMsg(), new GoCQHTTPSendPrivateForwardMsg(adapter),
new GoCQHTTPGetStrangerInfo(), new GoCQHTTPGetStrangerInfo(adapter),
new GoCQHTTPDownloadFile(), new GoCQHTTPDownloadFile(adapter),
new GetGuildList(), new GetGuildList(adapter),
new GoCQHTTPMarkMsgAsRead(), new GoCQHTTPMarkMsgAsRead(adapter),
new GoCQHTTPUploadGroupFile(), new GoCQHTTPUploadGroupFile(adapter),
new GoCQHTTPUploadPrivateFile(), new GoCQHTTPUploadPrivateFile(adapter),
new GoCQHTTPGetGroupMsgHistory(), new GoCQHTTPGetGroupMsgHistory(adapter),
new GoCQHTTGetForwardMsgAction(), new GoCQHTTGetForwardMsgAction(adapter),
new GoCQHTTHandleQuickOperation(), new GoCQHTTHandleQuickOperation(adapter),
new GoCQHTTPSetEssenceMsg(), new GoCQHTTPSetEssenceMsg(adapter),
new GoCQHTTPDelEssenceMsg(), new GoCQHTTPDelEssenceMsg(adapter),
new GoCQHTTPDelGroupFile() new GoCQHTTPDelGroupFile(adapter)
] ]
function initActionMap() {
const actionMap = new Map<string, BaseAction<any, any>>() const actionMap = new Map<string, BaseAction<any, any>>()
for (const action of actionHandlers) { for (const action of actionHandlers) {
actionMap.set(action.actionName, action) actionMap.set(action.actionName, action)
@@ -128,5 +127,3 @@ function initActionMap() {
return actionMap return actionMap
} }
export const actionMap = initActionMap()

View File

@@ -1,8 +1,7 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { Config } from '../../../common/types' import { Config } from '@/common/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { setConfig } from '../../../main/setConfig' import { getConfigUtil } from '@/common/config'
import { getConfigUtil } from '../../../common/config'
export class GetConfigAction extends BaseAction<null, Config> { export class GetConfigAction extends BaseAction<null, Config> {
actionName = ActionName.GetConfig actionName = ActionName.GetConfig
@@ -14,6 +13,6 @@ export class GetConfigAction extends BaseAction<null, Config> {
export class SetConfigAction extends BaseAction<Config, void> { export class SetConfigAction extends BaseAction<Config, void> {
actionName = ActionName.SetConfig actionName = ActionName.SetConfig
protected async _handle(payload: Config): Promise<void> { protected async _handle(payload: Config): Promise<void> {
setConfig(payload).then() getConfigUtil().setConfig(payload)
} }
} }

View File

@@ -1,16 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
// import * as ntqqApi from "../../../ntqqapi/api";
import {
NTQQMsgApi,
NTQQFriendApi,
NTQQGroupApi,
NTQQUserApi,
NTQQFileApi,
NTQQFileCacheApi,
NTQQWindowApi,
} from '../../../ntqqapi/api'
import { ActionName } from '../types' import { ActionName } from '../types'
import { log } from '../../../common/utils/log'
interface Payload { interface Payload {
method: string method: string
@@ -21,13 +10,13 @@ export default class Debug extends BaseAction<Payload, any> {
actionName = ActionName.Debug actionName = ActionName.Debug
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {
log('debug call ntqq api', payload) this.ctx.logger.info('debug call ntqq api', payload)
const ntqqApi = [NTQQMsgApi, NTQQFriendApi, NTQQGroupApi, NTQQUserApi, NTQQFileApi, NTQQFileCacheApi, NTQQWindowApi] const { ntMsgApi, ntFileApi, ntFileCacheApi, ntFriendApi, ntGroupApi, ntUserApi, ntWindowApi } = this.ctx
const ntqqApi = [ntMsgApi, ntFriendApi, ntGroupApi, ntUserApi, ntFileApi, ntFileCacheApi, ntWindowApi]
for (const ntqqApiClass of ntqqApi) { for (const ntqqApiClass of ntqqApi) {
//log('ntqqApiClass', ntqqApiClass) const method = ntqqApiClass[payload.method] as Function
const method = ntqqApiClass[payload.method]
if (method) { if (method) {
const result = method(...payload.args) const result = method.apply(ntqqApiClass, payload.args)
if (method.constructor.name === 'AsyncFunction') { if (method.constructor.name === 'AsyncFunction') {
return await result return await result
} }
@@ -35,8 +24,5 @@ export default class Debug extends BaseAction<Payload, any> {
} }
} }
throw `${payload.method}方法 不存在` throw `${payload.method}方法 不存在`
// const info = await NTQQApi.getUserDetailInfo(friends[0].uid);
// return info
} }
} }

View File

@@ -1,8 +1,10 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getHttpEvent } from '../../server/event-for-http' import { getHttpEvent } from '../../helper/eventForHttp'
import { PostEventType } from '../../server/post-ob11-event' import { OB11Message } from '../../types'
// import { log } from "../../../common/utils"; import { OB11BaseEvent } from '../../event/OB11BaseEvent'
type PostEventType = OB11BaseEvent | OB11Message
interface Payload { interface Payload {
key: string key: string
@@ -14,10 +16,10 @@ export default class GetEvent extends BaseAction<Payload, PostEventType[]> {
protected async _handle(payload: Payload): Promise<PostEventType[]> { protected async _handle(payload: Payload): Promise<PostEventType[]> {
let key = '' let key = ''
if (payload.key) { if (payload.key) {
key = payload.key; key = payload.key
} }
let timeout = parseInt(payload.timeout?.toString()) || 0; let timeout = parseInt(payload.timeout?.toString()) || 0
let evts = await getHttpEvent(key,timeout); let evts = await getHttpEvent(key, timeout)
return evts; return evts
} }
} }

View File

@@ -1,8 +1,6 @@
import { GroupNotify, GroupNotifyStatus } from '../../../ntqqapi/types' import { GroupNotify, GroupNotifyStatus } from '@/ntqqapi/types'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQUserApi } from '../../../ntqqapi/api/user'
import { NTQQGroupApi } from '../../../ntqqapi/api/group'
interface OB11GroupRequestNotify { interface OB11GroupRequestNotify {
group_id: number group_id: number
@@ -14,11 +12,11 @@ export default class GetGroupAddRequest extends BaseAction<null, OB11GroupReques
actionName = ActionName.GetGroupIgnoreAddRequest actionName = ActionName.GetGroupIgnoreAddRequest
protected async _handle(payload: null): Promise<OB11GroupRequestNotify[]> { protected async _handle(payload: null): Promise<OB11GroupRequestNotify[]> {
const data = await NTQQGroupApi.getGroupIgnoreNotifies() const data = await this.ctx.ntGroupApi.getGroupIgnoreNotifies()
const notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.WAIT_HANDLE) const notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.WAIT_HANDLE)
const returnData: OB11GroupRequestNotify[] = [] const returnData: OB11GroupRequestNotify[] = []
for (const notify of notifies) { for (const notify of notifies) {
const uin = await NTQQUserApi.getUinByUid(notify.user1.uid) const uin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
returnData.push({ returnData.push({
group_id: parseInt(notify.group.groupCode), group_id: parseInt(notify.group.groupCode),
user_id: parseInt(uin), user_id: parseInt(uin),

View File

@@ -1,9 +1,7 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import * as fs from 'node:fs' import * as fs from 'node:fs'
import { NTQQUserApi } from '../../../ntqqapi/api/user'
import { checkFileReceived, uri2local } from '../../../common/utils/file' import { checkFileReceived, uri2local } from '../../../common/utils/file'
// import { log } from "../../../common/utils";
interface Payload { interface Payload {
file: string file: string
@@ -19,22 +17,22 @@ export default class SetAvatar extends BaseAction<Payload, null> {
} }
if (path) { if (path) {
await checkFileReceived(path, 5000) // 文件不存在QQ会崩溃需要提前判断 await checkFileReceived(path, 5000) // 文件不存在QQ会崩溃需要提前判断
const ret = await NTQQUserApi.setQQAvatar(path) const ret = await this.ctx.ntUserApi.setQQAvatar(path)
if (!isLocal) { if (!isLocal) {
fs.unlink(path, () => {}) fs.unlink(path, () => { })
} }
if (!ret) { if (!ret) {
throw `头像${payload.file}设置失败,api无返回` throw `头像${payload.file}设置失败,api无返回`
} }
// log(`头像设置返回:${JSON.stringify(ret)}`) // log(`头像设置返回:${JSON.stringify(ret)}`)
if (ret['result'] == 1004022) { if ((ret.result as number) === 1004022) {
throw `头像${payload.file}设置失败,文件可能不是图片格式` throw `头像${payload.file}设置失败,文件可能不是图片格式`
} else if (ret['result'] != 0) { } else if (ret.result !== 0) {
throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}` throw `头像${payload.file}设置失败,未知的错误,${ret['result']}:${ret['errMsg']}`
} }
} else { } else {
if (!isLocal) { if (!isLocal) {
fs.unlink(path, () => {}) fs.unlink(path, () => { })
} }
throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在` throw `头像${payload.file}设置失败,无法获取头像,文件可能不存在`
} }

View File

@@ -1,7 +1,6 @@
import { ActionName } from '../types' import { ActionName } from '../types'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { NTQQMsgApi } from '@/ntqqapi/api/msg' import { MessageUnique } from '@/common/utils/messageUnique'
import { MessageUnique } from '@/common/utils/MessageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
@@ -18,7 +17,7 @@ class DeleteMsg extends BaseAction<Payload, void> {
if (!msg) { if (!msg) {
throw `消息${payload.message_id}不存在` throw `消息${payload.message_id}不存在`
} }
await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]) await this.ctx.ntMsgApi.recallMsg(msg.Peer, [msg.MsgId])
} }
} }

View File

@@ -1,9 +1,8 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { NTQQMsgApi, NTQQUserApi } from '@/ntqqapi/api'
import { ChatType } from '@/ntqqapi/types' import { ChatType } from '@/ntqqapi/types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { Peer } from '@/ntqqapi/types' import { Peer } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/MessageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
message_id: number | string message_id: number | string
@@ -14,7 +13,7 @@ interface Payload {
abstract class ForwardSingleMsg extends BaseAction<Payload, null> { abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
protected async getTargetPeer(payload: Payload): Promise<Peer> { protected async getTargetPeer(payload: Payload): Promise<Peer> {
if (payload.user_id) { if (payload.user_id) {
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString()) const peerUid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!peerUid) { if (!peerUid) {
throw new Error(`无法找到私聊对象${payload.user_id}`) throw new Error(`无法找到私聊对象${payload.user_id}`)
} }
@@ -32,7 +31,7 @@ abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
throw new Error(`无法找到消息${payload.message_id}`) throw new Error(`无法找到消息${payload.message_id}`)
} }
const peer = await this.getTargetPeer(payload) const peer = await this.getTargetPeer(payload)
const ret = await NTQQMsgApi.forwardMsg(msg.Peer, peer, [msg.MsgId]) const ret = await this.ctx.ntMsgApi.forwardMsg(msg.Peer, peer, [msg.MsgId])
if (ret.result !== 0) { if (ret.result !== 0) {
throw new Error(`转发消息失败 ${ret.errMsg}`) throw new Error(`转发消息失败 ${ret.errMsg}`)
} }

View File

@@ -1,10 +1,8 @@
import BaseAction from '../BaseAction'
import { OB11Message } from '../../types' import { OB11Message } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQMsgApi } from '@/ntqqapi/api' import { MessageUnique } from '@/common/utils/messageUnique'
import { MessageUnique } from '@/common/utils/MessageUnique'
import { getMsgCache } from '@/common/data'
export interface PayloadType { export interface PayloadType {
message_id: number | string message_id: number | string
@@ -16,7 +14,6 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
actionName = ActionName.GetMsg actionName = ActionName.GetMsg
protected async _handle(payload: PayloadType) { protected async _handle(payload: PayloadType) {
// log("history msg ids", Object.keys(msgHistory));
if (!payload.message_id) { if (!payload.message_id) {
throw '参数message_id不能为空' throw '参数message_id不能为空'
} }
@@ -30,8 +27,8 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
peerUid: msgIdWithPeer.Peer.peerUid, peerUid: msgIdWithPeer.Peer.peerUid,
chatType: msgIdWithPeer.Peer.chatType chatType: msgIdWithPeer.Peer.chatType
} }
const msg = getMsgCache(msgIdWithPeer.MsgId) ?? (await NTQQMsgApi.getMsgsByMsgId(peer, [msgIdWithPeer.MsgId])).msgList[0] const msg = this.adapter.getMsgCache(msgIdWithPeer.MsgId) ?? (await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [msgIdWithPeer.MsgId])).msgList[0]
const retMsg = await OB11Constructor.message(msg) const retMsg = await OB11Constructor.message(this.ctx, msg)
retMsg.message_id = MessageUnique.createMsg(peer, msg.msgId)! retMsg.message_id = MessageUnique.createMsg(peer, msg.msgId)!
retMsg.message_seq = retMsg.message_id retMsg.message_seq = retMsg.message_id
retMsg.real_id = retMsg.message_id retMsg.real_id = retMsg.message_id

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