Merge pull request #479 from LLOneBot/dev

release: 4.0.12
This commit is contained in:
idranme 2024-10-18 21:16:46 +08:00 committed by GitHub
commit 8b89fd7a0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 130 additions and 117 deletions

View File

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

View File

@ -62,21 +62,35 @@ export class NTQQMsgApi extends Service {
async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) { async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) {
const uniqueId = await this.generateMsgUniqueId(peer.chatType) const uniqueId = await this.generateMsgUniqueId(peer.chatType)
peer.guildId = uniqueId const msgAttributeInfos = new Map()
msgAttributeInfos.set(0, {
attrType: 0,
attrId: uniqueId,
vasMsgInfo: {
msgNamePlateInfo: {},
bubbleInfo: {},
avatarPendantInfo: {},
vasFont: {},
iceBreakInfo: {}
}
})
let sentMsgId: string
const data = await invoke<{ msgList: RawMessage[] }>( const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/sendMsg', 'nodeIKernelMsgService/sendMsg',
[{ [{
msgId: '0', msgId: '0',
peer, peer,
msgElements, msgElements,
msgAttributeInfos: new Map() msgAttributeInfos
}], }],
{ {
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate', cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false, afterFirstCmd: false,
cmdCB: payload => { cmdCB: payload => {
for (const msgRecord of payload.msgList) { for (const msgRecord of payload.msgList) {
if (msgRecord.guildId === uniqueId && msgRecord.sendStatus === 2) { if (msgRecord.msgAttrs.get(0)?.attrId === uniqueId && msgRecord.sendStatus === 2) {
sentMsgId = msgRecord.msgId
return true return true
} }
} }
@ -85,8 +99,8 @@ export class NTQQMsgApi extends Service {
timeout timeout
} }
) )
delete peer.guildId
return data.msgList.find(msgRecord => msgRecord.guildId === uniqueId) return data.msgList.find(msgRecord => msgRecord.msgId === sentMsgId)
} }
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {

View File

@ -173,9 +173,10 @@ class Core extends Service {
}) })
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => { registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => {
const startTime = this.startTime / 1000
for (const message of payload.msgList) { for (const message of payload.msgList) {
// 过滤启动之前的消息 // 过滤启动之前的消息
if (parseInt(message.msgTime) < this.startTime / 1000) { if (parseInt(message.msgTime) < startTime) {
continue continue
} }
if (message.senderUin && message.senderUin !== '0') { if (message.senderUin && message.senderUin !== '0') {
@ -202,7 +203,9 @@ class Core extends Service {
this.ctx.parallel('nt/message-deleted', msg) this.ctx.parallel('nt/message-deleted', msg)
} else if (sentMsgIds.get(msg.msgId)) { } else if (sentMsgIds.get(msg.msgId)) {
sentMsgIds.delete(msg.msgId) sentMsgIds.delete(msg.msgId)
this.ctx.parallel('nt/message-sent', msg) if (msg.sendStatus === 2) {
this.ctx.parallel('nt/message-sent', msg)
}
} }
} }
}) })
@ -211,7 +214,7 @@ class Core extends Service {
sentMsgIds.set(payload.msgRecord.msgId, true) sentMsgIds.set(payload.msgRecord.msgId, true)
}) })
const groupNotifyFlags: string[] = [] const groupNotifyIgnore: string[] = []
registerReceiveHook<{ registerReceiveHook<{
doubt: boolean doubt: boolean
oldestUnreadSeq: string oldestUnreadSeq: string
@ -225,13 +228,11 @@ class Core extends Service {
return return
} }
for (const notify of notifies) { for (const notify of notifies) {
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type const notifyTime = Math.trunc(+notify.seq / 1000)
const notifyTime = parseInt(notify.seq) / 1000 if (groupNotifyIgnore.includes(notify.seq) || notifyTime < this.startTime) {
if (groupNotifyFlags.includes(flag) || notifyTime < this.startTime) {
continue continue
} }
groupNotifyFlags.shift() groupNotifyIgnore.push(notify.seq)
groupNotifyFlags.push(flag)
this.ctx.parallel('nt/group-notify', notify) this.ctx.parallel('nt/group-notify', notify)
} }
} }

