Compare commits

..

10 Commits

Author SHA1 Message Date
idranme
a4beeba528 Merge pull request #474 from LLOneBot/dev
release: 4.0.9
2024-10-13 23:51:30 +08:00
idranme
c837e970df chore: bump versions 2024-10-13 23:48:50 +08:00
idranme
4ced7fa3cf fix 2024-10-13 23:48:22 +08:00
idranme
dbd71d4376 chore 2024-10-13 23:42:01 +08:00
idranme
d43612b2a3 feat(satori): support for receiving llonebot:ark element 2024-10-13 20:31:20 +08:00
idranme
31ad0195d8 fix(onebot): get_group_msg_history API 2024-10-13 19:52:29 +08:00
idranme
9b32140f87 Merge pull request #470 from LLOneBot/dev
release: 4.0.8
2024-10-13 16:36:12 +08:00
idranme
dc5982c6b2 chore: bump versions 2024-10-13 16:34:35 +08:00
idranme
ce46c99330 fix 2024-10-13 16:32:39 +08:00
idranme
d3abaf806f optimize 2024-10-13 16:32:18 +08:00
16 changed files with 78 additions and 36 deletions

View File

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

View File

@@ -58,7 +58,7 @@ export class NTQQFriendApi extends Service {
return Object.values(data.userSimpleInfos).filter(v => uids.includes(v.uid!))
}
/** uid => uin */
/** uid -> uin */
async getBuddyIdMap(refresh = false): Promise<Map<string, string>> {
const retMap: Map<string, string> = new Map()
const data = await invoke<{

View File

@@ -45,8 +45,11 @@ export class NTQQGroupApi extends Service {
return result.groupList
}
async getGroupMembers(groupCode: string, num = 3000): Promise<Map<string, GroupMember>> {
const sceneId = await invoke(NTMethod.GROUP_MEMBER_SCENE, [{ groupCode, scene: 'groupMemberList_MainWindow' }])
async getGroupMembers(groupCode: string, num = 3000) {
const sceneId = await invoke(NTMethod.GROUP_MEMBER_SCENE, [{
groupCode,
scene: 'groupMemberList_MainWindow'
}])
const data = await invoke(NTMethod.GROUP_MEMBERS, [{ sceneId, num }])
if (data.errCode !== 0) {
throw new Error('获取群成员列表出错,' + data.errMsg)

View File

@@ -52,7 +52,7 @@ export class NTQQMsgApi extends Service {
}
async getMsgHistory(peer: Peer, msgId: string, cnt: number, queryOrder = false) {
// 默认情况下消息时间从旧到
// 默认情况下消息时间从新到旧
return await invoke(NTMethod.HISTORY_MSG, [{ peer, msgId, cnt, queryOrder }])
}
@@ -93,7 +93,7 @@ export class NTQQMsgApi extends Service {
const uniqueId = await this.generateMsgUniqueId(destPeer.chatType)
destPeer.guildId = uniqueId
const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/forwardMsg',
'nodeIKernelMsgService/forwardMsgWithComment',
[{
msgIds,
srcContact: srcPeer,
@@ -111,7 +111,8 @@ export class NTQQMsgApi extends Service {
}
}
return false
}
},
timeout: 3000
}
)
delete destPeer.guildId

View File

@@ -118,7 +118,7 @@ export class NTQQUserApi extends Service {
await this.ctx.sleep(30)
member = await this.ctx.ntGroupApi.searchMember(groupCode, uin)
}
uid = member.values().find(e => e.uin === uin)?.uid
uid = Array.from(member.values()).find(e => e.uin === uin)?.uid
}
if (!uid) {
const unveifyUid = (await this.getUserDetailInfoByUin(uin)).info.uid
@@ -178,7 +178,7 @@ export class NTQQUserApi extends Service {
if (uin) return uin
uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])).uinInfo.get(uid)
if (uin) return uin
uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).get(uid)
uin = (await this.ctx.ntFriendApi.getBuddyIdMap()).get(uid)
if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin
return uin

View File

