Compare commits

...

63 Commits

Author SHA1 Message Date
idranme
5c68d4de84 Merge pull request #404 from LLOneBot/dev
3.31.10
2024-09-07 21:55:04 +08:00
idranme
c1c22e872e chore: v3.31.10 2024-09-07 21:52:34 +08:00
idranme
709c0b6f7b fix 2024-09-07 21:51:17 +08:00
idranme
dd34286b43 refactor 2024-09-07 17:45:36 +08:00
idranme
80487e31f5 chore 2024-09-07 03:03:33 +08:00
idranme
bdf1c7fd5f chore 2024-09-07 02:56:59 +08:00
idranme
285fc1d33d Merge pull request #398 from LLOneBot/dev
3.31.9
2024-09-06 23:12:47 +08:00
idranme
e14ba108fc chore: v3.31.9 2024-09-06 23:06:43 +08:00
idranme
e0be0bcc77 fix 2024-09-06 23:04:17 +08:00
idranme
c24fa6cea1 chore: improve code quality
chore: improve code quality
2024-09-06 23:04:17 +08:00
idranme
04b2a323a7 chore: improve code quality 2024-09-06 23:03:04 +08:00
idranme
970f1a98ec chore: improve code quality
chore: improve code quality
2024-09-06 23:03:04 +08:00
idranme
3064a6eb7c chore: improve code quality 2024-09-06 22:59:14 +08:00
idranme
f93e2b5a95 chore: improve code quality
chore: improve code quality
2024-09-06 22:59:14 +08:00
idranme
e185e700b7 chore: improve code quality
chore: improve code quality

chore: improve code quality
2024-09-06 22:58:00 +08:00
idranme
eae6e09e22 optimize 2024-09-05 17:16:42 +08:00
idranme
e204bb0957 Merge pull request #395 from LLOneBot/dev
3.31.8
2024-09-05 15:00:57 +08:00
idranme
ed546ace3d chore: v3.31.8 2024-09-05 15:00:05 +08:00
idranme
3c79cffa42 optimize 2024-09-05 14:58:52 +08:00
idranme
acce444dee optimize 2024-09-05 02:26:42 +08:00
idranme
f359e3ea9d fix 2024-09-05 02:20:23 +08:00
idranme
fe99da985f Merge pull request #394 from LLOneBot/dev
3.31.7
2024-09-04 20:35:08 +08:00
idranme
58d5de572c chore: v3.31.7 2024-09-04 20:32:44 +08:00
idranme
b2088824cc feat 2024-09-04 17:15:41 +08:00
idranme
fffa664400 fix: reply message segment 2024-09-04 16:18:48 +08:00
idranme
02e5222f92 feat: SendGroupNotice 2024-09-04 15:42:10 +08:00
idranme
18d253edf6 fix: GroupMsgEmojiLikeEvent 2024-09-04 13:13:49 +08:00
idranme
da8b5e2429 chore 2024-09-04 13:12:39 +08:00
idranme
502be69bc5 feat: SetOnlineStatus 2024-09-04 01:23:25 +08:00
idranme
273d4133eb refactor 2024-09-04 00:44:41 +08:00
idranme
44bfc5aab9 optimize 2024-09-03 21:46:26 +08:00
idranme
050c9d9b54 fix 2024-09-03 21:43:18 +08:00
idranme
7904f45c20 Merge pull request #392 from LLOneBot/dev
3.31.6
2024-09-03 18:38:07 +08:00
idranme
1afdad1452 chore: v3.31.6 2024-09-03 18:34:30 +08:00
idranme
cd930c43b6 feat: GetGroupRootFiles 2024-09-03 15:14:05 +08:00
idranme
b7efbdf239 fix: ws 2024-09-03 13:16:25 +08:00
idranme
56706f3838 chore 2024-09-03 01:24:21 +08:00
idranme
387c9dcb52 refactor 2024-09-03 01:04:16 +08:00
idranme
a7bb55b31c chore 2024-09-02 19:53:18 +08:00
idranme
fbf09e1db4 chore 2024-09-02 19:48:17 +08:00
idranme
9b98f8f33d optimize 2024-09-02 19:30:23 +08:00
idranme
727f399de6 fix: GetGroupMsgHistory 2024-09-02 19:24:27 +08:00
idranme
e03b82fb44 optimize: ci 2024-09-02 18:28:21 +08:00
idranme
ba413b9581 Merge pull request #390 from LLOneBot/dev
3.31.5
2024-09-02 16:42:35 +08:00
idranme
abcec99ce0 chore: v3.31.5 2024-09-02 16:39:36 +08:00
idranme
a7da7ab598 optimize 2024-09-02 01:58:31 +08:00
idranme
5cc8a2b96e fix 2024-09-02 01:46:08 +08:00
idranme
f0d8c851d4 optimize 2024-09-02 01:24:15 +08:00
idranme
828b20e0e8 optimize 2024-09-02 01:05:58 +08:00
idranme
3570349fcd optimize 2024-09-02 00:42:35 +08:00
idranme
ad74854e42 fix 2024-09-01 20:28:12 +08:00
idranme
15e7afed62 Merge pull request #385 from LLOneBot/dev
3.31.4
2024-09-01 18:50:38 +08:00
idranme
bf71328650 chore: v3.31.4 2024-09-01 18:50:09 +08:00
idranme
b3299ba1e3 chore 2024-09-01 15:39:37 +08:00
idranme
d36ea93e63 refactor 2024-09-01 15:26:34 +08:00
idranme
0bd3f8f1a2 feat 2024-09-01 15:26:11 +08:00
idranme
4bf79e021e Merge pull request #383 from LLOneBot/dev
3.31.3
2024-09-01 00:36:41 +08:00
idranme
2dac109e58 chore: v3.31.3 2024-09-01 00:34:08 +08:00
idranme
2637a5da6d chore 2024-08-31 22:59:42 +08:00
idranme
f8b2be246f optimize 2024-08-31 22:55:26 +08:00
idranme
44921e85ad chore 2024-08-31 19:46:35 +08:00
idranme
388e016365 optimize 2024-08-31 19:41:48 +08:00
idranme
a2056a43f3 fix 2024-08-31 01:29:44 +08:00
102 changed files with 1788 additions and 2378 deletions

View File

@@ -1,4 +1,4 @@
name: 'publish' name: Publish
on: on:
push: push:
tags: tags:
@@ -8,31 +8,36 @@ jobs:
build-and-publish: build-and-publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: setup node - name: Setup node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
- name: install dependenies - name: Install dependenies
run: | run: |
export ELECTRON_SKIP_BINARY_DOWNLOAD=1 export ELECTRON_SKIP_BINARY_DOWNLOAD=1
npm install npm install
- name: build - name: Build
run: npm run build run: npm run build
- name: zip - name: Compress
run: | run: |
sudo apt install zip -y sudo apt install zip -y
cd ./dist/ cd ./dist/
zip -r ../LLOneBot.zip ./* zip -r ../LLOneBot.zip ./*
- name: publish - name: Extract version from tag
id: get-version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
- name: Release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
artifacts: 'LLOneBot.zip' artifacts: 'LLOneBot.zip'
draft: true draft: true
token: ${{ secrets.RELEASE_TOKEN }} token: ${{ secrets.RELEASE_TOKEN }}
name: LLOneBot v${{ steps.get-version.outputs.VERSION }}

View File

@@ -15,10 +15,6 @@ TG 群:<https://t.me/+nLZEnpne-pQ1OWFl>
<img src="./doc/image/setting.png" width="400px" alt="设置界面"/> <img src="./doc/image/setting.png" width="400px" alt="设置界面"/>
## HTTP 调用示例
<img src="./doc/image/example.jpg" width="500px" alt="HTTP调用示例"/>
## 支持的 API ## 支持的 API
<https://llonebot.github.io/zh-CN/develop/api> <https://llonebot.github.io/zh-CN/develop/api>
@@ -31,10 +27,10 @@ TG 群:<https://t.me/+nLZEnpne-pQ1OWFl>
- [NapCatQQ](https://github.com/NapNeko/NapCatQQ) - [NapCatQQ](https://github.com/NapNeko/NapCatQQ)
- [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) - [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
- [chronocat](https://github.com/chrononeko/chronocat) - [Chronocat](https://github.com/chrononeko/chronocat)
- [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot) - [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot)
- [silk-wasm](https://github.com/idranme/silk-wasm) - [silk-wasm](https://github.com/idranme/silk-wasm)
## 友链 ## 友链
- [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) 一款用C#实现的NTQQ纯协议跨平台QQ机器人框架 - [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core): An Implementation of NTQQ Protocol

View File

@@ -1,6 +1,7 @@
import cp from 'vite-plugin-cp' import cp from 'vite-plugin-cp'
import path from 'node:path' import path from 'node:path'
import './scripts/gen-manifest' import './scripts/gen-manifest'
import type { ElectronViteConfig } from 'electron-vite'
const external = [ const external = [
'silk-wasm', 'silk-wasm',
@@ -12,7 +13,7 @@ function genCpModule(module: string) {
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false } return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false }
} }
let config = { const config: ElectronViteConfig = {
main: { main: {
build: { build: {
outDir: 'dist/main', outDir: 'dist/main',
@@ -39,9 +40,6 @@ let config = {
...external.map(genCpModule), ...external.map(genCpModule),
{ src: './manifest.json', dest: 'dist' }, { src: './manifest.json', dest: 'dist' },
{ src: './icon.webp', dest: 'dist' }, { src: './icon.webp', dest: 'dist' },
// { src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
// { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
// { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
], ],
}), }),
], ],

View File

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

View File

@@ -22,7 +22,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"cosmokit": "^1.6.2", "cosmokit": "^1.6.2",
"express": "^4.19.2", "express": "^4.19.2",
"fast-xml-parser": "^4.4.1", "fast-xml-parser": "^4.5.0",
"file-type": "^19.4.1", "file-type": "^19.4.1",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"minato": "^3.5.1", "minato": "^3.5.1",
@@ -38,7 +38,7 @@
"electron": "^31.4.0", "electron": "^31.4.0",
"electron-vite": "^2.3.0", "electron-vite": "^2.3.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vite": "^5.4.2", "vite": "^5.4.3",
"vite-plugin-cp": "^4.0.8" "vite-plugin-cp": "^4.0.8"
}, },
"packageManager": "yarn@4.4.1" "packageManager": "yarn@4.4.1"

View File

@@ -1,25 +1,8 @@
import fs from 'node:fs' import fs from 'node:fs'
import { Config, OB11Config } from './types'
import path from 'node:path' import path from 'node:path'
import { Config, OB11Config } from './types'
import { selfInfo, DATA_DIR } from './globalVars' import { selfInfo, DATA_DIR } from './globalVars'
import { mergeNewProperties } from './utils/misc'
// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
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
@@ -38,7 +21,7 @@ export class ConfigUtil {
} }
reloadConfig(): Config { reloadConfig(): Config {
let ob11Default: OB11Config = { const ob11Default: OB11Config = {
httpPort: 3000, httpPort: 3000,
httpHosts: [], httpHosts: [],
httpSecret: '', httpSecret: '',
@@ -50,9 +33,10 @@ export class ConfigUtil {
enableWsReverse: false, enableWsReverse: false,
messagePostFormat: 'array', messagePostFormat: 'array',
enableHttpHeart: false, enableHttpHeart: false,
enableQOAutoQuote: false enableQOAutoQuote: false,
listenLocalhost: false
} }
let defaultConfig: Config = { const defaultConfig: Config = {
enableLLOB: true, enableLLOB: true,
ob11: ob11Default, ob11: ob11Default,
heartInterval: 60000, heartInterval: 60000,
@@ -83,7 +67,6 @@ export class ConfigUtil {
this.checkOldConfig(jsonData.ob11, jsonData, 'httpPort', 'http') this.checkOldConfig(jsonData.ob11, jsonData, 'httpPort', 'http')
this.checkOldConfig(jsonData.ob11, jsonData, 'httpHosts', 'hosts') this.checkOldConfig(jsonData.ob11, jsonData, 'httpHosts', 'hosts')
this.checkOldConfig(jsonData.ob11, jsonData, 'wsPort', 'wsPort') this.checkOldConfig(jsonData.ob11, jsonData, 'wsPort', 'wsPort')
// console.log("get config", jsonData);
this.config = jsonData this.config = jsonData
return this.config return this.config
} }
@@ -95,15 +78,15 @@ export class ConfigUtil {
} }
private checkOldConfig( private checkOldConfig(
currentConfig: Config | OB11Config, currentConfig: OB11Config,
oldConfig: Config | OB11Config, oldConfig: Config,
currentKey: string, currentKey: 'httpPort' | 'httpHosts' | 'wsPort',
oldKey: string, oldKey: 'http' | 'hosts' | 'wsPort',
) { ) {
// 迁移旧的配置到新配置,避免用户重新填写配置 // 迁移旧的配置到新配置,避免用户重新填写配置
const oldValue = oldConfig[oldKey] const oldValue = oldConfig[oldKey]
if (oldValue) { if (oldValue) {
currentConfig[currentKey] = oldValue Object.assign(currentConfig, { [currentKey]: oldValue })
delete oldConfig[oldKey] delete oldConfig[oldKey]
} }
} }

View File

@@ -11,6 +11,7 @@ export interface OB11Config {
messagePostFormat?: 'array' | 'string' messagePostFormat?: 'array' | 'string'
enableHttpHeart?: boolean enableHttpHeart?: boolean
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息 enableQOAutoQuote: boolean // 快速操作回复自动引用原消息
listenLocalhost: boolean
} }
export interface CheckVersion { export interface CheckVersion {
@@ -34,6 +35,12 @@ export interface Config {
ignoreBeforeLoginMsg?: boolean ignoreBeforeLoginMsg?: boolean
/** 单位为秒 */ /** 单位为秒 */
msgCacheExpire?: number msgCacheExpire?: number
/** @deprecated */
http?: string
/** @deprecated */
hosts?: string[]
/** @deprecated */
wsPort?: string
} }
export interface LLOneBotError { export interface LLOneBotError {

View File

@@ -3,7 +3,6 @@ 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 { TEMP_DIR } from '../globalVars' import { TEMP_DIR } from '../globalVars'
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' import { Context } from 'cordis'
@@ -17,8 +16,8 @@ type Input = string | Readable
function convert(ctx: Context, input: Input, options: FFmpegOptions): Promise<Buffer> function convert(ctx: Context, input: Input, options: FFmpegOptions): Promise<Buffer>
function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath: string): Promise<string> function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath: string): Promise<string>
function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer> | Promise<string> { function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?: string): Promise<Buffer | string> {
return new Promise<any>((resolve, reject) => { return new Promise((resolve, reject) => {
const chunks: Buffer[] = [] const chunks: Buffer[] = []
let command = ffmpeg(input) let command = ffmpeg(input)
.on('error', err => { .on('error', err => {
@@ -38,7 +37,7 @@ function convert(ctx: Context, input: Input, options: FFmpegOptions, outputPath?
if (options.output) { if (options.output) {
command = command.outputOptions(options.output) command = command.outputOptions(options.output)
} }
const ffmpegPath = getConfigUtil().getConfig().ffmpeg const ffmpegPath: string | undefined = ctx.config.ffmpeg
if (ffmpegPath) { if (ffmpegPath) {
command = command.setFfmpegPath(ffmpegPath) command = command.setFfmpegPath(ffmpegPath)
} }
@@ -85,8 +84,8 @@ export async function encodeSilk(ctx: Context, filePath: string) {
let duration = 1 let duration = 1
try { try {
duration = getDuration(silk) / 1000 duration = getDuration(silk) / 1000
} catch (e: any) { } catch (e) {
ctx.logger.warn('获取语音文件时长失败, 默认为1秒', filePath, e.stack) ctx.logger.warn('获取语音文件时长失败, 默认为1秒', filePath, (e as Error).stack)
} }
return { return {
converted: false, converted: false,
@@ -94,8 +93,8 @@ export async function encodeSilk(ctx: Context, filePath: string) {
duration, duration,
} }
} }
} catch (error: any) { } catch (err) {
ctx.logger.error('convert silk failed', error.stack) ctx.logger.error('convert silk failed', (err as Error).stack)
return {} return {}
} }
} }

View File

@@ -1,234 +0,0 @@
import { NodeIQQNTWrapperSession } from '@/ntqqapi/wrapper'
import { randomUUID } from 'node:crypto'
interface Internal_MapKey {
timeout: number
createtime: number
func: (...arg: any[]) => any
checker: ((...args: any[]) => boolean) | undefined
}
export class ListenerClassBase {
[key: string]: string
}
export interface ListenerIBase {
new(listener: any): ListenerClassBase
}
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/common/utils/EventTask.ts#L20
export class NTEventWrapper {
private ListenerMap: { [key: string]: ListenerIBase } | undefined//ListenerName-Unique -> Listener构造函数
private WrapperSession: NodeIQQNTWrapperSession | undefined//WrapperSession
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}
public initialised = false
constructor() {
}
createProxyDispatch(ListenerMainName: string) {
const current = this
return new Proxy({}, {
get(target: any, prop: any, receiver: any) {
// console.log('get', prop, typeof target[prop])
if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod
return (...args: any[]) => {
current.dispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then()
}
}
// 如果方法存在,正常返回
return Reflect.get(target, prop, receiver)
}
})
}
init({ ListenerMap, WrapperSession }: { ListenerMap: { [key: string]: typeof ListenerClassBase }, WrapperSession: NodeIQQNTWrapperSession }) {
this.ListenerMap = ListenerMap
this.WrapperSession = WrapperSession
this.initialised = true
}
createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
const eventNameArr = eventName.split('/')
type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
}
if (eventNameArr.length > 1) {
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '')
const eventName = eventNameArr[1]
//getNodeIKernelGroupListener,GroupService
//console.log('2', eventName)
const services = (this.WrapperSession as unknown as eventType)[serviceName]()
let event = services[eventName]
//重新绑定this
event = event.bind(services)
if (event) {
return event as T
}
return undefined
}
}
createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
const ListenerType = this.ListenerMap![listenerMainName]
let Listener = this.ListenerManger.get(listenerMainName + uniqueCode)
if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName))
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1]
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener'
const addfunc = this.createEventFunction<(listener: T) => number>(Service)
addfunc!(Listener as T)
//console.log(addfunc!(Listener as T))
this.ListenerManger.set(listenerMainName + uniqueCode, Listener)
}
return Listener as T
}
//统一回调清理事件
async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args)
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout)
if (task.createtime + task.timeout < Date.now()) {
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid)
return
}
if (task.checker && task.checker(...args)) {
task.func(...args)
}
})
}
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.createEventFunction<EventType>(EventName)
let complete = false
const Timeouter = setTimeout(() => {
if (!complete) {
reject(new Error('NTEvent EventName:' + EventName + ' timeout'))
}
}, timeout)
const retData = await EventFunc!(...args)
complete = true
resolve(retData)
})
}
async RegisterListen<ListenerType extends (...args: any[]) => void>(ListenerName = '', waitTimes = 1, timeout = 5000, checker: (...args: Parameters<ListenerType>) => boolean) {
return new Promise<Parameters<ListenerType>>((resolve, reject) => {
const ListenerNameList = ListenerName.split('/')
const ListenerMainName = ListenerNameList[0]
const ListenerSubName = ListenerNameList[1]
const id = randomUUID()
let complete = 0
let retData: Parameters<ListenerType> | undefined = undefined
const databack = () => {
if (complete == 0) {
reject(new Error(' ListenerName:' + ListenerName + ' timeout'))
} else {
resolve(retData!)
}
}
const Timeouter = setTimeout(databack, timeout)
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: Parameters<ListenerType>) => {
complete++
retData = args
if (complete >= waitTimes) {
clearTimeout(Timeouter)
databack()
}
}
}
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map())
}
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
this.createListenerFunction(ListenerMainName)
})
}
async CallNormalEvent<EventType extends (...args: any[]) => Promise<any>, ListenerType extends (...args: any[]) => void>
(EventName = '', ListenerName = '', waitTimes = 1, timeout: number = 3000, checker: (...args: Parameters<ListenerType>) => boolean, ...args: Parameters<EventType>) {
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
const id = randomUUID()
let complete = 0
let retData: Parameters<ListenerType> | undefined = undefined
let retEvent: any = {}
const databack = () => {
if (complete == 0) {
reject(new Error('Timeout: NTEvent EventName:' + EventName + ' ListenerName:' + ListenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'))
} else {
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!])
}
}
const ListenerNameList = ListenerName.split('/')
const ListenerMainName = ListenerNameList[0]
const ListenerSubName = ListenerNameList[1]
const Timeouter = setTimeout(databack, timeout)
const eventCallbak = {
timeout: timeout,
createtime: Date.now(),
checker: checker,
func: (...args: any[]) => {
complete++
//console.log('func', ...args)
retData = args as Parameters<ListenerType>
if (complete >= waitTimes) {
clearTimeout(Timeouter)
databack()
}
}
}
if (!this.EventTask.get(ListenerMainName)) {
this.EventTask.set(ListenerMainName, new Map())
}
if (!(this.EventTask.get(ListenerMainName)?.get(ListenerSubName))) {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
}
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
this.createListenerFunction(ListenerMainName)
const EventFunc = this.createEventFunction<EventType>(EventName)
retEvent = await EventFunc!(...(args as any[]))
})
}
}
export const NTEventDispatch = new NTEventWrapper()
// 示例代码 快速创建事件
// let NTEvent = new NTEventWrapper()
// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise<Number>>('NodeIKernelProfileLikeService/GetTest')
// if (TestEvent) {
// TestEvent(true)
// }
// 示例代码 快速创建监听Listener类
// let NTEvent = new NTEventWrapper()
// NTEvent.CreatListenerFunction<NodeIKernelMsgListener>('NodeIKernelMsgListener', 'core')
// 调用接口
//let NTEvent = new NTEventWrapper()
//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise<Number>, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true)
// 注册监听 解除监听
// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb)
// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core')
// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode)
// GetTest('test')
// always模式
// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) })

View File