View File

@ -29,11 +29,10 @@ export enum ReceiveCmdS {
const logHook = false const logHook = false
const receiveHooks: Array<{ const receiveHooks: Map<string, {
method: ReceiveCmdS[] method: ReceiveCmdS[]
hookFunc: (payload: any) => void | Promise<void> hookFunc: (payload: any) => void | Promise<void>
id: string }> = new Map()
}> = []
const callHooks: Array<{ const callHooks: Array<{
method: NTMethod[] method: NTMethod[]
@ -72,7 +71,7 @@ export function startHook() {
} }
} else if (args[2]) { } else if (args[2]) {
for (const receiveData of args[2]) { for (const receiveData of args[2]) {
for (const hook of receiveHooks) { for (const hook of receiveHooks.values()) {
if (hook.method.includes(receiveData.cmdName)) { if (hook.method.includes(receiveData.cmdName)) {
Promise.resolve(hook.hookFunc(receiveData.payload)) Promise.resolve(hook.hookFunc(receiveData.payload))
} }
@ -106,10 +105,9 @@ export function registerReceiveHook<PayloadType>(
if (!Array.isArray(method)) { if (!Array.isArray(method)) {
method = [method] method = [method]
} }
receiveHooks.push({ receiveHooks.set(id, {
method: method as ReceiveCmdS[], method: method as ReceiveCmdS[],
hookFunc, hookFunc,
id,
}) })
return id return id
} }
@ -128,6 +126,5 @@ export function registerCallHook(
} }
export function removeReceiveHook(id: string) { export function removeReceiveHook(id: string) {
const index = receiveHooks.findIndex((h) => h.id === id) receiveHooks.delete(id)
receiveHooks.splice(index, 1)
} }

View File

@ -147,17 +147,13 @@ export function invoke<
const secondCallback = () => { const secondCallback = () => {
eventId = registerReceiveHook<R>(options.cbCmd!, (payload) => { eventId = registerReceiveHook<R>(options.cbCmd!, (payload) => {
if (options.cmdCB) { if (options.cmdCB) {
if (options.cmdCB(payload, result)) { if (!options.cmdCB(payload, result)) {
removeReceiveHook(eventId) return
clearTimeout(timeoutId)
resolve(payload)
} }
} }
else { removeReceiveHook(eventId)
removeReceiveHook(eventId) clearTimeout(timeoutId)
clearTimeout(timeoutId) resolve(payload)
resolve(payload)
}
}) })
} }
!afterFirstCmd && secondCallback() !afterFirstCmd && secondCallback()
@ -167,9 +163,12 @@ export function invoke<
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback()
} }
else { else {
log('ntqq api call failed,', method, args, res)
clearTimeout(timeoutId) clearTimeout(timeoutId)
reject(`ntqq api call failed, ${method}, ${res?.errMsg}`) if (eventId) {
removeReceiveHook(eventId)
}
log('ntqq api call failed,', method, args, res)
reject(`ntqq api call failed, ${method}, ${JSON.stringify(res)}`)
} }
} }
} }

View File

