Compare commits

..

53 Commits

Author SHA1 Message Date
linyuchen
52a065542e chore: v3.26.6 2024-06-10 14:38:20 +08:00
linyuchen
fd10469685 feat: video url 2024-06-10 14:35:00 +08:00
linyuchen
a2ee75b113 refactor: sent msg status waiter 2024-06-09 15:27:33 +08:00
linyuchen
0f7f243b98 Merge pull request #250 from Bluefissure/reverse-ws-ua
feat: add ua to reverse websocket headers
2024-06-06 17:35:21 +08:00
Bluefissure
97d7996a50 fix: add version to ua 2024-06-06 08:53:37 +00:00
Bluefissure
b658d164f9 feat: add ua to reverse websocket headers 2024-06-06 08:48:18 +00:00
linyuchen
f150ae478b chore: v3.26.5 2024-06-01 20:19:05 +08:00
linyuchen
d1f68553f1 fix: 加载卡顿,群成员名片变动 2024-06-01 20:18:38 +08:00
linyuchen
f47f0800de Merge remote-tracking branch 'origin/main' 2024-05-29 16:56:08 +08:00
linyuchen
b7ddefc950 fix: QZone cookies 2024-05-29 16:38:22 +08:00
linyuchen
25b3325a44 fix: comment 2024-05-29 16:28:46 +08:00
linyuchen
c281b87bab merge main 2024-05-29 16:27:06 +08:00
linyuchen
c0946ddda2 chore: version 3.26.4 2024-05-29 16:26:04 +08:00
linyuchen
1128cf679c refactor: send file timeout 2024-05-29 16:25:42 +08:00
linyuchen
ff65a42350 Merge pull request #242 from LLOneBot/dev
feat: support qzone cookies
2024-05-29 16:24:32 +08:00
手瓜一十雪
c459587dcd refactor: get cookies 2024-05-29 12:03:35 +08:00
手瓜一十雪
6f8ea9677f feat: support qzone cookies 2024-05-28 17:14:24 +08:00
手瓜一十雪
38197527fa Merge branch 'main' into dev 2024-05-28 17:11:13 +08:00
手瓜一十雪
21b2bd2c8e feat: cookies 2024-05-28 17:11:07 +08:00
linyuchen
25158eee55 chore: version 3.26.3 2024-05-28 16:41:28 +08:00
linyuchen
1aa804f255 chore: version 3.26.3 2024-05-28 16:41:22 +08:00
linyuchen
fbe101339d fix: #237 2024-05-28 16:40:51 +08:00
linyuchen
a4aeb8171d fix: QQ package.json on macOS 2024-05-28 15:42:22 +08:00
linyuchen
27f98a459c fix: member info change on version 24108 2024-05-28 15:31:59 +08:00
linyuchen
e6b0eaa46d Merge pull request #235 from LLOneBot/dev
快速操作回复自动引用原消息开关
2024-05-24 17:14:54 +08:00
linyuchen
f336317a33 chore: version 3.26.2 2024-05-24 17:12:35 +08:00
linyuchen
17b44cc0fa refactor: #226 Quick operation reply automatically quotes the original message switch 2024-05-24 17:10:41 +08:00
linyuchen
debe3a8597 chore: version 3.26.1 2024-05-24 08:54:23 +08:00
linyuchen
f36c5e849f Merge pull request #234 from LLOneBot/dev
fix: #215 get_forward_msg params missing id(onebot11)
2024-05-24 08:52:34 +08:00
linyuchen
abbd6797c4 fix: #215 get_forward_msg params missing id(onebot11) 2024-05-24 08:50:22 +08:00
linyuchen
fdb7784a7d Merge pull request #233 from LLOneBot/dev
[Feature] OneBot11消息构造添加raw字段,单条转发消息接口返回message_id
2024-05-24 08:40:44 +08:00
linyuchen
92b49015b0 feat: Forward single msg return message_id 2024-05-24 08:36:42 +08:00
linyuchen
1765ffff7b style: format 2024-05-24 08:15:08 +08:00
linyuchen
3024316b5b feat: #232 /get_msg, /get_group_msg_history add raw message 2024-05-24 08:11:38 +08:00
linyuchen
9a0d89bfbf Update README.md 2024-05-19 07:52:12 +08:00
linyuchen
807ef3b700 Merge pull request #228 from LLOneBot/dev
feat: Quick operation reply auto quote original message
2024-05-18 16:53:37 +08:00
linyuchen
948f10d4e3 feat: Quick operation reply auto quote original message 2024-05-18 16:51:34 +08:00
linyuchen
0f99b5cb87 Merge pull request #227 from LLOneBot/dev
fix: Send msg timeout minimum
2024-05-18 16:36:30 +08:00
linyuchen
6413b0ff82 fix: Send msg timeout minimum 2024-05-18 16:34:12 +08:00
linyuchen
39713d8e11 Merge branch 'main' into dev 2024-05-18 16:31:22 +08:00
linyuchen
739a497af6 chore: v3.26.0 2024-05-18 13:16:45 +08:00
linyuchen
de2fe9b0aa Merge pull request #225 from LLOneBot/dev
Feature: #209,New API get_friends_with_category
2024-05-18 13:11:30 +08:00
linyuchen
44448895a0 feat: 209 2024-05-18 13:09:45 +08:00
linyuchen
cfd9097769 feat: 209 2024-05-18 13:08:44 +08:00
linyuchen
627042fd25 Merge pull request #224 from LLOneBot/dev
Fix: #219,发送视频图片进行文件大小判断,超时时间根据文件大小(512kb/s)动态调整
2024-05-18 12:53:42 +08:00
linyuchen
b51ce24d0c fix: #219 2024-05-18 12:50:11 +08:00
linyuchen
fc0881eccc Merge pull request #223 from LLOneBot/dev
fix: #218
2024-05-18 12:13:23 +08:00
linyuchen
6b8509d2b2 fix: #218 2024-05-18 12:12:16 +08:00
linyuchen
cf1d67a5cf Merge pull request #222 from LLOneBot/dev
Feature: websocket .handle_quick_operation
2024-05-18 11:47:56 +08:00
linyuchen
473ebd25b8 fix: promise catch 2024-05-18 11:46:51 +08:00
linyuchen
d4427cfff4 feat: .handle_quick_operation of websocket 2024-05-18 11:45:42 +08:00
linyuchen
9d2e9786cc chore: v3.25.0 2024-05-15 23:03:19 +08:00
linyuchen
9968f714c7 chore: v3.25.0 2024-05-15 23:03:04 +08:00
37 changed files with 1154 additions and 771 deletions

View File

@@ -1,4 +1,4 @@
# LLOneBot API # LLOneBot
LiteLoaderQQNT插件使你的NTQQ支持OneBot11协议进行QQ机器人开发 LiteLoaderQQNT插件使你的NTQQ支持OneBot11协议进行QQ机器人开发

View File

@@ -1,10 +1,10 @@
{ {
"manifest_version": 4, "manifest_version": 4,
"type": "extension", "type": "extension",
"name": "LLOneBot v3.24.4", "name": "LLOneBot v3.26.6",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新", "description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
"version": "3.24.4", "version": "3.26.6",
"icon": "./icon.jpg", "icon": "./icon.jpg",
"authors": [ "authors": [
{ {

View File

@@ -40,6 +40,7 @@ export class ConfigUtil {
enableWsReverse: false, enableWsReverse: false,
messagePostFormat: 'array', messagePostFormat: 'array',
enableHttpHeart: false, enableHttpHeart: false,
enableQOAutoQuote: false
} }
let defaultConfig: Config = { let defaultConfig: Config = {
ob11: ob11Default, ob11: ob11Default,

View File

@@ -1,4 +1,12 @@
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 { type FileCache, type LLOneBotError } from './types'
import { NTQQGroupApi } from '../ntqqapi/api/group' import { NTQQGroupApi } from '../ntqqapi/api/group'
import { log } from './utils/log' import { log } from './utils/log'
@@ -14,8 +22,8 @@ export const selfInfo: SelfInfo = {
} }
export const WebGroupData = { export const WebGroupData = {
GroupData: new Map<string, Array<WebApiGroupMember>>(), GroupData: new Map<string, Array<WebApiGroupMember>>(),
GroupTime: new Map<string, number>() GroupTime: new Map<string, number>(),
}; }
export let groups: Group[] = [] export let groups: Group[] = []
export let friends: Friend[] = [] export let friends: Friend[] = []
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>() export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
@@ -53,7 +61,8 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
if (group) { if (group) {
groups.push(group) groups.push(group)
} }
} catch (e) {} } catch (e) {
}
} }
return group return group
} }
@@ -111,3 +120,5 @@ export function getUidByUin(uin: string) {
} }
export let tempGroupCodeMap: Record<string, string> = {} // peerUid => 群号 export let tempGroupCodeMap: Record<string, string> = {} // peerUid => 群号
export let rawFriends: CategoryFriend[] = []

View File

