Compare commits

...

24 Commits

Author SHA1 Message Date
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
49 changed files with 589 additions and 348 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.3", "version": "3.31.6",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {

View File

@@ -38,7 +38,7 @@ export class ConfigUtil {
} }
reloadConfig(): Config { reloadConfig(): Config {
let ob11Default: OB11Config = { const ob11Default: OB11Config = {
httpPort: 3000, httpPort: 3000,
httpHosts: [], httpHosts: [],
httpSecret: '', httpSecret: '',
@@ -52,7 +52,7 @@ export class ConfigUtil {
enableHttpHeart: false, enableHttpHeart: false,
enableQOAutoQuote: false enableQOAutoQuote: false
} }
let defaultConfig: Config = { const defaultConfig: Config = {
enableLLOB: true, enableLLOB: true,
ob11: ob11Default, ob11: ob11Default,
heartInterval: 60000, heartInterval: 60000,
@@ -83,7 +83,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 +94,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 currentConfig[currentKey] = oldValue as any
delete oldConfig[oldKey] delete oldConfig[oldKey]
} }
} }

View File

@@ -34,6 +34,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 {

2
src/global.d.ts vendored
View File

@@ -4,4 +4,6 @@ import { Dict } from 'cosmokit'
declare global { declare global {
var llonebot: LLOneBot var llonebot: LLOneBot
var LiteLoader: Dict var LiteLoader: Dict
var authData: Dict | undefined
var navigation: Dict | undefined
} }

View File

@@ -187,7 +187,7 @@ export class NTQQFileApi extends Service {
filePath = data[1].filePath filePath = data[1].filePath
} else { } else {
const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>( const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>(
NTMethod.DOWNLOAD_MEDIA, 'nodeIKernelMsgService/downloadRichMedia',
[ [
{ {
getReq: { getReq: {

View File

@@ -1,11 +1,11 @@
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify } from '../types' import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GroupNotify, 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 { NTEventDispatch } from '@/common/utils/eventTask'
import { NodeIKernelGroupListener } from '../listeners' import { NodeIKernelGroupListener, OnGroupFileInfoUpdateParams } 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,7 +19,7 @@ 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)
@@ -132,7 +132,7 @@ export class NTQQGroupApi extends Service {
} else { } 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],
{ {
@@ -246,7 +246,7 @@ export class NTQQGroupApi extends Service {
} }
} }
async getGroupAtAllRemainCount(groupCode: string) { async getGroupRemainAtTimes(groupCode: string) {
return await invoke< return await invoke<
GeneralCallResult & { GeneralCallResult & {
atInfo: { atInfo: {
@@ -301,4 +301,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

@@ -68,6 +68,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')
@@ -153,12 +157,7 @@ export class NTQQMsgApi extends Service {
) )
msgList = data.msgList msgList = data.msgList
} }
const retMsg = msgList.find(msgRecord => { return msgList.find(msgRecord => msgRecord.guildId === msgId)
if (msgRecord.guildId === msgId) {
return true
}
})
return retMsg!
} }
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
@@ -265,22 +264,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) {

View File

@@ -3,7 +3,7 @@ import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListene
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 { NodeIKernelProfileService, UserDetailSource, ProfileBizType } from '../services'
import { NodeIKernelProfileListener } from '../listeners' import { NodeIKernelProfileListener } from '../listeners'
import { NTEventDispatch } from '@/common/utils/eventTask' import { NTEventDispatch } from '@/common/utils/eventTask'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
@@ -17,7 +17,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 +25,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,
@@ -187,16 +187,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
} }
@@ -221,11 +236,11 @@ export class NTQQUserApi extends Service {
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) {
@@ -247,25 +262,25 @@ export class NTQQUserApi extends Service {
} }
} }
async getUserDetailInfoByUin(Uin: string) { async getUserDetailInfoByUin(uin: string) {
return NTEventDispatch.CallNoListenerEvent return NTEventDispatch.CallNoListenerEvent
<(Uin: string) => Promise<UserDetailInfoByUin>>( <(Uin: string) => Promise<UserDetailInfoByUin>>(
'NodeIKernelProfileService/getUserDetailInfoByUin', 'NodeIKernelProfileService/getUserDetailInfoByUin',
5000, 5000,
Uin uin
) )
} }
async getUinByUidV1(Uid: string) { async getUinByUidV1(uid: string) {
const ret = await NTEventDispatch.CallNoListenerEvent const ret = await NTEventDispatch.CallNoListenerEvent
<(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>( <(Uin: string[]) => Promise<{ uinInfo: Map<string, string> }>>(
'NodeIKernelUixConvertService/getUin', 'NodeIKernelUixConvertService/getUin',
5000, 5000,
[Uid] [uid]
) )
let uin = ret.uinInfo.get(Uid) let uin = ret.uinInfo.get(uid)
if (!uin) { if (!uin) {
uin = (await this.getUserDetailInfo(Uid)).uin //从QQ Native 转换 uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
} }
return uin return uin
} }
@@ -293,11 +308,11 @@ export class NTQQUserApi extends Service {
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() {

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 {
@@ -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

@@ -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
@@ -295,7 +335,7 @@ export class MsgListener implements IKernelMsgListener {
} }
onGroupFileInfoUpdate(groupFileListResult: onGroupFileInfoUpdateParamType) { onGroupFileInfoUpdate(groupFileListResult: OnGroupFileInfoUpdateParams) {
} }

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,16 +102,16 @@ 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) => any>>>,
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
@@ -157,12 +132,13 @@ export function invoke<
} }
} }
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)
@@ -176,14 +152,14 @@ export function invoke<
}) })
} }
!afterFirstCmd && secondCallback() !afterFirstCmd && secondCallback()
hookApiCallbacks[callbackId] = (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}`)
} }
} }
} }

View File

@@ -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 & {

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

View File

@@ -11,7 +11,8 @@ 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 {
@@ -72,7 +73,7 @@ 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 (let 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], {

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,7 +6,7 @@ interface Payload {
message_id: number | string message_id: number | string
} }
export default class GoCQHTTPDelEssenceMsg extends BaseAction<Payload, any> { export class DelEssenceMsg extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_DelEssenceMsg; actionName = ActionName.GoCQHTTP_DelEssenceMsg;
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {

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

@@ -13,7 +13,7 @@ interface Response {
messages: (OB11Message & { content: OB11MessageData })[] messages: (OB11Message & { content: OB11MessageData })[]
} }
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): Promise<any> {
const msgId = payload.id || payload.message_id const msgId = payload.id || payload.message_id

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,7 +24,7 @@ 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(payload: void) {

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,7 +5,7 @@ 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(payload: Payload): Promise<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

@@ -6,7 +6,7 @@ interface Payload {
message_id: number | string message_id: number | string
} }
export default class GoCQHTTPSetEssenceMsg extends BaseAction<Payload, any> { export class SetEssenceMsg extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_SetEssenceMsg; actionName = ActionName.GoCQHTTP_SetEssenceMsg;
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {

View File

@@ -16,7 +16,7 @@ interface Payload {
folder_id?: string folder_id?: string
} }
export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> { export class UploadGroupFile extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_UploadGroupFile actionName = ActionName.GoCQHTTP_UploadGroupFile
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
@@ -37,7 +37,7 @@ export class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
} }
} }
export class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null> { export class UploadPrivateFile extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_UploadPrivateFile actionName = ActionName.GoCQHTTP_UploadPrivateFile
async getPeer(payload: Payload): Promise<Peer> { async getPeer(payload: Payload): Promise<Peer> {

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,31 @@ 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'
export function initActionMap(adapter: Adapter) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@@ -105,24 +107,26 @@ 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)
] ]
const actionMap = new Map<string, BaseAction<any, any>>() const actionMap = new Map<string, BaseAction<any, any>>()
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

@@ -14,8 +14,8 @@ export default class Debug extends BaseAction<Payload, any> {
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

@@ -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

@@ -48,16 +48,16 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
} }
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
const Uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString()) const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
const isBuddy = await this.ctx.ntFriendApi.isBuddy(Uid!) if (!uid) throw new Error('无法获取用户信息')
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy) const isBuddy = await this.ctx.ntFriendApi.isBuddy(uid)
return { return {
chatType: isBuddy ? ChatType.friend : ChatType.temp, chatType: isBuddy ? ChatType.friend : ChatType.temp,
peerUid: Uid!, peerUid: uid,
guildId: payload.group_id?.toString() || '' //临时主动发起时需要传入群号 guildId: isBuddy ? '' : payload.group_id?.toString() || ''
} }
} }
throw '请指定 group_id 或 user_id' throw new Error('请指定 group_id 或 user_id')
} }
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> { protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
@@ -160,6 +160,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! }
} }
@@ -251,9 +254,12 @@ 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, [], true)
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

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

View File

@@ -76,5 +76,7 @@ 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'
} }

View File

@@ -10,6 +10,7 @@ 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: any) => Promise<any>
@@ -159,7 +160,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,
} }

View File

@@ -14,7 +14,7 @@ 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) {
} }
@@ -31,7 +31,7 @@ class OB11WebSocket {
} }
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 +53,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)
} }
@@ -70,8 +70,8 @@ class OB11WebSocket {
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)
} }
} }
@@ -122,33 +122,40 @@ class OB11WebSocket {
} }
} }
private connect(socket: WebSocket) { private connect(socket: WebSocket, req: IncomingMessage) {
try { const url = req.url?.split('?').shift()
this.reply(socket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT)) if (['/api', '/api/', '/', undefined].includes(url)) {
} catch (e) { socket.on('message', msg => {
this.ctx.logger.error('发送生命周期失败', e) this.handleAction(socket, msg.toString())
})
}
if (['/event', '/event/', '/', undefined].includes(url)) {
try {
this.reply(socket, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT))
} catch (e) {
this.ctx.logger.error('发送生命周期失败', e)
}
const disposeHeartBeat = this.ctx.setInterval(() => {
this.reply(socket, new OB11HeartbeatEvent(selfInfo.online!, true, this.config.heartInterval))
}, this.config.heartInterval)
socket.on('close', () => {
disposeHeartBeat()
this.ctx.logger.info('有一个 Websocket 连接断开')
})
} }
socket.on('error', err => this.ctx.logger.error(err.message)) socket.on('error', err => this.ctx.logger.error(err.message))
socket.on('message', msg => {
this.handleAction(socket, msg.toString())
})
socket.on('ping', () => { socket.on('ping', () => {
socket.pong() socket.pong()
}) })
const disposeHeartBeat = this.ctx.setInterval(() => { this.wsClients.push({
this.reply(socket, new OB11HeartbeatEvent(selfInfo.online!, true, this.config.heartInterval)) socket,
}, this.config.heartInterval) emitEvent: ['/event', '/event/', '/', undefined].includes(url)
socket.on('close', () => {
disposeHeartBeat()
this.ctx.logger.info('有一个 Websocket 连接断开')
}) })
this.wsClients.push(socket)
} }
} }
@@ -192,8 +199,8 @@ class OB11WebSocketReverse {
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)
} }
} }

View File

@@ -44,7 +44,7 @@ export function decodeCQCode(source: string): OB11MessageData[] {
return elements return elements
} }
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

@@ -37,7 +37,6 @@ 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'
@@ -47,6 +46,7 @@ import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'
import { omit, isNullable } from 'cosmokit' import { omit, isNullable } 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'
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> {
@@ -105,10 +105,7 @@ export namespace OB11Entities {
} }
for (let element of msg.elements) { for (let 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,12 +135,16 @@ 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) {
const { replyElement } = element const { replyElement } = element
@@ -163,7 +164,7 @@ export namespace OB11Entities {
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') { if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
throw new Error('回复消息消息验证失败') throw new Error('回复消息消息验证失败')
} }
message_data = { messageSegment = {
type: OB11MessageDataType.reply, type: OB11MessageDataType.reply,
data: { data: {
id: MessageUnique.createMsg(peer, replyMsg ? replyMsg.msgId : records.msgId).toString() id: MessageUnique.createMsg(peer, replyMsg ? replyMsg.msgId : records.msgId).toString()
@@ -175,18 +176,17 @@ export namespace OB11Entities {
} }
} }
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,
@@ -195,21 +195,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,
@@ -218,17 +223,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,
@@ -237,17 +248,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,
@@ -256,59 +272,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) { if (messageSegment) {
const cqCode = encodeCQCode(message_data) 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
} }
} }

View File

@@ -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)
@@ -270,8 +270,10 @@ export async function sendMsg(
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, waitComplete, timeout)
returnMsg.msgShortId = MessageUnique.createMsg(peer, returnMsg.msgId) if (returnMsg) {
ctx.logger.info('消息发送', returnMsg.msgShortId) returnMsg.msgShortId = MessageUnique.createMsg(peer, returnMsg.msgId)
deleteAfterSentFiles.map(path => fsPromise.unlink(path)) ctx.logger.info('消息发送', returnMsg.msgShortId)
return returnMsg deleteAfterSentFiles.map(path => fsPromise.unlink(path))
return returnMsg
}
} }

View File

@@ -133,20 +133,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 +172,7 @@ export interface OB11MessageFileBase {
name?: string name?: string
file: string file: string
url?: string url?: string
file_size?: string //扩展
} }
} }
@@ -184,14 +186,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 +310,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

@@ -38,11 +38,9 @@ window.customElements.define(
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 any)) buttonClick()
})
const windowPointerDown = ({ target }) => {
if (!this.contains(target)) buttonClick()
} }
this._button.addEventListener('click', buttonClick) this._button.addEventListener('click', buttonClick)

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'
import { version } from '../version'
// @ts-ignore // @ts-ignore
import StyleRaw from './style.css?raw' import StyleRaw from './style.css?raw'
import { version } from '../version'
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: any) => {
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)
} }
@@ -244,7 +249,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: any = {}) => {
const dom = { const dom = {
container: document.createElement('setting-item'), container: document.createElement('setting-item'),
input: document.createElement('input'), input: document.createElement('input'),
@@ -276,7 +281,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: any = {}) => {
const result: HTMLElement[] = [] const result: HTMLElement[] = []
hosts.forEach((host, index) => { hosts.forEach((host, index) => {
@@ -285,12 +290,12 @@ 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: any = {}) => {
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()) ;[...hostContainerDom?.childNodes!].forEach((dom) => dom.remove())
buildHostList(ob11Config[type], type).forEach((dom) => { buildHostList(ob11Config[type], type).forEach((dom) => {
@@ -431,7 +436,7 @@ function init() {
} }
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()
} }

View File

@@ -1 +1 @@
export const version = '3.31.3' export const version = '3.31.6'

View File

@@ -1,10 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"module": "commonjs", "module": "CommonJS",
"outDir": "./dist", "outDir": "./dist",
"strict": true, "strict": true,
"noImplicitAny": false, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"experimentalDecorators": true, "experimentalDecorators": true,