Compare commits

..

13 Commits

Author SHA1 Message Date
linyuchen
2eb0ad589a chore: ver 3.7.0 2024-02-23 19:57:20 +08:00
linyuchen
829aba18f8 feat: 管理员变动事件
feat: 加群事件
feat: 加群请求处理api
feat: 退群api
fix: 回复消息id改为string
2024-02-23 19:56:20 +08:00
linyuchen
67dfd7c22f Merge branch 'main' into dev 2024-02-23 14:06:36 +08:00
linyuchen
27745087ad Merge pull request #69 from MisaLiu/fix_app_version 2024-02-23 11:50:37 +08:00
linyuchen
4ba333b6f5 Merge pull request #68 from MisaLiu/fix_echo 2024-02-23 11:50:28 +08:00
Misa Liu
f4fe26fbe1 fix: Fix app_version in get_version_info 2024-02-23 10:33:15 +08:00
Misa Liu
30e488aeaf fix: Fix var type of echo 2024-02-23 10:22:42 +08:00
linyuchen
1f0dad786c feat: group admin change notice 2024-02-23 04:08:20 +08:00
linyuchen
8dfc71ab6d fix: message id int32 2024-02-22 23:05:07 +08:00
linyuchen
12d1f87ad5 fix: message id int32 2024-02-22 23:02:23 +08:00
linyuchen
b27dadbbca temp save 2024-02-22 22:55:52 +08:00
linyuchen
688624500f docs: tg 2024-02-22 17:35:14 +08:00
linyuchen
eefb919f0f docs: update readme 2024-02-21 22:21:32 +08:00
25 changed files with 364 additions and 71 deletions

View File