@@ -10,6 +10,7 @@ export interface OB11Config {
enableWsReverse?: boolean enableWsReverse?: boolean
messagePostFormat?: 'array' | 'string' messagePostFormat?: 'array' | 'string'
enableHttpHeart?: boolean enableHttpHeart?: boolean
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息
} }
export interface CheckVersion { export interface CheckVersion {
result: boolean result: boolean
@@ -45,5 +46,6 @@ export interface FileCache {
fileUuid?: string fileUuid?: string
url?: string url?: string
msgId?: string msgId?: string
elementId: string
downloadFunc?: () => Promise<void> downloadFunc?: () => Promise<void>
} }

View File

@@ -3,26 +3,34 @@ import fs from 'node:fs'
import os from 'node:os' import os from 'node:os'
import { systemPlatform } from './system' import { systemPlatform } from './system'
export const exePath = process.execPath; export const exePath = process.execPath
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json'); function getPKGPath() {
let configVersionInfoPath; let p = path.join(path.dirname(exePath), 'resources', 'app', 'package.json')
if (systemPlatform === 'darwin') {
p = path.join(path.dirname(path.dirname(exePath)), 'Resources', 'app', 'package.json')
}
return p
}
export const pkgInfoPath = getPKGPath()
let configVersionInfoPath: string
if (os.platform() !== 'linux') { if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json'); configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json')
} else { }
const userPath = os.homedir(); else {
const appDataPath = path.resolve(userPath, './.config/QQ'); const userPath = os.homedir()
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); const appDataPath = path.resolve(userPath, './.config/QQ')
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json')
} }
if (typeof configVersionInfoPath !== 'string') { if (typeof configVersionInfoPath !== 'string') {
throw new Error('Something went wrong when load QQ info path'); throw new Error('Something went wrong when load QQ info path')
} }
export { configVersionInfoPath }; export { configVersionInfoPath }
type QQPkgInfo = { type QQPkgInfo = {
version: string; version: string;
@@ -43,21 +51,21 @@ let _qqVersionConfigInfo: QQVersionConfigInfo = {
'curVersion': '9.9.9-23361', 'curVersion': '9.9.9-23361',
'prevVersion': '', 'prevVersion': '',
'onErrorVersions': [], 'onErrorVersions': [],
'buildId': '23361' 'buildId': '23361',
}; }
if (fs.existsSync(configVersionInfoPath)) { if (fs.existsSync(configVersionInfoPath)) {
try { try {
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString()); const _ = JSON.parse(fs.readFileSync(configVersionInfoPath).toString())
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _); _qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _)
} catch (e) { } catch (e) {
console.error('Load QQ version config info failed, Use default version', e); console.error('Load QQ version config info failed, Use default version', e)
} }
} }
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo; export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath); export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath)
// platform_type: 3, // platform_type: 3,
// app_type: 4, // app_type: 4,
// app_version: '9.9.9-23159', // app_version: '9.9.9-23159',
@@ -66,10 +74,10 @@ export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath);
// platVer: '10.0.26100', // platVer: '10.0.26100',
// clientVer: '9.9.9-23159', // clientVer: '9.9.9-23159',
let _appid: string = '537213803'; // 默认为 Windows 平台的 appid let _appid: string = '537213803' // 默认为 Windows 平台的 appid
if (systemPlatform === 'linux') { if (systemPlatform === 'linux') {
_appid = '537213827'; _appid = '537213827'
} }
// todo: mac 平台的 appid // todo: mac 平台的 appid
export const appid = _appid; export const appid = _appid
export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106' export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106'

View File

@@ -1,30 +1,50 @@
import https from 'node:https'; import https from 'node:https';
import http from 'node:http'; import http from 'node:http';
import { log } from '@/common/utils/log'
export class RequestUtil { export class RequestUtil {
// 适用于获取服务器下发cookies时获取仅GET // 适用于获取服务器下发cookies时获取仅GET
static async HttpsGetCookies(url: string): Promise<Map<string, string>> { static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
return new Promise<Map<string, string>>((resolve, reject) => { const client = url.startsWith('https') ? https : http;
const protocol = url.startsWith('https://') ? https : http; return new Promise((resolve, reject) => {
protocol.get(url, (res) => { client.get(url, (res) => {
const cookiesHeader = res.headers['set-cookie']; let cookies: { [key: string]: string } = {};
if (!cookiesHeader) { const handleRedirect = (res: http.IncomingMessage) => {
resolve(new Map<string, string>()); //console.log(res.headers.location);
} else { if (res.statusCode === 301 || res.statusCode === 302) {
const cookiesMap = new Map<string, string>(); if (res.headers.location) {
cookiesHeader.forEach((cookieStr) => { const redirectUrl = new URL(res.headers.location, url);
cookieStr.split(';').forEach((cookiePart) => { RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
const trimmedPart = cookiePart.trim(); // 合并重定向过程中的cookies
if (trimmedPart.includes('=')) { log('redirectCookies', redirectCookies)
const [key, value] = trimmedPart.split('=').map(part => part.trim()); cookies = { ...cookies, ...redirectCookies };
cookiesMap.set(key, decodeURIComponent(value)); // 解码cookie值 resolve(cookies);
} });
}); } else {
resolve(cookies);
}
} else {
resolve(cookies);
}
};
res.on('data', () => { }); // Necessary to consume the stream
res.on('end', () => {
handleRedirect(res);
});
if (res.headers['set-cookie']) {
// console.log(res.headers['set-cookie']);
log('set-cookie', url, res.headers['set-cookie']);
res.headers['set-cookie'].forEach((cookie) => {
const parts = cookie.split(';')[0].split('=');
const key = parts[0];
const value = parts[1];
if (key && value && key.length > 0 && value.length > 0) {
cookies[key] = value;
}
}); });
resolve(cookiesMap);
} }
}).on('error', (error) => { }).on('error', (err) => {
reject(error); reject(err);
}); });
}); });
} }

View File