@ -438,6 +438,10 @@ export interface RawMessage {
likesCnt: string likesCnt: string
isClicked: boolean isClicked: boolean
}[] }[]
msgAttrs: Map<number, {
attrType: number
attrId: string
}>
} }
export interface Peer { export interface Peer {

View File

@ -296,7 +296,7 @@ export interface UserDetailInfoByUin {
birthday_year: number birthday_year: number
birthday_month: number birthday_month: number
birthday_day: number birthday_day: number
sex: number //0 sex: number
topTime: string topTime: string
constellation: number constellation: number
shengXiao: number shengXiao: number

View File

@ -3,59 +3,51 @@ import { OB11User } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
import { getBuildVersion } from '@/common/utils' import { getBuildVersion } from '@/common/utils'
import { OB11UserSex } from '../../types'
import { calcQQLevel } from '@/common/utils/misc' import { calcQQLevel } from '@/common/utils/misc'
interface Payload { interface Payload {
user_id: number | string user_id: number | string
} }
export class GetStrangerInfo extends BaseAction<Payload, OB11User> { interface Response extends OB11User {
reg_time: number
long_nick: string
}
export class GetStrangerInfo extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetStrangerInfo actionName = ActionName.GoCQHTTP_GetStrangerInfo
payloadSchema = Schema.object({ payloadSchema = Schema.object({
user_id: Schema.union([Number, String]).required() user_id: Schema.union([Number, String]).required()
}) })
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload) {
if (!(getBuildVersion() >= 26702)) { const uin = payload.user_id.toString()
const user_id = payload.user_id.toString() if (getBuildVersion() >= 26702) {
const extendData = await this.ctx.ntUserApi.getUserDetailInfoByUin(user_id) const data = await this.ctx.ntUserApi.getUserDetailInfoByUinV2(uin)
const uid = (await this.ctx.ntUserApi.getUidByUin(user_id))! return {
if (!uid || uid.indexOf('*') != -1) { user_id: parseInt(data.detail.uin) || 0,
const ret = { nickname: data.detail.simpleInfo.coreInfo.nick,
...extendData, sex: OB11Entities.sex(data.detail.simpleInfo.baseInfo.sex),
user_id: parseInt(extendData.info.uin) || 0, age: data.detail.simpleInfo.baseInfo.age,
nickname: extendData.info.nick, qid: data.detail.simpleInfo.baseInfo.qid,
sex: OB11UserSex.Unknown, level: data.detail.commonExt.qqLevel && calcQQLevel(data.detail.commonExt.qqLevel) || 0,
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year, login_days: 0,
qid: extendData.info.qid, reg_time: data.detail.commonExt.regTime,
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0, long_nick: data.detail.simpleInfo.baseInfo.longNick
login_days: 0,
uid: ''
}
return ret
} }
const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) }
return OB11Entities.stranger(data)
} else { } else {
const user_id = payload.user_id.toString() const data = await this.ctx.ntUserApi.getUserDetailInfoByUin(uin)
const extendData = await this.ctx.ntUserApi.getUserDetailInfoByUinV2(user_id) return {
const uid = (await this.ctx.ntUserApi.getUidByUin(user_id))! user_id: parseInt(data.info.uin) || 0,
if (!uid || uid.indexOf('*') != -1) { nickname: data.info.nick,
const ret = { sex: OB11Entities.sex(data.info.sex),
...extendData, age: data.info.birthday_year === 0 ? 0 : new Date().getFullYear() - data.info.birthday_year,
user_id: parseInt(extendData.detail.uin) || 0, qid: data.info.qid,
nickname: extendData.detail.simpleInfo.coreInfo.nick, level: data.info.qqLevel && calcQQLevel(data.info.qqLevel) || 0,
sex: OB11UserSex.Unknown, login_days: 0,
age: 0, reg_time: data.info.regTime,
level: extendData.detail.commonExt.qqLevel && calcQQLevel(extendData.detail.commonExt.qqLevel) || 0, long_nick: data.info.longNick
login_days: 0,
uid: ''
}
return ret
} }
const data = { ...extendData, ...(await this.ctx.ntUserApi.getUserDetailInfo(uid)) }
return OB11Entities.stranger(data)
} }
} }
} }

View File

@ -27,7 +27,11 @@ export class UploadGroupFile extends BaseAction<Payload, null> {
if (!success) { if (!success) {
throw new Error(errMsg) throw new Error(errMsg)
} }
const file = await SendElement.file(this.ctx, path, payload.name || fileName, payload.folder ?? payload.folder_id) const name = payload.name || fileName
if (name.includes('/') || name.includes('\\')) {
throw new Error(`文件名 ${name} 不合法`)
}
const file = await SendElement.file(this.ctx, path, name, payload.folder ?? payload.folder_id)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group) const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
await sendMsg(this.ctx, peer, [file], []) await sendMsg(this.ctx, peer, [file], [])
return null return null

View File

