This commit is contained in:
idranme 2024-08-18 20:58:26 +08:00
parent a56eac0251
commit 2245d0d3de
No known key found for this signature in database
GPG Key ID: 926F7B5B668E495F
10 changed files with 83 additions and 69 deletions

View File

@ -1,6 +1,6 @@
# LLOneBot # LLOneBot
LiteLoaderQQNT 插件,实现 OneBot 11 协议,用 QQ 机器人开发 LiteLoaderQQNT 插件,实现 OneBot 11 协议,用 QQ 机器人开发
> [!CAUTION]\ > [!CAUTION]\
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息** > **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息**

View File

@ -6,7 +6,7 @@ const manifest = {
type: 'extension', type: 'extension',
name: 'LLOneBot', name: 'LLOneBot',
slug: 'LLOneBot', slug: 'LLOneBot',
description: '实现 OneBot 11 协议,用 QQ 机器人开发', description: '实现 OneBot 11 协议,用 QQ 机器人开发',
version, version,
icon: './icon.webp', icon: './icon.webp',
authors: [ authors: [

View File

@ -66,6 +66,7 @@ export async function getGroupMember(groupCode: string | number, memberUinOrUid:
let member = getMember() let member = getMember()
if (!member) { if (!member) {
members = await NTQQGroupApi.getGroupMembers(groupCodeStr) members = await NTQQGroupApi.getGroupMembers(groupCodeStr)
groupMembers.set(groupCodeStr, members)
member = getMember() member = getMember()
} }
return member return member

View File

@ -16,7 +16,7 @@ export class RequestUtil {
const redirectUrl = new URL(res.headers.location, url); const redirectUrl = new URL(res.headers.location, url);
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
// 合并重定向过程中的cookies // 合并重定向过程中的cookies
log('redirectCookies', redirectCookies) //log('redirectCookies', redirectCookies)
cookies = { ...cookies, ...redirectCookies }; cookies = { ...cookies, ...redirectCookies };
resolve(cookies); resolve(cookies);
}); });
@ -33,7 +33,7 @@ export class RequestUtil {
}); });
if (res.headers['set-cookie']) { if (res.headers['set-cookie']) {
// console.log(res.headers['set-cookie']); // console.log(res.headers['set-cookie']);
log('set-cookie', url, res.headers['set-cookie']); //log('set-cookie', url, res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => { res.headers['set-cookie'].forEach((cookie) => {
const parts = cookie.split(';')[0].split('='); const parts = cookie.split(';')[0].split('=');
const key = parts[0]; const key = parts[0];

View File

@ -138,45 +138,47 @@ export class WebApi {
return ret return ret
} }
@CacheClassFuncAsync(3600 * 1000, 'webapi_get_group_members')
static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> { static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
//logDebug('webapi 获取群成员', GroupCode) const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
let MemberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>() const cookieObject = await NTQQUserApi.getCookies('qun.qq.com')
try { const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
const CookiesObject = await NTQQUserApi.getCookies('qun.qq.com')
const CookieValue = Object.entries(CookiesObject).map(([key, value]) => `${key}=${value}`).join('; ')
const Bkn = WebApi.genBkn(CookiesObject.skey)
const retList: Promise<WebApiGroupMemberRet>[] = [] const retList: Promise<WebApiGroupMemberRet>[] = []
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>('https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?st=0&end=40&sort=1&gc=' + GroupCode + '&bkn=' + Bkn, 'POST', '', { 'Cookie': CookieValue }); const params = new URLSearchParams({
st: '0',
end: '40',
sort: '1',
gc: GroupCode,
bkn: WebApi.genBkn(cookieObject.skey)
})
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${params}`, 'POST', '', { 'Cookie': cookieStr })
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) { if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
return [] return []
} else { } else {
for (const key in fastRet.mems) { for (const member of fastRet.mems) {
MemberData.push(fastRet.mems[key]) memberData.push(member)
} }
} }
//初始化获取PageNum const pageNum = Math.ceil(fastRet.count / 40)
const PageNum = Math.ceil(fastRet.count / 40)
//遍历批量请求 //遍历批量请求
for (let i = 2; i <= PageNum; i++) { for (let i = 2; i <= pageNum; i++) {
const ret: Promise<WebApiGroupMemberRet> = RequestUtil.HttpGetJson<WebApiGroupMemberRet>('https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?st=' + (i - 1) * 40 + '&end=' + i * 40 + '&sort=1&gc=' + GroupCode + '&bkn=' + Bkn, 'POST', '', { 'Cookie': CookieValue }); params.set('st', String((i - 1) * 40))
params.set('end', String(i * 40))
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>(`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${params}`, 'POST', '', { 'Cookie': cookieStr })
retList.push(ret) retList.push(ret)
} }
//批量等待 //批量等待
for (let i = 1; i <= PageNum; i++) { for (let i = 1; i <= pageNum; i++) {
const ret = await (retList[i]) const ret = await (retList[i])
if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) { if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) {
continue continue
} }
for (const key in ret.mems) { for (const member of ret.mems) {
MemberData.push(ret.mems[key]) memberData.push(member)
} }
} }
} catch { return memberData
return MemberData
}
return MemberData
} }
// public static async addGroupDigest(groupCode: string, msgSeq: string) { // public static async addGroupDigest(groupCode: string, msgSeq: string) {
// const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`; // const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`;
// const res = await this.request(url); // const res = await this.request(url);

View File

@ -10,7 +10,7 @@ export class OB11Response {
data: data, data: data,
message: message, message: message,
wording: message, wording: message,
echo: null, echo: undefined,
} }
} }

View File