@@ -142,8 +142,8 @@ export async function uri2local(uri: string, filename?: string): Promise<Uri2Loc
const filePath = path.join(TEMP_DIR, filename) const filePath = path.join(TEMP_DIR, filename)
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) {
const errMsg = `${uri} 下载失败, ${e.message}` const errMsg = `${uri} 下载失败, ${(e as Error).message}`
return { success: false, errMsg, fileName: '', path: '', isLocal: false } return { success: false, errMsg, fileName: '', path: '', isLocal: false }
} }
} }
@@ -175,7 +175,7 @@ export async function copyFolder(sourcePath: string, destPath: string) {
try { try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true }) const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true })
await fsPromise.mkdir(destPath, { recursive: true }) await fsPromise.mkdir(destPath, { recursive: true })
for (let entry of entries) { for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name) const srcPath = path.join(sourcePath, entry.name)
const dstPath = path.join(destPath, entry.name) const dstPath = path.join(destPath, entry.name)
if (entry.isDirectory()) { if (entry.isDirectory()) {

View File

@@ -2,8 +2,9 @@ import fs from 'fs'
import path from 'node:path' import path from 'node:path'
import { getConfigUtil } from '../config' import { getConfigUtil } from '../config'
import { LOG_DIR } from '../globalVars' import { LOG_DIR } from '../globalVars'
import { Dict } from 'cosmokit'
function truncateString(obj: any, maxLength = 500) { function truncateString(obj: Dict | null, maxLength = 500) {
if (obj !== null && typeof obj === 'object') { if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') { if (typeof obj[key] === 'string') {
@@ -22,7 +23,7 @@ function truncateString(obj: any, maxLength = 500) {
export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-') export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-')
export function log(...msg: any[]) { export function log(...msg: unknown[]) {
if (!getConfigUtil().getConfig().log) { if (!getConfigUtil().getConfig().log) {
return return
} }

View File

@@ -82,7 +82,7 @@ class MessageUniqueWrapper {
return ret.map((t) => t?.MsgId).filter((t) => t !== undefined) return ret.map((t) => t?.MsgId).filter((t) => t !== undefined)
} }
createMsg(peer: Peer, msgId: string): number | undefined { createMsg(peer: Peer, msgId: string): number {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}` const key = `${msgId}|${peer.chatType}|${peer.peerUid}`
const hash = createHash('md5').update(key).digest() const hash = createHash('md5').update(key).digest()
//设置第一个bit为0 保证shortId为正数 //设置第一个bit为0 保证shortId为正数

View File

@@ -1,4 +1,5 @@
import { QQLevel } from '@/ntqqapi/types' import { QQLevel } from '@/ntqqapi/types'
import { Dict } from 'cosmokit'
export function isNumeric(str: string) { export function isNumeric(str: string) {
return /^\d+$/.test(str) return /^\d+$/.test(str)
@@ -14,3 +15,21 @@ export function getBuildVersion(): number {
const version: string = globalThis.LiteLoader.versions.qqnt const version: string = globalThis.LiteLoader.versions.qqnt
return +version.split('-')[1] return +version.split('-')[1]
} }
/** 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 */
export function mergeNewProperties(newObj: Dict, oldObj: Dict) {
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]
}
}
})
}

View File

@@ -1,106 +1,104 @@
import https from 'node:https' import https from 'node:https'
import http from 'node:http' import http from 'node:http'
import { Dict } from 'cosmokit'
export class RequestUtil { export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET // 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> { static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
const client = url.startsWith('https') ? https : http; const client = url.startsWith('https') ? https : http
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
client.get(url, (res) => { client.get(url, (res) => {
let cookies: { [key: string]: string } = {}; let cookies: { [key: string]: string } = {}
const handleRedirect = (res: http.IncomingMessage) => { const handleRedirect = (res: http.IncomingMessage) => {
//console.log(res.headers.location);
if (res.statusCode === 301 || res.statusCode === 302) { if (res.statusCode === 301 || res.statusCode === 302) {
if (res.headers.location) { if (res.headers.location) {
const redirectUrl = new URL(res.headers.location, url); const redirectUrl = new URL(res.headers.location, url)
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
// 合并重定向过程中的cookies // 合并重定向过程中的cookies
//log('redirectCookies', redirectCookies) //log('redirectCookies', redirectCookies)
cookies = { ...cookies, ...redirectCookies }; cookies = { ...cookies, ...redirectCookies }
resolve(cookies); resolve(cookies)
}); })
} else { } else {
resolve(cookies); resolve(cookies)
} }
} else { } else {
resolve(cookies); resolve(cookies)
} }
}; }
res.on('data', () => { }); // Necessary to consume the stream res.on('data', () => { }) // Necessary to consume the stream
res.on('end', () => { res.on('end', () => {
handleRedirect(res); handleRedirect(res)
}); })
if (res.headers['set-cookie']) { if (res.headers['set-cookie']) {
// console.log(res.headers['set-cookie']); //log('set-cookie', url, res.headers['set-cookie'])
//log('set-cookie', url, res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => { res.headers['set-cookie'].forEach((cookie) => {
const parts = cookie.split(';')[0].split('='); const parts = cookie.split(';')[0].split('=')
const key = parts[0]; const key = parts[0]
const value = parts[1]; const value = parts[1]
if (key && value && key.length > 0 && value.length > 0) { if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value; cookies[key] = value
} }
}); })
} }
}).on('error', (err) => { }).on('error', (err) => {
reject(err); reject(err)
}); })
}); })
} }
// 请求和回复都是JSON data传原始内容 自动编码json // 请求和回复都是JSON data传原始内容 自动编码json
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> { static async HttpGetJson<T>(url: string, method: string = 'GET', data?: unknown, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
let option = new URL(url); const option = new URL(url)
const protocol = url.startsWith('https://') ? https : http; const protocol = url.startsWith('https://') ? https : http
const options = { const options = {
hostname: option.hostname, hostname: option.hostname,
port: option.port, port: option.port,
path: option.href, path: option.href,
method: method, method: method,
headers: headers headers: headers
}; }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = protocol.request(options, (res: any) => { const req = protocol.request(options, (res: Dict) => {
let responseBody = ''; let responseBody = ''
res.on('data', (chunk: string | Buffer) => { res.on('data', (chunk: string | Buffer) => {
responseBody += chunk.toString(); responseBody += chunk.toString()
}); })
res.on('end', () => { res.on('end', () => {
try { try {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
if (isJsonRet) { if (isJsonRet) {
const responseJson = JSON.parse(responseBody); const responseJson = JSON.parse(responseBody)
resolve(responseJson as T); resolve(responseJson as T)
} else { } else {
resolve(responseBody as T); resolve(responseBody as T)
} }
} else { } else {
reject(new Error(`Unexpected status code: ${res.statusCode}`)); reject(new Error(`Unexpected status code: ${res.statusCode}`))
} }
} catch (parseError) { } catch (parseError) {
reject(parseError); reject(parseError)
} }
}); })
}); })
req.on('error', (error: any) => { req.on('error', (error) => {
reject(error); reject(error)
}); })
if (method === 'POST' || method === 'PUT' || method === 'PATCH') { if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
if (isArgJson) { if (isArgJson) {
req.write(JSON.stringify(data)); req.write(JSON.stringify(data))
} else { } else {
req.write(data); req.write(data)
} }
} }
req.end(); req.end()
}); })
} }
// 请求返回都是原始内容 // 请求返回都是原始内容
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) { static async HttpGetText(url: string, method: string = 'GET', data?: unknown, headers: Record<string, string> = {}) {
return this.HttpGetJson<string>(url, method, data, headers, false, false); return this.HttpGetJson<string>(url, method, data, headers, false, false)
} }
} }

View File

@@ -14,7 +14,7 @@ export async function checkNewVersion() {
//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 (const k of [0, 1, 2]) {
if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) { if (parseInt(latestVersion[k]) > parseInt(currentVersion[k])) {
log('') log('')
return { result: true, version: latestVersionText } return { result: true, version: latestVersionText }
@@ -47,14 +47,14 @@ export async function upgradeLLOneBot() {
return false return false
} }
const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion) const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion)
let uncompressedPromise = async function () { const uncompressedPromise = async function () {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>(resolve => {
compressing.zip compressing.zip
.uncompress(filePath, temp_ver_dir) .uncompress(filePath, temp_ver_dir)
.then(() => { .then(() => {
resolve(true) resolve(true)
}) })
.catch((reason: any) => { .catch(reason => {
log('llonebot upgrade failed, ', reason) log('llonebot upgrade failed, ', reason)
if (reason?.errno == -4082) { if (reason?.errno == -4082) {
resolve(true) resolve(true)
@@ -75,8 +75,8 @@ export async function upgradeLLOneBot() {
export async function getRemoteVersion() { export async function getRemoteVersion() {
let Version = '' let Version = ''
for (let i = 0; i < checkVersionMirrorHosts.length; i++) { for (let i = 0; i < checkVersionMirrorHosts.length; i++) {
let mirrorGithub = checkVersionMirrorHosts[i] const mirrorGithub = checkVersionMirrorHosts[i]
let tVersion = await getRemoteVersionByMirror(mirrorGithub) const tVersion = await getRemoteVersionByMirror(mirrorGithub)
if (tVersion && tVersion != '') { if (tVersion && tVersion != '') {
Version = tVersion Version = tVersion
break break

File diff suppressed because one or more lines are too long

7
src/global.d.ts vendored
View File

@@ -2,9 +2,8 @@ import type { LLOneBot } from './preload'
import { Dict } from 'cosmokit' import { Dict } from 'cosmokit'
declare global { declare global {
interface Window { var llonebot: LLOneBot
llonebot: LLOneBot
LiteLoader: Dict
}
var LiteLoader: Dict var LiteLoader: Dict
var authData: Dict | undefined
var navigation: Dict | undefined
} }

View File

@@ -53,15 +53,15 @@ function onLoad() {
fs.mkdirSync(LOG_DIR) fs.mkdirSync(LOG_DIR)
} }
ipcMain.handle(CHANNEL_CHECK_VERSION, async (event, arg) => { ipcMain.handle(CHANNEL_CHECK_VERSION, async () => {
return checkNewVersion() return checkNewVersion()
}) })
ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => { ipcMain.handle(CHANNEL_UPDATE, async () => {
return upgradeLLOneBot() return upgradeLLOneBot()
}) })
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => { ipcMain.handle(CHANNEL_SELECT_FILE, async () => {
const selectPath = new Promise<string>((resolve, reject) => { const selectPath = new Promise<string>((resolve, reject) => {
dialog dialog
.showOpenDialog({ .showOpenDialog({
@@ -90,10 +90,10 @@ function onLoad() {
} }
}) })
ipcMain.handle(CHANNEL_ERROR, async (event, arg) => { ipcMain.handle(CHANNEL_ERROR, async () => {
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, 视频尺寸可能异常'
let { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError const { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError
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()
@@ -101,12 +101,12 @@ function onLoad() {
return error return error
}) })
ipcMain.handle(CHANNEL_GET_CONFIG, async (event, arg) => { ipcMain.handle(CHANNEL_GET_CONFIG, async () => {
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
return config return config
}) })
ipcMain.handle(CHANNEL_SET_CONFIG, (event, ask: boolean, config: LLOBConfig) => { ipcMain.handle(CHANNEL_SET_CONFIG, (_event, ask: boolean, config: LLOBConfig) => {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
if (!ask) { if (!ask) {
getConfigUtil().setConfig(config) getConfigUtil().setConfig(config)
@@ -139,7 +139,7 @@ function onLoad() {
}) })
}) })
ipcMain.on(CHANNEL_LOG, (event, arg) => { ipcMain.on(CHANNEL_LOG, (_event, arg) => {
log(arg) log(arg)
}) })
@@ -175,6 +175,9 @@ function onLoad() {
debug: config.debug!, debug: config.debug!,
reportSelfMessage: config.reportSelfMessage!, reportSelfMessage: config.reportSelfMessage!,
msgCacheExpire: config.msgCacheExpire!, msgCacheExpire: config.msgCacheExpire!,
musicSignUrl: config.musicSignUrl,
enableLocalFile2Url: config.enableLocalFile2Url!,
ffmpeg: config.ffmpeg,
}) })
ctx.start() ctx.start()
ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => { ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => {
@@ -209,15 +212,15 @@ function onBrowserWindowCreated(window: BrowserWindow) {
try { try {
hookNTQQApiCall(window, window.id !== 2) hookNTQQApiCall(window, window.id !== 2)
hookNTQQApiReceive(window, window.id !== 2) hookNTQQApiReceive(window, window.id !== 2)
} catch (e: any) { } catch (e) {
log('LLOneBot hook error: ', e.toString()) log('LLOneBot hook error: ', String(e))
} }
} }
try { try {
onLoad() onLoad()
} catch (e: any) { } catch (e) {
console.log(e.toString()) console.log(e)
} }
// 这两个函数都是可选的 // 这两个函数都是可选的

View File

@@ -21,7 +21,6 @@ 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 { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners' import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
@@ -143,51 +142,8 @@ export class NTQQFileApi extends Service {
return sourcePath return sourcePath
} }
} }
let filePath: string
if (NTEventDispatch.initialised) {
const data = await NTEventDispatch.CallNormalEvent<
(
params: {
fileModelId: string,
downloadSourceType: number,
triggerType: number,
msgId: string,
chatType: ChatType,
peerUid: string,
elementId: string,
thumbSize: number,
downloadType: number,
filePath: string
}) => Promise<unknown>,
(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void
>(
'NodeIKernelMsgService/downloadRichMedia',
'NodeIKernelMsgListener/onRichMediaDownloadComplete',
1,
timeout,
(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
}
)
filePath = data[1].filePath
} else {
const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>( const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>(
NTMethod.DOWNLOAD_MEDIA, 'nodeIKernelMsgService/downloadRichMedia',
[ [
{ {
getReq: { getReq: {
@@ -211,8 +167,7 @@ export class NTQQFileApi extends Service {
timeout timeout
} }
) )
filePath = data.notifyInfo.filePath let 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)
@@ -240,17 +195,17 @@ export class NTQQFileApi extends Service {
const fileMd5 = element.md5HexStr const fileMd5 = element.md5HexStr
if (url) { if (url) {
const UrlParse = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接 const parsedUrl = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接
const imageAppid = UrlParse.searchParams.get('appid') const imageAppid = parsedUrl.searchParams.get('appid')
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid) const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid)
if (isNewPic) { if (isNewPic) {
let UrlRkey = UrlParse.searchParams.get('rkey') let rkey = parsedUrl.searchParams.get('rkey')
if (UrlRkey) { if (rkey) {
return IMAGE_HTTP_HOST_NT + url return IMAGE_HTTP_HOST_NT + url
} }
const rkeyData = await this.rkeyManager.getRkey() const rkeyData = await this.rkeyManager.getRkey()
UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey rkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey
return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}` return IMAGE_HTTP_HOST_NT + url + rkey
} else { } else {
// 老的图片url不需要rkey // 老的图片url不需要rkey
return IMAGE_HTTP_HOST + url return IMAGE_HTTP_HOST + url

View File

@@ -2,10 +2,8 @@ import { Friend, FriendV2, SimpleInfo, CategoryFriend } from '../types'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { invoke, NTMethod, NTClass } from '../ntcall' import { invoke, NTMethod, NTClass } from '../ntcall'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { BuddyListReqType, NodeIKernelProfileService } from '../services' import { BuddyListReqType } from '../services'
import { NTEventDispatch } from '@/common/utils/eventTask' import { Dict, pick } from 'cosmokit'
import { LimitedHashTable } from '@/common/utils/table'
import { pick } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
declare module 'cordis' { declare module 'cordis' {
@@ -20,7 +18,7 @@ export class NTQQFriendApi extends Service {
} }
/** 大于或等于 26702 应使用 getBuddyV2 */ /** 大于或等于 26702 应使用 getBuddyV2 */
async getFriends(forced = false) { async getFriends() {
const data = await invoke<{ const data = await invoke<{
data: { data: {
categoryId: number categoryId: number
@@ -76,9 +74,7 @@ export class NTQQFriendApi extends Service {
const buddyService = session.getBuddyService() const buddyService = session.getBuddyService()
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) uids.push(...buddyListV2.data.flatMap(item => item.buddyUids))
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
)
return Array.from(data.values()) return Array.from(data.values())
} else { } else {
const data = await invoke<{ const data = await invoke<{
@@ -93,28 +89,27 @@ export class NTQQFriendApi extends Service {
afterFirstCmd: false, afterFirstCmd: false,
} }
) )
const categoryUids: Map<number, string[]> = new Map() const uids = data.buddyCategory.flatMap(item => item.buddyUids)
for (const item of data.buddyCategory) { return Object.values(data.userSimpleInfos).filter(v => uids.includes(v.uid!))
categoryUids.set(item.categoryId, item.buddyUids)
}
return Object.values(data.userSimpleInfos).filter(v => v.baseInfo && categoryUids.get(v.baseInfo.categoryId)?.includes(v.uid!))
} }
} }
async getBuddyIdMap(refresh = false): Promise<LimitedHashTable<string, string>> { /** uid => uin */
const retMap: LimitedHashTable<string, string> = new LimitedHashTable<string, string>(5000) async getBuddyIdMap(refresh = false): Promise<Map<string, string>> {
const retMap: Map<string, string> = new Map()
const session = getSession() const session = getSession()
if (session) { if (session) {
const uids: string[] = [] const uids: string[] = []
const buddyService = session?.getBuddyService() const buddyService = session.getBuddyService()
const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)
uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) uids.push(...buddyListV2.data.flatMap(item => item.buddyUids))
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids for (const [, item] of data) {
) if (retMap.size > 5000) {
data.forEach((value, key) => { break
retMap.set(value.uin!, value.uid!) }
}) retMap.set(item.uid!, item.uin!)
}
} else { } else {
const data = await invoke<{ const data = await invoke<{
buddyCategory: CategoryFriend[] buddyCategory: CategoryFriend[]
@@ -129,7 +124,10 @@ export class NTQQFriendApi extends Service {
} }
) )
for (const item of Object.values(data.userSimpleInfos)) { for (const item of Object.values(data.userSimpleInfos)) {
retMap.set(item.uin!, item.uid!) if (retMap.size > 5000) {
break
}
retMap.set(item.uid!, item.uin!)
} }
} }
return retMap return retMap
@@ -139,19 +137,17 @@ export class NTQQFriendApi extends Service {
const session = getSession() const session = getSession()
if (session) { if (session) {
const uids: string[] = [] const uids: string[] = []
const categoryMap: Map<string, any> = new Map() const categoryMap: Map<string, Dict> = new Map()
const buddyService = session.getBuddyService() const buddyService = session.getBuddyService()
const buddyListV2 = (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data
uids.push( uids.push(
...buddyListV2?.flatMap(item => { ...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => { item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName }) categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName })
}) })
return item.buddyUids return item.buddyUids
})!) }))
const data = await NTEventDispatch.CallNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>( const data = await session.getProfileService().getCoreAndBaseInfo('nodeStore', uids)
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids
)
return Array.from(data).map(([key, value]) => { return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key) const category = categoryMap.get(key)
return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value

View File

@@ -1,11 +1,10 @@
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify } from '../types' import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GetFileListParam } from '../types'
import { invoke, NTClass, NTMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { NTQQWindows } from './window' import { NTQQWindows } from './window'
import { getSession } from '../wrapper' import { getSession } from '../wrapper'
import { NTEventDispatch } from '@/common/utils/eventTask' import { OnGroupFileInfoUpdateParams } from '../listeners'
import { NodeIKernelGroupListener } from '../listeners'
import { NodeIKernelGroupService } from '../services' import { NodeIKernelGroupService } from '../services'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { isNumeric } from '@/common/utils/misc' import { isNumeric } from '@/common/utils/misc'
@@ -19,27 +18,13 @@ declare module 'cordis' {
export class NTQQGroupApi extends Service { export class NTQQGroupApi extends Service {
static inject = ['ntWindowApi'] static inject = ['ntWindowApi']
private groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>() public groupMembers: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>()
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntGroupApi', true) super(ctx, 'ntGroupApi', true)
} }
async getGroups(forced = false): Promise<Group[]> { async getGroups(): Promise<Group[]> {
if (NTEventDispatch.initialised) {
type ListenerType = NodeIKernelGroupListener['onGroupListUpdate']
const [, , groupList] = await NTEventDispatch.CallNormalEvent
<(force: boolean) => Promise<any>, ListenerType>
(
'NodeIKernelGroupService/getGroupList',
'NodeIKernelGroupListener/onGroupListUpdate',
1,
5000,
() => true,
forced
)
return groupList
} else {
const result = await invoke<{ const result = await invoke<{
updateType: number updateType: number
groupList: Group[] groupList: Group[]
@@ -54,7 +39,6 @@ export class NTQQGroupApi extends Service {
) )
return result.groupList return result.groupList
} }
}
async getGroupMembers(groupCode: string, num = 3000): Promise<Map<string, GroupMember>> { async getGroupMembers(groupCode: string, num = 3000): Promise<Map<string, GroupMember>> {
const session = getSession() const session = getSession()
@@ -76,30 +60,29 @@ export class NTQQGroupApi extends Service {
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) { async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
const groupCodeStr = groupCode.toString() const groupCodeStr = groupCode.toString()
const memberUinOrUidStr = memberUinOrUid.toString() const memberUinOrUidStr = memberUinOrUid.toString()
let members = this.groupMembers.get(groupCodeStr) if (!this.groupMembers.has(groupCodeStr)) {
if (!members) {
try { try {
members = await this.getGroupMembers(groupCodeStr)
// 更新群成员列表 // 更新群成员列表
this.groupMembers.set(groupCodeStr, members) this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
} }
catch (e) { catch (e) {
return null return null
} }
} }
let members = this.groupMembers.get(groupCodeStr)!
const getMember = () => { const getMember = () => {
let member: GroupMember | undefined = undefined let member: GroupMember | undefined = undefined
if (isNumeric(memberUinOrUidStr)) { if (isNumeric(memberUinOrUidStr)) {
member = Array.from(members!.values()).find(member => member.uin === memberUinOrUidStr) member = Array.from(members.values()).find(member => member.uin === memberUinOrUidStr)
} else { } else {
member = members!.get(memberUinOrUidStr) member = members.get(memberUinOrUidStr)
} }
return member return member
} }
let member = getMember() let member = getMember()
if (!member) { if (!member) {
members = await this.getGroupMembers(groupCodeStr) this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
this.groupMembers.set(groupCodeStr, members) members = this.groupMembers.get(groupCodeStr)!
member = getMember() member = getMember()
} }
return member return member
@@ -115,24 +98,9 @@ export class NTQQGroupApi extends Service {
} }
async getSingleScreenNotifies(num: number) { async getSingleScreenNotifies(num: number) {
if (NTEventDispatch.initialised) {
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',
1,
5000,
() => true,
false,
'',
num,
)
return notifies
} else {
invoke(ReceiveCmdS.GROUP_NOTIFY, [], { classNameIsRegister: true }) invoke(ReceiveCmdS.GROUP_NOTIFY, [], { classNameIsRegister: true })
return (await invoke<GroupNotifies>( return (await invoke<GroupNotifies>(
NTMethod.GET_GROUP_NOTICE, 'nodeIKernelGroupService/getSingleScreenNotifies',
[{ doubt: false, startSeq: '', number: num }, null], [{ doubt: false, startSeq: '', number: num }, null],
{ {
@@ -141,7 +109,6 @@ export class NTQQGroupApi extends Service {
} }
)).notifies )).notifies
} }
}
async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) { async handleGroupRequest(flag: string, operateType: GroupRequestOperateTypes, reason?: string) {
const flagitem = flag.split('|') const flagitem = flag.split('|')
@@ -246,7 +213,7 @@ export class NTQQGroupApi extends Service {
} }
} }
async getGroupAtAllRemainCount(groupCode: string) { async getGroupRemainAtTimes(groupCode: string) {
return await invoke< return await invoke<
GeneralCallResult & { GeneralCallResult & {
atInfo: { atInfo: {
@@ -261,30 +228,30 @@ export class NTQQGroupApi extends Service {
} }
/** 27187 TODO */ /** 27187 TODO */
async removeGroupEssence(GroupCode: string, msgId: string) { async removeGroupEssence(groupCode: string, msgId: string) {
const session = getSession() const session = getSession()
// 代码没测过 // 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) const data = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false)
let param = { const param = {
groupCode: GroupCode, groupCode: groupCode,
msgRandom: parseInt(MsgData?.msgList[0].msgRandom!), msgRandom: Number(data?.msgList[0].msgRandom),
msgSeq: parseInt(MsgData?.msgList[0].msgSeq!) msgSeq: Number(data?.msgList[0].msgSeq)
} }
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数 // GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
return session?.getGroupService().removeGroupEssence(param) return session?.getGroupService().removeGroupEssence(param)
} }
/** 27187 TODO */ /** 27187 TODO */
async addGroupEssence(GroupCode: string, msgId: string) { async addGroupEssence(groupCode: string, msgId: string) {
const session = getSession() const session = getSession()
// 代码没测过 // 代码没测过
// 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom // 需要 ob11msgid->msgId + (peer) -> msgSeq + msgRandom
let MsgData = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: GroupCode }, msgId, 1, false) const data = await session?.getMsgService().getMsgsIncludeSelf({ chatType: 2, guildId: '', peerUid: groupCode }, msgId, 1, false)
let param = { const param = {
groupCode: GroupCode, groupCode: groupCode,
msgRandom: parseInt(MsgData?.msgList[0].msgRandom!), msgRandom: Number(data?.msgList[0].msgRandom),
msgSeq: parseInt(MsgData?.msgList[0].msgSeq!) msgSeq: Number(data?.msgList[0].msgSeq)
} }
// GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数 // GetMsgByShoretID(ShoretID) -> MsgService.getMsgs(Peer,MsgId,1,false) -> 组出参数
return session?.getGroupService().addGroupEssence(param) return session?.getGroupService().addGroupEssence(param)
@@ -301,4 +268,24 @@ export class NTQQGroupApi extends Service {
async deleteGroupFile(groupId: string, fileIdList: string[]) { async deleteGroupFile(groupId: string, fileIdList: string[]) {
return await invoke('nodeIKernelRichMediaService/deleteGroupFile', [{ groupId, busIdList: [102], fileIdList }, null]) return await invoke('nodeIKernelRichMediaService/deleteGroupFile', [{ groupId, busIdList: [102], fileIdList }, null])
} }
async getGroupFileList(groupId: string, fileListForm: GetFileListParam) {
invoke('nodeIKernelMsgListener/onGroupFileInfoUpdate', [], { classNameIsRegister: true })
const data = await invoke<{ fileInfo: OnGroupFileInfoUpdateParams }>(
'nodeIKernelRichMediaService/getGroupFileList',
[
{
groupId,
fileListForm
},
null,
],
{
cbCmd: 'nodeIKernelMsgListener/onGroupFileInfoUpdate',
afterFirstCmd: false,
cmdCB: (payload, result) => payload.fileInfo.reqId === result
}
)
return data.fileInfo.item
}
} }

View File

@@ -2,7 +2,6 @@ import { invoke, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types' import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types'
import { getSession } from '@/ntqqapi/wrapper' import { getSession } from '@/ntqqapi/wrapper'
import { NTEventDispatch } from '@/common/utils/eventTask'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -68,6 +67,10 @@ export class NTQQMsgApi extends Service {
return await invoke<GeneralCallResult>(NTMethod.ACTIVE_CHAT_HISTORY, [{ peer, cnt: 20 }, null]) return await invoke<GeneralCallResult>(NTMethod.ACTIVE_CHAT_HISTORY, [{ peer, cnt: 20 }, null])
} }
async getAioFirstViewLatestMsgs(peer: Peer, cnt: number) {
return await invoke('nodeIKernelMsgService/getAioFirstViewLatestMsgs', [{ peer, cnt }, null])
}
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')
@@ -98,34 +101,9 @@ export class NTQQMsgApi extends Service {
} }
} }
async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) {
const msgId = generateMsgId() const msgId = generateMsgId()
peer.guildId = msgId peer.guildId = msgId
let msgList: RawMessage[]
if (NTEventDispatch.initialised) {
const 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 (const msgRecord of msgRecords) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
return true
}
}
return false
},
'0',
peer,
msgElements,
new Map()
)
msgList = data[1]
} else {
const data = await invoke<{ msgList: RawMessage[] }>( const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/sendMsg', 'nodeIKernelMsgService/sendMsg',
[ [
@@ -151,14 +129,7 @@ export class NTQQMsgApi extends Service {
timeout timeout
} }
) )
msgList = data.msgList return data.msgList.find(msgRecord => msgRecord.guildId === msgId)
}
const retMsg = msgList.find(msgRecord => {
if (msgRecord.guildId === msgId) {
return true
}
})
return retMsg!
} }
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
@@ -166,7 +137,7 @@ export class NTQQMsgApi extends Service {
if (session) { if (session) {
return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], []) return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])
} else { } else {
return await invoke<GeneralCallResult>(NTMethod.FORWARD_MSG, [{ return await invoke(NTMethod.FORWARD_MSG, [{
msgIds, msgIds,
srcContact: srcPeer, srcContact: srcPeer,
dstContacts: [destPeer], dstContacts: [destPeer],
@@ -182,32 +153,6 @@ export class NTQQMsgApi extends Service {
return { msgId: id, senderShowName } return { msgId: id, senderShowName }
}) })
const selfUid = selfInfo.uid const selfUid = selfInfo.uid
let msgList: RawMessage[]
if (NTEventDispatch.initialised) {
const data = await NTEventDispatch.CallNormalEvent<
(msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array<any>, attr: Map<any, any>,) => Promise<unknown>,
(msgList: RawMessage[]) => void
>(
'NodeIKernelMsgService/multiForwardMsgWithComment',
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgRecords: RawMessage[]) => {
for (let msgRecord of msgRecords) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) {
return true
}
}
return false
},
msgInfos,
srcPeer,
destPeer,
[],
new Map()
)
msgList = data[1]
} else {
const data = await invoke<{ msgList: RawMessage[] }>( const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/multiForwardMsgWithComment', 'nodeIKernelMsgService/multiForwardMsgWithComment',
[ [
@@ -233,14 +178,12 @@ export class NTQQMsgApi extends Service {
}, },
} }
) )
msgList = data.msgList for (const msg of 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
} }
const forwardData: any = JSON.parse(arkElement.arkElement.bytesData) const forwardData = JSON.parse(arkElement.arkElement.bytesData)
if (forwardData.app != 'com.tencent.multimsg') { if (forwardData.app != 'com.tencent.multimsg') {
continue continue
} }
@@ -265,22 +208,6 @@ export class NTQQMsgApi extends Service {
} }
} }
/** 27187 TODO */
async getLastestMsgByUids(peer: Peer, count = 20, isReverseOrder = false) {
const session = getSession()
const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: isReverseOrder, //此参数有点离谱 注意不是本次查询的排序 而是全部消历史信息的排序 默认false 从新消息拉取到旧消息
isIncludeCurrent: true,
pageLimit: count,
})
return ret
}
async getSingleMsg(peer: Peer, msgSeq: string) { async getSingleMsg(peer: Peer, msgSeq: string) {
const session = getSession() const session = getSession()
if (session) { if (session) {
@@ -289,4 +216,40 @@ export class NTQQMsgApi extends Service {
return await invoke('nodeIKernelMsgService/getSingleMsg', [{ peer, msgSeq }, null]) return await invoke('nodeIKernelMsgService/getSingleMsg', [{ peer, msgSeq }, null])
} }
} }
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId: '0',
msgTime: '0',
msgSeq,
params: {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: true,
isIncludeCurrent: true,
pageLimit: 1,
}
}, null])
}
async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[]) {
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId: '0',
msgTime: '0',
msgSeq,
params: {
chatInfo: peer,
filterMsgType: [],
filterSendersUid,
filterMsgToTime: filterMsgTime,
filterMsgFromTime: filterMsgTime,
isReverseOrder: true,
isIncludeCurrent: true,
pageLimit: 1,
}
}, null])
}
} }

View File

@@ -1,11 +1,9 @@
import { invoke, NTMethod } from '../ntcall' import { invoke } from '../ntcall'
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types' import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types'
import { getBuildVersion } from '@/common/utils' import { 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, forceFetchClientKeyRetType } from '../services' import { UserDetailSource, ProfileBizType } from '../services'
import { NodeIKernelProfileListener } from '../listeners'
import { NTEventDispatch } from '@/common/utils/eventTask'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
@@ -17,7 +15,7 @@ declare module 'cordis' {
} }
export class NTQQUserApi extends Service { export class NTQQUserApi extends Service {
static inject = ['ntFriendApi'] static inject = ['ntFriendApi', 'ntGroupApi']
constructor(protected ctx: Context) { constructor(protected ctx: Context) {
super(ctx, 'ntUserApi', true) super(ctx, 'ntUserApi', true)
@@ -25,7 +23,7 @@ export class NTQQUserApi extends Service {
async setQQAvatar(path: string) { async setQQAvatar(path: string) {
return await invoke( return await invoke(
NTMethod.SET_QQ_AVATAR, 'nodeIKernelProfileService/setHeader',
[ [
{ path }, { path },
null, null,
@@ -37,25 +35,6 @@ export class NTQQUserApi extends Service {
} }
async fetchUserDetailInfo(uid: string) { async fetchUserDetailInfo(uid: string) {
let info: UserDetailInfoListenerArg
if (NTEventDispatch.initialised) {
type EventService = NodeIKernelProfileService['fetchUserDetailInfo']
type EventListener = NodeIKernelProfileListener['onUserDetailInfoChanged']
const [_retData, profile] = await NTEventDispatch.CallNormalEvent
<EventService, EventListener>
(
'NodeIKernelProfileService/fetchUserDetailInfo',
'NodeIKernelProfileListener/onUserDetailInfoChanged',
1,
5000,
(profile) => profile.uid === uid,
'BuddyProfileStore',
[uid],
UserDetailSource.KSERVER,
[ProfileBizType.KALL]
)
info = profile
} else {
const result = await invoke<{ info: UserDetailInfoListenerArg }>( const result = await invoke<{ info: UserDetailInfoListenerArg }>(
'nodeIKernelProfileService/fetchUserDetailInfo', 'nodeIKernelProfileService/fetchUserDetailInfo',
[ [
@@ -73,8 +52,7 @@ export class NTQQUserApi extends Service {
cmdCB: payload => payload.info.uid === uid, cmdCB: payload => payload.info.uid === uid,
} }
) )
info = result.info const { info } = result
}
const ret: User = { const ret: User = {
...info.simpleInfo.coreInfo, ...info.simpleInfo.coreInfo,
...info.simpleInfo.status, ...info.simpleInfo.status,
@@ -87,26 +65,10 @@ export class NTQQUserApi extends Service {
return ret return ret
} }
async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) { async getUserDetailInfo(uid: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return this.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 }>( const result = await invoke<{ info: User }>(
'nodeIKernelProfileService/getUserDetailInfoWithBizInfo', 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
[ [
@@ -124,7 +86,6 @@ export class NTQQUserApi extends Service {
) )
return result.info return result.info
} }
}
async getSkey(): Promise<string> { async getSkey(): Promise<string> {
const clientKeyData = await this.forceFetchClientKey() const clientKeyData = await this.forceFetchClientKey()
@@ -144,7 +105,7 @@ export class NTQQUserApi extends Service {
} }
const uin = selfInfo.uin 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 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
} }
@@ -187,16 +148,31 @@ export class NTQQUserApi extends Service {
} }
} }
async getUidByUinV1(Uin: string) { 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)
if (!uid) { if (!uid) {
let unveifyUid = (await this.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三 for (const membersList of this.ctx.ntGroupApi.groupMembers.values()) { //从群友列表转
if (unveifyUid.indexOf('*') == -1) { for (const member of membersList.values()) {
if (member.uin === uin) {
uid = member.uid
break
}
}
if (uid) break
}
}
if (!uid) {
const unveifyUid = (await this.getUserDetailInfoByUin(uin)).info.uid //特殊转换
if (unveifyUid.indexOf('*') === -1) {
uid = unveifyUid uid = unveifyUid
} }
} }
if (!uid) {
const friends = await this.ctx.ntFriendApi.getFriends() //从好友列表转
uid = friends.find(item => item.uin === uin)?.uid
}
return uid return uid
} }
@@ -210,33 +186,25 @@ export class NTQQUserApi extends Service {
uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin) uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin)
if (uid) return uid if (uid) return uid
} else { } else {
let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uin: [uin] }, null])).uids.get(uin) let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uin: [uin] }])).uids.get(uin)
if (uid) return uid if (uid) return uid
uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }, null])).get(uin) uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }])).get(uin)
if (uid) return uid if (uid) return uid
uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uin: [uin] }, null])).uidInfo.get(uin) uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
if (uid) return uid if (uid) return uid
} }
const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换 const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) return unveifyUid if (unveifyUid.indexOf('*') == -1) return unveifyUid
} }
async getUidByUin(Uin: string) { async getUidByUin(uin: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return await this.getUidByUinV2(Uin) return this.getUidByUinV2(uin)
} }
return await this.getUidByUinV1(Uin) return this.getUidByUinV1(uin)
} }
async getUserDetailInfoByUinV2(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>( return await invoke<UserDetailInfoByUinV2>(
'nodeIKernelProfileService/getUserDetailInfoByUin', 'nodeIKernelProfileService/getUserDetailInfoByUin',
[ [
@@ -245,27 +213,22 @@ export class NTQQUserApi extends Service {
], ],
) )
} }
}
async getUserDetailInfoByUin(Uin: string) { async getUserDetailInfoByUin(uin: string) {
return NTEventDispatch.CallNoListenerEvent return await invoke<UserDetailInfoByUin>(
<(Uin: string) => Promise<UserDetailInfoByUin>>( 'nodeIKernelProfileService/getUserDetailInfoByUin',
'NodeIKernelProfileService/getUserDetailInfoByUin', [
5000, { uin },
Uin null,
],
) )
} }
async getUinByUidV1(Uid: string) { async getUinByUidV1(uid: string) {
const ret = await NTEventDispatch.CallNoListenerEvent const ret = await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>( let uin = ret.uinInfo.get(uid)
'NodeIKernelUixConvertService/getUin',
5000,
[Uid]
)
let uin = ret.uinInfo.get(Uid)
if (!uin) { if (!uin) {
uin = (await this.getUserDetailInfo(Uid)).uin //从QQ Native 转换 uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
} }
return uin return uin
} }
@@ -280,24 +243,24 @@ export class NTQQUserApi extends Service {
uin = (await session.getUixConvertService().getUin([uid])).uinInfo.get(uid) uin = (await session.getUixConvertService().getUin([uid])).uinInfo.get(uid)
if (uin) return uin if (uin) return uin
} else { } else {
let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uid: [uid] }, null])).uins.get(uid) let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uid: [uid] }])).uins.get(uid)
if (uin) return uin if (uin) return uin
uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }, null])).get(uid) uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }])).get(uid)
if (uin) return uin if (uin) return uin
uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uid: [uid] }, null])).uinInfo.get(uid) uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])).uinInfo.get(uid)
if (uin) return uin if (uin) return uin
} }
let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).getKey(uid) let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).get(uid)
if (uin) return uin if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换 uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
return uin return uin
} }
async getUinByUid(Uid: string) { async getUinByUid(uid: string) {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return (await this.getUinByUidV2(Uid))! return this.getUinByUidV2(uid)
} }
return await this.getUinByUidV1(Uid) return this.getUinByUidV1(uid)
} }
async forceFetchClientKey() { async forceFetchClientKey() {
@@ -319,4 +282,14 @@ export class NTQQUserApi extends Service {
} }
return selfInfo.nick return selfInfo.nick
} }
async setSelfStatus(status: number, extStatus: number, batteryStatus: number) {
return await invoke('nodeIKernelMsgService/setStatus', [{
statusReq: {
status,
extStatus,
batteryStatus,
}
}, null])
}
} }

