Merge pull request #456 from LLOneBot/dev

release: 3.34.0
This commit is contained in:
idranme 2024-10-01 21:32:24 +08:00 committed by GitHub
commit 0876e4645f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 427 additions and 436 deletions

View File

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

View File

@ -2,24 +2,7 @@ import fs from 'fs'
import path from 'node:path'
import { getConfigUtil } from '../config'
import { LOG_DIR } from '../globalVars'
import { Dict } from 'cosmokit'
function truncateString(obj: Dict | null, maxLength = 500) {
if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'string') {
// 如果是字符串且超过指定长度,则截断
if (obj[key].length > maxLength) {
obj[key] = obj[key].substring(0, maxLength) + '...'
}
} else if (typeof obj[key] === 'object') {
// 如果是对象或数组,则递归调用
truncateString(obj[key], maxLength)
}
})
}
return obj
}
import { inspect } from 'node:util'
export const logFileName = `llonebot-${new Date().toLocaleString('zh-CN')}.log`.replace(/\//g, '-').replace(/:/g, '-')
@ -29,9 +12,8 @@ export function log(...msg: unknown[]) {
}
let logMsg = ''
for (const msgItem of msg) {
// 判断是否是对象
if (typeof msgItem === 'object') {
logMsg += JSON.stringify(truncateString(msgItem)) + ' '
logMsg += inspect(msgItem, { depth: 10, compact: true, breakLength: Infinity }) + ' '
continue
}
logMsg += msgItem + ' '

View File

@ -191,7 +191,9 @@ function onLoad() {
ctx.plugin(SQLiteDriver, {
path: path.join(dbDir, `${selfInfo.uin}.db`)
})
ctx.plugin(Store)
ctx.plugin(Store, {
msgCacheExpire: config.msgCacheExpire! * 1000
})
ctx.start()
ipcMain.on(CHANNEL_SET_CONFIG_CONFIRMED, (event, config: LLOBConfig) => {
ctx.parallel('llonebot/config-updated', config)

View File

@ -1,4 +1,4 @@
import { Peer } from '@/ntqqapi/types'
import { Peer, RawMessage } from '@/ntqqapi/types'
import { createHash } from 'node:crypto'
import { LimitedHashTable } from '@/common/utils/table'
import { FileCacheV2 } from '@/common/types'
@ -24,13 +24,15 @@ interface MsgInfo {
peer: Peer
}
export default class Store extends Service {
class Store extends Service {
static inject = ['database', 'model']
private cache: LimitedHashTable<string, number>
private messages: Map<string, RawMessage>
constructor(protected ctx: Context) {
constructor(protected ctx: Context, public config: Store.Config) {
super(ctx, 'store', true)
this.cache = new LimitedHashTable(1000)
this.messages = new Map()
this.initDatabase()
}
@ -123,4 +125,29 @@ export default class Store extends Service {
getFileCacheById(fileUuid: string) {
return this.ctx.database.get('file_v2', { fileUuid })
}
async addMsgCache(msg: RawMessage) {
const expire = this.config.msgCacheExpire
if (expire === 0) {
return
}
const id = msg.msgId
this.messages.set(id, msg)
setTimeout(() => {
this.messages.delete(id)
}, expire)
}
getMsgCache(msgId: string) {
return this.messages.get(msgId)
}
}
namespace Store {
export interface Config {
/** 单位为毫秒 */
msgCacheExpire: number
}
}
export default Store

View File

@ -16,7 +16,6 @@ import path from 'node:path'
import { existsSync } from 'node:fs'
import { ReceiveCmdS } from '../hook'
import { RkeyManager } from '@/ntqqapi/helper/rkey'
import { getSession } from '@/ntqqapi/wrapper'
import { OnRichMediaDownloadCompleteParams, Peer } from '@/ntqqapi/types/msg'
import { calculateFileMD5 } from '@/common/utils/file'
import { fileTypeFromFile } from 'file-type'
@ -39,39 +38,28 @@ export class NTQQFileApi extends Service {
this.rkeyManager = new RkeyManager(ctx, 'https://llob.linyuchen.net/rkey')
}
async getVideoUrl(peer: Peer, msgId: string, elementId: string) {
const session = getSession()
if (session) {
return (await session.getRichMediaService().getVideoPlayUrlV2(
peer,
msgId,
elementId,
0,
{ downSourceType: 1, triggerType: 1 }
)).urlResult.domainUrl[0]?.url
} else {
const data = await invoke('nodeIKernelRichMediaService/getVideoPlayUrlV2', [{
peer,
msgId,
elemId: elementId,
videoCodecFormat: 0,
exParams: {
downSourceType: 1,
triggerType: 1
},
}, null])
if (data.result !== 0) {
this.ctx.logger.warn('getVideoUrl', data)
async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string | undefined> {
const data = await invoke('nodeIKernelRichMediaService/getVideoPlayUrlV2', [{
peer,
msgId,
elemId: elementId,
videoCodecFormat: 0,
params: {
downSourceType: 1,
triggerType: 1
}
return data.urlResult.domainUrl[0]?.url
}])
if (data.result !== 0) {
this.ctx.logger.warn('getVideoUrl', data)
}
return data.urlResult.domainUrl[0]?.url
}
async getFileType(filePath: string) {
return fileTypeFromFile(filePath)
}
// 上传文件到QQ的文件夹
/** 上传文件到 QQ 的文件夹 */
async uploadFile(filePath: string, elementType = ElementType.Pic, elementSubType = 0) {
const fileMd5 = await calculateFileMD5(filePath)
let fileName = path.basename(filePath)
@ -174,8 +162,8 @@ export class NTQQFileApi extends Service {
if (url) {
const parsedUrl = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接
const imageAppid = parsedUrl.searchParams.get('appid')
const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid)
if (isNewPic) {
const isNTPic = imageAppid && ['1406', '1407'].includes(imageAppid)
if (isNTPic) {
let rkey = parsedUrl.searchParams.get('rkey')
if (rkey) {
return IMAGE_HTTP_HOST_NT + url

View File

@ -1,8 +1,7 @@
import { Friend, FriendV2, SimpleInfo, CategoryFriend, BuddyListReqType } from '../types'
import { Friend, SimpleInfo, CategoryFriend } from '../types'
import { ReceiveCmdS } from '../hook'
import { invoke, NTMethod, NTClass } from '../ntcall'
import { getSession } from '@/ntqqapi/wrapper'
import { Dict, pick } from 'cosmokit'
import { Service, Context } from 'cordis'
declare module 'cordis' {
@ -60,7 +59,7 @@ export class NTQQFriendApi extends Service {
}
}
async getBuddyV2(refresh = false): Promise<FriendV2[]> {
async getBuddyV2(refresh = false): Promise<SimpleInfo[]> {
const data = await invoke<{
buddyCategory: CategoryFriend[]
userSimpleInfos: Record<string, SimpleInfo>
@ -118,12 +117,7 @@ export class NTQQFriendApi extends Service {
}
async isBuddy(uid: string): Promise<boolean> {
const session = getSession()
if (session) {
return session.getBuddyService().isBuddy(uid)
} else {
return await invoke('nodeIKernelBuddyService/isBuddy', [{ uid }, null])
}
return await invoke('nodeIKernelBuddyService/isBuddy', [{ uid }])
}
async getBuddyRecommendContact(uin: string) {

View File

@ -27,17 +27,12 @@ export class NTQQMsgApi extends Service {
}
}
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, setEmoji: boolean = true) {
async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, setEmoji: boolean) {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
// 其实以官方文档为准是最好的https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
const session = getSession()
const emojiType = emojiId.length > 3 ? '2' : '1'
if (session) {
return session.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiType, setEmoji)
} else {
return await invoke(NTMethod.EMOJI_LIKE, [{ peer, msgSeq, emojiId, emojiType, setEmoji }, null])
}
return await invoke(NTMethod.EMOJI_LIKE, [{ peer, msgSeq, emojiId, emojiType, setEmoji }])
}
async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
@ -58,59 +53,41 @@ export class NTQQMsgApi extends Service {
}
async getAioFirstViewLatestMsgs(peer: Peer, cnt: number) {
return await invoke('nodeIKernelMsgService/getAioFirstViewLatestMsgs', [{ peer, cnt }, null])
return await invoke('nodeIKernelMsgService/getAioFirstViewLatestMsgs', [{ peer, cnt }])
}
async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
async getMsgsByMsgId(peer: Peer, msgIds: string[]) {
if (!peer) throw new Error('peer is not allowed')
if (!msgIds) throw new Error('msgIds is not allowed')
const session = getSession()
if (session) {
return session.getMsgService().getMsgsByMsgId(peer, msgIds)
} else {
return await invoke('nodeIKernelMsgService/getMsgsByMsgId', [{ peer, msgIds }, null])
}
return await invoke('nodeIKernelMsgService/getMsgsByMsgId', [{ peer, msgIds }])
}
async getMsgHistory(peer: Peer, msgId: string, cnt: number, isReverseOrder: boolean = false) {
const session = getSession()
// 消息时间从旧到新
if (session) {
return session.getMsgService().getMsgsIncludeSelf(peer, msgId, cnt, isReverseOrder)
} else {
return await invoke(NTMethod.HISTORY_MSG, [{ peer, msgId, cnt, queryOrder: isReverseOrder }, null])
}
return await invoke(NTMethod.HISTORY_MSG, [{ peer, msgId, cnt, queryOrder: isReverseOrder }])
}
async recallMsg(peer: Peer, msgIds: string[]) {
const session = getSession()
if (session) {
return session.getMsgService().recallMsg(peer, msgIds)
} else {
return await invoke(NTMethod.RECALL_MSG, [{ peer, msgIds }, null])
}
return await invoke(NTMethod.RECALL_MSG, [{ peer, msgIds }])
}
async sendMsg(peer: Peer, msgElements: SendMessageElement[], timeout = 10000) {
const msgId = await this.generateMsgUniqueId(peer.chatType)
peer.guildId = msgId
const uniqueId = await this.generateMsgUniqueId(peer.chatType)
peer.guildId = uniqueId
const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/sendMsg',
[
{
msgId: '0',
peer,
msgElements,
msgAttributeInfos: new Map()
},
null
],
[{
msgId: '0',
peer,
msgElements,
msgAttributeInfos: new Map()
}],
{
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false,
cmdCB: payload => {
for (const msgRecord of payload.msgList) {
if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) {
if (msgRecord.guildId === uniqueId && msgRecord.sendStatus === 2) {
return true
}
}
@ -119,22 +96,35 @@ export class NTQQMsgApi extends Service {
timeout
}
)
return data.msgList.find(msgRecord => msgRecord.guildId === msgId)
return data.msgList.find(msgRecord => msgRecord.guildId === uniqueId)
}
async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
const session = getSession()
if (session) {
return session.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])
} else {
return await invoke(NTMethod.FORWARD_MSG, [{
const uniqueId = await this.generateMsgUniqueId(destPeer.chatType)
destPeer.guildId = uniqueId
const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/forwardMsg',
[{
msgIds,
srcContact: srcPeer,
dstContacts: [destPeer],
commentElements: [],
msgAttributeInfos: new Map(),
}, null])
}
}],
{
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false,
cmdCB: payload => {
for (const msgRecord of payload.msgList) {
if (msgRecord.guildId === uniqueId && msgRecord.sendStatus === 2) {
return true
}
}
return false
}
}
)
return data.msgList.filter(msgRecord => msgRecord.guildId === uniqueId)
}
async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise<RawMessage> {
@ -145,27 +135,24 @@ export class NTQQMsgApi extends Service {
const selfUid = selfInfo.uid
const data = await invoke<{ msgList: RawMessage[] }>(
'nodeIKernelMsgService/multiForwardMsgWithComment',
[
{
msgInfos,
srcContact: srcPeer,
dstContact: destPeer,
commentElements: [],
msgAttributeInfos: new Map(),
},
null,
],
[{
msgInfos,
srcContact: srcPeer,
dstContact: destPeer,
commentElements: [],
msgAttributeInfos: new Map(),
}],
{
cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate',
afterFirstCmd: false,
cmdCB: payload => {
for (const msgRecord of payload.msgList) {
if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfUid) {
if (msgRecord.peerUid === destPeer.peerUid && msgRecord.senderUid === selfUid) {
return true
}
}
return false
},
}
}
)
for (const msg of data.msgList) {
@ -174,10 +161,10 @@ export class NTQQMsgApi extends Service {
continue
}
const forwardData = JSON.parse(arkElement.arkElement!.bytesData)
if (forwardData.app != 'com.tencent.multimsg') {
if (forwardData.app !== 'com.tencent.multimsg') {
continue
}
if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfUid) {
if (msg.peerUid === destPeer.peerUid && msg.senderUid === selfUid) {
return msg
}
}
@ -240,7 +227,7 @@ export class NTQQMsgApi extends Service {
isIncludeCurrent: true,
pageLimit: 1,
}
}, null])
}])
}
async setMsgRead(peer: Peer) {
@ -254,7 +241,7 @@ export class NTQQMsgApi extends Service {
emojiId,
emojiType,
cnt: count
}, null])
}])
}
async fetchFavEmojiList(count: number) {
@ -267,7 +254,8 @@ export class NTQQMsgApi extends Service {
}
async generateMsgUniqueId(chatType: number) {
const uniqueId = await invoke('nodeIKernelMsgService/generateMsgUniqueId', [{ chatType }])
const time = await this.getServerTime()
const uniqueId = await invoke('nodeIKernelMsgService/generateMsgUniqueId', [{ chatType, time }])
if (typeof uniqueId === 'string') {
return uniqueId
} else {
@ -275,4 +263,32 @@ export class NTQQMsgApi extends Service {
return `${Date.now()}${random}`
}
}
async queryMsgsById(chatType: ChatType, msgId: string) {
const msgTime = this.getMsgTimeFromId(msgId)
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId,
msgTime: '0',
msgSeq: '0',
params: {
chatInfo: {
peerUid: '',
chatType
},
filterMsgToTime: msgTime,
filterMsgFromTime: msgTime,
isIncludeCurrent: true,
pageLimit: 1,
}
}])
}
getMsgTimeFromId(msgId: string) {
// 小概率相差1毫秒
return String(BigInt(msgId) >> 32n)
}
async getServerTime() {
return await invoke('nodeIKernelMSFService/getServerTime', [null])
}
}

View File

@ -1,9 +1,9 @@
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg, UserDetailSource, ProfileBizType } from '../types'
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfo, UserDetailSource, ProfileBizType, SimpleInfo } from '../types'
import { invoke } from '../ntcall'
import { getBuildVersion } from '@/common/utils'
import { getSession } from '@/ntqqapi/wrapper'
import { RequestUtil } from '@/common/utils/request'
import { Time } from 'cosmokit'
import { isNullable, Time } from 'cosmokit'
import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars'
@ -34,17 +34,14 @@ export class NTQQUserApi extends Service {
}
async fetchUserDetailInfo(uid: string) {
const result = await invoke<{ info: UserDetailInfoListenerArg }>(
const result = await invoke<{ info: UserDetailInfo }>(
'nodeIKernelProfileService/fetchUserDetailInfo',
[
{
callFrom: 'BuddyProfileStore',
uid: [uid],
source: UserDetailSource.KSERVER,
bizList: [ProfileBizType.KALL]
},
null
],
[{
callFrom: 'BuddyProfileStore',
uid: [uid],
source: UserDetailSource.KSERVER,
bizList: [ProfileBizType.KALL]
}],
{
cbCmd: 'nodeIKernelProfileListener/onUserDetailInfoChanged',
afterFirstCmd: false,
@ -70,13 +67,10 @@ export class NTQQUserApi extends Service {
}
const result = await invoke<{ info: User }>(
'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
[
{
uid,
bizList: [0]
},
null,
],
[{
uid,
bizList: [0]
}],
{
cbCmd: 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
afterFirstCmd: false,
@ -129,9 +123,7 @@ export class NTQQUserApi extends Service {
}
async getUidByUinV1(uin: string) {
const session = getSession()
// 通用转换开始尝试
let uid = (await session?.getUixConvertService().getUid([uin]))?.uidInfo.get(uin)
let uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
if (!uid) {
for (const membersList of this.ctx.ntGroupApi.groupMembers.values()) { //从群友列表转
for (const member of membersList.values()) {
@ -181,20 +173,14 @@ export class NTQQUserApi extends Service {
async getUserDetailInfoByUinV2(uin: string) {
return await invoke<UserDetailInfoByUinV2>(
'nodeIKernelProfileService/getUserDetailInfoByUin',
[
{ uin },
null,
],
[{ uin }]
)
}
async getUserDetailInfoByUin(uin: string) {
return await invoke<UserDetailInfoByUin>(
'nodeIKernelProfileService/getUserDetailInfoByUin',
[
{ uin },
null,
],
[{ uin }]
)
}
@ -202,31 +188,21 @@ export class NTQQUserApi extends Service {
const ret = await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])
let uin = ret.uinInfo.get(uid)
if (!uin) {
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
uin = (await this.getUserDetailInfo(uid)).uin
}
return uin
}
async getUinByUidV2(uid: string) {
const session = getSession()
if (session) {
let uin = (await session.getGroupService().getUinByUids([uid])).uins.get(uid)
if (uin) return uin
uin = (await session.getProfileService().getUinByUid('FriendsServiceImpl', [uid])).get(uid)
if (uin) return uin
uin = (await session.getUixConvertService().getUin([uid])).uinInfo.get(uid)
if (uin) return uin
} else {
let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uid: [uid] }])).uins.get(uid)
if (uin) return uin
uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }])).get(uid)
if (uin) return uin
uin = (await invoke('nodeIKernelUixConvertService/getUin', [{ uids: [uid] }])).uinInfo.get(uid)
if (uin) return uin
}
let uin = (await this.ctx.ntFriendApi.getBuddyIdMap(true)).get(uid)
let uin = (await invoke('nodeIKernelGroupService/getUinByUids', [{ uid: [uid] }])).uins.get(uid)
if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin //从QQ Native 转换
uin = (await invoke('nodeIKernelProfileService/getUinByUid', [{ callFrom: 'FriendsServiceImpl', uid: [uid] }])).get(uid)
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)
if (uin) return uin
uin = (await this.getUserDetailInfo(uid)).uin
return uin
}
@ -246,13 +222,10 @@ export class NTQQUserApi extends Service {
}
}
async getSelfNick(refresh = false) {
async getSelfNick(refresh = true) {
if ((refresh || !selfInfo.nick) && selfInfo.uid) {
const userInfo = await this.getUserDetailInfo(selfInfo.uid)
if (userInfo) {
Object.assign(selfInfo, { nick: userInfo.nick })
return userInfo.nick
}
const { profiles } = await this.getUserSimpleInfo(selfInfo.uid)
selfInfo.nick = profiles[selfInfo.uid].coreInfo.nick
}
return selfInfo.nick
}
@ -281,4 +254,44 @@ export class NTQQUserApi extends Service {
}
}, null])
}
async getUserSimpleInfo(uid: string, force = true) {
return await invoke<{ profiles: Record<string, SimpleInfo> }>(
'nodeIKernelProfileService/getUserSimpleInfo',
[{
uids: [uid],
force
}],
{
cbCmd: 'onProfileSimpleChanged',
afterFirstCmd: false,
cmdCB: payload => !isNullable(payload.profiles[uid]),
}
)
}
async getCoreAndBaseInfo(uids: string[]) {
return await invoke(
'nodeIKernelProfileService/getCoreAndBaseInfo',
[{
uids,
callFrom: 'nodeStore'
}]
)
}
async getRobotUinRange() {
const data = await invoke(
'nodeIKernelRobotService/getRobotUinRange',
[{
req: {
justFetchMsgConfig: '1',
type: 1,
version: 0,
aioKeywordVersion: 0
}
}]
)
return data.response.robotUinRanges
}
}