@ -23,7 +23,11 @@ export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null
if (!success) { if (!success) {
throw new Error(errMsg) throw new Error(errMsg)
} }
const sendFileEle = await SendElement.file(this.ctx, path, payload.name || fileName) const name = payload.name || fileName
if (name.includes('/') || name.includes('\\')) {
throw new Error(`文件名 ${name} 不合法`)
}
const sendFileEle = await SendElement.file(this.ctx, path, name)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private) const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
await sendMsg(this.ctx, peer, [sendFileEle], []) await sendMsg(this.ctx, peer, [sendFileEle], [])
return null return null

View File

@ -24,13 +24,13 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
if (!uid) throw new Error('无法获取用户信息') if (!uid) throw new Error('无法获取用户信息')
const member = await this.ctx.ntGroupApi.getGroupMember(groupCode, uid, payload.no_cache) const member = await this.ctx.ntGroupApi.getGroupMember(groupCode, uid, payload.no_cache)
if (member) { if (member) {
const ret = OB11Entities.groupMember(groupCode, member) const ret = OB11Entities.groupMember(+groupCode, member)
const date = Math.round(Date.now() / 1000) const date = Math.trunc(Date.now() / 1000)
ret.last_sent_time ??= date ret.last_sent_time ??= date
ret.join_time ??= date ret.join_time ??= date
const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid) const info = await this.ctx.ntUserApi.getUserDetailInfo(member.uid)
ret.sex = OB11Entities.sex(info.sex!) ret.sex = OB11Entities.sex(info.sex!)
ret.qq_level = (info.qqLevel && calcQQLevel(info.qqLevel)) || 0 ret.qq_level = info.qqLevel && calcQQLevel(info.qqLevel) || 0
ret.age = info.age ?? 0 ret.age = info.age ?? 0
return ret return ret
} }

View File

@ -21,18 +21,22 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
if (groupMembers.size > 0) { if (groupMembers.size > 0) {
break break
} }
await this.ctx.sleep(100) await this.ctx.sleep(60)
groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode) groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode)
} }
const groupMembersArr = Array.from(groupMembers.values())
const date = Math.round(Date.now() / 1000)
return groupMembersArr.map(item => { const date = Math.trunc(Date.now() / 1000)
const member = OB11Entities.groupMember(groupCode, item) const groupId = Number(payload.group_id)
const ret: OB11GroupMember[] = []
for (const item of groupMembers.values()) {
const member = OB11Entities.groupMember(groupId, item)
member.join_time ??= date member.join_time ??= date
member.last_sent_time ??= date member.last_sent_time ??= date
return member ret.push(member)
}) }
return ret
} }
} }

View File

@ -23,11 +23,11 @@ export default class SetFriendAddRequest extends BaseAction<Payload, null> {
if (payload.remark) { if (payload.remark) {
await this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark) await this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark)
} }
await this.ctx.ntMsgApi.activateChat({ /*await this.ctx.ntMsgApi.activateChat({
peerUid: uid, peerUid: uid,
chatType: ChatType.C2C, chatType: ChatType.C2C,
guildId: '' guildId: ''
}) })*/
return null return null
} }
} }

View File