View File

@@ -1,5 +1,6 @@
import { RequestUtil } from '@/common/utils/request' import { RequestUtil } from '@/common/utils/request'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { Dict } from 'cosmokit'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@@ -40,7 +41,7 @@ interface WebApiGroupMemberRet {
em: string em: string
cache: number cache: number
adm_num: number adm_num: number
levelname: any levelname: unknown
mems: WebApiGroupMember[] mems: WebApiGroupMember[]
count: number count: number
svr_time: number svr_time: number
@@ -49,56 +50,6 @@ interface WebApiGroupMemberRet {
extmode: number extmode: number
} }
export interface WebApiGroupNoticeFeed {
u: number//发送者
fid: string//fid
pubt: number//时间
msg: {
text: string
text_face: string
title: string,
pics?: {
id: string,
w: string,
h: string
}[]
}
type: number
fn: number
cn: number
vn: number
settings: {
is_show_edit_card: number
remind_ts: number
tip_window_type: number
confirm_required: number
}
read_num: number
is_read: number
is_all_confirm: number
}
export interface WebApiGroupNoticeRet {
ec: number
em: string
ltsm: number
srv_code: number
read_only: number
role: number
feeds: WebApiGroupNoticeFeed[]
group: {
group_id: number
class_ext: number
}
sta: number,
gln: number
tst: number,
ui: any
server_time: number
svrt: number
ad: number
}
interface GroupEssenceMsg { interface GroupEssenceMsg {
group_code: string group_code: string
msg_seq: number msg_seq: number
@@ -109,7 +60,7 @@ interface GroupEssenceMsg {
add_digest_uin: string add_digest_uin: string
add_digest_nick: string add_digest_nick: string
add_digest_time: number add_digest_time: number
msg_content: any[] msg_content: unknown[]
can_be_removed: true can_be_removed: true
} }
@@ -124,6 +75,30 @@ export interface GroupEssenceMsgRet {
} }
} }
interface SetGroupNoticeParams {
groupCode: string
content: string
pinned: number
type: number
isShowEditCard: number
tipWindowType: number
confirmRequired: number
picId: string
imgWidth?: number
imgHeight?: number
}
interface SetGroupNoticeRet {
ec: number
em: string
id: number
ltsm: number
new_fid: string
read_only: number
role: number
srv_code: number
}
export class NTQQWebApi extends Service { export class NTQQWebApi extends Service {
static inject = ['ntUserApi'] static inject = ['ntUserApi']
@@ -131,16 +106,16 @@ export class NTQQWebApi extends Service {
super(ctx, 'ntWebApi', true) super(ctx, 'ntWebApi', true)
} }
async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> { async getGroupMembers(groupCode: string): Promise<WebApiGroupMember[]> {
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>() const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
const cookieObject = await this.ctx.ntUserApi.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 = this.cookieToString(cookieObject)
const retList: Promise<WebApiGroupMemberRet>[] = [] const retList: Promise<WebApiGroupMemberRet>[] = []
const params = new URLSearchParams({ const params = new URLSearchParams({
st: '0', st: '0',
end: '40', end: '40',
sort: '1', sort: '1',
gc: GroupCode, gc: groupCode,
bkn: this.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 })
@@ -173,132 +148,161 @@ export class NTQQWebApi extends Service {
} }
genBkn(sKey: string) { genBkn(sKey: string) {
sKey = sKey || ''; sKey = sKey || ''
let hash = 5381; let hash = 5381
for (let i = 0; i < sKey.length; i++) { for (let i = 0; i < sKey.length; i++) {
const code = sKey.charCodeAt(i); const code = sKey.charCodeAt(i)
hash = hash + (hash << 5) + code; hash = hash + (hash << 5) + code
} }
return (hash & 0x7FFFFFFF).toString()
return (hash & 0x7FFFFFFF).toString();
} }
//实现未缓存 考虑2h缓存 //实现未缓存 考虑2h缓存
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) { async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
const getDataInternal = async (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(); const url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString()
let res = ''; let resJson
let resJson;
try { try {
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr }); const 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())
} }
if (Internal_type === 1) { if (Internal_type === 1) {
return resJson?.talkativeList; return resJson?.talkativeList
} else { } else {
return resJson?.actorList; return resJson?.actorList
} }
} catch (e) { } catch (e) {
this.ctx.logger.error('获取当前群荣耀失败', url, e); this.ctx.logger.error('获取当前群荣耀失败', url, e)
} }
return undefined; return undefined
} }
let HonorInfo: any = { group_id: groupCode }; const honorInfo: Dict = { group_id: groupCode }
const cookieObject = await this.ctx.ntUserApi.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 = this.cookieToString(cookieObject)
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) { if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 1); const RetInternal = await getDataInternal(groupCode, 1)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取龙王信息失败'); throw new Error('获取龙王信息失败')
} }
HonorInfo.current_talkative = { honorInfo.current_talkative = {
user_id: RetInternal[0]?.uin, user_id: RetInternal[0]?.uin,
avatar: RetInternal[0]?.avatar, avatar: RetInternal[0]?.avatar,
nickname: RetInternal[0]?.name, nickname: RetInternal[0]?.name,
day_count: 0, day_count: 0,
description: RetInternal[0]?.desc description: RetInternal[0]?.desc
} }
HonorInfo.talkative_list = []; honorInfo.talkative_list = [];
for (const talkative_ele of RetInternal) { for (const talkative_ele of RetInternal) {
HonorInfo.talkative_list.push({ honorInfo.talkative_list.push({
user_id: talkative_ele?.uin, user_id: talkative_ele?.uin,
avatar: talkative_ele?.avatar, avatar: talkative_ele?.avatar,
description: talkative_ele?.desc, description: talkative_ele?.desc,
day_count: 0, day_count: 0,
nickname: talkative_ele?.name nickname: talkative_ele?.name
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error(e); this.ctx.logger.error(e)
} }
} }
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 2); const RetInternal = await getDataInternal(groupCode, 2)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取群聊之火失败'); throw new Error('获取群聊之火失败')
} }
HonorInfo.performer_list = []; honorInfo.performer_list = []
for (const performer_ele of RetInternal) { for (const performer_ele of RetInternal) {
HonorInfo.performer_list.push({ honorInfo.performer_list.push({
user_id: performer_ele?.uin, user_id: performer_ele?.uin,
nickname: performer_ele?.name, nickname: performer_ele?.name,
avatar: performer_ele?.avatar, avatar: performer_ele?.avatar,
description: performer_ele?.desc description: performer_ele?.desc
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error(e); this.ctx.logger.error(e)
} }
} }
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) { if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 3); const RetInternal = await getDataInternal(groupCode, 3)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取群聊炽焰失败'); throw new Error('获取群聊炽焰失败')
} }
HonorInfo.legend_list = []; honorInfo.legend_list = []
for (const legend_ele of RetInternal) { for (const legend_ele of RetInternal) {
HonorInfo.legend_list.push({ honorInfo.legend_list.push({
user_id: legend_ele?.uin, user_id: legend_ele?.uin,
nickname: legend_ele?.name, nickname: legend_ele?.name,
avatar: legend_ele?.avatar, avatar: legend_ele?.avatar,
desc: legend_ele?.description desc: legend_ele?.description
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error('获取群聊炽焰失败', e); this.ctx.logger.error('获取群聊炽焰失败', e)
} }
} }
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
try { try {
let RetInternal = await getDataInternal(groupCode, 6); const RetInternal = await getDataInternal(groupCode, 6)
if (!RetInternal) { if (!RetInternal) {
throw new Error('获取快乐源泉失败'); throw new Error('获取快乐源泉失败')
} }
HonorInfo.emotion_list = []; honorInfo.emotion_list = []
for (const emotion_ele of RetInternal) { for (const emotion_ele of RetInternal) {
HonorInfo.emotion_list.push({ honorInfo.emotion_list.push({
user_id: emotion_ele?.uin, user_id: emotion_ele?.uin,
nickname: emotion_ele?.name, nickname: emotion_ele?.name,
avatar: emotion_ele?.avatar, avatar: emotion_ele?.avatar,
desc: emotion_ele?.description desc: emotion_ele?.description
}); })
} }
} catch (e) { } catch (e) {
this.ctx.logger.error('获取快乐源泉失败', e); this.ctx.logger.error('获取快乐源泉失败', e)
} }
} }
//冒尖小春笋好像已经被tx扬了 //冒尖小春笋好像已经被tx扬了
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) { if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
HonorInfo.strong_newbie_list = []; honorInfo.strong_newbie_list = []
} }
return HonorInfo; return honorInfo
}
async setGroupNotice(params: SetGroupNoticeParams): Promise<SetGroupNoticeRet> {
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
const settings = JSON.stringify({
is_show_edit_card: params.isShowEditCard,
tip_window_type: params.tipWindowType,
confirm_required: params.confirmRequired
})
return await RequestUtil.HttpGetJson<SetGroupNoticeRet>(
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
bkn: this.genBkn(cookieObject.skey),
qid: params.groupCode,
text: params.content,
pinned: params.pinned.toString(),
type: params.type.toString(),
settings: settings,
...(params.picId !== '' && {
pic: params.picId,
imgWidth: params.imgWidth?.toString(),
imgHeight: params.imgHeight?.toString(),
})
})}`,
'POST',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
)
}
private cookieToString(cookieObject: Dict) {
return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
} }
} }

View File

@@ -38,7 +38,7 @@ export class NTQQWindowApi extends Service {
// 打开窗口并获取对应的下发事件 // 打开窗口并获取对应的下发事件
async openWindow<R = GeneralCallResult>( async openWindow<R = GeneralCallResult>(
ntQQWindow: NTQQWindow, ntQQWindow: NTQQWindow,
args: any[], args: unknown[],
cbCmd: ReceiveCmd | undefined, cbCmd: ReceiveCmd | undefined,
autoCloseSeconds: number = 2, autoCloseSeconds: number = 2,
) { ) {

View File

@@ -2,8 +2,6 @@ import fs from 'node:fs'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook' import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook'
import { MessageUnique } from '../common/utils/messageUnique' import { MessageUnique } from '../common/utils/messageUnique'
import { NTEventDispatch } from '../common/utils/eventTask'
import { wrapperConstructor, getSession } from './wrapper'
import { Config as LLOBConfig } from '../common/types' import { Config as LLOBConfig } from '../common/types'
import { llonebotError } from '../common/globalVars' import { llonebotError } from '../common/globalVars'
import { isNumeric } from '../common/utils/misc' import { isNumeric } from '../common/utils/misc'
@@ -37,7 +35,7 @@ declare module 'cordis' {
} }
class Core extends Service { class Core extends Service {
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi'] static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi']
constructor(protected ctx: Context, public config: Core.Config) { constructor(protected ctx: Context, public config: Core.Config) {
super(ctx, 'app', true) super(ctx, 'app', true)
@@ -45,13 +43,12 @@ class Core extends Service {
public start() { public start() {
llonebotError.otherError = '' llonebotError.otherError = ''
const WrapperSession = getSession()
if (WrapperSession) {
NTEventDispatch.init({ ListenerMap: wrapperConstructor, WrapperSession })
}
MessageUnique.init(selfInfo.uin) MessageUnique.init(selfInfo.uin)
this.registerListener() this.registerListener()
this.ctx.logger.info(`LLOneBot/${version}`) this.ctx.logger.info(`LLOneBot/${version}`)
this.ctx.on('llonebot/config-updated', input => {
Object.assign(this.config, input)
})
} }
private registerListener() { private registerListener() {
@@ -59,8 +56,8 @@ class Core extends Service {
data: CategoryFriend[] data: CategoryFriend[]
}>(ReceiveCmdS.FRIENDS, (payload) => { }>(ReceiveCmdS.FRIENDS, (payload) => {
type V2data = { userSimpleInfos: Map<string, SimpleInfo> } type V2data = { userSimpleInfos: Map<string, SimpleInfo> }
let friendList: User[] = []; let friendList: User[] = []
if ((payload as any).userSimpleInfos) { if ('userSimpleInfos' in payload) {
friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => { friendList = Object.values((payload as unknown as V2data).userSimpleInfos).map((v: SimpleInfo) => {
return { return {
...v.coreInfo, ...v.coreInfo,
@@ -86,11 +83,11 @@ class Core extends Service {
for (const msgElement of message.elements) { for (const msgElement of message.elements) {
setTimeout(() => { setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()] const picThumbPath = [...(msgElement.picElement?.thumbPath ?? []).values()]
const pttPath = msgElement.pttElement?.filePath const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.filePath const videoPath = msgElement.videoElement?.filePath
const videoThumbPath: string[] = [...msgElement.videoElement.thumbPath?.values()!] const videoThumbPath = [...(msgElement.videoElement?.thumbPath ?? []).values()]
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath] const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
if (msgElement.picElement) { if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath)) pathList.push(...Object.values(msgElement.picElement.thumbPath))
@@ -123,15 +120,15 @@ class Core extends Service {
}[] }[]
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => { }>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
for (const recentContact of payload.changedRecentContactLists) { for (const recentContact of payload.changedRecentContactLists) {
for (const changedContact of recentContact.changedList) { for (const contact of recentContact.changedList) {
if (activatedPeerUids.includes(changedContact.id)) continue if (activatedPeerUids.includes(contact.id)) continue
activatedPeerUids.push(changedContact.id) activatedPeerUids.push(contact.id)
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType } const peer = { peerUid: contact.id, chatType: contact.chatType }
if (changedContact.chatType === ChatType.temp) { if (contact.chatType === ChatType.temp) {
this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => { this.ctx.ntMsgApi.activateChatAndGetHistory(peer).then(() => {
this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => { this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
const lastTempMsg = msgList.at(-1) const lastTempMsg = msgList.at(-1)
if (Date.now() / 1000 - parseInt(lastTempMsg?.msgTime!) < 5) { if (Date.now() / 1000 - Number(lastTempMsg?.msgTime) < 5) {
this.ctx.parallel('nt/message-created', [lastTempMsg!]) this.ctx.parallel('nt/message-created', [lastTempMsg!])
} }
}) })

View File

@@ -22,7 +22,7 @@ import { encodeSilk } from '../common/utils/audio'
import { Context } from 'cordis' import { Context } from 'cordis'
import { isNullable } from 'cosmokit' import { isNullable } from 'cosmokit'
export const mFaceCache = new Map<string, string>() // emojiId -> faceName //export const mFaceCache = new Map<string, string>() // emojiId -> faceName
export namespace SendElementEntities { export namespace SendElementEntities {
export function text(content: string): SendTextElement { export function text(content: string): SendTextElement {
@@ -99,7 +99,7 @@ export namespace SendElementEntities {
} }
} }
export async function file(ctx: Context, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> { export async function file(ctx: Context, filePath: string, fileName = '', folderId = ''): Promise<SendFileElement> {
const { fileName: _fileName, path, fileSize } = await ctx.ntFileApi.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'
@@ -117,14 +117,14 @@ export namespace SendElementEntities {
return element return element
} }
export async function video(ctx: Context, filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> { export async function video(ctx: Context, filePath: string, fileName = '', diyThumbPath = ''): Promise<SendVideoElement> {
try { try {
await stat(filePath) await stat(filePath)
} catch (e) { } catch (e) {
throw `文件${filePath}异常,不存在` throw `文件${filePath}异常,不存在`
} }
ctx.logger.info('复制视频到QQ目录', filePath) ctx.logger.info('复制视频到QQ目录', filePath)
let { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO) const { fileName: _fileName, path, fileSize, md5 } = await ctx.ntFileApi.uploadFile(filePath, ElementType.VIDEO)
ctx.logger.info('复制视频到QQ目录完成', path) ctx.logger.info('复制视频到QQ目录完成', path)
if (fileSize === 0) { if (fileSize === 0) {
@@ -147,7 +147,7 @@ export namespace SendElementEntities {
filePath, filePath,
} }
try { try {
videoInfo = await getVideoInfo(path) videoInfo = await getVideoInfo(ctx, path)
ctx.logger.info('视频信息', videoInfo) ctx.logger.info('视频信息', videoInfo)
} catch (e) { } catch (e) {
ctx.logger.info('获取视频信息失败', e) ctx.logger.info('获取视频信息失败', e)
@@ -170,7 +170,7 @@ export namespace SendElementEntities {
setTimeout(useDefaultThumb, 5000) setTimeout(useDefaultThumb, 5000)
ffmpeg(filePath) ffmpeg(filePath)
.on('error', (err) => { .on('error', () => {
if (diyThumbPath) { if (diyThumbPath) {
copyFile(diyThumbPath, thumbPath) copyFile(diyThumbPath, thumbPath)
.then(() => { .then(() => {
@@ -194,14 +194,14 @@ export namespace SendElementEntities {
resolve(thumbPath) resolve(thumbPath)
}) })
}) })
let thumbPath = new Map() const thumbPath = new Map()
const _thumbPath = await createThumb const _thumbPath = await createThumb
ctx.logger.info('生成视频缩略图', _thumbPath) ctx.logger.info('生成视频缩略图', _thumbPath)
const thumbSize = (await stat(_thumbPath)).size const thumbSize = (await stat(_thumbPath)).size
// log("生成缩略图", _thumbPath) // log("生成缩略图", _thumbPath)
thumbPath.set(0, _thumbPath) thumbPath.set(0, _thumbPath)
const thumbMd5 = await calculateFileMD5(_thumbPath) const thumbMd5 = await calculateFileMD5(_thumbPath)
let element: SendVideoElement = { const element: SendVideoElement = {
elementType: ElementType.VIDEO, elementType: ElementType.VIDEO,
elementId: '', elementId: '',
videoElement: { videoElement: {
@@ -295,7 +295,7 @@ export namespace SendElementEntities {
} }
} }
export function mface(emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement { export function mface(emojiPackageId: number, emojiId: string, key: string, summary?: string): SendMarketFaceElement {
return { return {
elementType: ElementType.MFACE, elementType: ElementType.MFACE,
marketFaceElement: { marketFaceElement: {
@@ -304,14 +304,13 @@ export namespace SendElementEntities {
emojiPackageId, emojiPackageId,
emojiId, emojiId,
key, key,
faceName: faceName || mFaceCache.get(emojiId) || '[商城表情]', faceName: summary || '[商城表情]',
}, },
} }
} }
export function dice(resultId: number | null): SendFaceElement { export function dice(resultId?: string | number): SendFaceElement {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
// 随机1到6 // 随机1到6
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 6) + 1
return { return {
@@ -325,7 +324,7 @@ export namespace SendElementEntities {
stickerId: '33', stickerId: '33',
sourceType: 1, sourceType: 1,
stickerType: 2, stickerType: 2,
resultId: resultId?.toString(), resultId: resultId.toString(),
surpriseId: '', surpriseId: '',
// "randomType": 1, // "randomType": 1,
}, },
@@ -333,7 +332,7 @@ export namespace SendElementEntities {
} }
// 猜拳(石头剪刀布)表情 // 猜拳(石头剪刀布)表情
export function rps(resultId: number | null): SendFaceElement { export function rps(resultId?: string | number): SendFaceElement {
// 实际测试并不能控制结果 // 实际测试并不能控制结果
if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1 if (isNullable(resultId)) resultId = Math.floor(Math.random() * 3) + 1
return { return {
@@ -347,7 +346,7 @@ export namespace SendElementEntities {
stickerId: '34', stickerId: '34',
sourceType: 1, sourceType: 1,
stickerType: 2, stickerType: 2,
resultId: resultId?.toString(), resultId: resultId.toString(),
surpriseId: '', surpriseId: '',
// "randomType": 1, // "randomType": 1,
}, },

View File

@@ -35,7 +35,7 @@ export class RkeyManager {
return now > this.rkeyData.expired_time return now > this.rkeyData.expired_time
} }
async refreshRkey(): Promise<any> { async refreshRkey() {
//刷新rkey //刷新rkey
this.rkeyData = await this.fetchServerRkey() this.rkeyData = await this.fetchServerRkey()
} }

View File

@@ -2,8 +2,9 @@ import type { BrowserWindow } from 'electron'
import { NTClass, NTMethod } from './ntcall' import { NTClass, NTMethod } from './ntcall'
import { log } from '@/common/utils' import { log } from '@/common/utils'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Dict } from 'cosmokit'
export const hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export const hookApiCallbacks: Record<string, (res: any) => void> = {}
export const ReceiveCmdS = { export const ReceiveCmdS = {
RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2', RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2',
@@ -26,11 +27,11 @@ export const ReceiveCmdS = {
CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan', CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan',
MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete', MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete',
SKEY_UPDATE: 'onSkeyUpdate', SKEY_UPDATE: 'onSkeyUpdate',
} } as const
export type ReceiveCmd = string export type ReceiveCmd = string
interface NTQQApiReturnData<Payload = unknown> extends Array<any> { interface NTQQApiReturnData extends Array<unknown> {
0: { 0: {
type: 'request' type: 'request'
eventName: NTClass eventName: NTClass
@@ -39,7 +40,7 @@ interface NTQQApiReturnData<Payload = unknown> extends Array<any> {
1: { 1: {
cmdName: ReceiveCmd cmdName: ReceiveCmd
cmdType: 'event' cmdType: 'event'
payload: Payload payload: unknown
}[] }[]
} }
@@ -67,16 +68,16 @@ export function hookNTQQApiReceive(window: BrowserWindow, onlyLog: boolean) {
} catch { } } catch { }
if (!onlyLog) { if (!onlyLog) {
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
// log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData)) // log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData))
for (const hook of receiveHooks) { for (const hook of receiveHooks) {
if (hook.method.includes(ntQQApiMethodName)) { if (hook.method.includes(ntQQApiMethodName)) {
new Promise((resolve, reject) => { new Promise(resolve => {
try { try {
hook.hookFunc(receiveData.payload) hook.hookFunc(receiveData.payload)
} catch (e: any) { } catch (e) {
log('hook error', ntQQApiMethodName, e.stack.toString()) log('hook error', ntQQApiMethodName, (e as Error).stack?.toString())
} }
resolve(undefined) resolve(undefined)
}).then() }).then()
@@ -88,8 +89,7 @@ export function hookNTQQApiReceive(window: BrowserWindow, onlyLog: boolean) {
// log("hookApiCallback", hookApiCallbacks, args) // log("hookApiCallback", hookApiCallbacks, args)
const callbackId = args[0].callbackId const callbackId = args[0].callbackId
if (hookApiCallbacks[callbackId]) { if (hookApiCallbacks[callbackId]) {
// log("callback found") new Promise(resolve => {
new Promise((resolve, reject) => {
hookApiCallbacks[callbackId](args[1]) hookApiCallbacks[callbackId](args[1])
resolve(undefined) resolve(undefined)
}).then() }).then()
@@ -104,7 +104,7 @@ export function hookNTQQApiReceive(window: BrowserWindow, onlyLog: boolean) {
export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) { export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
// 监听调用NTQQApi // 监听调用NTQQApi
let webContents = window.webContents as any const webContents = window.webContents as Dict
const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message'] const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message']
const proxyIpcMsg = new Proxy(ipc_message_proxy, { const proxyIpcMsg = new Proxy(ipc_message_proxy, {
@@ -125,10 +125,10 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
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 => {
try { try {
hook.hookFunc(callParams) hook.hookFunc(callParams)
} catch (e: any) { } catch (e) {
log('hook call error', e, _args) log('hook call error', e, _args)
} }
resolve(undefined) resolve(undefined)
@@ -150,14 +150,13 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke'] const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke']
const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, { const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
// console.log(args);
//HOOK_LOG && log('call NTQQ invoke api', thisArg, args) //HOOK_LOG && log('call NTQQ invoke api', thisArg, args)
args[0]['_replyChannel']['sendReply'] = new Proxy(args[0]['_replyChannel']['sendReply'], { args[0]['_replyChannel']['sendReply'] = new Proxy(args[0]['_replyChannel']['sendReply'], {
apply(sendtarget, sendthisArg, sendargs) { apply(sendtarget, sendthisArg, sendargs) {
sendtarget.apply(sendthisArg, sendargs) sendtarget.apply(sendthisArg, sendargs)
}, },
}) })
let ret = target.apply(thisArg, args) const ret = target.apply(thisArg, args)
/*try { /*try {
HOOK_LOG && log('call NTQQ invoke api return', ret) HOOK_LOG && log('call NTQQ invoke api return', ret)
} catch (e) { }*/ } catch (e) { }*/

View File

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

View File

@@ -23,15 +23,55 @@ export interface OnRichMediaDownloadCompleteParams {
userUsedSpacePerDay: unknown | null userUsedSpacePerDay: unknown | null
} }
export interface onGroupFileInfoUpdateParamType { export interface OnGroupFileInfoUpdateParams {
retCode: number retCode: number
retMsg: string retMsg: string
clientWording: string clientWording: string
isEnd: boolean isEnd: boolean
item: Array<any> item: {
allFileCount: string peerId: string
nextIndex: string type: number
reqId: string folderInfo?: {
folderId: string
parentFolderId: string
folderName: string
createTime: number
modifyTime: number
createUin: string
creatorName: string
totalFileCount: number
modifyUin: string
modifyName: string
usedSpace: string
}
fileInfo?: {
fileModelId: string
fileId: string
fileName: string
fileSize: string
busId: number
uploadedSize: string
uploadTime: number
deadTime: number
modifyTime: number
downloadTimes: number
sha: string
sha3: string
md5: string
uploaderLocalPath: string
uploaderName: string
uploaderUin: string
parentFolderId: string
localPath: string
transStatus: number
transType: number
elementId: string
isFolder: boolean
}
}[]
allFileCount: number
nextIndex: number
reqId: number
} }
// { // {
@@ -82,7 +122,7 @@ export interface IKernelMsgListener {
onGroupFileInfoAdd(groupItem: unknown): void onGroupFileInfoAdd(groupItem: unknown): void
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType): void onGroupFileInfoUpdate(groupFileListResult: OnGroupFileInfoUpdateParams): void
onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void
@@ -225,290 +265,4 @@ export interface IKernelMsgListener {
// 第一次发现于Win 9.9.9 23159 // 第一次发现于Win 9.9.9 23159
onBroadcastHelperProgerssUpdate(...args: unknown[]): void onBroadcastHelperProgerssUpdate(...args: unknown[]): void
}
export interface NodeIKernelMsgListener extends IKernelMsgListener {
// eslint-disable-next-line @typescript-eslint/no-misused-new
new(listener: IKernelMsgListener): NodeIKernelMsgListener
}
export class MsgListener implements IKernelMsgListener {
onAddSendMsg(msgRecord: RawMessage) {
}
onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown) {
}
onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown) {
}
onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown) {
}
onContactUnreadCntUpdate(hashMap: unknown) {
}
onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown) {
}
onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown) {
}
onEmojiDownloadComplete(emojiNotifyInfo: unknown) {
}
onEmojiResourceUpdate(emojiResourceInfo: unknown) {
}
onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
}
onFileMsgCome(arrayList: unknown) {
}
onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown) {
}
onFirstViewGroupGuildMapping(arrayList: unknown) {
}
onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown) {
}
onGroupFileInfoAdd(groupItem: unknown) {
}
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) {
}
onGroupGuildUpdate(groupGuildNotifyInfo: unknown) {
}
onGroupTransferInfoAdd(groupItem: unknown) {
}
onGroupTransferInfoUpdate(groupFileListResult: unknown) {
}
onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown) {
}
onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown) {
}
onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown) {
}
onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown) {
}
onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown) {
}
onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown) {
}
onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown) {
}
onInputStatusPush(inputStatusInfo: unknown) {
}
onKickedOffLine(kickedInfo: unknown) {
}
onLineDev(arrayList: unknown) {
}
onLogLevelChanged(j2: unknown) {
}
onMsgAbstractUpdate(arrayList: unknown) {
}
onMsgBoxChanged(arrayList: unknown) {
}
onMsgDelete(contact: unknown, arrayList: unknown) {
}
onMsgEventListUpdate(hashMap: unknown) {
}
onMsgInfoListAdd(arrayList: unknown) {
}
onMsgInfoListUpdate(msgList: RawMessage[]) {
}
onMsgQRCodeStatusChanged(i2: unknown) {
}
onMsgRecall(i2: unknown, str: unknown, j2: unknown) {
}
onMsgSecurityNotify(msgRecord: unknown) {
}
onMsgSettingUpdate(msgSetting: unknown) {
}
onNtFirstViewMsgSyncEnd() {
}
onNtMsgSyncEnd() {
}
onNtMsgSyncStart() {
}
onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown) {
}
onRecvGroupGuildFlag(i2: unknown) {
}
onRecvMsg(arrayList: RawMessage[]) {
}
onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown) {
}
onRecvOnlineFileMsg(arrayList: unknown) {
}
onRecvS2CMsg(arrayList: unknown) {
}
onRecvSysMsg(arrayList: unknown) {
}
onRecvUDCFlag(i2: unknown) {
}
onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) {
}
onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown) {
}
onRichMediaUploadComplete(fileTransNotifyInfo: unknown) {
}
onSearchGroupFileInfoUpdate(searchGroupFileResult: unknown) {
}
onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown) {
}
onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown) {
}
onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams) {
}
onUnreadCntAfterFirstView(hashMap: unknown) {
}
onUnreadCntUpdate(hashMap: unknown) {
}
onUserChannelTabStatusChanged(z: unknown) {
}
onUserOnlineStatusChanged(z: unknown) {
}
onUserTabStatusChanged(arrayList: unknown) {
}
onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
}
onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown) {
}
// 第一次发现于Linux
onUserSecQualityChanged(...args: unknown[]) {
}
onMsgWithRichLinkInfoUpdate(...args: unknown[]) {
}
onRedTouchChanged(...args: unknown[]) {
}
// 第一次发现于Win 9.9.9-23159
onBroadcastHelperProgerssUpdate(...args: unknown[]) {
}
} }

View File

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

View File

@@ -32,73 +32,48 @@ export enum NTClass {
} }
export enum NTMethod { export enum NTMethod {
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息 ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息 ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf', HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf',
GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg', GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg',
DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid', DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid',
ENTER_OR_EXIT_AIO = 'nodeIKernelMsgService/enterOrExitAio', MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild',
RECALL_MSG = 'nodeIKernelMsgService/recallMsg',
EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes',
FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment',
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike',
SELF_INFO = 'fetchAuthData', SELF_INFO = 'fetchAuthData',
FRIENDS = 'nodeIKernelBuddyService/getBuddyList',
GROUPS = 'nodeIKernelGroupService/getGroupList',
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
GROUP_MEMBERS_INFO = 'nodeIKernelGroupService/getMemberInfo',
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo',
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo',
USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
FILE_TYPE = 'getFileType', FILE_TYPE = 'getFileType',
FILE_MD5 = 'getFileMd5', FILE_MD5 = 'getFileMd5',
FILE_COPY = 'copyFile', FILE_COPY = 'copyFile',
IMAGE_SIZE = 'getImageSizeFromPath', IMAGE_SIZE = 'getImageSizeFromPath',
FILE_SIZE = 'getFileSize', FILE_SIZE = 'getFileSize',
MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild', CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath',
CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath',
CACHE_PATH_SESSION = 'getCleanableAppSessionPathList',
OPEN_EXTRA_WINDOW = 'openExternalWindow',
RECALL_MSG = 'nodeIKernelMsgService/recallMsg', GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
SEND_MSG = 'nodeIKernelMsgService/sendMsg', GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes',
DOWNLOAD_MEDIA = 'nodeIKernelMsgService/downloadRichMedia',
FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment',
MULTI_FORWARD_MSG = 'nodeIKernelMsgService/multiForwardMsgWithComment', // 合并转发
GET_GROUP_NOTICE = 'nodeIKernelGroupService/getSingleScreenNotifies',
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',
HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest',
KICK_MEMBER = 'nodeIKernelGroupService/kickMember', KICK_MEMBER = 'nodeIKernelGroupService/kickMember',
MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp', MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp',
MUTE_GROUP = 'nodeIKernelGroupService/setGroupShutUp', MUTE_GROUP = 'nodeIKernelGroupService/setGroupShutUp',
SET_MEMBER_CARD = 'nodeIKernelGroupService/modifyMemberCardName', SET_MEMBER_CARD = 'nodeIKernelGroupService/modifyMemberCardName',
SET_MEMBER_ROLE = 'nodeIKernelGroupService/modifyMemberRole', SET_MEMBER_ROLE = 'nodeIKernelGroupService/modifyMemberRole',
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin',
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName', SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName',
SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle',
ACTIVATE_MEMBER_LIST_CHANGE = 'nodeIKernelGroupListener/onMemberListChange', HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest',
ACTIVATE_MEMBER_INFO_CHANGE = 'nodeIKernelGroupListener/onMemberInfoChange',
GET_MSG_BOX_INFO = 'nodeIKernelMsgService/getABatchOfContactMsgBoxInfo',
GET_GROUP_ALL_INFO = 'nodeIKernelGroupService/getGroupAllInfo',
CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath',
CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath',
CACHE_PATH_SESSION = 'getCleanableAppSessionPathList',
CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache', CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache',
CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys', CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys',
CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo', CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo',
CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo', CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo',
CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo', CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo',
OPEN_EXTRA_WINDOW = 'openExternalWindow',
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
} }
export enum NTChannel { export enum NTChannel {
@@ -127,43 +102,43 @@ interface InvokeOptions<ReturnType> {
channel?: NTChannel channel?: NTChannel
classNameIsRegister?: boolean classNameIsRegister?: boolean
cbCmd?: string | string[] cbCmd?: string | string[]
cmdCB?: (payload: ReturnType) => boolean cmdCB?: (payload: ReturnType, result: unknown) => boolean
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
timeout?: number timeout?: number
} }
export function invoke< export function invoke<
R extends Awaited<ReturnType<NTService[S][M] extends (...args: any) => any ? NTService[S][M] : any>>, R extends Awaited<ReturnType<Extract<NTService[S][M], (...args: any) => unknown>>>,
S extends keyof NTService = any, S extends keyof NTService = any,
M extends keyof NTService[S] & string = any M extends keyof NTService[S] & string = any
>(method: `${unknown extends `${S}/${M}` ? `${S}/${M}` : string}`, args?: unknown[], options: InvokeOptions<R> = {}) { >(method: Extract<unknown, `${S}/${M}`> | string, args: unknown[], options: InvokeOptions<R> = {}) {
const className = options.className ?? NTClass.NT_API const className = options.className ?? NTClass.NT_API
const channel = options.channel ?? NTChannel.IPC_UP_2 const channel = options.channel ?? NTChannel.IPC_UP_2
const timeout = options.timeout ?? 5000 const timeout = options.timeout ?? 5000
const afterFirstCmd = options.afterFirstCmd ?? true const afterFirstCmd = options.afterFirstCmd ?? true
const uuid = randomUUID()
let eventName = className + '-' + channel[channel.length - 1] let eventName = className + '-' + channel[channel.length - 1]
if (options.classNameIsRegister) { if (options.classNameIsRegister) {
eventName += '-register' eventName += '-register'
} }
const apiArgs = [method, ...(args ?? [])] return new Promise<R>((resolve, reject) => {
//log('callNTQQApi', channel, eventName, apiArgs, uuid) const apiArgs = [method, ...args]
return new Promise((resolve: (data: R) => void, reject) => { const callbackId = randomUUID()
let success = false let success = false
if (!options.cbCmd) { if (!options.cbCmd) {
// QQ后端会返回结果并且可以根据uuid识别 // QQ后端会返回结果并且可以根据uuid识别
hookApiCallbacks[uuid] = (r: R) => { hookApiCallbacks[callbackId] = res => {
success = true success = true
resolve(r) resolve(res)
} }
} }
else { else {
let result: unknown
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => { const secondCallback = () => {
const hookId = registerReceiveHook<R>(options.cbCmd!, (payload) => { const hookId = registerReceiveHook<R>(options.cbCmd!, (payload) => {
// log(methodName, "second callback", cbCmd, payload, cmdCB); // log(methodName, "second callback", cbCmd, payload, cmdCB);
if (!!options.cmdCB) { if (options.cmdCB) {
if (options.cmdCB(payload)) { if (options.cmdCB(payload, result)) {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true success = true
resolve(payload) resolve(payload)
@@ -177,14 +152,14 @@ export function invoke<
}) })
} }
!afterFirstCmd && secondCallback() !afterFirstCmd && secondCallback()
hookApiCallbacks[uuid] = (result: GeneralCallResult) => { hookApiCallbacks[callbackId] = (res: GeneralCallResult) => {
if (result?.result === 0 || result === undefined) { result = res
//log(`${params.methodName} callback`, result) if (res?.result === 0 || ['undefined', 'number'].includes(typeof res)) {
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback()
} }
else { else {
log('ntqq api call failed,', method, result) log('ntqq api call failed,', method, res)
reject(`ntqq api call failed, ${method}, ${result.errMsg}`) reject(`ntqq api call failed, ${method}, ${res.errMsg}`)
} }
} }
} }
@@ -199,11 +174,11 @@ export function invoke<
channel, channel,
{ {
sender: { sender: {
send: (..._args: unknown[]) => { send: () => {
}, },
}, },
}, },
{ type: 'request', callbackId: uuid, eventName }, { type: 'request', callbackId, eventName },
apiArgs, apiArgs,
) )
}) })

View File

@@ -29,7 +29,7 @@ export interface NodeIKernelBuddyService {
buddyUids: Array<string>//Uids buddyUids: Array<string>//Uids
}>> }>>
addKernelBuddyListener(listener: any): number addKernelBuddyListener(listener: unknown): number
getAllBuddyCount(): number getAllBuddyCount(): number

View File

@@ -1,4 +1,3 @@
import { NodeIKernelGroupListener } from '@/ntqqapi/listeners'
import { import {
GroupExtParam, GroupExtParam,
GroupMember, GroupMember,
@@ -7,8 +6,7 @@ import {
GroupRequestOperateTypes, GroupRequestOperateTypes,
} from '@/ntqqapi/types' } from '@/ntqqapi/types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
//高版本的接口不应该随意使用 使用应该严格进行pr审核 同时部分ipc中未出现的接口不要过于依赖 应该做好数据兜底
export interface NodeIKernelGroupService { export interface NodeIKernelGroupService {
getMemberCommonInfo(Req: { getMemberCommonInfo(Req: {
@@ -29,8 +27,10 @@ export interface NodeIKernelGroupService {
onlineFlag: string, onlineFlag: string,
realSpecialTitleFlag: number realSpecialTitleFlag: number
}): Promise<unknown> }): Promise<unknown>
//26702 //26702
getGroupMemberLevelInfo(groupCode: string): Promise<unknown> getGroupMemberLevelInfo(groupCode: string): Promise<unknown>
//26702 //26702
getGroupHonorList(groupCodes: Array<string>): unknown getGroupHonorList(groupCodes: Array<string>): unknown
@@ -45,6 +45,7 @@ export interface NodeIKernelGroupService {
errMsg: string, errMsg: string,
uids: Map<string, string> uids: Map<string, string>
}> }>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
checkGroupMemberCache(arrayList: Array<string>): Promise<unknown> checkGroupMemberCache(arrayList: Array<string>): Promise<unknown>
@@ -70,12 +71,16 @@ export interface NodeIKernelGroupService {
brief: string brief: string
} }
}): Promise<unknown> }): Promise<unknown>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown> isEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown> queryCachedEssenceMsg(Req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<unknown>
//26702(其实更早 但是我不知道) //26702(其实更早 但是我不知道)
fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown> fetchGroupEssenceList(Req: { groupCode: string, pageStart: number, pageLimit: number }, Arg: unknown): Promise<unknown>
//26702 //26702
getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{ getAllMemberList(groupCode: string, forceFetch: boolean): Promise<{
errCode: number, errCode: number,
@@ -85,7 +90,7 @@ export interface NodeIKernelGroupService {
uid: string, uid: string,
index: number//0 index: number//0
}>, }>,
infos: {}, infos: Dict,
finish: true, finish: true,
hasRobot: false hasRobot: false
} }
@@ -93,7 +98,7 @@ export interface NodeIKernelGroupService {
setHeader(uid: string, path: string): unknown setHeader(uid: string, path: string): unknown
addKernelGroupListener(listener: NodeIKernelGroupListener): number addKernelGroupListener(listener: unknown): number
removeKernelGroupListener(listenerId: unknown): void removeKernelGroupListener(listenerId: unknown): void
@@ -171,7 +176,7 @@ export interface NodeIKernelGroupService {
clearGroupNotifies(groupCode: string): void clearGroupNotifies(groupCode: string): void
getGroupNotifiesUnreadCount(unknown: Boolean): Promise<GeneralCallResult> getGroupNotifiesUnreadCount(unknown: boolean): Promise<GeneralCallResult>
clearGroupNotifiesUnreadCount(groupCode: string): void clearGroupNotifiesUnreadCount(groupCode: string): void
@@ -193,7 +198,7 @@ export interface NodeIKernelGroupService {
deleteGroupBulletin(groupCode: string, seq: string): void deleteGroupBulletin(groupCode: string, seq: string): void
publishGroupBulletin(groupCode: string, pskey: string, data: any): Promise<GeneralCallResult> publishGroupBulletin(groupCode: string, pskey: string, data: unknown): Promise<GeneralCallResult>
publishInstructionForNewcomers(groupCode: string, arg: unknown): void publishInstructionForNewcomers(groupCode: string, arg: unknown): void

View File

@@ -1,6 +1,6 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types' import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types'
import { NodeIKernelMsgListener } from '@/ntqqapi/listeners/NodeIKernelMsgListener'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
export interface QueryMsgsParams { export interface QueryMsgsParams {
chatInfo: Peer, chatInfo: Peer,
@@ -29,16 +29,15 @@ export interface TmpChatInfo {
} }
export interface NodeIKernelMsgService { export interface NodeIKernelMsgService {
generateMsgUniqueId(chatType: number, time: string): string generateMsgUniqueId(chatType: number, time: string): string
addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number addKernelMsgListener(nodeIKernelMsgListener: unknown): number
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>): Promise<GeneralCallResult> sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult> recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>
addKernelMsgImportToolListener(arg: Object): unknown addKernelMsgImportToolListener(arg: Dict): unknown
removeKernelMsgListener(args: unknown): unknown removeKernelMsgListener(args: unknown): unknown
@@ -52,7 +51,7 @@ export interface NodeIKernelMsgService {
getOnLineDev(): void getOnLineDev(): void
kickOffLine(DevInfo: Object): unknown kickOffLine(DevInfo: Dict): unknown
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult> setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>
@@ -81,11 +80,11 @@ export interface NodeIKernelMsgService {
// this.voipToken = bArr2 // this.voipToken = bArr2
// this.profileId = str // this.profileId = str
setToken(arg: Object): unknown setToken(arg: Dict): unknown
switchForeGround(): unknown switchForeGround(): unknown
switchBackGround(arg: Object): unknown switchBackGround(arg: Dict): unknown
//hex //hex
setTokenForMqq(token: string): unknown setTokenForMqq(token: string): unknown
@@ -111,10 +110,11 @@ export interface NodeIKernelMsgService {
resendMsg(...args: unknown[]): unknown resendMsg(...args: unknown[]): unknown
reeditRecallMsg(...args: unknown[]): unknown reeditRecallMsg(...args: unknown[]): unknown
//调用请检查除开commentElements其余参数不能为null //调用请检查除开commentElements其余参数不能为null
forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult> forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult>
forwardMsgWithComment(...args: unknown[]): unknown forwardMsgWithComment(...args: unknown[]): Promise<GeneralCallResult>
forwardSubMsgWithComment(...args: unknown[]): unknown forwardSubMsgWithComment(...args: unknown[]): unknown
@@ -142,9 +142,9 @@ export interface NodeIKernelMsgService {
addLocalTofuRecordMsg(...args: unknown[]): unknown addLocalTofuRecordMsg(...args: unknown[]): unknown
addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<any> | number, front: boolean): Promise<unknown> addLocalRecordMsg(Peer: Peer, msgId: string, ele: MessageElement, attr: Array<unknown> | number, front: boolean): Promise<unknown>
deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<any> deleteMsg(Peer: Peer, msgIds: Array<string>): Promise<unknown>
updateElementExtBufForUI(...args: unknown[]): unknown updateElementExtBufForUI(...args: unknown[]): unknown
@@ -168,9 +168,10 @@ export interface NodeIKernelMsgService {
getLastMessageList(peer: Peer[]): Promise<unknown> getLastMessageList(peer: Peer[]): Promise<unknown>
getAioFirstViewLatestMsgs(peer: Peer, num: number): unknown getAioFirstViewLatestMsgs(peer: Peer, num: number): Promise<GeneralCallResult & {
msgList: RawMessage[]
}>
//deprecated 从9.9.15-26702版本开始该接口已经废弃请使用getMsgsEx
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown> getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & { getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
@@ -369,8 +370,9 @@ export interface NodeIKernelMsgService {
getFileThumbSavePathForSend(...args: unknown[]): unknown getFileThumbSavePathForSend(...args: unknown[]): unknown
getFileThumbSavePath(...args: unknown[]): unknown getFileThumbSavePath(...args: unknown[]): unknown
//猜测居多 //猜测居多
translatePtt2Text(MsgId: string, Peer: {}, MsgElement: {}): unknown translatePtt2Text(MsgId: string, Peer: Dict, MsgElement: Dict): unknown
setPttPlayedState(...args: unknown[]): unknown setPttPlayedState(...args: unknown[]): unknown
// NodeIQQNTWrapperSession fetchFavEmojiList [ // NodeIQQNTWrapperSession fetchFavEmojiList [
@@ -447,7 +449,7 @@ export interface NodeIKernelMsgService {
getEmojiResourcePath(...args: unknown[]): unknown getEmojiResourcePath(...args: unknown[]): unknown
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: unknown): unknown
getMsgAbstracts(...args: unknown[]): unknown getMsgAbstracts(...args: unknown[]): unknown
@@ -622,7 +624,6 @@ export interface NodeIKernelMsgService {
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown> sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>
//chattype,uid->Promise<any>
getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi> getTempChatInfo(ChatType: number, Uid: string): Promise<TmpChatInfoApi>
setContactLocalTop(...args: unknown[]): unknown setContactLocalTop(...args: unknown[]): unknown
@@ -653,7 +654,7 @@ export interface NodeIKernelMsgService {
recordEmoji(...args: unknown[]): unknown recordEmoji(...args: unknown[]): unknown
fetchGetHitEmotionsByWord(args: Object): Promise<unknown>//表情推荐? fetchGetHitEmotionsByWord(args: Dict): Promise<unknown>//表情推荐?
deleteAllRoamMsgs(...args: unknown[]): unknown//漫游消息? deleteAllRoamMsgs(...args: unknown[]): unknown//漫游消息?

View File

@@ -9,10 +9,10 @@ export interface NodeIKernelProfileLikeService {
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<unknown>,
'friendMaxVotes': number, friendMaxVotes: number,
'start': number start: number
} }
}> }>

View File

@@ -33,7 +33,7 @@ export interface NodeIKernelProfileService {
fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown> fetchUserDetailInfo(trace: string, uids: string[], arg2: number, arg3: number[]): Promise<unknown>
addKernelProfileListener(listener: any): number addKernelProfileListener(listener: unknown): number
removeKernelProfileListener(listenerId: number): void removeKernelProfileListener(listenerId: number): void
@@ -64,7 +64,7 @@ export interface NodeIKernelProfileService {
modifySelfProfile(...args: unknown[]): Promise<unknown> modifySelfProfile(...args: unknown[]): Promise<unknown>
modifyDesktopMiniProfile(param: any): Promise<GeneralCallResult> modifyDesktopMiniProfile(param: unknown): Promise<GeneralCallResult>
setNickName(NickName: string): Promise<unknown> setNickName(NickName: string): Promise<unknown>
@@ -82,9 +82,9 @@ export interface NodeIKernelProfileService {
getUserDetailInfo(uid: string): Promise<unknown> getUserDetailInfo(uid: string): Promise<unknown>
getUserDetailInfoWithBizInfo(uid: string, Biz: any[]): Promise<GeneralCallResult> getUserDetailInfoWithBizInfo(uid: string, Biz: unknown[]): Promise<GeneralCallResult>
getUserDetailInfoByUin(uin: string): Promise<any> getUserDetailInfoByUin(uin: string): Promise<unknown>
getZplanAvatarInfos(args: string[]): Promise<unknown> getZplanAvatarInfos(args: string[]): Promise<unknown>
@@ -99,7 +99,7 @@ export interface NodeIKernelProfileService {
getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown> getProfileQzonePicInfo(uid: string, type: number, force: boolean): Promise<unknown>
//profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList) //profileService.getCoreInfo("UserRemarkServiceImpl::getStrangerRemarkByUid", arrayList)
getCoreInfo(name: string, arg: any[]): unknown getCoreInfo(name: string, arg: unknown[]): unknown
//m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>()) //m429253e12.getOtherFlag("FriendListInfoCache_getKernelDataAndPutCache", new ArrayList<>())
isNull(): boolean isNull(): boolean

View File

@@ -169,7 +169,7 @@ export interface NodeIKernelRichMediaService {
downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown downloadFileForFileInfo(fileInfo: CommonFileInfo[], savePath: string): unknown
createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: any, groupItem: Array<any> } }> createGroupFolder(GroupCode: string, FolderName: string): Promise<GeneralCallResult & { resultWithGroupItem: { result: unknown, groupItem: Array<unknown> } }>
downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown downloadFile(commonFile: CommonFileInfo, arg2: unknown, arg3: unknown, savePath: string): unknown
@@ -177,8 +177,6 @@ export interface NodeIKernelRichMediaService {
renameGroupFolder(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 deleteTransferInfo(arg1: unknown, arg2: unknown): unknown
cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown cancelTransferTask(arg1: unknown, arg2: unknown, arg3: unknown): unknown
@@ -224,9 +222,9 @@ export interface NodeIKernelRichMediaService {
deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & { deleteGroupFile(GroupCode: string, params: Array<number>, Files: Array<string>): Promise<GeneralCallResult & {
transGroupFileResult: { transGroupFileResult: {
result: any result: unknown
successFileIdList: Array<any> successFileIdList: Array<unknown>
failFileIdList: Array<any> failFileIdList: Array<unknown>
} }
}> }>

View File

@@ -1,75 +1,75 @@
import { ChatType } from '../types' import { ChatType } from '../types'
export interface NodeIKernelSearchService { export interface NodeIKernelSearchService {
addKernelSearchListener(...args: any[]): unknown// needs 1 arguments addKernelSearchListener(...args: unknown[]): unknown// needs 1 arguments
removeKernelSearchListener(...args: any[]): unknown// needs 1 arguments removeKernelSearchListener(...args: unknown[]): unknown// needs 1 arguments
searchStranger(...args: any[]): unknown// needs 3 arguments searchStranger(...args: unknown[]): unknown// needs 3 arguments
searchGroup(...args: any[]): unknown// needs 1 arguments searchGroup(...args: unknown[]): 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: unknown[]): unknown// needs 3 arguments
searchBuddyChatInfo(...args: any[]): unknown// needs 2 arguments searchBuddyChatInfo(...args: unknown[]): unknown// needs 2 arguments
searchMoreBuddyChatInfo(...args: any[]): unknown// needs 1 arguments searchMoreBuddyChatInfo(...args: unknown[]): unknown// needs 1 arguments
cancelSearchBuddyChatInfo(...args: any[]): unknown// needs 3 arguments cancelSearchBuddyChatInfo(...args: unknown[]): unknown// needs 3 arguments
searchContact(...args: any[]): unknown// needs 2 arguments searchContact(...args: unknown[]): unknown// needs 2 arguments
searchMoreContact(...args: any[]): unknown// needs 1 arguments searchMoreContact(...args: unknown[]): unknown// needs 1 arguments
cancelSearchContact(...args: any[]): unknown// needs 3 arguments cancelSearchContact(...args: unknown[]): unknown// needs 3 arguments
searchGroupChatInfo(...args: any[]): unknown// needs 3 arguments searchGroupChatInfo(...args: unknown[]): unknown// needs 3 arguments
resetSearchGroupChatInfoSortType(...args: any[]): unknown// needs 3 arguments resetSearchGroupChatInfoSortType(...args: unknown[]): unknown// needs 3 arguments
resetSearchGroupChatInfoFilterMembers(...args: any[]): unknown// needs 3 arguments resetSearchGroupChatInfoFilterMembers(...args: unknown[]): unknown// needs 3 arguments
searchMoreGroupChatInfo(...args: any[]): unknown// needs 1 arguments searchMoreGroupChatInfo(...args: unknown[]): unknown// needs 1 arguments
cancelSearchGroupChatInfo(...args: any[]): unknown// needs 3 arguments cancelSearchGroupChatInfo(...args: unknown[]): unknown// needs 3 arguments
searchChatsWithKeywords(...args: any[]): unknown// needs 3 arguments searchChatsWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchMoreChatsWithKeywords(...args: any[]): unknown// needs 1 arguments searchMoreChatsWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatsWithKeywords(...args: any[]): unknown// needs 3 arguments cancelSearchChatsWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchChatMsgs(...args: any[]): unknown// needs 2 arguments searchChatMsgs(...args: unknown[]): unknown// needs 2 arguments
searchMoreChatMsgs(...args: any[]): unknown// needs 1 arguments searchMoreChatMsgs(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatMsgs(...args: any[]): unknown// needs 3 arguments cancelSearchChatMsgs(...args: unknown[]): unknown// needs 3 arguments
searchMsgWithKeywords(...args: any[]): unknown// needs 2 arguments searchMsgWithKeywords(...args: unknown[]): unknown// needs 2 arguments
searchMoreMsgWithKeywords(...args: any[]): unknown// needs 1 arguments searchMoreMsgWithKeywords(...args: unknown[]): unknown// needs 1 arguments
cancelSearchMsgWithKeywords(...args: any[]): unknown// needs 3 arguments cancelSearchMsgWithKeywords(...args: unknown[]): 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: unknown[]): unknown// needs 1 arguments
cancelSearchFileWithKeywords(...args: any[]): unknown// needs 3 arguments cancelSearchFileWithKeywords(...args: unknown[]): unknown// needs 3 arguments
searchAtMeChats(...args: any[]): unknown// needs 3 arguments searchAtMeChats(...args: unknown[]): unknown// needs 3 arguments
searchMoreAtMeChats(...args: any[]): unknown// needs 1 arguments searchMoreAtMeChats(...args: unknown[]): unknown// needs 1 arguments
cancelSearchAtMeChats(...args: any[]): unknown// needs 3 arguments cancelSearchAtMeChats(...args: unknown[]): unknown// needs 3 arguments
searchChatAtMeMsgs(...args: any[]): unknown// needs 1 arguments searchChatAtMeMsgs(...args: unknown[]): unknown// needs 1 arguments
searchMoreChatAtMeMsgs(...args: any[]): unknown// needs 1 arguments searchMoreChatAtMeMsgs(...args: unknown[]): unknown// needs 1 arguments
cancelSearchChatAtMeMsgs(...args: any[]): unknown// needs 3 arguments cancelSearchChatAtMeMsgs(...args: unknown[]): unknown// needs 3 arguments
addSearchHistory(param: { addSearchHistory(param: {
type: number,//4 type: number,//4
@@ -120,9 +120,9 @@ export interface NodeIKernelSearchService {
id?: number id?: number
}> }>
removeSearchHistory(...args: any[]): unknown// needs 1 arguments removeSearchHistory(...args: unknown[]): unknown// needs 1 arguments
searchCache(...args: any[]): unknown// needs 3 arguments searchCache(...args: unknown[]): unknown// needs 3 arguments
clearSearchCache(...args: any[]): unknown// needs 1 arguments clearSearchCache(...args: unknown[]): unknown// needs 1 arguments
} }

View File

@@ -36,6 +36,7 @@ export interface Group {
memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q" memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q"
} }
members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段 members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段
createTime: string
} }
export enum GroupMemberRole { export enum GroupMemberRole {

View File

@@ -275,7 +275,7 @@ export interface PicElement {
thumbPath: Map<number, string> thumbPath: Map<number, string>
picWidth: number picWidth: number
picHeight: number picHeight: number
fileSize: number fileSize: string
fileName: string fileName: string
fileUuid: string fileUuid: string
md5HexStr?: string md5HexStr?: string
@@ -352,7 +352,7 @@ export interface VideoElement {
thumbHeight?: number thumbHeight?: number
busiType?: 0 // 未知 busiType?: 0 // 未知
subBusiType?: 0 // 未知 subBusiType?: 0 // 未知
thumbPath?: Map<number, any> thumbPath?: Map<number, string>
transferStatus?: 0 // 未知 transferStatus?: 0 // 未知
progress?: 0 // 下载进度? progress?: 0 // 下载进度?
invalidState?: 0 // 未知 invalidState?: 0 // 未知
@@ -480,6 +480,8 @@ export interface RawMessage {
sourceMsgIsIncPic: boolean // 原消息是否有图片 sourceMsgIsIncPic: boolean // 原消息是否有图片
sourceMsgText: string sourceMsgText: string
replayMsgSeq: string // 源消息的msgSeq可以通过这个找到源消息的msgId replayMsgSeq: string // 源消息的msgSeq可以通过这个找到源消息的msgId
senderUidStr: string
replyMsgTime: string
} }
textElement: { textElement: {
atType: AtType atType: AtType
@@ -521,22 +523,22 @@ export interface MessageElement {
grayTipElement?: GrayTipElement grayTipElement?: GrayTipElement
arkElement?: ArkElement arkElement?: ArkElement
fileElement?: FileElement fileElement?: FileElement
liveGiftElement?: null liveGiftElement?: unknown
markdownElement?: MarkdownElement markdownElement?: MarkdownElement
structLongMsgElement?: any structLongMsgElement?: unknown
multiForwardMsgElement?: MultiForwardMsgElement multiForwardMsgElement?: MultiForwardMsgElement
giphyElement?: any giphyElement?: unknown
walletElement?: null walletElement?: unknown
inlineKeyboardElement?: InlineKeyboardElement inlineKeyboardElement?: InlineKeyboardElement
textGiftElement?: null //???? textGiftElement?: unknown //????
calendarElement?: any calendarElement?: unknown
yoloGameResultElement?: any yoloGameResultElement?: unknown
avRecordElement?: any avRecordElement?: unknown
structMsgElement?: null structMsgElement?: unknown
faceBubbleElement?: any faceBubbleElement?: unknown
shareLocationElement?: any shareLocationElement?: unknown
tofuRecordElement?: any tofuRecordElement?: unknown
taskTopMsgElement?: any taskTopMsgElement?: unknown
recommendedMsgElement?: any recommendedMsgElement?: unknown
actionBarElement?: any actionBarElement?: unknown
} }

View File

@@ -124,7 +124,7 @@ interface VideoInfo {
interface ExtOnlineBusinessInfo { interface ExtOnlineBusinessInfo {
buf: string buf: string
customStatus: any customStatus: unknown
videoBizInfo: VideoBizInfo videoBizInfo: VideoBizInfo
videoInfo: VideoInfo videoInfo: VideoInfo
} }
@@ -142,7 +142,7 @@ interface UserStatus {
termType: number termType: number
netType: number netType: number
iconType: number iconType: number
customStatus: any customStatus: unknown
setTime: string setTime: string
specialFlag: number specialFlag: number
abiFlag: number abiFlag: number
@@ -156,8 +156,8 @@ interface UserStatus {
interface PrivilegeIcon { interface PrivilegeIcon {
jumpUrl: string jumpUrl: string
openIconList: any[] openIconList: unknown[]
closeIconList: any[] closeIconList: unknown[]
} }
interface VasInfo { interface VasInfo {
@@ -180,7 +180,7 @@ interface VasInfo {
fontEffect: number fontEffect: number
newLoverDiamondFlag: number newLoverDiamondFlag: number
extendNameplateId: number extendNameplateId: number
diyNameplateIDs: any[] diyNameplateIDs: unknown[]
vipStartFlag: number vipStartFlag: number
vipDataFlag: number vipDataFlag: number
gameNameplateId: string gameNameplateId: string
@@ -200,8 +200,8 @@ export interface SimpleInfo {
status: UserStatus | null status: UserStatus | null
vasInfo: VasInfo | null vasInfo: VasInfo | null
relationFlags: RelationFlags | null relationFlags: RelationFlags | null
otherFlags: any | null otherFlags: unknown | null
intimate: any | null intimate: unknown | null
} }
interface RelationFlags { interface RelationFlags {
@@ -241,7 +241,7 @@ interface CommonExt {
address: string address: string
regTime: number regTime: number
interest: string interest: string
labels: any[] labels: string[]
qqLevel: QQLevel qqLevel: QQLevel
} }
@@ -323,12 +323,12 @@ export interface UserDetailInfoByUin {
regTime: number regTime: number
interest: string interest: string
termType: number termType: number
labels: any[] labels: unknown[]
qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number } qqLevel: { crownNum: number, sunNum: number, moonNum: number, starNum: number }
isHideQQLevel: number isHideQQLevel: number
privilegeIcon: { jumpUrl: string, openIconList: any[], closeIconList: any[] } privilegeIcon: { jumpUrl: string, openIconList: unknown[], closeIconList: unknown[] }
isHidePrivilegeIcon: number isHidePrivilegeIcon: number
photoWall: { picList: any[] } photoWall: { picList: unknown[] }
vipFlag: boolean vipFlag: boolean
yearVipFlag: boolean yearVipFlag: boolean
svipFlag: boolean svipFlag: boolean

View File

@@ -11,11 +11,11 @@ import {
NodeIKernelTipOffService, NodeIKernelTipOffService,
NodeIKernelSearchService NodeIKernelSearchService
} from './services' } from './services'
import os from 'node:os' import { constants } from 'node:os'
import { Dict } from 'cosmokit'
const Process = require('node:process') const Process = require('node:process')
export interface NodeIQQNTWrapperSession { export interface NodeIQQNTWrapperSession {
[key: string]: any
getBuddyService(): NodeIKernelBuddyService getBuddyService(): NodeIKernelBuddyService
getGroupService(): NodeIKernelGroupService getGroupService(): NodeIKernelGroupService
getProfileService(): NodeIKernelProfileService getProfileService(): NodeIKernelProfileService
@@ -34,20 +34,7 @@ export interface WrapperApi {
} }
export interface WrapperConstructor { export interface WrapperConstructor {
[key: string]: any [key: string]: unknown
NodeIKernelBuddyListener?: any
NodeIKernelGroupListener?: any
NodeQQNTWrapperUtil?: any
NodeIKernelMsgListener?: any
NodeIQQNTWrapperEngine?: any
NodeIGlobalAdapter?: any
NodeIDependsAdapter?: any
NodeIDispatcherAdapter?: any
NodeIKernelSessionListener?: any
NodeIKernelLoginService?: any
NodeIKernelLoginListener?: any
NodeIKernelProfileService?: any
NodeIKernelProfileListener?: any
} }
const wrapperApi: WrapperApi = {} const wrapperApi: WrapperApi = {}
@@ -72,11 +59,11 @@ const constructor = [
Process.dlopenOrig = Process.dlopen Process.dlopenOrig = Process.dlopen
Process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) { Process.dlopen = function (module: Dict, filename: string, flags = constants.dlopen.RTLD_LAZY) {
const dlopenRet = this.dlopenOrig(module, filename, flags) const dlopenRet = this.dlopenOrig(module, filename, flags)
for (let export_name in module.exports) { for (const export_name in module.exports) {
module.exports[export_name] = new Proxy(module.exports[export_name], { module.exports[export_name] = new Proxy(module.exports[export_name], {
construct: (target, args, _newTarget) => { construct: (target, args) => {
const ret = new target(...args) const ret = new target(...args)
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
return ret return ret

View File

@@ -26,13 +26,13 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
try { try {
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) {
this.ctx.logger.error('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200) return OB11Response.error(e?.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200)
} }
} }
public async websocketHandle(payload: PayloadType, echo: any): Promise<OB11Return<ReturnDataType | null>> { public async websocketHandle(payload: PayloadType, echo: unknown): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload) const result = await this.check(payload)
if (!result.valid) { if (!result.valid) {
return OB11Response.error(result.message, 1400) return OB11Response.error(result.message, 1400)
@@ -40,9 +40,9 @@ abstract class BaseAction<PayloadType, ReturnDataType> {
try { try {
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) {
this.ctx.logger.error('发生错误', e) this.ctx.logger.error('发生错误', e)
return OB11Response.error(e.stack?.toString() || e.toString(), 1200, echo) return OB11Response.error((e as Error)?.stack?.toString() || String(e), 1200, echo)
} }
} }

View File

@@ -13,16 +13,16 @@ export class OB11Response {
} }
} }
static ok<T>(data: T, echo: any = null) { static ok<T>(data: T, echo?: unknown) {
let res = OB11Response.res<T>(data, 'ok', 0) const res = OB11Response.res<T>(data, 'ok', 0)
if (!isNullable(echo)) { if (!isNullable(echo)) {
res.echo = echo res.echo = echo
} }
return res return res
} }
static error(err: string, retcode: number, echo: any = null) { static error(err: string, retcode: number, echo?: unknown) {
let res = OB11Response.res(null, 'failed', retcode, err) const res = OB11Response.res(null, 'failed', retcode, err)
if (!isNullable(echo)) { if (!isNullable(echo)) {
res.echo = echo res.echo = echo
} }

View File

@@ -1,6 +1,5 @@
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 { ActionName } from '../types' import { ActionName } from '../types'
import { Peer, ElementType } from '@/ntqqapi/types' import { Peer, ElementType } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
@@ -20,7 +19,7 @@ export interface GetFileResponse {
export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> { export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
// forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/onebot11/action/file/GetFile.ts#L44 // forked from https://github.com/NapNeko/NapCatQQ/blob/6f6b258f22d7563f15d84e7172c4d4cbb547f47e/src/onebot11/action/file/GetFile.ts#L44
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> { protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
const { enableLocalFile2Url } = getConfigUtil().getConfig() const { enableLocalFile2Url } = this.adapter.config
let fileCache = await MessageUnique.getFileCacheById(String(payload.file)) let fileCache = await MessageUnique.getFileCacheById(String(payload.file))
if (!fileCache?.length) { if (!fileCache?.length) {

View File

@@ -1,7 +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 path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import fs from 'node:fs'
@@ -13,11 +12,11 @@ export default class GetRecord extends GetFileBase {
actionName = ActionName.GetRecord actionName = ActionName.GetRecord
protected async _handle(payload: Payload): Promise<GetFileResponse> { protected async _handle(payload: Payload): Promise<GetFileResponse> {
let res = await super._handle(payload) const res = await super._handle(payload)
res.file = await decodeSilk(this.ctx, res.file!, payload.out_format) res.file = await decodeSilk(this.ctx, res.file!, payload.out_format)
res.file_name = path.basename(res.file) res.file_name = path.basename(res.file)
res.file_size = fs.statSync(res.file).size.toString() res.file_size = fs.statSync(res.file).size.toString()
if (getConfigUtil().getConfig().enableLocalFile2Url) { if (this.adapter.config.enableLocalFile2Url) {
res.base64 = fs.readFileSync(res.file, 'base64') res.base64 = fs.readFileSync(res.file, 'base64')
} }
return res return res

View File

@@ -7,7 +7,7 @@ interface Payload {
parent_id?: '/' parent_id?: '/'
} }
export class GoCQHTTPCreateGroupFileFolder extends BaseAction<Payload, null> { export class CreateGroupFileFolder extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_CreateGroupFileFolder actionName = ActionName.GoCQHTTP_CreateGroupFileFolder
async _handle(payload: Payload) { async _handle(payload: Payload) {

View File

@@ -6,10 +6,10 @@ interface Payload {
message_id: number | string message_id: number | string
} }
export default class GoCQHTTPDelEssenceMsg extends BaseAction<Payload, any> { export class DelEssenceMsg extends BaseAction<Payload, unknown> {
actionName = ActionName.GoCQHTTP_DelEssenceMsg; actionName = ActionName.GoCQHTTP_DelEssenceMsg
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
if (!payload.message_id) { if (!payload.message_id) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
} }

View File

@@ -7,7 +7,7 @@ interface Payload {
busid?: 102 busid?: 102
} }
export class GoCQHTTPDelGroupFile extends BaseAction<Payload, null> { export class DelGroupFile extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_DelGroupFile actionName = ActionName.GoCQHTTP_DelGroupFile
async _handle(payload: Payload) { async _handle(payload: Payload) {

View File

@@ -6,7 +6,7 @@ interface Payload {
folder_id: string folder_id: string
} }
export class GoCQHTTPDelGroupFolder extends BaseAction<Payload, null> { export class DelGroupFolder extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_DelGroupFolder actionName = ActionName.GoCQHTTP_DelGroupFolder
async _handle(payload: Payload) { async _handle(payload: Payload) {

View File

@@ -6,6 +6,7 @@ import { ActionName } from '../types'
import { calculateFileMD5, fetchFile } from '@/common/utils' import { calculateFileMD5, fetchFile } from '@/common/utils'
import { TEMP_DIR } from '@/common/globalVars' import { TEMP_DIR } from '@/common/globalVars'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Dict } from 'cosmokit'
interface Payload { interface Payload {
thread_count?: number thread_count?: number
@@ -19,7 +20,7 @@ interface FileResponse {
file: string file: string
} }
export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileResponse> { export class DownloadFile extends BaseAction<Payload, FileResponse> {
actionName = ActionName.GoCQHTTP_DownloadFile actionName = ActionName.GoCQHTTP_DownloadFile
protected async _handle(payload: Payload): Promise<FileResponse> { protected async _handle(payload: Payload): Promise<FileResponse> {
@@ -51,7 +52,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
} }
getHeaders(headersIn?: string | string[]): Record<string, string> { getHeaders(headersIn?: string | string[]): Record<string, string> {
const headers = {} const headers: Dict = {}
if (typeof headersIn == 'string') { if (typeof headersIn == 'string') {
headersIn = headersIn.split('[\\r\\n]') headersIn = headersIn.split('[\\r\\n]')
} }

View File

@@ -1,5 +1,5 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types' import { OB11ForwardMessage } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
@@ -10,12 +10,12 @@ interface Payload {
} }
interface Response { interface Response {
messages: (OB11Message & { content: OB11MessageData })[] messages: OB11ForwardMessage[]
} }
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, Response> { export class GetForwardMsg extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetForwardMsg actionName = ActionName.GoCQHTTP_GetForwardMsg
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
const msgId = payload.id || payload.message_id const msgId = payload.id || payload.message_id
if (!msgId) { if (!msgId) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
@@ -36,15 +36,16 @@ export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, Response> {
resMsg.message_id = MessageUnique.createMsg({ resMsg.message_id = MessageUnique.createMsg({
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
}, msg.msgId)! }, msg.msgId)
return resMsg return resMsg
}), }),
) )
messages.map(v => { const forwardMessages = messages.map(v => {
const msg = v as Partial<OB11ForwardMessage> const msg = v as Partial<OB11ForwardMessage>
msg.content = msg.message msg.content = msg.message
delete msg.message delete msg.message
return msg as OB11ForwardMessage
}) })
return { messages } return { messages: forwardMessages }
} }
} }

View File

@@ -0,0 +1,25 @@
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number | string
}
interface Response {
can_at_all: boolean
remain_at_all_count_for_group: number
remain_at_all_count_for_uin: number
}
export class GetGroupAtAllRemain extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString())
return {
can_at_all: data.atInfo.canAtAll,
remain_at_all_count_for_group: data.atInfo.RemainAtAllCountForGroup,
remain_at_all_count_for_uin: data.atInfo.RemainAtAllCountForUin
}
}
}

View File

@@ -8,8 +8,8 @@ import { MessageUnique } from '@/common/utils/messageUnique'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
message_seq?: number message_seq?: number | string
count?: number count?: number | string
reverseOrder?: boolean reverseOrder?: boolean
} }
@@ -17,7 +17,7 @@ interface Response {
messages: OB11Message[] messages: OB11Message[]
} }
export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Response> { export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory actionName = ActionName.GoCQHTTP_GetGroupMsgHistory
protected async _handle(payload: Payload): Promise<Response> { protected async _handle(payload: Payload): Promise<Response> {
@@ -27,13 +27,13 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
let msgList: RawMessage[] | undefined let msgList: RawMessage[] | undefined
// 包含 message_seq 0 // 包含 message_seq 0
if (!payload.message_seq) { if (!payload.message_seq) {
msgList = (await this.ctx.ntMsgApi.getLastestMsgByUids(peer, count))?.msgList msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList
} else { } else {
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq))?.MsgId
if (!startMsgId) throw `消息${payload.message_seq}不存在` if (!startMsgId) throw new Error(`消息${payload.message_seq}不存在`)
msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, count)).msgList msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList
} }
if (!msgList?.length) throw '未找到消息' if (!msgList?.length) throw new Error('未找到消息')
if (isReverseOrder) msgList.reverse() if (isReverseOrder) msgList.reverse()
await Promise.all( await Promise.all(
msgList.map(async msg => { msgList.map(async msg => {

View File

@@ -0,0 +1,62 @@
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '../../types'
interface Payload {
group_id: string | number
file_count: string | number
}
interface Response {
files: OB11GroupFile[]
folders: OB11GroupFileFolder[]
}
export class GetGroupRootFiles extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupRootFiles
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: +(payload.file_count ?? 50),
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
this.ctx.logger.info(data)
return {
files: data.filter(item => item.fileInfo)
.map(item => {
const file = item.fileInfo!
return {
group_id: +item.peerId,
file_id: file.fileId,
file_name: file.fileName,
busid: file.busId,
file_size: +file.fileSize,
upload_time: file.uploadTime,
dead_time: file.deadTime,
modify_time: file.modifyTime,
download_times: file.downloadTimes,
uploader: +file.uploaderUin,
uploader_name: file.uploaderName
}
}),
folders: data.filter(item => item.folderInfo)
.map(item => {
const folder = item.folderInfo!
return {
group_id: +item.peerId,
folder_id: folder.folderId,
folder_name: folder.folderName,
create_time: folder.createTime,
creator: +folder.createUin,
creator_name: folder.creatorName,
total_file_count: folder.totalFileCount
}
})
}
}
}

View File

@@ -24,10 +24,10 @@ interface Response {
}[] }[]
} }
export class GoCQHTTPGetGroupSystemMsg extends BaseAction<void, Response> { export class GetGroupSystemMsg extends BaseAction<void, Response> {
actionName = ActionName.GoCQHTTP_GetGroupSystemMsg actionName = ActionName.GoCQHTTP_GetGroupSystemMsg
async _handle(payload: void) { async _handle() {
const singleScreenNotifies = await this.ctx.ntGroupApi.getSingleScreenNotifies(10) const singleScreenNotifies = await this.ctx.ntGroupApi.getSingleScreenNotifies(10)
const data: Response = { invited_requests: [], join_requests: [] } const data: Response = { invited_requests: [], join_requests: [] }
for (const notify of singleScreenNotifies) { for (const notify of singleScreenNotifies) {

View File

@@ -10,7 +10,7 @@ interface Payload {
user_id: number | string user_id: number | string
} }
export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11User> { export class GetStrangerInfo extends BaseAction<Payload, OB11User> {
actionName = ActionName.GoCQHTTP_GetStrangerInfo actionName = ActionName.GoCQHTTP_GetStrangerInfo
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload): Promise<OB11User> {

View File

@@ -5,10 +5,10 @@ interface Payload {
message_id: number message_id: number
} }
export default class GoCQHTTPMarkMsgAsRead extends BaseAction<Payload, null> { export class MarkMsgAsRead extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_MarkMsgAsRead actionName = ActionName.GoCQHTTP_MarkMsgAsRead
protected async _handle(payload: Payload): Promise<null> { protected async _handle() {
return null return null
} }
} }

View File

@@ -7,7 +7,7 @@ interface Payload {
operation: QuickOperation operation: QuickOperation
} }
export class GoCQHTTHandleQuickOperation extends BaseAction<Payload, null> { export class HandleQuickOperation extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_HandleQuickOperation actionName = ActionName.GoCQHTTP_HandleQuickOperation
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e)) handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e))

View File

@@ -3,7 +3,7 @@ import { OB11PostSendMsg } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { convertMessage2List } from '../../helper/createMessage' import { convertMessage2List } from '../../helper/createMessage'
export class GoCQHTTPSendForwardMsg extends SendMsg { export class SendForwardMsg extends SendMsg {
actionName = ActionName.GoCQHTTP_SendForwardMsg actionName = ActionName.GoCQHTTP_SendForwardMsg
protected async check(payload: OB11PostSendMsg) { protected async check(payload: OB11PostSendMsg) {
@@ -12,10 +12,10 @@ export class GoCQHTTPSendForwardMsg extends SendMsg {
} }
} }
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsg { export class SendPrivateForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg
} }
export class GoCQHTTPSendGroupForwardMsg extends GoCQHTTPSendForwardMsg { export class SendGroupForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendGroupForwardMsg actionName = ActionName.GoCQHTTP_SendGroupForwardMsg
} }

View File

@@ -0,0 +1,37 @@
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number | string
content: string
image?: string
pinned?: number | string //扩展
confirm_required?: number | string //扩展
}
export class SendGroupNotice extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SendGroupNotice
async _handle(payload: Payload) {
const type = 1
const isShowEditCard = 0
const tipWindowType = 0
const pinned = Number(payload.pinned ?? 0)
const confirmRequired = Number(payload.confirm_required ?? 1)
const result = await this.ctx.ntWebApi.setGroupNotice({
groupCode: payload.group_id.toString(),
content: payload.content,
pinned,
type,
isShowEditCard,
tipWindowType,
confirmRequired,
picId: ''
})
if (result.ec !== 0) {
throw new Error(`设置群公告失败, 错误信息: ${result.em}`)
}
return null
}
}

View File

@@ -6,10 +6,10 @@ interface Payload {
message_id: number | string message_id: number | string
} }
export default class GoCQHTTPSetEssenceMsg extends BaseAction<Payload, any> { export class SetEssenceMsg extends BaseAction<Payload, unknown> {
actionName = ActionName.GoCQHTTP_SetEssenceMsg; actionName = ActionName.GoCQHTTP_SetEssenceMsg
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
if (!payload.message_id) { if (!payload.message_id) {
throw Error('message_id不能为空') throw Error('message_id不能为空')
} }

View File

@@ -2,24 +2,22 @@ import fs from 'node:fs'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { SendElementEntities } from '@/ntqqapi/entities' import { SendElementEntities } from '@/ntqqapi/entities'
import { ChatType, SendFileElement } from '@/ntqqapi/types' import { SendFileElement } from '@/ntqqapi/types'
import { uri2local } from '@/common/utils' import { uri2local } from '@/common/utils'
import { Peer } from '@/ntqqapi/types' import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
import { sendMsg } from '../../helper/createMessage'
interface Payload { interface UploadGroupFilePayload {
user_id: number | string group_id: number | string
group_id?: number | string
file: string file: string
name: string name: string
folder?: string folder?: string
folder_id?: string folder_id?: string
} }
export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> { export class UploadGroupFile extends BaseAction<UploadGroupFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadGroupFile actionName = ActionName.GoCQHTTP_UploadGroupFile
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: UploadGroupFilePayload): Promise<null> {
let file = payload.file let file = payload.file
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
file = `file://${file}` file = `file://${file}`
@@ -29,31 +27,23 @@ export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
throw new Error(downloadResult.errMsg) throw new Error(downloadResult.errMsg)
} }
const sendFileEle = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name, payload.folder_id) const sendFileEle = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name, payload.folder_id)
await sendMsg(this.ctx, { const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
chatType: ChatType.group, await sendMsg(this.ctx, peer, [sendFileEle], [])
peerUid: payload.group_id?.toString()!,
}, [sendFileEle], [], true)
return null return null
} }
} }
export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> { interface UploadPrivateFilePayload {
user_id: number | string
file: string
name: string
}
export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null> {
actionName = ActionName.GoCQHTTP_UploadPrivateFile actionName = ActionName.GoCQHTTP_UploadPrivateFile
async getPeer(payload: Payload): Promise<Peer> { protected async _handle(payload: UploadPrivateFilePayload): Promise<null> {
if (payload.user_id) { const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
const peerUid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!peerUid) {
throw `私聊${payload.user_id}不存在`
}
const isBuddy = await this.ctx.ntFriendApi.isBuddy(peerUid)
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid }
}
throw '缺少参数 user_id'
}
protected async _handle(payload: Payload): Promise<null> {
const peer = await this.getPeer(payload)
let file = payload.file let file = payload.file
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
file = `file://${file}` file = `file://${file}`
@@ -63,7 +53,7 @@ export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> {
throw new Error(downloadResult.errMsg) throw new Error(downloadResult.errMsg)
} }
const sendFileEle: SendFileElement = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name) const sendFileEle: SendFileElement = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name)
await sendMsg(this.ctx, peer, [sendFileEle], [], true) await sendMsg(this.ctx, peer, [sendFileEle], [])
return null return null
} }
} }