View File

@ -2,7 +2,6 @@ import { unlink } from 'node:fs/promises'
import { Service, Context } from 'cordis'
import { registerCallHook, registerReceiveHook, ReceiveCmdS } from './hook'
import { Config as LLOBConfig } from '../common/types'
import { llonebotError } from '../common/globalVars'
import { isNumeric } from '../common/utils/misc'
import { NTMethod } from './ntcall'
import {
@ -13,7 +12,8 @@ import {
GroupMember,
CategoryFriend,
SimpleInfo,
ChatType
ChatType,
BuddyReqType
} from './types'
import { selfInfo } from '../common/globalVars'
import { version } from '../version'
@ -24,25 +24,26 @@ declare module 'cordis' {
app: Core
}
interface Events {
'nt/message-created': (input: RawMessage[]) => void
'nt/message-created': (input: RawMessage) => void
'nt/message-deleted': (input: RawMessage) => void
'nt/message-sent': (input: RawMessage) => void
'nt/group-notify': (input: GroupNotify[]) => void
'nt/friend-request': (input: FriendRequest[]) => void
'nt/group-notify': (input: GroupNotify) => void
'nt/friend-request': (input: FriendRequest) => void
'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void
'nt/system-message-created': (input: Uint8Array) => void
}
}
class Core extends Service {
static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi']
static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi', 'store']
public startTime = 0
constructor(protected ctx: Context, public config: Core.Config) {
super(ctx, 'app', true)
}
public start() {
llonebotError.otherError = ''
this.startTime = Date.now()
this.registerListener()
this.ctx.logger.info(`LLOneBot/${version}`)
this.ctx.on('llonebot/config-updated', input => {
@ -121,7 +122,7 @@ class Core extends Service {
this.ctx.ntMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
const lastTempMsg = msgList.at(-1)
if (Date.now() / 1000 - Number(lastTempMsg?.msgTime) < 5) {
this.ctx.parallel('nt/message-created', [lastTempMsg!])
this.ctx.parallel('nt/message-created', lastTempMsg!)
}
})
})
@ -161,7 +162,16 @@ class Core extends Service {
})
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], payload => {
this.ctx.parallel('nt/message-created', payload.msgList)
for (const message of payload.msgList) {
// 过滤启动之前的消息
if (parseInt(message.msgTime) < this.startTime / 1000) {
continue
}
if (message.senderUin && message.senderUin !== '0') {
this.ctx.store.addMsgCache(message)
}
this.ctx.parallel('nt/message-created', message)
}
})
const sentMsgIds = new Map<string, boolean>()
@ -199,20 +209,28 @@ class Core extends Service {
} catch (e) {
return
}
const list = notifies.filter(v => {
const flag = v.group.groupCode + '|' + v.seq + '|' + v.type
if (groupNotifyFlags.includes(flag)) {
return false
for (const notify of notifies) {
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
const notifyTime = parseInt(notify.seq) / 1000
if (groupNotifyFlags.includes(flag) || notifyTime < this.startTime) {
continue
}
groupNotifyFlags.push(flag)
return true
})
this.ctx.parallel('nt/group-notify', list)
this.ctx.parallel('nt/group-notify', notify)
}
}
})
registerReceiveHook<FriendRequestNotify>(ReceiveCmdS.FRIEND_REQUEST, payload => {
this.ctx.parallel('nt/friend-request', payload.data.buddyReqs)
for (const req of payload.data.buddyReqs) {
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.MeInitiatorWaitPeerConfirm)) {
continue
}
if (+req.reqTime < this.startTime / 1000) {
continue
}
this.ctx.parallel('nt/friend-request', req)
}
})
invoke('nodeIKernelMsgListener/onRecvSysMsg', [], { registerEvent: true })