@@ -25,7 +25,7 @@ import {
selfInfo, selfInfo,
uidMaps, uidMaps,
} from '../common/data' } from '../common/data'
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook } from '../ntqqapi/hook' import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, startHook } from '../ntqqapi/hook'
import { OB11Constructor } from '../onebot11/constructor' import { OB11Constructor } from '../onebot11/constructor'
import { import {
ChatType, ChatType,
@@ -38,7 +38,7 @@ import {
import { httpHeart, ob11HTTPServer } from '../onebot11/server/http' import { httpHeart, ob11HTTPServer } from '../onebot11/server/http'
import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent' import { OB11FriendRecallNoticeEvent } from '../onebot11/event/notice/OB11FriendRecallNoticeEvent'
import { OB11GroupRecallNoticeEvent } from '../onebot11/event/notice/OB11GroupRecallNoticeEvent' 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 { ob11ReverseWebsockets } from '../onebot11/server/ws/ReverseWebsocket'
import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent' import { OB11GroupAdminNoticeEvent } from '../onebot11/event/notice/OB11GroupAdminNoticeEvent'
import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest' import { OB11GroupRequestEvent } from '../onebot11/event/request/OB11GroupRequest'
@@ -56,6 +56,7 @@ import { getConfigUtil } from '../common/config'
import { checkFfmpeg } from '../common/utils/video' import { checkFfmpeg } from '../common/utils/video'
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
import '../ntqqapi/native/wrapper' import '../ntqqapi/native/wrapper'
import { sentMessages } from '@/ntqqapi/api'
let running = false let running = false
@@ -142,7 +143,8 @@ function onLoad() {
.catch((e) => { .catch((e) => {
log('保存设置失败', e.stack) log('保存设置失败', e.stack)
}) })
} else { }
else {
} }
}) })
.catch((err) => { .catch((err) => {
@@ -169,12 +171,8 @@ function onLoad() {
OB11Constructor.message(message) OB11Constructor.message(message)
.then((msg) => { .then((msg) => {
if (debug) { if (!debug && msg.message.length === 0) {
msg.raw = message return
} else {
if (msg.message.length === 0) {
return
}
} }
const isSelfMsg = msg.user_id.toString() == selfInfo.uin const isSelfMsg = msg.user_id.toString() == selfInfo.uin
if (isSelfMsg && !reportSelfMessage) { if (isSelfMsg && !reportSelfMessage) {
@@ -183,26 +181,27 @@ function onLoad() {
if (isSelfMsg) { if (isSelfMsg) {
msg.target_id = parseInt(message.peerUin) msg.target_id = parseInt(message.peerUin)
} }
postOB11Event(msg) postOb11Event(msg)
// log("post msg", msg) // log("post msg", msg)
}) })
.catch((e) => log('constructMessage error: ', e.stack.toString())) .catch((e) => log('constructMessage error: ', e.stack.toString()))
OB11Constructor.GroupEvent(message).then((groupEvent) => { OB11Constructor.GroupEvent(message).then((groupEvent) => {
if (groupEvent) { if (groupEvent) {
// log("post group event", groupEvent); // log("post group event", groupEvent);
postOB11Event(groupEvent) postOb11Event(groupEvent)
} }
}) })
OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => { OB11Constructor.FriendAddEvent(message).then((friendAddEvent) => {
if (friendAddEvent) { if (friendAddEvent) {
// log("post friend add event", friendAddEvent); // log("post friend add event", friendAddEvent);
postOB11Event(friendAddEvent) postOb11Event(friendAddEvent)
} }
}) })
} }
} }
async function startReceiveHook() { async function startReceiveHook() {
startHook().then()
if (getConfigUtil().getConfig().enablePoke) { if (getConfigUtil().getConfig().enablePoke) {
crychic.loadNode() crychic.loadNode()
crychic.registerPokeHandler((id, isGroup) => { crychic.registerPokeHandler((id, isGroup) => {
@@ -210,10 +209,11 @@ function onLoad() {
let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent
if (isGroup) { if (isGroup) {
pokeEvent = new OB11GroupPokeEvent(parseInt(id)) pokeEvent = new OB11GroupPokeEvent(parseInt(id))
} else { }
else {
pokeEvent = new OB11FriendPokeEvent(parseInt(id)) pokeEvent = new OB11FriendPokeEvent(parseInt(id))
} }
postOB11Event(pokeEvent) postOb11Event(pokeEvent)
}) })
} }
registerReceiveHook<{ registerReceiveHook<{
@@ -228,6 +228,10 @@ function onLoad() {
const recallMsgIds: string[] = [] // 避免重复上报 const recallMsgIds: string[] = [] // 避免重复上报
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
for (const message of payload.msgList) { for (const message of payload.msgList) {
const sentMessage = sentMessages[message.msgId]
if (sentMessage){
Object.assign(sentMessage, message)
}
log('message update', message.msgId, message) log('message update', message.msgId, message)
if (message.recallTime != '0') { if (message.recallTime != '0') {
if (recallMsgIds.includes(message.msgId)) { if (recallMsgIds.includes(message.msgId)) {
@@ -244,7 +248,7 @@ function onLoad() {
OB11Constructor.RecallEvent(message).then((recallEvent) => { OB11Constructor.RecallEvent(message).then((recallEvent) => {
if (recallEvent) { if (recallEvent) {
log('post recall event', recallEvent) log('post recall event', recallEvent)
postOB11Event(recallEvent) postOb11Event(recallEvent)
} }
}) })
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了 // 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
@@ -322,11 +326,13 @@ function onLoad() {
? 'unset' ? 'unset'
: 'set' : 'set'
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal; // member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
postOB11Event(groupAdminNoticeEvent, true) postOb11Event(groupAdminNoticeEvent, true)
} else { }
else {
log('获取群通知的成员信息失败', notify, getGroup(notify.group.groupCode)) 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) log('有成员退出通知', notify)
try { try {
const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid) const member1 = await NTQQUserApi.getUserDetailInfo(notify.user1.uid)
@@ -344,43 +350,62 @@ function onLoad() {
parseInt(operatorId), parseInt(operatorId),
subType, subType,
) )
postOB11Event(groupDecreaseEvent, true) postOb11Event(groupDecreaseEvent, true)
} catch (e) { } catch (e) {
log('获取群通知的成员信息失败', notify, e.stack.toString()) log('获取群通知的成员信息失败', notify, e.stack.toString())
} }
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) { }
else if ([GroupNotifyTypes.JOIN_REQUEST, GroupNotifyTypes.JOIN_REQUEST_BY_INVITED].includes(notify.type)) {
log('有加群请求') log('有加群请求')
let groupRequestEvent = new OB11GroupRequestEvent() let groupRequestEvent = new OB11GroupRequestEvent()
groupRequestEvent.group_id = parseInt(notify.group.groupCode) groupRequestEvent.group_id = parseInt(notify.group.groupCode)
let requestQQ = '' let requestQQ = uidMaps[notify.user1.uid]
try { if (!requestQQ) {
requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin try {
} catch (e) { requestQQ = (await NTQQUserApi.getUserDetailInfo(notify.user1.uid)).uin
log('获取加群人QQ号失败', e) } catch (e) {
log('获取加群人QQ号失败', e)
}
} }
groupRequestEvent.user_id = parseInt(requestQQ) || 0 groupRequestEvent.user_id = parseInt(requestQQ) || 0
groupRequestEvent.sub_type = 'add' groupRequestEvent.sub_type = 'add'
groupRequestEvent.comment = notify.postscript groupRequestEvent.comment = notify.postscript
groupRequestEvent.flag = notify.seq groupRequestEvent.flag = notify.seq
postOB11Event(groupRequestEvent) if (notify.type == GroupNotifyTypes.JOIN_REQUEST_BY_INVITED) {
} else if (notify.type == GroupNotifyTypes.INVITE_ME) { // groupRequestEvent.sub_type = 'invite'
let invitorQQ = uidMaps[notify.user2.uid]
if (!invitorQQ) {
try {
let invitor = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))
groupRequestEvent.invitor_id = parseInt(invitor.uin)
} catch (e) {
groupRequestEvent.invitor_id = 0
log('获取邀请人QQ号失败', e)
}
}
}
postOb11Event(groupRequestEvent)
}
else if (notify.type == GroupNotifyTypes.INVITE_ME) {
log('收到邀请我加群通知') log('收到邀请我加群通知')
let groupInviteEvent = new OB11GroupRequestEvent() let groupInviteEvent = new OB11GroupRequestEvent()
groupInviteEvent.group_id = parseInt(notify.group.groupCode) groupInviteEvent.group_id = parseInt(notify.group.groupCode)
let user_id = (await getFriend(notify.user2.uid))?.uin let user_id = uidMaps[notify.user2.uid]
if (!user_id) { if (!user_id) {
user_id = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin user_id = (await NTQQUserApi.getUserDetailInfo(notify.user2.uid))?.uin
} }
groupInviteEvent.user_id = parseInt(user_id) groupInviteEvent.user_id = parseInt(user_id)
groupInviteEvent.sub_type = 'invite' groupInviteEvent.sub_type = 'invite'
// groupInviteEvent.invitor_id = parseInt(user_id)
groupInviteEvent.flag = notify.seq groupInviteEvent.flag = notify.seq
postOB11Event(groupInviteEvent) postOb11Event(groupInviteEvent)
} }
} catch (e) { } catch (e) {
log('解析群通知失败', e.stack.toString()) log('解析群通知失败', e.stack.toString())
} }
} }
} else if (payload.doubt) { }
else if (payload.doubt) {
// 可能有群管理员变动 // 可能有群管理员变动
} }
}) })
@@ -400,7 +425,7 @@ function onLoad() {
} }
friendRequestEvent.flag = flag friendRequestEvent.flag = flag
friendRequestEvent.comment = req.extWords friendRequestEvent.comment = req.extWords
postOB11Event(friendRequestEvent) postOb11Event(friendRequestEvent)
} }
} }
}) })
@@ -417,8 +442,33 @@ function onLoad() {
uidMaps[value] = key uidMaps[value] = key
} }
}) })
startReceiveHook().then() try{
NTQQGroupApi.getGroups(true).then() log('start get groups')
const _groups = await NTQQGroupApi.getGroups()
log('_groups', _groups)
await Promise.all(
_groups.map(async (group) => {
try {
const members = await NTQQGroupApi.getGroupMembers(group.groupCode)
group.members = members
groups.push(group)
} catch (e) {
log('获取群成员失败', e)
}
})
)
}
catch (e) {
log('获取群列表失败', e)
}
finally {
log('start activate group member info')
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
NTQQGroupApi.activateMemberListChange().then().catch(log)
startReceiveHook().then()
}
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
if (config.ob11.enableHttp) { if (config.ob11.enableHttp) {
ob11HTTPServer.start(config.ob11.httpPort) ob11HTTPServer.start(config.ob11.httpPort)
@@ -473,7 +523,8 @@ function onLoad() {
getUserNick().then() getUserNick().then()
start().then() start().then()
} else { }
else {
setTimeout(init, 1000) setTimeout(init, 1000)
} }
} }

View File