@@ -1,7 +1,9 @@
# LLOneBot API
# LLOneBot API
LiteLoaderQQNT的OneBot11协议插件 LiteLoaderQQNT的OneBot11协议插件
TG群<https://t.me/+nLZEnpne-pQ1OWFl>
*注意:本文档对应的是 LiteLoader 1.0.0及以上版本如果你使用的是旧版本请切换到本项目v1分支查看文档* *注意:本文档对应的是 LiteLoader 1.0.0及以上版本如果你使用的是旧版本请切换到本项目v1分支查看文档*
*V3之后不再需要LLAPI* *V3之后不再需要LLAPI*
@@ -31,9 +33,13 @@ LiteLoaderQQNT的OneBot11协议插件
- [x] 获取群列表 - [x] 获取群列表
- [x] 获取群成员列表 - [x] 获取群成员列表
- [x] 撤回消息 - [x] 撤回消息
- [x] 处理加群请求
- [x] 退群
- [x] 上报好友消息 - [x] 上报好友消息
- [x] 上报群消息 - [x] 上报群消息
- [x] 上报好友、群消息撤回 - [x] 上报好友、群消息撤回
- [x] 上报加群请求
- [x] 上报群员人数变动
消息格式支持: 消息格式支持:
- [x] cq码 - [x] cq码
@@ -60,6 +66,9 @@ LiteLoaderQQNT的OneBot11协议插件
- [x] get_group_member_info - [x] get_group_member_info
- [x] get_friend_list - [x] get_friend_list
- [x] get_msg - [x] get_msg
- [x] send_like
- [x] set_group_add_request
- [x] set_group_leave
- [x] get_version_info - [x] get_version_info
- [x] get_status - [x] get_status
- [x] can_send_image - [x] can_send_image
@@ -68,6 +77,7 @@ LiteLoaderQQNT的OneBot11协议插件
支持的go-cqhtp api: 支持的go-cqhtp api:
- [x] send_private_forward_msg - [x] send_private_forward_msg
- [x] send_group_forward_msg - [x] send_group_forward_msg
- [x] get_stranger_info
## 示例 ## 示例
@@ -108,7 +118,7 @@ LiteLoaderQQNT的OneBot11协议插件
- [x] 重构摆脱LLAPI目前调用LLAPI只能在renderer进程调用需重构成在main进程调用 - [x] 重构摆脱LLAPI目前调用LLAPI只能在renderer进程调用需重构成在main进程调用
- [x] 支持正、反向websocket感谢@disymayufei的PR - [x] 支持正、反向websocket感谢@disymayufei的PR
- [x] 转发消息记录 - [x] 转发消息记录
- [ ] 好友点赞api - [x] 好友点赞api
## onebot11文档 ## onebot11文档
<https://11.onebot.dev/> <https://11.onebot.dev/>

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "LiteLoaderQQNT的OneBotApi", "description": "LiteLoaderQQNT的OneBotApi",
"version": "3.6.0", "version": "3.7.0",
"thumbnail": "./icon.png", "thumbnail": "./icon.png",
"authors": [ "authors": [
{ {

View File

@@ -2,6 +2,8 @@ import fs from "fs";
import {Config, OB11Config} from "./types"; import {Config, OB11Config} from "./types";
import {mergeNewProperties} from "./utils"; import {mergeNewProperties} from "./utils";
export const HOOK_LOG = false;
export class ConfigUtil { export class ConfigUtil {
private readonly configPath: string; private readonly configPath: string;
private config: Config | null = null; private config: Config | null = null;

View File

@@ -1,15 +1,15 @@
import {NTQQApi} from '../ntqqapi/ntcall'; import {NTQQApi} from '../ntqqapi/ntcall';
import {Friend, Group, GroupMember, RawMessage, SelfInfo} from "../ntqqapi/types"; import {Friend, Group, GroupMember, GroupNotify, RawMessage, SelfInfo} from "../ntqqapi/types";
export let groups: Group[] = [] export let groups: Group[] = []
export let friends: Friend[] = [] export let friends: Friend[] = []
export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage
let globalMsgId = Date.now() let globalMsgId = Math.floor(Date.now() / 1000);
export function addHistoryMsg(msg: RawMessage): boolean{ export function addHistoryMsg(msg: RawMessage): boolean {
let existMsg = msgHistory[msg.msgId] let existMsg = msgHistory[msg.msgId]
if (existMsg){ if (existMsg) {
Object.assign(existMsg, msg) Object.assign(existMsg, msg)
msg.msgShortId = existMsg.msgShortId; msg.msgShortId = existMsg.msgShortId;
return false return false
@@ -19,7 +19,7 @@ export function addHistoryMsg(msg: RawMessage): boolean{
return true return true
} }
export function getHistoryMsgByShortId(shortId: number | string){ export function getHistoryMsgByShortId(shortId: number | string) {
// log("getHistoryMsgByShortId", shortId, Object.values(msgHistory).map(m=>m.msgShortId)) // log("getHistoryMsgByShortId", shortId, Object.values(msgHistory).map(m=>m.msgShortId))
return Object.values(msgHistory).find(msg => msg.msgShortId.toString() == shortId.toString()) return Object.values(msgHistory).find(msg => msg.msgShortId.toString() == shortId.toString())
} }
@@ -43,20 +43,19 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
return group return group
} }
export async function getGroupMember(groupQQ: string, memberQQ: string=null, memberUid: string=null) { export async function getGroupMember(groupQQ: string, memberQQ: string, memberUid: string = null) {
const group = await getGroup(groupQQ) const group = await getGroup(groupQQ)
if (group) { if (group) {
let filterFunc: (member: GroupMember) => boolean let filterFunc: (member: GroupMember) => boolean
if (memberQQ){ if (memberQQ) {
filterFunc = member => member.uin === memberQQ filterFunc = member => member.uin === memberQQ
} } else if (memberUid) {
else if (memberUid){
filterFunc = member => member.uid === memberUid filterFunc = member => member.uid === memberUid
} }
let member = group.members?.find(filterFunc) let member = group.members?.find(filterFunc)
if (!member){ if (!member) {
const _members = await NTQQApi.getGroupMembers(groupQQ) const _members = await NTQQApi.getGroupMembers(groupQQ)
if (_members.length){ if (_members.length) {
group.members = _members group.members = _members
} }
member = group.members?.find(filterFunc) member = group.members?.find(filterFunc)
@@ -77,7 +76,7 @@ export function getHistoryMsgBySeq(seq: string) {
} }
export let uidMaps:Record<string, string> = {} // 一串加密的字符串(uid) -> qq号 export let uidMaps: Record<string, string> = {} // 一串加密的字符串(uid) -> qq号
export function getUidByUin(uin: string) { export function getUidByUin(uin: string) {
for (const key in uidMaps) { for (const key in uidMaps) {
@@ -87,4 +86,6 @@ export function getUidByUin(uin: string) {
} }
} }
export const version = "v3.6.0" export const version = "3.7.0"
export let groupNotifies: Map<string, GroupNotify> = new Map();

View File

@@ -193,4 +193,8 @@ export async function encodeSilk(filePath: string) {
log("convert silk failed", error.stack); log("convert silk failed", error.stack);
return {}; return {};
} }
}
export function isNull(value: any) {
return value === undefined || value === null;
} }

View File

@@ -6,17 +6,19 @@ import {Config} from "../common/types";
import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "../common/channels"; import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "../common/channels";
import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer"; import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
import {CONFIG_DIR, getConfigUtil, log} from "../common/utils"; import {CONFIG_DIR, getConfigUtil, log} from "../common/utils";
import {addHistoryMsg, getGroupMember, msgHistory, selfInfo} from "../common/data"; import {addHistoryMsg, getGroup, getGroupMember, groupNotifies, msgHistory, selfInfo} from "../common/data";
import {hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook"; import {hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook";
import {OB11Constructor} from "../onebot11/constructor"; import {OB11Constructor} from "../onebot11/constructor";
import {NTQQApi} from "../ntqqapi/ntcall"; import {NTQQApi} from "../ntqqapi/ntcall";
import {ChatType, RawMessage} from "../ntqqapi/types"; import {ChatType, GroupMember, GroupNotifies, GroupNotifyTypes, RawMessage} from "../ntqqapi/types";
import {ob11HTTPServer} from "../onebot11/server/http"; import {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 {postEvent} from "../onebot11/server/postevent"; import {postEvent} from "../onebot11/server/postevent";
import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket"; import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket";
import {EventType} from "../onebot11/event/OB11BaseEvent"; import {OB11GroupAdminNoticeEvent} from "../onebot11/event/notice/OB11GroupAdminNoticeEvent";
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupRequestEvent} from "../onebot11/event/request/OB11GroupRequest";
let running = false; let running = false;
@@ -105,8 +107,7 @@ function onLoad() {
} }
} }
async function startReceiveHook() {
async function start() {
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
try { try {
postReceiveMsg(payload.msgList); postReceiveMsg(payload.msgList);
@@ -159,6 +160,81 @@ function onLoad() {
log("report self message error: ", e.toString()); log("report self message error: ", e.toString());
} }
}) })
registerReceiveHook<{
"doubt": boolean,
"oldestUnreadSeq": string,
"unreadCount": number
}>(ReceiveCmd.UNREAD_GROUP_NOTIFY, async (payload) => {
if (payload.unreadCount) {
log("开始获取群通知详情")
let notify: GroupNotifies;
try {
notify = await NTQQApi.getGroupNotifies();
}catch (e) {
// log("获取群通知详情失败", e);
return
}
const notifies = notify.notifies.slice(0, payload.unreadCount)
log("获取群通知详情完成", notifies, payload);
try {
for (const notify of notifies) {
if (parseInt(notify.seq) / 1000 < startTime){
continue;
}
const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
let member2: GroupMember;
if (notify.user2.uid){
member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
}
if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET].includes(notify.type)) {
log("有管理员变动通知");
let groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent()
groupAdminNoticeEvent.group_id = parseInt(notify.group.groupCode);
log("开始获取变动的管理员")
if(member1){
log("变动管理员获取成功")
groupAdminNoticeEvent.user_id = parseInt(member1.uin);
groupAdminNoticeEvent.sub_type = notify.type == GroupNotifyTypes.ADMIN_UNSET ? "unset" : "set";
postEvent(groupAdminNoticeEvent, true);
}
else{
log("获取群通知的成员信息失败", notify, getGroup(notify.group.groupCode));
}
}
else if (notify.type == GroupNotifyTypes.MEMBER_EXIT){
log("有成员退出通知");
let groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(notify.group.groupCode), parseInt(member1.uin))
// postEvent(groupDecreaseEvent, true);
}
else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)){
log("有加群请求");
groupNotifies[notify.seq] = notify;
let groupRequestEvent = new OB11GroupRequestEvent();
groupRequestEvent.group_id = parseInt(notify.group.groupCode);
let requestQQ = ""
try {
requestQQ = (await NTQQApi.getUserInfo(notify.user1.uid)).uin;
}catch (e) {
log("获取加群人QQ号失败", e)
}
groupRequestEvent.user_id = parseInt(requestQQ) || 0;
groupRequestEvent.sub_type = "add"
groupRequestEvent.comment = notify.postscript;
groupRequestEvent.flag = notify.seq;
postEvent(groupRequestEvent);
}
}
}catch (e) {
log("解析群通知失败", e.stack);
}
}
})
}
let startTime = 0;
async function start() {
startTime = Date.now();
startReceiveHook().then();
NTQQApi.getGroups(true).then() NTQQApi.getGroups(true).then()
const config = getConfigUtil().getConfig() const config = getConfigUtil().getConfig()
if (config.ob11.enableHttp) { if (config.ob11.enableHttp) {
@@ -168,16 +244,17 @@ function onLoad() {
log("http server start failed", e); log("http server start failed", e);
} }
} }
if (config.ob11.enableWs){ if (config.ob11.enableWs) {
ob11WebsocketServer.start(config.ob11.wsPort); ob11WebsocketServer.start(config.ob11.wsPort);
} }
if (config.ob11.enableWsReverse){ if (config.ob11.enableWsReverse) {
ob11ReverseWebsockets.start(); ob11ReverseWebsockets.start();
} }
log("LLOneBot start") log("LLOneBot start")
} }
let getSelfNickCount = 0;
const init = async () => { const init = async () => {
try { try {
const _ = await NTQQApi.getSelfInfo(); const _ = await NTQQApi.getSelfInfo();
@@ -194,7 +271,10 @@ function onLoad() {
if (userInfo) { if (userInfo) {
selfInfo.nick = userInfo.nick; selfInfo.nick = userInfo.nick;
} else { } else {
return setTimeout(init, 1000); getSelfNickCount++;
if (getSelfNickCount < 10){
return setTimeout(init, 1000);
}
} }
} catch (e) { } catch (e) {
log("get self nickname failed", e.toString()); log("get self nickname failed", e.toString());

View File

@@ -7,6 +7,7 @@ import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecrease
import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent"; import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
import {v4 as uuidv4} from "uuid" import {v4 as uuidv4} from "uuid"
import {postEvent} from "../onebot11/server/postevent"; import {postEvent} from "../onebot11/server/postevent";
import {HOOK_LOG} from "../common/config";
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
@@ -18,7 +19,9 @@ export enum ReceiveCmd {
GROUPS = "nodeIKernelGroupListener/onGroupListUpdate", GROUPS = "nodeIKernelGroupListener/onGroupListUpdate",
GROUPS_UNIX = "onGroupListUpdate", GROUPS_UNIX = "onGroupListUpdate",
FRIENDS = "onBuddyListChange", FRIENDS = "onBuddyListChange",
MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete" MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete",
UNREAD_GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated",
GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupSingleScreenNotifies"
} }
interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> { interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
@@ -44,7 +47,7 @@ let receiveHooks: Array<{
export function hookNTQQApiReceive(window: BrowserWindow) { export function hookNTQQApiReceive(window: BrowserWindow) {
const originalSend = window.webContents.send; const originalSend = window.webContents.send;
const patchSend = (channel: string, ...args: NTQQApiReturnData) => { const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
// log(`received ntqq api message: ${channel}`, JSON.stringify(args)) HOOK_LOG && log(`received ntqq api message: ${channel}`, JSON.stringify(args))
if (args?.[1] instanceof Array) { if (args?.[1] instanceof Array) {
for (let receiveData of args?.[1]) { for (let receiveData of args?.[1]) {
const ntQQApiMethodName = receiveData.cmdName; const ntQQApiMethodName = receiveData.cmdName;
@@ -88,15 +91,15 @@ export function hookNTQQApiCall(window: BrowserWindow) {
const proxyIpcMsg = new Proxy(ipc_message_proxy, { const proxyIpcMsg = new Proxy(ipc_message_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
log("call NTQQ api", thisArg, args); HOOK_LOG && log("call NTQQ api", thisArg, args);
return target.apply(thisArg, args); return target.apply(thisArg, args);
}, },
}); });
// if (webContents._events["-ipc-message"]?.[0]) { if (webContents._events["-ipc-message"]?.[0]) {
// webContents._events["-ipc-message"][0] = proxyIpcMsg; webContents._events["-ipc-message"][0] = proxyIpcMsg;
// } else { } else {
// webContents._events["-ipc-message"] = proxyIpcMsg; webContents._events["-ipc-message"] = proxyIpcMsg;
// } }
} }
export function registerReceiveHook<PayloadType>(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void): string { export function registerReceiveHook<PayloadType>(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void): string {
@@ -251,3 +254,4 @@ registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, ({msgRe
} }
} }
}) })

View File

@@ -1,9 +1,19 @@
import {ipcMain} from "electron"; import {ipcMain} from "electron";
import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook"; import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook";
import {log} from "../common/utils"; import {log} from "../common/utils";
import {ChatType, Friend, Group, GroupMember, RawMessage, SelfInfo, SendMessageElement, User} from "./types"; import {
ChatType,
Friend,
Group,
GroupMember,
GroupNotifies, GroupNotify, GroupRequestOperateTypes,
RawMessage,
SelfInfo,
SendMessageElement,
User
} from "./types";
import * as fs from "fs"; import * as fs from "fs";
import {addHistoryMsg, msgHistory, selfInfo} from "../common/data"; import {addHistoryMsg, groupNotifies, msgHistory, selfInfo} from "../common/data";
import {v4 as uuidv4} from "uuid" import {v4 as uuidv4} from "uuid"
interface IPCReceiveEvent { interface IPCReceiveEvent {
@@ -41,7 +51,10 @@ export enum NTQQApiMethod {
RECALL_MSG = "nodeIKernelMsgService/recallMsg", RECALL_MSG = "nodeIKernelMsgService/recallMsg",
SEND_MSG = "nodeIKernelMsgService/sendMsg", SEND_MSG = "nodeIKernelMsgService/sendMsg",
DOWNLOAD_MEDIA = "nodeIKernelMsgService/downloadRichMedia", DOWNLOAD_MEDIA = "nodeIKernelMsgService/downloadRichMedia",
MULTI_FORWARD_MSG = "nodeIKernelMsgService/multiForwardMsgWithComment" // 合并转发 MULTI_FORWARD_MSG = "nodeIKernelMsgService/multiForwardMsgWithComment", // 合并转发
GET_GROUP_NOTICE = "nodeIKernelGroupService/getSingleScreenNotifies",
HANDLE_GROUP_REQUEST = "nodeIKernelGroupService/operateSysNotify",
QUIT_GROUP = "nodeIKernelGroupService/quitGroup",
} }
enum NTQQApiChannel { enum NTQQApiChannel {
@@ -56,15 +69,11 @@ export interface Peer {
guildId?: "" guildId?: ""
} }
enum CallBackType {
UUID,
METHOD
}
interface NTQQApiParams { interface NTQQApiParams {
methodName: NTQQApiMethod, methodName: NTQQApiMethod | string,
className?: NTQQApiClass, className?: NTQQApiClass,
channel?: NTQQApiChannel, channel?: NTQQApiChannel,
classNameIsRegister?: boolean
args?: unknown[], args?: unknown[],
cbCmd?: ReceiveCmd | null, cbCmd?: ReceiveCmd | null,
cmdCB?: (payload: any) => boolean; cmdCB?: (payload: any) => boolean;
@@ -75,7 +84,7 @@ function callNTQQApi<ReturnType>(params: NTQQApiParams) {
let { let {
className, methodName, channel, args, className, methodName, channel, args,
cbCmd, timeoutSecond: timeout, cbCmd, timeoutSecond: timeout,
cmdCB classNameIsRegister, cmdCB
} = params; } = params;
className = className ?? NTQQApiClass.NT_API; className = className ?? NTQQApiClass.NT_API;
channel = channel ?? NTQQApiChannel.IPC_UP_2; channel = channel ?? NTQQApiChannel.IPC_UP_2;
@@ -87,6 +96,11 @@ function callNTQQApi<ReturnType>(params: NTQQApiParams) {
// log("callNTQQApiPromise", channel, className, methodName, args, uuid) // log("callNTQQApiPromise", channel, className, methodName, args, uuid)
const _timeout = timeout * 1000 const _timeout = timeout * 1000
let success = false let success = false
let eventName = className + "-" + channel[channel.length - 1];
if (classNameIsRegister) {
eventName += "-register";
}
const apiArgs = [methodName, ...args]
if (!cbCmd) { if (!cbCmd) {
// QQ后端会返回结果并且可以插根据uuid识别 // QQ后端会返回结果并且可以插根据uuid识别
hookApiCallbacks[uuid] = (r: ReturnType) => { hookApiCallbacks[uuid] = (r: ReturnType) => {
@@ -121,12 +135,11 @@ function callNTQQApi<ReturnType>(params: NTQQApiParams) {
setTimeout(() => { setTimeout(() => {
// log("ntqq api timeout", success, channel, className, methodName) // log("ntqq api timeout", success, channel, className, methodName)
if (!success) { if (!success) {
log(`ntqq api timeout ${channel}, ${className}, ${methodName}`) log(`ntqq api timeout ${channel}, ${eventName}, ${methodName}`, apiArgs);
reject(`ntqq api timeout ${channel}, ${className}, ${methodName}`) reject(`ntqq api timeout ${channel}, ${eventName}, ${methodName}, ${apiArgs}`)
} }
}, _timeout) }, _timeout)
const eventName = className + "-" + channel[channel.length - 1];
const apiArgs = [methodName, ...args]
ipcMain.emit( ipcMain.emit(
channel, channel,
{}, {},
@@ -240,6 +253,7 @@ export class NTQQApi {
} }
// log(uidMaps); // log(uidMaps);
// log("members info", values); // log("members info", values);
log(`get group ${groupQQ} members success`)
return members return members
} catch (e) { } catch (e) {
log(`get group ${groupQQ} members failed`, e) log(`get group ${groupQQ} members failed`, e)
@@ -343,10 +357,11 @@ export class NTQQApi {
methodName: NTQQApiMethod.DOWNLOAD_MEDIA, methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
args: apiParams, args: apiParams,
cbCmd: ReceiveCmd.MEDIA_DOWNLOAD_COMPLETE, cbCmd: ReceiveCmd.MEDIA_DOWNLOAD_COMPLETE,
cmdCB:(payload: {notifyInfo: {filePath: string}})=>{ cmdCB: (payload: { notifyInfo: { filePath: string } }) => {
// log("media 下载完成判断", payload.notifyInfo.filePath, sourcePath); // log("media 下载完成判断", payload.notifyInfo.filePath, sourcePath);
return payload.notifyInfo.filePath == sourcePath; return payload.notifyInfo.filePath == sourcePath;
}}) }
})
return sourcePath return sourcePath
} }
@@ -397,6 +412,7 @@ export class NTQQApi {
return reject("发送超时") return reject("发送超时")
} }
if (msgHistory[rawMessage.msgId]?.sendStatus == 2) { if (msgHistory[rawMessage.msgId]?.sendStatus == 2) {
log(`${peerUid}发送消息成功`)
success = true; success = true;
resolve(rawMessage); resolve(rawMessage);
} else { } else {
@@ -407,6 +423,7 @@ export class NTQQApi {
checkSendComplete(); checkSendComplete();
} else { } else {
success = true; success = true;
log(`${peerUid}发送消息成功`)
resolve(rawMessage); resolve(rawMessage);
} }
} }
@@ -476,4 +493,56 @@ export class NTQQApi {
}) })
}) })
} }
static async getGroupNotifies() {
// 获取管理员变更
// 加群通知,退出通知,需要管理员权限
await callNTQQApi<GeneralCallResult>({
methodName: ReceiveCmd.GROUP_NOTIFY,
classNameIsRegister: true,
})
return await callNTQQApi<GroupNotifies>({
methodName: NTQQApiMethod.GET_GROUP_NOTICE,
cbCmd: ReceiveCmd.GROUP_NOTIFY,
args: [
{"doubt": false, "startSeq": "", "number": 14},
null
]
});
}
static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) {
const notify: GroupNotify = groupNotifies[seq];
if (!notify){
throw `${seq}对应的加群通知不存在`
}
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
args: [
{
"doubt": false,
"operateMsg": {
"operateType": operateType, // 2 拒绝
"targetMsg": {
"seq": seq, // 通知序列号
"type": notify.type,
"groupCode": notify.group.groupCode,
"postscript": reason
}
}
},
null
]
});
}
static async quitGroup(groupQQ: string){
await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.QUIT_GROUP,
args:[
{"groupCode": groupQQ},
null
]
})
}
} }

