Compare commits

...

34 Commits

Author SHA1 Message Date
linyuchen
825c7c8e29 Merge branch 'dev' 2024-11-16 14:49:13 +08:00
linyuchen
c8d5eebe5d feat: new api set_friend_remark, set_friend_category, set_group_remark, set_group_msg_mask, set_restart 2024-11-16 14:48:45 +08:00
linyuchen
466a3e4d66 Merge branch 'dev'
# Conflicts:
#	manifest.json
#	src/version.ts
2024-11-15 11:01:52 +08:00
linyuchen
f6263375f1 chore: version 4.2.2 2024-11-15 11:01:25 +08:00
linyuchen
f79581d97e 🐛修复 hook ipc 时获取不到 callbackId 导致其他插件 ipc 通信失败 2024-11-15 10:58:35 +08:00
linyuchen
56f26e9aa8 chore: version 4.2.1 2024-11-14 20:00:36 +08:00
linyuchen
9e03071629 Merge branch 'dev' 2024-11-14 19:58:27 +08:00
idranme
1f02c98c8f chore 2024-11-14 15:24:46 +08:00
linyuchen
e1e5c278b9 🐛修复 hook ipc 时获取不到 eventName 导致其他插件 ipc 通信失败 2024-11-14 14:51:01 +08:00
idranme
104839f7ea fix 2024-11-14 12:34:48 +08:00
idranme
bb8771a5b4 refactor 2024-11-14 11:40:19 +08:00
linyuchen
4a2523463b Merge remote-tracking branch 'origin/main' 2024-11-13 19:35:04 +08:00
linyuchen
a23a99310a Merge branch 'dev' 2024-11-13 19:34:18 +08:00
linyuchen
5c5105ce88 chore: version 4.2.0 2024-11-13 19:33:29 +08:00
linyuchen
1bf5e41bdc chore: 协议包支持 macOS 2024-11-13 19:27:50 +08:00
linyuchen
cd679cc041 refactor: 设置群员头衔的时候检查是否群主 2024-11-13 19:27:21 +08:00
linyuchen
eabee466bb refactor: 使用 napcat packet 实现戳一戳、群头衔、群打卡 2024-11-12 22:09:50 +08:00
idranme
d3f93257ce feat: get_stranger_info API adds city field 2024-11-10 16:59:58 +08:00
idranme
33f340ca81 chore 2024-11-10 14:18:09 +08:00
idranme
0d27ef7ebc Merge pull request #502 from LLOneBot/dev
release: 4.1.4
2024-11-09 22:29:16 +08:00
idranme
479e8c9d25 optimize 2024-11-09 22:21:04 +08:00
linyuchen
e3dffa24f8 Merge branch 'dev' 2024-11-09 21:40:06 +08:00
linyuchen
30b8793ee1 fix: 修复 IPC 超时 2024-11-09 21:37:41 +08:00
linyuchen
edf7a97269 Merge branch 'dev' 2024-11-08 18:27:06 +08:00
linyuchen
47b068737d chore: bump version, add author 2024-11-08 06:05:17 +08:00
linyuchen
bfb67188ce fix: DownloadFile接口参数url和base64二选一即可 2024-11-08 05:45:21 +08:00
linyuchen
7ad384d407 fix: 发送文件路径包含#%时发送失败 2024-11-08 05:44:55 +08:00
idranme
66335ddf9b Merge pull request #492 from LLOneBot/dev
release: 4.1.2
2024-10-27 12:11:50 +08:00
idranme
f7926c2e1b chore: bump versions 2024-10-27 12:07:21 +08:00
idranme
b669e28038 fix 2024-10-27 12:06:33 +08:00
idranme
70b3005005 Merge pull request #489 from LLOneBot/dev
release: 4.1.1
2024-10-26 00:19:15 +08:00
idranme
94f1d84dd8 chore: bump versions 2024-10-26 00:16:18 +08:00
idranme
aa2b4a160d fix 2024-10-26 00:15:48 +08:00
idranme
9be43de04b fix: forward 2024-10-26 00:14:27 +08:00
50 changed files with 7626 additions and 1762 deletions

View File

