refactor: pre-release

This commit is contained in:
linyuchen
2024-02-11 19:57:20 +08:00
parent d08601505b
commit 8d2353a524
16 changed files with 607 additions and 759 deletions

View File

@@ -1,16 +1,3 @@
export const CHANNEL_SEND_MSG = "llonebot_send_msg"
export const CHANNEL_SEND_BACK_MSG = "llonebot_send_back_msg"
export const CHANNEL_RECALL_MSG = "llonebot_recall_msg"
export const CHANNEL_GET_CONFIG = "llonebot_get_config"
export const CHANNEL_SET_CONFIG = "llonebot_set_config"
export const CHANNEL_START_HTTP_SERVER = "llonebot_start_http_server"
export const CHANNEL_UPDATE_GROUPS = "llonebot_update_groups"
export const CHANNEL_UPDATE_FRIENDS = "llonebot_update_friends"
export const CHANNEL_LOG = "llonebot_log"
export const CHANNEL_POST_ONEBOT_DATA = "llonebot_post_onebot_data"
export const CHANNEL_SET_SELF_INFO= "llonebot_set_self_info"
export const CHANNEL_DOWNLOAD_FILE= "llonebot_download_file"
export const CHANNEL_DELETE_FILE= "llonebot_delete_file"
export const CHANNEL_GET_RUNNING_STATUS= "llonebot_get_running_status"
export const CHANNEL_FILE2BASE64= "llonebot_file2base64"
export const CHANNEL_GET_HISTORY_MSG= "llonebot_get_history_msg"

View File