@@ -1,7 +1,9 @@
import { Context } from 'cordis'
import { Dict } from 'cosmokit'
import { getBuildVersion } from '@/common/utils/misc'
// @ts-expect-error: Unreachable code error
import { TEMP_DIR } from '@/common/globalVars'
import { copyFile } from 'fs/promises'
import path from 'node:path'
import addon from './external/crychic-win32-x64.node?asset'
export class Native {
@@ -19,11 +21,11 @@ export class Native {
checkVersion() {
const version = getBuildVersion()
// 27187—27597
return version >= 27187 && version < 28060
// 27333—27597
return version >= 27333 && version < 28060
}
start() {
async start() {
if (this.crychic) {
return
}
@@ -34,8 +36,11 @@ export class Native {
return
}
try {
this.crychic = require(addon)
this.crychic.init()
const fileName = path.basename(addon)
const dest = path.join(TEMP_DIR, fileName)
await copyFile(addon, dest)
this.crychic = require(dest)
this.crychic!.init()
} catch (e) {
this.ctx.logger.warn('crychic 加载失败', e)
}

View File

@@ -124,7 +124,12 @@ export function invoke<
return new Promise<R>((resolve, reject) => {
const apiArgs = [method, ...args]
const callbackId = randomUUID()
let eventId: string
const timeoutId = setTimeout(() => {
if (eventId) {
removeReceiveHook(eventId)
}
log(`ntqq api timeout ${channel}, ${eventName}, ${method}`, args)
reject(`ntqq api timeout ${channel}, ${eventName}, ${method}, ${JSON.stringify(args)}`)
}, timeout)
@@ -140,16 +145,16 @@ export function invoke<
let result: unknown
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => {
const hookId = registerReceiveHook<R>(options.cbCmd!, (payload) => {
eventId = registerReceiveHook<R>(options.cbCmd!, (payload) => {
if (options.cmdCB) {
if (options.cmdCB(payload, result)) {
removeReceiveHook(hookId)
removeReceiveHook(eventId)
clearTimeout(timeoutId)
resolve(payload)
}
}
else {
removeReceiveHook(hookId)
removeReceiveHook(eventId)
clearTimeout(timeoutId)
resolve(payload)
}

View File

@@ -294,6 +294,9 @@ export interface GrayTipElement {
jsonGrayTipElement?: {
busiId: string
jsonStr: string
xmlToJsonParam?: {
templParam: Map<string, string>
}
}
}

View File

@@ -30,12 +30,12 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
const { count, reverseOrder } = payload
const peer = { chatType: ChatType.Group, peerUid: payload.group_id.toString() }
let msgList: RawMessage[]
if (!payload.message_seq || payload.message_seq === '0') {
if (!payload.message_seq || +payload.message_seq === 0) {
msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList
} else {
const startMsgId = (await this.ctx.store.getMsgInfoByShortId(+payload.message_seq))?.msgId
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, true)).msgList
}
if (!msgList?.length) throw new Error('未找到消息')
if (reverseOrder) msgList.reverse()

View File

@@ -17,7 +17,10 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
protected async _handle(payload: Payload) {
const groupCode = payload.group_id.toString()
let groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
if (groupMembers.size === 0) {
for (let i = 0; i < 5; i++) {
if (groupMembers.size > 0) {
break
}
await this.ctx.sleep(100)
groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
}

View File

@@ -17,7 +17,7 @@ export class FriendPoke extends BaseAction<Payload, null> {
throw new Error('当前系统平台或架构不支持')
}
if (!this.ctx.app.native.checkVersion()) {
throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27187—27597`)
throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27333—27597`)
}
await this.ctx.app.native.sendFriendPoke(+payload.user_id)
return null

View File

@@ -19,7 +19,7 @@ export class GroupPoke extends BaseAction<Payload, null> {
throw new Error('当前系统平台或架构不支持')
}
if (!this.ctx.app.native.checkVersion()) {
throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27187—27597`)
throw new Error(`当前 QQ 版本 ${getBuildVersion()} 不支持,可尝试其他版本 27333—27597`)
}
await this.ctx.app.native.sendGroupPoke(+payload.group_id, +payload.user_id)
return null

View File

@@ -79,11 +79,14 @@ export namespace OB11Entities {
if (msg.chatType === ChatType.Group) {
resMsg.sub_type = 'normal'
resMsg.group_id = parseInt(msg.peerUin)
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUid)
if (member) {
resMsg.sender.role = groupMemberRole(member.role)
resMsg.sender.nickname = member.nick
resMsg.sender.title = member.memberSpecialTitle ?? ''
// 284840486: 合并转发内部
if (msg.peerUin !== '284840486') {
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUin, msg.senderUid)
if (member) {
resMsg.sender.role = groupMemberRole(member.role)
resMsg.sender.nickname = member.nick
resMsg.sender.title = member.memberSpecialTitle ?? ''
}
}
}
else if (msg.chatType === ChatType.C2C) {
@@ -548,14 +551,23 @@ export namespace OB11Entities {
else if (grayTipElement.subElementType == GrayTipElementSubType.JSON) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement!.jsonStr)
if (grayTipElement.jsonGrayTipElement?.busiId === '1061') {
const param = grayTipElement.jsonGrayTipElement.xmlToJsonParam
if (param) {
return new OB11GroupPokeEvent(
Number(msg.peerUid),
Number(param.templParam.get('uin_str1')),
Number(param.templParam.get('uin_str2')),
json.items
)
}
const pokedetail: Dict[] = json.items
//筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid)
if (poke_uid.length == 2) {
return new OB11GroupPokeEvent(
parseInt(msg.peerUid),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid) ?? 0),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid) ?? 0),
Number(msg.peerUid),
Number(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid) ?? 0),
Number(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid) ?? 0),
pokedetail
)
}
@@ -665,11 +677,13 @@ export namespace OB11Entities {
}
export function groupMember(group_id: string, member: GroupMember): OB11GroupMember {
const titleExpireTime = +member.specialTitleExpireTime
const int32Max = Math.pow(2, 31) - 1
return {
group_id: parseInt(group_id),
user_id: parseInt(member.uin),
nickname: member.nick,
card: member.cardName,
card: member.cardName || member.nick,
sex: OB11UserSex.Unknown,
age: 0,
area: '',
@@ -677,7 +691,7 @@ export namespace OB11Entities {
qq_level: 0,
join_time: member.joinTime,
last_sent_time: member.lastSpeakTime,
title_expire_time: +member.specialTitleExpireTime,
title_expire_time: titleExpireTime > int32Max ? 0 : titleExpireTime,
unfriendly: false,
card_changeable: true,
is_robot: member.isRobot,

View File

@@ -129,6 +129,11 @@ async function decodeElement(ctx: Context, data: NT.RawMessage, quoted = false)
type: String(faceType),
platform: 'llonebot'
}))
} else if (v.arkElement) {
// llonebot:ark
buffer.push(h('llonebot:ark', {
data: v.arkElement.bytesData
}))
}
}
return buffer

View File

@@ -1 +1 @@
export const version = '4.0.7'
export const version = '4.0.9'

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ESNext",
"target": "ES2023",
"module": "CommonJS",
"outDir": "./dist",
"strict": true,
@@ -20,7 +20,10 @@
"./src/ntqqapi/*"
]
},
"noEmit": true
"noEmit": true,
"types": [
"electron-vite/node"
]
},
"include": [
"src",