@@ -39,6 +39,7 @@ const config: ElectronViteConfig = {
...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/napcat-protocol-packet/Moehoo/*', dest: 'dist/main/Moehoo' },
], ],
}), }),
], ],

View File

@@ -4,12 +4,16 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "实现 OneBot 11 和 Satori 协议,用于 QQ 机器人开发", "description": "实现 OneBot 11 和 Satori 协议,用于 QQ 机器人开发",
"version": "4.1.0", "version": "4.3.0",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {
"name": "linyuchen", "name": "linyuchen",
"link": "https://github.com/linyuchen" "link": "https://github.com/linyuchen"
},
{
"name": "idranme",
"link": "https://github.com/idranme"
} }
], ],
"repository": { "repository": {

View File

@@ -12,7 +12,7 @@
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %LITELOADERQQNT_PROFILE%\\plugins\\LLOneBot\\\"", "deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %LITELOADERQQNT_PROFILE%\\plugins\\LLOneBot\\\"",
"format": "prettier -cw .", "format": "prettier -cw .",
"check": "tsc", "check": "tsc",
"compile:proto": "pbjs --no-create --no-convert --no-verify -t static-module -w es6 -p src/ntqqapi/proto -o src/ntqqapi/proto/compiled.js profileLikeTip.proto groupNotify.proto message.proto richMedia.proto && pbts -o src/ntqqapi/proto/compiled.d.ts src/ntqqapi/proto/compiled.js" "compile:proto": "pbjs --no-create --no-convert --no-delimited --no-verify -t static-module -w es6 -p src/ntqqapi/proto -o src/ntqqapi/proto/compiled.js profileLikeTip.proto groupNotify.proto message.proto richMedia.proto && pbts -o src/ntqqapi/proto/compiled.d.ts src/ntqqapi/proto/compiled.js"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
@@ -26,23 +26,23 @@
"cosmokit": "^1.6.3", "cosmokit": "^1.6.3",
"express": "^5.0.1", "express": "^5.0.1",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"minato": "^3.6.0", "minato": "^3.6.1",
"protobufjs": "^7.4.0", "protobufjs": "^7.4.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.3",
"ts-case-convert": "^2.1.0", "ts-case-convert": "^2.1.0",
"ws": "^8.18.0" "ws": "^8.18.0"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/fluent-ffmpeg": "^2.1.26", "@types/fluent-ffmpeg": "^2.1.27",
"@types/node": "^20.14.15", "@types/node": "^20.14.15",
"@types/ws": "^8.5.12", "@types/ws": "^8.5.13",
"electron": "^31.4.0", "electron": "^31.4.0",
"electron-vite": "^2.3.0", "electron-vite": "^2.3.0",
"protobufjs-cli": "^1.1.3", "protobufjs-cli": "^1.1.3",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"vite": "^5.4.9", "vite": "^5.4.10",
"vite-plugin-cp": "^4.0.8" "vite-plugin-cp": "^4.0.8"
}, },
"packageManager": "yarn@4.5.1" "packageManager": "yarn@4.5.1"

View File

@@ -13,6 +13,10 @@ const manifest = {
{ {
name: 'linyuchen', name: 'linyuchen',
link: 'https://github.com/linyuchen' link: 'https://github.com/linyuchen'
},
{
"name": "idranme",
"link": "https://github.com/idranme"
} }
], ],
repository: { repository: {

View File

@@ -27,9 +27,10 @@ export function checkFileReceived(path: string, timeout: number = 3000): Promise
export function calculateFileMD5(filePath: string): Promise<string> { export function calculateFileMD5(filePath: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = createHash('md5')
// 创建一个流式读取器 // 创建一个流式读取器
const stream = fs.createReadStream(filePath) const stream = fs.createReadStream(filePath)
const hash = createHash('md5')
stream.on('data', (data: Buffer) => { stream.on('data', (data: Buffer) => {
// 当读取到数据时,更新哈希对象的状态 // 当读取到数据时,更新哈希对象的状态
@@ -122,8 +123,11 @@ export async function uri2local(ctx: Context, uri: string, needExt?: boolean): P
const { type } = checkUriType(uri) const { type } = checkUriType(uri)
if (type === FileUriType.FileURL) { if (type === FileUriType.FileURL) {
const filePath = fileURLToPath(uri) const fileUri = uri.replace('%', '%25').replace('#', '%23')
const filePath = fileURLToPath(fileUri)
const fileName = path.basename(filePath) const fileName = path.basename(filePath)
// console.log('fileURLToPath', filePath)
// console.log('fileName', fileName)
return { success: true, errMsg: '', fileName, path: filePath, isLocal: true } return { success: true, errMsg: '', fileName, path: filePath, isLocal: true }
} }

View File

@@ -0,0 +1,9 @@
import { BrowserWindow } from 'electron'
import { log } from '@/common/utils'
export function getAllWindowIds(): number[] {
const allWindows = BrowserWindow.getAllWindows();
const ids = allWindows.map(window => window.id);
log('getAllWindowIds', ids);
return ids;
}

View File

@@ -35,6 +35,7 @@ import {
NTQQWindowApi NTQQWindowApi
} from '../ntqqapi/api' } from '../ntqqapi/api'
import { existsSync, mkdirSync } from 'node:fs' import { existsSync, mkdirSync } from 'node:fs'
import { NTQQSystemApi } from '@/ntqqapi/api/system'
declare module 'cordis' { declare module 'cordis' {
interface Events { interface Events {
@@ -74,6 +75,7 @@ function onLoad() {
ctx.plugin(NTQQWebApi) ctx.plugin(NTQQWebApi)
ctx.plugin(NTQQWindowApi) ctx.plugin(NTQQWindowApi)
ctx.plugin(Database) ctx.plugin(Database)
ctx.plugin(NTQQSystemApi)
let started = false let started = false
@@ -180,18 +182,16 @@ function onLoad() {
if (self.uin) { if (self.uin) {
clearInterval(intervalId) clearInterval(intervalId)
log('process pid', process.pid) log('process pid', process.pid)
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
if (config.enableLLOB && (config.satori.enable || config.ob11.enable)) { if (config.enableLLOB && (config.satori.enable || config.ob11.enable)) {
startHook() startHook()
await ctx.sleep(350) await ctx.sleep(600)
} else { } else {
llonebotError.otherError = 'LLOneBot 未启动' llonebotError.otherError = 'LLOneBot 未启动'
log('LLOneBot 开关设置为关闭,不启动 LLOneBot') log('LLOneBot 开关设置为关闭,不启动 LLOneBot')
return return
} }
ctx.plugin(Log, { ctx.plugin(Log, {
enable: config.log!, enable: config.log!,
filename: logFileName filename: logFileName
@@ -225,11 +225,14 @@ function onLoad() {
started = true started = true
llonebotError.otherError = '' llonebotError.otherError = ''
} }
}, 600) }, 500)
} }
// 创建窗口时触发 // 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) { function onBrowserWindowCreated(window: BrowserWindow) {
if (window.id === 2) {
mainWindow = window
}
} }
try { try {

View File

@@ -43,7 +43,7 @@ export class NTQQFileApi extends Service {
msgId, msgId,
elemId: elementId, elemId: elementId,
videoCodecFormat: 0, videoCodecFormat: 0,
params: { exParams: {
downSourceType: 1, downSourceType: 1,
triggerType: 1 triggerType: 1
} }

View File

@@ -107,7 +107,8 @@ export class NTQQFriendApi extends Service {
return ret.arkMsg return ret.arkMsg
} }
async setBuddyRemark(uid: string, remark: string) { async setBuddyRemark(uid: string, remark?: string) {
remark = remark ?? ''
return await invoke('nodeIKernelBuddyService/setBuddyRemark', [{ return await invoke('nodeIKernelBuddyService/setBuddyRemark', [{
remarkParams: { uid, remark } remarkParams: { uid, remark }
}]) }])
@@ -122,4 +123,8 @@ export class NTQQFriendApi extends Service {
} }
}]) }])
} }
async setBuddyCategory(uid: string, categoryId: number) {
return await invoke('nodeIKernelBuddyService/setBuddyCategory', [{uid, categoryId}])
}
} }

View File

@@ -22,6 +22,12 @@ declare module 'cordis' {
} }
} }
export enum GroupMsgMask {
ALLOW_NOTIFY = 1, // 允许提醒
ALLOW_NOT_NOTIFY = 4, // 接受消息不提醒
BOX_NOT_NOTIFY = 2, // 收进群助手不提醒
NOT_ALLOW = 3, // 屏蔽
}
export class NTQQGroupApi extends Service { export class NTQQGroupApi extends Service {
static inject = ['ntWindowApi'] static inject = ['ntWindowApi']
@@ -110,8 +116,9 @@ export class NTQQGroupApi extends Service {
const groupCode = flagitem[0] const groupCode = flagitem[0]
const seq = flagitem[1] const seq = flagitem[1]
const type = parseInt(flagitem[2]) const type = parseInt(flagitem[2])
const doubt = flagitem[3] === '1'
return await invoke(NTMethod.HANDLE_GROUP_REQUEST, [{ return await invoke(NTMethod.HANDLE_GROUP_REQUEST, [{
doubt: false, doubt,
operateMsg: { operateMsg: {
operateType, operateType,
targetMsg: { targetMsg: {
@@ -340,4 +347,13 @@ export class NTQQGroupApi extends Service {
[{ groupId }] [{ groupId }]
) )
} }
async setGroupMsgMask(groupCode: string, msgMask: GroupMsgMask){
return await invoke('nodeIKernelGroupService/setGroupMsgMask', [{ groupCode, msgMask }])
}
async setGroupRemark(groupCode: string, groupRemark?: string) {
groupRemark = groupRemark ?? ''
return await invoke('nodeIKernelGroupService/modifyGroupRemark', [{ groupCode, groupRemark }])
}
} }

View File

@@ -153,7 +153,17 @@ export class NTQQMsgApi extends Service {
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: payload => { cmdCB: payload => {
for (const msgRecord of payload.msgList) { for (const msgRecord of payload.msgList) {
if (msgRecord.peerUid === destPeer.peerUid && msgRecord.senderUid === selfUid) { if (
msgRecord.msgType === 11 &&
msgRecord.subMsgType === 7 &&
msgRecord.peerUid === destPeer.peerUid &&
msgRecord.senderUid === selfUid
) {
const element = msgRecord.elements[0]
const data = JSON.parse(element.arkElement!.bytesData)
if (data.app !== 'com.tencent.multimsg' || !data.meta.detail.resid) {
continue
}
return true return true
} }
} }
@@ -161,20 +171,12 @@ export class NTQQMsgApi extends Service {
} }
} }
) )
for (const msg of data.msgList) { return data.msgList.find(msgRecord => {
const arkElement = msg.elements.find(ele => ele.arkElement) const { arkElement } = msgRecord.elements[0]
if (!arkElement) { if (arkElement?.bytesData.includes('com.tencent.multimsg')) {
continue return true
} }
const forwardData = JSON.parse(arkElement.arkElement!.bytesData) })!
if (forwardData.app !== 'com.tencent.multimsg') {
continue
}
if (msg.peerUid === destPeer.peerUid && msg.senderUid === selfUid) {
return msg
}
}
throw new Error('转发消息超时')
} }
async getSingleMsg(peer: Peer, msgSeq: string) { async getSingleMsg(peer: Peer, msgSeq: string) {

39
src/ntqqapi/api/system.ts Normal file
View File

@@ -0,0 +1,39 @@
import { Context, Service } from 'cordis'
import { invoke, NTClass } from '@/ntqqapi/ntcall'
declare module 'cordis' {
interface Context {
ntSystemApi: NTQQSystemApi
}
}
export class NTQQSystemApi extends Service {
static inject = ['ntUserApi']
constructor(protected ctx: Context) {
super(ctx, 'ntSystemApi', true)
}
async restart(){
// todo: 调用此接口后会将 NTQQ 设置里面的自动登录和无需手机确认打开,重启后将状态恢复到之前的状态
// 设置自动登录
await this.setSettingAutoLogin(true)
// 退出账号
invoke('quitAccount', [], {
className: NTClass.BUSINESS_API
}).then()
invoke('notifyQQClose', [{ type: 1 }], { className: NTClass.QQ_EX_API }).then()
// 等待登录界面,模拟点击登录按钮?还是直接调用登录方法?
}
// 是否自动登录
async getSettingAutoLogin(): Promise<boolean>{
return invoke('nodeIKernelNodeMiscService/queryAutoRun', [])
}
async setSettingAutoLogin(state: boolean){
await invoke('nodeIKernelSettingService/setNeedConfirmSwitch', [{state: 1}]) // 1不需要手机确认2需要手机确认
await invoke('nodeIKernelSettingService/setAutoLoginSwitch', [{state}])
}
}

View File

@@ -16,10 +16,11 @@ import {
BuddyReqType, BuddyReqType,
GrayTipElementSubType GrayTipElementSubType
} from './types' } from './types'
import { selfInfo, llonebotError } from '../common/globalVars' import { selfInfo } from '../common/globalVars'
import { version } from '../version' import { version } from '../version'
import { invoke } from './ntcall' import { invoke } from './ntcall'
import { Native } from './native/index' import { Native } from './native/crychic'
import { initWrapperSession, NTQQPacketApi } from './native/napcat-protocol-packet'
declare module 'cordis' { declare module 'cordis' {
interface Context { interface Context {
@@ -29,7 +30,7 @@ declare module 'cordis' {
'nt/message-created': (input: RawMessage) => void 'nt/message-created': (input: RawMessage) => void
'nt/message-deleted': (input: RawMessage) => void 'nt/message-deleted': (input: RawMessage) => void
'nt/message-sent': (input: RawMessage) => void 'nt/message-sent': (input: RawMessage) => void
'nt/group-notify': (input: GroupNotify) => void 'nt/group-notify': (input: { notify: GroupNotify, doubt: boolean }) => void
'nt/friend-request': (input: FriendRequest) => void 'nt/friend-request': (input: FriendRequest) => void
'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void 'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void
'nt/system-message-created': (input: Uint8Array) => void 'nt/system-message-created': (input: Uint8Array) => void
@@ -40,10 +41,14 @@ class Core extends Service {
static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi', 'store'] static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi', 'store']
public startTime = 0 public startTime = 0
public native public native
public packet!: NTQQPacketApi
constructor(protected ctx: Context, public config: Core.Config) { constructor(protected ctx: Context, public config: Core.Config) {
super(ctx, 'app', true) super(ctx, 'app', true)
this.native = new Native(ctx) this.native = new Native(ctx)
initWrapperSession().then(session => {
this.packet = new NTQQPacketApi(session)
})
} }
public start() { public start() {
@@ -228,7 +233,7 @@ class Core extends Service {
continue continue
} }
groupNotifyIgnore.push(notify.seq) groupNotifyIgnore.push(notify.seq)
this.ctx.parallel('nt/group-notify', notify) this.ctx.parallel('nt/group-notify', { notify, doubt: payload.doubt })
} }
} }
}) })

View File

@@ -2,6 +2,7 @@ import { NTMethod } from './ntcall'
import { log } from '@/common/utils' import { log } from '@/common/utils'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { Dict } from 'cosmokit'
export const hookApiCallbacks: Record<string, (res: any) => void> = {} export const hookApiCallbacks: Record<string, (res: any) => void> = {}
@@ -27,7 +28,7 @@ export enum ReceiveCmdS {
MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete', MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete',
} }
const logHook = false const logHook = true
const receiveHooks: Map<string, { const receiveHooks: Map<string, {
method: ReceiveCmdS[] method: ReceiveCmdS[]
@@ -40,40 +41,43 @@ const callHooks: Array<{
}> = [] }> = []
export function startHook() { export function startHook() {
log('start hook')
const senderExclude = Symbol() const senderExclude = Symbol()
ipcMain.emit = new Proxy(ipcMain.emit, { ipcMain.emit = new Proxy(ipcMain.emit, {
apply(target, thisArg, args: [eventName: string, ...args: any]) { apply(target, thisArg, args: [channel: string, ...args: any]) {
if (args[2]?.eventName.startsWith('ns-LoggerApi')) { if (args[2]?.eventName?.startsWith('ns-LoggerApi')) {
return target.apply(thisArg, args) return target.apply(thisArg, args)
} }
if (logHook) { if (logHook) {
log('request', args) log('request', args)
} }
const event = args[1] const event = args[1]
if (event.sender && !event.sender[senderExclude]) { if (event.sender && !event.sender[senderExclude]) {
event.sender[senderExclude] = true event.sender[senderExclude] = true
event.sender.send = new Proxy(event.sender.send, { event.sender.send = new Proxy(event.sender.send, {
apply(target, thisArg, args: any[]) { apply(target, thisArg, args: [channel: string, meta: Dict, data: Dict[]]) {
if (args[1].eventName?.startsWith('ns-LoggerApi')) { if (args[1]?.eventName?.startsWith('ns-LoggerApi')) {
return target.apply(thisArg, args) return target.apply(thisArg, args)
} }
if (logHook) { if (logHook) {
log('received', args) log('received', args)
} }
const callbackId = args[1].callbackId const callbackId = args[1]?.callbackId
if (callbackId) { if (callbackId) {
if (hookApiCallbacks[callbackId]) { if (hookApiCallbacks[callbackId]) {
Promise.resolve(hookApiCallbacks[callbackId](args[2])) Promise.resolve(hookApiCallbacks[callbackId](args[2]))
delete hookApiCallbacks[callbackId] delete hookApiCallbacks[callbackId]
} }
} else if (args[2]) { } else if (args[2]) {
for (const receiveData of args[2]) { if (['IPC_DOWN_2', 'IPC_DOWN_3'].includes(args[0])) {
for (const hook of receiveHooks.values()) { for (const receiveData of args[2]) {
if (hook.method.includes(receiveData.cmdName)) { for (const hook of receiveHooks.values()) {
Promise.resolve(hook.hookFunc(receiveData.payload)) if (hook.method.includes(receiveData.cmdName)) {
Promise.resolve(hook.hookFunc(receiveData.payload))
}
} }
} }
} }
@@ -93,7 +97,7 @@ export function startHook() {
} }
} }
return target.apply(thisArg, args) return target.apply(thisArg, args)
} },
}) })
} }

View File

@@ -1,9 +1,9 @@
import { Context } from 'cordis' import { Context } from 'cordis'
import { Dict } from 'cosmokit' import { Dict } from 'cosmokit'
import { getBuildVersion } from '@/common/utils/misc' import { getBuildVersion } from '../../../common/utils/misc'
import { TEMP_DIR } from '@/common/globalVars' import { TEMP_DIR } from '../../../common/globalVars'
import { copyFile } from 'fs/promises' import { copyFile } from 'fs/promises'
import { ChatType, Peer } from '../types' import { ChatType, Peer } from '../../types'
import path from 'node:path' import path from 'node:path'
import addon from './external/crychic-win32-x64.node?asset' import addon from './external/crychic-win32-x64.node?asset'
@@ -11,7 +11,7 @@ export class Native {
public activated = false public activated = false
private crychic?: Dict private crychic?: Dict
private seq = 0 private seq = 0
private cb: Map<number, Function> = new Map() private cb: Map<number, (res: any) => void> = new Map()
constructor(private ctx: Context) { constructor(private ctx: Context) {
ctx.on('ready', () => { ctx.on('ready', () => {

View File

@@ -0,0 +1,17 @@
import { WrapperSession } from './wrapper-session/types';
export { initWrapperSession } from './wrapper-session';
export declare class NTQQPacketApi {
private qqVersion;
private packetSession;
private logger;
private readonly wrapperSession;
constructor(wrapperSession: WrapperSession);
get available(): boolean;
private checkQQVersion;
private InitSendPacket;
private sendPacket;
private sendOidbPacket;
sendPokePacket(peer: number, group?: number): Promise<void>;
sendGroupSignPacket(selfUin: string, groupCode: string): Promise<void>;
sendSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): Promise<void>;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
export declare function initWrapperSession(): Promise<any>;

View File

@@ -0,0 +1,6 @@
export interface MsgService {
sendSsoCmdReqByContend: (cmd: string, trace_id: string) => Promise<unknown>;
}
export type WrapperSession = {
getMsgService(): MsgService;
};

View File

@@ -1,6 +1,6 @@
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { hookApiCallbacks, registerReceiveHook, removeReceiveHook } from './hook' import { hookApiCallbacks, registerReceiveHook, removeReceiveHook } from './hook'
import { getBuildVersion, log } from '../common/utils' import { log } from '../common/utils'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { import {
GeneralCallResult, GeneralCallResult,
@@ -29,7 +29,8 @@ export enum NTClass {
SKEY_API = 'ns-SkeyApi', SKEY_API = 'ns-SkeyApi',
GROUP_HOME_WORK = 'ns-GroupHomeWork', GROUP_HOME_WORK = 'ns-GroupHomeWork',
GROUP_ESSENCE = 'ns-GroupEssence', GROUP_ESSENCE = 'ns-GroupEssence',
NODE_STORE_API = 'ns-NodeStoreApi' NODE_STORE_API = 'ns-NodeStoreApi',
QQ_EX_API = 'ns-QQEXApi',
} }
export enum NTMethod { export enum NTMethod {
@@ -108,13 +109,26 @@ interface InvokeOptions<ReturnType> {
timeout?: number timeout?: number
} }
let channel: NTChannel
function getChannel() {
if (channel) {
return channel
}
if (ipcMain.eventNames().includes(NTChannel.IPC_UP_2)) {
return channel = NTChannel.IPC_UP_2
} else {
return channel = NTChannel.IPC_UP_3
}
}
export function invoke< export function invoke<
R extends Awaited<ReturnType<Extract<NTService[S][M], (...args: any) => unknown>>>, 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: Extract<unknown, `${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 ?? getBuildVersion() >= 28788 ? NTChannel.IPC_UP_3 : NTChannel.IPC_UP_2 const channel = options.channel ?? getChannel()
const timeout = options.timeout ?? 5000 const timeout = options.timeout ?? 5000
const afterFirstCmd = options.afterFirstCmd ?? true const afterFirstCmd = options.afterFirstCmd ?? true
let eventName = className + '-' + channel[channel.length - 1] let eventName = className + '-' + channel[channel.length - 1]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -442,6 +442,7 @@ export interface RawMessage {
attrType: number attrType: number
attrId: string attrId: string
}> }>
isOnlineMsg: boolean
} }
export interface Peer { export interface Peer {

View File

@@ -23,8 +23,8 @@ interface FileResponse {
export class DownloadFile extends BaseAction<Payload, FileResponse> { export class DownloadFile extends BaseAction<Payload, FileResponse> {
actionName = ActionName.GoCQHTTP_DownloadFile actionName = ActionName.GoCQHTTP_DownloadFile
payloadSchema = Schema.object({ payloadSchema = Schema.object({
url: String, url: Schema.string(),
base64: String, base64: Schema.string(),
headers: Schema.union([String, Schema.array(String)]) headers: Schema.union([String, Schema.array(String)])
}) })

View File

@@ -12,6 +12,7 @@ interface Payload {
interface Response extends OB11User { interface Response extends OB11User {
reg_time: number reg_time: number
long_nick: string long_nick: string
city: string
} }
export class GetStrangerInfo extends BaseAction<Payload, Response> { export class GetStrangerInfo extends BaseAction<Payload, Response> {
@@ -33,7 +34,8 @@ export class GetStrangerInfo extends BaseAction<Payload, Response> {
level: data.detail.commonExt.qqLevel && calcQQLevel(data.detail.commonExt.qqLevel) || 0, level: data.detail.commonExt.qqLevel && calcQQLevel(data.detail.commonExt.qqLevel) || 0,
login_days: 0, login_days: 0,
reg_time: data.detail.commonExt.regTime, reg_time: data.detail.commonExt.regTime,
long_nick: data.detail.simpleInfo.baseInfo.longNick long_nick: data.detail.simpleInfo.baseInfo.longNick,
city: data.detail.commonExt.city
} }
} else { } else {
const data = await this.ctx.ntUserApi.getUserDetailInfoByUin(uin) const data = await this.ctx.ntUserApi.getUserDetailInfoByUin(uin)
@@ -46,7 +48,8 @@ export class GetStrangerInfo extends BaseAction<Payload, Response> {
level: data.info.qqLevel && calcQQLevel(data.info.qqLevel) || 0, level: data.info.qqLevel && calcQQLevel(data.info.qqLevel) || 0,
login_days: 0, login_days: 0,
reg_time: data.info.regTime, reg_time: data.info.regTime,
long_nick: data.info.longNick long_nick: data.info.longNick,
city: data.info.city
} }
} }
} }

View File

@@ -19,7 +19,7 @@ interface Payload {
interface Response { interface Response {
message_id: number message_id: number
forward_id?: string forward_id: string
} }
export class SendForwardMsg extends BaseAction<Payload, Response> { export class SendForwardMsg extends BaseAction<Payload, Response> {
@@ -60,14 +60,11 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
} }
} }
let msg: RawMessage
if (fake && this.ctx.app.native.activated) { if (fake && this.ctx.app.native.activated) {
msg = await this.handleFakeForwardNode(peer, nodes) return await this.handleFakeForwardNode(peer, nodes)
} else { } else {
msg = await this.handleForwardNode(peer, nodes) return await this.handleForwardNode(peer, nodes)
} }
const msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
return { message_id: msgShortId }
} }
private parseNodeContent(nodes: OB11MessageNode[]) { private parseNodeContent(nodes: OB11MessageNode[]) {
@@ -82,7 +79,7 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
}) })
} }
private async handleFakeForwardNode(peer: Peer, nodes: OB11MessageNode[]) { private async handleFakeForwardNode(peer: Peer, nodes: OB11MessageNode[]): Promise<Response> {
const encoder = new MessageEncoder(this.ctx, peer) const encoder = new MessageEncoder(this.ctx, peer)
const raw = await encoder.generate(nodes) const raw = await encoder.generate(nodes)
const transmit = Msg.PbMultiMsgTransmit.encode({ pbItemList: raw.multiMsgItems }).finish() const transmit = Msg.PbMultiMsgTransmit.encode({ pbItemList: raw.multiMsgItems }).finish()
@@ -122,7 +119,11 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
}) })
} }
}], 1800) }], 1800)
return msg! const msgShortId = this.ctx.store.createMsgShortId({
chatType: msg!.chatType,
peerUid: msg!.peerUid
}, msg!.msgId)
return { message_id: msgShortId, forward_id: resid }
} catch (e) { } catch (e) {
this.ctx.logger.error('合并转发失败', e) this.ctx.logger.error('合并转发失败', e)
throw new Error(`发送伪造合并转发消息失败 (res_id: ${resid} `) throw new Error(`发送伪造合并转发消息失败 (res_id: ${resid} `)
@@ -153,7 +154,7 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
} }
// 返回一个合并转发的消息id // 返回一个合并转发的消息id
private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) { private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<Response> {
const selfPeer = { const selfPeer = {
chatType: ChatType.C2C, chatType: ChatType.C2C,
peerUid: selfInfo.uid, peerUid: selfInfo.uid,
@@ -245,8 +246,13 @@ export class SendForwardMsg extends BaseAction<Payload, Response> {
if (retMsgIds.length === 0) { if (retMsgIds.length === 0) {
throw Error('转发消息失败,节点为空') throw Error('转发消息失败,节点为空')
} }
const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds) const msg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, retMsgIds)
return returnMsg const resid = JSON.parse(msg.elements[0].arkElement!.bytesData).meta.detail.resid
const msgShortId = this.ctx.store.createMsgShortId({
chatType: msg.chatType,
peerUid: msg.peerUid
}, msg.msgId)
return { message_id: msgShortId, forward_id: resid }
} }
} }

View File

@@ -0,0 +1,19 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars'
interface Payload {
group_id: number | string
}
export class SendGroupSign extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SendGroupSign
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
})
async _handle(payload: Payload) {
await this.ctx.app.packet.sendGroupSignPacket(selfInfo.uin, payload.group_id.toString())
return null
}
}

View File

@@ -0,0 +1,30 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars'
import { GroupMemberRole } from '@/ntqqapi/types'
interface Payload {
group_id: number | string
user_id: number | string
special_title?: string
}
export class SetGroupSpecialTitle extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SetGroupSpecialTitle
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
special_title: Schema.string()
})
async _handle(payload: Payload) {
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString(), payload.group_id.toString())
if (!uid) throw new Error(`用户${payload.user_id}的uid获取失败`)
const self = await this.ctx.ntGroupApi.getGroupMember(payload.group_id.toString(), selfInfo.uid, false)
if (self.role !== GroupMemberRole.Owner){
throw new Error(`不是群${payload.group_id}的群主,无法设置群头衔`)
}
await this.ctx.app.packet.sendSetSpecialTittlePacket(payload.group_id.toString(), uid, payload.special_title || "")
return null
}
}

View File

@@ -78,6 +78,13 @@ import { GroupPoke } from './llonebot/GroupPoke'
import { FriendPoke } from './llonebot/FriendPoke' import { FriendPoke } from './llonebot/FriendPoke'
import { GetGroupFileSystemInfo } from './go-cqhttp/GetGroupFileSystemInfo' import { GetGroupFileSystemInfo } from './go-cqhttp/GetGroupFileSystemInfo'
import { GetCredentials } from './system/GetCredentials' import { GetCredentials } from './system/GetCredentials'
import { SetGroupSpecialTitle } from '@/onebot11/action/go-cqhttp/SetGroupSpecialTitle'
import { SendGroupSign } from '@/onebot11/action/go-cqhttp/SendGroupSign'
import { SetRestart } from '@/onebot11/action/system/SetRestart'
import { SetFriendCategory } from '@/onebot11/action/llonebot/SetFriendCategory'
import { SetFriendRemark } from '@/onebot11/action/llonebot/SetFriendRemark'
import { SetGroupMsgMask } from '@/onebot11/action/llonebot/SetGroupMsgMask'
import { SetGroupRemark } from '@/onebot11/action/llonebot/SetGroupRemark'
export function initActionMap(adapter: Adapter) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@@ -98,6 +105,10 @@ export function initActionMap(adapter: Adapter) {
new GetRobotUinRange(adapter), new GetRobotUinRange(adapter),
new GroupPoke(adapter), new GroupPoke(adapter),
new FriendPoke(adapter), new FriendPoke(adapter),
new SetFriendCategory(adapter),
new SetFriendRemark(adapter),
new SetGroupMsgMask(adapter),
new SetGroupRemark(adapter),
// onebot11 // onebot11
new SendLike(adapter), new SendLike(adapter),
new GetMsg(adapter), new GetMsg(adapter),
@@ -131,6 +142,7 @@ export function initActionMap(adapter: Adapter) {
new ForwardFriendSingleMsg(adapter), new ForwardFriendSingleMsg(adapter),
new ForwardGroupSingleMsg(adapter), new ForwardGroupSingleMsg(adapter),
new GetCredentials(adapter), new GetCredentials(adapter),
new SetRestart(adapter),
// go-cqhttp // go-cqhttp
new GetEssenceMsgList(adapter), new GetEssenceMsgList(adapter),
new GetGroupHonorInfo(adapter), new GetGroupHonorInfo(adapter),
@@ -161,6 +173,8 @@ export function initActionMap(adapter: Adapter) {
new DeleteFriend(adapter), new DeleteFriend(adapter),
new OCRImage(adapter), new OCRImage(adapter),
new GetGroupFileSystemInfo(adapter), new GetGroupFileSystemInfo(adapter),
new SetGroupSpecialTitle(adapter),
new SendGroupSign(adapter),
] ]
const actionMap = new Map() const actionMap = new Map()
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

@@ -1,6 +1,5 @@
import { BaseAction, Schema } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils/misc'
interface Payload { interface Payload {
user_id: number | string user_id: number | string
@@ -13,13 +12,15 @@ export class FriendPoke extends BaseAction<Payload, null> {
}) })
async _handle(payload: Payload) { async _handle(payload: Payload) {
if (!this.ctx.app.native.checkPlatform()) { // if (!this.ctx.app.native.checkPlatform()) {
throw new Error('当前系统平台或架构不支持') // throw new Error('当前系统平台或架构不支持')
} // }
if (!this.ctx.app.native.checkVersion()) { // if (!this.ctx.app.native.checkVersion()) {
throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27333—27597`) // throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27333—27597`)
} // }
await this.ctx.app.native.sendFriendPoke(+payload.user_id) // await this.ctx.app.native.sendFriendPoke(+payload.user_id)
await this.ctx.app.packet.sendPokePacket(+payload.user_id)
return null return null
} }
} }

View File

@@ -1,6 +1,5 @@
import { BaseAction, Schema } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
@@ -15,13 +14,14 @@ export class GroupPoke extends BaseAction<Payload, null> {
}) })
async _handle(payload: Payload) { async _handle(payload: Payload) {
if (!this.ctx.app.native.checkPlatform()) { // if (!this.ctx.app.native.checkPlatform()) {
throw new Error('当前系统平台或架构不支持') // throw new Error('当前系统平台或架构不支持')
} // }
if (!this.ctx.app.native.checkVersion()) { // if (!this.ctx.app.native.checkVersion()) {
throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27333—27597`) // throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27333—27597`)
} // }
await this.ctx.app.native.sendGroupPoke(+payload.group_id, +payload.user_id) // await this.ctx.app.native.sendGroupPoke(+payload.group_id, +payload.user_id)
await this.ctx.app.packet.sendPokePacket(+payload.user_id, +payload.group_id)
return null return null
} }
} }

View File

@@ -0,0 +1,17 @@
import { BaseAction } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
user_id: string,
category_id: number
}
export class SetFriendCategory extends BaseAction<Payload, null> {
actionName = ActionName.SetFriendCategory
protected async _handle(payload: Payload): Promise<null> {
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!uid) throw new Error('无法获取好友信息')
return this.ctx.ntFriendApi.setBuddyCategory(uid, +payload.category_id)
}
}

View File

@@ -0,0 +1,17 @@
import { BaseAction } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
user_id: string,
remark?: string
}
export class SetFriendRemark extends BaseAction<Payload, null> {
actionName = ActionName.SetFriendRemark
protected async _handle(payload: Payload): Promise<null> {
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
if (!uid) throw new Error('无法获取好友信息')
return this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark || '')
}
}

View File

@@ -0,0 +1,16 @@
import { BaseAction } from '../BaseAction'
import { ActionName } from '../types'
import { GroupMsgMask } from '@/ntqqapi/api'
interface Payload {
group_id: string,
mask: GroupMsgMask
}
export class SetGroupMsgMask extends BaseAction<Payload, unknown> {
actionName = ActionName.SetGroupMsgMask
protected async _handle(payload: Payload): Promise<unknown>{
return this.ctx.ntGroupApi.setGroupMsgMask(payload.group_id.toString(), +payload.mask)
}
}

View File

@@ -0,0 +1,15 @@
import { BaseAction } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: string,
remark?: string
}
export class SetGroupRemark extends BaseAction<Payload, unknown> {
actionName = ActionName.SetGroupRemark
protected async _handle(payload: Payload): Promise<unknown>{
return this.ctx.ntGroupApi.setGroupRemark(payload.group_id.toString(), payload.remark)
}
}

View File

@@ -7,62 +7,52 @@ import { ChatCacheListItemBasic, CacheFileType } from '@/ntqqapi/types'
export default class CleanCache extends BaseAction<void, void> { export default class CleanCache extends BaseAction<void, void> {
actionName = ActionName.CleanCache actionName = ActionName.CleanCache
protected _handle(): Promise<void> { protected async _handle(): Promise<void> {
return new Promise<void>(async (res, rej) => { const cacheFilePaths: string[] = []
try {
// dbUtil.clearCache()
const cacheFilePaths: string[] = []
await this.ctx.ntFileCacheApi.setCacheSilentScan(false) await this.ctx.ntFileCacheApi.setCacheSilentScan(false)
cacheFilePaths.push(await this.ctx.ntFileCacheApi.getHotUpdateCachePath()) cacheFilePaths.push(await this.ctx.ntFileCacheApi.getHotUpdateCachePath())
cacheFilePaths.push(await this.ctx.ntFileCacheApi.getDesktopTmpPath()) cacheFilePaths.push(await this.ctx.ntFileCacheApi.getDesktopTmpPath())
const list = await this.ctx.ntFileCacheApi.getCacheSessionPathList() const list = await this.ctx.ntFileCacheApi.getCacheSessionPathList()
list.forEach((e) => cacheFilePaths.push(e.value)) list.forEach((e) => cacheFilePaths.push(e.value))
// await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知 // await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知
const cacheScanResult = await this.ctx.ntFileCacheApi.scanCache() const cacheScanResult = await this.ctx.ntFileCacheApi.scanCache()
const cacheSize = parseInt(cacheScanResult.size[6]) const cacheSize = parseInt(cacheScanResult.size[6])
if (cacheScanResult.result !== 0) { if (cacheScanResult.result !== 0) {
throw 'Something went wrong while scanning cache. Code: ' + cacheScanResult.result throw 'Something went wrong while scanning cache. Code: ' + cacheScanResult.result
} }
await this.ctx.ntFileCacheApi.setCacheSilentScan(true) await this.ctx.ntFileCacheApi.setCacheSilentScan(true)
if (cacheSize > 0 && cacheFilePaths.length > 2) { if (cacheSize > 0 && cacheFilePaths.length > 2) {
// 存在缓存文件且大小不为 0 时执行清理动作 // 存在缓存文件且大小不为 0 时执行清理动作
// await NTQQApi.clearCache([ 'tmp', 'hotUpdate', ...cacheScanResult ]) // XXX: 也是调用就崩溃,调用 fs 删除得了 // await NTQQApi.clearCache([ 'tmp', 'hotUpdate', ...cacheScanResult ]) // XXX: 也是调用就崩溃,调用 fs 删除得了
deleteCachePath(cacheFilePaths) deleteCachePath(cacheFilePaths)
} }
// 获取聊天记录列表 // 获取聊天记录列表
// NOTE: 以防有人不需要删除聊天记录,暂时先注释掉,日后加个开关 // NOTE: 以防有人不需要删除聊天记录,暂时先注释掉,日后加个开关
// const privateChatCache = await getCacheList(ChatType.friend); // 私聊消息 // const privateChatCache = await getCacheList(ChatType.friend); // 私聊消息
// const groupChatCache = await getCacheList(ChatType.group); // 群聊消息 // const groupChatCache = await getCacheList(ChatType.group); // 群聊消息
// const chatCacheList = [ ...privateChatCache, ...groupChatCache ]; // const chatCacheList = [ ...privateChatCache, ...groupChatCache ];
const chatCacheList: ChatCacheListItemBasic[] = [] const chatCacheList: ChatCacheListItemBasic[] = []
// 获取聊天缓存文件列表 // 获取聊天缓存文件列表
const cacheFileList: string[] = [] const cacheFileList: string[] = []
for (const name in CacheFileType) { for (const name in CacheFileType) {
if (!isNaN(parseInt(name))) continue if (!isNaN(parseInt(name))) continue
const fileTypeAny: any = CacheFileType[name] const fileType = CacheFileType[name] as unknown as CacheFileType
const fileType: CacheFileType = fileTypeAny
cacheFileList.push(...(await this.ctx.ntFileCacheApi.getFileCacheInfo(fileType)).infos.map((file) => file.fileKey)) cacheFileList.push(...(await this.ctx.ntFileCacheApi.getFileCacheInfo(fileType)).infos.map((file) => file.fileKey))
} }
// 一并清除 // 一并清除
await this.ctx.ntFileCacheApi.clearChatCache(chatCacheList, cacheFileList) await this.ctx.ntFileCacheApi.clearChatCache(chatCacheList, cacheFileList)
res()
} catch (e) {
console.error('清理缓存时发生了错误')
rej(e)
}
})
} }
} }

View File

@@ -0,0 +1,10 @@
import { BaseAction } from '@/onebot11/action/BaseAction'
import { ActionName } from '@/onebot11/action/types'
export class SetRestart extends BaseAction<null, void> {
actionName = ActionName.SetRestart
protected async _handle() {
await this.ctx.ntSystemApi.restart()
}
}

View File

@@ -29,6 +29,10 @@ export enum ActionName {
GetRobotUinRange = 'get_robot_uin_range', GetRobotUinRange = 'get_robot_uin_range',
GroupPoke = 'group_poke', GroupPoke = 'group_poke',
FriendPoke = 'friend_poke', FriendPoke = 'friend_poke',
SetFriendRemark = 'set_friend_remark',
SetFriendCategory = 'set_friend_category',
SetGroupMsgMask = 'set_group_msg_mask',
SetGroupRemark = 'set_group_remark',
// onebot 11 // onebot 11
SendLike = 'send_like', SendLike = 'send_like',
GetLoginInfo = 'get_login_info', GetLoginInfo = 'get_login_info',
@@ -47,6 +51,7 @@ export enum ActionName {
SetGroupLeave = 'set_group_leave', SetGroupLeave = 'set_group_leave',
GetVersionInfo = 'get_version_info', GetVersionInfo = 'get_version_info',
GetStatus = 'get_status', GetStatus = 'get_status',
SetRestart = 'set_restart',
CanSendRecord = 'can_send_record', CanSendRecord = 'can_send_record',
CanSendImage = 'can_send_image', CanSendImage = 'can_send_image',
SetGroupKick = 'set_group_kick', SetGroupKick = 'set_group_kick',
@@ -91,4 +96,6 @@ export enum ActionName {
GoCQHTTP_DeleteFriend = 'delete_friend', GoCQHTTP_DeleteFriend = 'delete_friend',
GoCQHTTP_OCRImage = 'ocr_image', GoCQHTTP_OCRImage = 'ocr_image',
GoCQHTTP_GetGroupFileSystemInfo = 'get_group_file_system_info', GoCQHTTP_GetGroupFileSystemInfo = 'get_group_file_system_info',
GoCQHTTP_SetGroupSpecialTitle = 'set_group_special_title',
GoCQHTTP_SendGroupSign = 'send_group_sign',
} }

View File

@@ -15,7 +15,6 @@ import { OB11Config, Config as LLOBConfig } from '../common/types'
import { OB11WebSocket, OB11WebSocketReverseManager } from './connect/ws' import { OB11WebSocket, OB11WebSocketReverseManager } from './connect/ws'
import { OB11Http, OB11HttpPost } from './connect/http' import { OB11Http, OB11HttpPost } from './connect/http'
import { OB11BaseEvent } from './event/OB11BaseEvent' import { OB11BaseEvent } from './event/OB11BaseEvent'
import { OB11Message } from './types'
import { OB11BaseMetaEvent } from './event/meta/OB11BaseMetaEvent' import { OB11BaseMetaEvent } from './event/meta/OB11BaseMetaEvent'
import { postHttpEvent } from './helper/eventForHttp' import { postHttpEvent } from './helper/eventForHttp'
import { initActionMap } from './action' import { initActionMap } from './action'
@@ -88,9 +87,9 @@ class OneBot11Adapter extends Service {
} }
} }
private async handleGroupNotify(notify: GroupNotify) { private async handleGroupNotify(notify: GroupNotify, doubt: boolean) {
try { try {
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type const flag = `${notify.group.groupCode}|${notify.seq}|${notify.type}|${doubt === true ? '1' : '0'}`
if ([GroupNotifyType.MemberLeaveNotifyAdmin, GroupNotifyType.KickMemberNotifyAdmin].includes(notify.type)) { if ([GroupNotifyType.MemberLeaveNotifyAdmin, GroupNotifyType.KickMemberNotifyAdmin].includes(notify.type)) {
if (notify.user2.uid) { if (notify.user2.uid) {
this.ctx.logger.info('有群成员被踢', notify.group.groupCode, notify.user1.uid, notify.user2.uid) this.ctx.logger.info('有群成员被踢', notify.group.groupCode, notify.user1.uid, notify.user2.uid)
@@ -338,7 +337,8 @@ class OneBot11Adapter extends Service {
this.handleMsg(input) this.handleMsg(input)
}) })
this.ctx.on('nt/group-notify', input => { this.ctx.on('nt/group-notify', input => {
this.handleGroupNotify(input) const { doubt, notify } = input
this.handleGroupNotify(notify, doubt)
}) })
this.ctx.on('nt/friend-request', input => { this.ctx.on('nt/friend-request', input => {
this.handleFriendRequest(input) this.handleFriendRequest(input)
@@ -370,12 +370,6 @@ class OneBot11Adapter extends Service {
const userId = Number(memberUin) const userId = Number(memberUin)
const event = new OB11GroupDecreaseEvent(tip.groupCode, userId, userId) const event = new OB11GroupDecreaseEvent(tip.groupCode, userId, userId)
this.dispatch(event) this.dispatch(event)
} else if (msgType === 87) {
const tip = SysMsg.GroupInvite.decode(sysMsg.body!.msgContent!)
this.ctx.logger.info('群成员增加', tip)
const operatorUin = await this.ctx.ntUserApi.getUinByUid(tip.operatorUid)
const event = new OB11GroupIncreaseEvent(tip.groupCode, +selfInfo.uin, +operatorUin, 'invite')
this.dispatch(event)
} }
}) })
} }

View File

@@ -478,6 +478,13 @@ export namespace OB11Entities {
) )
} }
} }
else if (groupElement.type === TipGroupElementType.MemberIncrease) {
const { memberUid, adminUid } = groupElement
if (memberUid !== selfInfo.uid) return
ctx.logger.info('收到群成员增加消息', groupElement)
const adminUin = adminUid ? await ctx.ntUserApi.getUinByUid(adminUid) : selfInfo.uin
return new OB11GroupIncreaseEvent(+msg.peerUid, +selfInfo.uin, +adminUin)
}
} }
else if (element.fileElement) { else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(+msg.peerUid, +msg.senderUin!, { return new OB11GroupUploadNoticeEvent(+msg.peerUid, +msg.senderUin!, {
@@ -520,7 +527,6 @@ export namespace OB11Entities {
ctx.logger.info('收到新人被邀请进群消息', xmlElement) ctx.logger.info('收到新人被邀请进群消息', xmlElement)
const invitor = xmlElement.templParam.get('invitor') const invitor = xmlElement.templParam.get('invitor')
const invitee = xmlElement.templParam.get('invitee') const invitee = xmlElement.templParam.get('invitee')
if (invitee === selfInfo.uin) return
if (invitor && invitee) { if (invitor && invitee) {
return new OB11GroupIncreaseEvent(+msg.peerUid, +invitee, +invitor, 'invite') return new OB11GroupIncreaseEvent(+msg.peerUid, +invitee, +invitor, 'invite')
} }

View File

@@ -174,7 +174,7 @@ export class MessageEncoder {
const { type, data } = segment const { type, data } = segment
if (type === OB11MessageDataType.Node) { if (type === OB11MessageDataType.Node) {
await this.render(data.content as OB11MessageData[]) await this.render(data.content as OB11MessageData[])
const id = data.id ?? data.user_id const id = data.uin ?? data.user_id
this.uin = id ? +id : undefined this.uin = id ? +id : undefined
this.name = data.name ?? data.nickname this.name = data.name ?? data.nickname
await this.flush() await this.flush()
@@ -221,7 +221,7 @@ export class MessageEncoder {
} }
} }
async generate(content: any[]) { async generate(content: OB11MessageData[]) {
await this.render(content) await this.render(content)
return { return {
multiMsgItems: [{ multiMsgItems: [{

View File

@@ -11,6 +11,10 @@ function isEmpty(value: unknown) {
} }
async function onSettingWindowCreated(view: Element) { async function onSettingWindowCreated(view: Element) {
console.log(view)
if (!view){
return
}
const config = await window.llonebot.getConfig() const config = await window.llonebot.getConfig()
const ob11Config = { ...config.ob11 } const ob11Config = { ...config.ob11 }
@@ -247,7 +251,9 @@ async function onSettingWindowCreated(view: Element) {
} else { } else {
errDom?.classList.add('show') errDom?.classList.add('show')
} }
errCodeDom!.innerHTML = errMsg if (errCodeDom) {
errCodeDom.innerHTML = errMsg
}
} }
showError().then() showError().then()
@@ -456,8 +462,8 @@ async function onSettingWindowCreated(view: Element) {
} }
window.llonebot.checkVersion().then(checkVersionFunc) window.llonebot.checkVersion().then(checkVersionFunc)
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
window.llonebot.getConfig().then(oldConfig=>{ window.llonebot.getConfig().then(oldConfig => {
if(JSON.stringify(oldConfig) !== JSON.stringify(config)){ if (JSON.stringify(oldConfig) !== JSON.stringify(config)) {
window.llonebot.setConfig(true, config) window.llonebot.setConfig(true, config)
} }
}) })

View File

@@ -91,7 +91,7 @@ class SatoriAdapter extends Service {
input.subMsgType === 12 && input.subMsgType === 12 &&
input.elements[0]?.grayTipElement?.xmlElement?.templId === '10382' input.elements[0]?.grayTipElement?.xmlElement?.templId === '10382'
) { ) {
// 机器人被表情回应
} }
else { else {
// 普通的消息 // 普通的消息
@@ -99,7 +99,7 @@ class SatoriAdapter extends Service {
} }
} }
async handleGroupNotify(input: NT.GroupNotify) { async handleGroupNotify(input: NT.GroupNotify, doubt: boolean) {
if ( if (
input.type === NT.GroupNotifyType.InvitedByMember && input.type === NT.GroupNotifyType.InvitedByMember &&
input.status === NT.GroupNotifyStatus.Unhandle input.status === NT.GroupNotifyStatus.Unhandle
@@ -119,14 +119,14 @@ class SatoriAdapter extends Service {
input.status === NT.GroupNotifyStatus.Unhandle input.status === NT.GroupNotifyStatus.Unhandle
) { ) {
// 他人主动申请,需管理员同意 // 他人主动申请,需管理员同意
return await parseGuildMemberRequest(this, input) return await parseGuildMemberRequest(this, input, doubt)
} }
else if ( else if (
input.type === NT.GroupNotifyType.InvitedNeedAdminiStratorPass && input.type === NT.GroupNotifyType.InvitedNeedAdminiStratorPass &&
input.status === NT.GroupNotifyStatus.Unhandle input.status === NT.GroupNotifyStatus.Unhandle
) { ) {
// 他人被邀请,需管理员同意 // 他人被邀请,需管理员同意
return await parseGuildMemberRequest(this, input) return await parseGuildMemberRequest(this, input, doubt)
} }
} }
@@ -140,7 +140,8 @@ class SatoriAdapter extends Service {
}) })
this.ctx.on('nt/group-notify', async input => { this.ctx.on('nt/group-notify', async input => {
const event = await this.handleGroupNotify(input) const { doubt, notify } = input
const event = await this.handleGroupNotify(notify, doubt)
.catch(e => this.ctx.logger.error(e)) .catch(e => this.ctx.logger.error(e))
event && this.server.dispatch(event) event && this.server.dispatch(event)
}) })

View File

@@ -46,9 +46,9 @@ export async function parseGuildMemberRemoved(bot: SatoriAdapter, input: GroupNo
}) })
} }
export async function parseGuildMemberRequest(bot: SatoriAdapter, input: GroupNotify) { export async function parseGuildMemberRequest(bot: SatoriAdapter, input: GroupNotify, doubt: boolean) {
const groupCode = input.group.groupCode const groupCode = input.group.groupCode
const flag = groupCode + '|' + input.seq + '|' + input.type const flag = `${groupCode}|${input.seq}|${input.type}|${doubt === true ? '1' : '0'}`
return bot.event('guild-member-request', { return bot.event('guild-member-request', {
guild: decodeGuild(input.group), guild: decodeGuild(input.group),

View File

@@ -1 +1 @@
export const version = '4.1.0' export const version = '4.3.0'