@@ -1,26 +1,25 @@
import {SelfInfo} from "./types";
import { NTQQApi } from '../ntqqapi/ntcall';
import { Group, RawMessage, User } from "../ntqqapi/types";
import { Friend, Group, RawMessage, SelfInfo } from "../ntqqapi/types";
export let groups: Group[] = []
export let friends: User[] = []
export let friends: Friend[] = []
export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage
export async function getFriend(qq: string): Promise<User | undefined> {
export async function getFriend(qq: string): Promise<Friend | undefined> {
let friend = friends.find(friend => friend.uin === qq)
if (!friend){
friends = await NTQQApi.getFriends(true)
friend = friends.find(friend => friend.uin === qq)
}
// if (!friend){
// friends = (await NTQQApi.getFriends(true))
// friend = friends.find(friend => friend.uin === qq)
// }
return friend
}
export async function getGroup(qq: string): Promise<Group | undefined> {
let group = groups.find(group => group.groupCode === qq)
if (!group){
groups = await NTQQApi.getGroups(true);
group = groups.find(group => group.groupCode === qq)
}
// if (!group){
// groups = await NTQQApi.getGroups(true);
// group = groups.find(group => group.groupCode === qq)
// }
return group
}
@@ -29,7 +28,10 @@ export async function getGroupMember(groupQQ: string, memberQQ: string) {
if (group) {
let member = group.members?.find(member => member.uin === memberQQ)
if (!member){
group.members = await NTQQApi.getGroupMembers(groupQQ)
const _members = await NTQQApi.getGroupMembers(groupQQ)
if (_members.length){
group.members = _members
}
member = group.members?.find(member => member.uin === memberQQ)
}
return member
@@ -37,8 +39,9 @@ export async function getGroupMember(groupQQ: string, memberQQ: string) {
}
export let selfInfo: SelfInfo = {
user_id: "",
nickname: ""
uid: "",
uin: "",
nick: "",
}
@@ -47,7 +50,7 @@ export function getHistoryMsgBySeq(seq: string) {
}
export let uidMaps:Record<string, User> = {} // 一串加密的字符串(uid) -> qq号
export let uidMaps:Record<string, Friend> = {} // 一串加密的字符串(uid) -> qq号
export function getStrangerByUin(uin: string) {
for (const key in uidMaps) {

View File

@@ -1,10 +1,3 @@
import {OB11ApiName, OB11MessageData} from "../onebot11/types";
export interface SelfInfo {
user_id: string;
nickname: string;
}
export interface Config {
port: number
hosts: string[]

View File

@@ -1,15 +1,15 @@
import * as path from "path";
import {json} from "express";
import {selfInfo} from "./data";
import {ConfigUtil} from "./config";
import util from "util";
import { sendLog } from '../main/ipcsend';
const fs = require('fs');
export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
export function getConfigUtil() {
const configFilePath = path.join(CONFIG_DIR, `config_${selfInfo.user_id}.json`)
const configFilePath = path.join(CONFIG_DIR, `config_${selfInfo.uin}.json`)
return new ConfigUtil(configFilePath)
}
@@ -23,7 +23,7 @@ export function log(...msg: any[]) {
const month = date.getMonth() + 1;
const day = date.getDate();
const currentDate = `${year}-${month}-${day}`;
const userInfo = selfInfo.user_id ? `${selfInfo.nickname}(${selfInfo.user_id})` : ""
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : ""
let logMsg = "";
for (let msgItem of msg){
// 判断是否是对象
@@ -34,6 +34,8 @@ export function log(...msg: any[]) {
logMsg += msgItem + " ";
}
logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n`
// sendLog(...msg);
// console.log(msg)
fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), logMsg, (err: any) => {
})

View File

@@ -1,50 +1,18 @@
import {ipcMain, webContents} from 'electron';
import {OB11PostSendMsg} from "../onebot11/types"
import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG,CHANNEL_SEND_BACK_MSG} from "../common/channels";
import {v4 as uuid4} from "uuid";
import {log} from "../common/utils";
import {webContents} from 'electron';
import { CHANNEL_LOG } from '../common/channels';
import {OB11Return} from "../onebot11/types";
function sendIPCMsg(channel: string, data: any) {
function sendIPCMsg(channel: string, ...data: any) {
let contents = webContents.getAllWebContents();
for (const content of contents) {
try {
content.send(channel, data)
content.send(channel, ...data)
} catch (e) {
console.log("llonebot send ipc msg to render error:", e)
}
}
}
export interface SendIPCMsgSession<T> {
id: string
data: T
export function sendLog(...args){
sendIPCMsg(CHANNEL_LOG, ...args)
}
export function sendIPCSendQQMsg(postData: OB11PostSendMsg, handleSendResult: (data: OB11Return<any>) => void) {
const onceSessionId = uuid4();
const handler = (event: any, session: SendIPCMsgSession<OB11Return<any>>) => {
// log("llonebot send msg ipcMain.once:" + JSON.stringify(sendResult));
if (session?.id !== onceSessionId) {
return
}
try {
handleSendResult(session.data)
ipcMain.off(CHANNEL_SEND_BACK_MSG, handler)
return
} catch (e) {
log("llonebot send msg sendIPCSendQQMsg handler error:" + JSON.stringify(e))
}
}
ipcMain.on(CHANNEL_SEND_BACK_MSG, handler)
sendIPCMsg(CHANNEL_SEND_MSG, {
id: onceSessionId,
data: postData,
});
}
export function sendIPCRecallQQMsg(message_id: string) {
sendIPCMsg(CHANNEL_RECALL_MSG, { message_id: message_id });
}

View File

@@ -1,29 +1,23 @@
// 运行在 Electron 主进程 下的插件入口
import * as path from "path";
import {BrowserWindow, ipcMain} from 'electron';
import { BrowserWindow, ipcMain } from 'electron';
import * as util from 'util';
import {Config, Group, RawMessage, SelfInfo, User} from "../common/types";
import { Config } from "../common/types";
import {
CHANNEL_DOWNLOAD_FILE,
CHANNEL_GET_CONFIG,
CHANNEL_SET_SELF_INFO,
CHANNEL_LOG,
CHANNEL_POST_ONEBOT_DATA,
CHANNEL_SET_CONFIG,
CHANNEL_START_HTTP_SERVER,
CHANNEL_UPDATE_FRIENDS,
CHANNEL_UPDATE_GROUPS, CHANNEL_DELETE_FILE, CHANNEL_GET_RUNNING_STATUS, CHANNEL_FILE2BASE64, CHANNEL_GET_HISTORY_MSG
} from "../common/channels";
import {ConfigUtil} from "../common/config";
import {postMsg, startExpress} from "../server/httpserver";
import {checkFileReceived, CONFIG_DIR, file2base64, getConfigUtil, isGIF, log} from "../common/utils";
import {friends, groups, msgHistory, selfInfo} from "../common/data";
import {} from "../global";
import {hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook";
import {OB11Constructor} from "../onebot11/constructor";
import {NTQQApi} from "../ntqqapi/ntcall";
import { ConfigUtil } from "../common/config";
import { postMsg, startExpress } from "../onebot11/server";
import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
import { friends, groups, msgHistory, selfInfo } from "../common/data";
import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
import { OB11Constructor } from "../onebot11/constructor";
import { NTQQApi } from "../ntqqapi/ntcall";
import { Group, RawMessage, SelfInfo } from "../ntqqapi/types";
const fs = require('fs');
@@ -38,7 +32,7 @@ function onLoad() {
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, {recursive: true});
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => {
return getConfigUtil().getConfig()
@@ -52,18 +46,18 @@ function onLoad() {
})
function postRawMsg(msgList:RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig();
function postRawMsg(msgList: RawMessage[]) {
const { debug, reportSelfMessage } = getConfigUtil().getConfig();
for (const message of msgList) {
OB11Constructor.message(message).then((msg) => {
if (debug) {
msg.raw = message;
}
if (msg.user_id == selfInfo.user_id && !reportSelfMessage) {
if (msg.user_id == selfInfo.uin && !reportSelfMessage) {
return
}
postMsg(msg);
}).catch(e=>log("constructMessage error: ", e.toString()));
}).catch(e => log("constructMessage error: ", e.toString()));
}
}
@@ -76,7 +70,7 @@ function onLoad() {
})
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => {
const {reportSelfMessage} = getConfigUtil().getConfig()
const { reportSelfMessage } = getConfigUtil().getConfig()
if (!reportSelfMessage) {
return
}
@@ -87,46 +81,56 @@ function onLoad() {
log("report self message error: ", e.toString())
}
})
async function getSelfInfo(){
if (!selfInfo.user_id){
setTimeout(()=>{
getSelfInfo().then()
})
async function getSelfInfo() {
try{
const _ = await NTQQApi.getSelfInfo()
Object.assign(selfInfo, _)
selfInfo.nick = selfInfo.uin
log("get self simple info", _)
}catch(e){
log("retry get self info")
}
const _ = await NTQQApi.getSelfInfo()
if (_.uin){
log("get self info success", _)
selfInfo.user_id = _.uin
let nickName = _.uin
try{
const userInfo = (await NTQQApi.getUserInfo(_.uid))
if (userInfo){
nickName = userInfo.nickName
if (selfInfo.uin) {
try {
const userInfo = (await NTQQApi.getUserInfo(selfInfo.uid))
if (userInfo) {
selfInfo.nick = userInfo.nick
}
}
catch(e){
catch (e) {
log("get self nickname failed", e.toString())
}
selfInfo.nickname = nickName
try{
// let _friends = await NTQQApi.getFriends(true)
// log("friends api:", _friends)
// for (let f of _friends){
// friends.push(f)
// }
let _groups = await NTQQApi.getGroups(true)
log("groups api:", _groups)
for (let g of _groups){
g.members = (await NTQQApi.getGroupMembers(g.uid))
groups.push(g)
}
// try {
// friends.push(...(await NTQQApi.getFriends(true)))
// log("get friends", friends)
// let _groups: Group[] = []
// for(let i=0; i++; i<3){
// try{
// _groups = await NTQQApi.getGroups(true)
// log("get groups sucess", _groups)
// break
// } catch(e) {
// log("get groups failed", e)
// }
// }
// for (let g of _groups) {
// g.members = (await NTQQApi.getGroupMembers(g.groupCode))
// log("group members", g.members)
// groups.push(g)
// }
}catch(e){
log("!!!初始化失败", e.stack.toString())
}
// } catch (e) {
// log("!!!初始化失败", e.stack.toString())
// }
startExpress(getConfigUtil().getConfig().port)
}
else{
setTimeout(() => {
getSelfInfo().then()
}, 100)
}
}
getSelfInfo().then()
}
@@ -136,7 +140,7 @@ function onLoad() {
function onBrowserWindowCreated(window: BrowserWindow) {
try {
hookNTQQApiReceive(window);
} catch (e){
} catch (e) {
log("llonebot hook error: ", e.toString())
}
}

View File

@@ -1,5 +1,4 @@
import {AtType} from "../common/types";
import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement} from "./types";
import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, AtType} from "./types";
import {NTQQApi} from "./ntcall";

View File

@@ -1,9 +1,10 @@
import {BrowserWindow} from 'electron';
import {getConfigUtil, log} from "../common/utils";
import {NTQQApiClass, sendMessagePool} from "./ntcall";
import { Group } from "./types";
import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
import { Group, User } from "./types";
import { RawMessage } from "./types";
import {groups, msgHistory} from "../common/data";
import {friends, groups, msgHistory} from "../common/data";
import { v4 as uuidv4 } from 'uuid';
export let hookApiCallbacks: Record<string, (apiReturn: any)=>void>={}
@@ -13,7 +14,8 @@ export enum ReceiveCmd {
SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg",
USER_INFO = "nodeIKernelProfileListener/onProfileDetailInfoChanged",
GROUPS = "nodeIKernelGroupListener/onGroupListUpdate",
GROUPS_UNIX = "onGroupListUpdate"
GROUPS_UNIX = "onGroupListUpdate",
FRIENDS = "onBuddyListChange"
}
interface NTQQApiReturnData<PayloadType=unknown> extends Array<any> {
@@ -32,14 +34,14 @@ interface NTQQApiReturnData<PayloadType=unknown> extends Array<any> {
let receiveHooks: Array<{
method: ReceiveCmd,
hookFunc: (payload: any) => void
hookFunc: (payload: any) => void,
id: string
}> = []
export function hookNTQQApiReceive(window: BrowserWindow) {
const originalSend = window.webContents.send;
const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
// 判断是否是列表
log(`received ntqq api message: ${channel}`, JSON.stringify(args))
// log(`received ntqq api message: ${channel}`, JSON.stringify(args))
if (args?.[1] instanceof Array) {
for (let receiveData of args?.[1]) {
const ntQQApiMethodName = receiveData.cmdName;
@@ -73,32 +75,53 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
window.webContents.send = patchSend;
}
export function registerReceiveHook<PayloadType>(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void) {
export function registerReceiveHook<PayloadType>(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void): string {
const id = uuidv4()
receiveHooks.push({
method,
hookFunc
hookFunc,
id
})
return id;
}
function updateGroups(_groups: Group[]){
export function removeReceiveHook(id: string){
const index = receiveHooks.findIndex(h=>h.id === id)
receiveHooks.splice(index, 1);
}
async function updateGroups(_groups: Group[]){
for(let group of _groups){
let existGroup = groups.find(g=>g.groupCode == group.groupCode)
if (!existGroup){
groups.push(group)
log("update group")
let _membeers = await NTQQApi.getGroupMembers(group.groupCode)
if (_membeers){
group.members = _membeers
}
log("update group members", group.members)
}
else{
Object.assign(existGroup, group);
group.members = [...existGroup.members]
}
}
groups.length = 0;
groups.push(..._groups)
}
registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS, (payload)=>updateGroups(payload.groupList))
registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS_UNIX, (payload)=>updateGroups(payload.groupList))
registerReceiveHook<any>(ReceiveCmd.USER_INFO, (payload)=>{
log("user info", payload);
registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS, (payload)=>updateGroups(payload.groupList).then())
registerReceiveHook<{groupList: Group[]}>(ReceiveCmd.GROUPS_UNIX, (payload)=>updateGroups(payload.groupList).then())
registerReceiveHook<{data:{categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[]}[]}>(ReceiveCmd.FRIENDS, payload=>{
friends.length = 0
for (const fData of payload.data) {
friends.push(...fData.buddyList)
}
})
// registerReceiveHook<any>(ReceiveCmd.USER_INFO, (payload)=>{
// log("user info", payload);
// })
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payload) => {
for (const message of payload.msgList) {
msgHistory[message.msgId] = message;

View File

@@ -1,13 +1,12 @@
import {ipcMain} from "electron";
import {v4 as uuidv4} from "uuid";
import {hookApiCallbacks} from "./hook";
import {log} from "../common/utils";
import { ChatType } from "./types";
import { ipcMain } from "electron";
import { v4 as uuidv4 } from "uuid";
import { ReceiveCmd, hookApiCallbacks, registerReceiveHook, removeReceiveHook } from "./hook";
import { log } from "../common/utils";
import { ChatType, Friend, SelfInfo, User } from "./types";
import { Group } from "./types";
import { GroupMember } from "./types";
import { RawMessage } from "./types";
import { User } from "./types";
import {SendMessageElement} from "./types";
import { SendMessageElement } from "./types";
interface IPCReceiveEvent {
eventName: string
@@ -31,7 +30,7 @@ export enum NTQQApiMethod {
LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike",
UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate",
SELF_INFO = "fetchAuthData",
FRIENDS = "nodeIKernelProfileService/getBuddyProfileList",
FRIENDS = "nodeIKernelBuddyService/getBuddyList",
GROUPS = "nodeIKernelGroupService/getGroupList",
GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene",
GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList",
@@ -58,26 +57,65 @@ export interface Peer {
guildId?: ""
}
enum CallBackType {
UUID,
METHOD
}
function callNTQQApi<ReturnType>(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[] = []) {
function callNTQQApi<ReturnType>(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[] = [], cbCmd: ReceiveCmd | null = null, timeout = 5) {
const uuid = uuidv4();
// log("callNTQQApi", channel, className, methodName, args, uuid)
return new Promise((resolve: (data: ReturnType) => void, reject) => {
// log("callNTQQApiPromise", channel, className, methodName, args, uuid)
hookApiCallbacks[uuid] = resolve;
const _timeout = timeout * 1000
let success = false
if (!cbCmd) {
// QQ后端会返回结果并且可以插根据uuid识别
hookApiCallbacks[uuid] = (r: ReturnType) => {
success = true
resolve(r)
};
}
else {
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
hookApiCallbacks[uuid] = (result: GeneralCallResult) => {
log(`${methodName} callback`, result)
if (result.result == 0) {
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
log(methodName, "second callback", cbCmd, payload);
removeReceiveHook(hookId);
success = true
resolve(payload);
})
}
else {
success = true
reject(`ntqq api call failed, ${result.errMsg}`);
}
}
}
setTimeout(() => {
// log("ntqq api timeout", success, channel, className, methodName)
if (!success) {
log(`ntqq api timeout ${channel}, ${className}, ${methodName}`)
reject(`ntqq api timeout ${channel}, ${className}, ${methodName}`)
}
}, _timeout)
ipcMain.emit(
channel,
{},
{type: 'request', callbackId: uuid, eventName: className + "-" + channel[channel.length - 1]},
{ type: 'request', callbackId: uuid, eventName: className + "-" + channel[channel.length - 1] },
[methodName, ...args],
)
})
}
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage)=>void) | null> = {}// peerUid: callbackFunnc
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {}// peerUid: callbackFunnc
interface GeneralCallResult{
result:0,
interface GeneralCallResult {
result: number, // 0: success
errMsg: string
}
@@ -97,41 +135,63 @@ export class NTQQApi {
}
static getSelfInfo() {
return callNTQQApi<User>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.GLOBAL_DATA, NTQQApiMethod.SELF_INFO, [])
return callNTQQApi<SelfInfo>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.GLOBAL_DATA, NTQQApiMethod.SELF_INFO, [], null, 2)
}
static getFriends(forced = false) {
return callNTQQApi<GeneralCallResult>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{force_update: forced}, undefined])
}
static getGroups(forced = false) {
return callNTQQApi<GeneralCallResult>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{force_update: forced}, undefined])
}
static async getGroupMembers(groupQQ: string, num = 5000) {
const sceneId = callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{
groupCode: groupQQ,
scene: "groupMemberList_MainWindow"
}]
)
return callNTQQApi<GroupMember[]>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBERS,
[{
sceneId: sceneId,
num: num
},
null
])
}
static async getUserInfo(uid: string) {
const result = await callNTQQApi<GeneralCallResult>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO,
[{force: true, uids: [uid]}, undefined])
log("get user info result", result);
return result[0].payload.profiles.get(uid);
const result = await callNTQQApi<{ info: User }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO,
[{ force: true, uids: [uid] }, undefined], ReceiveCmd.USER_INFO)
return result.info
}
// static async getFriends(forced = false) {
// const data = await callNTQQApi<{ data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: Friend[] }[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{ force_update: forced }, undefined], ReceiveCmd.FRIENDS)
// let _friends: Friend[] = [];
// for (const fData of data.data) {
// _friends.push(...fData.buddyList)
// }
// return _friends
// }
// static async getGroups(forced = false) {
// let cbCmd = ReceiveCmd.GROUPS
// if (process.platform != "win32") {
// cbCmd = ReceiveCmd.GROUPS_UNIX
// }
// const result = await callNTQQApi<{ updateType: number, groupList: Group[] }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{ force_update: forced }, undefined], cbCmd)
// return result.groupList
// }
static async getGroupMembers(groupQQ: string, num = 3000) {
const sceneId = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{
groupCode: groupQQ,
scene: "groupMemberList_MainWindow"
}])
// log("get group member sceneId", sceneId);
try {
const result = await callNTQQApi<{result:{infos: any}}>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBERS,
[{
sceneId: sceneId,
num: num
},
null
])
// log("members info", typeof result.result.infos, Object.keys(result.result.infos))
let values = result.result.infos.values()
values = Array.from(values) as GroupMember[]
// log("members info", values);
return values
} catch (e) {
log(`get group ${groupQQ} members failed`, e)
return []
}
}
static getFileType(filePath: string) {
return callNTQQApi<{
ext: string
@@ -184,40 +244,40 @@ export class NTQQApi {
}
}
static recallMsg(peer: Peer, msgIds: string[]){
return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.RECALL_MSG, [{peer, msgIds}, null])
static recallMsg(peer: Peer, msgIds: string[]) {
return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.RECALL_MSG, [{ peer, msgIds }, null])
}
static sendMsg(peer: Peer, msgElements: SendMessageElement[]){
static sendMsg(peer: Peer, msgElements: SendMessageElement[]) {
const sendTimeout = 10 * 1000
return new Promise<RawMessage>((resolve, reject)=>{
return new Promise<RawMessage>((resolve, reject) => {
const peerUid = peer.peerUid;
let usingTime = 0;
let success = false;
const checkSuccess = ()=>{
if (!success){
const checkSuccess = () => {
if (!success) {
sendMessagePool[peerUid] = null;
reject("发送超时")
}
}
setTimeout(checkSuccess, sendTimeout);
const checkLastSend = ()=>{
const checkLastSend = () => {
let lastSending = sendMessagePool[peerUid]
if (sendTimeout < usingTime){
if (sendTimeout < usingTime) {
sendMessagePool[peerUid] = null;
reject("发送超时")
}
if (!!lastSending){
if (!!lastSending) {
// log("有正在发送的消息,等待中...")
usingTime += 100;
setTimeout(checkLastSend, 100);
}
else{
else {
log("可以进行发送消息,设置发送成功回调", sendMessagePool)
sendMessagePool[peerUid] = (rawMessage: RawMessage)=>{
sendMessagePool[peerUid] = (rawMessage: RawMessage) => {
success = true;
sendMessagePool[peerUid] = null;
resolve(rawMessage);

View File

@@ -1,15 +1,18 @@
export interface User {
uid: string; // 加密的字符串
uin: string; // QQ号
nick: string;
avatarUrl?: string;
longNick: string; // 签名
raw: {
remark: string;
};
longNick?: string; // 签名
remark?: string
}
export interface SelfInfo extends User{
}
export interface Friend extends User{}
export interface Group{
groupCode: string,
maxMember: number,

View File

@@ -1,7 +1,7 @@
import {OB11MessageDataType, OB11GroupMemberRole, OB11Message, OB11MessageData, OB11Group, OB11GroupMember, Friend} from "./types";
import { AtType, ChatType, Group, GroupMember, RawMessage, User } from '../ntqqapi/types';
import {getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo} from "../common/data";
import {file2base64, getConfigUtil} from "../common/utils";
import {OB11MessageDataType, OB11GroupMemberRole, OB11Message, OB11MessageData, OB11Group, OB11GroupMember, OB11User} from "./types";
import { AtType, ChatType, Group, GroupMember, RawMessage, SelfInfo, User } from '../ntqqapi/types';
import { getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo } from '../common/data';
import {file2base64, getConfigUtil, log} from "../common/utils";
export class OB11Constructor {
@@ -9,7 +9,7 @@ export class OB11Constructor {
const {enableBase64} = getConfigUtil().getConfig()
const message_type = msg.chatType == ChatType.group ? "group" : "private";
const resMsg: OB11Message = {
self_id: selfInfo.user_id,
self_id: selfInfo.uin,
user_id: msg.senderUin,
time: parseInt(msg.msgTime) || 0,
message_id: msg.msgId,
@@ -36,7 +36,7 @@ export class OB11Constructor {
resMsg.sub_type = "friend"
const friend = await getFriend(msg.senderUin);
if (friend) {
resMsg.sender.nickname = friend.nickName;
resMsg.sender.nickname = friend.nick;
}
} else if (msg.chatType == ChatType.temp) {
resMsg.sub_type = "group"
@@ -119,16 +119,23 @@ export class OB11Constructor {
return resMsg;
}
static friend(friend: User): Friend{
static friend(friend: User): OB11User{
return {
user_id: friend.uin,
nickname: friend.nickName,
remark: friend.raw.remark
nickname: friend.nick,
remark: friend.remark
}
}
static friends(friends: User[]): Friend[]{
static selfInfo(selfInfo: SelfInfo): OB11User{
return {
user_id: selfInfo.uin,
nickname: selfInfo.nick
}
}
static friends(friends: User[]): OB11User[]{
return friends.map(OB11Constructor.friend)
}
@@ -150,6 +157,7 @@ export class OB11Constructor {
}
static groupMembers(group: Group): OB11GroupMember[]{
log("construct ob11 group members", group)
return group.members.map(m=>OB11Constructor.groupMember(group.groupCode, m))
}

331
src/onebot11/server.ts Normal file
View File

@@ -0,0 +1,331 @@
import { getConfigUtil, log } from "../common/utils";
const express = require("express");
import { Request } from 'express';
import { Response } from 'express';
const JSONbig = require('json-bigint')({ storeAsString: true });
import { AtType, ChatType, Group, SelfInfo } from "../ntqqapi/types";
import { friends, getGroup, getGroupMember, getStrangerByUin, groups, msgHistory, selfInfo } from "../common/data";
import { OB11ApiName, OB11Message, OB11Return, OB11MessageData, OB11Group, OB11GroupMember, OB11PostSendMsg, OB11MessageDataType, OB11User } from './types';
import { OB11Constructor } from "./constructor";
import { NTQQApi } from "../ntqqapi/ntcall";
import { Peer } from "../ntqqapi/ntcall";
import { SendMessageElement } from "../ntqqapi/types";
import { SendMsgElementConstructor } from "../ntqqapi/constructor";
import { uri2local } from "./utils";
import { v4 as uuid4 } from 'uuid';
// @SiberianHusky 2021-08-15
function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean {
const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
return pattern.test(uri);
}
for (let msg of sendMsgList) {
if (msg["type"] && msg["data"]) {
let type = msg["type"];
let data = msg["data"];
if (type === "text" && !data["text"]) {
return 400;
} else if (["image", "voice", "record"].includes(type)) {
if (!data["file"]) {
return 400;
} else {
if (checkUri(data["file"])) {
return 200;
} else {
return 400;
}
}
} else if (type === "at" && !data["qq"]) {
return 400;
} else if (type === "reply" && !data["id"]) {
return 400;
}
} else {
return 400
}
}
return 200;
}
// ==end==
class OB11Response {
static res<T>(data: T, status: number = 0, message: string = ""): OB11Return<T> {
return {
status: status,
retcode: status,
data: data,
message: message
}
}
static ok<T>(data: T) {
return OB11Response.res<T>(data)
}
static error(err: string) {
return OB11Response.res(null, -1, err)
}
}
const expressAPP = express();
expressAPP.use(express.urlencoded({ extended: true, limit: "500mb" }));
expressAPP.use((req, res, next) => {
let data = '';
req.on('data', chunk => {
data += chunk.toString();
});
req.on('end', () => {
if (data) {
try {
// log("receive raw", data)
req.body = JSONbig.parse(data);
} catch (e) {
return next(e);
}
}
next();
});
});
// expressAPP.use(express.json({
// limit: '500mb',
// verify: (req: any, res: any, buf: any, encoding: any) => {
// req.rawBody = buf;
// }
// }));
export function startExpress(port: number) {
expressAPP.get('/', (req: Request, res: Response) => {
res.send('llonebot已启动');
})
expressAPP.listen(port, "0.0.0.0", () => {
console.log(`llonebot started 0.0.0.0:${port}`);
});
}
export function postMsg(msg: OB11Message) {
const { reportSelfMessage } = getConfigUtil().getConfig()
if (!reportSelfMessage) {
if (msg.user_id == selfInfo.uin) {
return
}
}
for (const host of getConfigUtil().getConfig().hosts) {
fetch(host, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-self-id": selfInfo.uin
},
body: JSON.stringify(msg)
}).then((res: any) => {
log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg));
}, (err: any) => {
log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg));
});
}
}
let routers: Record<string, (payload: any) => Promise<OB11Return<any>>> = {};
function registerRouter<PayloadType, ReturnDataType>(action: OB11ApiName, handle: (payload: PayloadType) => Promise<OB11Return<ReturnDataType | null>>) {
let url = action.toString()
if (!action.startsWith("/")) {
url = "/" + action
}
async function _handle(res: Response, payload: PayloadType) {
log("receive post data", url, payload)
try {
const result = await handle(payload)
res.send(result)
}
catch (e) {
log(e.stack);
res.send(OB11Response.error(e.stack.toString()))
}
}
expressAPP.post(url, (req: Request, res: Response) => {
_handle(res, req.body).then()
});
expressAPP.get(url, (req: Request, res: Response) => {
_handle(res, req.query as any).then()
});
routers[url] = handle
}
registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => {
log("history msg ids", Object.keys(msgHistory));
const msg = msgHistory[payload.message_id.toString()]
if (msg) {
const msgData = await OB11Constructor.message(msg);
return OB11Response.ok(msgData)
} else {
return OB11Response.error("消息不存在")
}
})
registerRouter<{}, OB11User>("get_login_info", async (payload) => {
return OB11Response.ok(OB11Constructor.selfInfo(selfInfo));
})
registerRouter<{}, OB11User[]>("get_friend_list", async (payload) => {
return OB11Response.ok(OB11Constructor.friends(friends));
})
registerRouter<{}, OB11Group[]>("get_group_list", async (payload) => {
return OB11Response.ok(OB11Constructor.groups(groups));
})
registerRouter<{ group_id: number }, OB11Group[]>("get_group_info", async (payload) => {
const group = await getGroup(payload.group_id.toString())
if (group) {
return OB11Response.ok(OB11Constructor.groups(groups));
}
else {
return OB11Response.error(`${payload.group_id}不存在`)
}
})
registerRouter<{ group_id: number }, OB11GroupMember[]>("get_group_member_list", async (payload) => {
const group = await getGroup(payload.group_id.toString());
if (group) {
if (!group.members?.length){
group.members = await NTQQApi.getGroupMembers(payload.group_id.toString())
}
return OB11Response.ok(OB11Constructor.groupMembers(group));
}
else {
return OB11Response.error(`${payload.group_id}不存在`)
}
})
registerRouter<{ group_id: number, user_id: number }, OB11GroupMember>("get_group_member_info", async (payload) => {
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString())
if (member) {
return OB11Response.ok(OB11Constructor.groupMember(payload.group_id.toString(), member))
}
else {
return OB11Response.error(`群成员${payload.user_id}不存在`)
}
})
const handleSendMsg = async (payload) => {
const peer: Peer = {
chatType: ChatType.friend,
peerUid: ""
}
let group: Group | undefined = undefined;
if (payload?.group_id) {
group = await getGroup(payload.group_id.toString())
if (!group) {
return OB11Response.error(`${payload.group_id}不存在`)
}
peer.chatType = ChatType.group
// peer.name = group.name
peer.peerUid = group.groupCode
}
else if (payload?.user_id) {
const friend = friends.find(f => f.uin == payload.user_id.toString())
if (friend) {
// peer.name = friend.nickName
peer.peerUid = friend.uid
}
else {
peer.chatType = ChatType.temp
const tempUser = getStrangerByUin(payload.user_id.toString())
if (!tempUser) {
return OB11Response.error(`找不到私聊对象${payload.user_id}`)
}
// peer.name = tempUser.nickName
peer.peerUid = tempUser.uid
}
}
if (typeof payload.message === "string") {
payload.message = [{
type: OB11MessageDataType.text,
data: {
text: payload.message
}
}] as OB11MessageData[]
}
else if (!Array.isArray(payload.message)) {
payload.message = [payload.message]
}
const sendElements: SendMessageElement[] = []
for (let sendMsg of payload.message) {
switch (sendMsg.type) {
case OB11MessageDataType.text: {
const text = sendMsg.data?.text;
if (text) {
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
}
} break;
case OB11MessageDataType.at: {
let atQQ = sendMsg.data?.qq;
if (atQQ) {
atQQ = atQQ.toString()
if (atQQ === "all") {
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
}
else {
const atMember = group?.members.find(m => m.uin == atQQ)
if (atMember) {
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
}
}
}
} break;
case OB11MessageDataType.reply: {
let replyMsgId = sendMsg.data.id;
if (replyMsgId) {
replyMsgId = replyMsgId.toString()
const replyMsg = msgHistory[replyMsgId]
if (replyMsg) {
sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin))
}
}
} break;
case OB11MessageDataType.image: {
const file = sendMsg.data?.file
if (file) {
const picPath = await (await uri2local(uuid4(), file)).path
if (picPath) {
sendElements.push(await SendMsgElementConstructor.pic(picPath))
}
}
} break;
case OB11MessageDataType.voice: {
const file = sendMsg.data?.file
if (file) {
const voicePath = await (await uri2local(uuid4(), file)).path
if (voicePath) {
sendElements.push(await SendMsgElementConstructor.ptt(voicePath))
}
}
}
}
}
log("send msg:", peer, sendElements)
try {
const returnMsg = await NTQQApi.sendMsg(peer, sendElements)
return OB11Response.ok({ message_id: returnMsg.msgId })
} catch (e) {
return OB11Response.error(e.toString())
}
}
registerRouter<OB11PostSendMsg, { message_id: string }>("send_msg", handleSendMsg)
registerRouter<OB11PostSendMsg, { message_id: string }>("send_private_msg", handleSendMsg)
registerRouter<OB11PostSendMsg, { message_id: string }>("send_group_msg", handleSendMsg)

View File

@@ -1,9 +1,9 @@
import {SelfInfo} from "../common/types";
import { AtType } from "../ntqqapi/types";
import { RawMessage } from "../ntqqapi/types";
import { User } from "../ntqqapi/types";
export interface Friend extends SelfInfo{
export interface OB11User{
user_id: string;
nickname: string;
remark?: string
}

View File

@@ -1,63 +1,18 @@
// Electron 主进程 与 渲染进程 交互的桥梁
import {Config, SelfInfo} from "./common/types";
import { Group } from "./ntqqapi/types";
import { RawMessage } from "./ntqqapi/types";
import { User } from "./ntqqapi/types";
import {Config} from "./common/types";
import {
CHANNEL_DOWNLOAD_FILE,
CHANNEL_GET_CONFIG,
CHANNEL_SET_SELF_INFO,
CHANNEL_LOG,
CHANNEL_POST_ONEBOT_DATA,
CHANNEL_RECALL_MSG,
CHANNEL_SEND_MSG,
CHANNEL_SET_CONFIG,
CHANNEL_START_HTTP_SERVER,
CHANNEL_UPDATE_FRIENDS,
CHANNEL_UPDATE_GROUPS,
CHANNEL_DELETE_FILE,
CHANNEL_GET_RUNNING_STATUS,
CHANNEL_FILE2BASE64,
CHANNEL_GET_HISTORY_MSG,
CHANNEL_SEND_BACK_MSG,
} from "./common/channels";
import {OB11Return, OB11SendMsgReturn} from "./onebot11/types";
import { SendIPCMsgSession } from "./main/ipcsend";
const {contextBridge} = require("electron");
const {ipcRenderer} = require('electron');
// 在window对象下导出只读对象
contextBridge.exposeInMainWorld("llonebot", {
postData: (data: any) => {
ipcRenderer.send(CHANNEL_POST_ONEBOT_DATA, data);
},
updateGroups: (groups: Group[]) => {
ipcRenderer.send(CHANNEL_UPDATE_GROUPS, groups);
},
updateFriends: (friends: User[]) => {
ipcRenderer.send(CHANNEL_UPDATE_FRIENDS, friends);
},
sendSendMsgResult: (sessionId: string, msgResult: OB11SendMsgReturn)=>{
ipcRenderer.send(CHANNEL_SEND_BACK_MSG, {
id: sessionId,
data: msgResult,
});
},
listenRecallMessage: (handle: (jsonData: {message_id: string}) => void) => {
ipcRenderer.on(CHANNEL_RECALL_MSG, (event: any, args: {message_id: string}) => {
handle(args)
})
},
startExpress: () => {
ipcRenderer.send(CHANNEL_START_HTTP_SERVER);
},
log: (data: any) => {
ipcRenderer.send(CHANNEL_LOG, data);
},
@@ -67,20 +22,4 @@ contextBridge.exposeInMainWorld("llonebot", {
getConfig: async () => {
return ipcRenderer.invoke(CHANNEL_GET_CONFIG);
},
setSelfInfo(selfInfo: SelfInfo){
ipcRenderer.invoke(CHANNEL_SET_SELF_INFO, selfInfo)
},
downloadFile: (arg: {uri: string, localFilePath: string}) => {
return ipcRenderer.invoke(CHANNEL_DOWNLOAD_FILE, arg);
},
deleteFile: async (localFilePath: string[]) => {
ipcRenderer.send(CHANNEL_DELETE_FILE, localFilePath);
},
getRunningStatus: () => {
return ipcRenderer.invoke(CHANNEL_GET_RUNNING_STATUS);
},
getHistoryMsg: async (msgId: string):Promise<RawMessage> => {
return await ipcRenderer.invoke(CHANNEL_GET_HISTORY_MSG, msgId)
}
// startExpress,
});

View File

@@ -1,3 +1,5 @@
import { ipcRenderer } from 'electron';
import { CHANNEL_LOG } from './common/channels';
/// <reference path="./global.d.ts" />

View File

@@ -1,474 +0,0 @@
import {getConfigUtil, log} from "../common/utils";
const express = require("express");
import {Request, text} from 'express';
import {Response} from 'express';
const JSONbig = require('json-bigint')({storeAsString: true});
import {sendIPCRecallQQMsg, sendIPCSendQQMsg} from "../main/ipcsend";
import {AtType, ChatType, Group, SelfInfo} from "../common/types";
import {friends, getGroup, getGroupMember, getStrangerByUin, groups, msgHistory, selfInfo} from "../common/data";
import { OB11ApiName, OB11Message, OB11Return, OB11MessageData, OB11Group, OB11GroupMember, OB11PostSendMsg, OB11MessageDataType, Friend } from '../onebot11/types';
import {OB11Constructor} from "../onebot11/constructor";
import { NTQQApi } from "../ntqqapi/ntcall";
import { Peer } from "../ntqqapi/ntcall";
import { ElementType, SendMessageElement } from "../ntqqapi/types";
import { SendMsgElementConstructor } from "../ntqqapi/constructor";
import { uri2local } from "../onebot11/utils";
import { v4 as uuid4 } from 'uuid';
// @SiberianHusky 2021-08-15
function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean {
const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
return pattern.test(uri);
}
for (let msg of sendMsgList) {
if (msg["type"] && msg["data"]) {
let type = msg["type"];
let data = msg["data"];
if (type === "text" && !data["text"]) {
return 400;
} else if (["image", "voice", "record"].includes(type)) {
if (!data["file"]) {
return 400;
} else {
if (checkUri(data["file"])) {
return 200;
} else {
return 400;
}
}
} else if (type === "at" && !data["qq"]) {
return 400;
} else if (type === "reply" && !data["id"]) {
return 400;
}
} else {
return 400
}
}
return 200;
}
// ==end==
function constructReturnData<T>(data: T, status: number=0, message: string = ""): OB11Return<T> {
return {
status: status,
retcode: status,
data: data,
message: message
}
}
function constructErrorReturn(err: string){
return constructReturnData(null, -1, err);
}
function handlePost(jsonData: any, handleSendResult: (data: OB11Return<any>) => void) {
log("API receive post:" + JSON.stringify(jsonData))
if (!jsonData.params) {
jsonData.params = JSON.parse(JSON.stringify(jsonData));
delete jsonData.params.params;
}
let resData = {
status: 0,
retcode: 0,
data: {},
message: ''
}
if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") {
if (jsonData.action == "send_private_msg") {
jsonData.message_type = "private"
} else {
jsonData.message_type = "group"
}
// @SiberianHuskY 2021-10-20 22:00:00
resData.status = checkSendMessage(jsonData.message);
if (resData.status == 200) {
resData.message = "发送成功";
resData.data = jsonData.message;
sendIPCSendQQMsg(jsonData, handleSendResult);
return;
} else {
resData.message = "发送失败, 请检查消息格式";
resData.data = jsonData.message;
}
// == end ==
} else if (jsonData.action == "get_group_list") {
resData["data"] = groups.map(group => {
return {
group_id: group.uid,
group_name: group.name,
member_count: group.members?.length,
group_members: group.members?.map(member => {
return {
user_id: member.uin,
user_name: member.cardName || member.nick,
user_display_name: member.cardName || member.nick
}
})
}
})
} else if (jsonData.action == "get_group_info") {
let group = groups.find(group => group.uid == jsonData.params.group_id)
if (group) {
resData["data"] = {
group_id: group.uid,
group_name: group.name,
member_count: group.members?.length,
}
}
} else if (jsonData.action == "get_group_member_info") {
let member = groups.find(group => group.uid == jsonData.params.group_id)?.members?.find(member => member.uin == jsonData.params.user_id)
resData["data"] = {
user_id: member?.uin,
user_name: member?.nick,
user_display_name: member?.cardName || member?.nick,
nickname: member?.nick,
card: member?.cardName,
role: member && OB11Constructor.groupMemberRole(member.role),
}
} else if (jsonData.action == "get_group_member_list") {
let group = groups.find(group => group.uid == jsonData.params.group_id)
if (group) {
resData["data"] = group?.members?.map(member => {
return {
user_id: member.uin,
user_name: member.nick,
user_display_name: member.cardName || member.nick,
nickname: member.nick,
card: member.cardName,
role: OB11Constructor.groupMemberRole(member.role),
}
}) || []
} else {
resData["data"] = []
}
} else if (jsonData.action == "get_friend_list") {
resData["data"] = friends.map(friend => {
return {
user_id: friend.uin,
user_name: friend.nickName,
}
})
} else if (jsonData.action == "delete_msg") {
sendIPCRecallQQMsg(jsonData.message_id)
}
return resData
}
const expressAPP = express();
expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
expressAPP.use((req, res, next) => {
let data = '';
req.on('data', chunk => {
data += chunk.toString();
});
req.on('end', () => {
if (data) {
try {
// log("receive raw", data)
req.body = JSONbig.parse(data);
} catch (e) {
return next(e);
}
}
next();
});
});
// expressAPP.use(express.json({
// limit: '500mb',
// verify: (req: any, res: any, buf: any, encoding: any) => {
// req.rawBody = buf;
// }
// }));
export function startExpress(port: number) {
// function parseToOnebot12(action: OB11ApiName) {
// expressAPP.post('/' + action, (req: Request, res: Response) => {
// let jsonData: PostDataSendMsg = req.body;
// jsonData.action = action
// let resData = handlePost(jsonData, (data: OB11Return<any>) => {
// res.send(data)
// })
// if (resData) {
// res.send(resData)
// }
// });
// }
const actionList: OB11ApiName[] = ["get_login_info", "send_private_msg", "send_group_msg",
"get_group_list", "get_friend_list", "delete_msg", "get_group_member_list", "get_group_member_info"]
// for (const action of actionList) {
// parseToOnebot12(action as OB11ApiName)
// }
expressAPP.get('/', (req: Request, res: Response) => {
res.send('llonebot已启动');
})
// 处理POST请求的路由
// expressAPP.post('/', (req: Request, res: Response) => {
// let jsonData: PostDataSendMsg = req.body;
// let resData = handlePost(jsonData, (data: OB11Return<any>) => {
// res.send(data)
// })
// if (resData) {
// res.send(resData)
// }
// });
// expressAPP.post('/send_msg', (req: Request, res: Response) => {
// let jsonData: PostDataSendMsg = req.body;
// if (jsonData.message_type == "private") {
// jsonData.action = "send_private_msg"
// } else if (jsonData.message_type == "group") {
// jsonData.action = "send_group_msg"
// } else {
// if (jsonData.params?.group_id) {
// jsonData.action = "send_group_msg"
// } else {
// jsonData.action = "send_private_msg"
// }
// }
// let resData = handlePost(jsonData, (data: OB11Return<any>) => {
// res.send(data)
// })
// if (resData) {
// res.send(resData)
// }
// })
expressAPP.listen(port, "0.0.0.0", () => {
console.log(`llonebot started 0.0.0.0:${port}`);
});
}
export function postMsg(msg: OB11Message) {
const {reportSelfMessage} = getConfigUtil().getConfig()
if (!reportSelfMessage) {
if (msg.user_id == selfInfo.user_id) {
return
}
}
for (const host of getConfigUtil().getConfig().hosts) {
fetch(host, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-self-id": selfInfo.user_id
},
body: JSON.stringify(msg)
}).then((res: any) => {
log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg));
}, (err: any) => {
log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg));
});
}
}
let routers: Record<string, (payload: any)=>Promise<OB11Return<any>>> = {};
function registerRouter<PayloadType, ReturnDataType>(action: OB11ApiName, handle: (payload: PayloadType) => Promise<OB11Return<ReturnDataType | null>>) {
let url = action.toString()
if (!action.startsWith("/")){
url = "/" + action
}
async function _handle(res: Response, payload: PayloadType) {
log("receive post data", url, payload)
try{
const result = await handle(payload)
res.send(result)
}
catch(e){
log(e.stack);
res.send(constructErrorReturn(e.stack.toString()))
}
}
expressAPP.post(url, (req: Request, res: Response) => {
_handle(res, req.body).then()
});
expressAPP.get(url, (req: Request, res: Response) => {
_handle(res, req.query as any).then()
});
routers[url] = handle
}
registerRouter<{ message_id: string }, OB11Message>("get_msg", async (payload) => {
log("history msg ids", Object.keys(msgHistory));
const msg = msgHistory[payload.message_id.toString()]
if (msg) {
const msgData = await OB11Constructor.message(msg);
return constructReturnData(msgData)
} else {
return constructErrorReturn("消息不存在")
}
})
registerRouter<{}, SelfInfo>("get_login_info", async (payload)=>{
return constructReturnData(selfInfo);
})
registerRouter<{}, Friend[]>("get_friend_list", async (payload)=>{
return constructReturnData(OB11Constructor.friends(friends));
})
registerRouter<{}, OB11Group[]>("get_group_list", async (payload)=>{
return constructReturnData(OB11Constructor.groups(groups));
})
registerRouter<{group_id: number}, OB11Group[]>("get_group_info", async (payload)=>{
const group = await getGroup(payload.group_id.toString())
if (group){
return constructReturnData(OB11Constructor.groups(groups));
}
else{
return constructErrorReturn(`${payload.group_id}不存在`)
}
})
registerRouter<{group_id: number}, OB11GroupMember[]>("get_group_member_list", async (payload)=>{
const group = await getGroup(payload.group_id.toString());
if (group){
return constructReturnData(OB11Constructor.groupMembers(group));
}
else{
return constructErrorReturn(`${payload.group_id}不存在`)
}
})
registerRouter<{group_id: number, user_id: number}, OB11GroupMember>("get_group_member_info", async (payload)=>{
const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString())
if (member){
return constructReturnData(OB11Constructor.groupMember(payload.group_id.toString(), member))
}
else{
return constructErrorReturn(`群成员${payload.user_id}不存在`)
}
})
const handleSendMsg = async (payload)=>{
const peer: Peer = {
chatType: ChatType.friend,
peerUid: ""
}
let group: Group | undefined = undefined;
if(payload?.group_id){
group = groups.find(g=>g.uid == payload.group_id?.toString())
if (!group){
return constructErrorReturn(`${payload.group_id}不存在`)
}
peer.chatType = ChatType.group
// peer.name = group.name
peer.peerUid = group.uid
}
else if (payload?.user_id){
const friend = friends.find(f=>f.uin == payload.user_id.toString())
if (friend){
// peer.name = friend.nickName
peer.peerUid = friend.uid
}
else{
peer.chatType = ChatType.temp
const tempUser = getStrangerByUin(payload.user_id.toString())
if (!tempUser){
return constructErrorReturn(`找不到私聊对象${payload.user_id}`)
}
// peer.name = tempUser.nickName
peer.peerUid = tempUser.uid
}
}
if (typeof payload.message === "string"){
payload.message = [{
type: OB11MessageDataType.text,
data: {
text: payload.message
}
}] as OB11MessageData[]
}
else if (!Array.isArray(payload.message)){
payload.message = [payload.message]
}
const sendElements: SendMessageElement[] = []
for (let sendMsg of payload.message){
switch(sendMsg.type){
case OB11MessageDataType.text: {
const text = sendMsg.data?.text;
if (text){
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
}
}break;
case OB11MessageDataType.at: {
let atQQ = sendMsg.data?.qq;
if (atQQ){
atQQ = atQQ.toString()
if (atQQ === "all"){
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
}
else{
const atMember = group?.members.find(m=>m.uin == atQQ)
if (atMember){
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
}
}
}
}break;
case OB11MessageDataType.reply: {
let replyMsgId = sendMsg.data.id;
if (replyMsgId){
replyMsgId = replyMsgId.toString()
const replyMsg = msgHistory[replyMsgId]
if (replyMsg){
sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsgId, replyMsg.senderUin, replyMsg.senderUin))
}
}
}break;
case OB11MessageDataType.image: {
const file = sendMsg.data?.file
if (file){
const picPath = await (await uri2local(uuid4(), file)).path
if (picPath){
sendElements.push(await SendMsgElementConstructor.pic(picPath))
}
}
}break;
case OB11MessageDataType.voice: {
const file = sendMsg.data?.file
if (file){
const voicePath = await (await uri2local(uuid4(), file)).path
if (voicePath){
sendElements.push(await SendMsgElementConstructor.ptt(voicePath))
}
}
}
}
}
log("send msg:", peer, sendElements)
try{
const returnMsg = await NTQQApi.sendMsg(peer, sendElements)
return constructReturnData({message_id: returnMsg.msgId})
}catch(e){
return constructErrorReturn(e.toString())
}
}
registerRouter<OB11PostSendMsg, {message_id: string}>("send_msg", handleSendMsg)
registerRouter<OB11PostSendMsg, {message_id: string}>("send_private_msg", handleSendMsg)
registerRouter<OB11PostSendMsg, {message_id: string}>("send_group_msg", handleSendMsg)