View File

@@ -10,7 +10,7 @@ interface PayloadType {
export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet | void> { export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet | void> {
actionName = ActionName.GoCQHTTP_GetEssenceMsg actionName = ActionName.GoCQHTTP_GetEssenceMsg
protected async _handle(payload: PayloadType) { protected async _handle() {
throw '此 api 暂不支持' throw '此 api 暂不支持'
} }
} }

View File

@@ -7,11 +7,10 @@ interface Payload {
type?: WebHonorType type?: WebHonorType
} }
export class GetGroupHonorInfo extends BaseAction<Payload, Array<any>> { export class GetGroupHonorInfo extends BaseAction<Payload, unknown> {
actionName = ActionName.GetGroupHonorInfo actionName = ActionName.GetGroupHonorInfo
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
// console.log(await NTQQUserApi.getRobotUinRange())
if (!payload.group_id) { if (!payload.group_id) {
throw '缺少参数group_id' throw '缺少参数group_id'
} }

View File

@@ -10,8 +10,8 @@ interface Payload {
class GetGroupList extends BaseAction<Payload, OB11Group[]> { class GetGroupList extends BaseAction<Payload, OB11Group[]> {
actionName = ActionName.GetGroupList actionName = ActionName.GetGroupList
protected async _handle(payload: Payload) { protected async _handle() {
const groupList = await this.ctx.ntGroupApi.getGroups(payload?.no_cache === true || payload?.no_cache === 'true') const groupList = await this.ctx.ntGroupApi.getGroups()
return OB11Entities.groups(groupList) return OB11Entities.groups(groupList)
} }
} }

View File

@@ -18,7 +18,7 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
if (member) { if (member) {
if (isNullable(member.sex)) { if (isNullable(member.sex)) {
//log('获取群成员详细信息') //log('获取群成员详细信息')
const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid, true) const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid)
//log('群成员详细信息结果', info) //log('群成员详细信息结果', info)
Object.assign(member, info) Object.assign(member, info)
} }

View File

@@ -4,7 +4,7 @@ import { ActionName } from '../types'
export default class GetGuildList extends BaseAction<null, null> { export default class GetGuildList extends BaseAction<null, null> {
actionName = ActionName.GetGuildList actionName = ActionName.GetGuildList
protected async _handle(payload: null): Promise<null> { protected async _handle() {
return null return null
} }
} }

View File

@@ -6,10 +6,10 @@ interface Payload {
is_dismiss: boolean is_dismiss: boolean
} }
export default class SetGroupLeave extends BaseAction<Payload, any> { export default class SetGroupLeave extends BaseAction<Payload, void> {
actionName = ActionName.SetGroupLeave actionName = ActionName.SetGroupLeave
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
try { try {
await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString()) await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString())
} catch (e) { } catch (e) {

View File

@@ -16,11 +16,11 @@ import CanSendRecord from './system/CanSendRecord'
import CanSendImage from './system/CanSendImage' import CanSendImage from './system/CanSendImage'
import GetStatus from './system/GetStatus' import GetStatus from './system/GetStatus'
import { import {
GoCQHTTPSendForwardMsg, SendForwardMsg,
GoCQHTTPSendGroupForwardMsg, SendGroupForwardMsg,
GoCQHTTPSendPrivateForwardMsg, SendPrivateForwardMsg,
} from './go-cqhttp/SendForwardMsg' } from './go-cqhttp/SendForwardMsg'
import GoCQHTTPGetStrangerInfo from './go-cqhttp/GetStrangerInfo' import { GetStrangerInfo } from './go-cqhttp/GetStrangerInfo'
import SendLike from './user/SendLike' import SendLike from './user/SendLike'
import SetGroupAddRequest from './group/SetGroupAddRequest' import SetGroupAddRequest from './group/SetGroupAddRequest'
import SetGroupLeave from './group/SetGroupLeave' import SetGroupLeave from './group/SetGroupLeave'
@@ -35,29 +35,33 @@ import SetGroupAdmin from './group/SetGroupAdmin'
import SetGroupCard from './group/SetGroupCard' import SetGroupCard from './group/SetGroupCard'
import GetImage from './file/GetImage' import GetImage from './file/GetImage'
import GetRecord from './file/GetRecord' import GetRecord from './file/GetRecord'
import GoCQHTTPMarkMsgAsRead from './msg/MarkMsgAsRead' import { MarkMsgAsRead } from './go-cqhttp/MarkMsgAsRead'
import CleanCache from './system/CleanCache' import CleanCache from './system/CleanCache'
import { GoCQHTTPUploadGroupFile, GoCQHTTPUploadPrivateFile } from './go-cqhttp/UploadFile' import { UploadGroupFile, UploadPrivateFile } from './go-cqhttp/UploadFile'
import { GetConfigAction, SetConfigAction } from './llonebot/Config' import { GetConfigAction, SetConfigAction } from './llonebot/Config'
import GetGroupAddRequest from './llonebot/GetGroupAddRequest' import GetGroupAddRequest from './llonebot/GetGroupAddRequest'
import SetQQAvatar from './llonebot/SetQQAvatar' import SetQQAvatar from './llonebot/SetQQAvatar'
import GoCQHTTPDownloadFile from './go-cqhttp/DownloadFile' import { DownloadFile } from './go-cqhttp/DownloadFile'
import GoCQHTTPGetGroupMsgHistory from './go-cqhttp/GetGroupMsgHistory' import { GetGroupMsgHistory } from './go-cqhttp/GetGroupMsgHistory'
import GetFile from './file/GetFile' import GetFile from './file/GetFile'
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg' import { GetForwardMsg } from './go-cqhttp/GetForwardMsg'
import { GetCookies } from './user/GetCookie' import { GetCookies } from './user/GetCookie'
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike' import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg' import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg'
import { GetGroupEssence } from './group/GetGroupEssence' import { GetGroupEssence } from './group/GetGroupEssence'
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo' import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation' import { HandleQuickOperation } from './go-cqhttp/QuickOperation'
import GoCQHTTPSetEssenceMsg from './go-cqhttp/SetEssenceMsg' import { SetEssenceMsg } from './go-cqhttp/SetEssenceMsg'
import GoCQHTTPDelEssenceMsg from './go-cqhttp/DelEssenceMsg' import { DelEssenceMsg } from './go-cqhttp/DelEssenceMsg'
import GetEvent from './llonebot/GetEvent' import GetEvent from './llonebot/GetEvent'
import { GoCQHTTPDelGroupFile } from './go-cqhttp/DelGroupFile' import { DelGroupFile } from './go-cqhttp/DelGroupFile'
import { GoCQHTTPGetGroupSystemMsg } from './go-cqhttp/GetGroupSystemMsg' import { GetGroupSystemMsg } from './go-cqhttp/GetGroupSystemMsg'
import { GoCQHTTPCreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder' import { CreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder'
import { GoCQHTTPDelGroupFolder } from './go-cqhttp/DelGroupFolder' import { DelGroupFolder } from './go-cqhttp/DelGroupFolder'
import { GetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain'
import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles'
import { SetOnlineStatus } from './llonebot/SetOnlineStatus'
import { SendGroupNotice } from './go-cqhttp/SendGroupNotice'
export function initActionMap(adapter: Adapter) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@@ -69,6 +73,7 @@ export function initActionMap(adapter: Adapter) {
new SetQQAvatar(adapter), new SetQQAvatar(adapter),
new GetFriendWithCategory(adapter), new GetFriendWithCategory(adapter),
new GetEvent(adapter), new GetEvent(adapter),
new SetOnlineStatus(adapter),
// onebot11 // onebot11
new SendLike(adapter), new SendLike(adapter),
new GetMsg(adapter), new GetMsg(adapter),
@@ -105,26 +110,29 @@ export function initActionMap(adapter: Adapter) {
//以下为go-cqhttp api //以下为go-cqhttp api
new GetGroupEssence(adapter), new GetGroupEssence(adapter),
new GetGroupHonorInfo(adapter), new GetGroupHonorInfo(adapter),
new GoCQHTTPSendForwardMsg(adapter), new SendForwardMsg(adapter),
new GoCQHTTPSendGroupForwardMsg(adapter), new SendGroupForwardMsg(adapter),
new GoCQHTTPSendPrivateForwardMsg(adapter), new SendPrivateForwardMsg(adapter),
new GoCQHTTPGetStrangerInfo(adapter), new GetStrangerInfo(adapter),
new GoCQHTTPDownloadFile(adapter), new DownloadFile(adapter),
new GetGuildList(adapter), new GetGuildList(adapter),
new GoCQHTTPMarkMsgAsRead(adapter), new MarkMsgAsRead(adapter),
new GoCQHTTPUploadGroupFile(adapter), new UploadGroupFile(adapter),
new GoCQHTTPUploadPrivateFile(adapter), new UploadPrivateFile(adapter),
new GoCQHTTPGetGroupMsgHistory(adapter), new GetGroupMsgHistory(adapter),
new GoCQHTTGetForwardMsgAction(adapter), new GetForwardMsg(adapter),
new GoCQHTTHandleQuickOperation(adapter), new HandleQuickOperation(adapter),
new GoCQHTTPSetEssenceMsg(adapter), new SetEssenceMsg(adapter),
new GoCQHTTPDelEssenceMsg(adapter), new DelEssenceMsg(adapter),
new GoCQHTTPDelGroupFile(adapter), new DelGroupFile(adapter),
new GoCQHTTPGetGroupSystemMsg(adapter), new GetGroupSystemMsg(adapter),
new GoCQHTTPCreateGroupFileFolder(adapter), new CreateGroupFileFolder(adapter),
new GoCQHTTPDelGroupFolder(adapter) new DelGroupFolder(adapter),
new GetGroupAtAllRemain(adapter),
new GetGroupRootFiles(adapter),
new SendGroupNotice(adapter)
] ]
const actionMap = new Map<string, BaseAction<any, any>>() const actionMap = new Map<string, BaseAction<unknown, unknown>>()
for (const action of actionHandlers) { for (const action of actionHandlers) {
actionMap.set(action.actionName, action) actionMap.set(action.actionName, action)
actionMap.set(action.actionName + '_async', action) actionMap.set(action.actionName + '_async', action)

View File

@@ -5,13 +5,15 @@ 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
protected async _handle(payload: null): Promise<Config> {
protected async _handle(): Promise<Config> {
return getConfigUtil().getConfig() return getConfigUtil().getConfig()
} }
} }
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> {
getConfigUtil().setConfig(payload) getConfigUtil().setConfig(payload)
} }

View File

@@ -3,19 +3,19 @@ import { ActionName } from '../types'
interface Payload { interface Payload {
method: string method: string
args: any[] args: unknown[]
} }
export default class Debug extends BaseAction<Payload, any> { export default class Debug extends BaseAction<Payload, unknown> {
actionName = ActionName.Debug actionName = ActionName.Debug
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload) {
this.ctx.logger.info('debug call ntqq api', payload) this.ctx.logger.info('debug call ntqq api', payload)
const { ntMsgApi, ntFileApi, ntFileCacheApi, ntFriendApi, ntGroupApi, ntUserApi, ntWindowApi } = this.ctx const { ntMsgApi, ntFileApi, ntFileCacheApi, ntFriendApi, ntGroupApi, ntUserApi, ntWindowApi } = this.ctx
const ntqqApi = [ntMsgApi, ntFriendApi, ntGroupApi, ntUserApi, ntFileApi, ntFileCacheApi, ntWindowApi] const ntqqApi = [ntMsgApi, ntFriendApi, ntGroupApi, ntUserApi, ntFileApi, ntFileCacheApi, ntWindowApi]
for (const ntqqApiClass of ntqqApi) { for (const ntqqApiClass of ntqqApi) {
const method = ntqqApiClass[payload.method] as Function const method = ntqqApiClass[payload.method as keyof typeof ntqqApiClass]
if (method) { if (method && method instanceof Function) {
const result = method.apply(ntqqApiClass, 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

View File

@@ -13,13 +13,14 @@ interface Payload {
export default class GetEvent extends BaseAction<Payload, PostEventType[]> { export default class GetEvent extends BaseAction<Payload, PostEventType[]> {
actionName = ActionName.GetEvent actionName = ActionName.GetEvent
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 const timeout = parseInt(payload.timeout?.toString()) || 0
let evts = await getHttpEvent(key, timeout) const evts = await getHttpEvent(key, timeout)
return evts return evts
} }
} }

View File

@@ -11,7 +11,7 @@ interface OB11GroupRequestNotify {
export default class GetGroupAddRequest extends BaseAction<null, OB11GroupRequestNotify[]> { export default class GetGroupAddRequest extends BaseAction<null, OB11GroupRequestNotify[]> {
actionName = ActionName.GetGroupIgnoreAddRequest actionName = ActionName.GetGroupIgnoreAddRequest
protected async _handle(payload: null): Promise<OB11GroupRequestNotify[]> { protected async _handle(): Promise<OB11GroupRequestNotify[]> {
const data = await this.ctx.ntGroupApi.getGroupIgnoreNotifies() const data = await this.ctx.ntGroupApi.getGroupIgnoreNotifies()
const notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.KUNHANDLE) const notifies: GroupNotify[] = data.notifies.filter((notify) => notify.status === GroupNotifyStatus.KUNHANDLE)
const returnData: OB11GroupRequestNotify[] = [] const returnData: OB11GroupRequestNotify[] = []

View File

@@ -0,0 +1,25 @@
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
status: number | string
ext_status: number | string
battery_status: number | string
}
export class SetOnlineStatus extends BaseAction<Payload, null> {
actionName = ActionName.SetOnlineStatus
async _handle(payload: Payload) {
const ret = await this.ctx.ntUserApi.setSelfStatus(
Number(payload.status),
Number(payload.ext_status),
Number(payload.battery_status),
)
if (ret.result !== 0) {
this.ctx.logger.error(ret)
throw new Error('设置在线状态失败')
}
return null
}
}

View File

@@ -11,13 +11,17 @@ class DeleteMsg extends BaseAction<Payload, void> {
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (!payload.message_id) { if (!payload.message_id) {
throw Error('message_id不能为空') throw new Error('参数message_id不能为空')
} }
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) { if (!msg) {
throw `消息${payload.message_id}不存在` throw new Error(`消息${payload.message_id}不存在`)
}
const data = await this.ctx.ntMsgApi.recallMsg(msg.Peer, [msg.MsgId])
if (data.result !== 0) {
this.ctx.logger.error('delete_msg', payload.message_id, data)
throw new Error(`消息撤回失败`)
} }
await this.ctx.ntMsgApi.recallMsg(msg.Peer, [msg.MsgId])
} }
} }

View File

@@ -13,53 +13,22 @@ import {
OB11MessageNode, OB11MessageNode,
OB11PostSendMsg, OB11PostSendMsg,
} from '../../types' } from '../../types'
import fs from 'node:fs'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName, BaseCheckResult } from '../types' import { ActionName, BaseCheckResult } from '../types'
import fs from 'node:fs'
import { getConfigUtil } from '@/common/config'
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '@/common/utils/sign' import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '@/common/utils/sign'
import { Peer } from '@/ntqqapi/types/msg' import { Peer } from '@/ntqqapi/types/msg'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
import { convertMessage2List, createSendElements, sendMsg } from '../../helper/createMessage' import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
export interface ReturnDataType { interface ReturnData {
message_id: number message_id: number
} }
export enum ContextMode { export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnData> {
Normal = 0,
Private = 1,
Group = 2
}
export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
actionName = ActionName.SendMsg actionName = ActionName.SendMsg
private async createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
// This function determines the type of message by the existence of user_id / group_id,
// not message_type.
// This redundant design of Ob11 here should be blamed.
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
return {
chatType: ChatType.group,
peerUid: payload.group_id.toString(),
}
}
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
const Uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
const isBuddy = await this.ctx.ntFriendApi.isBuddy(Uid!)
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy)
return {
chatType: isBuddy ? ChatType.friend : ChatType.temp,
peerUid: Uid!,
guildId: payload.group_id?.toString() || '' //临时主动发起时需要传入群号
}
}
throw '请指定 group_id 或 user_id'
}
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
const messages = convertMessage2List(payload.message) const messages = convertMessage2List(payload.message)
const fmNum = this.getSpecialMsgNum(messages, OB11MessageDataType.node) const fmNum = this.getSpecialMsgNum(messages, OB11MessageDataType.node)
@@ -76,21 +45,19 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
message: '音乐消息不可以和其他消息混在一起发送', message: '音乐消息不可以和其他消息混在一起发送',
} }
} }
if (payload.user_id && payload.message_type !== 'group') {
}
return { return {
valid: true, valid: true,
} }
} }
protected async _handle(payload: OB11PostSendMsg) { protected async _handle(payload: OB11PostSendMsg) {
let contextMode = ContextMode.Normal let contextMode = CreatePeerMode.Normal
if (payload.message_type === 'group') { if (payload.message_type === 'group') {
contextMode = ContextMode.Group contextMode = CreatePeerMode.Group
} else if (payload.message_type === 'private') { } else if (payload.message_type === 'private') {
contextMode = ContextMode.Private contextMode = CreatePeerMode.Private
} }
const peer = await this.createContext(payload, contextMode) const peer = await createPeer(this.ctx, payload, contextMode)
const messages = convertMessage2List( const messages = convertMessage2List(
payload.message, payload.message,
payload.auto_escape === true || payload.auto_escape === 'true', payload.auto_escape === true || payload.auto_escape === 'true',
@@ -98,15 +65,15 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) { if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) {
try { try {
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[]) const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[])
return { message_id: returnMsg?.msgShortId! } return { message_id: returnMsg.msgShortId! }
} catch (e: any) { } catch (e) {
throw '发送转发消息失败 ' + e.toString() throw '发送转发消息失败 ' + e
} }
} }
else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) { else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
const music = messages[0] as OB11MessageMusic const music = messages[0] as OB11MessageMusic
if (music) { if (music) {
const { musicSignUrl } = getConfigUtil().getConfig() const { musicSignUrl } = this.adapter.config
if (!musicSignUrl) { if (!musicSignUrl) {
throw '音乐签名地址未配置' throw '音乐签名地址未配置'
} }
@@ -160,6 +127,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
} }
const returnMsg = await sendMsg(this.ctx, peer, sendElements, deleteAfterSentFiles) const returnMsg = await sendMsg(this.ctx, peer, sendElements, deleteAfterSentFiles)
if (!returnMsg) {
throw new Error('消息发送失败')
}
return { message_id: returnMsg.msgShortId! } return { message_id: returnMsg.msgShortId! }
} }
@@ -172,26 +142,20 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> { private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
this.ctx.logger.info('克隆的目标消息', msg) this.ctx.logger.info('克隆的目标消息', msg)
let sendElements: SendMessageElement[] = [] const sendElements: SendMessageElement[] = []
for (const ele of msg.elements) { for (const ele of msg.elements) {
sendElements.push(ele as SendMessageElement) sendElements.push(ele as SendMessageElement)
// Object.keys(ele).forEach((eleKey) => {
// if (eleKey.endsWith("Element")) {
// }
} }
if (sendElements.length === 0) { if (sendElements.length === 0) {
this.ctx.logger.warn('需要clone的消息无法解析将会忽略掉', msg) this.ctx.logger.warn('需要clone的消息无法解析将会忽略掉', msg)
} }
this.ctx.logger.info('克隆消息', sendElements) this.ctx.logger.info('克隆消息', sendElements)
try { try {
const nodeMsg = await this.ctx.ntMsgApi.sendMsg( const peer = {
{
chatType: ChatType.friend, chatType: ChatType.friend,
peerUid: selfInfo.uid, peerUid: selfInfo.uid
}, }
sendElements, const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements)
true,
)
await this.ctx.sleep(400) await this.ctx.sleep(400)
return nodeMsg return nodeMsg
} catch (e) { } catch (e) {
@@ -209,7 +173,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
// 先判断一遍是不是id和自定义混用 // 先判断一遍是不是id和自定义混用
for (const messageNode of messageNodes) { for (const messageNode of messageNodes) {
// 一个node表示一个人的消息 // 一个node表示一个人的消息
let nodeId = messageNode.data.id const nodeId = messageNode.data.id
// 有nodeId表示一个子转发消息卡片 // 有nodeId表示一个子转发消息卡片
if (nodeId) { if (nodeId) {
const nodeMsg = await MessageUnique.getMsgIdAndPeerByShortId(+nodeId) || await MessageUnique.getPeerByMsgId(nodeId) const nodeMsg = await MessageUnique.getMsgIdAndPeerByShortId(+nodeId) || await MessageUnique.getPeerByMsgId(nodeId)
@@ -229,7 +193,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
destPeer destPeer
) )
this.ctx.logger.info('开始生成转发节点', sendElements) this.ctx.logger.info('开始生成转发节点', sendElements)
let sendElementsSplit: SendMessageElement[][] = [] const sendElementsSplit: SendMessageElement[][] = []
let splitIndex = 0 let splitIndex = 0
for (const ele of sendElements) { for (const ele of sendElements) {
if (!sendElementsSplit[splitIndex]) { if (!sendElementsSplit[splitIndex]) {
@@ -250,10 +214,13 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
// log("分割后的转发节点", sendElementsSplit) // log("分割后的转发节点", sendElementsSplit)
for (const eles of sendElementsSplit) { for (const eles of sendElementsSplit) {
const nodeMsg = await sendMsg(this.ctx, selfPeer, eles, [], true) const nodeMsg = await sendMsg(this.ctx, selfPeer, eles, [])
if (!nodeMsg) {
this.ctx.logger.warn('转发节点生成失败', eles)
continue
}
nodeMsgIds.push(nodeMsg.msgId) nodeMsgIds.push(nodeMsg.msgId)
await this.ctx.sleep(400) await this.ctx.sleep(400)
this.ctx.logger.info('转发节点生成成功', nodeMsg.msgId)
} }
deleteAfterSentFiles.map((f) => fs.unlink(f, () => { deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
})) }))

View File

@@ -7,7 +7,7 @@ interface Payload {
emoji_id: number | string emoji_id: number | string
} }
export class SetMsgEmojiLike extends BaseAction<Payload, any> { export class SetMsgEmojiLike extends BaseAction<Payload, unknown> {
actionName = ActionName.SetMsgEmojiLike actionName = ActionName.SetMsgEmojiLike
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {

View File

@@ -5,10 +5,10 @@ interface ReturnType {
yes: boolean yes: boolean
} }
export default class CanSendRecord extends BaseAction<any, ReturnType> { export default class CanSendRecord extends BaseAction<null, ReturnType> {
actionName = ActionName.CanSendRecord actionName = ActionName.CanSendRecord
protected async _handle(payload): Promise<ReturnType> { protected async _handle() {
return { return {
yes: true, yes: true,
} }

View File

@@ -6,7 +6,7 @@ import { selfInfo } from '@/common/globalVars'
class GetLoginInfo extends BaseAction<null, OB11User> { class GetLoginInfo extends BaseAction<null, OB11User> {
actionName = ActionName.GetLoginInfo actionName = ActionName.GetLoginInfo
protected async _handle(payload: null) { protected async _handle() {
let nickname = selfInfo.nick let nickname = selfInfo.nick
try { try {
nickname = await this.ctx.ntUserApi.getSelfNick(true) nickname = await this.ctx.ntUserApi.getSelfNick(true)

View File

@@ -3,10 +3,10 @@ import { OB11Status } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
export default class GetStatus extends BaseAction<any, OB11Status> { export default class GetStatus extends BaseAction<null, OB11Status> {
actionName = ActionName.GetStatus actionName = ActionName.GetStatus
protected async _handle(payload: any): Promise<OB11Status> { protected async _handle(): Promise<OB11Status> {
return { return {
online: selfInfo.online!, online: selfInfo.online!,
good: true, good: true,

View File

@@ -3,10 +3,10 @@ import { OB11Version } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { version } from '../../../version' import { version } from '../../../version'
export default class GetVersionInfo extends BaseAction<any, OB11Version> { export default class GetVersionInfo extends BaseAction<null, OB11Version> {
actionName = ActionName.GetVersionInfo actionName = ActionName.GetVersionInfo
protected async _handle(payload: any): Promise<OB11Version> { protected async _handle(): Promise<OB11Version> {
return { return {
app_name: 'LLOneBot', app_name: 'LLOneBot',
protocol_version: 'v11', protocol_version: 'v11',

View File

@@ -2,15 +2,11 @@ export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
export interface ValidCheckResult { export interface ValidCheckResult {
valid: true valid: true
[k: string | number]: any
} }
export interface InvalidCheckResult { export interface InvalidCheckResult {
valid: false valid: false
message: string message: string
[k: string | number]: any
} }
export enum ActionName { export enum ActionName {
@@ -23,6 +19,7 @@ export enum ActionName {
GetFile = 'get_file', GetFile = 'get_file',
GetFriendsWithCategory = 'get_friends_with_category', GetFriendsWithCategory = 'get_friends_with_category',
GetEvent = 'get_event', GetEvent = 'get_event',
SetOnlineStatus = 'set_online_status',
// onebot 11 // onebot 11
SendLike = 'send_like', SendLike = 'send_like',
GetLoginInfo = 'get_login_info', GetLoginInfo = 'get_login_info',
@@ -76,5 +73,8 @@ export enum ActionName {
GoCQHTTP_DelGroupFile = 'delete_group_file', GoCQHTTP_DelGroupFile = 'delete_group_file',
GoCQHTTP_GetGroupSystemMsg = 'get_group_system_msg', GoCQHTTP_GetGroupSystemMsg = 'get_group_system_msg',
GoCQHTTP_CreateGroupFileFolder = 'create_group_file_folder', GoCQHTTP_CreateGroupFileFolder = 'create_group_file_folder',
GoCQHTTP_DelGroupFolder = 'delete_group_folder' GoCQHTTP_DelGroupFolder = 'delete_group_folder',
GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
GoCQHTTP_SendGroupNotice = '_send_group_notice',
} }

View File

@@ -16,15 +16,15 @@ export class GetFriendList extends BaseAction<Payload, OB11User[]> {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
return OB11Entities.friendsV2(await this.ctx.ntFriendApi.getBuddyV2(refresh)) return OB11Entities.friendsV2(await this.ctx.ntFriendApi.getBuddyV2(refresh))
} }
return OB11Entities.friends(await this.ctx.ntFriendApi.getFriends(refresh)) return OB11Entities.friends(await this.ctx.ntFriendApi.getFriends())
} }
} }
// extend // extend
export class GetFriendWithCategory extends BaseAction<void, any> { export class GetFriendWithCategory extends BaseAction<void, OB11User[]> {
actionName = ActionName.GetFriendsWithCategory actionName = ActionName.GetFriendsWithCategory
protected async _handle(payload: void) { protected async _handle() {
if (getBuildVersion() >= 26702) { if (getBuildVersion() >= 26702) {
//全新逻辑 //全新逻辑
return OB11Entities.friendsV2(await this.ctx.ntFriendApi.getBuddyV2ExWithCate(true)) return OB11Entities.friendsV2(await this.ctx.ntFriendApi.getBuddyV2ExWithCate(true))

View File

@@ -35,7 +35,7 @@ declare module 'cordis' {
} }
class OneBot11Adapter extends Service { class OneBot11Adapter extends Service {
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi'] static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi', 'ntWebApi']
public messages: Map<string, RawMessage> = new Map() public messages: Map<string, RawMessage> = new Map()
public startTime = 0 public startTime = 0
@@ -50,7 +50,8 @@ class OneBot11Adapter extends Service {
this.ob11Http = new OB11Http(ctx, { this.ob11Http = new OB11Http(ctx, {
port: config.httpPort, port: config.httpPort,
token: config.token, token: config.token,
actionMap actionMap,
listenLocalhost: config.listenLocalhost
}) })
this.ob11HttpPost = new OB11HttpPost(ctx, { this.ob11HttpPost = new OB11HttpPost(ctx, {
hosts: config.httpHosts, hosts: config.httpHosts,
@@ -62,7 +63,8 @@ class OneBot11Adapter extends Service {
port: config.wsPort, port: config.wsPort,
heartInterval: config.heartInterval, heartInterval: config.heartInterval,
token: config.token, token: config.token,
actionMap actionMap,
listenLocalhost: config.listenLocalhost
}) })
this.ob11WebSocketReverseManager = new OB11WebSocketReverseManager(ctx, { this.ob11WebSocketReverseManager = new OB11WebSocketReverseManager(ctx, {
hosts: config.wsHosts, hosts: config.wsHosts,
@@ -110,10 +112,10 @@ class OneBot11Adapter extends Service {
for (const notify of notifies) { for (const notify of notifies) {
try { try {
const notifyTime = parseInt(notify.seq) / 1000 const notifyTime = parseInt(notify.seq) / 1000
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
if (notifyTime < this.startTime) { if (notifyTime < this.startTime) {
continue continue
} }
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
if ([GroupNotifyType.MEMBER_LEAVE_NOTIFY_ADMIN, GroupNotifyType.KICK_MEMBER_NOTIFY_ADMIN].includes(notify.type)) { if ([GroupNotifyType.MEMBER_LEAVE_NOTIFY_ADMIN, GroupNotifyType.KICK_MEMBER_NOTIFY_ADMIN].includes(notify.type)) {
this.ctx.logger.info('有成员退出通知', notify) this.ctx.logger.info('有成员退出通知', notify)
const member1Uin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid) const member1Uin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
@@ -170,14 +172,14 @@ class OneBot11Adapter extends Service {
) )
this.dispatch(event) this.dispatch(event)
} }
} catch (e: any) { } catch (e) {
this.ctx.logger.error('解析群通知失败', e.stack) this.ctx.logger.error('解析群通知失败', (e as Error).stack)
} }
} }
} }
private handleMsg(msgList: RawMessage[]) { private handleMsg(msgList: RawMessage[]) {
for (let message of msgList) { for (const message of msgList) {
// 过滤启动之前的消息 // 过滤启动之前的消息
if (parseInt(message.msgTime) < this.startTime / 1000) { if (parseInt(message.msgTime) < this.startTime / 1000) {
continue continue
@@ -292,7 +294,7 @@ class OneBot11Adapter extends Service {
} }
} }
// HTTP 端口变化,重启服务 // HTTP 端口变化,重启服务
if (config.ob11.httpPort !== old.httpPort) { if ((config.ob11.httpPort !== old.httpPort || config.ob11.listenLocalhost !== old.listenLocalhost) && config.ob11.enableHttp) {
await this.ob11Http.stop() await this.ob11Http.stop()
this.ob11Http.start() this.ob11Http.start()
} }
@@ -305,7 +307,7 @@ class OneBot11Adapter extends Service {
} }
} }
// 正向 WebSocket 端口变化,重启服务 // 正向 WebSocket 端口变化,重启服务
if (config.ob11.wsPort !== old.wsPort) { if ((config.ob11.wsPort !== old.wsPort || config.ob11.listenLocalhost !== old.listenLocalhost) && config.ob11.enableWs) {
await this.ob11WebSocket.stop() await this.ob11WebSocket.stop()
this.ob11WebSocket.start() this.ob11WebSocket.start()
llonebotError.wsServerError = '' llonebotError.wsServerError = ''
@@ -340,10 +342,13 @@ class OneBot11Adapter extends Service {
Object.assign(this.config, { Object.assign(this.config, {
...config.ob11, ...config.ob11,
heartInterval: config.heartInterval, heartInterval: config.heartInterval,
token: config.token!, token: config.token,
debug: config.debug!, debug: config.debug,
reportSelfMessage: config.reportSelfMessage!, reportSelfMessage: config.reportSelfMessage,
msgCacheExpire: config.msgCacheExpire!, msgCacheExpire: config.msgCacheExpire,
musicSignUrl: config.musicSignUrl,
enableLocalFile2Url: config.enableLocalFile2Url,
ffmpeg: config.ffmpeg
}) })
} }
@@ -351,12 +356,12 @@ class OneBot11Adapter extends Service {
for (const member of members) { for (const member of members) {
const existMember = await this.ctx.ntGroupApi.getGroupMember(groupCode, member.uin) const existMember = await this.ctx.ntGroupApi.getGroupMember(groupCode, member.uin)
if (existMember) { if (existMember) {
if (member.cardName != existMember.cardName) { if (member.cardName !== existMember.cardName) {
this.ctx.logger.info('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName) this.ctx.logger.info('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
this.dispatch( this.dispatch(
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName), new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
) )
} else if (member.role != existMember.role) { } else if (member.role !== existMember.role) {
this.ctx.logger.info('有管理员变动通知') this.ctx.logger.info('有管理员变动通知')
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent( const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
member.role == GroupMemberRole.admin ? 'set' : 'unset', member.role == GroupMemberRole.admin ? 'set' : 'unset',
@@ -394,7 +399,7 @@ class OneBot11Adapter extends Service {
this.handleRecallMsg(input) this.handleRecallMsg(input)
}) })
this.ctx.on('nt/message-sent', input => { this.ctx.on('nt/message-sent', input => {
this.handleRecallMsg(input) this.handleMsg(input)
}) })
this.ctx.on('nt/group-notify', input => { this.ctx.on('nt/group-notify', input => {
this.handleGroupNotify(input) this.handleGroupNotify(input)
@@ -415,6 +420,9 @@ namespace OneBot11Adapter {
debug: boolean debug: boolean
reportSelfMessage: boolean reportSelfMessage: boolean
msgCacheExpire: number msgCacheExpire: number
musicSignUrl?: string
enableLocalFile2Url: boolean
ffmpeg?: string
} }
} }

View File

@@ -10,8 +10,9 @@ import { OB11Message } from '../types'
import { OB11BaseEvent } from '../event/OB11BaseEvent' import { OB11BaseEvent } from '../event/OB11BaseEvent'
import { handleQuickOperation, QuickOperationEvent } from '../helper/quickOperation' import { handleQuickOperation, QuickOperationEvent } from '../helper/quickOperation'
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent' import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
import { Dict } from 'cosmokit'
type RegisterHandler = (res: Response, payload: any) => Promise<any> type RegisterHandler = (res: Response, payload: unknown) => Promise<unknown>
class OB11Http { class OB11Http {
private readonly expressAPP: Express private readonly expressAPP: Express
@@ -50,13 +51,14 @@ class OB11Http {
this.expressAPP.get('/', (req: Request, res: Response) => { this.expressAPP.get('/', (req: Request, res: Response) => {
res.send(`LLOneBot server 已启动`) res.send(`LLOneBot server 已启动`)
}) })
this.server = this.expressAPP.listen(this.config.port, '0.0.0.0', () => { const host = this.config.listenLocalhost ? '127.0.0.1' : '0.0.0.0'
this.ctx.logger.info(`HTTP server started 0.0.0.0:${this.config.port}`) this.server = this.expressAPP.listen(this.config.port, host, () => {
this.ctx.logger.info(`HTTP server started ${host}:${this.config.port}`)
}) })
llonebotError.httpServerError = '' llonebotError.httpServerError = ''
} catch (e: any) { } catch (e) {
this.ctx.logger.error('HTTP服务启动失败', e.toString()) this.ctx.logger.error('HTTP服务启动失败', e)
llonebotError.httpServerError = 'HTTP服务启动失败, ' + e.toString() llonebotError.httpServerError = 'HTTP服务启动失败, ' + e
} }
} }
@@ -82,7 +84,7 @@ class OB11Http {
} }
private authorize(req: Request, res: Response, next: () => void) { private authorize(req: Request, res: Response, next: () => void) {
let serverToken = this.config.token const serverToken = this.config.token
let clientToken = '' let clientToken = ''
const authHeader = req.get('authorization') const authHeader = req.get('authorization')
if (authHeader) { if (authHeader) {
@@ -97,7 +99,7 @@ class OB11Http {
this.ctx.logger.info('receive http url token', clientToken) this.ctx.logger.info('receive http url token', clientToken)
} }
if (serverToken && clientToken != serverToken) { if (serverToken && clientToken !== serverToken) {
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' })) return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }))
} }
next() next()
@@ -123,8 +125,8 @@ class OB11Http {
this.ctx.logger.info('收到 HTTP 请求', url, payload) this.ctx.logger.info('收到 HTTP 请求', url, payload)
try { try {
res.send(await handler(res, payload)) res.send(await handler(res, payload))
} catch (e: any) { } catch (e) {
res.send(OB11Response.error(e.stack.toString(), 200)) res.send(OB11Response.error((e as Error).stack!.toString(), 200))
} }
}) })
} }
@@ -134,7 +136,8 @@ namespace OB11Http {
export interface Config { export interface Config {
port: number port: number
token?: string token?: string
actionMap: Map<string, BaseAction<any, any>> actionMap: Map<string, BaseAction<unknown, unknown>>
listenLocalhost: boolean
} }
} }
@@ -159,7 +162,7 @@ class OB11HttpPost {
public async emitEvent(event: OB11BaseEvent | OB11Message) { public async emitEvent(event: OB11BaseEvent | OB11Message) {
const msgStr = JSON.stringify(event) const msgStr = JSON.stringify(event)
const headers = { const headers: Dict = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'x-self-id': selfInfo.uin, 'x-self-id': selfInfo.uin,
} }
@@ -187,7 +190,7 @@ class OB11HttpPost {
//log(`新消息事件HTTP上报没有返回快速操作不需要处理`) //log(`新消息事件HTTP上报没有返回快速操作不需要处理`)
} }
}, },
(err: any) => { (err) => {
this.ctx.logger.error(`HTTP 事件上报失败: ${host}`, err, event) this.ctx.logger.error(`HTTP 事件上报失败: ${host}`, err, event)
}, },
).catch(e => this.ctx.logger.error(e)) ).catch(e => this.ctx.logger.error(e))

View File

@@ -14,24 +14,29 @@ import { version } from '../../version'
class OB11WebSocket { class OB11WebSocket {
private wsServer?: WebSocketServer private wsServer?: WebSocketServer
private wsClients: WebSocket[] = [] private wsClients: { socket: WebSocket; emitEvent: boolean }[] = []
constructor(protected ctx: Context, public config: OB11WebSocket.Config) { constructor(protected ctx: Context, public config: OB11WebSocket.Config) {
} }
public start() { public start() {
if (this.wsServer) return if (this.wsServer) return
this.ctx.logger.info(`WebSocket server started 0.0.0.0:${this.config.port}`) const host = this.config.listenLocalhost ? '127.0.0.1' : '0.0.0.0'
this.ctx.logger.info(`WebSocket server started ${host}:${this.config.port}`)
try { try {
this.wsServer = new WebSocketServer({ port: this.config.port, maxPayload: 1024 * 1024 * 1024 }) this.wsServer = new WebSocketServer({
host,
port: this.config.port,
maxPayload: 1024 * 1024 * 1024
})
llonebotError.wsServerError = '' llonebotError.wsServerError = ''
} catch (e: any) { } catch (e) {
llonebotError.wsServerError = '正向 WebSocket 服务启动失败, ' + e.toString() llonebotError.wsServerError = '正向 WebSocket 服务启动失败, ' + e
return return
} }
this.wsServer?.on('connection', (socket, req) => { this.wsServer?.on('connection', (socket, req) => {
this.authorize(socket, req) this.authorize(socket, req)
this.connect(socket) this.connect(socket, req)
}) })
} }
@@ -53,8 +58,8 @@ class OB11WebSocket {
} }
public async emitEvent(event: OB11BaseEvent | OB11Message) { public async emitEvent(event: OB11BaseEvent | OB11Message) {
this.wsClients.forEach(socket => { this.wsClients.forEach(({ socket, emitEvent }) => {
if (socket.readyState === WebSocket.OPEN) { if (emitEvent && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(event)) socket.send(JSON.stringify(event))
this.ctx.logger.info('WebSocket 事件上报', socket.url ?? '', event.post_type) this.ctx.logger.info('WebSocket 事件上报', socket.url ?? '', event.post_type)
} }
@@ -65,13 +70,13 @@ class OB11WebSocket {
Object.assign(this.config, config) Object.assign(this.config, config)
} }
private reply(socket: WebSocket, data: OB11Return<any> | OB11BaseEvent | OB11Message) { private reply(socket: WebSocket, data: OB11Return<unknown> | OB11BaseEvent | OB11Message) {
if (socket.readyState !== WebSocket.OPEN) { if (socket.readyState !== WebSocket.OPEN) {
return return
} }
socket.send(JSON.stringify(data)) socket.send(JSON.stringify(data))
if (data['post_type']) { if ('post_type' in data) {
this.ctx.logger.info('WebSocket 事件上报', socket.url ?? '', data['post_type']) this.ctx.logger.info('WebSocket 事件上报', socket.url ?? '', data.post_type)
} }
} }
@@ -102,14 +107,14 @@ class OB11WebSocket {
} }
private async handleAction(socket: WebSocket, msg: string) { private async handleAction(socket: WebSocket, msg: string) {
let receive: { action: ActionName | null; params: any; echo?: any } = { action: null, params: {} } let receive: { action: ActionName | null; params: unknown; echo?: unknown } = { action: null, params: {} }
try { try {
receive = JSON.parse(msg.toString()) receive = JSON.parse(msg.toString())
this.ctx.logger.info('收到正向 Websocket 消息', receive) this.ctx.logger.info('收到正向 Websocket 消息', receive)
} catch (e) { } catch (e) {
return this.reply(socket, OB11Response.error('json解析失败请检查数据格式', 1400)) return this.reply(socket, OB11Response.error('json解析失败请检查数据格式', 1400))
} }
const action: BaseAction<any, any> = this.config.actionMap.get(receive.action!)! const action = this.config.actionMap.get(receive.action!)!
if (!action) { if (!action) {
return this.reply(socket, OB11Response.error('不支持的api ' + receive.action, 1404, receive.echo)) return this.reply(socket, OB11Response.error('不支持的api ' + receive.action, 1404, receive.echo))
} }
@@ -117,28 +122,25 @@ class OB11WebSocket {
const handleResult = await action.websocketHandle(receive.params, receive.echo) const handleResult = await action.websocketHandle(receive.params, receive.echo)
handleResult.echo = receive.echo handleResult.echo = receive.echo
this.reply(socket, handleResult) this.reply(socket, handleResult)
} catch (e: any) { } catch (e) {
this.reply(socket, OB11Response.error(`api处理出错:${e.stack}`, 1200, receive.echo)) this.reply(socket, OB11Response.error(`api处理出错:${(e as Error).stack}`, 1200, receive.echo))
} }
} }
private connect(socket: WebSocket) { private connect(socket: WebSocket, req: IncomingMessage) {
const url = req.url?.split('?').shift()
if (['/api', '/api/', '/', undefined].includes(url)) {
socket.on('message', msg => {
this.handleAction(socket, msg.toString())
})
}
if (['/event', '/event/', '/', undefined].includes(url)) {
try { try {
this.reply(socket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) this.reply(socket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT))
} catch (e) { } catch (e) {
this.ctx.logger.error('发送生命周期失败', e) this.ctx.logger.error('发送生命周期失败', e)
} }
socket.on('error', err => this.ctx.logger.error(err.message))
socket.on('message', msg => {
this.handleAction(socket, msg.toString())
})
socket.on('ping', () => {
socket.pong()
})
const disposeHeartBeat = this.ctx.setInterval(() => { const disposeHeartBeat = this.ctx.setInterval(() => {
this.reply(socket, new OB11HeartbeatEvent(selfInfo.online!, true, this.config.heartInterval)) this.reply(socket, new OB11HeartbeatEvent(selfInfo.online!, true, this.config.heartInterval))
}, this.config.heartInterval) }, this.config.heartInterval)
@@ -147,8 +149,18 @@ class OB11WebSocket {
disposeHeartBeat() disposeHeartBeat()
this.ctx.logger.info('有一个 Websocket 连接断开') this.ctx.logger.info('有一个 Websocket 连接断开')
}) })
}
this.wsClients.push(socket) socket.on('error', err => this.ctx.logger.error(err.message))
socket.on('ping', () => {
socket.pong()
})
this.wsClients.push({
socket,
emitEvent: ['/event', '/event/', '/', undefined].includes(url)
})
} }
} }
@@ -157,7 +169,8 @@ namespace OB11WebSocket {
port: number port: number
heartInterval: number heartInterval: number
token?: string token?: string
actionMap: Map<string, BaseAction<any, any>> actionMap: Map<string, BaseAction<unknown, unknown>>
listenLocalhost: boolean
} }
} }
@@ -187,30 +200,30 @@ class OB11WebSocketReverse {
} }
} }
private reply(socket: WebSocket, data: OB11Return<any> | OB11BaseEvent | OB11Message) { private reply(socket: WebSocket, data: OB11Return<unknown> | OB11BaseEvent | OB11Message) {
if (socket.readyState !== WebSocket.OPEN) { if (socket.readyState !== WebSocket.OPEN) {
return return
} }
socket.send(JSON.stringify(data)) socket.send(JSON.stringify(data))
if (data['post_type']) { if ('post_type' in data) {
this.ctx.logger.info('WebSocket 事件上报', socket.url ?? '', data['post_type']) this.ctx.logger.info('WebSocket 事件上报', socket.url ?? '', data.post_type)
} }
} }
private async handleAction(msg: string) { private async handleAction(msg: string) {
let receive: { action: ActionName | null; params: any; echo?: any } = { action: null, params: {} } let receive: { action: ActionName | null; params: unknown; echo?: unknown } = { action: null, params: {} }
try { try {
receive = JSON.parse(msg.toString()) receive = JSON.parse(msg.toString())
this.ctx.logger.info('收到反向Websocket消息', receive) this.ctx.logger.info('收到反向Websocket消息', receive)
} catch (e) { } catch (e) {
return this.reply(this.wsClient!, OB11Response.error('json解析失败请检查数据格式', 1400, receive.echo)) return this.reply(this.wsClient!, OB11Response.error('json解析失败请检查数据格式', 1400, receive.echo))
} }
const action: BaseAction<any, any> = this.config.actionMap.get(receive.action!)! const action = this.config.actionMap.get(receive.action!)!
if (!action) { if (!action) {
return this.reply(this.wsClient!, OB11Response.error('不支持的api ' + receive.action, 1404, receive.echo)) return this.reply(this.wsClient!, OB11Response.error('不支持的api ' + receive.action, 1404, receive.echo))
} }
try { try {
let handleResult = await action.websocketHandle(receive.params, receive.echo) const handleResult = await action.websocketHandle(receive.params, receive.echo)
this.reply(this.wsClient!, handleResult) this.reply(this.wsClient!, handleResult)
} catch (e) { } catch (e) {
this.reply(this.wsClient!, OB11Response.error(`api处理出错:${e}`, 1200, receive.echo)) this.reply(this.wsClient!, OB11Response.error(`api处理出错:${e}`, 1200, receive.echo))
@@ -274,7 +287,7 @@ namespace OB11WebSocketReverse {
url: string url: string
heartInterval: number heartInterval: number
token?: string token?: string
actionMap: Map<string, BaseAction<any, any>> actionMap: Map<string, BaseAction<unknown, unknown>>
} }
} }
@@ -299,8 +312,8 @@ class OB11WebSocketReverseManager {
for (const ws of this.list) { for (const ws of this.list) {
try { try {
ws.stop() ws.stop()
} catch (e: any) { } catch (e) {
this.ctx.logger.error('反向 WebSocket 关闭:', e.stack) this.ctx.logger.error('反向 WebSocket 关闭:', (e as Error).stack)
} }
} }
this.list.length = 0 this.list.length = 0
@@ -322,7 +335,7 @@ namespace OB11WebSocketReverseManager {
hosts: string[] hosts: string[]
heartInterval: number heartInterval: number
token?: string token?: string
actionMap: Map<string, BaseAction<any, any>> actionMap: Map<string, BaseAction<unknown, unknown>>
} }
} }

View File

@@ -10,7 +10,7 @@ function from(source: string) {
const capture = pattern.exec(source) const capture = pattern.exec(source)
if (!capture) return null if (!capture) return null
const [, type, attrs] = capture const [, type, attrs] = capture
const data: Record<string, any> = {} const data: Record<string, unknown> = {}
attrs && attrs &&
attrs attrs
.slice(1) .slice(1)
@@ -22,7 +22,7 @@ function from(source: string) {
return { type, data, capture } return { type, data, capture }
} }
function h(type: string, data: any) { function h(type: string, data: unknown) {
return { return {
type, type,
data, data,
@@ -30,7 +30,7 @@ function h(type: string, data: any) {
} }
export function decodeCQCode(source: string): OB11MessageData[] { export function decodeCQCode(source: string): OB11MessageData[] {
const elements: any[] = [] const elements: unknown[] = []
let result: ReturnType<typeof from> let result: ReturnType<typeof from>
while ((result = from(source))) { while ((result = from(source))) {
const { type, data, capture } = result const { type, data, capture } = result
@@ -41,10 +41,10 @@ export function decodeCQCode(source: string): OB11MessageData[] {
source = source.slice(capture.index + capture[0].length) source = source.slice(capture.index + capture[0].length)
} }
if (source) elements.push(h('text', { text: unescape(source) })) if (source) elements.push(h('text', { text: unescape(source) }))
return elements return elements as OB11MessageData[]
} }
export function encodeCQCode(data: OB11MessageData) { export function encodeCQCode(input: OB11MessageData) {
const CQCodeEscapeText = (text: string) => { const CQCodeEscapeText = (text: string) => {
return text.replace(/\&/g, '&amp;').replace(/\[/g, '&#91;').replace(/\]/g, '&#93;') return text.replace(/\&/g, '&amp;').replace(/\[/g, '&#91;').replace(/\]/g, '&#93;')
} }
@@ -53,21 +53,20 @@ export function encodeCQCode(data: OB11MessageData) {
return text.replace(/\&/g, '&amp;').replace(/\[/g, '&#91;').replace(/\]/g, '&#93;').replace(/,/g, '&#44;') return text.replace(/\&/g, '&amp;').replace(/\[/g, '&#91;').replace(/\]/g, '&#93;').replace(/,/g, '&#44;')
} }
if (data.type === 'text') { if (input.type === 'text') {
return CQCodeEscapeText(data.data.text) return CQCodeEscapeText(input.data.text)
} }
let result = '[CQ:' + data.type let result = '[CQ:' + input.type
for (const name in data.data) { for (const [key, value] of Object.entries(input.data)) {
const value = data.data[name]
if (value === undefined) { if (value === undefined) {
continue continue
} }
try { try {
const text = value.toString() const text = value.toString()
result += `,${name}=${CQCodeEscape(text)}` result += `,${key}=${CQCodeEscape(text)}`
} catch (error) { } catch (error) {
// If it can't be converted, skip this name-value pair // If it can't be converted, skip this key-value pair
} }
} }
result += ']' result += ']'

View File

@@ -1,4 +1,4 @@
import fastXmlParser from 'fast-xml-parser' import { XMLParser } from 'fast-xml-parser'
import { import {
OB11Group, OB11Group,
OB11GroupMember, OB11GroupMember,
@@ -32,29 +32,28 @@ import { OB11GroupBanEvent } from './event/notice/OB11GroupBanEvent'
import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent' import { OB11GroupUploadNoticeEvent } from './event/notice/OB11GroupUploadNoticeEvent'
import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent' import { OB11GroupNoticeEvent } from './event/notice/OB11GroupNoticeEvent'
import { calcQQLevel } from '../common/utils/misc' import { calcQQLevel } from '../common/utils/misc'
import { getConfigUtil } from '../common/config'
import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent' import { OB11GroupTitleEvent } from './event/notice/OB11GroupTitleEvent'
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent' import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'
import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent' import { OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent'
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent' import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
import { mFaceCache } from '../ntqqapi/entities'
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent' import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent' import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent' import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent' import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent' import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent'
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent' import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'
import { omit, isNullable } from 'cosmokit' import { omit, isNullable, pick, Dict } from 'cosmokit'
import { Context } from 'cordis' import { Context } from 'cordis'
import { selfInfo } from '@/common/globalVars' import { selfInfo } from '@/common/globalVars'
import { pathToFileURL } from 'node:url'
import OneBot11Adapter from './adapter'
export namespace OB11Entities { export namespace OB11Entities {
export async function message(ctx: Context, msg: RawMessage): Promise<OB11Message> { export async function message(ctx: Context, msg: RawMessage): Promise<OB11Message> {
let config = getConfigUtil().getConfig()
const { const {
debug, debug,
ob11: { messagePostFormat }, messagePostFormat,
} = config } = ctx.config as OneBot11Adapter.Config
const selfUin = selfInfo.uin const selfUin = selfInfo.uin
const resMsg: OB11Message = { const resMsg: OB11Message = {
self_id: parseInt(selfUin), self_id: parseInt(selfUin),
@@ -67,7 +66,7 @@ export namespace OB11Entities {
sender: { sender: {
user_id: parseInt(msg.senderUin!), user_id: parseInt(msg.senderUin!),
nickname: msg.sendNickName, nickname: msg.sendNickName,
card: msg.sendMemberName || '', card: msg.sendMemberName ?? '',
}, },
raw_message: '', raw_message: '',
font: 14, font: 14,
@@ -79,36 +78,34 @@ export namespace OB11Entities {
if (debug) { if (debug) {
resMsg.raw = msg resMsg.raw = msg
} }
if (msg.chatType == ChatType.group) { if (msg.chatType === ChatType.group) {
resMsg.sub_type = 'normal' resMsg.sub_type = 'normal'
resMsg.group_id = parseInt(msg.peerUin) resMsg.group_id = parseInt(msg.peerUin)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUin!) const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUin!)
if (member) { if (member) {
resMsg.sender.role = groupMemberRole(member.role) resMsg.sender.role = groupMemberRole(member.role)
resMsg.sender.nickname = member.nick resMsg.sender.nickname = member.nick
resMsg.sender.title = member.memberSpecialTitle ?? ''
} }
} }
else if (msg.chatType == ChatType.friend) { else if (msg.chatType === ChatType.friend) {
resMsg.sub_type = 'friend' resMsg.sub_type = 'friend'
resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick
} }
else if (msg.chatType as unknown as ChatType2 == ChatType2.KCHATTYPETEMPC2CFROMGROUP) { else if (msg.chatType as unknown as ChatType2 === ChatType2.KCHATTYPETEMPC2CFROMGROUP) {
resMsg.sub_type = 'group' resMsg.sub_type = 'group'
resMsg.temp_source = 0 //群聊
resMsg.sender.nickname = (await ctx.ntUserApi.getUserDetailInfo(msg.senderUid)).nick
const ret = await ctx.ntMsgApi.getTempChatInfo(ChatType2.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid) const ret = await ctx.ntMsgApi.getTempChatInfo(ChatType2.KCHATTYPETEMPC2CFROMGROUP, msg.senderUid)
if (ret?.result === 0) { if (ret?.result === 0) {
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode) resMsg.sender.group_id = Number(ret.tmpChatInfo?.groupCode)
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick
} else { } else {
resMsg.group_id = 284840486 //兜底数据 resMsg.sender.group_id = 284840486 //兜底数据
resMsg.sender.nickname = '临时会话'
} }
} }
for (let element of msg.elements) { for (const element of msg.elements) {
let message_data: OB11MessageData = { let messageSegment: OB11MessageData | undefined
data: {} as any,
type: 'unknown' as any,
}
if (element.textElement && element.textElement?.atType !== AtType.notAt) { if (element.textElement && element.textElement?.atType !== AtType.notAt) {
let qq: string let qq: string
let name: string | undefined let name: string | undefined
@@ -129,7 +126,7 @@ export namespace OB11Entities {
name = content.replace('@', '') name = content.replace('@', '')
} }
} }
message_data = { messageSegment = {
type: OB11MessageDataType.at, type: OB11MessageDataType.at,
data: { data: {
qq: qq!, qq: qq!,
@@ -138,58 +135,61 @@ export namespace OB11Entities {
} }
} }
else if (element.textElement) { else if (element.textElement) {
message_data['type'] = OB11MessageDataType.text const text = element.textElement.content
let text = element.textElement.content
if (!text.trim()) { if (!text.trim()) {
continue continue
} }
message_data['data']['text'] = text messageSegment = {
type: OB11MessageDataType.text,
data: {
text
}
}
} }
else if (element.replyElement) { else if (element.replyElement) {
message_data['type'] = OB11MessageDataType.reply const { replyElement } = element
try {
const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords)
if (!records) throw new Error('找不到回复消息')
let replyMsg = (await ctx.ntMsgApi.getMsgsBySeqAndCount({
peerUid: msg.peerUid,
guildId: '',
chatType: msg.chatType,
}, element.replyElement.replayMsgSeq, 1, true, true))?.msgList[0]
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
const peer = { const peer = {
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
guildId: '', guildId: ''
} }
replyMsg = (await ctx.ntMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq))?.msgList[0] try {
const { replayMsgSeq, replyMsgTime, senderUidStr } = replyElement
const records = msg.records.find(msgRecord => msgRecord.msgId === replyElement.sourceMsgIdInRecords)
if (!records || !replyMsgTime || !senderUidStr) {
throw new Error('找不到回复消息')
} }
const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUidStr])
const replyMsg = msgList.find(msg => msg.msgRandom === records.msgRandom)
// 284840486: 合并消息内侧 消息具体定位不到 // 284840486: 合并消息内侧 消息具体定位不到
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') { if (!replyMsg && msg.peerUin !== '284840486') {
throw new Error('回复消息消息验证失败') ctx.logger.info('queryMsgs', msgList.map(e => pick(e, ['msgSeq', 'msgRandom'])))
throw new Error('回复消息验证失败')
} }
message_data['data']['id'] = replyMsg && MessageUnique.createMsg({ messageSegment = {
peerUid: msg.peerUid, type: OB11MessageDataType.reply,
guildId: '', data: {
chatType: msg.chatType, id: MessageUnique.createMsg(peer, replyMsg ? replyMsg.msgId : records.msgId).toString()
}, replyMsg.msgId)?.toString() }
} catch (e: any) { }
ctx.logger.error('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq) } catch (e) {
ctx.logger.error('获取不到引用的消息', replyElement, (e as Error).stack)
continue continue
} }
} }
else if (element.picElement) { else if (element.picElement) {
message_data['type'] = OB11MessageDataType.image
const { picElement } = element const { picElement } = element
/*let fileName = picElement.fileName const fileSize = picElement.fileSize ?? '0'
const isGif = picElement.picType === PicType.gif messageSegment = {
if (isGif && !fileName.endsWith('.gif')) { type: OB11MessageDataType.image,
fileName += '.gif' data: {
}*/ file: picElement.fileName,
message_data['data']['file'] = picElement.fileName subType: picElement.picSubType,
message_data['data']['subType'] = picElement.picSubType url: await ctx.ntFileApi.getImageUrl(picElement),
//message_data['data']['file_id'] = picElement.fileUuid file_size: fileSize,
message_data['data']['url'] = await ctx.ntFileApi.getImageUrl(picElement) }
message_data['data']['file_size'] = picElement.fileSize }
MessageUnique.addFileCache({ MessageUnique.addFileCache({
peerUid: msg.peerUid, peerUid: msg.peerUid,
msgId: msg.msgId, msgId: msg.msgId,
@@ -198,21 +198,26 @@ export namespace OB11Entities {
elementId: element.elementId, elementId: element.elementId,
elementType: element.elementType, elementType: element.elementType,
fileName: picElement.fileName, fileName: picElement.fileName,
fileSize: String(picElement.fileSize || '0'), fileUuid: picElement.fileUuid,
fileUuid: picElement.fileUuid fileSize,
}) })
} }
else if (element.videoElement) { else if (element.videoElement) {
message_data['type'] = OB11MessageDataType.video
const { videoElement } = element const { videoElement } = element
message_data['data']['file'] = videoElement.fileName const videoUrl = await ctx.ntFileApi.getVideoUrl({
message_data['data']['path'] = videoElement.filePath
//message_data['data']['file_id'] = videoElement.fileUuid
message_data['data']['file_size'] = videoElement.fileSize
message_data['data']['url'] = await ctx.ntFileApi.getVideoUrl({
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
}, msg.msgId, element.elementId) }, msg.msgId, element.elementId)
const fileSize = videoElement.fileSize ?? '0'
messageSegment = {
type: OB11MessageDataType.video,
data: {
file: videoElement.fileName,
url: videoUrl || pathToFileURL(videoElement.filePath).href,
path: videoElement.filePath,
file_size: fileSize,
}
}
MessageUnique.addFileCache({ MessageUnique.addFileCache({
peerUid: msg.peerUid, peerUid: msg.peerUid,
msgId: msg.msgId, msgId: msg.msgId,
@@ -221,17 +226,23 @@ export namespace OB11Entities {
elementId: element.elementId, elementId: element.elementId,
elementType: element.elementType, elementType: element.elementType,
fileName: videoElement.fileName, fileName: videoElement.fileName,
fileSize: String(videoElement.fileSize || '0'), fileUuid: videoElement.fileUuid!,
fileUuid: videoElement.fileUuid! fileSize,
}) })
} }
else if (element.fileElement) { else if (element.fileElement) {
message_data['type'] = OB11MessageDataType.file
const { fileElement } = element const { fileElement } = element
message_data['data']['file'] = fileElement.fileName const fileSize = fileElement.fileSize ?? '0'
message_data['data']['path'] = fileElement.filePath messageSegment = {
message_data['data']['file_id'] = fileElement.fileUuid type: OB11MessageDataType.file,
message_data['data']['file_size'] = fileElement.fileSize data: {
file: fileElement.fileName,
url: pathToFileURL(fileElement.filePath).href,
file_id: fileElement.fileUuid,
path: fileElement.filePath,
file_size: fileSize,
}
}
MessageUnique.addFileCache({ MessageUnique.addFileCache({
peerUid: msg.peerUid, peerUid: msg.peerUid,
msgId: msg.msgId, msgId: msg.msgId,
@@ -240,17 +251,22 @@ export namespace OB11Entities {
elementId: element.elementId, elementId: element.elementId,
elementType: element.elementType, elementType: element.elementType,
fileName: fileElement.fileName, fileName: fileElement.fileName,
fileSize: String(fileElement.fileSize || '0'), fileUuid: fileElement.fileUuid!,
fileUuid: fileElement.fileUuid! fileSize,
}) })
} }
else if (element.pttElement) { else if (element.pttElement) {
message_data['type'] = OB11MessageDataType.voice
const { pttElement } = element const { pttElement } = element
message_data['data']['file'] = pttElement.fileName const fileSize = pttElement.fileSize ?? '0'
message_data['data']['path'] = pttElement.filePath messageSegment = {
//message_data['data']['file_id'] = pttElement.fileUuid type: OB11MessageDataType.voice,
message_data['data']['file_size'] = pttElement.fileSize data: {
file: pttElement.fileName,
url: pathToFileURL(pttElement.filePath).href,
path: pttElement.filePath,
file_size: fileSize,
}
}
MessageUnique.addFileCache({ MessageUnique.addFileCache({
peerUid: msg.peerUid, peerUid: msg.peerUid,
msgId: msg.msgId, msgId: msg.msgId,
@@ -259,59 +275,91 @@ export namespace OB11Entities {
elementId: element.elementId, elementId: element.elementId,
elementType: element.elementType, elementType: element.elementType,
fileName: pttElement.fileName, fileName: pttElement.fileName,
fileSize: String(pttElement.fileSize || '0'), fileUuid: pttElement.fileUuid,
fileUuid: pttElement.fileUuid fileSize,
}) })
} }
else if (element.arkElement) { else if (element.arkElement) {
message_data['type'] = OB11MessageDataType.json const { arkElement } = element
message_data['data']['data'] = element.arkElement.bytesData messageSegment = {
type: OB11MessageDataType.json,
data: {
data: arkElement.bytesData
}
}
} }
else if (element.faceElement) { else if (element.faceElement) {
const faceId = element.faceElement.faceIndex const { faceElement } = element
const faceId = faceElement.faceIndex
if (faceId === FaceIndex.dice) { if (faceId === FaceIndex.dice) {
message_data['type'] = OB11MessageDataType.dice messageSegment = {
message_data['data']['result'] = element.faceElement.resultId type: OB11MessageDataType.dice,
data: {
result: faceElement.resultId!
}
}
} }
else if (faceId === FaceIndex.RPS) { else if (faceId === FaceIndex.RPS) {
message_data['type'] = OB11MessageDataType.RPS messageSegment = {
message_data['data']['result'] = element.faceElement.resultId type: OB11MessageDataType.RPS,
data: {
result: faceElement.resultId!
}
}
} }
else { else {
message_data['type'] = OB11MessageDataType.face messageSegment = {
message_data['data']['id'] = element.faceElement.faceIndex.toString() type: OB11MessageDataType.face,
data: {
id: faceId.toString()
}
}
} }
} }
else if (element.marketFaceElement) { else if (element.marketFaceElement) {
message_data['type'] = OB11MessageDataType.mface const { marketFaceElement } = element
message_data['data']['summary'] = element.marketFaceElement.faceName const { emojiId } = marketFaceElement
const md5 = element.marketFaceElement.emojiId
// 取md5的前两位 // 取md5的前两位
const dir = md5.substring(0, 2) const dir = emojiId.substring(0, 2)
// 获取组装url // 获取组装url
// const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000` // const url = `https://p.qpic.cn/CDN_STATIC/0/data/imgcache/htdocs/club/item/parcel/item/${dir}/${md5}/300x300.gif?max_age=31536000`
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${md5}/raw300.gif` const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`
message_data['data']['url'] = url messageSegment = {
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId type: OB11MessageDataType.mface,
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId) data: {
message_data['data']['key'] = element.marketFaceElement.key summary: marketFaceElement.faceName!,
mFaceCache.set(md5, element.marketFaceElement.faceName!) url,
emoji_id: emojiId,
emoji_package_id: marketFaceElement.emojiPackageId,
key: marketFaceElement.key
}
}
//mFaceCache.set(emojiId, element.marketFaceElement.faceName!)
} }
else if (element.markdownElement) { else if (element.markdownElement) {
message_data['type'] = OB11MessageDataType.markdown const { markdownElement } = element
message_data['data']['data'] = element.markdownElement.content messageSegment = {
type: OB11MessageDataType.markdown,
data: {
data: markdownElement.content
}
}
} }
else if (element.multiForwardMsgElement) { else if (element.multiForwardMsgElement) {
message_data['type'] = OB11MessageDataType.forward messageSegment = {
message_data['data']['id'] = msg.msgId type: OB11MessageDataType.forward,
data: {
id: msg.msgId
} }
if ((message_data.type as string) !== 'unknown' && message_data.data) { }
const cqCode = encodeCQCode(message_data) }
if (messageSegment) {
const cqCode = encodeCQCode(messageSegment)
if (messagePostFormat === 'string') { if (messagePostFormat === 'string') {
(resMsg.message as string) += cqCode (resMsg.message as string) += cqCode
} else {
(resMsg.message as OB11MessageData[]).push(messageSegment)
} }
else (resMsg.message as OB11MessageData[]).push(message_data)
resMsg.raw_message += cqCode resMsg.raw_message += cqCode
} }
} }
@@ -330,7 +378,7 @@ export namespace OB11Entities {
if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) { if (element.grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型 //判断业务类型
//Poke事件 //Poke事件
const pokedetail: any[] = json.items const pokedetail: Dict[] = json.items
//筛选item带有uid的元素 //筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid) const poke_uid = pokedetail.filter(item => item.uid)
if (poke_uid.length == 2) { if (poke_uid.length == 2) {
@@ -357,7 +405,7 @@ export namespace OB11Entities {
return return
} }
if (msg.senderUin) { if (msg.senderUin) {
let member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, msg.senderUin) const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, msg.senderUin)
if (member && member.cardName !== msg.sendMemberName) { if (member && member.cardName !== msg.sendMemberName) {
const event = new OB11GroupCardEvent( const event = new OB11GroupCardEvent(
parseInt(msg.peerUid), parseInt(msg.peerUid),
@@ -369,12 +417,10 @@ export namespace OB11Entities {
return event return event
} }
} }
// log("group msg", msg) for (const element of msg.elements) {
for (let element of msg.elements) {
const grayTipElement = element.grayTipElement const grayTipElement = element.grayTipElement
const groupElement = grayTipElement?.groupElement const groupElement = grayTipElement?.groupElement
if (groupElement) { if (groupElement) {
// log("收到群提示消息", groupElement)
if (groupElement.type === TipGroupElementType.memberIncrease) { if (groupElement.type === TipGroupElementType.memberIncrease) {
ctx.logger.info('收到群成员增加消息', groupElement) ctx.logger.info('收到群成员增加消息', groupElement)
await ctx.sleep(1000) await ctx.sleep(1000)
@@ -383,14 +429,10 @@ export namespace OB11Entities {
if (!memberUin) { if (!memberUin) {
memberUin = (await ctx.ntUserApi.getUserDetailInfo(groupElement.memberUid)).uin memberUin = (await ctx.ntUserApi.getUserDetailInfo(groupElement.memberUid)).uin
} }
// log("获取新群成员QQ", memberUin)
const adminMember = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.adminUid) const adminMember = await ctx.ntGroupApi.getGroupMember(msg.peerUid, groupElement.adminUid)
// log("获取同意新成员入群的管理员", adminMember)
if (memberUin) { if (memberUin) {
const operatorUin = adminMember?.uin || memberUin const operatorUin = adminMember?.uin || memberUin
let event = new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin)) return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin))
// log("构造群增加事件", event)
return event
} }
} }
else if (groupElement.type === TipGroupElementType.ban) { else if (groupElement.type === TipGroupElementType.ban) {
@@ -398,8 +440,8 @@ export namespace OB11Entities {
const memberUid = groupElement.shutUp?.member.uid const memberUid = groupElement.shutUp?.member.uid
const adminUid = groupElement.shutUp?.admin.uid const adminUid = groupElement.shutUp?.admin.uid
let memberUin: string = '' let memberUin: string = ''
let duration = parseInt(groupElement.shutUp?.duration!) let duration = Number(groupElement.shutUp?.duration)
let sub_type: 'ban' | 'lift_ban' = duration > 0 ? 'ban' : 'lift_ban' const subType = duration > 0 ? 'ban' : 'lift_ban'
if (memberUid) { if (memberUid) {
memberUin = memberUin =
(await ctx.ntGroupApi.getGroupMember(msg.peerUid, memberUid))?.uin || (await ctx.ntGroupApi.getGroupMember(msg.peerUid, memberUid))?.uin ||
@@ -419,7 +461,7 @@ export namespace OB11Entities {
parseInt(memberUin), parseInt(memberUin),
parseInt(adminUin), parseInt(adminUin),
duration, duration,
sub_type, subType,
) )
} }
} }
@@ -467,38 +509,36 @@ export namespace OB11Entities {
// <url jp= \"\" msgseq=\"74711\" col=\"3\" txt=\"消息:\"/> // <url jp= \"\" msgseq=\"74711\" col=\"3\" txt=\"消息:\"/>
// <face type=\"1\" id=\"76\"> </face> // <face type=\"1\" id=\"76\"> </face>
// </gtip>", // </gtip>",
const emojiLikeData = new fastXmlParser.XMLParser({ const emojiLikeData = new XMLParser({
ignoreAttributes: false, ignoreAttributes: false,
attributeNamePrefix: '', attributeNamePrefix: '',
}).parse(xmlElement.content) }).parse(xmlElement.content)
ctx.logger.info('收到表情回应我的消息', emojiLikeData) ctx.logger.info('收到表情回应我的消息', emojiLikeData)
try { try {
const senderUin = emojiLikeData.gtip.qq.jp const senderUin: string = emojiLikeData.gtip.qq.jp
const msgSeq = emojiLikeData.gtip.url.msgseq const msgSeq: string = emojiLikeData.gtip.url.msgseq
const emojiId = emojiLikeData.gtip.face.id const emojiId: string = emojiLikeData.gtip.face.id
const replyMsgList = (await ctx.ntMsgApi.getMsgsBySeqAndCount({ const peer = {
chatType: ChatType.group, chatType: ChatType.group,
guildId: '', guildId: '',
peerUid: msg.peerUid, peerUid: msg.peerUid,
}, msgSeq, 1, true, true))?.msgList }
const replyMsgList = (await ctx.ntMsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList
if (!replyMsgList?.length) { if (!replyMsgList?.length) {
return return
} }
const likes = [
{
emoji_id: emojiId,
count: 1,
},
]
const shortId = MessageUnique.getShortIdByMsgId(replyMsgList[0].msgId) const shortId = MessageUnique.getShortIdByMsgId(replyMsgList[0].msgId)
return new OB11GroupMsgEmojiLikeEvent( return new OB11GroupMsgEmojiLikeEvent(
parseInt(msg.peerUid), parseInt(msg.peerUid),
parseInt(senderUin), parseInt(senderUin),
shortId!, shortId!,
likes [{
emoji_id: emojiId,
count: 1,
}]
) )
} catch (e: any) { } catch (e) {
ctx.logger.error('解析表情回应消息失败', e.stack) ctx.logger.error('解析表情回应消息失败', (e as Error).stack)
} }
} }
@@ -552,14 +592,14 @@ export namespace OB11Entities {
if (grayTipElement.jsonGrayTipElement.busiId == 1061) { if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
//判断业务类型 //判断业务类型
//Poke事件 //Poke事件
const pokedetail: any[] = json.items const pokedetail: Dict[] = json.items
//筛选item带有uid的元素 //筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid) const poke_uid = pokedetail.filter(item => item.uid)
if (poke_uid.length == 2) { if (poke_uid.length == 2) {
return new OB11GroupPokeEvent( return new OB11GroupPokeEvent(
parseInt(msg.peerUid), parseInt(msg.peerUid),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid)), parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid) ?? 0),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid)), parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid) ?? 0),
pokedetail pokedetail
) )
} }
@@ -723,6 +763,8 @@ export namespace OB11Entities {
return { return {
group_id: parseInt(group.groupCode), group_id: parseInt(group.groupCode),
group_name: group.groupName, group_name: group.groupName,
group_memo: group.remarkName,
group_create_time: +group.createTime,
member_count: group.memberCount, member_count: group.memberCount,
max_member_count: group.maxMember, max_member_count: group.maxMember,
} }

View File

@@ -5,14 +5,13 @@ abstract class OB11PokeEvent extends OB11BaseNoticeEvent {
sub_type = 'poke' sub_type = 'poke'
target_id = 0 target_id = 0
abstract user_id: number abstract user_id: number
raw_message: any
} }
export class OB11FriendPokeEvent extends OB11PokeEvent { export class OB11FriendPokeEvent extends OB11PokeEvent {
user_id: number user_id: number
raw_info: any raw_info: unknown
constructor(user_id: number, target_id: number, raw_message: any) { constructor(user_id: number, target_id: number, raw_message: unknown) {
super() super()
this.target_id = target_id this.target_id = target_id
this.user_id = user_id this.user_id = user_id
@@ -24,9 +23,9 @@ export class OB11FriendPokeEvent extends OB11PokeEvent {
export class OB11GroupPokeEvent extends OB11PokeEvent { export class OB11GroupPokeEvent extends OB11PokeEvent {
user_id: number user_id: number
group_id: number group_id: number
raw_info: any raw_info: unknown
constructor(group_id: number, user_id: number = 0, target_id: number = 0, raw_message: any) { constructor(group_id: number, user_id: number, target_id: number, raw_message: unknown) {
super() super()
this.group_id = group_id this.group_id = group_id
this.target_id = target_id this.target_id = target_id

View File

@@ -27,9 +27,9 @@ export async function createSendElements(
peer: Peer, peer: Peer,
ignoreTypes: OB11MessageDataType[] = [], ignoreTypes: OB11MessageDataType[] = [],
) { ) {
let sendElements: SendMessageElement[] = [] const sendElements: SendMessageElement[] = []
let deleteAfterSentFiles: string[] = [] const deleteAfterSentFiles: string[] = []
for (let sendMsg of messageData) { for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) { if (ignoreTypes.includes(sendMsg.type)) {
continue continue
} }
@@ -54,7 +54,7 @@ export async function createSendElements(
let isAdmin: boolean = true let isAdmin: boolean = true
if (groupCode) { if (groupCode) {
try { try {
remainAtAllCount = (await ctx.ntGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo remainAtAllCount = (await ctx.ntGroupApi.getGroupRemainAtTimes(groupCode)).atInfo
.RemainAtAllCountForUin .RemainAtAllCountForUin
ctx.logger.info(`${groupCode}剩余at全体次数`, remainAtAllCount) ctx.logger.info(`${groupCode}剩余at全体次数`, remainAtAllCount)
const self = await ctx.ntGroupApi.getGroupMember(groupCode, selfInfo.uin) const self = await ctx.ntGroupApi.getGroupMember(groupCode, selfInfo.uin)
@@ -164,10 +164,6 @@ export async function createSendElements(
sendElements.push(SendElementEntities.ark(sendMsg.data.data)) sendElements.push(SendElementEntities.ark(sendMsg.data.data))
} }
break break
case OB11MessageDataType.poke: {
let qq = sendMsg.data?.qq || sendMsg.data?.id
}
break
case OB11MessageDataType.dice: { case OB11MessageDataType.dice: {
const resultId = sendMsg.data?.result const resultId = sendMsg.data?.result
sendElements.push(SendElementEntities.dice(resultId)) sendElements.push(SendElementEntities.dice(resultId))
@@ -240,8 +236,7 @@ export async function sendMsg(
ctx: Context, ctx: Context,
peer: Peer, peer: Peer,
sendElements: SendMessageElement[], sendElements: SendMessageElement[],
deleteAfterSentFiles: string[], deleteAfterSentFiles: string[]
waitComplete = true,
) { ) {
if (!sendElements.length) { if (!sendElements.length) {
throw '消息体无法解析,请检查是否发送了不支持的消息类型' throw '消息体无法解析,请检查是否发送了不支持的消息类型'
@@ -269,9 +264,41 @@ export async function sendMsg(
//log('发送消息总大小', totalSize, 'bytes') //log('发送消息总大小', totalSize, 'bytes')
const timeout = 10000 + (totalSize / 1024 / 256 * 1000) // 10s Basic Timeout + PredictTime( For File 512kb/s ) const timeout = 10000 + (totalSize / 1024 / 256 * 1000) // 10s Basic Timeout + PredictTime( For File 512kb/s )
//log('设置消息超时时间', timeout) //log('设置消息超时时间', timeout)
const returnMsg = await ctx.ntMsgApi.sendMsg(peer, sendElements, waitComplete, timeout) const returnMsg = await ctx.ntMsgApi.sendMsg(peer, sendElements, timeout)
if (returnMsg) {
returnMsg.msgShortId = MessageUnique.createMsg(peer, returnMsg.msgId) returnMsg.msgShortId = MessageUnique.createMsg(peer, returnMsg.msgId)
ctx.logger.info('消息发送', returnMsg.msgShortId) ctx.logger.info('消息发送', returnMsg.msgShortId)
deleteAfterSentFiles.map(path => fsPromise.unlink(path)) deleteAfterSentFiles.map(path => fsPromise.unlink(path))
return returnMsg return returnMsg
} }
}
export interface CreatePeerPayload {
group_id?: string | number
user_id?: string | number
}
export enum CreatePeerMode {
Normal = 0,
Private = 1,
Group = 2
}
export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode: CreatePeerMode): Promise<Peer> {
if ((mode === CreatePeerMode.Group || mode === CreatePeerMode.Normal) && payload.group_id) {
return {
chatType: ChatType.group,
peerUid: payload.group_id.toString(),
}
}
if ((mode === CreatePeerMode.Private || mode === CreatePeerMode.Normal) && payload.user_id) {
const uid = await ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!uid) throw new Error('无法获取用户信息')
const isBuddy = await ctx.ntFriendApi.isBuddy(uid)
return {
chatType: isBuddy ? ChatType.friend : ChatType.temp,
peerUid: uid,
}
}
throw new Error('请指定 group_id 或 user_id')
}

View File

@@ -33,8 +33,8 @@ export async function getHttpEvent(userKey: string, timeout = 0) {
// 清除过时的user5分钟没访问过的user将被删除 // 清除过时的user5分钟没访问过的user将被删除
const now = Date.now() const now = Date.now()
for (let key in httpUser) { for (const key in httpUser) {
let user = httpUser[key] const user = httpUser[key]
if (now - user.lastAccessTime > 1000 * 60 * 5) { if (now - user.lastAccessTime > 1000 * 60 * 5) {
delete httpUser[key] delete httpUser[key]
} }
@@ -55,7 +55,7 @@ export async function getHttpEvent(userKey: string, timeout = 0) {
} }
// 取数据 // 取数据
for (let i = 0; i < eventList.length; i++) { for (let i = 0; i < eventList.length; i++) {
let evt = eventList[i] const evt = eventList[i]
if (evt.seq > user.userSeq) { if (evt.seq > user.userSeq) {
toRetEvent.push(evt.event) toRetEvent.push(evt.event)
} }

View File

@@ -1,12 +1,12 @@
import { OB11Message, OB11MessageAt, OB11MessageData, OB11MessageDataType } from '../types' import { OB11Message, OB11MessageAt, OB11MessageData, OB11MessageDataType } from '../types'
import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest' import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest'
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest' import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest'
import { ChatType, GroupRequestOperateTypes, Peer } from '@/ntqqapi/types' import { GroupRequestOperateTypes } from '@/ntqqapi/types'
import { convertMessage2List, createSendElements, sendMsg } from '../helper/createMessage' import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../helper/createMessage'
import { getConfigUtil } from '@/common/config'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
import { isNullable } from 'cosmokit' import { isNullable } from 'cosmokit'
import { Context } from 'cordis' import { Context } from 'cordis'
import { OB11Config } from '@/common/types'
interface QuickOperationPrivateMessage { interface QuickOperationPrivateMessage {
reply?: string reply?: string
@@ -57,21 +57,14 @@ export async function handleQuickOperation(ctx: Context, event: QuickOperationEv
async function handleMsg(ctx: Context, msg: OB11Message, quickAction: QuickOperationPrivateMessage | QuickOperationGroupMessage) { async function handleMsg(ctx: Context, msg: OB11Message, quickAction: QuickOperationPrivateMessage | QuickOperationGroupMessage) {
const reply = quickAction.reply const reply = quickAction.reply
const ob11Config = getConfigUtil().getConfig().ob11 const ob11Config: OB11Config = ctx.config
const peer: Peer = { let contextMode = CreatePeerMode.Normal
chatType: ChatType.friend, if (msg.message_type === 'group') {
peerUid: msg.user_id.toString(), contextMode = CreatePeerMode.Group
} } else if (msg.message_type === 'private') {
if (msg.message_type == 'private') { contextMode = CreatePeerMode.Private
peer.peerUid = (await ctx.ntUserApi.getUidByUin(msg.user_id.toString()))!
if (msg.sub_type === 'group') {
peer.chatType = ChatType.temp
}
}
else {
peer.chatType = ChatType.group
peer.peerUid = msg.group_id?.toString()!
} }
const peer = await createPeer(ctx, msg, contextMode)
if (reply) { if (reply) {
let replyMessage: OB11MessageData[] = [] let replyMessage: OB11MessageData[] = []
if (ob11Config.enableQOAutoQuote) { if (ob11Config.enableQOAutoQuote) {
@@ -95,7 +88,7 @@ async function handleMsg(ctx: Context, msg: OB11Message, quickAction: QuickOpera
} }
replyMessage = replyMessage.concat(convertMessage2List(reply, quickAction.auto_escape)) replyMessage = replyMessage.concat(convertMessage2List(reply, quickAction.auto_escape))
const { sendElements, deleteAfterSentFiles } = await createSendElements(ctx, replyMessage, peer) const { sendElements, deleteAfterSentFiles } = await createSendElements(ctx, replyMessage, peer)
sendMsg(ctx, peer, sendElements, deleteAfterSentFiles, false).catch(e => ctx.logger.error(e)) sendMsg(ctx, peer, sendElements, deleteAfterSentFiles).catch(e => ctx.logger.error(e))
} }
if (msg.message_type === 'group') { if (msg.message_type === 'group') {
const groupMsgQuickAction = quickAction as QuickOperationGroupMessage const groupMsgQuickAction = quickAction as QuickOperationGroupMessage

View File

@@ -54,8 +54,10 @@ export interface OB11GroupMember {
export interface OB11Group { export interface OB11Group {
group_id: number group_id: number
group_name: string group_name: string
member_count?: number group_memo: string
max_member_count?: number group_create_time: number
member_count: number
max_member_count: number
} }
interface OB11Sender { interface OB11Sender {
@@ -66,6 +68,8 @@ interface OB11Sender {
card?: string // 群名片 card?: string // 群名片
level?: string // 群等级 level?: string // 群等级
role?: OB11GroupMemberRole role?: OB11GroupMemberRole
group_id?: number // 当私聊 sub_type 为 group 时
title?: string // 群聊专属头衔
} }
export enum OB11MessageType { export enum OB11MessageType {
@@ -91,6 +95,7 @@ export interface OB11Message {
font: number font: number
post_type?: EventType post_type?: EventType
raw?: RawMessage raw?: RawMessage
temp_source?: 0 | 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9
} }
export interface OB11ForwardMessage extends OB11Message { export interface OB11ForwardMessage extends OB11Message {
@@ -102,7 +107,7 @@ export interface OB11Return<DataType> {
retcode: number retcode: number
data: DataType data: DataType
message: string message: string
echo?: any // ws调用api才有此字段 echo?: unknown // ws调用api才有此字段
wording?: string // go-cqhttp字段错误信息 wording?: string // go-cqhttp字段错误信息
} }
@@ -133,20 +138,21 @@ export interface OB11MessageMFace {
emoji_package_id: number emoji_package_id: number
emoji_id: string emoji_id: string
key: string key: string
summary: string summary?: string
url?: string
} }
} }
export interface OB11MessageDice { export interface OB11MessageDice {
type: OB11MessageDataType.dice type: OB11MessageDataType.dice
data: { data: {
result: number result: number /* intended */ | string /* in fact */
} }
} }
export interface OB11MessageRPS { export interface OB11MessageRPS {
type: OB11MessageDataType.RPS type: OB11MessageDataType.RPS
data: { data: {
result: number result: number | string
} }
} }
@@ -171,6 +177,7 @@ export interface OB11MessageFileBase {
name?: string name?: string
file: string file: string
url?: string url?: string
file_size?: string //扩展
} }
} }
@@ -184,14 +191,24 @@ export interface OB11MessageImage extends OB11MessageFileBase {
export interface OB11MessageRecord extends OB11MessageFileBase { export interface OB11MessageRecord extends OB11MessageFileBase {
type: OB11MessageDataType.voice type: OB11MessageDataType.voice
data: OB11MessageFileBase['data'] & {
path?: string //扩展
}
} }
export interface OB11MessageFile extends OB11MessageFileBase { export interface OB11MessageFile extends OB11MessageFileBase {
type: OB11MessageDataType.file type: OB11MessageDataType.file
data: OB11MessageFileBase['data'] & {
file_id?: string
path?: string
}
} }
export interface OB11MessageVideo extends OB11MessageFileBase { export interface OB11MessageVideo extends OB11MessageFileBase {
type: OB11MessageDataType.video type: OB11MessageDataType.video
data: OB11MessageFileBase['data'] & {
path?: string //扩展
}
} }
export interface OB11MessageAt { export interface OB11MessageAt {
@@ -298,3 +315,27 @@ export interface OB11Status {
online: boolean | null online: boolean | null
good: boolean good: boolean
} }
export interface OB11GroupFile {
group_id: number
file_id: string
file_name: string
busid: number
file_size: number
upload_time: number
dead_time: number
modify_time: number
download_times: number
uploader: number
uploader_name: string
}
export interface OB11GroupFileFolder {
group_id: number
folder_id: string
folder_name: string
create_time: number
creator: number
creator_name: string
total_file_count: number
}

View File

@@ -14,7 +14,7 @@ const { contextBridge } = require('electron')
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
const llonebot = { const llonebot = {
log: (data: any) => { log: (data: unknown) => {
ipcRenderer.send(CHANNEL_LOG, data) ipcRenderer.send(CHANNEL_LOG, data)
}, },
checkVersion: async (): Promise<CheckVersion> => { checkVersion: async (): Promise<CheckVersion> => {

View File

@@ -32,17 +32,15 @@ window.customElements.define(
this.attachShadow({ mode: 'open' }) this.attachShadow({ mode: 'open' })
this.shadowRoot?.append(SelectTemplate.content.cloneNode(true)) this.shadowRoot?.append(SelectTemplate.content.cloneNode(true))
this._button = this.shadowRoot?.querySelector('div[part="button"]')! this._button = this.shadowRoot?.querySelector('div[part="button"]') as HTMLDivElement
this._text = this.shadowRoot?.querySelector('input[part="current-text"]')! this._text = this.shadowRoot?.querySelector('input[part="current-text"]') as HTMLInputElement
this._context = this.shadowRoot?.querySelector('ul[part="option-list"]')! this._context = this.shadowRoot?.querySelector('ul[part="option-list"]') as HTMLUListElement
const buttonClick = () => { const buttonClick = () => {
const isHidden = this._context.classList.toggle('hidden') const isHidden = this._context.classList.toggle('hidden')
window[`${isHidden ? 'remove' : 'add'}EventListener`]('pointerdown', windowPointerDown) window[`${isHidden ? 'remove' : 'add'}EventListener`]('pointerdown', ({ target }) => {
} if (!this.contains(target as Node)) buttonClick()
})
const windowPointerDown = ({ target }) => {
if (!this.contains(target)) buttonClick()
} }
this._button.addEventListener('click', buttonClick) this._button.addEventListener('click', buttonClick)
@@ -69,12 +67,12 @@ window.customElements.define(
) )
}) })
this._text.value = this.querySelector('setting-option[is-selected]')?.textContent! this._text.value = this.querySelector('setting-option[is-selected]')?.textContent as string
} }
}, },
) )
export const SettingSelect = (items: Array<{ text: string; value: string }>, configKey?: string, configValue?: any) => { export const SettingSelect = (items: Array<{ text: string; value: string }>, configKey?: string, configValue?: unknown) => {
return `<ob-setting-select ${configKey ? `data-config-key="${configKey}"` : ''}> return `<ob-setting-select ${configKey ? `data-config-key="${configKey}"` : ''}>
${items ${items
.map((e, i) => { .map((e, i) => {

View File

@@ -1,8 +1,10 @@
import { CheckVersion } from '../common/types' import { CheckVersion, Config } from '../common/types'
import { SettingButton, SettingItem, SettingList, SettingSwitch, SettingSelect } from './components' import { SettingButton, SettingItem, SettingList, SettingSwitch, SettingSelect } from './components'
// @ts-ignore
import StyleRaw from './style.css?raw'
import { version } from '../version' import { version } from '../version'
// @ts-expect-error: Unreachable code error
import StyleRaw from './style.css?raw'
type HostsType = 'httpHosts' | 'wsHosts'
function isEmpty(value: unknown) { function isEmpty(value: unknown) {
return value === undefined || value === null || value === '' return value === undefined || value === null || value === ''
@@ -10,17 +12,20 @@ function isEmpty(value: unknown) {
async function onSettingWindowCreated(view: Element) { async function onSettingWindowCreated(view: Element) {
//window.llonebot.log('setting window created') //window.llonebot.log('setting window created')
let config = await window.llonebot.getConfig() const config = await window.llonebot.getConfig()
let ob11Config = { ...config.ob11 } const ob11Config = { ...config.ob11 }
const setConfig = (key: string, value: any) => { const setConfig = (key: string, value: unknown) => {
const configKey = key.split('.') const configKey = key.split('.')
if (key.indexOf('ob11') === 0) { if (key.startsWith('ob11')) {
if (configKey.length === 2) ob11Config[configKey[1]] = value if (configKey.length === 2) Object.assign(ob11Config, { [configKey[1]]: value })
else ob11Config[key] = value else Object.assign(ob11Config, { [key]: value })
} else { } else {
if (configKey.length === 2) config[configKey[0]][configKey[1]] = value if (configKey.length === 2) {
else config[key] = value Object.assign(config[configKey[0] as keyof Config[keyof Config]], { [configKey[1]]: value })
} else {
Object.assign(config, { [key]: value })
}
if (!['heartInterval', 'token', 'ffmpeg'].includes(key)) { if (!['heartInterval', 'token', 'ffmpeg'].includes(key)) {
window.llonebot.setConfig(false, config) window.llonebot.setConfig(false, config)
} }
@@ -82,9 +87,7 @@ async function onSettingWindowCreated(view: Element) {
<setting-text>HTTP 事件上报密钥</setting-text> <setting-text>HTTP 事件上报密钥</setting-text>
</div> </div>
<div class="q-input"> <div class="q-input">
<input id="config-ob11-httpSecret" class="q-input__inner" data-config-key="ob11.httpSecret" type="text" value="${ <input id="config-ob11-httpSecret" class="q-input__inner" data-config-key="ob11.httpSecret" type="text" value="${config.ob11.httpSecret}" placeholder="未设置" />
config.ob11.httpSecret
}" placeholder="未设置" />
</div> </div>
</setting-item> </setting-item>
<setting-item data-direction="row"> <setting-item data-direction="row">
@@ -147,8 +150,7 @@ async function onSettingWindowCreated(view: Element) {
), ),
SettingItem( SettingItem(
'FFmpeg 路径,发送语音、视频需要', 'FFmpeg 路径,发送语音、视频需要',
`<a href="javascript:LiteLoader.api.openExternal(\'https://llonebot.github.io/zh-CN/guide/ffmpeg\');">可点此下载</a>, 路径: <span id="config-ffmpeg-path-text">${ `<a href="javascript:LiteLoader.api.openExternal(\'https://llonebot.github.io/zh-CN/guide/ffmpeg\');">可点此下载</a>, 路径: <span id="config-ffmpeg-path-text">${!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'
!isEmpty(config.ffmpeg) ? config.ffmpeg : '未指定'
}</span>, 需保证 FFprobe 和 FFmpeg 在一起`, }</span>, 需保证 FFprobe 和 FFmpeg 在一起`,
SettingButton('选择 FFmpeg', 'config-ffmpeg-select'), SettingButton('选择 FFmpeg', 'config-ffmpeg-select'),
), ),
@@ -161,7 +163,12 @@ async function onSettingWindowCreated(view: Element) {
SettingItem( SettingItem(
'快速操作回复自动引用原消息', '快速操作回复自动引用原消息',
null, null,
SettingSwitch('ob11.enableQOAutoQuote', config.ob11.enableQOAutoQuote, { 'control-display-id': 'config-ob11-enableQOAutoQuote' }), SettingSwitch('ob11.enableQOAutoQuote', config.ob11.enableQOAutoQuote),
),
SettingItem(
'HTTP、正向 WebSocket 服务仅监听 127.0.0.1',
'而不是 0.0.0.0',
SettingSwitch('ob11.listenLocalhost', config.ob11.listenLocalhost),
), ),
SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')),
]), ]),
@@ -244,7 +251,7 @@ async function onSettingWindowCreated(view: Element) {
window.LiteLoader.api.openExternal('https://llonebot.github.io/') window.LiteLoader.api.openExternal('https://llonebot.github.io/')
}) })
// 生成反向地址列表 // 生成反向地址列表
const buildHostListItem = (type: string, host: string, index: number, inputAttrs: any = {}) => { const buildHostListItem = (type: HostsType, host: string, index: number, inputAttrs = {}) => {
const dom = { const dom = {
container: document.createElement('setting-item'), container: document.createElement('setting-item'),
input: document.createElement('input'), input: document.createElement('input'),
@@ -276,7 +283,7 @@ async function onSettingWindowCreated(view: Element) {
return dom.container return dom.container
} }
const buildHostList = (hosts: string[], type: string, inputAttr: any = {}) => { const buildHostList = (hosts: string[], type: HostsType, inputAttr = {}) => {
const result: HTMLElement[] = [] const result: HTMLElement[] = []
hosts.forEach((host, index) => { hosts.forEach((host, index) => {
@@ -285,14 +292,15 @@ async function onSettingWindowCreated(view: Element) {
return result return result
} }
const addReverseHost = (type: string, doc: Document = document, inputAttr: any = {}) => { const addReverseHost = (type: HostsType, doc: Document = document, inputAttr = {}) => {
const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`)
hostContainerDom?.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr)) hostContainerDom?.appendChild(buildHostListItem(type, '', ob11Config[type].length, inputAttr))
ob11Config[type].push('') ob11Config[type].push('')
} }
const initReverseHost = (type: string, doc: Document = document) => { const initReverseHost = (type: HostsType, doc: Document = document) => {
const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`) const hostContainerDom = doc.body.querySelector(`#config-ob11-${type}-list`)!
;[...hostContainerDom?.childNodes!].forEach((dom) => dom.remove()) const nodes = [...hostContainerDom.childNodes]
nodes.forEach((dom) => dom.remove())
buildHostList(ob11Config[type], type).forEach((dom) => { buildHostList(ob11Config[type], type).forEach((dom) => {
hostContainerDom?.appendChild(dom) hostContainerDom?.appendChild(dom)
}) })
@@ -417,23 +425,23 @@ async function onSettingWindowCreated(view: Element) {
} }
} }
window.llonebot.checkVersion().then(checkVersionFunc) window.llonebot.checkVersion().then(checkVersionFunc)
window.addEventListener('beforeunload', (event) => { window.addEventListener('beforeunload', () => {
if (JSON.stringify(ob11Config) === JSON.stringify(config.ob11)) return if (JSON.stringify(ob11Config) === JSON.stringify(config.ob11)) return
config.ob11 = ob11Config config.ob11 = ob11Config
window.llonebot.setConfig(true, config) window.llonebot.setConfig(true, config)
}) })
} }
function init() { /**function init() {
const hash = location.hash const hash = location.hash
if (hash === '#/blank') { if (hash === '#/blank') {
} }
} }
if (location.hash === '#/blank') { if (location.hash === '#/blank') {
globalThis.navigation.addEventListener('navigatesuccess', init, { once: true }) globalThis.navigation?.addEventListener('navigatesuccess', init, { once: true })
} else { } else {
init() init()
} }*/
export { onSettingWindowCreated } export { onSettingWindowCreated }

View File

@@ -1 +1 @@
export const version = '3.31.2' export const version = '3.31.10'

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