mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
debe3a8597 | ||
![]() |
f36c5e849f | ||
![]() |
abbd6797c4 | ||
![]() |
fdb7784a7d | ||
![]() |
92b49015b0 | ||
![]() |
1765ffff7b | ||
![]() |
3024316b5b | ||
![]() |
9a0d89bfbf | ||
![]() |
807ef3b700 | ||
![]() |
948f10d4e3 | ||
![]() |
0f99b5cb87 | ||
![]() |
6413b0ff82 | ||
![]() |
39713d8e11 | ||
![]() |
739a497af6 | ||
![]() |
de2fe9b0aa | ||
![]() |
44448895a0 | ||
![]() |
cfd9097769 | ||
![]() |
627042fd25 | ||
![]() |
b51ce24d0c | ||
![]() |
fc0881eccc | ||
![]() |
6b8509d2b2 | ||
![]() |
cf1d67a5cf | ||
![]() |
473ebd25b8 | ||
![]() |
d4427cfff4 | ||
![]() |
9d2e9786cc | ||
![]() |
9968f714c7 | ||
![]() |
bd212c4bf3 | ||
![]() |
32c7f904db | ||
![]() |
2ef017282f | ||
![]() |
9672f67a23 | ||
![]() |
6e5cfd827c | ||
![]() |
5402bef4a9 | ||
![]() |
4194512cce | ||
![]() |
b3aad8b0d9 | ||
![]() |
1489c6df25 | ||
![]() |
2e225045e6 | ||
![]() |
11ed06148c | ||
![]() |
a3fc018186 |
@@ -1,4 +1,4 @@
|
||||
# LLOneBot API
|
||||
# LLOneBot
|
||||
|
||||
LiteLoaderQQNT插件,使你的NTQQ支持OneBot11协议进行QQ机器人开发
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot v3.24.3",
|
||||
"name": "LLOneBot v3.26.1",
|
||||
"slug": "LLOneBot",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
||||
"version": "3.24.3",
|
||||
"version": "3.26.1",
|
||||
"icon": "./icon.jpg",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -1,9 +1,18 @@
|
||||
import { type Friend, type FriendRequest, type Group, type GroupMember, type SelfInfo } from '../ntqqapi/types'
|
||||
import {
|
||||
CategoryFriend,
|
||||
type Friend,
|
||||
type FriendRequest,
|
||||
type Group,
|
||||
type GroupMember,
|
||||
type SelfInfo,
|
||||
User,
|
||||
} from '../ntqqapi/types'
|
||||
import { type FileCache, type LLOneBotError } from './types'
|
||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||
import { log } from './utils/log'
|
||||
import { isNumeric } from './utils/helper'
|
||||
import { NTQQFriendApi } from '../ntqqapi/api'
|
||||
import { WebApiGroupMember } from '@/ntqqapi/api/webapi'
|
||||
|
||||
export const selfInfo: SelfInfo = {
|
||||
uid: '',
|
||||
@@ -11,6 +20,10 @@ export const selfInfo: SelfInfo = {
|
||||
nick: '',
|
||||
online: true,
|
||||
}
|
||||
export const WebGroupData = {
|
||||
GroupData: new Map<string, Array<WebApiGroupMember>>(),
|
||||
GroupTime: new Map<string, number>(),
|
||||
}
|
||||
export let groups: Group[] = []
|
||||
export let friends: Friend[] = []
|
||||
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
|
||||
@@ -48,7 +61,8 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
|
||||
if (group) {
|
||||
groups.push(group)
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return group
|
||||
}
|
||||
@@ -106,3 +120,5 @@ export function getUidByUin(uin: string) {
|
||||
}
|
||||
|
||||
export let tempGroupCodeMap: Record<string, string> = {} // peerUid => 群号
|
||||
|
||||
export let rawFriends: CategoryFriend[] = []
|
@@ -45,5 +45,6 @@ export interface FileCache {
|
||||
fileUuid?: string
|
||||
url?: string
|
||||
msgId?: string
|
||||
elementId: string
|
||||
downloadFunc?: () => Promise<void>
|
||||
}
|
||||
|
@@ -8,6 +8,8 @@ export const exePath = process.execPath;
|
||||
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
||||
let configVersionInfoPath;
|
||||
|
||||
|
||||
|
||||
if (os.platform() !== 'linux') {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
|
||||
} else {
|
||||
|
@@ -109,7 +109,7 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
||||
}
|
||||
}
|
||||
}
|
||||
const fetchRes = await net.fetch(url, headers)
|
||||
const fetchRes = await net.fetch(url, {headers})
|
||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
|
||||
|
||||
const blob = await fetchRes.blob()
|
||||
|
@@ -65,3 +65,33 @@ export function wrapText(str: string, maxLength: number): string {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 函数缓存装饰器,根据方法名、参数、自定义key生成缓存键,在一定时间内返回缓存结果
|
||||
* @param ttl 超时时间,单位毫秒
|
||||
* @param customKey 自定义缓存键前缀,可为空,防止方法名参数名一致时导致缓存键冲突
|
||||
* @returns 处理后缓存或调用原方法的结果
|
||||
*/
|
||||
export function cacheFunc(ttl: number, customKey: string='') {
|
||||
const cache = new Map<string, { expiry: number; value: any }>();
|
||||
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
||||
const originalMethod = descriptor.value;
|
||||
const className = target.constructor.name; // 获取类名
|
||||
const methodName = propertyKey; // 获取方法名
|
||||
descriptor.value = async function (...args: any[]){
|
||||
const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && cached.expiry > Date.now()) {
|
||||
return cached.value;
|
||||
} else {
|
||||
const result = await originalMethod.apply(this, args);
|
||||
cache.set(cacheKey, { value: result, expiry: Date.now() + ttl });
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
87
src/common/utils/request.ts
Normal file
87
src/common/utils/request.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import https from 'node:https';
|
||||
import http from 'node:http';
|
||||
|
||||
export class RequestUtil {
|
||||
// 适用于获取服务器下发cookies时获取,仅GET
|
||||
static async HttpsGetCookies(url: string): Promise<Map<string, string>> {
|
||||
return new Promise<Map<string, string>>((resolve, reject) => {
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
protocol.get(url, (res) => {
|
||||
const cookiesHeader = res.headers['set-cookie'];
|
||||
if (!cookiesHeader) {
|
||||
resolve(new Map<string, string>());
|
||||
} else {
|
||||
const cookiesMap = new Map<string, string>();
|
||||
cookiesHeader.forEach((cookieStr) => {
|
||||
cookieStr.split(';').forEach((cookiePart) => {
|
||||
const trimmedPart = cookiePart.trim();
|
||||
if (trimmedPart.includes('=')) {
|
||||
const [key, value] = trimmedPart.split('=').map(part => part.trim());
|
||||
cookiesMap.set(key, decodeURIComponent(value)); // 解码cookie值
|
||||
}
|
||||
});
|
||||
});
|
||||
resolve(cookiesMap);
|
||||
}
|
||||
}).on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 请求和回复都是JSON data传原始内容 自动编码json
|
||||
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
|
||||
let option = new URL(url);
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
const options = {
|
||||
hostname: option.hostname,
|
||||
port: option.port,
|
||||
path: option.href,
|
||||
method: method,
|
||||
headers: headers
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = protocol.request(options, (res: any) => {
|
||||
let responseBody = '';
|
||||
res.on('data', (chunk: string | Buffer) => {
|
||||
responseBody += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
if (isJsonRet) {
|
||||
const responseJson = JSON.parse(responseBody);
|
||||
resolve(responseJson as T);
|
||||
} else {
|
||||
resolve(responseBody as T);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||||
}
|
||||
} catch (parseError) {
|
||||
reject(parseError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error: any) => {
|
||||
reject(error);
|
||||
});
|
||||
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
||||
if (isArgJson) {
|
||||
req.write(JSON.stringify(data));
|
||||
} else {
|
||||
req.write(data);
|
||||
}
|
||||
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// 请求返回都是原始内容
|
||||
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) {
|
||||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
||||
export const systemPlatform = os.platform();
|
||||
export const cpuArch = os.arch();
|
||||
export const systemVersion = os.release();
|
||||
export const hostname = os.hostname();
|
||||
// export const hostname = os.hostname(); // win7不支持
|
||||
const homeDir = os.homedir();
|
||||
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||
export const systemName = os.type();
|
||||
|
@@ -5,7 +5,7 @@ import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from '.'
|
||||
import compressing from 'compressing'
|
||||
|
||||
const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
|
||||
const checkVersionMirrorHosts = ['https://521github.com']
|
||||
const checkVersionMirrorHosts = ['https://kkgithub.com']
|
||||
|
||||
export async function checkNewVersion() {
|
||||
const latestVersionText = await getRemoteVersion()
|
||||
|
@@ -38,7 +38,7 @@ import {
|
||||
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
|
||||
import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent'
|
||||
import { OB11GroupRecallNoticeEvent } from '../onebot11/event/notice/OB11GroupRecallNoticeEvent'
|
||||
import { postOB11Event } from '../onebot11/server/postOB11Event'
|
||||
import { postOb11Event } from '../onebot11/server/post-ob11-event'
|
||||
import { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
|
||||
import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent'
|
||||
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
|
||||
@@ -55,6 +55,7 @@ import { log } from '../common/utils/log'
|
||||
import { getConfigUtil } from '../common/config'
|
||||
import { checkFfmpeg } from '../common/utils/video'
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||
import '../ntqqapi/native/wrapper'
|
||||
|
||||
let running = false
|
||||
|
||||
@@ -141,7 +142,8 @@ function onLoad() {
|
||||
.catch((e) => {
|
||||
log('保存设置失败', e.stack)
|
||||
})
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -168,12 +170,8 @@ function onLoad() {
|
||||
|
||||
OB11Constructor.message(message)
|
||||
.then((msg) => {
|
||||
if (debug) {
|
||||
msg.raw = message
|
||||
} else {
|
||||
if (msg.message.length === 0) {
|
||||
return
|
||||
}
|
||||
if (!debug && msg.message.length === 0) {
|
||||
return
|
||||
}
|
||||
const isSelfMsg = msg.user_id.toString() == selfInfo.uin
|
||||
if (isSelfMsg && !reportSelfMessage) {
|
||||
@@ -182,20 +180,20 @@ function onLoad() {
|
||||
if (isSelfMsg) {
|
||||
msg.target_id = parseInt(message.peerUin)
|
||||
}
|
||||
postOB11Event(msg)
|
||||
postOb11Event(msg)
|
||||
// log("post msg", msg)
|
||||
})
|
||||
.catch((e) => log('constructMessage error: ', e.stack.toString()))
|
||||
OB11Constructor.GroupEvent(message).then((groupEvent) => {
|
||||
if (groupEvent) {
|
||||
// log("post group event", groupEvent);
|
||||
postOB11Event(groupEvent)
|
||||
postOb11Event(groupEvent)
|
||||
}
|
||||
})
|
||||
OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
|
||||
if (friendAddEvent) {
|
||||
// log("post friend add event", friendAddEvent);
|
||||
postOB11Event(friendAddEvent)
|
||||
postOb11Event(friendAddEvent)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -209,10 +207,11 @@ function onLoad() {
|
||||
let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent
|
||||
if (isGroup) {
|
||||
pokeEvent = new OB11GroupPokeEvent(parseInt(id))
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
pokeEvent = new OB11FriendPokeEvent(parseInt(id))
|
||||
}
|
||||
postOB11Event(pokeEvent)
|
||||
postOb11Event(pokeEvent)
|
||||
})
|
||||
}
|
||||
registerReceiveHook<{
|
||||
@@ -243,7 +242,7 @@ function onLoad() {
|
||||
OB11Constructor.RecallEvent(message).then((recallEvent) => {
|
||||
if (recallEvent) {
|
||||
log('post recall event', recallEvent)
|
||||
postOB11Event(recallEvent)
|
||||
postOb11Event(recallEvent)
|
||||
}
|
||||
})
|
||||
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
|
||||
@@ -321,11 +320,13 @@ function onLoad() {
|
||||
? 'unset'
|
||||
: 'set'
|
||||
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
|
||||
postOB11Event(groupAdminNoticeEvent, true)
|
||||
} else {
|
||||
postOb11Event(groupAdminNoticeEvent, true)
|
||||
}
|
||||
else {
|
||||
log('获取群通知的成员信息失败', notify, getGroup(notify.group.groupCode))
|
||||
}
|
||||
} else if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
|
||||
}
|
||||
else if (notify.type == GroupNotifyTypes.MEMBER_EXIT || notify.type == GroupNotifyTypes.KICK_MEMBER) {
|
||||
log('有成员退出通知', notify)
|
||||
try {
|
||||
const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid)
|
||||
@@ -343,11 +344,12 @@ function onLoad() {
|
||||
parseInt(operatorId),
|
||||
subType,
|
||||
)
|
||||
postOB11Event(groupDecreaseEvent, true)
|
||||
postOb11Event(groupDecreaseEvent, true)
|
||||
} catch (e) {
|
||||
log('获取群通知的成员信息失败', notify, e.stack.toString())
|
||||
}
|
||||
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
|
||||
}
|
||||
else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
|
||||
log('有加群请求')
|
||||
let groupRequestEvent = new OB11GroupRequestEvent()
|
||||
groupRequestEvent.group_id = parseInt(notify.group.groupCode)
|
||||
@@ -361,8 +363,9 @@ function onLoad() {
|
||||
groupRequestEvent.sub_type = 'add'
|
||||
groupRequestEvent.comment = notify.postscript
|
||||
groupRequestEvent.flag = notify.seq
|
||||
postOB11Event(groupRequestEvent)
|
||||
} else if (notify.type == GroupNotifyTypes.INVITE_ME) {
|
||||
postOb11Event(groupRequestEvent)
|
||||
}
|
||||
else if (notify.type == GroupNotifyTypes.INVITE_ME) {
|
||||
log('收到邀请我加群通知')
|
||||
let groupInviteEvent = new OB11GroupRequestEvent()
|
||||
groupInviteEvent.group_id = parseInt(notify.group.groupCode)
|
||||
@@ -373,13 +376,14 @@ function onLoad() {
|
||||
groupInviteEvent.user_id = parseInt(user_id)
|
||||
groupInviteEvent.sub_type = 'invite'
|
||||
groupInviteEvent.flag = notify.seq
|
||||
postOB11Event(groupInviteEvent)
|
||||
postOb11Event(groupInviteEvent)
|
||||
}
|
||||
} catch (e) {
|
||||
log('解析群通知失败', e.stack.toString())
|
||||
}
|
||||
}
|
||||
} else if (payload.doubt) {
|
||||
}
|
||||
else if (payload.doubt) {
|
||||
// 可能有群管理员变动
|
||||
}
|
||||
})
|
||||
@@ -399,7 +403,7 @@ function onLoad() {
|
||||
}
|
||||
friendRequestEvent.flag = flag
|
||||
friendRequestEvent.comment = req.extWords
|
||||
postOB11Event(friendRequestEvent)
|
||||
postOb11Event(friendRequestEvent)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -472,7 +476,8 @@ function onLoad() {
|
||||
|
||||
getUserNick().then()
|
||||
start().then()
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
setTimeout(init, 1000)
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,54 @@ export interface Peer {
|
||||
guildId?: ''
|
||||
}
|
||||
|
||||
async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 10000) {
|
||||
// 等待上一个相同的peer发送完
|
||||
const peerUid = peer.peerUid
|
||||
let checkLastSendUsingTime = 0
|
||||
const waitLastSend = async () => {
|
||||
if (checkLastSendUsingTime > timeout) {
|
||||
throw '发送超时'
|
||||
}
|
||||
let lastSending = sendMessagePool[peer.peerUid]
|
||||
if (lastSending) {
|
||||
// log("有正在发送的消息,等待中...")
|
||||
await sleep(500)
|
||||
checkLastSendUsingTime += 500
|
||||
return await waitLastSend()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
await waitLastSend()
|
||||
|
||||
let sentMessage: RawMessage = null
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
delete sendMessagePool[peerUid]
|
||||
sentMessage = rawMessage
|
||||
}
|
||||
|
||||
let checkSendCompleteUsingTime = 0
|
||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||
if (sentMessage) {
|
||||
if (waitComplete) {
|
||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
||||
return sentMessage
|
||||
}
|
||||
} else {
|
||||
return sentMessage
|
||||
}
|
||||
// log(`给${peerUid}发送消息成功`)
|
||||
}
|
||||
checkSendCompleteUsingTime += 500
|
||||
if (checkSendCompleteUsingTime > timeout) {
|
||||
throw '发送超时'
|
||||
}
|
||||
await sleep(500)
|
||||
return await checkSendComplete()
|
||||
}
|
||||
return checkSendComplete();
|
||||
}
|
||||
|
||||
export class NTQQMsgApi {
|
||||
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||
@@ -116,52 +164,7 @@ export class NTQQMsgApi {
|
||||
}
|
||||
|
||||
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
|
||||
const peerUid = peer.peerUid
|
||||
|
||||
// 等待上一个相同的peer发送完
|
||||
let checkLastSendUsingTime = 0
|
||||
const waitLastSend = async () => {
|
||||
if (checkLastSendUsingTime > timeout) {
|
||||
throw '发送超时'
|
||||
}
|
||||
let lastSending = sendMessagePool[peer.peerUid]
|
||||
if (lastSending) {
|
||||
// log("有正在发送的消息,等待中...")
|
||||
await sleep(500)
|
||||
checkLastSendUsingTime += 500
|
||||
return await waitLastSend()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
await waitLastSend()
|
||||
|
||||
let sentMessage: RawMessage = null
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
delete sendMessagePool[peerUid]
|
||||
sentMessage = rawMessage
|
||||
}
|
||||
|
||||
let checkSendCompleteUsingTime = 0
|
||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||
if (sentMessage) {
|
||||
if (waitComplete) {
|
||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
||||
return sentMessage
|
||||
}
|
||||
} else {
|
||||
return sentMessage
|
||||
}
|
||||
// log(`给${peerUid}发送消息成功`)
|
||||
}
|
||||
checkSendCompleteUsingTime += 500
|
||||
if (checkSendCompleteUsingTime > timeout) {
|
||||
throw '发送超时'
|
||||
}
|
||||
await sleep(500)
|
||||
return await checkSendComplete()
|
||||
}
|
||||
|
||||
const waiter = sendWaiter(peer, waitComplete, timeout);
|
||||
callNTQQApi({
|
||||
methodName: NTQQApiMethod.SEND_MSG,
|
||||
args: [
|
||||
@@ -174,11 +177,12 @@ export class NTQQMsgApi {
|
||||
null,
|
||||
],
|
||||
}).then()
|
||||
return await checkSendComplete()
|
||||
return await waiter;
|
||||
}
|
||||
|
||||
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
const waiter = sendWaiter(destPeer, true, 10000);
|
||||
callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.FORWARD_MSG,
|
||||
args: [
|
||||
{
|
||||
@@ -190,7 +194,8 @@ export class NTQQMsgApi {
|
||||
},
|
||||
null,
|
||||
],
|
||||
})
|
||||
}).then().catch(log)
|
||||
return await waiter;
|
||||
}
|
||||
|
||||
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
|
||||
|
@@ -3,10 +3,19 @@ import { Group, SelfInfo, User } from '../types'
|
||||
import { ReceiveCmdS } from '../hook'
|
||||
import { selfInfo, uidMaps } from '../../common/data'
|
||||
import { NTQQWindowApi, NTQQWindows } from './window'
|
||||
import { isQQ998, log, sleep } from '../../common/utils'
|
||||
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
|
||||
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
||||
import * as https from 'https'
|
||||
|
||||
let userInfoCache: Record<string, User> = {} // uid: User
|
||||
|
||||
export interface ClientKeyData extends GeneralCallResult {
|
||||
url: string;
|
||||
keyIndex: string;
|
||||
clientKey: string;
|
||||
expireTime: string;
|
||||
}
|
||||
|
||||
export class NTQQUserApi {
|
||||
static async setQQAvatar(filePath: string) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
@@ -28,6 +37,7 @@ export class NTQQUserApi {
|
||||
timeoutSecond: 2,
|
||||
})
|
||||
}
|
||||
|
||||
static async getUserInfo(uid: string) {
|
||||
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
||||
methodName: NTQQApiMethod.USER_INFO,
|
||||
@@ -36,6 +46,7 @@ export class NTQQUserApi {
|
||||
})
|
||||
return result.profiles.get(uid)
|
||||
}
|
||||
|
||||
static async getUserDetailInfo(uid: string, getLevel = false) {
|
||||
// this.getUserInfo(uid);
|
||||
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
||||
@@ -84,64 +95,46 @@ export class NTQQUserApi {
|
||||
],
|
||||
})
|
||||
}
|
||||
static async getSkey(groupName: string, groupCode: string): Promise<{ data: string }> {
|
||||
return await NTQQWindowApi.openWindow<{ data: string }>(
|
||||
NTQQWindows.GroupHomeWorkWindow,
|
||||
[
|
||||
{
|
||||
groupName,
|
||||
groupCode,
|
||||
source: 'funcbar',
|
||||
},
|
||||
],
|
||||
ReceiveCmdS.SKEY_UPDATE,
|
||||
1,
|
||||
)
|
||||
// return await callNTQQApi<string>({
|
||||
// className: NTQQApiClass.GROUP_HOME_WORK,
|
||||
// methodName: NTQQApiMethod.UPDATE_SKEY,
|
||||
// args: [
|
||||
// {
|
||||
// domain: "qun.qq.com"
|
||||
// }
|
||||
// ]
|
||||
// })
|
||||
// return await callNTQQApi<GeneralCallResult>({
|
||||
// methodName: NTQQApiMethod.GET_SKEY,
|
||||
// args: [
|
||||
// {
|
||||
// "domains": [
|
||||
// "qzone.qq.com",
|
||||
// "qlive.qq.com",
|
||||
// "qun.qq.com",
|
||||
// "gamecenter.qq.com",
|
||||
// "vip.qq.com",
|
||||
// "qianbao.qq.com",
|
||||
// "qidian.qq.com"
|
||||
// ],
|
||||
// "isForNewPCQQ": false
|
||||
// },
|
||||
// null
|
||||
// ]
|
||||
// })
|
||||
|
||||
static async getSkey(): Promise<string> {
|
||||
const clientKeyData = await this.getClientKey()
|
||||
if (clientKeyData.result !== 0) {
|
||||
throw new Error('获取clientKey失败')
|
||||
}
|
||||
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
|
||||
+ '&clientkey=' + clientKeyData.clientKey
|
||||
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.get(url, (res) => {
|
||||
const rawCookies = res.headers['set-cookie']
|
||||
const cookies = {}
|
||||
rawCookies.forEach(cookie => {
|
||||
// 使用正则表达式匹配 cookie 名称和值
|
||||
const regex = /([^=;]+)=([^;]*)/
|
||||
const match = regex.exec(cookie)
|
||||
if (match) {
|
||||
cookies[match[1].trim()] = match[2].trim()
|
||||
}
|
||||
})
|
||||
resolve(cookies['skey'])
|
||||
})
|
||||
req.on('error', e => {
|
||||
reject(e)
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
static async getCookie(group: Group) {
|
||||
let cookies = await this.getCookieWithoutSkey()
|
||||
let skey = ''
|
||||
for (let i = 0; i < 2; i++) {
|
||||
skey = (await this.getSkey(group.groupName, group.groupCode)).data
|
||||
skey = skey.trim()
|
||||
if (skey) {
|
||||
break
|
||||
}
|
||||
await sleep(1000)
|
||||
}
|
||||
if (!skey) {
|
||||
throw new Error('获取skey失败')
|
||||
@cacheFunc(60 * 30 * 1000)
|
||||
static async getCookies(domain: string) {
|
||||
const skey = await this.getSkey();
|
||||
const pskey= (await this.getPSkey([domain])).get(domain);
|
||||
if (!pskey || !skey) {
|
||||
throw new Error('获取Cookies失败')
|
||||
}
|
||||
const bkn = NTQQUserApi.genBkn(skey)
|
||||
cookies = cookies.replace('skey=;', `skey=${skey};`)
|
||||
const cookies = `p_skey=${pskey}; skey=${skey}; p_uin=o${selfInfo.uin}; uin=o${selfInfo.uin}`;
|
||||
return { cookies, bkn }
|
||||
}
|
||||
|
||||
@@ -156,4 +149,17 @@ export class NTQQUserApi {
|
||||
|
||||
return (hash & 0x7fffffff).toString()
|
||||
}
|
||||
|
||||
static async getPSkey(domains: string[]): Promise<Map<string, string>> {
|
||||
const res = await wrapperApi.NodeIQQNTWrapperSession.getTipOffService().getPskey(domains, true)
|
||||
if (res.result !== 0) {
|
||||
throw new Error(`获取Pskey失败: ${res.errMsg}`)
|
||||
}
|
||||
return res.domainPskeyMap
|
||||
}
|
||||
|
||||
static async getClientKey(): Promise<ClientKeyData> {
|
||||
return await wrapperApi.NodeIQQNTWrapperSession.getTicketService().forceFetchClientKey('')
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,76 +1,366 @@
|
||||
import { groups } from '../../common/data'
|
||||
import { log } from '../../common/utils'
|
||||
import { NTQQUserApi } from './user'
|
||||
|
||||
export class WebApi {
|
||||
private static bkn: string
|
||||
private static skey: string
|
||||
private static pskey: string
|
||||
private static cookie: string
|
||||
private defaultHeaders: Record<string, string> = {
|
||||
'User-Agent': 'QQ/8.9.28.635 CFNetwork/1312 Darwin/21.0.0',
|
||||
import { WebGroupData, groups, selfInfo } from '@/common/data';
|
||||
import { log } from '@/common/utils/log';
|
||||
import { NTQQUserApi } from './user';
|
||||
import { RequestUtil } from '@/common/utils/request';
|
||||
export enum WebHonorType {
|
||||
ALL = 'all',
|
||||
TALKACTIVE = 'talkative',
|
||||
PERFROMER = 'performer',
|
||||
LEGEND = 'legend',
|
||||
STORONGE_NEWBI = 'strong_newbie',
|
||||
EMOTION = 'emotion'
|
||||
}
|
||||
export interface WebApiGroupMember {
|
||||
uin: number
|
||||
role: number
|
||||
g: number
|
||||
join_time: number
|
||||
last_speak_time: number
|
||||
lv: {
|
||||
point: number
|
||||
level: number
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
public 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 res = await this.request(url)
|
||||
return await res.json()
|
||||
card: string
|
||||
tags: string
|
||||
flag: number
|
||||
nick: string
|
||||
qage: number
|
||||
rm: number
|
||||
}
|
||||
interface WebApiGroupMemberRet {
|
||||
ec: number
|
||||
errcode: number
|
||||
em: string
|
||||
cache: number
|
||||
adm_num: number
|
||||
levelname: any
|
||||
mems: WebApiGroupMember[]
|
||||
count: number
|
||||
svr_time: number
|
||||
max_count: number
|
||||
search_count: number
|
||||
extmode: number
|
||||
}
|
||||
export interface WebApiGroupNoticeFeed {
|
||||
u: number//发送者
|
||||
fid: string//fid
|
||||
pubt: number//时间
|
||||
msg: {
|
||||
text: string
|
||||
text_face: string
|
||||
title: string,
|
||||
pics?: {
|
||||
id: string,
|
||||
w: string,
|
||||
h: string
|
||||
}[]
|
||||
}
|
||||
|
||||
public async getGroupDigest(groupCode: string) {
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`
|
||||
const res = await this.request(url)
|
||||
log(res.headers)
|
||||
return await res.json()
|
||||
type: number
|
||||
fn: number
|
||||
cn: number
|
||||
vn: number
|
||||
settings: {
|
||||
is_show_edit_card: number
|
||||
remind_ts: number
|
||||
tip_window_type: number
|
||||
confirm_required: number
|
||||
}
|
||||
|
||||
private genBkn(sKey: string) {
|
||||
return NTQQUserApi.genBkn(sKey)
|
||||
read_num: number
|
||||
is_read: number
|
||||
is_all_confirm: number
|
||||
}
|
||||
export interface WebApiGroupNoticeRet {
|
||||
ec: number
|
||||
em: string
|
||||
ltsm: number
|
||||
srv_code: number
|
||||
read_only: number
|
||||
role: number
|
||||
feeds: WebApiGroupNoticeFeed[]
|
||||
group: {
|
||||
group_id: number
|
||||
class_ext: number
|
||||
}
|
||||
private async init() {
|
||||
if (!WebApi.bkn) {
|
||||
const group = groups[0]
|
||||
WebApi.skey = (await NTQQUserApi.getSkey(group.groupName, group.groupCode)).data
|
||||
WebApi.bkn = this.genBkn(WebApi.skey)
|
||||
let cookie = await NTQQUserApi.getCookieWithoutSkey()
|
||||
const pskeyRegex = /p_skey=([^;]+)/
|
||||
const match = cookie.match(pskeyRegex)
|
||||
const pskeyValue = match ? match[1] : null
|
||||
WebApi.pskey = pskeyValue
|
||||
if (cookie.indexOf('skey=;') !== -1) {
|
||||
cookie = cookie.replace('skey=;', `skey=${WebApi.skey};`)
|
||||
}
|
||||
WebApi.cookie = cookie
|
||||
// for(const kv of WebApi.cookie.split(";")){
|
||||
// const [key, value] = kv.split("=");
|
||||
// }
|
||||
// log("set cookie", key, value)
|
||||
// await session.defaultSession.cookies.set({
|
||||
// url: 'https://qun.qq.com', // 你要请求的域名
|
||||
// name: key.trim(),
|
||||
// value: value.trim(),
|
||||
// expirationDate: Date.now() / 1000 + 300000, // Cookie 过期时间,例如设置为当前时间之后的300秒
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private async request(url: string, method: 'GET' | 'POST' = 'GET', headers: Record<string, string> = {}) {
|
||||
await this.init()
|
||||
url += '&bkn=' + WebApi.bkn
|
||||
let _headers: Record<string, string> = {
|
||||
...this.defaultHeaders,
|
||||
...headers,
|
||||
Cookie: WebApi.cookie,
|
||||
credentials: 'include',
|
||||
}
|
||||
log('request', url, _headers)
|
||||
const options = {
|
||||
method: method,
|
||||
headers: _headers,
|
||||
}
|
||||
return fetch(url, options)
|
||||
sta: number,
|
||||
gln: number
|
||||
tst: number,
|
||||
ui: any
|
||||
server_time: number
|
||||
svrt: number
|
||||
ad: number
|
||||
}
|
||||
interface GroupEssenceMsg {
|
||||
group_code: string
|
||||
msg_seq: number
|
||||
msg_random: number
|
||||
sender_uin: string
|
||||
sender_nick: string
|
||||
sender_time: number
|
||||
add_digest_uin: string
|
||||
add_digest_nick: string
|
||||
add_digest_time: number
|
||||
msg_content: any[]
|
||||
can_be_removed: true
|
||||
}
|
||||
export interface GroupEssenceMsgRet {
|
||||
retcode: number
|
||||
retmsg: string
|
||||
data: {
|
||||
msg_list: GroupEssenceMsg[]
|
||||
is_end: boolean
|
||||
group_role: number
|
||||
config_page_url: string
|
||||
}
|
||||
}
|
||||
export class WebApi {
|
||||
static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet> {
|
||||
const {cookies: CookieValue, bkn: Bkn} = (await NTQQUserApi.getCookies('qun.qq.com'))
|
||||
const url = 'https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=' + Bkn + '&group_code=' + GroupCode + '&page_start=' + page_start + '&page_limit=20';
|
||||
let ret;
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(url, 'GET', '', { 'Cookie': CookieValue });
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
//console.log(url, CookieValue);
|
||||
if (ret.retcode !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
|
||||
log('webapi 获取群成员', GroupCode);
|
||||
let MemberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
|
||||
try {
|
||||
let cachedData = WebGroupData.GroupData.get(GroupCode);
|
||||
let cachedTime = WebGroupData.GroupTime.get(GroupCode);
|
||||
|
||||
if (!cachedTime || Date.now() - cachedTime > 1800 * 1000 || !cached) {
|
||||
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com'];
|
||||
const _Skey = await NTQQUserApi.getSkey();
|
||||
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + selfInfo.uin;
|
||||
if (!_Skey || !_Pskey) {
|
||||
return MemberData;
|
||||
}
|
||||
const Bkn = WebApi.genBkn(_Skey);
|
||||
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 });
|
||||
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
|
||||
return [];
|
||||
} else {
|
||||
for (const key in fastRet.mems) {
|
||||
MemberData.push(fastRet.mems[key]);
|
||||
}
|
||||
}
|
||||
//初始化获取PageNum
|
||||
const PageNum = Math.ceil(fastRet.count / 40);
|
||||
//遍历批量请求
|
||||
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 });
|
||||
retList.push(ret);
|
||||
}
|
||||
//批量等待
|
||||
for (let i = 1; i <= PageNum; i++) {
|
||||
const ret = await (retList[i]);
|
||||
if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) {
|
||||
continue;
|
||||
}
|
||||
for (const key in ret.mems) {
|
||||
MemberData.push(ret.mems[key]);
|
||||
}
|
||||
}
|
||||
WebGroupData.GroupData.set(GroupCode, MemberData);
|
||||
WebGroupData.GroupTime.set(GroupCode, Date.now());
|
||||
} else {
|
||||
MemberData = cachedData as Array<WebApiGroupMember>;
|
||||
}
|
||||
} catch {
|
||||
return MemberData;
|
||||
}
|
||||
return MemberData;
|
||||
}
|
||||
// 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 res = await this.request(url);
|
||||
// return await res.json();
|
||||
// }
|
||||
|
||||
// public async getGroupDigest(groupCode: string) {
|
||||
// const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`;
|
||||
// const res = await this.request(url);
|
||||
// return await res.json();
|
||||
// }
|
||||
static async setGroupNotice(GroupCode: string, Content: string = '') {
|
||||
//https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=${bkn}
|
||||
//qid=${群号}&bkn=${bkn}&text=${内容}&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}
|
||||
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com'];
|
||||
const _Skey = await NTQQUserApi.getSkey();
|
||||
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + selfInfo.uin;
|
||||
let ret: any = undefined;
|
||||
//console.log(CookieValue);
|
||||
if (!_Skey || !_Pskey) {
|
||||
//获取Cookies失败
|
||||
return undefined;
|
||||
}
|
||||
const Bkn = WebApi.genBkn(_Skey);
|
||||
const data = 'qid=' + GroupCode + '&bkn=' + Bkn + '&text=' + Content + '&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}';
|
||||
const url = 'https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=' + Bkn;
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<any>(url, 'GET', '', { 'Cookie': CookieValue });
|
||||
return ret;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
static async getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
|
||||
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com'];
|
||||
const _Skey = await NTQQUserApi.getSkey();
|
||||
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + selfInfo.uin;
|
||||
let ret: WebApiGroupNoticeRet | undefined = undefined;
|
||||
//console.log(CookieValue);
|
||||
if (!_Skey || !_Pskey) {
|
||||
//获取Cookies失败
|
||||
return undefined;
|
||||
}
|
||||
const Bkn = WebApi.genBkn(_Skey);
|
||||
const url = 'https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=' + Bkn + '&qid=' + GroupCode + '&ft=23&ni=1&n=1&i=1&log_read=1&platform=1&s=-1&n=20';
|
||||
try {
|
||||
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(url, 'GET', '', { 'Cookie': CookieValue });
|
||||
if (ret?.ec !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
return ret;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
static genBkn(sKey: string) {
|
||||
sKey = sKey || '';
|
||||
let hash = 5381;
|
||||
|
||||
for (let i = 0; i < sKey.length; i++) {
|
||||
const code = sKey.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
}
|
||||
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
//实现未缓存 考虑2h缓存
|
||||
static async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||
async function getDataInternal(Internal_groupCode: string, Internal_type: number) {
|
||||
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString();
|
||||
let res = '';
|
||||
let resJson;
|
||||
try {
|
||||
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': CookieValue });
|
||||
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
|
||||
if (match) {
|
||||
resJson = JSON.parse(match[1].trim());
|
||||
}
|
||||
if (Internal_type === 1) {
|
||||
return resJson?.talkativeList;
|
||||
} else {
|
||||
return resJson?.actorList;
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取当前群荣耀失败', url, e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let HonorInfo: any = { group_id: groupCode };
|
||||
const CookieValue = (await NTQQUserApi.getCookies('qun.qq.com')).cookies;
|
||||
|
||||
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 1);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取龙王信息失败');
|
||||
}
|
||||
HonorInfo.current_talkative = {
|
||||
user_id: RetInternal[0]?.uin,
|
||||
avatar: RetInternal[0]?.avatar,
|
||||
nickname: RetInternal[0]?.name,
|
||||
day_count: 0,
|
||||
description: RetInternal[0]?.desc
|
||||
}
|
||||
HonorInfo.talkative_list = [];
|
||||
for (const talkative_ele of RetInternal) {
|
||||
HonorInfo.talkative_list.push({
|
||||
user_id: talkative_ele?.uin,
|
||||
avatar: talkative_ele?.avatar,
|
||||
description: talkative_ele?.desc,
|
||||
day_count: 0,
|
||||
nickname: talkative_ele?.name
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 2);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊之火失败');
|
||||
}
|
||||
HonorInfo.performer_list = [];
|
||||
for (const performer_ele of RetInternal) {
|
||||
HonorInfo.performer_list.push({
|
||||
user_id: performer_ele?.uin,
|
||||
nickname: performer_ele?.name,
|
||||
avatar: performer_ele?.avatar,
|
||||
description: performer_ele?.desc
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 3);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊炽焰失败');
|
||||
}
|
||||
HonorInfo.legend_list = [];
|
||||
for (const legend_ele of RetInternal) {
|
||||
HonorInfo.legend_list.push({
|
||||
user_id: legend_ele?.uin,
|
||||
nickname: legend_ele?.name,
|
||||
avatar: legend_ele?.avatar,
|
||||
desc: legend_ele?.description
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取群聊炽焰失败', e);
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 6);
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取快乐源泉失败');
|
||||
}
|
||||
HonorInfo.emotion_list = [];
|
||||
for (const emotion_ele of RetInternal) {
|
||||
HonorInfo.emotion_list.push({
|
||||
user_id: emotion_ele?.uin,
|
||||
nickname: emotion_ele?.name,
|
||||
avatar: emotion_ele?.avatar,
|
||||
desc: emotion_ele?.description
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log('获取快乐源泉失败', e);
|
||||
}
|
||||
}
|
||||
//冒尖小春笋好像已经被tx扬了
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
HonorInfo.strong_newbie_list = [];
|
||||
}
|
||||
return HonorInfo;
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import {
|
||||
AtType,
|
||||
ElementType,
|
||||
FaceIndex,
|
||||
FaceType,
|
||||
PicType,
|
||||
SendArkElement,
|
||||
SendFaceElement,
|
||||
@@ -22,6 +21,7 @@ import { log } from '../common/utils/log'
|
||||
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
|
||||
import { encodeSilk } from '../common/utils/audio'
|
||||
import { isNull } from '../common/utils'
|
||||
import faceConfig from './face_config.json';
|
||||
|
||||
export const mFaceCache = new Map<string, string>() // emojiId -> faceName
|
||||
|
||||
@@ -76,6 +76,10 @@ export class SendMsgElementConstructor {
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0'
|
||||
}
|
||||
const maxMB = 30;
|
||||
if (fileSize > 1024 * 1024 * 30){
|
||||
throw `图片过大,最大支持${maxMB}MB,当前文件大小${fileSize}B`
|
||||
}
|
||||
const imageSize = await NTQQFileApi.getImageSize(picPath)
|
||||
const picElement = {
|
||||
md5HexStr: md5,
|
||||
@@ -131,6 +135,10 @@ export class SendMsgElementConstructor {
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0'
|
||||
}
|
||||
const maxMB = 100;
|
||||
if (fileSize > 1024 * 1024 * maxMB) {
|
||||
throw `视频过大,最大支持${maxMB}MB,当前文件大小${fileSize}B`
|
||||
}
|
||||
const pathLib = require('path')
|
||||
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
||||
thumbDir = pathLib.dirname(thumbDir)
|
||||
@@ -265,13 +273,30 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
|
||||
static face(faceId: number): SendFaceElement {
|
||||
// 从face_config.json中获取表情名称
|
||||
const sysFaces = faceConfig.sysface
|
||||
const emojiFaces = faceConfig.emoji
|
||||
const face = sysFaces.find((face) => face.QSid === faceId.toString())
|
||||
faceId = parseInt(faceId.toString())
|
||||
// let faceType = parseInt(faceId.toString().substring(0, 1));
|
||||
let faceType = 1
|
||||
if (faceId >= 222){
|
||||
faceType = 2
|
||||
}
|
||||
if (face.AniStickerType){
|
||||
faceType = 3;
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: '',
|
||||
faceElement: {
|
||||
faceIndex: faceId,
|
||||
faceType: faceId < 222 ? FaceType.normal : FaceType.normal2,
|
||||
faceType,
|
||||
faceText: face.QDes,
|
||||
stickerId: face.AniStickerId,
|
||||
stickerType: face.AniStickerType,
|
||||
packId: face.AniStickerPackId,
|
||||
sourceType: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -298,7 +323,7 @@ export class SendMsgElementConstructor {
|
||||
elementId: '',
|
||||
faceElement: {
|
||||
faceIndex: FaceIndex.dice,
|
||||
faceType: FaceType.dice,
|
||||
faceType: 3,
|
||||
faceText: '[骰子]',
|
||||
packId: '1',
|
||||
stickerId: '33',
|
||||
|
3665
src/ntqqapi/face_config.json
Normal file
3665
src/ntqqapi/face_config.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,26 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { NTQQApiClass, NTQQApiMethod } from './ntcall'
|
||||
import { NTQQMsgApi, sendMessagePool } from './api/msg'
|
||||
import { ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types'
|
||||
import { CategoryFriend, ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types'
|
||||
import {
|
||||
deleteGroup,
|
||||
friends,
|
||||
getFriend,
|
||||
getGroupMember,
|
||||
groups,
|
||||
groups, rawFriends,
|
||||
selfInfo,
|
||||
tempGroupCodeMap,
|
||||
uidMaps,
|
||||
} from '../common/data'
|
||||
} from '@/common/data'
|
||||
import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { postOB11Event } from '../onebot11/server/postOB11Event'
|
||||
import { getConfigUtil, HOOK_LOG } from '../common/config'
|
||||
import { postOb11Event } from '../onebot11/server/post-ob11-event'
|
||||
import { getConfigUtil, HOOK_LOG } from '@/common/config'
|
||||
import fs from 'fs'
|
||||
import { dbUtil } from '../common/db'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { NTQQGroupApi } from './api/group'
|
||||
import { log } from '../common/utils/log'
|
||||
import { isNumeric, sleep } from '../common/utils/helper'
|
||||
import { log } from '@/common/utils'
|
||||
import { isNumeric, sleep } from '@/common/utils'
|
||||
import { OB11Constructor } from '../onebot11/constructor'
|
||||
|
||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||
@@ -299,7 +299,7 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
|
||||
}
|
||||
for (const member of oldMembers) {
|
||||
if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) {
|
||||
postOB11Event(
|
||||
postOb11Event(
|
||||
new OB11GroupDecreaseEvent(
|
||||
parseInt(group.groupCode),
|
||||
parseInt(member.uin),
|
||||
@@ -380,8 +380,10 @@ registerReceiveHook<{
|
||||
|
||||
// 好友列表变动
|
||||
registerReceiveHook<{
|
||||
data: { categoryId: number; categroyName: string; categroyMbCount: number; buddyList: User[] }[]
|
||||
data:CategoryFriend[]
|
||||
}>(ReceiveCmdS.FRIENDS, (payload) => {
|
||||
rawFriends.length = 0;
|
||||
rawFriends.push(...payload.data);
|
||||
for (const fData of payload.data) {
|
||||
const _friends = fData.buddyList
|
||||
for (let friend of _friends) {
|
||||
@@ -497,7 +499,7 @@ registerReceiveHook<{
|
||||
let lastTempMsg = msgList.pop()
|
||||
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
|
||||
if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) {
|
||||
OB11Constructor.message(lastTempMsg).then((r) => postOB11Event(r))
|
||||
OB11Constructor.message(lastTempMsg).then((r) => postOb11Event(r))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@@ -1,54 +1,58 @@
|
||||
import {log} from "../../../common/utils";
|
||||
import {NTQQApi} from "../../ntcall";
|
||||
import {cpModule} from "../cpmodule";
|
||||
import { log } from '../../../common/utils'
|
||||
import { NTQQApi } from '../../ntcall'
|
||||
import { cpModule } from '../cpmodule'
|
||||
|
||||
type PokeHandler = (id: string, isGroup: boolean) => void
|
||||
type CrychicHandler = (event: string, id: string, isGroup: boolean) => void
|
||||
|
||||
let pokeRecords: Record<string, number> = {}
|
||||
|
||||
class Crychic{
|
||||
private crychic: any = undefined
|
||||
class Crychic {
|
||||
private crychic: any = undefined
|
||||
|
||||
loadNode(){
|
||||
if (!this.crychic){
|
||||
try {
|
||||
cpModule('crychic');
|
||||
this.crychic = require("./crychic.node")
|
||||
this.crychic.init()
|
||||
}catch (e) {
|
||||
log("crychic加载失败", e)
|
||||
}
|
||||
loadNode() {
|
||||
if (!this.crychic) {
|
||||
try {
|
||||
cpModule('crychic')
|
||||
this.crychic = require('./crychic.node')
|
||||
this.crychic.init()
|
||||
} catch (e) {
|
||||
log('crychic加载失败', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerPokeHandler(fn: PokeHandler) {
|
||||
this.registerHandler((event, id, isGroup) => {
|
||||
if (event === 'poke') {
|
||||
let existTime = pokeRecords[id]
|
||||
if (existTime) {
|
||||
if (Date.now() - existTime < 1500) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
registerPokeHandler(fn: PokeHandler){
|
||||
this.registerHandler((event, id, isGroup)=>{
|
||||
if (event === "poke"){
|
||||
let existTime = pokeRecords[id]
|
||||
if (existTime) {
|
||||
if (Date.now() - existTime < 1500) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pokeRecords[id] = Date.now()
|
||||
fn(id, isGroup);
|
||||
}
|
||||
})
|
||||
}
|
||||
registerHandler(fn: CrychicHandler){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.setCryHandler(fn)
|
||||
}
|
||||
sendFriendPoke(friendUid: string){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.sendFriendPoke(parseInt(friendUid))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
sendGroupPoke(groupCode: string, memberUin: string){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.sendGroupPoke(parseInt(memberUin), parseInt(groupCode))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
pokeRecords[id] = Date.now()
|
||||
fn(id, isGroup)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
registerHandler(fn: CrychicHandler) {
|
||||
if (!this.crychic) return
|
||||
this.crychic.setCryHandler(fn)
|
||||
}
|
||||
|
||||
sendFriendPoke(friendUid: string) {
|
||||
if (!this.crychic) return
|
||||
this.crychic.sendFriendPoke(parseInt(friendUid))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
|
||||
sendGroupPoke(groupCode: string, memberUin: string) {
|
||||
if (!this.crychic) return
|
||||
this.crychic.sendGroupPoke(parseInt(memberUin), parseInt(groupCode))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
}
|
||||
|
||||
export const crychic = new Crychic()
|
@@ -30,4 +30,4 @@ class HookApi {
|
||||
}
|
||||
}
|
||||
|
||||
export const hookApi = new HookApi();
|
||||
// export const hookApi = new HookApi();
|
||||
|
19
src/ntqqapi/native/wrapper.ts
Normal file
19
src/ntqqapi/native/wrapper.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
let Process = require('process')
|
||||
let os = require('os')
|
||||
Process.dlopenOrig = Process.dlopen
|
||||
|
||||
export const wrapperApi: any = {}
|
||||
|
||||
Process.dlopen = function(module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
|
||||
let dlopenRet = this.dlopenOrig(module, filename, flags)
|
||||
for (let export_name in module.exports) {
|
||||
module.exports[export_name] = new Proxy(module.exports[export_name], {
|
||||
construct: (target, args, _newTarget) => {
|
||||
let ret = new target(...args)
|
||||
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
|
||||
return ret
|
||||
},
|
||||
})
|
||||
}
|
||||
return dlopenRet
|
||||
}
|
@@ -4,7 +4,6 @@ import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeR
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { log } from '../common/utils/log'
|
||||
import { NTQQWindow, NTQQWindowApi, NTQQWindows } from './api/window'
|
||||
import { WebApi } from './api/webapi'
|
||||
import { HOOK_LOG } from '../common/config'
|
||||
|
||||
export enum NTQQApiClass {
|
||||
@@ -21,6 +20,7 @@ export enum NTQQApiClass {
|
||||
}
|
||||
|
||||
export enum NTQQApiMethod {
|
||||
TEST = 'NodeIKernelTipOffService/getPskey',
|
||||
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
||||
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
||||
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
||||
@@ -81,7 +81,7 @@ export enum NTQQApiMethod {
|
||||
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
||||
|
||||
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
||||
GET_SKEY = 'nodeIKernelTipOffService/getPskey',
|
||||
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
|
||||
UPDATE_SKEY = 'updatePskey',
|
||||
|
||||
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
||||
|
@@ -188,6 +188,7 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
|
||||
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
||||
|
||||
export interface PicElement {
|
||||
picType: PicType // 有这玩意儿吗
|
||||
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||
originImageMd5?: string
|
||||
sourcePath: string // 图片本地路径
|
||||
@@ -229,11 +230,6 @@ export interface GrayTipElement {
|
||||
}
|
||||
}
|
||||
|
||||
export enum FaceType {
|
||||
normal = 1, // 小黄脸
|
||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
||||
dice = 3, // 骰子
|
||||
}
|
||||
|
||||
export enum FaceIndex {
|
||||
dice = 358,
|
||||
@@ -242,7 +238,7 @@ export enum FaceIndex {
|
||||
|
||||
export interface FaceElement {
|
||||
faceIndex: number
|
||||
faceType: FaceType
|
||||
faceType: number
|
||||
faceText?: string
|
||||
packId?: string
|
||||
stickerId?: string
|
||||
|
@@ -10,6 +10,7 @@ export interface QQLevel {
|
||||
moonNum: number
|
||||
starNum: number
|
||||
}
|
||||
|
||||
export interface User {
|
||||
uid: string // 加密的字符串
|
||||
uin: string // QQ号
|
||||
@@ -72,4 +73,12 @@ export interface SelfInfo extends User {
|
||||
online?: boolean
|
||||
}
|
||||
|
||||
export interface Friend extends User {}
|
||||
export interface Friend extends User {
|
||||
}
|
||||
|
||||
export interface CategoryFriend {
|
||||
categoryId: number;
|
||||
categroyName: string;
|
||||
categroyMbCount: number;
|
||||
buddyList: User[]
|
||||
}
|
@@ -1,12 +1,12 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import fs from 'fs/promises'
|
||||
import { dbUtil } from '../../../common/db'
|
||||
import { getConfigUtil } from '../../../common/config'
|
||||
import { log, sleep, uri2local } from '../../../common/utils'
|
||||
import { NTQQFileApi } from '../../../ntqqapi/api/file'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import { log, sleep, uri2local } from '@/common/utils'
|
||||
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||
import { ActionName } from '../types'
|
||||
import { FileElement, RawMessage, VideoElement } from '../../../ntqqapi/types'
|
||||
import { FileCache } from '../../../common/types'
|
||||
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||
import { FileCache } from '@/common/types'
|
||||
|
||||
export interface GetFilePayload {
|
||||
file: string // 文件名或者fileUuid
|
||||
@@ -21,13 +21,12 @@ export interface GetFileResponse {
|
||||
}
|
||||
|
||||
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
private getElement(msg: RawMessage): { id: string; element: VideoElement | FileElement } {
|
||||
let element = msg.elements.find((e) => e.fileElement)
|
||||
private getElement(msg: RawMessage, elementId: string): VideoElement | FileElement {
|
||||
let element = msg.elements.find((e) => e.elementId === elementId)
|
||||
if (!element) {
|
||||
element = msg.elements.find((e) => e.videoElement)
|
||||
return { id: element.elementId, element: element.videoElement }
|
||||
throw new Error('element not found')
|
||||
}
|
||||
return { id: element.elementId, element: element.fileElement }
|
||||
return element.fileElement
|
||||
}
|
||||
private async download(cache: FileCache, file: string) {
|
||||
log('需要调用 NTQQ 下载文件api')
|
||||
@@ -35,14 +34,14 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
let msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||
if (msg) {
|
||||
log('找到了文件 msg', msg)
|
||||
let element = this.getElement(msg)
|
||||
let element = this.getElement(msg, cache.elementId)
|
||||
log('找到了文件 element', element)
|
||||
// 构建下载函数
|
||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, element.id, '', '', true)
|
||||
await sleep(1000)
|
||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
||||
await sleep(1000) // 这里延时是为何?
|
||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||
log('下载完成后的msg', msg)
|
||||
cache.filePath = this.getElement(msg).element.filePath
|
||||
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
||||
dbUtil.addFileCache(file, cache).then()
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,8 @@ import { OB11Constructor } from '../../constructor'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface Payload {
|
||||
message_id: string // long msg id
|
||||
message_id: string // long msg id,gocq
|
||||
id?: string // long msg id, onebot11
|
||||
}
|
||||
|
||||
interface Response {
|
||||
@@ -16,7 +17,11 @@ interface Response {
|
||||
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
actionName = ActionName.GoCQHTTP_GetForwardMsg
|
||||
protected async _handle(payload: Payload): Promise<any> {
|
||||
const rootMsg = await dbUtil.getMsgByLongId(payload.message_id)
|
||||
const message_id = payload.id || payload.message_id
|
||||
if (!message_id) {
|
||||
throw Error('message_id不能为空')
|
||||
}
|
||||
const rootMsg = await dbUtil.getMsgByLongId(message_id)
|
||||
if (!rootMsg) {
|
||||
throw Error('msg not found')
|
||||
}
|
||||
|
17
src/onebot11/action/go-cqhttp/QuickOperation.ts
Normal file
17
src/onebot11/action/go-cqhttp/QuickOperation.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { handleQuickOperation, QuickOperation, QuickOperationEvent } from '../quick-operation'
|
||||
import { log } from '@/common/utils'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface Payload{
|
||||
context: QuickOperationEvent,
|
||||
operation: QuickOperation
|
||||
}
|
||||
|
||||
export class GoCQHTTHandleQuickOperation extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.GoCQHTTP_HandleQuickOperation
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
handleQuickOperation(payload.context, payload.operation).then().catch(log);
|
||||
return null
|
||||
}
|
||||
}
|
24
src/onebot11/action/group/GetGroupEssence.ts
Normal file
24
src/onebot11/action/group/GetGroupEssence.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { GroupEssenceMsgRet, WebApi } from "@/ntqqapi/api";
|
||||
import BaseAction from "../BaseAction";
|
||||
import { ActionName } from "../types";
|
||||
|
||||
interface PayloadType {
|
||||
group_id: number;
|
||||
pages?: number;
|
||||
}
|
||||
|
||||
export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet> {
|
||||
actionName = ActionName.GoCQHTTP_GetEssenceMsg;
|
||||
|
||||
protected async _handle(payload: PayloadType) {
|
||||
throw '此 api 暂不支持'
|
||||
const ret = await WebApi.getGroupEssenceMsg(payload.group_id.toString(), payload.pages?.toString() || '0');
|
||||
if (!ret) {
|
||||
throw new Error('获取失败');
|
||||
}
|
||||
// ret.map((item) => {
|
||||
//
|
||||
// })
|
||||
return ret;
|
||||
}
|
||||
}
|
22
src/onebot11/action/group/GetGroupHonorInfo.ts
Normal file
22
src/onebot11/action/group/GetGroupHonorInfo.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { WebApi, WebHonorType } from "@/ntqqapi/api";
|
||||
import { ActionName } from "../types";
|
||||
import BaseAction from "../BaseAction";
|
||||
|
||||
interface Payload {
|
||||
group_id: number,
|
||||
type?: WebHonorType
|
||||
}
|
||||
export class GetGroupHonorInfo extends BaseAction<Payload, Array<any>> {
|
||||
actionName = ActionName.GetGroupHonorInfo;
|
||||
|
||||
protected async _handle(payload: Payload) {
|
||||
// console.log(await NTQQUserApi.getRobotUinRange());
|
||||
if (!payload.group_id) {
|
||||
throw '缺少参数group_id';
|
||||
}
|
||||
if (!payload.type) {
|
||||
payload.type = WebHonorType.ALL;
|
||||
}
|
||||
return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import GetMsg from './msg/GetMsg'
|
||||
import GetLoginInfo from './system/GetLoginInfo'
|
||||
import GetFriendList from './user/GetFriendList'
|
||||
import { GetFriendList, GetFriendWithCategory} from './user/GetFriendList'
|
||||
import GetGroupList from './group/GetGroupList'
|
||||
import GetGroupInfo from './group/GetGroupInfo'
|
||||
import GetGroupMemberList from './group/GetGroupMemberList'
|
||||
@@ -47,6 +47,9 @@ import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
|
||||
import { GetCookies } from './user/GetCookie'
|
||||
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
|
||||
import { ForwardFriendSingleMsg, ForwardSingleGroupMsg } from './msg/ForwardSingleMsg'
|
||||
import { GetGroupEssence } from './group/GetGroupEssence'
|
||||
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
|
||||
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation'
|
||||
|
||||
export const actionHandlers = [
|
||||
new GetFile(),
|
||||
@@ -55,6 +58,7 @@ export const actionHandlers = [
|
||||
new SetConfigAction(),
|
||||
new GetGroupAddRequest(),
|
||||
new SetQQAvatar(),
|
||||
new GetFriendWithCategory(),
|
||||
// onebot11
|
||||
new SendLike(),
|
||||
new GetMsg(),
|
||||
@@ -89,6 +93,8 @@ export const actionHandlers = [
|
||||
new ForwardFriendSingleMsg(),
|
||||
new ForwardSingleGroupMsg(),
|
||||
//以下为go-cqhttp api
|
||||
new GetGroupEssence(),
|
||||
new GetGroupHonorInfo(),
|
||||
new GoCQHTTPSendForwardMsg(),
|
||||
new GoCQHTTPSendGroupForwardMsg(),
|
||||
new GoCQHTTPSendPrivateForwardMsg(),
|
||||
@@ -100,6 +106,7 @@ export const actionHandlers = [
|
||||
new GoCQHTTPUploadPrivateFile(),
|
||||
new GoCQHTTPGetGroupMsgHistory(),
|
||||
new GoCQHTTGetForwardMsgAction(),
|
||||
new GoCQHTTHandleQuickOperation()
|
||||
]
|
||||
|
||||
function initActionMap() {
|
||||
|
@@ -11,7 +11,11 @@ interface Payload {
|
||||
user_id?: number
|
||||
}
|
||||
|
||||
class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
interface Response {
|
||||
message_id: number
|
||||
}
|
||||
|
||||
class ForwardSingleMsg extends BaseAction<Payload, Response> {
|
||||
protected async getTargetPeer(payload: Payload): Promise<Peer> {
|
||||
if (payload.user_id) {
|
||||
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) }
|
||||
@@ -19,10 +23,10 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
return { chatType: ChatType.group, peerUid: payload.group_id.toString() }
|
||||
}
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
protected async _handle(payload: Payload): Promise<Response> {
|
||||
const msg = await dbUtil.getMsgByShortId(payload.message_id)
|
||||
const peer = await this.getTargetPeer(payload)
|
||||
await NTQQMsgApi.forwardMsg(
|
||||
const sentMsg = await NTQQMsgApi.forwardMsg(
|
||||
{
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
@@ -30,7 +34,8 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
|
||||
peer,
|
||||
[msg.msgId],
|
||||
)
|
||||
return null
|
||||
const ob11MsgId = await dbUtil.addMsg(sentMsg)
|
||||
return {message_id: ob11MsgId}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -50,22 +50,28 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
||||
let data = msg['data']
|
||||
if (type === 'text' && !data['text']) {
|
||||
return 400
|
||||
} else if (['image', 'voice', 'record'].includes(type)) {
|
||||
}
|
||||
else if (['image', 'voice', 'record'].includes(type)) {
|
||||
if (!data['file']) {
|
||||
return 400
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (checkUri(data['file'])) {
|
||||
return 200
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return 400
|
||||
}
|
||||
}
|
||||
} else if (type === 'at' && !data['qq']) {
|
||||
return 400
|
||||
} else if (type === 'reply' && !data['id']) {
|
||||
}
|
||||
else if (type === 'at' && !data['qq']) {
|
||||
return 400
|
||||
}
|
||||
} else {
|
||||
else if (type === 'reply' && !data['id']) {
|
||||
return 400
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 400
|
||||
}
|
||||
}
|
||||
@@ -87,10 +93,12 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa
|
||||
},
|
||||
},
|
||||
]
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
message = decodeCQCode(message.toString())
|
||||
}
|
||||
} else if (!Array.isArray(message)) {
|
||||
}
|
||||
else if (!Array.isArray(message)) {
|
||||
message = [message]
|
||||
}
|
||||
return message
|
||||
@@ -108,179 +116,177 @@ export async function createSendElements(
|
||||
continue
|
||||
}
|
||||
switch (sendMsg.type) {
|
||||
case OB11MessageDataType.text:
|
||||
{
|
||||
const text = sendMsg.data?.text
|
||||
if (text) {
|
||||
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
|
||||
}
|
||||
case OB11MessageDataType.text: {
|
||||
const text = sendMsg.data?.text
|
||||
if (text) {
|
||||
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.at:
|
||||
{
|
||||
if (!target) {
|
||||
continue
|
||||
case OB11MessageDataType.at: {
|
||||
if (!target) {
|
||||
continue
|
||||
}
|
||||
let atQQ = sendMsg.data?.qq
|
||||
if (atQQ) {
|
||||
atQQ = atQQ.toString()
|
||||
if (atQQ === 'all') {
|
||||
// todo:查询剩余的at全体次数
|
||||
const groupCode = (target as Group)?.groupCode
|
||||
let remainAtAllCount = 1
|
||||
let isAdmin: boolean = true
|
||||
if (groupCode) {
|
||||
try {
|
||||
remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo
|
||||
.RemainAtAllCountForUin
|
||||
log(`群${groupCode}剩余at全体次数`, remainAtAllCount)
|
||||
const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin)
|
||||
isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
if (isAdmin && remainAtAllCount > 0) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员'))
|
||||
}
|
||||
}
|
||||
let atQQ = sendMsg.data?.qq
|
||||
if (atQQ) {
|
||||
atQQ = atQQ.toString()
|
||||
if (atQQ === 'all') {
|
||||
// todo:查询剩余的at全体次数
|
||||
const groupCode = (target as Group)?.groupCode
|
||||
let remainAtAllCount = 1
|
||||
let isAdmin: boolean = true
|
||||
if (groupCode) {
|
||||
try {
|
||||
remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo
|
||||
.RemainAtAllCountForUin
|
||||
log(`群${groupCode}剩余at全体次数`, remainAtAllCount)
|
||||
const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin)
|
||||
isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner
|
||||
} catch (e) {}
|
||||
}
|
||||
if (isAdmin && remainAtAllCount > 0) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员'))
|
||||
}
|
||||
} else {
|
||||
// const atMember = group?.members.find(m => m.uin == atQQ)
|
||||
const atMember = await getGroupMember((target as Group)?.groupCode, atQQ)
|
||||
if (atMember) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick),
|
||||
)
|
||||
}
|
||||
else {
|
||||
// const atMember = group?.members.find(m => m.uin == atQQ)
|
||||
const atMember = await getGroupMember((target as Group)?.groupCode, atQQ)
|
||||
if (atMember) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.reply:
|
||||
{
|
||||
let replyMsgId = sendMsg.data.id
|
||||
if (replyMsgId) {
|
||||
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
|
||||
if (replyMsg) {
|
||||
case OB11MessageDataType.reply: {
|
||||
let replyMsgId = sendMsg.data.id
|
||||
if (replyMsgId) {
|
||||
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
|
||||
if (replyMsg) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.reply(
|
||||
replyMsg.msgSeq,
|
||||
replyMsg.msgId,
|
||||
replyMsg.senderUin,
|
||||
replyMsg.senderUin,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.face: {
|
||||
const faceId = sendMsg.data?.id
|
||||
if (faceId) {
|
||||
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.mface: {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.mface(
|
||||
sendMsg.data.emoji_package_id,
|
||||
sendMsg.data.emoji_id,
|
||||
sendMsg.data.key,
|
||||
sendMsg.data.summary,
|
||||
),
|
||||
)
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.image:
|
||||
case OB11MessageDataType.file:
|
||||
case OB11MessageDataType.video:
|
||||
case OB11MessageDataType.voice: {
|
||||
const data = (sendMsg as OB11MessageFile).data
|
||||
let file = data.file
|
||||
const payloadFileName = data?.name
|
||||
if (file) {
|
||||
const cache = await dbUtil.getFileCache(file)
|
||||
if (cache) {
|
||||
if (fs.existsSync(cache.filePath)) {
|
||||
file = 'file://' + cache.filePath
|
||||
}
|
||||
else if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
file = cache.filePath
|
||||
}
|
||||
else if (cache.url) {
|
||||
file = cache.url
|
||||
}
|
||||
log('找到文件缓存', file)
|
||||
}
|
||||
const { path, isLocal, fileName, errMsg } = await uri2local(file)
|
||||
if (errMsg) {
|
||||
throw errMsg
|
||||
}
|
||||
if (path) {
|
||||
if (!isLocal) {
|
||||
// 只删除http和base64转过来的文件
|
||||
deleteAfterSentFiles.push(path)
|
||||
}
|
||||
if (sendMsg.type === OB11MessageDataType.file) {
|
||||
log('发送文件', path, payloadFileName || fileName)
|
||||
sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName))
|
||||
}
|
||||
else if (sendMsg.type === OB11MessageDataType.video) {
|
||||
log('发送视频', path, payloadFileName || fileName)
|
||||
let thumb = sendMsg.data?.thumb
|
||||
if (thumb) {
|
||||
let uri2LocalRes = await uri2local(thumb)
|
||||
if (uri2LocalRes.success) {
|
||||
thumb = uri2LocalRes.path
|
||||
}
|
||||
}
|
||||
sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb))
|
||||
}
|
||||
else if (sendMsg.type === OB11MessageDataType.voice) {
|
||||
sendElements.push(await SendMsgElementConstructor.ptt(path))
|
||||
}
|
||||
else if (sendMsg.type === OB11MessageDataType.image) {
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.reply(
|
||||
replyMsg.msgSeq,
|
||||
replyMsg.msgId,
|
||||
replyMsg.senderUin,
|
||||
replyMsg.senderUin,
|
||||
await SendMsgElementConstructor.pic(
|
||||
path,
|
||||
sendMsg.data.summary || '',
|
||||
<PicSubType>parseInt(sendMsg.data?.subType?.toString()) || 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.face:
|
||||
{
|
||||
const faceId = sendMsg.data?.id
|
||||
if (faceId) {
|
||||
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
|
||||
case OB11MessageDataType.json: {
|
||||
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.poke: {
|
||||
let qq = sendMsg.data?.qq || sendMsg.data?.id
|
||||
if (qq) {
|
||||
if ('groupCode' in target) {
|
||||
crychic.sendGroupPoke(target.groupCode, qq.toString())
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.mface:
|
||||
{
|
||||
sendElements.push(
|
||||
SendMsgElementConstructor.mface(
|
||||
sendMsg.data.emoji_package_id,
|
||||
sendMsg.data.emoji_id,
|
||||
sendMsg.data.key,
|
||||
sendMsg.data.summary,
|
||||
),
|
||||
)
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.image:
|
||||
case OB11MessageDataType.file:
|
||||
case OB11MessageDataType.video:
|
||||
case OB11MessageDataType.voice:
|
||||
{
|
||||
const data = (sendMsg as OB11MessageFile).data
|
||||
let file = data.file
|
||||
const payloadFileName = data?.name
|
||||
if (file) {
|
||||
const cache = await dbUtil.getFileCache(file)
|
||||
if (cache) {
|
||||
if (fs.existsSync(cache.filePath)) {
|
||||
file = 'file://' + cache.filePath
|
||||
} else if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
file = cache.filePath
|
||||
} else if (cache.url) {
|
||||
file = cache.url
|
||||
}
|
||||
log('找到文件缓存', file)
|
||||
}
|
||||
const { path, isLocal, fileName, errMsg } = await uri2local(file)
|
||||
if (errMsg) {
|
||||
throw errMsg
|
||||
}
|
||||
if (path) {
|
||||
if (!isLocal) {
|
||||
// 只删除http和base64转过来的文件
|
||||
deleteAfterSentFiles.push(path)
|
||||
}
|
||||
if (sendMsg.type === OB11MessageDataType.file) {
|
||||
log('发送文件', path, payloadFileName || fileName)
|
||||
sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName))
|
||||
} else if (sendMsg.type === OB11MessageDataType.video) {
|
||||
log('发送视频', path, payloadFileName || fileName)
|
||||
let thumb = sendMsg.data?.thumb
|
||||
if (thumb) {
|
||||
let uri2LocalRes = await uri2local(thumb)
|
||||
if (uri2LocalRes.success) {
|
||||
thumb = uri2LocalRes.path
|
||||
}
|
||||
}
|
||||
sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb))
|
||||
} else if (sendMsg.type === OB11MessageDataType.voice) {
|
||||
sendElements.push(await SendMsgElementConstructor.ptt(path))
|
||||
} else if (sendMsg.type === OB11MessageDataType.image) {
|
||||
sendElements.push(
|
||||
await SendMsgElementConstructor.pic(
|
||||
path,
|
||||
sendMsg.data.summary || '',
|
||||
<PicSubType>parseInt(sendMsg.data?.subType?.toString()) || 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
else {
|
||||
if (!qq) {
|
||||
qq = parseInt(target.uin)
|
||||
}
|
||||
crychic.sendFriendPoke(qq.toString())
|
||||
}
|
||||
sendElements.push(SendMsgElementConstructor.poke('', ''))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.json:
|
||||
{
|
||||
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
||||
}
|
||||
case OB11MessageDataType.dice: {
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.dice(resultId))
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.poke:
|
||||
{
|
||||
let qq = sendMsg.data?.qq || sendMsg.data?.id
|
||||
if (qq) {
|
||||
if ('groupCode' in target) {
|
||||
crychic.sendGroupPoke(target.groupCode, qq.toString())
|
||||
} else {
|
||||
if (!qq) {
|
||||
qq = parseInt(target.uin)
|
||||
}
|
||||
crychic.sendFriendPoke(qq.toString())
|
||||
}
|
||||
sendElements.push(SendMsgElementConstructor.poke('', ''))
|
||||
}
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.dice:
|
||||
{
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.dice(resultId))
|
||||
}
|
||||
break
|
||||
case OB11MessageDataType.RPS:
|
||||
{
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.rps(resultId))
|
||||
}
|
||||
case OB11MessageDataType.RPS: {
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.rps(resultId))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -300,10 +306,34 @@ export async function sendMsg(
|
||||
if (!sendElements.length) {
|
||||
throw '消息体无法解析,请检查是否发送了不支持的消息类型'
|
||||
}
|
||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, 20000)
|
||||
// 计算发送的文件大小
|
||||
let totalSize = 0
|
||||
for (const fileElement of sendElements) {
|
||||
try {
|
||||
if (fileElement.elementType === ElementType.PTT) {
|
||||
totalSize += fs.statSync(fileElement.pttElement.filePath).size
|
||||
}
|
||||
if (fileElement.elementType === ElementType.FILE) {
|
||||
totalSize += fs.statSync(fileElement.fileElement.filePath).size
|
||||
}
|
||||
if (fileElement.elementType === ElementType.VIDEO) {
|
||||
totalSize += fs.statSync(fileElement.videoElement.filePath).size
|
||||
}
|
||||
if (fileElement.elementType === ElementType.PIC) {
|
||||
totalSize += fs.statSync(fileElement.picElement.sourcePath).size
|
||||
}
|
||||
} catch (e) {
|
||||
log('文件大小计算失败', e, fileElement)
|
||||
}
|
||||
}
|
||||
log('发送消息总大小', totalSize, 'bytes')
|
||||
let timeout = ((totalSize / 1024 / 512) * 1000) + 5000 // 512kb/s
|
||||
log('设置消息超时时间', timeout)
|
||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout)
|
||||
log('消息发送结果', returnMsg)
|
||||
returnMsg.msgShortId = await dbUtil.addMsg(returnMsg)
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||
}))
|
||||
return returnMsg
|
||||
}
|
||||
|
||||
@@ -402,7 +432,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
} catch (e) {
|
||||
throw '发送转发消息失败 ' + e.toString()
|
||||
}
|
||||
} else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
|
||||
}
|
||||
else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
|
||||
const music = messages[0] as OB11MessageMusic
|
||||
if (music) {
|
||||
const { musicSignUrl } = getConfigUtil().getConfig()
|
||||
@@ -439,7 +470,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
let jsonContent: string
|
||||
try {
|
||||
jsonContent = await new MusicSign(musicSignUrl).sign(postData)
|
||||
if (!jsonContent){
|
||||
if (!jsonContent) {
|
||||
throw '音乐消息生成失败,提交内容有误或者签名服务器签名失败'
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -459,7 +490,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles)
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||
}))
|
||||
return { message_id: returnMsg.msgShortId }
|
||||
}
|
||||
|
||||
@@ -562,7 +594,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
await sleep(500)
|
||||
log('转发节点生成成功', nodeMsg.msgId)
|
||||
}
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {}))
|
||||
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
|
||||
}))
|
||||
} catch (e) {
|
||||
log('生成转发消息节点失败', e)
|
||||
}
|
||||
|
146
src/onebot11/action/quick-operation.ts
Normal file
146
src/onebot11/action/quick-operation.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
// handle quick action, create at 2024-5-18 10:54:39 by linyuchen
|
||||
|
||||
|
||||
import { OB11Message, OB11MessageAt, OB11MessageData, OB11MessageDataType } from '../types'
|
||||
import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest'
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, Peer } from '@/ntqqapi/api'
|
||||
import { ChatType, Group, GroupRequestOperateTypes } from '@/ntqqapi/types'
|
||||
import { getGroup, getUidByUin } from '@/common/data'
|
||||
import { convertMessage2List, createSendElements, sendMsg } from './msg/SendMsg'
|
||||
import { isNull, log } from '@/common/utils'
|
||||
|
||||
|
||||
interface QuickOperationPrivateMessage {
|
||||
reply?: string
|
||||
auto_escape?: boolean
|
||||
}
|
||||
|
||||
interface QuickOperationGroupMessage extends QuickOperationPrivateMessage {
|
||||
// 回复群消息
|
||||
at_sender?: boolean
|
||||
delete?: boolean
|
||||
kick?: boolean
|
||||
ban?: boolean
|
||||
ban_duration?: number
|
||||
//
|
||||
}
|
||||
|
||||
interface QuickOperationFriendRequest {
|
||||
approve?: boolean
|
||||
remark?: string
|
||||
}
|
||||
|
||||
interface QuickOperationGroupRequest {
|
||||
approve?: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export type QuickOperation = QuickOperationPrivateMessage &
|
||||
QuickOperationGroupMessage &
|
||||
QuickOperationFriendRequest &
|
||||
QuickOperationGroupRequest
|
||||
|
||||
export type QuickOperationEvent = OB11Message | OB11FriendRequestEvent | OB11GroupRequestEvent;
|
||||
|
||||
export async function handleQuickOperation(context: QuickOperationEvent, quickAction: QuickOperation) {
|
||||
if (context.post_type === 'message') {
|
||||
handleMsg(context as OB11Message, quickAction).then().catch(log)
|
||||
}
|
||||
if (context.post_type === 'request') {
|
||||
const friendRequest = context as OB11FriendRequestEvent;
|
||||
const groupRequest = context as OB11GroupRequestEvent;
|
||||
if ((friendRequest).request_type === 'friend') {
|
||||
handleFriendRequest(friendRequest, quickAction).then().catch(log)
|
||||
}
|
||||
else if (groupRequest.request_type === 'group') {
|
||||
handleGroupRequest(groupRequest, quickAction).then().catch(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMsg(msg: OB11Message, quickAction: QuickOperationPrivateMessage | QuickOperationGroupMessage) {
|
||||
msg = msg as OB11Message
|
||||
const rawMessage = await dbUtil.getMsgByShortId(msg.message_id)
|
||||
const reply = quickAction.reply
|
||||
let peer: Peer = {
|
||||
chatType: ChatType.friend,
|
||||
peerUid: msg.user_id.toString(),
|
||||
}
|
||||
if (msg.message_type == 'private') {
|
||||
peer.peerUid = getUidByUin(msg.user_id.toString())
|
||||
if (msg.sub_type === 'group') {
|
||||
peer.chatType = ChatType.temp
|
||||
}
|
||||
}
|
||||
else {
|
||||
peer.chatType = ChatType.group
|
||||
peer.peerUid = msg.group_id.toString()
|
||||
}
|
||||
if (reply) {
|
||||
let group: Group = null
|
||||
let replyMessage: OB11MessageData[] = [
|
||||
{
|
||||
type: OB11MessageDataType.reply,
|
||||
data: {
|
||||
id: msg.message_id.toString(),
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
if (msg.message_type == 'group') {
|
||||
group = await getGroup(msg.group_id.toString())
|
||||
if ((quickAction as QuickOperationGroupMessage).at_sender) {
|
||||
replyMessage.push({
|
||||
type: 'at',
|
||||
data: {
|
||||
qq: msg.user_id.toString(),
|
||||
},
|
||||
} as OB11MessageAt)
|
||||
}
|
||||
}
|
||||
replyMessage = replyMessage.concat(convertMessage2List(reply, quickAction.auto_escape))
|
||||
const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group)
|
||||
log(`发送消息给`, peer, sendElements)
|
||||
sendMsg(peer, sendElements, deleteAfterSentFiles, false).then().catch(log)
|
||||
}
|
||||
if (msg.message_type === 'group') {
|
||||
const groupMsgQuickAction = quickAction as QuickOperationGroupMessage
|
||||
// handle group msg
|
||||
if (groupMsgQuickAction.delete) {
|
||||
NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then().catch(log)
|
||||
}
|
||||
if (groupMsgQuickAction.kick) {
|
||||
NTQQGroupApi.kickMember(peer.peerUid, [rawMessage.senderUid]).then().catch(log)
|
||||
}
|
||||
if (groupMsgQuickAction.ban) {
|
||||
NTQQGroupApi.banMember(peer.peerUid, [
|
||||
{
|
||||
uid: rawMessage.senderUid,
|
||||
timeStamp: groupMsgQuickAction.ban_duration || 60 * 30,
|
||||
},
|
||||
]).then().catch(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFriendRequest(request: OB11FriendRequestEvent,
|
||||
quickAction: QuickOperationFriendRequest) {
|
||||
if (!isNull(quickAction.approve)) {
|
||||
// todo: set remark
|
||||
NTQQFriendApi.handleFriendRequest(request.flag, quickAction.approve).then().catch(log)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleGroupRequest(request: OB11GroupRequestEvent,
|
||||
quickAction: QuickOperationGroupRequest) {
|
||||
if (!isNull(quickAction.approve)) {
|
||||
NTQQGroupApi.handleGroupRequest(
|
||||
request.flag,
|
||||
quickAction.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
|
||||
quickAction.reason,
|
||||
).then().catch(log)
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@ export enum ActionName {
|
||||
SetConfig = 'set_config',
|
||||
Debug = 'llonebot_debug',
|
||||
GetFile = 'get_file',
|
||||
GetFriendsWithCategory = 'get_friends_with_category',
|
||||
// onebot 11
|
||||
SendLike = 'send_like',
|
||||
GetLoginInfo = 'get_login_info',
|
||||
@@ -66,4 +67,7 @@ export enum ActionName {
|
||||
GoCQHTTP_DownloadFile = 'download_file',
|
||||
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
||||
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
||||
GoCQHTTP_GetEssenceMsg = "get_essence_msg_list",
|
||||
GoCQHTTP_HandleQuickOperation = ".handle_quick_operation",
|
||||
GetGroupHonorInfo = "get_group_honor_info",
|
||||
}
|
||||
|
@@ -3,10 +3,15 @@ import { NTQQUserApi } from '../../../ntqqapi/api'
|
||||
import { groups } from '../../../common/data'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
export class GetCookies extends BaseAction<null, { cookies: string; bkn: string }> {
|
||||
interface Payload {
|
||||
domain: string
|
||||
}
|
||||
|
||||
export class GetCookies extends BaseAction<Payload, { cookies: string; bkn: string }> {
|
||||
actionName = ActionName.GetCookies
|
||||
|
||||
protected async _handle() {
|
||||
return NTQQUserApi.getCookie(groups[0])
|
||||
protected async _handle(payload: Payload) {
|
||||
const domain = payload.domain || 'qun.qq.com'
|
||||
return NTQQUserApi.getCookies(domain);
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { OB11User } from '../../types'
|
||||
import { OB11Constructor } from '../../constructor'
|
||||
import { friends } from '../../../common/data'
|
||||
import { friends, rawFriends } from '@/common/data'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
import { NTQQFriendApi } from '../../../ntqqapi/api'
|
||||
import { log } from '../../../common/utils'
|
||||
import { NTQQFriendApi } from '@/ntqqapi/api'
|
||||
import { CategoryFriend } from '@/ntqqapi/types'
|
||||
|
||||
interface Payload {
|
||||
no_cache: boolean | string
|
||||
}
|
||||
|
||||
class GetFriendList extends BaseAction<Payload, OB11User[]> {
|
||||
export class GetFriendList extends BaseAction<Payload, OB11User[]> {
|
||||
actionName = ActionName.GetFriendList
|
||||
|
||||
protected async _handle(payload: Payload) {
|
||||
@@ -26,4 +26,11 @@ class GetFriendList extends BaseAction<Payload, OB11User[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export default GetFriendList
|
||||
|
||||
export class GetFriendWithCategory extends BaseAction<void, Array<CategoryFriend>> {
|
||||
actionName = ActionName.GetFriendsWithCategory;
|
||||
|
||||
protected async _handle(payload: void) {
|
||||
return rawFriends;
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import fastXmlParser, { XMLParser } from 'fast-xml-parser'
|
||||
import fastXmlParser from 'fast-xml-parser'
|
||||
import {
|
||||
OB11Group,
|
||||
OB11GroupMember,
|
||||
@@ -16,8 +16,7 @@ import {
|
||||
GrayTipElementSubType,
|
||||
Group,
|
||||
GroupMember,
|
||||
IMAGE_HTTP_HOST,
|
||||
IMAGE_HTTP_HOST_NT,
|
||||
PicType,
|
||||
RawMessage,
|
||||
SelfInfo,
|
||||
Sex,
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
User,
|
||||
VideoElement,
|
||||
} from '../ntqqapi/types'
|
||||
import { deleteGroup, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap } from '../common/data'
|
||||
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap } from '../common/data'
|
||||
import { EventType } from './event/OB11BaseEvent'
|
||||
import { encodeCQCode } from './cqcode'
|
||||
import { dbUtil } from '../common/db'
|
||||
@@ -56,6 +55,7 @@ export class OB11Constructor {
|
||||
let config = getConfigUtil().getConfig()
|
||||
const {
|
||||
enableLocalFile2Url,
|
||||
debug,
|
||||
ob11: { messagePostFormat },
|
||||
} = config
|
||||
const message_type = msg.chatType == ChatType.group ? 'group' : 'private'
|
||||
@@ -79,8 +79,11 @@ export class OB11Constructor {
|
||||
message_format: messagePostFormat === 'string' ? 'string' : 'array',
|
||||
post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
|
||||
}
|
||||
if (debug) {
|
||||
resMsg.raw = msg
|
||||
}
|
||||
if (msg.chatType == ChatType.group) {
|
||||
resMsg.sub_type = 'normal' // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼
|
||||
resMsg.sub_type = 'normal'
|
||||
resMsg.group_id = parseInt(msg.peerUin)
|
||||
const member = await getGroupMember(msg.peerUin, msg.senderUin)
|
||||
if (member) {
|
||||
@@ -156,17 +159,23 @@ export class OB11Constructor {
|
||||
else if (element.picElement) {
|
||||
message_data['type'] = 'image'
|
||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||
message_data['data']['file'] = element.picElement.fileName
|
||||
let fileName = element.picElement.fileName
|
||||
const sourcePath = element.picElement.sourcePath
|
||||
if (element.picElement.picType === PicType.gif && !fileName.endsWith('.gif')) {
|
||||
fileName += '.gif'
|
||||
}
|
||||
message_data['data']['file'] = fileName
|
||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
||||
|
||||
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType);
|
||||
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType)
|
||||
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
||||
message_data['data']['file_size'] = element.picElement.fileSize
|
||||
dbUtil
|
||||
.addFileCache(element.picElement.fileName, {
|
||||
fileName: element.picElement.fileName,
|
||||
filePath: element.picElement.sourcePath,
|
||||
.addFileCache(fileName, {
|
||||
fileName,
|
||||
elementId: element.elementId,
|
||||
filePath: sourcePath,
|
||||
fileSize: element.picElement.fileSize.toString(),
|
||||
url: message_data['data']['url'],
|
||||
downloadFunc: async () => {
|
||||
@@ -194,6 +203,7 @@ export class OB11Constructor {
|
||||
dbUtil
|
||||
.addFileCache(videoOrFileElement.fileUuid, {
|
||||
msgId: msg.msgId,
|
||||
elementId: element.elementId,
|
||||
fileName: videoOrFileElement.fileName,
|
||||
filePath: videoOrFileElement.filePath,
|
||||
fileSize: videoOrFileElement.fileSize,
|
||||
@@ -221,6 +231,7 @@ export class OB11Constructor {
|
||||
message_data['data']['file_size'] = element.pttElement.fileSize
|
||||
dbUtil
|
||||
.addFileCache(element.pttElement.fileName, {
|
||||
elementId: element.elementId,
|
||||
fileName: element.pttElement.fileName,
|
||||
filePath: element.pttElement.filePath,
|
||||
fileSize: element.pttElement.fileSize,
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { Response } from 'express'
|
||||
import { OB11Response } from '../action/OB11Response'
|
||||
import { HttpServerBase } from '../../common/server/http'
|
||||
import { HttpServerBase } from '@/common/server/http'
|
||||
import { actionHandlers, actionMap } from '../action'
|
||||
import { getConfigUtil } from '../../common/config'
|
||||
import { postOB11Event } from './postOB11Event'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import { postOb11Event } from './post-ob11-event'
|
||||
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
|
||||
import { selfInfo } from '../../common/data'
|
||||
import { selfInfo } from '@/common/data'
|
||||
|
||||
class OB11HTTPServer extends HttpServerBase {
|
||||
name = 'OneBot V11 server'
|
||||
name = 'LLOneBot server'
|
||||
|
||||
handleFailed(res: Response, payload: any, e: any) {
|
||||
res.send(OB11Response.error(e.stack.toString(), 200))
|
||||
@@ -40,7 +40,7 @@ class HTTPHeart {
|
||||
}
|
||||
this.intervalId = setInterval(() => {
|
||||
// ws的心跳是ws自己维护的
|
||||
postOB11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false)
|
||||
postOb11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false)
|
||||
}, heartInterval)
|
||||
}
|
||||
|
||||
|
81
src/onebot11/server/post-ob11-event.ts
Normal file
81
src/onebot11/server/post-ob11-event.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { OB11Message } from '../types'
|
||||
import { selfInfo } from '@/common/data'
|
||||
import { OB11BaseMetaEvent } from '../event/meta/OB11BaseMetaEvent'
|
||||
import { OB11BaseNoticeEvent } from '../event/notice/OB11BaseNoticeEvent'
|
||||
import { WebSocket as WebSocketClass } from 'ws'
|
||||
import { wsReply } from './ws/reply'
|
||||
import { log } from '@/common/utils'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import crypto from 'crypto'
|
||||
import { handleQuickOperation, QuickOperationEvent } from '../action/quick-operation'
|
||||
|
||||
export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent
|
||||
|
||||
const eventWSList: WebSocketClass[] = []
|
||||
|
||||
export function registerWsEventSender(ws: WebSocketClass) {
|
||||
eventWSList.push(ws)
|
||||
}
|
||||
|
||||
export function unregisterWsEventSender(ws: WebSocketClass) {
|
||||
let index = eventWSList.indexOf(ws)
|
||||
if (index !== -1) {
|
||||
eventWSList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
export function postWsEvent(event: PostEventType) {
|
||||
for (const ws of eventWSList) {
|
||||
new Promise(() => {
|
||||
wsReply(ws, event)
|
||||
}).then().catch(log)
|
||||
}
|
||||
}
|
||||
|
||||
export function postOb11Event(msg: PostEventType, reportSelf = false, postWs = true) {
|
||||
const config = getConfigUtil().getConfig()
|
||||
// 判断msg是否是event
|
||||
if (!config.reportSelfMessage && !reportSelf) {
|
||||
if (msg.post_type === 'message' && (msg as OB11Message).user_id.toString() == selfInfo.uin) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (config.ob11.enableHttpPost) {
|
||||
const msgStr = JSON.stringify(msg)
|
||||
const hmac = crypto.createHmac('sha1', config.ob11.httpSecret)
|
||||
hmac.update(msgStr)
|
||||
const sig = hmac.digest('hex')
|
||||
let headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-self-id': selfInfo.uin,
|
||||
}
|
||||
if (config.ob11.httpSecret) {
|
||||
headers['x-signature'] = 'sha1=' + sig
|
||||
}
|
||||
for (const host of config.ob11.httpHosts) {
|
||||
fetch(host, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: msgStr,
|
||||
}).then(
|
||||
async (res) => {
|
||||
log(`新消息事件HTTP上报成功: ${host} `, msgStr)
|
||||
try {
|
||||
const resJson = await res.json()
|
||||
log(`新消息事件HTTP上报返回快速操作: `, JSON.stringify(resJson))
|
||||
handleQuickOperation(msg as QuickOperationEvent, resJson).then().catch(log);
|
||||
} catch (e) {
|
||||
log(`新消息事件HTTP上报没有返回快速操作,不需要处理`)
|
||||
return
|
||||
}
|
||||
},
|
||||
(err: any) => {
|
||||
log(`新消息事件HTTP上报失败: ${host} `, err, msg)
|
||||
},
|
||||
).catch(log)
|
||||
}
|
||||
}
|
||||
if (postWs) {
|
||||
postWsEvent(msg)
|
||||
}
|
||||
}
|
@@ -1,185 +0,0 @@
|
||||
import { OB11Message, OB11MessageAt, OB11MessageData } from '../types'
|
||||
import { getFriend, getGroup, getUidByUin, selfInfo } from '../../common/data'
|
||||
import { OB11BaseMetaEvent } from '../event/meta/OB11BaseMetaEvent'
|
||||
import { OB11BaseNoticeEvent } from '../event/notice/OB11BaseNoticeEvent'
|
||||
import { WebSocket as WebSocketClass } from 'ws'
|
||||
import { wsReply } from './ws/reply'
|
||||
import { log } from '../../common/utils/log'
|
||||
import { getConfigUtil } from '../../common/config'
|
||||
import crypto from 'crypto'
|
||||
import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, Peer } from '../../ntqqapi/api'
|
||||
import { ChatType, Group, GroupRequestOperateTypes } from '../../ntqqapi/types'
|
||||
import { convertMessage2List, createSendElements, sendMsg } from '../action/msg/SendMsg'
|
||||
import { dbUtil } from '../../common/db'
|
||||
import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest'
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest'
|
||||
import { isNull } from '../../common/utils'
|
||||
|
||||
export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent
|
||||
|
||||
interface QuickActionPrivateMessage {
|
||||
reply?: string
|
||||
auto_escape?: boolean
|
||||
}
|
||||
|
||||
interface QuickActionGroupMessage extends QuickActionPrivateMessage {
|
||||
// 回复群消息
|
||||
at_sender?: boolean
|
||||
delete?: boolean
|
||||
kick?: boolean
|
||||
ban?: boolean
|
||||
ban_duration?: number
|
||||
//
|
||||
}
|
||||
|
||||
interface QuickActionFriendRequest {
|
||||
approve?: boolean
|
||||
remark?: string
|
||||
}
|
||||
|
||||
interface QuickActionGroupRequest {
|
||||
approve?: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
type QuickAction = QuickActionPrivateMessage &
|
||||
QuickActionGroupMessage &
|
||||
QuickActionFriendRequest &
|
||||
QuickActionGroupRequest
|
||||
|
||||
const eventWSList: WebSocketClass[] = []
|
||||
|
||||
export function registerWsEventSender(ws: WebSocketClass) {
|
||||
eventWSList.push(ws)
|
||||
}
|
||||
|
||||
export function unregisterWsEventSender(ws: WebSocketClass) {
|
||||
let index = eventWSList.indexOf(ws)
|
||||
if (index !== -1) {
|
||||
eventWSList.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
export function postWsEvent(event: PostEventType) {
|
||||
for (const ws of eventWSList) {
|
||||
new Promise(() => {
|
||||
wsReply(ws, event)
|
||||
}).then()
|
||||
}
|
||||
}
|
||||
|
||||
export function postOB11Event(msg: PostEventType, reportSelf = false, postWs = true) {
|
||||
const config = getConfigUtil().getConfig()
|
||||
// 判断msg是否是event
|
||||
if (!config.reportSelfMessage && !reportSelf) {
|
||||
if (msg.post_type === 'message' && (msg as OB11Message).user_id.toString() == selfInfo.uin) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (config.ob11.enableHttpPost) {
|
||||
const msgStr = JSON.stringify(msg)
|
||||
const hmac = crypto.createHmac('sha1', config.ob11.httpSecret)
|
||||
hmac.update(msgStr)
|
||||
const sig = hmac.digest('hex')
|
||||
let headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-self-id': selfInfo.uin,
|
||||
}
|
||||
if (config.ob11.httpSecret) {
|
||||
headers['x-signature'] = 'sha1=' + sig
|
||||
}
|
||||
for (const host of config.ob11.httpHosts) {
|
||||
fetch(host, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: msgStr,
|
||||
}).then(
|
||||
async (res) => {
|
||||
log(`新消息事件HTTP上报成功: ${host} `, msgStr)
|
||||
// todo: 处理不够优雅,应该使用高级泛型进行QuickAction类型识别
|
||||
let resJson: QuickAction
|
||||
try {
|
||||
resJson = await res.json()
|
||||
log(`新消息事件HTTP上报返回快速操作: `, JSON.stringify(resJson))
|
||||
} catch (e) {
|
||||
log(`新消息事件HTTP上报没有返回快速操作,不需要处理`)
|
||||
return
|
||||
}
|
||||
if (msg.post_type === 'message') {
|
||||
msg = msg as OB11Message
|
||||
const rawMessage = await dbUtil.getMsgByShortId(msg.message_id)
|
||||
resJson = resJson as QuickActionPrivateMessage | QuickActionGroupMessage
|
||||
const reply = resJson.reply
|
||||
let peer: Peer = {
|
||||
chatType: ChatType.friend,
|
||||
peerUid: msg.user_id.toString(),
|
||||
}
|
||||
if (msg.message_type == 'private') {
|
||||
peer.peerUid = getUidByUin(msg.user_id.toString())
|
||||
if (msg.sub_type === 'group') {
|
||||
peer.chatType = ChatType.temp
|
||||
}
|
||||
} else {
|
||||
peer.chatType = ChatType.group
|
||||
peer.peerUid = msg.group_id.toString()
|
||||
}
|
||||
if (reply) {
|
||||
let group: Group = null
|
||||
let replyMessage: OB11MessageData[] = []
|
||||
|
||||
if (msg.message_type == 'group') {
|
||||
group = await getGroup(msg.group_id.toString())
|
||||
if ((resJson as QuickActionGroupMessage).at_sender) {
|
||||
replyMessage.push({
|
||||
type: 'at',
|
||||
data: {
|
||||
qq: msg.user_id.toString(),
|
||||
},
|
||||
} as OB11MessageAt)
|
||||
}
|
||||
}
|
||||
replyMessage = replyMessage.concat(convertMessage2List(reply, resJson.auto_escape))
|
||||
const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group)
|
||||
log(`发送消息给`, peer, sendElements)
|
||||
sendMsg(peer, sendElements, deleteAfterSentFiles, false).then()
|
||||
} else if (resJson.delete) {
|
||||
NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then()
|
||||
} else if (resJson.kick) {
|
||||
NTQQGroupApi.kickMember(peer.peerUid, [rawMessage.senderUid]).then()
|
||||
} else if (resJson.ban) {
|
||||
NTQQGroupApi.banMember(peer.peerUid, [
|
||||
{
|
||||
uid: rawMessage.senderUid,
|
||||
timeStamp: resJson.ban_duration || 60 * 30,
|
||||
},
|
||||
]).then()
|
||||
}
|
||||
} else if (msg.post_type === 'request') {
|
||||
if ((msg as OB11FriendRequestEvent).request_type === 'friend') {
|
||||
resJson = resJson as QuickActionFriendRequest
|
||||
if (!isNull(resJson.approve)) {
|
||||
// todo: set remark
|
||||
NTQQFriendApi.handleFriendRequest((msg as OB11FriendRequestEvent).flag, resJson.approve).then()
|
||||
}
|
||||
} else if ((msg as OB11GroupRequestEvent).request_type === 'group') {
|
||||
resJson = resJson as QuickActionGroupRequest
|
||||
if (!isNull(resJson.approve)) {
|
||||
NTQQGroupApi.handleGroupRequest(
|
||||
(msg as OB11FriendRequestEvent).flag,
|
||||
resJson.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
|
||||
resJson.reason,
|
||||
).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(err: any) => {
|
||||
log(`新消息事件HTTP上报失败: ${host} `, err, msg)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (postWs) {
|
||||
postWsEvent(msg)
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ import { ActionName } from '../../action/types'
|
||||
import { OB11Response } from '../../action/OB11Response'
|
||||
import BaseAction from '../../action/BaseAction'
|
||||
import { actionMap } from '../../action'
|
||||
import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../postOB11Event'
|
||||
import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../post-ob11-event'
|
||||
import { wsReply } from './reply'
|
||||
import { WebSocket as WebSocketClass } from 'ws'
|
||||
import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent'
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { WebSocket } from 'ws'
|
||||
import { actionMap } from '../../action'
|
||||
import { OB11Response } from '../../action/OB11Response'
|
||||
import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../postOB11Event'
|
||||
import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../post-ob11-event'
|
||||
import { ActionName } from '../../action/types'
|
||||
import BaseAction from '../../action/BaseAction'
|
||||
import { LifeCycleSubType, OB11LifeCycleEvent } from '../../event/meta/OB11LifeCycleEvent'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { WebSocket as WebSocketClass } from 'ws'
|
||||
import { OB11Response } from '../../action/OB11Response'
|
||||
import { PostEventType } from '../postOB11Event'
|
||||
import { PostEventType } from '../post-ob11-event'
|
||||
import { log } from '../../../common/utils/log'
|
||||
import { isNull } from '../../../common/utils/helper'
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
export const version = '3.24.3'
|
||||
export const version = '3.26.1'
|
||||
|
@@ -7,6 +7,8 @@
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/common/*": [
|
||||
|
Reference in New Issue
Block a user