Compare commits

..

6 Commits

Author SHA1 Message Date
linyuchen
92d780cf70 fix: download file add referer 2024-09-10 15:01:50 +08:00
idranme
ce6886011f Merge pull request #407 from LLOneBot/dev
3.32.1
2024-09-10 13:44:03 +08:00
idranme
319b275e4f chore: v3.32.1 2024-09-10 13:39:12 +08:00
idranme
9738c3f63c feat: GetProfileLike 2024-09-10 13:36:00 +08:00
idranme
3de054600c chore 2024-09-09 17:06:33 +08:00
idranme
da0ebd3f80 refactor 2024-09-08 23:37:20 +08:00
13 changed files with 116 additions and 106 deletions

View File

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

View File

@@ -7,7 +7,7 @@
"scripts": { "scripts": {
"build": "electron-vite build", "build": "electron-vite build",
"build-mac": "npm run build && npm run deploy-mac", "build-mac": "npm run build && npm run deploy-mac",
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/", "deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/Documents/LiteLoaderQQNT/plugins/LLOneBot/",
"build-win": "npm run build && npm run deploy-win", "build-win": "npm run build && npm run deploy-win",
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"", "deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"",
"format": "prettier -cw .", "format": "prettier -cw .",

View File

@@ -133,7 +133,7 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
if (type === FileUriType.RemoteURL) { if (type === FileUriType.RemoteURL) {
try { try {
const res = await fetchFile(uri) const res = await fetchFile(uri, {'Referer': uri})
const match = res.url.match(/.+\/([^/?]*)(?=\?)?/) const match = res.url.match(/.+\/([^/?]*)(?=\?)?/)
if (match?.[1]) { if (match?.[1]) {
filename ??= match[1].replace(/[/\\:*?"<>|]/g, '_') filename ??= match[1].replace(/[/\\:*?"<>|]/g, '_')

View File

@@ -296,4 +296,19 @@ export class NTQQUserApi extends Service {
} }
}, null]) }, null])
} }
async getProfileLike(uid: string) {
return await invoke('nodeIKernelProfileLikeService/getBuddyProfileLike', [{
req: {
friendUids: [uid],
basic: 1,
vote: 1,
favorite: 0,
userProfile: 1,
type: 2,
start: 0,
limit: 20,
}
}, null])
}
} }

View File

