feat: go-cqhttp api send_private_forward_msg & send_group_forward_msg

This commit is contained in:
linyuchen 2024-02-21 16:36:40 +08:00
parent 5ef221608c
commit e4508ea5c7
15 changed files with 155 additions and 103 deletions

View File

@ -91,6 +91,7 @@ export abstract class HttpServerBase {
if (method == "get"){
payload = req.query
}
log("收到http请求", url, payload);
try{
res.send(await handler(res, payload))
}catch (e) {

View File

@ -13,6 +13,23 @@ export function getConfigUtil() {
return new ConfigUtil(configFilePath)
}
function truncateString(obj: any, maxLength = 500) {
if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'string') {
// 如果是字符串且超过指定长度,则截断
if (obj[key].length > maxLength) {
obj[key] = obj[key].substring(0, maxLength) + '...';
}
} else if (typeof obj[key] === 'object') {
// 如果是对象或数组,则递归调用
truncateString(obj[key], maxLength);
}
});
}
return obj;
}
export function log(...msg: any[]) {
if (!getConfigUtil().getConfig().log) {
return
@ -28,7 +45,8 @@ export function log(...msg: any[]) {
for (let msgItem of msg) {
// 判断是否是对象
if (typeof msgItem === "object") {
logMsg += JSON.stringify(msgItem) + " ";
let obj = JSON.parse(JSON.stringify(msgItem));
logMsg += JSON.stringify(truncateString(obj)) + " ";
continue;
}
logMsg += msgItem + " ";

View File

@ -226,9 +226,9 @@ export class NTQQApi {
let members = Array.from(values) as GroupMember[]
for(const member of members){
uidMaps[member.uid] = member.uin;
// uidMaps[member.uid] = member.uin;
}
log(uidMaps);
// log(uidMaps);
// log("members info", values);
return members
} catch (e) {
@ -341,8 +341,8 @@ export class NTQQApi {
})
}
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false) {
const sendTimeout = 10 * 1000
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false, timeout=10000) {
const sendTimeout = timeout
return new Promise<RawMessage>((resolve, reject) => {
const peerUid = peer.peerUid;

View File

@ -1,6 +1,6 @@
import {ActionName, BaseCheckResult} from "./types"
import {OB11Response, OB11WebsocketResponse} from "./utils"
import {OB11Return, OB11WebsocketReturn} from "../types";
import {OB11Response} from "./utils"
import {OB11Return} from "../types";
class BaseAction<PayloadType, ReturnDataType> {
actionName: ActionName
@ -23,16 +23,16 @@ class BaseAction<PayloadType, ReturnDataType> {
}
}
public async websocketHandle(payload: PayloadType, echo: string): Promise<OB11WebsocketReturn<ReturnDataType | null>> {
public async websocketHandle(payload: PayloadType, echo: string): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload)
if (!result.valid) {
return OB11WebsocketResponse.error(result.message, 1400)
return OB11Response.error(result.message, 1400)
}
try {
const resData = await this._handle(payload)
return OB11WebsocketResponse.ok(resData, echo);
return OB11Response.ok(resData, echo);
} catch (e) {
return OB11WebsocketResponse.error(e.toString(), 1200)
return OB11Response.error(e.toString(), 1200, echo)
}
}

View File

@ -9,6 +9,7 @@ import {ActionName, BaseCheckResult} from "./types";
import * as fs from "fs";
import {log} from "../../common/utils";
import {v4 as uuidv4} from "uuid"
import {parseCQCode} from "../cqcode";
function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean {
@ -49,7 +50,7 @@ export interface ReturnDataType {
message_id: number
}
class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
actionName = ActionName.SendMsg
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
@ -115,14 +116,15 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
}
private convertMessage2List(message: OB11MessageMixType) {
protected convertMessage2List(message: OB11MessageMixType) {
if (typeof message === "string") {
message = [{
type: OB11MessageDataType.text,
data: {
text: message
}
}] as OB11MessageData[]
// message = [{
// type: OB11MessageDataType.text,
// data: {
// text: message
// }
// }] as OB11MessageData[]
message = parseCQCode(message.toString())
} else if (!Array.isArray(message)) {
message = [message]
}
@ -143,9 +145,8 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
peerUid: selfInfo.uid
}
let nodeIds: string[] = []
for (const messageNode of messageNodes) {
for (const messageNode of messageNodes){
// 一个node表示一个人的消息
let nodeId = messageNode.data.id;
// 有nodeId表示一个子转发消息卡片
if (nodeId) {
@ -153,14 +154,15 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} else {
// 自定义的消息
// 提取消息段发给自己生成消息id
const {
sendElements,
deleteAfterSentFiles
} = await this.createSendElements(this.convertMessage2List(messageNode.data.content), group)
try {
const {
sendElements,
deleteAfterSentFiles
} = await this.createSendElements(this.convertMessage2List(messageNode.data.content), group);
log("开始生成转发节点", sendElements);
const nodeMsg = await this.send(selfPeer, sendElements, deleteAfterSentFiles, true);
nodeIds.push(nodeMsg.msgId)
log("转发节点生成成功", nodeMsg.msgId);
} catch (e) {
log("生效转发消息节点失败", e)
}
@ -240,7 +242,8 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
}
}
} break;
}
break;
}
}
@ -255,7 +258,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (!sendElements.length) {
throw ("消息体无法解析")
}
const returnMsg = await NTQQApi.sendMsg(peer, sendElements, waitComplete)
const returnMsg = await NTQQApi.sendMsg(peer, sendElements, waitComplete, 20000);
addHistoryMsg(returnMsg)
deleteAfterSentFiles.map(f => fs.unlink(f, () => {
}))

View File

@ -0,0 +1,15 @@
import SendMsg, {ReturnDataType} from "../SendMsg";
import {OB11MessageMixType, OB11PostSendMsg} from "../../types";
import {ActionName, BaseCheckResult} from "../types";
export class GoCQHTTPSendGroupForwardMsg extends SendMsg{
actionName = ActionName.GoCQHTTP_SendGroupForwardMsg;
protected async check(payload: OB11PostSendMsg){
payload.message = this.convertMessage2List(payload.messages);
return super.check(payload);
}
}
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendGroupForwardMsg{
actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg;
}

View File

@ -14,6 +14,7 @@ import GetVersionInfo from "./GetVersionInfo";
import CanSendRecord from "./CanSendRecord";
import CanSendImage from "./CanSendImage";
import GetStatus from "./GetStatus";
import {GoCQHTTPSendGroupForwardMsg, GoCQHTTPSendPrivateForwardMsg} from "./go-cqhttp/SendForwardMsg";
export const actionHandlers = [
new GetMsg(),
@ -25,7 +26,12 @@ export const actionHandlers = [
new GetVersionInfo(),
new CanSendRecord(),
new CanSendImage(),
new GetStatus()
new GetStatus(),
//以下为go-cqhttp api
new GoCQHTTPSendGroupForwardMsg(),
new GoCQHTTPSendPrivateForwardMsg(),
]
function initActionMap() {

View File

@ -28,4 +28,7 @@ export enum ActionName {
GetStatus = "get_status",
CanSendRecord = "can_send_record",
CanSendImage = "can_send_image",
// 以下为go-cqhttp api
GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg",
GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg"
}

View File

@ -1,4 +1,4 @@
import {OB11Return, OB11WebsocketReturn} from '../types';
import {OB11Return} from '../types';
export class OB11Response {
static res<T>(data: T, status: string, retcode: number, message: string = ""): OB11Return<T> {
@ -6,31 +6,25 @@ export class OB11Response {
status: status,
retcode: retcode,
data: data,
message: message
message: message,
wording: message,
echo: ""
}
}
static ok<T>(data: T) {
return OB11Response.res<T>(data, "ok", 0)
}
static error(err: string, retcode: number) {
return OB11Response.res(null, "failed", retcode, err)
}
}
export class OB11WebsocketResponse {
static res<T>(data: T, status: string, retcode: number, echo: string, message: string = ""): OB11WebsocketReturn<T> {
return {
status: status,
retcode: retcode,
data: data,
echo: echo,
message: message
}
}
static ok<T>(data: T, echo: string = "") {
return OB11WebsocketResponse.res<T>(data, "ok", 0, echo)
let res = OB11Response.res<T>(data, "ok", 0)
if (echo) {
res.echo = echo;
}
return res;
}
static error(err: string, retcode: number, echo: string = "") {
return OB11WebsocketResponse.res(null, "failed", retcode, echo, err)
let res = OB11Response.res(null, "failed", retcode, err)
if (echo) {
res.echo = echo;
}
return res;
}
}

49
src/onebot11/cqcode.ts Normal file
View File

@ -0,0 +1,49 @@
import {OB11MessageData} from "./types";
const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)\]/
function unescape(source: string) {
return String(source)
.replace(/&#91;/g, '[')
.replace(/&#93;/g, ']')
.replace(/&#44;/g, ',')
.replace(/&amp;/g, '&')
}
function from(source: string) {
const capture = pattern.exec(source)
if (!capture) return null
const [, type, attrs] = capture
const data: Record<string, any> = {}
attrs && attrs.slice(1).split(',').forEach((str) => {
const index = str.indexOf('=')
data[str.slice(0, index)] = unescape(str.slice(index + 1))
})
return {type, data, capture}
}
function h(type: string, data: any) {
return {
type,
data,
}
}
export function parseCQCode(source: string): OB11MessageData[] {
const elements: any[] = []
let result: ReturnType<typeof from>
while ((result = from(source))) {
const {type, data, capture} = result
if (capture.index) {
elements.push(h('text', {text: unescape(source.slice(0, capture.index))}))
}
elements.push(h(type, data))
source = source.slice(capture.index + capture[0].length)
}
if (source) elements.push(h('text', {text: unescape(source)}))
return elements
}
// const result = parseCQCode("[CQ:at,qq=114514]早上好啊[CQ:image,file=http://baidu.com/1.jpg,type=show,id=40004]")
// const result = parseCQCode("好好好")
// console.log(JSON.stringify(result))

View File

@ -4,7 +4,7 @@ import * as WebSocket from "ws";
import {selfInfo} from "../../../common/data";
import {LifeCycleSubType, OB11LifeCycleEvent} from "../../event/meta/OB11LifeCycleEvent";
import {ActionName} from "../../action/types";
import {OB11WebsocketResponse} from "../../action/utils";
import {OB11Response} from "../../action/utils";
import BaseAction from "../../action/BaseAction";
import {actionMap} from "../../action";
import {registerWsEventSender, unregisterWsEventSender} from "../postevent";
@ -35,22 +35,22 @@ export class ReverseWebsocket {
public async onmessage(msg: string) {
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
let echo = ""
log("收到反向Websocket消息", msg.toString())
log("收到反向Websocket消息", msg)
try {
receiveData = JSON.parse(msg.toString())
echo = receiveData.echo
} catch (e) {
return wsReply(this.websocket, OB11WebsocketResponse.error("json解析失败请检查数据格式", 1400, echo))
return wsReply(this.websocket, OB11Response.error("json解析失败请检查数据格式", 1400, echo))
}
const action: BaseAction<any, any> = actionMap.get(receiveData.action);
if (!action) {
return wsReply(this.websocket, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
return wsReply(this.websocket, OB11Response.error("不支持的api " + receiveData.action, 1404, echo))
}
try {
let handleResult = await action.websocketHandle(receiveData.params, echo);
wsReply(this.websocket, handleResult)
} catch (e) {
wsReply(this.websocket, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
wsReply(this.websocket, OB11Response.error(`api处理出错:${e}`, 1200, echo))
}
}

View File

@ -1,7 +1,7 @@
import {WebSocket} from "ws";
import {getConfigUtil, log} from "../../../common/utils";
import {actionMap} from "../../action";
import {OB11WebsocketResponse} from "../../action/utils";
import {OB11Response} from "../../action/utils";
import {postWsEvent, registerWsEventSender, unregisterWsEventSender} from "../postevent";
import {ActionName} from "../../action/types";
import BaseAction from "../../action/BaseAction";
@ -15,19 +15,19 @@ let heartbeatRunning = false;
class OB11WebsocketServer extends WebsocketServerBase {
authorizeFailed(wsClient: WebSocket) {
wsClient.send(JSON.stringify(OB11WebsocketResponse.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) {
const action: BaseAction<any, any> = actionMap.get(actionName);
if (!action) {
return wsReply(wsClient, OB11WebsocketResponse.error("不支持的api " + actionName, 1404, echo))
return wsReply(wsClient, OB11Response.error("不支持的api " + actionName, 1404, echo))
}
try {
let handleResult = await action.websocketHandle(params, echo);
wsReply(wsClient, handleResult)
} catch (e) {
wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
wsReply(wsClient, OB11Response.error(`api处理出错:${e}`, 1200, echo))
}
}
@ -36,12 +36,12 @@ class OB11WebsocketServer extends WebsocketServerBase {
wsClient.on("message", async (msg) => {
let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
let echo = ""
log("收到正向Websocket消息", msg.toString())
log("收到正向Websocket消息", msg)
try {
receiveData = JSON.parse(msg.toString())
echo = receiveData.echo
} catch (e) {
return wsReply(wsClient, OB11WebsocketResponse.error("json解析失败请检查数据格式", 1400, echo))
return wsReply(wsClient, OB11Response.error("json解析失败请检查数据格式", 1400, echo))
}
this.handleAction(wsClient, receiveData.action, receiveData.params, receiveData.echo).then()
})

View File

@ -1,9 +1,9 @@
import * as websocket from "ws";
import {OB11WebsocketResponse} from "../../action/utils";
import {OB11Response} from "../../action/utils";
import {PostEventType} from "../postevent";
import {log} from "../../../common/utils";
export function wsReply(wsClient: websocket.WebSocket, data: OB11WebsocketResponse | PostEventType) {
export function wsReply(wsClient: websocket.WebSocket, data: OB11Response | PostEventType) {
try {
let packet = Object.assign({
}, data);

View File

@ -76,10 +76,8 @@ export interface OB11Return<DataType> {
retcode: number
data: DataType
message: string,
}
export interface OB11WebsocketReturn<DataType> extends OB11Return<DataType>{
echo: string
echo?: string, // ws调用api才有此字段
wording?: string, // go-cqhttp字段错误信息
}
export enum OB11MessageDataType {
@ -160,6 +158,7 @@ export interface OB11PostSendMsg {
user_id: string,
group_id?: string,
message: OB11MessageMixType;
messages?: OB11MessageMixType; // 兼容 go-cqhttp
}
export interface OB11Version {

View File

@ -60,40 +60,4 @@ export async function uri2local(fileName: string, uri: string){
res.success = true
res.path = filePath
return res
}
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;
}
}