View File

@@ -241,3 +241,42 @@ export interface RawMessage {
faceElement: FaceElement; faceElement: FaceElement;
}[]; }[];
} }
export enum GroupNotifyTypes {
INVITED_JOIN = 4, // 有人接受了邀请入群
JOIN_REQUEST = 7,
ADMIN_SET = 8,
ADMIN_UNSET = 12,
MEMBER_EXIT = 11, // 主动退出?
}
export interface GroupNotifies {
doubt: boolean,
nextStartSeq: string,
notifies: GroupNotify[],
}
export interface GroupNotify {
seq: string, // 转成数字再除以1000应该就是时间戳
type: GroupNotifyTypes,
status: 0, // 未知
group: { groupCode: string, groupName: string },
user1: { uid: string, nickName: string }, // 被设置管理员的人
user2: { uid: string, nickName: string }, // 操作者
actionUser: { uid: string, nickName: string }, //未知
actionTime: string,
invitationExt: {
srcType: number, // 0?未知
groupCode: string, waitStatus: number
},
postscript: string, // 加群用户填写的验证信息
repeatSeqs: [],
warningTips: string
}
export enum GroupRequestOperateTypes{
approve = 1,
reject = 2
}

View File

@@ -23,7 +23,7 @@ class BaseAction<PayloadType, ReturnDataType> {
} }
} }
public async websocketHandle(payload: PayloadType, echo: string): Promise<OB11Return<ReturnDataType | null>> { public async websocketHandle(payload: PayloadType, echo: any): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload) const result = await this.check(payload)
if (!result.valid) { if (!result.valid) {
return OB11Response.error(result.message, 1400) return OB11Response.error(result.message, 1400)

View File

@@ -0,0 +1,9 @@
import BaseAction from "./BaseAction";
import {ActionName} from "./types";
export default class GetGuildList extends BaseAction<null, null>{
actionName = ActionName.GetGuildList
protected async _handle(payload: null): Promise<null> {
return null;
}
}

View File

@@ -0,0 +1,30 @@
import BaseAction from "./BaseAction";
import {groupNotifies} from "../../common/data";
import {GroupNotify, GroupRequestOperateTypes} from "../../ntqqapi/types";
import {NTQQApi} from "../../ntqqapi/ntcall";
import {ActionName} from "./types";
interface Payload{
flag: string,
// sub_type: "add" | "invite",
// type: "add" | "invite"
approve: boolean,
reason: string
}
export default class SetGroupAddRequest extends BaseAction<Payload, null>{
actionName = ActionName.SetGroupAddRequest
protected async _handle(payload: Payload): Promise<null> {
const seq = payload.flag.toString();
const notify: GroupNotify = groupNotifies[seq]
try{
await NTQQApi.handleGroupRequest(seq,
payload.approve ? GroupRequestOperateTypes.approve: GroupRequestOperateTypes.reject,
payload.reason
)
}catch (e) {
throw e
}
return null
}
}

View File

@@ -0,0 +1,22 @@
import BaseAction from "./BaseAction";
import {NTQQApi} from "../../ntqqapi/ntcall";
import {log} from "../../common/utils";
import {ActionName} from "./types";
interface Payload{
group_id: number,
is_dismiss: boolean
}
export default class SetGroupLeave extends BaseAction<Payload, any>{
actionName = ActionName.SetGroupLeave
protected async _handle(payload: Payload): Promise<any> {
try{
await NTQQApi.quitGroup(payload.group_id.toString())
}
catch (e) {
log("退群失败", e)
throw e
}
}
}

View File

@@ -17,6 +17,9 @@ import GetStatus from "./GetStatus";
import {GoCQHTTPSendGroupForwardMsg, GoCQHTTPSendPrivateForwardMsg} from "./go-cqhttp/SendForwardMsg"; import {GoCQHTTPSendGroupForwardMsg, GoCQHTTPSendPrivateForwardMsg} from "./go-cqhttp/SendForwardMsg";
import GoCQHTTPGetStrangerInfo from "./go-cqhttp/GetStrangerInfo"; import GoCQHTTPGetStrangerInfo from "./go-cqhttp/GetStrangerInfo";
import SendLike from "./SendLike"; import SendLike from "./SendLike";
import SetGroupAddRequest from "./SetGroupAddRequest";
import SetGroupLeave from "./SetGroupLeave";
import GetGuildList from "./GetGuildList";
export const actionHandlers = [ export const actionHandlers = [
new SendLike(), new SendLike(),
@@ -26,6 +29,8 @@ export const actionHandlers = [
new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(), new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(),
new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(), new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(),
new DeleteMsg(), new DeleteMsg(),
new SetGroupAddRequest(),
new SetGroupLeave(),
new GetVersionInfo(), new GetVersionInfo(),
new CanSendRecord(), new CanSendRecord(),
new CanSendImage(), new CanSendImage(),
@@ -34,7 +39,8 @@ export const actionHandlers = [
//以下为go-cqhttp api //以下为go-cqhttp api
new GoCQHTTPSendGroupForwardMsg(), new GoCQHTTPSendGroupForwardMsg(),
new GoCQHTTPSendPrivateForwardMsg(), new GoCQHTTPSendPrivateForwardMsg(),
new GoCQHTTPGetStrangerInfo() new GoCQHTTPGetStrangerInfo(),
new GetGuildList()
] ]

View File

@@ -1,3 +1,5 @@
import GetGuildList from "./GetGuildList";
export type BaseCheckResult = ValidCheckResult | InvalidCheckResult export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
export interface ValidCheckResult { export interface ValidCheckResult {
@@ -25,6 +27,8 @@ export enum ActionName {
SendGroupMsg = "send_group_msg", SendGroupMsg = "send_group_msg",
SendPrivateMsg = "send_private_msg", SendPrivateMsg = "send_private_msg",
DeleteMsg = "delete_msg", DeleteMsg = "delete_msg",
SetGroupAddRequest = "set_group_add_request",
SetGroupLeave = "set_group_leave",
GetVersionInfo = "get_version_info", GetVersionInfo = "get_version_info",
GetStatus = "get_status", GetStatus = "get_status",
CanSendRecord = "can_send_record", CanSendRecord = "can_send_record",
@@ -32,5 +36,6 @@ export enum ActionName {
// 以下为go-cqhttp api // 以下为go-cqhttp api
GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg", GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg",
GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg", GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg",
GoCQHTTP_GetStrangerInfo = "get_stranger_info" GoCQHTTP_GetStrangerInfo = "get_stranger_info",
GetGuildList = "get_guild_list",
} }

View File

@@ -1,4 +1,5 @@
import {OB11Return} from '../types'; import {OB11Return} from '../types';
import {isNull} from '../../common/utils';
export class OB11Response { export class OB11Response {
static res<T>(data: T, status: string, retcode: number, message: string = ""): OB11Return<T> { static res<T>(data: T, status: string, retcode: number, message: string = ""): OB11Return<T> {
@@ -8,21 +9,21 @@ export class OB11Response {
data: data, data: data,
message: message, message: message,
wording: message, wording: message,
echo: "" echo: null
} }
} }
static ok<T>(data: T, echo: string = "") { static ok<T>(data: T, echo: any = null) {
let res = OB11Response.res<T>(data, "ok", 0) let res = OB11Response.res<T>(data, "ok", 0)
if (echo) { if (!isNull(echo)) {
res.echo = echo; res.echo = echo;
} }
return res; return res;
} }
static error(err: string, retcode: number, echo: string = "") { static error(err: string, retcode: number, echo: any = null) {
let res = OB11Response.res(null, "failed", retcode, err) let res = OB11Response.res(null, "failed", retcode, err)
if (echo) { if (!isNull(echo)) {
res.echo = echo; res.echo = echo;
} }
return res; return res;

View File

@@ -114,7 +114,7 @@ export class OB11Constructor {
message_data["type"] = "reply" message_data["type"] = "reply"
const replyMsg = getHistoryMsgBySeq(element.replyElement.replayMsgSeq) const replyMsg = getHistoryMsgBySeq(element.replyElement.replayMsgSeq)
if (replyMsg) { if (replyMsg) {
message_data["data"]["id"] = replyMsg.msgShortId message_data["data"]["id"] = replyMsg.msgShortId.toString()
} else { } else {
continue continue
} }
@@ -194,7 +194,8 @@ export class OB11Constructor {
group_id: parseInt(group_id), group_id: parseInt(group_id),
user_id: parseInt(member.uin), user_id: parseInt(member.uin),
nickname: member.nick, nickname: member.nick,
card: member.cardName card: member.cardName,
role: OB11Constructor.groupMemberRole(member.role),
} }
} }

View File

@@ -1,6 +1,7 @@
import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent"; import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
export class OB11GroupAdminNoticeEvent extends OB11BaseNoticeEvent { export class OB11GroupAdminNoticeEvent extends OB11GroupNoticeEvent {
notice_type = "group_admin" notice_type = "group_admin"
sub_type: string // "set" | "unset" sub_type: "set" | "unset" // "set" | "unset"
} }

View File

@@ -2,7 +2,7 @@ import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent { export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent {
notice_type = "group_decrease"; notice_type = "group_decrease";
sub_type = "leave"; // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me") sub_type: "leave" | "kick" | "kick_me" = "leave"; // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
operate_id: number; operate_id: number;
constructor(groupId: number, userId: number) { constructor(groupId: number, userId: number) {

View File

@@ -0,0 +1,9 @@
import {OB11GroupNoticeEvent} from "../notice/OB11GroupNoticeEvent";
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent{
request_type: "group" = "group";
sub_type: "add" | "invite" = "add";
comment: string;
flag: string;
}

View File

@@ -29,10 +29,10 @@ export function postWsEvent(event: PostEventType) {
} }
} }
export function postEvent(msg: PostEventType) { export function postEvent(msg: PostEventType, reportSelf=false) {
const config = getConfigUtil().getConfig(); const config = getConfigUtil().getConfig();
// 判断msg是否是event // 判断msg是否是event
if (!config.reportSelfMessage) { if (!config.reportSelfMessage && !reportSelf) {
if ((msg as OB11Message).user_id.toString() == selfInfo.uin) { if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
return return
} }

View File

@@ -33,8 +33,8 @@ export class ReverseWebsocket {
} }
public async onmessage(msg: string) { public async onmessage(msg: string) {
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}} let receiveData: { action: ActionName, params: any, echo?: any } = {action: null, params: {}}
let echo = "" let echo = null
try { try {
receiveData = JSON.parse(msg.toString()) receiveData = JSON.parse(msg.toString())
echo = receiveData.echo echo = receiveData.echo

View File

@@ -18,7 +18,7 @@ class OB11WebsocketServer extends WebsocketServerBase {
wsClient.send(JSON.stringify(OB11Response.res(null, "failed", 1403, "token验证失败"))) wsClient.send(JSON.stringify(OB11Response.res(null, "failed", 1403, "token验证失败")))
} }
async handleAction(wsClient: WebSocket, actionName: string, params: any, echo?: string) { async handleAction(wsClient: WebSocket, actionName: string, params: any, echo?: any) {
const action: BaseAction<any, any> = actionMap.get(actionName); const action: BaseAction<any, any> = actionMap.get(actionName);
if (!action) { if (!action) {
return wsReply(wsClient, OB11Response.error("不支持的api " + actionName, 1404, echo)) return wsReply(wsClient, OB11Response.error("不支持的api " + actionName, 1404, echo))
@@ -34,8 +34,8 @@ class OB11WebsocketServer extends WebsocketServerBase {
onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) { onConnect(wsClient: WebSocket, url: string, req: IncomingMessage) {
if (url == "/api" || url == "/api/" || url == "/") { if (url == "/api" || url == "/api/" || url == "/") {
wsClient.on("message", async (msg) => { wsClient.on("message", async (msg) => {
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}} let receiveData: { action: ActionName, params: any, echo?: any } = {action: null, params: {}}
let echo = "" let echo = null
try { try {
receiveData = JSON.parse(msg.toString()) receiveData = JSON.parse(msg.toString())
echo = receiveData.echo echo = receiveData.echo

View File

@@ -1,13 +1,13 @@
import * as websocket from "ws"; import * as websocket from "ws";
import {OB11Response} from "../../action/utils"; import {OB11Response} from "../../action/utils";
import {PostEventType} from "../postevent"; import {PostEventType} from "../postevent";
import {log} from "../../../common/utils"; import {isNull, log} from "../../../common/utils";
export function wsReply(wsClient: websocket.WebSocket, data: OB11Response | PostEventType) { export function wsReply(wsClient: websocket.WebSocket, data: OB11Response | PostEventType) {
try { try {
let packet = Object.assign({ let packet = Object.assign({
}, data); }, data);
if (!packet["echo"]){ if (isNull(packet["echo"])){
delete packet["echo"]; delete packet["echo"];
} }
wsClient.send(JSON.stringify(packet)) wsClient.send(JSON.stringify(packet))

View File

@@ -77,7 +77,7 @@ export interface OB11Return<DataType> {
retcode: number retcode: number
data: DataType data: DataType
message: string, message: string,
echo?: string, // ws调用api才有此字段 echo?: any, // ws调用api才有此字段
wording?: string, // go-cqhttp字段错误信息 wording?: string, // go-cqhttp字段错误信息
} }