@ -1,33 +1,45 @@
import { OB11GroupMember } from '../../types' import { OB11GroupMember } from '../../types'
import { getGroupMember } from '../../../common/data' import { getGroupMember, getSelfUid } from '@/common/data'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQUserApi } from '../../../ntqqapi/api/user' import { NTQQUserApi, WebApi } from '@/ntqqapi/api'
import { isNull } from '@/common/utils/helper'
import { log } from '../../../common/utils/log' import { log } from '../../../common/utils/log'
import { isNull } from '../../../common/utils/helper'
export interface PayloadType { interface Payload {
group_id: number group_id: number | string
user_id: number user_id: number | string
} }
class GetGroupMemberInfo extends BaseAction<PayloadType, OB11GroupMember> { class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
actionName = ActionName.GetGroupMemberInfo actionName = ActionName.GetGroupMemberInfo
protected async _handle(payload: PayloadType) { protected async _handle(payload: Payload) {
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString()) const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString())
if (member) { if (member) {
if (isNull(member.sex)) { if (isNull(member.sex)) {
log('获取群成员详细信息') //log('获取群成员详细信息')
let info = await NTQQUserApi.getUserDetailInfo(member.uid, true) const info = await NTQQUserApi.getUserDetailInfo(member.uid, true)
log('群成员详细信息结果', info) //log('群成员详细信息结果', info)
Object.assign(member, info) Object.assign(member, info)
} }
const ret = OB11Constructor.groupMember(payload.group_id.toString(), member) const ret = OB11Constructor.groupMember(payload.group_id.toString(), member)
const self = await getGroupMember(payload.group_id.toString(), getSelfUid())
if (self?.role === 3 || self?.role === 4) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString())
const target = webGroupMembers.find(e => e?.uin && e.uin === ret.user_id)
log(target)
if (target) {
ret.join_time = target.join_time
ret.last_sent_time = target.last_speak_time
ret.qage = target.qage
ret.level = target.lv.level.toString()
}
}
const date = Math.round(Date.now() / 1000) const date = Math.round(Date.now() / 1000)
ret.last_sent_time = Number(member.lastSpeakTime || date) ret.last_sent_time ||= Number(member.lastSpeakTime || date)
ret.join_time = Number(member.joinTime || date) ret.join_time ||= Number(member.joinTime || date)
return ret return ret
} else { } else {
throw `群成员${payload.user_id}不存在` throw `群成员${payload.user_id}不存在`

View File

@ -289,12 +289,12 @@ export async function sendMsg(
log('文件大小计算失败', e, fileElement) log('文件大小计算失败', e, fileElement)
} }
} }
log('发送消息总大小', totalSize, 'bytes') //log('发送消息总大小', totalSize, 'bytes')
let timeout = ((totalSize / 1024 / 100) * 1000) + 5000 // 100kb/s const timeout = 10000 + (totalSize / 1024 / 256 * 1000) // 10s Basic Timeout + PredictTime( For File 512kb/s )
log('设置消息超时时间', timeout) //log('设置消息超时时间', timeout)
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout) const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout)
log('消息发送结果', returnMsg)
returnMsg.msgShortId = MessageUnique.createMsg(peer, returnMsg.msgId) returnMsg.msgShortId = MessageUnique.createMsg(peer, returnMsg.msgId)
log('消息发送', returnMsg.msgShortId)
deleteAfterSentFiles.map(path => fsPromise.unlink(path)) deleteAfterSentFiles.map(path => fsPromise.unlink(path))
return returnMsg return returnMsg
} }

View File

@ -61,13 +61,15 @@ export function postOb11Event(msg: PostEventType, reportSelf = false, postWs = t
body: msgStr, body: msgStr,
}).then( }).then(
async (res) => { async (res) => {
log(`新消息事件HTTP上报成功: ${host} `, msgStr) if (msg.post_type) {
log(`HTTP 事件上报: ${host} `, msg.post_type)
}
try { try {
const resJson = await res.json() const resJson = await res.json()
log(`新消息事件HTTP上报返回快速操作: `, JSON.stringify(resJson)) log(`新消息事件HTTP上报返回快速操作: `, JSON.stringify(resJson))
handleQuickOperation(msg as QuickOperationEvent, resJson).then().catch(log); handleQuickOperation(msg as QuickOperationEvent, resJson).then().catch(log);
} catch (e) { } catch (e) {
log(`新消息事件HTTP上报没有返回快速操作不需要处理`) //log(`新消息事件HTTP上报没有返回快速操作不需要处理`)
return return
} }
}, },

View File

@ -1,18 +1,15 @@
import { WebSocket as WebSocketClass } from 'ws' import { WebSocket as WebSocketClass } from 'ws'
import { OB11Response } from '../../action/OB11Response'
import { PostEventType } from '../post-ob11-event' import { PostEventType } from '../post-ob11-event'
import { log } from '../../../common/utils/log' import { log } from '@/common/utils/log'
import { isNull } from '../../../common/utils/helper' import { OB11Return } from '../../types'
export function wsReply(wsClient: WebSocketClass, data: OB11Response | PostEventType) { export function wsReply(wsClient: WebSocketClass, data: OB11Return<any> | PostEventType) {
try { try {
const packet = Object.assign({}, data) wsClient.send(JSON.stringify(data))
if (isNull(packet['echo'])) { if (data['post_type']) {
delete packet['echo'] log('WebSocket 事件上报', wsClient.url ?? '', data['post_type'])
} }
wsClient.send(JSON.stringify(packet))
//log('ws 消息上报', wsClient.url || '', data)
} catch (e: any) { } catch (e: any) {
log('websocket 回复失败', e.stack, data) log('WebSocket 上报失败', e.stack, data)
} }
} }