@ -155,7 +155,7 @@ class OB11HttpPost {
} }
public async emitEvent(event: OB11BaseEvent | OB11Message) { public async emitEvent(event: OB11BaseEvent | OB11Message) {
if (!this.activated) { if (!this.activated || !this.config.hosts.length) {
return return
} }
const msgStr = JSON.stringify(event) const msgStr = JSON.stringify(event)

View File

@ -71,7 +71,7 @@ export namespace OB11Entities {
sub_type: 'friend', sub_type: 'friend',
message: messagePostFormat === 'string' ? '' : [], message: messagePostFormat === 'string' ? '' : [],
message_format: messagePostFormat === 'string' ? 'string' : 'array', message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: selfUin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, post_type: selfUin === msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
} }
if (debug) { if (debug) {
resMsg.raw = msg resMsg.raw = msg
@ -376,11 +376,23 @@ export namespace OB11Entities {
if (msg.chatType !== ChatType.C2C) { if (msg.chatType !== ChatType.C2C) {
return return
} }
if (msg.msgType !== 5) {
return
}
for (const element of msg.elements) { for (const element of msg.elements) {
if (element.grayTipElement) { if (element.grayTipElement) {
const { grayTipElement } = element const { grayTipElement } = element
if (grayTipElement.jsonGrayTipElement?.busiId === '1061') { if (grayTipElement.jsonGrayTipElement?.busiId === '1061') {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr) const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
const param = grayTipElement.jsonGrayTipElement.xmlToJsonParam
if (param) {
return new OB11FriendPokeEvent(
Number(param.templParam.get('uin_str1')),
Number(param.templParam.get('uin_str2')),
json.items
)
}
const pokedetail: Dict[] = json.items const pokedetail: Dict[] = json.items
//筛选item带有uid的元素 //筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid) const poke_uid = pokedetail.filter(item => item.uid)
@ -405,31 +417,15 @@ export namespace OB11Entities {
if (msg.chatType !== ChatType.Group) { if (msg.chatType !== ChatType.Group) {
return return
} }
/**if (msg.senderUin) { if (msg.msgType !== 5 && msg.msgType !== 3) {
const member = await ctx.ntGroupApi.getGroupMember(msg.peerUid, msg.senderUin) return
if (member && member.cardName !== msg.sendMemberName) { }
const event = new OB11GroupCardEvent(
parseInt(msg.peerUid),
parseInt(msg.senderUin),
msg.sendMemberName!,
member.cardName,
)
member.cardName = msg.sendMemberName!
return event
}
}*/
for (const element of msg.elements) { for (const element of msg.elements) {
const grayTipElement = element.grayTipElement const grayTipElement = element.grayTipElement
const groupElement = grayTipElement?.groupElement const groupElement = grayTipElement?.groupElement
if (groupElement) { if (groupElement) {
if (groupElement.type === TipGroupElementType.MemberIncrease) { if (groupElement.type === TipGroupElementType.Ban) {
/*ctx.logger.info('', groupElement)
const { memberUid, adminUid } = groupElement
const memberUin = await ctx.ntUserApi.getUinByUid(memberUid)
const operatorUin = adminUid ? await ctx.ntUserApi.getUinByUid(adminUid) : memberUin
return new OB11GroupIncreaseEvent(+msg.peerUid, +memberUin, +operatorUin)*/
}
else if (groupElement.type === TipGroupElementType.Ban) {
ctx.logger.info('收到群成员禁言提示', groupElement) ctx.logger.info('收到群成员禁言提示', groupElement)
const memberUid = groupElement.shutUp?.member.uid const memberUid = groupElement.shutUp?.member.uid
const adminUid = groupElement.shutUp?.admin.uid const adminUid = groupElement.shutUp?.admin.uid
@ -673,14 +669,14 @@ export namespace OB11Entities {
[Sex.female]: OB11UserSex.Female, [Sex.female]: OB11UserSex.Female,
[Sex.unknown]: OB11UserSex.Unknown, [Sex.unknown]: OB11UserSex.Unknown,
} }
return sexMap[sex] || OB11UserSex.Unknown return sexMap[sex] ?? OB11UserSex.Unknown
} }
export function groupMember(group_id: string, member: GroupMember): OB11GroupMember { export function groupMember(groupId: number, member: GroupMember): OB11GroupMember {
const titleExpireTime = +member.specialTitleExpireTime const titleExpireTime = +member.specialTitleExpireTime
const int32Max = Math.pow(2, 31) - 1 const int32Max = 2147483647
return { return {
group_id: parseInt(group_id), group_id: groupId,
user_id: parseInt(member.uin), user_id: parseInt(member.uin),
nickname: member.nick, nickname: member.nick,
card: member.cardName || member.nick, card: member.cardName || member.nick,

View File

@ -11,8 +11,6 @@ export interface OB11User {
age?: number age?: number
qid?: string qid?: string
login_days?: number login_days?: number
categroyName?: string
categoryId?: number
} }
export enum OB11UserSex { export enum OB11UserSex {

View File

@ -1 +1 @@
export const version = '4.0.11' export const version = '4.0.12'