@@ -10,15 +10,23 @@ import {
ElementType, ElementType,
IMAGE_HTTP_HOST, IMAGE_HTTP_HOST,
IMAGE_HTTP_HOST_NT, PicElement, IMAGE_HTTP_HOST_NT, PicElement,
RawMessage,
} from '../types' } from '../types'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { log } from '@/common/utils' import { log } from '@/common/utils'
import { rkeyManager } from '@/ntqqapi/api/rkey' import { rkeyManager } from '@/ntqqapi/api/rkey'
import { wrapperApi } from '@/ntqqapi/native/wrapper'
import { Peer } from '@/ntqqapi/api/msg'
export class NTQQFileApi { export class NTQQFileApi {
static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string> {
return (await wrapperApi.NodeIQQNTWrapperSession.getRichMediaService().getVideoPlayUrlV2(peer,
msgId,
elementId,
0,
{ downSourceType: 1, triggerType: 1 })).urlResult?.domainUrl[0]?.url;
}
static async getFileType(filePath: string) { static async getFileType(filePath: string) {
return await callNTQQApi<{ ext: string }>({ return await callNTQQApi<{ ext: string }>({
className: NTQQApiClass.FS_API, className: NTQQApiClass.FS_API,

View File

@@ -7,15 +7,48 @@ import { log } from '../../common/utils/log'
import { NTQQWindowApi, NTQQWindows } from './window' import { NTQQWindowApi, NTQQWindows } from './window'
export class NTQQGroupApi { export class NTQQGroupApi {
static async activateMemberListChange(){
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.ACTIVATE_MEMBER_LIST_CHANGE,
classNameIsRegister: true,
args: [],
})
}
static async activateMemberInfoChange(){
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.ACTIVATE_MEMBER_INFO_CHANGE,
classNameIsRegister: true,
args: [],
})
}
static async getGroupAllInfo(groupCode: string, source: number=4){
return await callNTQQApi<GeneralCallResult & Group>({
methodName: NTQQApiMethod.GET_GROUP_ALL_INFO,
args: [
{
groupCode,
source
},
null,
],
})
}
static async getGroups(forced = false) { static async getGroups(forced = false) {
let cbCmd = ReceiveCmdS.GROUPS // let cbCmd = ReceiveCmdS.GROUPS
if (process.platform != 'win32') { // if (process.platform != 'win32') {
cbCmd = ReceiveCmdS.GROUPS_STORE // cbCmd = ReceiveCmdS.GROUPS_STORE
} // }
const result = await callNTQQApi<{ const result = await callNTQQApi<{
updateType: number updateType: number
groupList: Group[] groupList: Group[]
}>({ methodName: NTQQApiMethod.GROUPS, args: [{ force_update: forced }, undefined], cbCmd }) }>({
methodName: NTQQApiMethod.GROUPS,
args: [{ force_update: forced }, undefined],
cbCmd: [ReceiveCmdS.GROUPS, ReceiveCmdS.GROUPS_STORE],
afterFirstCmd: false,
})
log('get groups result', result)
return result.groupList return result.groupList
} }
static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> { static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> {
@@ -58,6 +91,19 @@ export class NTQQGroupApi {
return [] return []
} }
} }
static async getGroupMembersInfo(groupCode: string, uids: string[], forceUpdate: boolean=false) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.GROUP_MEMBERS_INFO,
args: [
{
forceUpdate,
groupCode,
uids
},
null,
],
})
}
static async getGroupNotifies() { static async getGroupNotifies() {
// 获取管理员变更 // 获取管理员变更
// 加群通知,退出通知,需要管理员权限 // 加群通知,退出通知,需要管理员权限
@@ -158,7 +204,8 @@ export class NTQQGroupApi {
}) })
} }
static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) { static async setMemberCard(groupQQ: string, memberUid: string, cardName: string) {
return await callNTQQApi<GeneralCallResult>({ NTQQGroupApi.activateMemberListChange().then().catch(log)
const res = await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.SET_MEMBER_CARD, methodName: NTQQApiMethod.SET_MEMBER_CARD,
args: [ args: [
{ {
@@ -169,6 +216,8 @@ export class NTQQGroupApi {
null, null,
], ],
}) })
NTQQGroupApi.getGroupMembersInfo(groupQQ, [memberUid], true).then().catch(log)
return res;
} }
static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) { static async setMemberRole(groupQQ: string, memberUid: string, role: GroupMemberRole) {
return await callNTQQApi<GeneralCallResult>({ return await callNTQQApi<GeneralCallResult>({

View File

@@ -7,7 +7,9 @@ import { log } from '../../common/utils/log'
import { sleep } from '../../common/utils/helper' import { sleep } from '../../common/utils/helper'
import { isQQ998 } from '../../common/utils' import { isQQ998 } from '../../common/utils'
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunnc export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunc
export let sentMessages: Record<string, RawMessage> = {} // msgId: RawMessage
export interface Peer { export interface Peer {
chatType: ChatType chatType: ChatType
@@ -15,7 +17,78 @@ export interface Peer {
guildId?: '' 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
sentMessages[rawMessage.msgId] = rawMessage
}
let checkSendCompleteUsingTime = 0
const checkSendComplete = async (): Promise<RawMessage> => {
if (sentMessage) {
if (waitComplete) {
if (sentMessage.sendStatus == 2) {
delete sentMessages[sentMessage.msgId]
return sentMessage
}
}
else {
delete sentMessages[sentMessage.msgId]
return sentMessage
}
// log(`给${peerUid}发送消息成功`)
}
checkSendCompleteUsingTime += 500
if (checkSendCompleteUsingTime > timeout) {
throw '发送超时'
}
await sleep(500)
return await checkSendComplete()
}
return checkSendComplete()
}
export class NTQQMsgApi { export class NTQQMsgApi {
static enterOrExitAIO(peer: Peer, enter: boolean) {
return callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.ENTER_OR_EXIT_AIO,
args: [
{
"info_list": [
{
peer,
"option": enter ? 1 : 2
}
]
},
{
"send": true
},
],
})
}
static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
@@ -35,6 +108,7 @@ export class NTQQMsgApi {
], ],
}) })
} }
static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) { static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) {
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({ return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
methodName: NTQQApiMethod.GET_MULTI_MSG, methodName: NTQQApiMethod.GET_MULTI_MSG,
@@ -49,6 +123,20 @@ export class NTQQMsgApi {
}) })
} }
static async getMsgBoxInfo(peer: Peer) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.GET_MSG_BOX_INFO,
args: [
{
contacts: [
peer
],
},
null,
],
})
}
static async activateChat(peer: Peer) { static async activateChat(peer: Peer) {
// await this.fetchRecentContact(); // await this.fetchRecentContact();
// await sleep(500); // await sleep(500);
@@ -57,6 +145,7 @@ export class NTQQMsgApi {
args: [{ peer, cnt: 20 }, null], args: [{ peer, cnt: 20 }, null],
}) })
} }
static async activateChatAndGetHistory(peer: Peer) { static async activateChatAndGetHistory(peer: Peer) {
// await this.fetchRecentContact(); // await this.fetchRecentContact();
// await sleep(500); // await sleep(500);
@@ -66,6 +155,7 @@ export class NTQQMsgApi {
args: [{ peer, cnt: 20 }, null], args: [{ peer, cnt: 20 }, null],
}) })
} }
static async getMsgHistory(peer: Peer, msgId: string, count: number) { static async getMsgHistory(peer: Peer, msgId: string, count: number) {
// 消息时间从旧到新 // 消息时间从旧到新
return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({ return await callNTQQApi<GeneralCallResult & { msgList: RawMessage[] }>({
@@ -81,6 +171,7 @@ export class NTQQMsgApi {
], ],
}) })
} }
static async fetchRecentContact() { static async fetchRecentContact() {
await callNTQQApi({ await callNTQQApi({
methodName: NTQQApiMethod.RECENT_CONTACT, methodName: NTQQApiMethod.RECENT_CONTACT,
@@ -116,52 +207,7 @@ export class NTQQMsgApi {
} }
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) {
const peerUid = peer.peerUid const waiter = sendWaiter(peer, waitComplete, timeout)
// 等待上一个相同的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()
}
callNTQQApi({ callNTQQApi({
methodName: NTQQApiMethod.SEND_MSG, methodName: NTQQApiMethod.SEND_MSG,
args: [ args: [
@@ -174,11 +220,12 @@ export class NTQQMsgApi {
null, null,
], ],
}).then() }).then()
return await checkSendComplete() return await waiter
} }
static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { 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, methodName: NTQQApiMethod.FORWARD_MSG,
args: [ args: [
{ {
@@ -190,7 +237,8 @@ export class NTQQMsgApi {
}, },
null, null,
], ],
}) }).then().catch(log)
return await waiter
} }
static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {

View File

@@ -6,6 +6,7 @@ import { NTQQWindowApi, NTQQWindows } from './window'
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils' import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
import { wrapperApi } from '@/ntqqapi/native/wrapper' import { wrapperApi } from '@/ntqqapi/native/wrapper'
import * as https from 'https' import * as https from 'https'
import { RequestUtil } from '@/common/utils/request'
let userInfoCache: Record<string, User> = {} // uid: User let userInfoCache: Record<string, User> = {} // uid: User
@@ -47,9 +48,12 @@ export class NTQQUserApi {
return result.profiles.get(uid) return result.profiles.get(uid)
} }
static async getUserDetailInfo(uid: string, getLevel = false) { static async getUserDetailInfo(uid: string, getLevel = false, withBizInfo = true) {
// this.getUserInfo(uid); // this.getUserInfo(uid);
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
if (!withBizInfo) {
methodName = NTQQApiMethod.USER_DETAIL_INFO
}
const fetchInfo = async () => { const fetchInfo = async () => {
const result = await callNTQQApi<{ info: User }>({ const result = await callNTQQApi<{ info: User }>({
methodName, methodName,
@@ -95,7 +99,17 @@ export class NTQQUserApi {
], ],
}) })
} }
static async getQzoneCookies() {
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin + '&clientkey=' + (await this.getClientKey()).clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + selfInfo.uin + '%2Finfocenter&keyindex=19%27'
let cookies: { [key: string]: string; } = {};
try {
cookies = await RequestUtil.HttpsGetCookies(requestUrl);
} catch (e: any) {
log('获取QZone Cookies失败', e)
cookies = {}
}
return cookies;
}
static async getSkey(): Promise<string> { static async getSkey(): Promise<string> {
const clientKeyData = await this.getClientKey() const clientKeyData = await this.getClientKey()
if (clientKeyData.result !== 0) { if (clientKeyData.result !== 0) {
@@ -103,33 +117,19 @@ export class NTQQUserApi {
} }
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
+ '&clientkey=' + clientKeyData.clientKey + '&clientkey=' + clientKeyData.clientKey
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex + '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex;
return (await RequestUtil.HttpsGetCookies(url))?.skey;
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();
});
} }
@cacheFunc(60 * 30 * 1000) @cacheFunc(60 * 30 * 1000)
static async getCookies(domain: string) { static async getCookies(domain: string) {
if (domain.endsWith("qzone.qq.com")) {
let data = (await NTQQUserApi.getQzoneCookies());
const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + selfInfo.uin + '; uin=o' + selfInfo.uin;
return { bkn: NTQQUserApi.genBkn(data.p_skey), cookies: CookieValue };
}
const skey = await this.getSkey(); const skey = await this.getSkey();
const pskey= (await this.getPSkey([domain])).get(domain); const pskey = (await this.getPSkey([domain])).get(domain);
if (!pskey || !skey) { if (!pskey || !skey) {
throw new Error('获取Cookies失败') throw new Error('获取Cookies失败')
} }

View File

@@ -76,6 +76,10 @@ export class SendMsgElementConstructor {
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
const maxMB = 30;
if (fileSize > 1024 * 1024 * 30){
throw `图片过大,最大支持${maxMB}MB当前文件大小${fileSize}B`
}
const imageSize = await NTQQFileApi.getImageSize(picPath) const imageSize = await NTQQFileApi.getImageSize(picPath)
const picElement = { const picElement = {
md5HexStr: md5, md5HexStr: md5,
@@ -131,6 +135,10 @@ export class SendMsgElementConstructor {
if (fileSize === 0) { if (fileSize === 0) {
throw '文件异常大小为0' throw '文件异常大小为0'
} }
const maxMB = 100;
if (fileSize > 1024 * 1024 * maxMB) {
throw `视频过大,最大支持${maxMB}MB当前文件大小${fileSize}B`
}
const pathLib = require('path') const pathLib = require('path')
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`) let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
thumbDir = pathLib.dirname(thumbDir) thumbDir = pathLib.dirname(thumbDir)

View File

@@ -1,27 +1,28 @@
import { BrowserWindow } from 'electron' import { BrowserWindow } from 'electron'
import { NTQQApiClass, NTQQApiMethod } from './ntcall' import { NTQQApiClass, NTQQApiMethod } from './ntcall'
import { NTQQMsgApi, sendMessagePool } from './api/msg' 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 { import {
deleteGroup, deleteGroup,
friends, friends,
getFriend, getFriend,
getGroupMember, getGroupMember,
groups, groups, rawFriends,
selfInfo, selfInfo,
tempGroupCodeMap, tempGroupCodeMap,
uidMaps, uidMaps,
} from '../common/data' } from '@/common/data'
import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent' import { OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { postOB11Event } from '../onebot11/server/postOB11Event' import { postOb11Event } from '../onebot11/server/post-ob11-event'
import { getConfigUtil, HOOK_LOG } from '../common/config' import { getConfigUtil, HOOK_LOG } from '@/common/config'
import fs from 'fs' import fs from 'fs'
import { dbUtil } from '../common/db' import { dbUtil } from '@/common/db'
import { NTQQGroupApi } from './api/group' import { NTQQGroupApi } from './api/group'
import { log } from '../common/utils/log' import { log } from '@/common/utils'
import { isNumeric, sleep } from '../common/utils/helper' import { isNumeric, sleep } from '@/common/utils'
import { OB11Constructor } from '../onebot11/constructor' import { OB11Constructor } from '../onebot11/constructor'
import { OB11GroupCardEvent } from '../onebot11/event/notice/OB11GroupCardEvent'
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
@@ -299,7 +300,7 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
} }
for (const member of oldMembers) { for (const member of oldMembers) {
if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) { if (!newMembersSet.has(member.uin) && member.uin != selfInfo.uin) {
postOB11Event( postOb11Event(
new OB11GroupDecreaseEvent( new OB11GroupDecreaseEvent(
parseInt(group.groupCode), parseInt(group.groupCode),
parseInt(member.uin), parseInt(member.uin),
@@ -324,205 +325,222 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
} }
} }
// 群列表变动 export async function startHook() {
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
// updateType 3是群列表变动2是群成员变动
// log("群列表变动", payload.updateType, payload.groupList)
if (payload.updateType != 2) {
updateGroups(payload.groupList).then()
} else {
if (process.platform == 'win32') {
processGroupEvent(payload).then()
}
}
})
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
// updateType 3是群列表变动2是群成员变动
// log("群列表变动", payload.updateType, payload.groupList)
if (payload.updateType != 2) {
updateGroups(payload.groupList).then()
} else {
if (process.platform != 'win32') {
processGroupEvent(payload).then()
}
}
})
registerReceiveHook<{ // 群列表变动
groupCode: string registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
dataSource: number // updateType 3是群列表变动2是群成员变动
members: Set<GroupMember> // log("群列表变动", payload.updateType, payload.groupList)
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => { if (payload.updateType != 2) {
const groupCode = payload.groupCode updateGroups(payload.groupList).then()
const members = Array.from(payload.members.values())
// log("群成员信息变动", groupCode, members)
for (const member of members) {
const existMember = await getGroupMember(groupCode, member.uin)
if (existMember) {
Object.assign(existMember, member)
} }
} else {
// const existGroup = groups.find(g => g.groupCode == groupCode); if (process.platform == 'win32') {
// if (existGroup) { processGroupEvent(payload).then()
// log("对比群成员", existGroup.members, members) }
// for (const member of members) { }
// const existMember = existGroup.members.find(m => m.uin == member.uin); })
// if (existMember) { registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
// log("对比群名片", existMember.cardName, member.cardName) // updateType 3是群列表变动2是群成员变动
// if (existMember.cardName != member.cardName) { // log("群列表变动", payload.updateType, payload.groupList)
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName)); if (payload.updateType != 2) {
// } updateGroups(payload.groupList).then()
// Object.assign(existMember, member); }
// } else {
// } if (process.platform != 'win32') {
// } processGroupEvent(payload).then()
}) }
}
})
registerReceiveHook<{
groupCode: string
dataSource: number
members: Set<GroupMember>
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
const groupCode = payload.groupCode
const members = Array.from(payload.members.values())
// log("群成员信息变动", groupCode, members)
for (const member of members) {
const existMember = await getGroupMember(groupCode, member.uin)
if (existMember) {
if (member.cardName != existMember.cardName) {
log('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
postOb11Event(
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
)
}
Object.assign(existMember, member)
}
}
// const existGroup = groups.find(g => g.groupCode == groupCode);
// if (existGroup) {
// log("对比群成员", existGroup.members, members)
// for (const member of members) {
// const existMember = existGroup.members.find(m => m.uin == member.uin);
// if (existMember) {
// log("对比群名片", existMember.cardName, member.cardName)
// if (existMember.cardName != member.cardName) {
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName));
// }
// Object.assign(existMember, member);
// }
// }
// }
})
// 好友列表变动 // 好友列表变动
registerReceiveHook<{ registerReceiveHook<{
data: { categoryId: number; categroyName: string; categroyMbCount: number; buddyList: User[] }[] data: CategoryFriend[]
}>(ReceiveCmdS.FRIENDS, (payload) => { }>(ReceiveCmdS.FRIENDS, (payload) => {
for (const fData of payload.data) { rawFriends.length = 0;
const _friends = fData.buddyList rawFriends.push(...payload.data);
for (let friend of _friends) { for (const fData of payload.data) {
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then() const _friends = fData.buddyList
let existFriend = friends.find((f) => f.uin == friend.uin) for (let friend of _friends) {
if (!existFriend) { NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
friends.push(friend) let existFriend = friends.find((f) => f.uin == friend.uin)
} else { if (!existFriend) {
Object.assign(existFriend, friend) friends.push(friend)
}
else {
Object.assign(existFriend, friend)
}
} }
} }
} })
})
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
// 保存一下uid // 保存一下uid
for (const message of payload.msgList) { for (const message of payload.msgList) {
const uid = message.senderUid const uid = message.senderUid
const uin = message.senderUin const uin = message.senderUin
if (uid && uin) { if (uid && uin) {
if (message.chatType === ChatType.temp) { if (message.chatType === ChatType.temp) {
dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => { dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => {
if (!receivedTempUinMap[uin]) { if (!receivedTempUinMap[uin]) {
receivedTempUinMap[uin] = uid receivedTempUinMap[uin] = uid
dbUtil.setReceivedTempUinMap(receivedTempUinMap) dbUtil.setReceivedTempUinMap(receivedTempUinMap)
}
})
}
uidMaps[uid] = uin
}
}
// 自动清理新消息文件
const { autoDeleteFile } = getConfigUtil().getConfig()
if (!autoDeleteFile) {
return
}
for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message.msgId)
// dbUtil.addMsg(message).then()
// 清理文件
for (const msgElement of message.elements) {
setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.filePath
const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()]
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath))
}
const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement
if (aioOpGrayTipElement) {
tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat
}
// log("需要清理的文件", pathList);
for (const path of pathList) {
if (path) {
fs.unlink(picPath, () => {
log('删除文件成功', path)
})
}
}
}, getConfigUtil().getConfig().autoDeleteFileSecond * 1000)
}
}
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
const message = msgRecord
const peerUid = message.peerUid
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
// log("收到自己发送成功的消息", message.msgId, message.msgSeq);
dbUtil.addMsg(message).then()
const sendCallback = sendMessagePool[peerUid]
if (sendCallback) {
try {
sendCallback(message)
} catch (e) {
log('receive self msg error', e.stack)
}
}
})
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
selfInfo.online = info.info.status !== 20
})
let activatedPeerUids: string[] = []
registerReceiveHook<{
changedRecentContactLists: {
listType: number
sortedContactList: string[]
changedList: {
id: string // peerUid
chatType: ChatType
}[]
}[]
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
for (const recentContact of payload.changedRecentContactLists) {
for (const changedContact of recentContact.changedList) {
if (activatedPeerUids.includes(changedContact.id)) continue
activatedPeerUids.push(changedContact.id)
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
if (changedContact.chatType === ChatType.temp) {
log('收到临时会话消息', peer)
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
let lastTempMsg = msgList.pop()
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) {
OB11Constructor.message(lastTempMsg).then((r) => postOB11Event(r))
} }
}) })
}) }
} else { uidMaps[uid] = uin
NTQQMsgApi.activateChat(peer).then()
} }
} }
}
})
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => { // 自动清理新消息文件
const peerUid = payload[0] as string const { autoDeleteFile } = getConfigUtil().getConfig()
log('激活的聊天窗口被删除,准备重新激活', peerUid) if (!autoDeleteFile) {
let chatType = ChatType.friend return
if (isNumeric(peerUid)) { }
chatType = ChatType.group for (const message of payload.msgList) {
} else { // log("收到新消息push到历史记录", message.msgId)
// 检查是否好友 // dbUtil.addMsg(message).then()
if (!(await getFriend(peerUid))) { // 清理文件
chatType = ChatType.temp
for (const msgElement of message.elements) {
setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.filePath
const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()]
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath))
}
const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement
if (aioOpGrayTipElement) {
tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat
}
// log("需要清理的文件", pathList);
for (const path of pathList) {
if (path) {
fs.unlink(picPath, () => {
log('删除文件成功', path)
})
}
}
}, getConfigUtil().getConfig().autoDeleteFileSecond * 1000)
}
} }
}
const peer = { peerUid, chatType }
await sleep(1000)
NTQQMsgApi.activateChat(peer).then((r) => {
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
}) })
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
const message = msgRecord
const peerUid = message.peerUid
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
// log("收到自己发送成功的消息", message.msgId, message.msgSeq);
dbUtil.addMsg(message).then()
const sendCallback = sendMessagePool[peerUid]
if (sendCallback) {
try {
sendCallback(message)
} catch (e) {
log('receive self msg error', e.stack)
}
}
})
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
selfInfo.online = info.info.status !== 20
})
let activatedPeerUids: string[] = []
registerReceiveHook<{
changedRecentContactLists: {
listType: number
sortedContactList: string[]
changedList: {
id: string // peerUid
chatType: ChatType
}[]
}[]
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
for (const recentContact of payload.changedRecentContactLists) {
for (const changedContact of recentContact.changedList) {
if (activatedPeerUids.includes(changedContact.id)) continue
activatedPeerUids.push(changedContact.id)
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
if (changedContact.chatType === ChatType.temp) {
log('收到临时会话消息', peer)
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
let lastTempMsg = msgList.pop()
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) {
OB11Constructor.message(lastTempMsg).then((r) => postOb11Event(r))
}
})
})
}
else {
NTQQMsgApi.activateChat(peer).then()
}
}
}
})
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
const peerUid = payload[0] as string
log('激活的聊天窗口被删除,准备重新激活', peerUid)
let chatType = ChatType.friend
if (isNumeric(peerUid)) {
chatType = ChatType.group
}
else {
// 检查是否好友
if (!(await getFriend(peerUid))) {
chatType = ChatType.temp
}
}
const peer = { peerUid, chatType }
await sleep(1000)
NTQQMsgApi.activateChat(peer).then((r) => {
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
})
})
}

View File

@@ -27,13 +27,17 @@ export enum NTQQApiMethod {
HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf', HISTORY_MSG = 'nodeIKernelMsgService/getMsgsIncludeSelf',
GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg', GET_MULTI_MSG = 'nodeIKernelMsgService/getMultiMsg',
DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid', DELETE_ACTIVE_CHAT = 'nodeIKernelMsgService/deleteActiveChatByUid',
ENTER_OR_EXIT_AIO = 'nodeIKernelMsgService/enterOrExitAio',
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike', LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike',
SELF_INFO = 'fetchAuthData', SELF_INFO = 'fetchAuthData',
FRIENDS = 'nodeIKernelBuddyService/getBuddyList', FRIENDS = 'nodeIKernelBuddyService/getBuddyList',
GROUPS = 'nodeIKernelGroupService/getGroupList', GROUPS = 'nodeIKernelGroupService/getGroupList',
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene', GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene',
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList', GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList',
GROUP_MEMBERS_INFO = 'nodeIKernelGroupService/getMemberInfo',
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo', USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo',
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo', USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo',
USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo', USER_DETAIL_INFO_WITH_BIZ_INFO = 'nodeIKernelProfileService/getUserDetailInfoWithBizInfo',
@@ -65,6 +69,10 @@ export enum NTQQApiMethod {
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin', PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin',
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName', SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName',
SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle', SET_GROUP_TITLE = 'nodeIKernelGroupService/modifyMemberSpecialTitle',
ACTIVATE_MEMBER_LIST_CHANGE = 'nodeIKernelGroupListener/onMemberListChange',
ACTIVATE_MEMBER_INFO_CHANGE = 'nodeIKernelGroupListener/onMemberInfoChange',
GET_MSG_BOX_INFO = 'nodeIKernelMsgService/getABatchOfContactMsgBoxInfo',
GET_GROUP_ALL_INFO = 'nodeIKernelGroupService/getGroupAllInfo',
CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
@@ -99,7 +107,7 @@ interface NTQQApiParams {
channel?: NTQQApiChannel channel?: NTQQApiChannel
classNameIsRegister?: boolean classNameIsRegister?: boolean
args?: unknown[] args?: unknown[]
cbCmd?: ReceiveCmd | null cbCmd?: ReceiveCmd | ReceiveCmd[] | null
cmdCB?: (payload: any) => boolean cmdCB?: (payload: any) => boolean
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
timeoutSecond?: number timeoutSecond?: number
@@ -139,7 +147,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
success = true success = true
resolve(r) resolve(r)
} }
} else { }
else {
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => { const secondCallback = () => {
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => { const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
@@ -150,7 +159,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
success = true success = true
resolve(payload) resolve(payload)
} }
} else { }
else {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true success = true
resolve(payload) resolve(payload)
@@ -162,7 +172,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
log(`${methodName} callback`, result) log(`${methodName} callback`, result)
if (result?.result == 0 || result === undefined) { if (result?.result == 0 || result === undefined) {
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback()
} else { }
else {
success = true success = true
reject(`ntqq api call failed, ${result.errMsg}`) reject(`ntqq api call failed, ${result.errMsg}`)
} }
@@ -180,7 +191,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
channel, channel,
{ {
sender: { sender: {
send: (..._args: unknown[]) => {}, send: (..._args: unknown[]) => {
},
}, },
}, },
{ type: 'request', callbackId: uuid, eventName }, { type: 'request', callbackId: uuid, eventName },

View File

@@ -1,6 +1,7 @@
export enum GroupNotifyTypes { export enum GroupNotifyTypes {
INVITE_ME = 1, INVITE_ME = 1,
INVITED_JOIN = 4, // 有人接受了邀请入群 INVITED_JOIN = 4, // 有人接受了邀请入群
JOIN_REQUEST_BY_INVITED = 5, // 有人邀请了别人入群
JOIN_REQUEST = 7, JOIN_REQUEST = 7,
ADMIN_SET = 8, ADMIN_SET = 8,
KICK_MEMBER = 9, KICK_MEMBER = 9,

View File

@@ -10,6 +10,7 @@ export interface QQLevel {
moonNum: number moonNum: number
starNum: number starNum: number
} }
export interface User { export interface User {
uid: string // 加密的字符串 uid: string // 加密的字符串
uin: string // QQ号 uin: string // QQ号
@@ -72,4 +73,12 @@ export interface SelfInfo extends User {
online?: boolean online?: boolean
} }
export interface Friend extends User {} export interface Friend extends User {
}
export interface CategoryFriend {
categoryId: number;
categroyName: string;
categroyMbCount: number;
buddyList: User[]
}

View File

@@ -21,13 +21,12 @@ export interface GetFileResponse {
} }
export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> { export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
private getElement(msg: RawMessage): { id: string; element: VideoElement | FileElement } { private getElement(msg: RawMessage, elementId: string): VideoElement | FileElement {
let element = msg.elements.find((e) => e.fileElement) let element = msg.elements.find((e) => e.elementId === elementId)
if (!element) { if (!element) {
element = msg.elements.find((e) => e.videoElement) throw new Error('element not found')
return { id: element.elementId, element: element.videoElement }
} }
return { id: element.elementId, element: element.fileElement } return element.fileElement
} }
private async download(cache: FileCache, file: string) { private async download(cache: FileCache, file: string) {
log('需要调用 NTQQ 下载文件api') log('需要调用 NTQQ 下载文件api')
@@ -35,14 +34,14 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
let msg = await dbUtil.getMsgByLongId(cache.msgId) let msg = await dbUtil.getMsgByLongId(cache.msgId)
if (msg) { if (msg) {
log('找到了文件 msg', msg) log('找到了文件 msg', msg)
let element = this.getElement(msg) let element = this.getElement(msg, cache.elementId)
log('找到了文件 element', element) log('找到了文件 element', element)
// 构建下载函数 // 构建下载函数
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, element.id, '', '', true) await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
await sleep(1000) await sleep(1000) // 这里延时是为何?
msg = await dbUtil.getMsgByLongId(cache.msgId) msg = await dbUtil.getMsgByLongId(cache.msgId)
log('下载完成后的msg', msg) log('下载完成后的msg', msg)
cache.filePath = this.getElement(msg).element.filePath cache.filePath = this.getElement(msg, cache.elementId).filePath
dbUtil.addFileCache(file, cache).then() dbUtil.addFileCache(file, cache).then()
} }
} }

View File

@@ -6,7 +6,8 @@ import { OB11Constructor } from '../../constructor'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
message_id: string // long msg id message_id: string // long msg idgocq
id?: string // long msg id, onebot11
} }
interface Response { interface Response {
@@ -16,7 +17,11 @@ interface Response {
export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> { export class GoCQHTTGetForwardMsgAction extends BaseAction<Payload, any> {
actionName = ActionName.GoCQHTTP_GetForwardMsg actionName = ActionName.GoCQHTTP_GetForwardMsg
protected async _handle(payload: Payload): Promise<any> { 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) { if (!rootMsg) {
throw Error('msg not found') throw Error('msg not found')
} }

View 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
}
}

View File

@@ -1,6 +1,6 @@
import GetMsg from './msg/GetMsg' import GetMsg from './msg/GetMsg'
import GetLoginInfo from './system/GetLoginInfo' import GetLoginInfo from './system/GetLoginInfo'
import GetFriendList from './user/GetFriendList' import { GetFriendList, GetFriendWithCategory} from './user/GetFriendList'
import GetGroupList from './group/GetGroupList' import GetGroupList from './group/GetGroupList'
import GetGroupInfo from './group/GetGroupInfo' import GetGroupInfo from './group/GetGroupInfo'
import GetGroupMemberList from './group/GetGroupMemberList' import GetGroupMemberList from './group/GetGroupMemberList'
@@ -46,9 +46,10 @@ import GetFile from './file/GetFile'
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg' import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
import { GetCookies } from './user/GetCookie' import { GetCookies } from './user/GetCookie'
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike' import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
import { ForwardFriendSingleMsg, ForwardSingleGroupMsg } from './msg/ForwardSingleMsg' import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg'
import { GetGroupEssence } from './group/GetGroupEssence' import { GetGroupEssence } from './group/GetGroupEssence'
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo' import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation'
export const actionHandlers = [ export const actionHandlers = [
new GetFile(), new GetFile(),
@@ -57,6 +58,7 @@ export const actionHandlers = [
new SetConfigAction(), new SetConfigAction(),
new GetGroupAddRequest(), new GetGroupAddRequest(),
new SetQQAvatar(), new SetQQAvatar(),
new GetFriendWithCategory(),
// onebot11 // onebot11
new SendLike(), new SendLike(),
new GetMsg(), new GetMsg(),
@@ -89,7 +91,7 @@ export const actionHandlers = [
new GetCookies(), new GetCookies(),
new SetMsgEmojiLike(), new SetMsgEmojiLike(),
new ForwardFriendSingleMsg(), new ForwardFriendSingleMsg(),
new ForwardSingleGroupMsg(), new ForwardGroupSingleMsg(),
//以下为go-cqhttp api //以下为go-cqhttp api
new GetGroupEssence(), new GetGroupEssence(),
new GetGroupHonorInfo(), new GetGroupHonorInfo(),
@@ -104,6 +106,7 @@ export const actionHandlers = [
new GoCQHTTPUploadPrivateFile(), new GoCQHTTPUploadPrivateFile(),
new GoCQHTTPGetGroupMsgHistory(), new GoCQHTTPGetGroupMsgHistory(),
new GoCQHTTGetForwardMsgAction(), new GoCQHTTGetForwardMsgAction(),
new GoCQHTTHandleQuickOperation()
] ]
function initActionMap() { function initActionMap() {

View File

@@ -11,7 +11,11 @@ interface Payload {
user_id?: number 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> { protected async getTargetPeer(payload: Payload): Promise<Peer> {
if (payload.user_id) { if (payload.user_id) {
return { chatType: ChatType.friend, peerUid: getUidByUin(payload.user_id.toString()) } 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() } 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 msg = await dbUtil.getMsgByShortId(payload.message_id)
const peer = await this.getTargetPeer(payload) const peer = await this.getTargetPeer(payload)
await NTQQMsgApi.forwardMsg( const sentMsg = await NTQQMsgApi.forwardMsg(
{ {
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
@@ -30,7 +34,8 @@ class ForwardSingleMsg extends BaseAction<Payload, null> {
peer, peer,
[msg.msgId], [msg.msgId],
) )
return null const ob11MsgId = await dbUtil.addMsg(sentMsg)
return {message_id: ob11MsgId}
} }
} }
@@ -38,6 +43,6 @@ export class ForwardFriendSingleMsg extends ForwardSingleMsg {
actionName = ActionName.ForwardFriendSingleMsg actionName = ActionName.ForwardFriendSingleMsg
} }
export class ForwardSingleGroupMsg extends ForwardSingleMsg { export class ForwardGroupSingleMsg extends ForwardSingleMsg {
actionName = ActionName.ForwardGroupSingleMsg actionName = ActionName.ForwardGroupSingleMsg
} }

View File

@@ -50,22 +50,28 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
let data = msg['data'] let data = msg['data']
if (type === 'text' && !data['text']) { if (type === 'text' && !data['text']) {
return 400 return 400
} else if (['image', 'voice', 'record'].includes(type)) { }
else if (['image', 'voice', 'record'].includes(type)) {
if (!data['file']) { if (!data['file']) {
return 400 return 400
} else { }
else {
if (checkUri(data['file'])) { if (checkUri(data['file'])) {
return 200 return 200
} else { }
else {
return 400 return 400
} }
} }
} else if (type === 'at' && !data['qq']) { }
return 400 else if (type === 'at' && !data['qq']) {
} else if (type === 'reply' && !data['id']) {
return 400 return 400
} }
} else { else if (type === 'reply' && !data['id']) {
return 400
}
}
else {
return 400 return 400
} }
} }
@@ -87,10 +93,12 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa
}, },
}, },
] ]
} else { }
else {
message = decodeCQCode(message.toString()) message = decodeCQCode(message.toString())
} }
} else if (!Array.isArray(message)) { }
else if (!Array.isArray(message)) {
message = [message] message = [message]
} }
return message return message
@@ -108,179 +116,177 @@ export async function createSendElements(
continue continue
} }
switch (sendMsg.type) { switch (sendMsg.type) {
case OB11MessageDataType.text: case OB11MessageDataType.text: {
{ const text = sendMsg.data?.text
const text = sendMsg.data?.text if (text) {
if (text) { sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
}
} }
}
break break
case OB11MessageDataType.at: case OB11MessageDataType.at: {
{ if (!target) {
if (!target) { continue
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 else {
if (atQQ) { // const atMember = group?.members.find(m => m.uin == atQQ)
atQQ = atQQ.toString() const atMember = await getGroupMember((target as Group)?.groupCode, atQQ)
if (atQQ === 'all') { if (atMember) {
// todo查询剩余的at全体次数 sendElements.push(
const groupCode = (target as Group)?.groupCode SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick),
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),
)
}
} }
} }
} }
}
break break
case OB11MessageDataType.reply: case OB11MessageDataType.reply: {
{ let replyMsgId = sendMsg.data.id
let replyMsgId = sendMsg.data.id if (replyMsgId) {
if (replyMsgId) { const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId))
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId)) if (replyMsg) {
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( sendElements.push(
SendMsgElementConstructor.reply( await SendMsgElementConstructor.pic(
replyMsg.msgSeq, path,
replyMsg.msgId, sendMsg.data.summary || '',
replyMsg.senderUin, <PicSubType>parseInt(sendMsg.data?.subType?.toString()) || 0,
replyMsg.senderUin,
), ),
) )
} }
} }
} }
}
break break
case OB11MessageDataType.face: case OB11MessageDataType.json: {
{ sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
const faceId = sendMsg.data?.id }
if (faceId) { break
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId))) case OB11MessageDataType.poke: {
let qq = sendMsg.data?.qq || sendMsg.data?.id
if (qq) {
if ('groupCode' in target) {
crychic.sendGroupPoke(target.groupCode, qq.toString())
} }
} else {
break if (!qq) {
case OB11MessageDataType.mface: qq = parseInt(target.uin)
{
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,
),
)
}
} }
crychic.sendFriendPoke(qq.toString())
} }
sendElements.push(SendMsgElementConstructor.poke('', ''))
} }
}
break break
case OB11MessageDataType.json: case OB11MessageDataType.dice: {
{ const resultId = sendMsg.data?.result
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data)) sendElements.push(SendMsgElementConstructor.dice(resultId))
} }
break break
case OB11MessageDataType.poke: case OB11MessageDataType.RPS: {
{ const resultId = sendMsg.data?.result
let qq = sendMsg.data?.qq || sendMsg.data?.id sendElements.push(SendMsgElementConstructor.rps(resultId))
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))
}
break break
} }
} }
@@ -300,10 +306,34 @@ export async function sendMsg(
if (!sendElements.length) { if (!sendElements.length) {
throw '消息体无法解析,请检查是否发送了不支持的消息类型' 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 / 100) * 1000) + 5000 // 100kb/s
log('设置消息超时时间', timeout)
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout)
log('消息发送结果', returnMsg) log('消息发送结果', returnMsg)
returnMsg.msgShortId = await dbUtil.addMsg(returnMsg) returnMsg.msgShortId = await dbUtil.addMsg(returnMsg)
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {})) deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
}))
return returnMsg return returnMsg
} }
@@ -402,7 +432,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} catch (e) { } catch (e) {
throw '发送转发消息失败 ' + e.toString() throw '发送转发消息失败 ' + e.toString()
} }
} else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) { }
else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
const music = messages[0] as OB11MessageMusic const music = messages[0] as OB11MessageMusic
if (music) { if (music) {
const { musicSignUrl } = getConfigUtil().getConfig() const { musicSignUrl } = getConfigUtil().getConfig()
@@ -439,7 +470,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
let jsonContent: string let jsonContent: string
try { try {
jsonContent = await new MusicSign(musicSignUrl).sign(postData) jsonContent = await new MusicSign(musicSignUrl).sign(postData)
if (!jsonContent){ if (!jsonContent) {
throw '音乐消息生成失败,提交内容有误或者签名服务器签名失败' throw '音乐消息生成失败,提交内容有误或者签名服务器签名失败'
} }
} catch (e) { } catch (e) {
@@ -459,7 +490,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
} }
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles) const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles)
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {})) deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
}))
return { message_id: returnMsg.msgShortId } return { message_id: returnMsg.msgShortId }
} }
@@ -562,7 +594,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
await sleep(500) await sleep(500)
log('转发节点生成成功', nodeMsg.msgId) log('转发节点生成成功', nodeMsg.msgId)
} }
deleteAfterSentFiles.map((f) => fs.unlink(f, () => {})) deleteAfterSentFiles.map((f) => fs.unlink(f, () => {
}))
} catch (e) { } catch (e) {
log('生成转发消息节点失败', e) log('生成转发消息节点失败', e)
} }

View File

@@ -0,0 +1,149 @@
// 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'
import { getConfigUtil } from '@/common/config'
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
const ob11Config = getConfigUtil().getConfig().ob11
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 (ob11Config.enableQOAutoQuote) {
replyMessage.push({
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)
}
}

View File

@@ -21,6 +21,7 @@ export enum ActionName {
SetConfig = 'set_config', SetConfig = 'set_config',
Debug = 'llonebot_debug', Debug = 'llonebot_debug',
GetFile = 'get_file', GetFile = 'get_file',
GetFriendsWithCategory = 'get_friends_with_category',
// onebot 11 // onebot 11
SendLike = 'send_like', SendLike = 'send_like',
GetLoginInfo = 'get_login_info', GetLoginInfo = 'get_login_info',
@@ -67,5 +68,6 @@ export enum ActionName {
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history', GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
GoCQHTTP_GetForwardMsg = 'get_forward_msg', GoCQHTTP_GetForwardMsg = 'get_forward_msg',
GoCQHTTP_GetEssenceMsg = "get_essence_msg_list", GoCQHTTP_GetEssenceMsg = "get_essence_msg_list",
GoCQHTTP_HandleQuickOperation = ".handle_quick_operation",
GetGroupHonorInfo = "get_group_honor_info", GetGroupHonorInfo = "get_group_honor_info",
} }

View File

@@ -1,17 +1,16 @@
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { NTQQUserApi } from '../../../ntqqapi/api' import { NTQQUserApi } from '@/ntqqapi/api'
import { groups } from '../../../common/data' import { ActionName } from '../types'
import { ActionName } from '../types'
interface Payload {
interface Payload { domain: string
domain: string }
}
export class GetCookies extends BaseAction<Payload, { cookies: string; bkn: string }> {
export class GetCookies extends BaseAction<Payload, { cookies: string; bkn: string }> { actionName = ActionName.GetCookies
actionName = ActionName.GetCookies
protected async _handle(payload: Payload) {
protected async _handle(payload: Payload) { const domain = payload.domain || 'qun.qq.com'
const domain = payload.domain || 'qun.qq.com' return NTQQUserApi.getCookies(domain);
return NTQQUserApi.getCookies(domain); }
} }
}

View File

@@ -1,16 +1,16 @@
import { OB11User } from '../../types' import { OB11User } from '../../types'
import { OB11Constructor } from '../../constructor' import { OB11Constructor } from '../../constructor'
import { friends } from '../../../common/data' import { friends, rawFriends } from '@/common/data'
import BaseAction from '../BaseAction' import BaseAction from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { NTQQFriendApi } from '../../../ntqqapi/api' import { NTQQFriendApi } from '@/ntqqapi/api'
import { log } from '../../../common/utils' import { CategoryFriend } from '@/ntqqapi/types'
interface Payload { interface Payload {
no_cache: boolean | string no_cache: boolean | string
} }
class GetFriendList extends BaseAction<Payload, OB11User[]> { export class GetFriendList extends BaseAction<Payload, OB11User[]> {
actionName = ActionName.GetFriendList actionName = ActionName.GetFriendList
protected async _handle(payload: Payload) { 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;
}
}

View File

@@ -55,6 +55,7 @@ export class OB11Constructor {
let config = getConfigUtil().getConfig() let config = getConfigUtil().getConfig()
const { const {
enableLocalFile2Url, enableLocalFile2Url,
debug,
ob11: { messagePostFormat }, ob11: { messagePostFormat },
} = config } = config
const message_type = msg.chatType == ChatType.group ? 'group' : 'private' const message_type = msg.chatType == ChatType.group ? 'group' : 'private'
@@ -78,8 +79,11 @@ export class OB11Constructor {
message_format: messagePostFormat === 'string' ? 'string' : 'array', message_format: messagePostFormat === 'string' ? 'string' : 'array',
post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE, post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
} }
if (debug) {
resMsg.raw = msg
}
if (msg.chatType == ChatType.group) { if (msg.chatType == ChatType.group) {
resMsg.sub_type = 'normal' // 这里go-cqhttp是group而onebot11标准是normal, 蛋疼 resMsg.sub_type = 'normal'
resMsg.group_id = parseInt(msg.peerUin) resMsg.group_id = parseInt(msg.peerUin)
const member = await getGroupMember(msg.peerUin, msg.senderUin) const member = await getGroupMember(msg.peerUin, msg.senderUin)
if (member) { if (member) {
@@ -155,21 +159,22 @@ export class OB11Constructor {
else if (element.picElement) { else if (element.picElement) {
message_data['type'] = 'image' message_data['type'] = 'image'
// message_data["data"]["file"] = element.picElement.sourcePath // message_data["data"]["file"] = element.picElement.sourcePath
let fileName = element.picElement.fileName; let fileName = element.picElement.fileName
const sourcePath = element.picElement.sourcePath; const sourcePath = element.picElement.sourcePath
if (element.picElement.picType === PicType.gif && !fileName.endsWith('.gif')){ if (element.picElement.picType === PicType.gif && !fileName.endsWith('.gif')) {
fileName += ".gif"; fileName += '.gif'
} }
message_data['data']['file'] = fileName message_data['data']['file'] = fileName
// message_data["data"]["path"] = element.picElement.sourcePath // message_data["data"]["path"] = element.picElement.sourcePath
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64" // 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_id"] = element.picElement.fileUuid
message_data['data']['file_size'] = element.picElement.fileSize message_data['data']['file_size'] = element.picElement.fileSize
dbUtil dbUtil
.addFileCache(fileName, { .addFileCache(fileName, {
fileName, fileName,
elementId: element.elementId,
filePath: sourcePath, filePath: sourcePath,
fileSize: element.picElement.fileSize.toString(), fileSize: element.picElement.fileSize.toString(),
url: message_data['data']['url'], url: message_data['data']['url'],
@@ -195,9 +200,17 @@ export class OB11Constructor {
message_data['data']['path'] = videoOrFileElement.filePath message_data['data']['path'] = videoOrFileElement.filePath
message_data['data']['file_id'] = videoOrFileElement.fileUuid message_data['data']['file_id'] = videoOrFileElement.fileUuid
message_data['data']['file_size'] = videoOrFileElement.fileSize message_data['data']['file_size'] = videoOrFileElement.fileSize
if (element.videoElement) {
message_data['data']['url'] = await NTQQFileApi.getVideoUrl({
chatType: msg.chatType,
peerUid: msg.peerUid,
}, msg.msgId, element.elementId,
)
}
dbUtil dbUtil
.addFileCache(videoOrFileElement.fileUuid, { .addFileCache(videoOrFileElement.fileUuid, {
msgId: msg.msgId, msgId: msg.msgId,
elementId: element.elementId,
fileName: videoOrFileElement.fileName, fileName: videoOrFileElement.fileName,
filePath: videoOrFileElement.filePath, filePath: videoOrFileElement.filePath,
fileSize: videoOrFileElement.fileSize, fileSize: videoOrFileElement.fileSize,
@@ -225,6 +238,7 @@ export class OB11Constructor {
message_data['data']['file_size'] = element.pttElement.fileSize message_data['data']['file_size'] = element.pttElement.fileSize
dbUtil dbUtil
.addFileCache(element.pttElement.fileName, { .addFileCache(element.pttElement.fileName, {
elementId: element.elementId,
fileName: element.pttElement.fileName, fileName: element.pttElement.fileName,
filePath: element.pttElement.filePath, filePath: element.pttElement.filePath,
fileSize: element.pttElement.fileSize, fileSize: element.pttElement.fileSize,

View File

@@ -5,6 +5,7 @@ export class OB11GroupRequestEvent extends OB11GroupNoticeEvent {
post_type = EventType.REQUEST post_type = EventType.REQUEST
request_type: 'group' = 'group' request_type: 'group' = 'group'
sub_type: 'add' | 'invite' = 'add' sub_type: 'add' | 'invite' = 'add'
invitor_id: number | undefined = undefined
comment: string comment: string
flag: string flag: string
} }

View File

@@ -3,7 +3,7 @@ import { OB11Response } from '../action/OB11Response'
import { HttpServerBase } from '@/common/server/http' import { HttpServerBase } from '@/common/server/http'
import { actionHandlers, actionMap } from '../action' import { actionHandlers, actionMap } from '../action'
import { getConfigUtil } from '@/common/config' import { getConfigUtil } from '@/common/config'
import { postOB11Event } from './postOB11Event' import { postOb11Event } from './post-ob11-event'
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent' import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
import { selfInfo } from '@/common/data' import { selfInfo } from '@/common/data'
@@ -40,7 +40,7 @@ class HTTPHeart {
} }
this.intervalId = setInterval(() => { this.intervalId = setInterval(() => {
// ws的心跳是ws自己维护的 // ws的心跳是ws自己维护的
postOB11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false) postOb11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false)
}, heartInterval) }, heartInterval)
} }

View 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)
}
}

View File

@@ -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)
}
}

View File

@@ -4,12 +4,13 @@ import { ActionName } from '../../action/types'
import { OB11Response } from '../../action/OB11Response' import { OB11Response } from '../../action/OB11Response'
import BaseAction from '../../action/BaseAction' import BaseAction from '../../action/BaseAction'
import { actionMap } from '../../action' import { actionMap } from '../../action'
import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../postOB11Event' import { postWsEvent, registerWsEventSender, unregisterWsEventSender } from '../post-ob11-event'
import { wsReply } from './reply' import { wsReply } from './reply'
import { WebSocket as WebSocketClass } from 'ws' import { WebSocket as WebSocketClass } from 'ws'
import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent' import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent'
import { log } from '../../../common/utils/log' import { log } from '../../../common/utils/log'
import { getConfigUtil } from '../../../common/config' import { getConfigUtil } from '../../../common/config'
import { version } from '../../../version'
export let rwsList: ReverseWebsocket[] = [] export let rwsList: ReverseWebsocket[] = []
@@ -85,6 +86,7 @@ export class ReverseWebsocket {
'X-Self-ID': selfInfo.uin, 'X-Self-ID': selfInfo.uin,
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段 'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段
'User-Agent': `LLOneBot/${version}`,
}, },
}) })
registerWsEventSender(this.websocket) registerWsEventSender(this.websocket)

View File

@@ -1,7 +1,7 @@
import { WebSocket } from 'ws' import { WebSocket } from 'ws'
import { actionMap } from '../../action' import { actionMap } from '../../action'
import { OB11Response } from '../../action/OB11Response' 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 { ActionName } from '../../action/types'
import BaseAction from '../../action/BaseAction' import BaseAction from '../../action/BaseAction'
import { LifeCycleSubType, OB11LifeCycleEvent } from '../../event/meta/OB11LifeCycleEvent' import { LifeCycleSubType, OB11LifeCycleEvent } from '../../event/meta/OB11LifeCycleEvent'

View File

@@ -1,6 +1,6 @@
import { WebSocket as WebSocketClass } from 'ws' import { WebSocket as WebSocketClass } from 'ws'
import { OB11Response } from '../../action/OB11Response' import { OB11Response } from '../../action/OB11Response'
import { PostEventType } from '../postOB11Event' import { PostEventType } from '../post-ob11-event'
import { log } from '../../../common/utils/log' import { log } from '../../../common/utils/log'
import { isNull } from '../../../common/utils/helper' import { isNull } from '../../../common/utils/helper'

View File

@@ -171,6 +171,11 @@ async function onSettingWindowCreated(view: Element) {
`<div class="q-input" style="width:210px;"><input class="q-input__inner" data-config-key="musicSignUrl" type="text" value="${config.musicSignUrl}" placeholder="未设置" /></div>`, `<div class="q-input" style="width:210px;"><input class="q-input__inner" data-config-key="musicSignUrl" type="text" value="${config.musicSignUrl}" placeholder="未设置" /></div>`,
'config-musicSignUrl', 'config-musicSignUrl',
), ),
SettingItem(
'快速操作回复自动引用原消息',
null,
SettingSwitch('ob11.enableQOAutoQuote', config.ob11.enableQOAutoQuote, { 'control-display-id': 'config-ob11-enableQOAutoQuote' }),
),
SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')), SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')),
]), ]),
SettingList([ SettingList([

View File

@@ -1 +1 @@
export const version = '3.24.4' export const version = '3.26.6'