View File

@ -23,9 +23,7 @@ import { encodeSilk } from '../common/utils/audio'
import { Context } from 'cordis'
import { isNullable } from 'cosmokit'
//export const mFaceCache = new Map<string, string>() // emojiId -> faceName
export namespace SendElementEntities {
export namespace SendElement {
export function text(content: string): SendTextElement {
return {
elementType: ElementType.Text,

View File

@ -31,30 +31,18 @@ export class RkeyManager {
isExpired(): boolean {
const now = new Date().getTime() / 1000
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`)
return now > this.rkeyData.expired_time
}
async refreshRkey() {
//刷新rkey
this.rkeyData = await this.fetchServerRkey()
}
async fetchServerRkey() {
return new Promise<ServerRkeyData>((resolve, reject) => {
fetch(this.serverUrl)
.then(response => {
if (!response.ok) {
return reject(response.statusText) // 请求失败,返回错误信息
}
return response.json() // 解析 JSON 格式的响应体
})
.then(data => {
resolve(data)
})
.catch(error => {
reject(error)
})
})
async fetchServerRkey(): Promise<ServerRkeyData> {
const response = await fetch(this.serverUrl)
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json()
}
}

View File

@ -26,7 +26,6 @@ export enum ReceiveCmdS {
SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged',
CACHE_SCAN_FINISH = 'nodeIKernelStorageCleanListener/onFinishScan',
MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete',
SKEY_UPDATE = 'onSkeyUpdate',
}
type NTReturnData = [
@ -96,7 +95,7 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
const isLogger = args[3]?.[0]?.eventName?.startsWith('ns-LoggerApi')
if (!isLogger) {
try {
logHook && log('call NTQQ api', thisArg, args)
logHook && log('call NTQQ api', args)
} catch (e) { }
if (!onlyLog) {
try {

View File

@ -13,7 +13,8 @@ import {
NodeIKernelUixConvertService,
NodeIKernelRichMediaService,
NodeIKernelTicketService,
NodeIKernelTipOffService
NodeIKernelTipOffService,
NodeIKernelRobotService
} from './services'
export enum NTClass {
@ -39,7 +40,6 @@ export enum NTMethod {
MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild',
RECALL_MSG = 'nodeIKernelMsgService/recallMsg',
EMOJI_LIKE = 'nodeIKernelMsgService/setMsgEmojiLikes',
FORWARD_MSG = 'nodeIKernelMsgService/forwardMsgWithComment',
SELF_INFO = 'fetchAuthData',
FILE_TYPE = 'getFileType',
@ -93,6 +93,7 @@ interface NTService {
nodeIKernelRichMediaService: NodeIKernelRichMediaService
nodeIKernelTicketService: NodeIKernelTicketService
nodeIKernelTipOffService: NodeIKernelTipOffService
nodeIKernelRobotService: NodeIKernelRobotService
}
interface InvokeOptions<ReturnType> {

View File

@ -10,7 +10,9 @@ export interface NodeIKernelMsgService {
setStatus(args: { status: number, extStatus: number, batteryStatus: number }): Promise<GeneralCallResult>
forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult>
forwardMsg(msgIds: string[], srcContact: Peer, dstContacts: Peer[], commentElements: MessageElement[]): Promise<GeneralCallResult & {
detailErr: Map<unknown, unknown>
}>
forwardMsgWithComment(...args: unknown[]): Promise<GeneralCallResult>
@ -71,7 +73,7 @@ export interface NodeIKernelMsgService {
downloadRichMedia(...args: unknown[]): unknown
setMsgEmojiLikes(...args: unknown[]): unknown
setMsgEmojiLikes(...args: unknown[]): Promise<GeneralCallResult>
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{
result: number

View File

@ -0,0 +1,13 @@
import { GeneralCallResult } from './common'
export interface NodeIKernelRobotService {
getRobotUinRange(req: unknown): Promise<GeneralCallResult & {
response: {
version: number
robotUinRanges: {
minUin: string
maxUin: string
}[]
}
}>
}

View File

@ -9,3 +9,4 @@ export * from './NodeIKernelUixConvertService'
export * from './NodeIKernelRichMediaService'
export * from './NodeIKernelTicketService'
export * from './NodeIKernelTipOffService'
export * from './NodeIKernelRobotService'

View File

@ -56,20 +56,8 @@ export enum GroupRequestOperateTypes {
}
export enum BuddyReqType {
KMEINITIATOR,
KPEERINITIATOR,
KMEAGREED,
KMEAGREEDANDADDED,
KPEERAGREED,
KPEERAGREEDANDADDED,
KPEERREFUSED,
KMEREFUSED,
KMEIGNORED,
KMEAGREEANYONE,
KMESETQUESTION,
KMEAGREEANDADDFAILED,
KMSGINFO,
KMEINITIATORWAITPEERCONFIRM
MsgInfo = 12,
MeInitiatorWaitPeerConfirm = 13,
}
export interface FriendRequest {
@ -128,4 +116,4 @@ export interface GroupExtParam {
memberIcon: number
memberInfoSeq: number
}
}
}

View File

@ -221,11 +221,6 @@ interface RelationFlags {
isHidePrivilegeIcon: number
}
export interface FriendV2 extends SimpleInfo {
categoryId?: number
categroyName?: string
}
interface CommonExt {
constellation: number
shengXiao: number
@ -255,7 +250,7 @@ interface PhotoWall {
picList: Pic[]
}
export interface UserDetailInfoListenerArg {
export interface UserDetailInfo {
uid: string
uin: string
simpleInfo: SimpleInfo

View File

@ -1,6 +1,6 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { SendElementEntities } from '@/ntqqapi/entities'
import { SendElement } from '@/ntqqapi/entities'
import { uri2local } from '@/common/utils'
import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
@ -27,7 +27,7 @@ export class UploadGroupFile extends BaseAction<Payload, null> {
if (!success) {
throw new Error(errMsg)
}
const file = await SendElementEntities.file(this.ctx, path, payload.name || fileName, payload.folder ?? payload.folder_id)
const file = await SendElement.file(this.ctx, path, payload.name || fileName, payload.folder ?? payload.folder_id)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
await sendMsg(this.ctx, peer, [file], [])
return null

View File

@ -1,6 +1,6 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { SendElementEntities } from '@/ntqqapi/entities'
import { SendElement } from '@/ntqqapi/entities'
import { uri2local } from '@/common/utils'
import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
@ -23,7 +23,7 @@ export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null
if (!success) {
throw new Error(errMsg)
}
const sendFileEle = await SendElementEntities.file(this.ctx, path, payload.name || fileName)
const sendFileEle = await SendElement.file(this.ctx, path, payload.name || fileName)
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
await sendMsg(this.ctx, peer, [sendFileEle], [])
return null

View File

@ -45,7 +45,7 @@ import { GetGroupMsgHistory } from './go-cqhttp/GetGroupMsgHistory'
import GetFile from './file/GetFile'
import { GetForwardMsg } from './go-cqhttp/GetForwardMsg'
import { GetCookies } from './user/GetCookie'
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
import { SetMsgEmojiLike } from './llonebot/SetMsgEmojiLike'
import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg'
import { GetEssenceMsgList } from './go-cqhttp/GetGroupEssence'
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
@ -71,6 +71,7 @@ import { UploadGroupFile } from './go-cqhttp/UploadGroupFile'
import { UploadPrivateFile } from './go-cqhttp/UploadPrivateFile'
import { GetGroupFileUrl } from './go-cqhttp/GetGroupFileUrl'
import { GetGroupNotice } from './go-cqhttp/GetGroupNotice'
import { GetRobotUinRange } from './llonebot/GetRobotUinRange'
export function initActionMap(adapter: Adapter) {
const actionHandlers = [
@ -87,6 +88,8 @@ export function initActionMap(adapter: Adapter) {
new GetFriendMsgHistory(adapter),
new FetchEmojiLike(adapter),
new FetchCustomFace(adapter),
new SetMsgEmojiLike(adapter),
new GetRobotUinRange(adapter),
// onebot11
new SendLike(adapter),
new GetMsg(adapter),
@ -117,7 +120,6 @@ export function initActionMap(adapter: Adapter) {
new GetRecord(adapter),
new CleanCache(adapter),
new GetCookies(adapter),
new SetMsgEmojiLike(adapter),
new ForwardFriendSingleMsg(adapter),
new ForwardGroupSingleMsg(adapter),
// go-cqhttp

View File

@ -0,0 +1,11 @@
import { BaseAction } from '../BaseAction'
import { ActionName } from '../types'
import { Dict } from 'cosmokit'
export class GetRobotUinRange extends BaseAction<void, Dict[]> {
actionName = ActionName.GetRobotUinRange
async _handle() {
return await this.ctx.ntUserApi.getRobotUinRange()
}
}

View File

@ -19,8 +19,8 @@ abstract class ForwardSingleMsg extends BaseAction<Payload, null> {
}
const peer = await createPeer(this.ctx, payload)
const ret = await this.ctx.ntMsgApi.forwardMsg(msg.peer, peer, [msg.msgId])
if (ret.result !== 0) {
throw new Error(`转发消息失败 ${ret.errMsg}`)
if (ret.length === 0) {
throw new Error(`转发消息失败`)
}
return null
}

View File

@ -25,14 +25,11 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
peerUid: msgInfo.peer.peerUid,
chatType: msgInfo.peer.chatType
}
const msg = this.adapter.getMsgCache(msgInfo.msgId) ?? (await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [msgInfo.msgId])).msgList[0]
const msg = this.ctx.store.getMsgCache(msgInfo.msgId) ?? (await this.ctx.ntMsgApi.getMsgsByMsgId(peer, [msgInfo.msgId])).msgList[0]
const retMsg = await OB11Entities.message(this.ctx, msg)
if (!retMsg) {
throw new Error('消息为空')
}
retMsg.message_id = this.ctx.store.createMsgShortId(peer, msg.msgId)
retMsg.message_seq = retMsg.message_id
retMsg.real_id = retMsg.message_id
return retMsg
}
}

View File

@ -25,6 +25,8 @@ export enum ActionName {
FetchCustomFace = 'fetch_custom_face',
GetFriendMsgHistory = 'get_friend_msg_history',
SendForwardMsg = 'send_forward_msg',
SetMsgEmojiLike = 'set_msg_emoji_like',
GetRobotUinRange = 'get_robot_uin_range',
// onebot 11
SendLike = 'send_like',
GetLoginInfo = 'get_login_info',
@ -38,7 +40,6 @@ export enum ActionName {
SendGroupMsg = 'send_group_msg',
SendPrivateMsg = 'send_private_msg',
DeleteMsg = 'delete_msg',
SetMsgEmojiLike = 'set_msg_emoji_like',
SetGroupAddRequest = 'set_group_add_request',
SetFriendAddRequest = 'set_friend_add_request',
SetGroupLeave = 'set_group_leave',
@ -58,7 +59,7 @@ export enum ActionName {
GetCookies = 'get_cookies',
ForwardFriendSingleMsg = 'forward_friend_single_msg',
ForwardGroupSingleMsg = 'forward_group_single_msg',
// 以下为go-cqhttp api
// go-cqhttp
GoCQHTTP_SendGroupForwardMsg = 'send_group_forward_msg',
GoCQHTTP_SendPrivateForwardMsg = 'send_private_forward_msg',
GoCQHTTP_GetStrangerInfo = 'get_stranger_info',

View File

@ -37,8 +37,6 @@ declare module 'cordis' {
class OneBot11Adapter extends Service {
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi', 'ntWebApi', 'store']
public messages: Map<string, RawMessage> = new Map()
public startTime = 0
private ob11WebSocket: OB11WebSocket
private ob11WebSocketReverseManager: OB11WebSocketReverseManager
private ob11Http: OB11Http
@ -74,24 +72,6 @@ class OneBot11Adapter extends Service {
})
}
/** 缓存近期消息内容 */
public async addMsgCache(msg: RawMessage) {
const expire = this.config.msgCacheExpire * 1000
if (expire === 0) {
return
}
const id = msg.msgId
this.messages.set(id, msg)
setTimeout(() => {
this.messages.delete(id)
}, expire)
}
/** 获取近期消息内容 */
public getMsgCache(msgId: string) {
return this.messages.get(msgId)
}
public dispatch(event: OB11BaseEvent | OB11Message) {
if (this.config.enableWs) {
this.ob11WebSocket.emitEvent(event)
@ -108,115 +88,99 @@ class OneBot11Adapter extends Service {
}
}
private async handleGroupNotify(notifies: GroupNotify[]) {
for (const notify of notifies) {
try {
const notifyTime = parseInt(notify.seq) / 1000
if (notifyTime < this.startTime) {
continue
}
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
if ([GroupNotifyType.MEMBER_LEAVE_NOTIFY_ADMIN, GroupNotifyType.KICK_MEMBER_NOTIFY_ADMIN].includes(notify.type)) {
this.ctx.logger.info('有成员退出通知', notify)
const member1Uin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
let operatorId = member1Uin
let subType: GroupDecreaseSubType = 'leave'
if (notify.user2.uid) {
// 是被踢的
const member2Uin = await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)
if (member2Uin) {
operatorId = member2Uin
}
subType = 'kick'
private async handleGroupNotify(notify: GroupNotify) {
try {
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
if ([GroupNotifyType.MEMBER_LEAVE_NOTIFY_ADMIN, GroupNotifyType.KICK_MEMBER_NOTIFY_ADMIN].includes(notify.type)) {
this.ctx.logger.info('有成员退出通知', notify)
const member1Uin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
let operatorId = member1Uin
let subType: GroupDecreaseSubType = 'leave'
if (notify.user2.uid) {
// 是被踢的
const member2Uin = await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)
if (member2Uin) {
operatorId = member2Uin
}
const event = new OB11GroupDecreaseEvent(
parseInt(notify.group.groupCode),
parseInt(member1Uin),
parseInt(operatorId),
subType,
)
this.dispatch(event)
subType = 'kick'
}
else if (notify.type === GroupNotifyType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS && notify.status === GroupNotifyStatus.KUNHANDLE) {
this.ctx.logger.info('有加群请求')
const requestUin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
const event = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(requestUin) || 0,
flag,
notify.postscript,
)
this.dispatch(event)
}
else if (notify.type === GroupNotifyType.INVITED_BY_MEMBER && notify.status === GroupNotifyStatus.KUNHANDLE) {
this.ctx.logger.info('收到邀请我加群通知')
const userId = await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)
const event = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(userId) || 0,
flag,
notify.postscript,
undefined,
'invite'
)
this.dispatch(event)
}
else if (notify.type === GroupNotifyType.INVITED_NEED_ADMINI_STRATOR_PASS && notify.status === GroupNotifyStatus.KUNHANDLE) {
this.ctx.logger.info('收到群员邀请加群通知')
const userId = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
const event = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(userId) || 0,
flag,
notify.postscript
)
this.dispatch(event)
}
} catch (e) {
this.ctx.logger.error('解析群通知失败', (e as Error).stack)
const event = new OB11GroupDecreaseEvent(
parseInt(notify.group.groupCode),
parseInt(member1Uin),
parseInt(operatorId),
subType,
)
this.dispatch(event)
}
else if (notify.type === GroupNotifyType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS && notify.status === GroupNotifyStatus.KUNHANDLE) {
this.ctx.logger.info('有加群请求')
const requestUin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
const event = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(requestUin) || 0,
flag,
notify.postscript,
)
this.dispatch(event)
}
else if (notify.type === GroupNotifyType.INVITED_BY_MEMBER && notify.status === GroupNotifyStatus.KUNHANDLE) {
this.ctx.logger.info('收到邀请我加群通知')
const userId = await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)
const event = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(userId) || 0,
flag,
notify.postscript,
undefined,
'invite'
)
this.dispatch(event)
}
else if (notify.type === GroupNotifyType.INVITED_NEED_ADMINI_STRATOR_PASS && notify.status === GroupNotifyStatus.KUNHANDLE) {
this.ctx.logger.info('收到群员邀请加群通知')
const userId = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
const event = new OB11GroupRequestEvent(
parseInt(notify.group.groupCode),
parseInt(userId) || 0,
flag,
notify.postscript
)
this.dispatch(event)
}
} catch (e) {
this.ctx.logger.error('解析群通知失败', (e as Error).stack)
}
}
private handleMsg(msgList: RawMessage[]) {
for (const message of msgList) {
// 过滤启动之前的消息
if (parseInt(message.msgTime) < this.startTime / 1000) {
continue
private handleMsg(message: RawMessage) {
OB11Entities.message(this.ctx, message).then(msg => {
if (!msg) {
return
}
this.addMsgCache(message)
if (!this.config.debug && msg.message.length === 0) {
return
}
const isSelfMsg = msg.user_id.toString() === selfInfo.uin
if (isSelfMsg && !this.config.reportSelfMessage) {
return
}
if (isSelfMsg) {
msg.target_id = parseInt(message.peerUin)
}
this.dispatch(msg)
}).catch(e => this.ctx.logger.error('constructMessage error: ', e.stack.toString()))
OB11Entities.message(this.ctx, message)
.then((msg) => {
if (!msg) {
return
}
if (!this.config.debug && msg.message.length === 0) {
return
}
const isSelfMsg = msg.user_id.toString() === selfInfo.uin
if (isSelfMsg && !this.config.reportSelfMessage) {
return
}
if (isSelfMsg) {
msg.target_id = parseInt(message.peerUin)
}
this.dispatch(msg)
})
.catch((e) => this.ctx.logger.error('constructMessage error: ', e.stack.toString()))
OB11Entities.groupEvent(this.ctx, message).then(groupEvent => {
if (groupEvent) {
this.dispatch(groupEvent)
}
})
OB11Entities.groupEvent(this.ctx, message).then((groupEvent) => {
if (groupEvent) {
this.dispatch(groupEvent)
}
})
OB11Entities.privateEvent(this.ctx, message).then((privateEvent) => {
if (privateEvent) {
this.dispatch(privateEvent)
}
})
}
OB11Entities.privateEvent(this.ctx, message).then(privateEvent => {
if (privateEvent) {
this.dispatch(privateEvent)
}
})
}
private handleRecallMsg(message: RawMessage) {
@ -235,30 +199,22 @@ class OneBot11Adapter extends Service {
})
}
private async handleFriendRequest(buddyReqs: FriendRequest[]) {
for (const req of buddyReqs) {
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
continue
}
if (+req.reqTime < this.startTime / 1000) {
continue
}
let userId = 0
try {
const requesterUin = await this.ctx.ntUserApi.getUinByUid(req.friendUid)
userId = parseInt(requesterUin)
} catch (e) {
this.ctx.logger.error('获取加好友者QQ号失败', e)
}
const flag = req.friendUid + '|' + req.reqTime
const comment = req.extWords
const friendRequestEvent = new OB11FriendRequestEvent(
userId,
comment,
flag
)
this.dispatch(friendRequestEvent)
private async handleFriendRequest(req: FriendRequest) {
let userId = 0
try {
const requesterUin = await this.ctx.ntUserApi.getUinByUid(req.friendUid)
userId = parseInt(requesterUin)
} catch (e) {
this.ctx.logger.error('获取加好友者QQ号失败', e)
}
const flag = req.friendUid + '|' + req.reqTime
const comment = req.extWords
const friendRequestEvent = new OB11FriendRequestEvent(
userId,
comment,
flag
)
this.dispatch(friendRequestEvent)
}
private async handleConfigUpdated(config: LLOBConfig) {
@ -374,7 +330,6 @@ class OneBot11Adapter extends Service {
}
public start() {
this.startTime = Date.now()
if (this.config.enableWs) {
this.ob11WebSocket.start()
}
@ -397,7 +352,7 @@ class OneBot11Adapter extends Service {
this.handleRecallMsg(input)
})
this.ctx.on('nt/message-sent', input => {
this.handleMsg([input])
this.handleMsg(input)
})
this.ctx.on('nt/group-notify', input => {
this.handleGroupNotify(input)

View File

@ -20,7 +20,7 @@ import {
Sex,
TipGroupElementType,
User,
FriendV2
SimpleInfo
} from '../ntqqapi/types'
import { EventType } from './event/OB11BaseEvent'
import { encodeCQCode } from './cqcode'
@ -338,7 +338,6 @@ export namespace OB11Entities {
key: marketFaceElement.key
}
}
//mFaceCache.set(emojiId, element.marketFaceElement.faceName!)
}
else if (element.markdownElement) {
const { markdownElement } = element
@ -519,11 +518,11 @@ export namespace OB11Entities {
if (!replyMsgList?.length) {
return
}
const shortId = ctx.store.getShortIdByMsgInfo(peer, replyMsgList[0].msgId)
const shortId = ctx.store.createMsgShortId(peer, replyMsgList[0].msgId)
return new OB11GroupMsgEmojiLikeEvent(
parseInt(msg.peerUid),
parseInt(senderUin),
shortId!,
shortId,
[{
emoji_id: emojiId,
count: 1,
@ -569,8 +568,7 @@ export namespace OB11Entities {
pokedetail
)
}
}
if (grayTipElement.jsonGrayTipElement?.busiId === '2401' && json.items[2]) {
} else if (grayTipElement.jsonGrayTipElement?.busiId === '2401' && json.items[2]) {
ctx.logger.info('收到群精华消息', json)
const searchParams = new URL(json.items[2].jp).searchParams
const msgSeq = searchParams.get('seq')
@ -592,8 +590,7 @@ export namespace OB11Entities {
parseInt(essence.items[0]?.msgSenderUin ?? sourceMsg.senderUin),
parseInt(essence.items[0]?.opUin ?? '0'),
)
}
if (grayTipElement.jsonGrayTipElement?.busiId === '2407') {
} else if (grayTipElement.jsonGrayTipElement?.busiId === '2407') {
const memberUin = json.items[1].param[0]
const title = json.items[3].txt
ctx.logger.info('收到群成员新头衔消息', json)
@ -603,6 +600,11 @@ export namespace OB11Entities {
}
})
return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title)
} else if (grayTipElement.jsonGrayTipElement?.busiId === '19217') {
ctx.logger.info('收到新人被邀请进群消息', grayTipElement)
const userId = new URL(json.items[2].jp).searchParams.get('robot_uin')
const operatorId = new URL(json.items[0].jp).searchParams.get('uin')
return new OB11GroupIncreaseEvent(Number(msg.peerUid), Number(userId), Number(operatorId), 'invite')
}
}
}
@ -649,7 +651,7 @@ export namespace OB11Entities {
return friends.map(friend)
}
export function friendV2(raw: FriendV2): OB11User {
export function friendV2(raw: SimpleInfo): OB11User {
return {
...omit(raw.baseInfo, ['richBuffer', 'phoneNum']),
...omit(raw.coreInfo, ['nick']),
@ -661,7 +663,7 @@ export namespace OB11Entities {
}
}
export function friendsV2(raw: FriendV2[]): OB11User[] {
export function friendsV2(raw: SimpleInfo[]): OB11User[] {
return raw.map(friendV2)
}

View File

@ -8,17 +8,15 @@ export interface MsgEmojiLike {
export class OB11GroupMsgEmojiLikeEvent extends OB11GroupNoticeEvent {
notice_type = 'group_msg_emoji_like'
message_id: number
sub_type?: 'ban' | 'lift_ban'
likes: MsgEmojiLike[]
group_id: number
user_id: number
constructor(groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[], sub_type?: 'ban' | 'lift_ban') {
constructor(groupId: number, userId: number, messageId: number, likes: MsgEmojiLike[]) {
super()
this.group_id = groupId
this.user_id = userId // 可为空表示是对别人的消息操作如果是对bot自己的消息则不为空
this.message_id = messageId
this.likes = likes
this.sub_type = sub_type
}
}

View File

@ -15,7 +15,7 @@ import {
} from '../types'
import { decodeCQCode } from '../cqcode'
import { Peer } from '@/ntqqapi/types/msg'
import { SendElementEntities } from '@/ntqqapi/entities'
import { SendElement } from '@/ntqqapi/entities'
import { selfInfo } from '@/common/globalVars'
import { uri2local } from '@/common/utils'
import { Context } from 'cordis'
@ -36,7 +36,7 @@ export async function createSendElements(
case OB11MessageDataType.text: {
const text = sendMsg.data?.text
if (text) {
sendElements.push(SendElementEntities.text(sendMsg.data!.text))
sendElements.push(SendElement.text(sendMsg.data!.text))
}
}
break
@ -62,7 +62,7 @@ export async function createSendElements(
}
}
if (isAdmin && remainAtAllCount > 0) {
sendElements.push(SendElementEntities.at(atQQ, atQQ, AtType.All, '@全体成员'))
sendElements.push(SendElement.at(atQQ, atQQ, AtType.All, '@全体成员'))
}
}
else if (peer.chatType === ChatType.Group) {
@ -70,14 +70,14 @@ export async function createSendElements(
if (atMember) {
const display = `@${atMember.cardName || atMember.nick}`
sendElements.push(
SendElementEntities.at(atQQ, atMember.uid, AtType.One, display),
SendElement.at(atQQ, atMember.uid, AtType.One, display),
)
} else {
const atNmae = sendMsg.data?.name
const uid = await ctx.ntUserApi.getUidByUin(atQQ) || ''
const display = atNmae ? `@${atNmae}` : ''
sendElements.push(
SendElementEntities.at(atQQ, uid, AtType.One, display),
SendElement.at(atQQ, uid, AtType.One, display),
)
}
}
@ -97,7 +97,7 @@ export async function createSendElements(
)).msgList[0]
if (replyMsg) {
sendElements.push(
SendElementEntities.reply(
SendElement.reply(
replyMsg.msgSeq,
replyMsg.msgId,
replyMsg.senderUin!,
@ -111,13 +111,13 @@ export async function createSendElements(
case OB11MessageDataType.face: {
const faceId = sendMsg.data?.id
if (faceId) {
sendElements.push(SendElementEntities.face(parseInt(faceId)))
sendElements.push(SendElement.face(parseInt(faceId)))
}
}
break
case OB11MessageDataType.mface: {
sendElements.push(
SendElementEntities.mface(
SendElement.mface(
+sendMsg.data.emoji_package_id,
sendMsg.data.emoji_id,
sendMsg.data.key,
@ -127,7 +127,7 @@ export async function createSendElements(
}
break
case OB11MessageDataType.image: {
const res = await SendElementEntities.pic(
const res = await SendElement.pic(
ctx,
(await handleOb11FileLikeMessage(ctx, sendMsg, { deleteAfterSentFiles })).path,
sendMsg.data.summary || '',
@ -140,7 +140,7 @@ export async function createSendElements(
break
case OB11MessageDataType.file: {
const { path, fileName } = await handleOb11FileLikeMessage(ctx, sendMsg, { deleteAfterSentFiles })
sendElements.push(await SendElementEntities.file(ctx, path, fileName))
sendElements.push(await SendElement.file(ctx, path, fileName))
}
break
case OB11MessageDataType.video: {
@ -150,38 +150,38 @@ export async function createSendElements(
const uri2LocalRes = await uri2local(thumb)
if (uri2LocalRes.success) thumb = uri2LocalRes.path
}
const res = await SendElementEntities.video(ctx, path, fileName, thumb)
const res = await SendElement.video(ctx, path, fileName, thumb)
deleteAfterSentFiles.push(res.videoElement.filePath)
sendElements.push(res)
}
break
case OB11MessageDataType.voice: {
const { path } = await handleOb11FileLikeMessage(ctx, sendMsg, { deleteAfterSentFiles })
sendElements.push(await SendElementEntities.ptt(ctx, path))
sendElements.push(await SendElement.ptt(ctx, path))
}
break
case OB11MessageDataType.json: {
sendElements.push(SendElementEntities.ark(sendMsg.data.data))
sendElements.push(SendElement.ark(sendMsg.data.data))
}
break
case OB11MessageDataType.dice: {
const resultId = sendMsg.data?.result
sendElements.push(SendElementEntities.dice(resultId))
sendElements.push(SendElement.dice(resultId))
}
break
case OB11MessageDataType.RPS: {
const resultId = sendMsg.data?.result
sendElements.push(SendElementEntities.rps(resultId))
sendElements.push(SendElement.rps(resultId))
}
break
case OB11MessageDataType.contact: {
const { type, id } = sendMsg.data
const data = type === 'qq' ? ctx.ntFriendApi.getBuddyRecommendContact(id) : ctx.ntGroupApi.getGroupRecommendContact(id)
sendElements.push(SendElementEntities.ark(await data))
sendElements.push(SendElement.ark(await data))
}
break
case OB11MessageDataType.shake: {
sendElements.push(SendElementEntities.shake())
sendElements.push(SendElement.shake())
}
break
}

View File

@ -1 +1 @@
export const version = '3.33.10'
export const version = '3.34.0'