mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
feat: go-cqhttp api send_private_forward_msg & send_group_forward_msg
This commit is contained in:
parent
5ef221608c
commit
e4508ea5c7
@ -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) {
|
||||
|
@ -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 + " ";
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, () => {
|
||||
}))
|
||||
|
15
src/onebot11/action/go-cqhttp/SendForwardMsg.ts
Normal file
15
src/onebot11/action/go-cqhttp/SendForwardMsg.ts
Normal 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;
|
||||
}
|
@ -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() {
|
||||
|
@ -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"
|
||||
}
|
@ -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
49
src/onebot11/cqcode.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {OB11MessageData} from "./types";
|
||||
|
||||
const pattern = /\[CQ:(\w+)((,\w+=[^,\]]*)*)\]/
|
||||
|
||||
function unescape(source: string) {
|
||||
return String(source)
|
||||
.replace(/[/g, '[')
|
||||
.replace(/]/g, ']')
|
||||
.replace(/,/g, ',')
|
||||
.replace(/&/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))
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user