@@ -1,6 +1,6 @@
import { invoke, NTClass, NTMethod } from '../ntcall' import { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { ReceiveCmd } from '../hook' import { ReceiveCmdS } from '../hook'
import { BrowserWindow } from 'electron' import { BrowserWindow } from 'electron'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
@@ -39,7 +39,7 @@ export class NTQQWindowApi extends Service {
async openWindow<R = GeneralCallResult>( async openWindow<R = GeneralCallResult>(
ntQQWindow: NTQQWindow, ntQQWindow: NTQQWindow,
args: unknown[], args: unknown[],
cbCmd: ReceiveCmd | undefined, cbCmd: ReceiveCmdS | undefined,
autoCloseSeconds: number = 2, autoCloseSeconds: number = 2,
) { ) {
const result = await invoke<R>( const result = await invoke<R>(

View File

@@ -185,12 +185,6 @@ class Core extends Service {
}) })
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => { registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => {
const { msgId, chatType, peerUid } = payload.msgRecord
const peer = {
chatType,
peerUid
}
MessageUnique.createMsg(peer, msgId)
if (!this.config.reportSelfMessage) { if (!this.config.reportSelfMessage) {
return return
} }

View File

@@ -6,48 +6,46 @@ import { Dict } from 'cosmokit'
export const hookApiCallbacks: Record<string, (res: any) => void> = {} export const hookApiCallbacks: Record<string, (res: any) => void> = {}
export const ReceiveCmdS = { export enum ReceiveCmdS {
RECENT_CONTACT: 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2', RECENT_CONTACT = 'nodeIKernelRecentContactListener/onRecentContactListChangedVer2',
UPDATE_MSG: 'nodeIKernelMsgListener/onMsgInfoListUpdate', UPDATE_MSG = 'nodeIKernelMsgListener/onMsgInfoListUpdate',
UPDATE_ACTIVE_MSG: 'nodeIKernelMsgListener/onActiveMsgInfoUpdate', UPDATE_ACTIVE_MSG = 'nodeIKernelMsgListener/onActiveMsgInfoUpdate',
NEW_MSG: `nodeIKernelMsgListener/onRecvMsg`, NEW_MSG = 'nodeIKernelMsgListener/onRecvMsg',
NEW_ACTIVE_MSG: `nodeIKernelMsgListener/onRecvActiveMsg`, NEW_ACTIVE_MSG = 'nodeIKernelMsgListener/onRecvActiveMsg',
SELF_SEND_MSG: 'nodeIKernelMsgListener/onAddSendMsg', SELF_SEND_MSG = 'nodeIKernelMsgListener/onAddSendMsg',
USER_INFO: 'nodeIKernelProfileListener/onProfileSimpleChanged', USER_INFO = 'nodeIKernelProfileListener/onProfileSimpleChanged',
USER_DETAIL_INFO: 'nodeIKernelProfileListener/onProfileDetailInfoChanged', USER_DETAIL_INFO = 'nodeIKernelProfileListener/onProfileDetailInfoChanged',
GROUPS: 'nodeIKernelGroupListener/onGroupListUpdate', GROUPS = 'nodeIKernelGroupListener/onGroupListUpdate',
GROUPS_STORE: 'onGroupListUpdate', GROUPS_STORE = 'onGroupListUpdate',
GROUP_MEMBER_INFO_UPDATE: 'nodeIKernelGroupListener/onMemberInfoChange', GROUP_MEMBER_INFO_UPDATE = 'nodeIKernelGroupListener/onMemberInfoChange',
FRIENDS: 'onBuddyListChange', FRIENDS = 'onBuddyListChange',
MEDIA_DOWNLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaDownloadComplete', MEDIA_DOWNLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaDownloadComplete',
UNREAD_GROUP_NOTIFY: 'nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated', UNREAD_GROUP_NOTIFY = 'nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated',
GROUP_NOTIFY: 'nodeIKernelGroupListener/onGroupSingleScreenNotifies', GROUP_NOTIFY = 'nodeIKernelGroupListener/onGroupSingleScreenNotifies',
FRIEND_REQUEST: 'nodeIKernelBuddyListener/onBuddyReqChange', FRIEND_REQUEST = 'nodeIKernelBuddyListener/onBuddyReqChange',
SELF_STATUS: 'nodeIKernelProfileListener/onSelfStatusChanged', SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged',
CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan', CACHE_SCAN_FINISH = 'nodeIKernelStorageCleanListener/onFinishScan',
MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete', MEDIA_UPLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaUploadComplete',
SKEY_UPDATE: 'onSkeyUpdate', SKEY_UPDATE = 'onSkeyUpdate',
} as const }
export type ReceiveCmd = string type NTReturnData = [
{
interface NTQQApiReturnData extends Array<unknown> {
0: {
type: 'request' type: 'request'
eventName: NTClass eventName: NTClass
callbackId?: string callbackId?: string
} },
1: { {
cmdName: ReceiveCmd cmdName: ReceiveCmdS
cmdType: 'event' cmdType: 'event'
payload: unknown payload: unknown
}[] }[]
} ]
const logHook = false const logHook = false
const receiveHooks: Array<{ const receiveHooks: Array<{
method: ReceiveCmd[] method: ReceiveCmdS[]
hookFunc: (payload: any) => void | Promise<void> hookFunc: (payload: any) => void | Promise<void>
id: string id: string
}> = [] }> = []
@@ -58,62 +56,44 @@ const callHooks: Array<{
}> = [] }> = []
export function hookNTQQApiReceive(window: BrowserWindow, onlyLog: boolean) { export function hookNTQQApiReceive(window: BrowserWindow, onlyLog: boolean) {
const originalSend = window.webContents.send window.webContents.send = new Proxy(window.webContents.send, {
const patchSend = (channel: string, ...args: NTQQApiReturnData) => { apply(target, thisArg, args: [channel: string, ...args: NTReturnData]) {
try { try {
const isLogger = args[0]?.eventName?.startsWith('ns-LoggerApi') if (logHook && !args[1]?.eventName?.startsWith('ns-LoggerApi')) {
if (logHook && !isLogger) { log('received ntqq api message', args)
log(`received ntqq api message: ${channel}`, args) }
} } catch { }
} catch { } if (!onlyLog) {
if (!onlyLog) { if (args[2] instanceof Array) {
if (args?.[1] instanceof Array) { for (const receiveData of args[2]) {
for (const receiveData of args[1]) { const ntMethodName = receiveData.cmdName
const ntQQApiMethodName = receiveData.cmdName for (const hook of receiveHooks) {
// log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData)) if (hook.method.includes(ntMethodName)) {
for (const hook of receiveHooks) { Promise.resolve(hook.hookFunc(receiveData.payload))
if (hook.method.includes(ntQQApiMethodName)) { }
new Promise(resolve => {
try {
hook.hookFunc(receiveData.payload)
} catch (e) {
log('hook error', ntQQApiMethodName, (e as Error).stack?.toString())
}
resolve(undefined)
}).then()
} }
} }
} }
} if (args[1]?.callbackId) {
if (args[0]?.callbackId) { const callbackId = args[1].callbackId
// log("hookApiCallback", hookApiCallbacks, args) if (hookApiCallbacks[callbackId]) {
const callbackId = args[0].callbackId Promise.resolve(hookApiCallbacks[callbackId](args[2]))
if (hookApiCallbacks[callbackId]) { delete hookApiCallbacks[callbackId]
new Promise(resolve => { }
hookApiCallbacks[callbackId](args[1])
resolve(undefined)
}).then()
delete hookApiCallbacks[callbackId]
} }
} }
} return target.apply(thisArg, args)
originalSend.call(window.webContents, channel, ...args) },
} })
window.webContents.send = patchSend
} }
export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) { export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
// 监听调用NTQQApi
const webContents = window.webContents as Dict const webContents = window.webContents as Dict
const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message'] const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message']
const proxyIpcMsg = new Proxy(ipc_message_proxy, { const proxyIpcMsg = new Proxy(ipc_message_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
// console.log(thisArg, args); const isLogger = args[3]?.[0]?.eventName?.startsWith('ns-LoggerApi')
let isLogger = false
try {
isLogger = args[3][0].eventName.startsWith('ns-LoggerApi')
} catch (e) { }
if (!isLogger) { if (!isLogger) {
try { try {
logHook && log('call NTQQ api', thisArg, args) logHook && log('call NTQQ api', thisArg, args)
@@ -121,21 +101,14 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
if (!onlyLog) { if (!onlyLog) {
try { try {
const _args: unknown[] = args[3][1] const _args: unknown[] = args[3][1]
const cmdName: NTMethod = _args[0] as NTMethod const cmdName = _args[0] as NTMethod
const callParams = _args.slice(1) const callParams = _args.slice(1)
callHooks.forEach((hook) => { callHooks.forEach((hook) => {
if (hook.method.includes(cmdName)) { if (hook.method.includes(cmdName)) {
new Promise(resolve => { Promise.resolve(hook.hookFunc(callParams))
try {
hook.hookFunc(callParams)
} catch (e) {
log('hook call error', e, _args)
}
resolve(undefined)
}).then()
} }
}) })
} catch (e) { } } catch { }
} }
} }
return target.apply(thisArg, args) return target.apply(thisArg, args)
@@ -147,7 +120,7 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
webContents._events['-ipc-message'] = proxyIpcMsg webContents._events['-ipc-message'] = proxyIpcMsg
} }
const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke'] /*const ipc_invoke_proxy = webContents._events['-ipc-invoke']?.[0] || webContents._events['-ipc-invoke']
const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, { const proxyIpcInvoke = new Proxy(ipc_invoke_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
//HOOK_LOG && log('call NTQQ invoke api', thisArg, args) //HOOK_LOG && log('call NTQQ invoke api', thisArg, args)
@@ -157,9 +130,7 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
}, },
}) })
const ret = target.apply(thisArg, args) const ret = target.apply(thisArg, args)
/*try { //HOOK_LOG && log('call NTQQ invoke api return', ret)
HOOK_LOG && log('call NTQQ invoke api return', ret)
} catch (e) { }*/
return ret return ret
}, },
}) })
@@ -167,11 +138,11 @@ export function hookNTQQApiCall(window: BrowserWindow, onlyLog: boolean) {
webContents._events['-ipc-invoke'][0] = proxyIpcInvoke webContents._events['-ipc-invoke'][0] = proxyIpcInvoke
} else { } else {
webContents._events['-ipc-invoke'] = proxyIpcInvoke webContents._events['-ipc-invoke'] = proxyIpcInvoke
} }*/
} }
export function registerReceiveHook<PayloadType>( export function registerReceiveHook<PayloadType>(
method: ReceiveCmd | ReceiveCmd[], method: string | string[],
hookFunc: (payload: PayloadType) => void, hookFunc: (payload: PayloadType) => void,
): string { ): string {
const id = randomUUID() const id = randomUUID()
@@ -179,7 +150,7 @@ export function registerReceiveHook<PayloadType>(
method = [method] method = [method]
} }
receiveHooks.push({ receiveHooks.push({
method, method: method as ReceiveCmdS[],
hookFunc, hookFunc,
id, id,
}) })

View File

@@ -136,7 +136,6 @@ export function invoke<
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => { const secondCallback = () => {
const hookId = registerReceiveHook<R>(options.cbCmd!, (payload) => { const hookId = registerReceiveHook<R>(options.cbCmd!, (payload) => {
// log(methodName, "second callback", cbCmd, payload, cmdCB);
if (options.cmdCB) { if (options.cmdCB) {
if (options.cmdCB(payload, result)) { if (options.cmdCB(payload, result)) {
removeReceiveHook(hookId) removeReceiveHook(hookId)

View File

@@ -1,5 +1,6 @@
import { BuddyProfileLikeReq } from '../types' import { BuddyProfileLikeReq } from '../types'
import { GeneralCallResult } from './common' import { GeneralCallResult } from './common'
import { Dict } from 'cosmokit'
export interface NodeIKernelProfileLikeService { export interface NodeIKernelProfileLikeService {
addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void addKernelProfileLikeListener(listener: NodeIKernelProfileLikeService): void
@@ -10,8 +11,18 @@ export interface NodeIKernelProfileLikeService {
getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & { getBuddyProfileLike(req: BuddyProfileLikeReq): Promise<GeneralCallResult & {
info: { info: {
userLikeInfos: Array<unknown>, userLikeInfos: {
friendMaxVotes: number, uid: string
time: string
favoriteInfo: {
total_count: number
last_time: number
today_count: number
userInfos: Dict[]
}
voteInfo: Dict
}[]
friendMaxVotes: number
start: number start: number
} }
}> }>

View File

@@ -62,6 +62,7 @@ import { GetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain'
import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles' import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles'
import { SetOnlineStatus } from './llonebot/SetOnlineStatus' import { SetOnlineStatus } from './llonebot/SetOnlineStatus'
import { SendGroupNotice } from './go-cqhttp/SendGroupNotice' import { SendGroupNotice } from './go-cqhttp/SendGroupNotice'
import { GetProfileLike } from './llonebot/GetProfileLike'
export function initActionMap(adapter: Adapter) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@@ -74,6 +75,7 @@ export function initActionMap(adapter: Adapter) {
new GetFriendWithCategory(adapter), new GetFriendWithCategory(adapter),
new GetEvent(adapter), new GetEvent(adapter),
new SetOnlineStatus(adapter), new SetOnlineStatus(adapter),
new GetProfileLike(adapter),
// onebot11 // onebot11
new SendLike(adapter), new SendLike(adapter),
new GetMsg(adapter), new GetMsg(adapter),

View File

@@ -0,0 +1,17 @@
import BaseAction from '../BaseAction'
import { ActionName } from '../types'
import { selfInfo } from '@/common/globalVars'
import { Dict } from 'cosmokit'
export class GetProfileLike extends BaseAction<void, Dict[]> {
actionName = ActionName.GetProfileLike
async _handle() {
const ret = await this.ctx.ntUserApi.getProfileLike(selfInfo.uid)
const listdata = ret.info.userLikeInfos[0].favoriteInfo.userInfos
for (const item of listdata) {
item.uin = Number(await this.ctx.ntUserApi.getUinByUid(item.uid)) || 0
}
return listdata
}
}

View File

@@ -20,6 +20,7 @@ export enum ActionName {
GetFriendsWithCategory = 'get_friends_with_category', GetFriendsWithCategory = 'get_friends_with_category',
GetEvent = 'get_event', GetEvent = 'get_event',
SetOnlineStatus = 'set_online_status', SetOnlineStatus = 'set_online_status',
GetProfileLike = 'get_profile_like',
// onebot 11 // onebot 11
SendLike = 'send_like', SendLike = 'send_like',
GetLoginInfo = 'get_login_info', GetLoginInfo = 'get_login_info',

View File

@@ -1 +1 @@
export const version = '3.32.0' export const version = '3.32.2'