mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
@@ -4,7 +4,7 @@
|
||||
"name": "LLOneBot",
|
||||
"slug": "LLOneBot",
|
||||
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发",
|
||||
"version": "3.31.6",
|
||||
"version": "3.31.7",
|
||||
"icon": "./icon.webp",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -22,7 +22,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"cosmokit": "^1.6.2",
|
||||
"express": "^4.19.2",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"fast-xml-parser": "^4.5.0",
|
||||
"file-type": "^19.4.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"minato": "^3.5.1",
|
||||
@@ -38,7 +38,7 @@
|
||||
"electron": "^31.4.0",
|
||||
"electron-vite": "^2.3.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.2",
|
||||
"vite": "^5.4.3",
|
||||
"vite-plugin-cp": "^4.0.8"
|
||||
},
|
||||
"packageManager": "yarn@4.4.1"
|
||||
|
@@ -1,25 +1,8 @@
|
||||
import fs from 'node:fs'
|
||||
import { Config, OB11Config } from './types'
|
||||
import path from 'node:path'
|
||||
import { Config, OB11Config } from './types'
|
||||
import { selfInfo, DATA_DIR } from './globalVars'
|
||||
|
||||
// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
|
||||
function mergeNewProperties(newObj: any, oldObj: any) {
|
||||
Object.keys(newObj).forEach((key) => {
|
||||
// 如果老对象不存在当前属性,则直接复制
|
||||
if (!oldObj.hasOwnProperty(key)) {
|
||||
oldObj[key] = newObj[key]
|
||||
} else {
|
||||
// 如果老对象和新对象的当前属性都是对象,则递归合并
|
||||
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
|
||||
mergeNewProperties(newObj[key], oldObj[key])
|
||||
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
|
||||
// 属性冲突,有一方不是对象,直接覆盖
|
||||
oldObj[key] = newObj[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
import { mergeNewProperties } from './utils/misc'
|
||||
|
||||
export class ConfigUtil {
|
||||
private readonly configPath: string
|
||||
@@ -50,7 +33,8 @@ export class ConfigUtil {
|
||||
enableWsReverse: false,
|
||||
messagePostFormat: 'array',
|
||||
enableHttpHeart: false,
|
||||
enableQOAutoQuote: false
|
||||
enableQOAutoQuote: false,
|
||||
listenLocalhost: false
|
||||
}
|
||||
const defaultConfig: Config = {
|
||||
enableLLOB: true,
|
||||
|
@@ -11,6 +11,7 @@ export interface OB11Config {
|
||||
messagePostFormat?: 'array' | 'string'
|
||||
enableHttpHeart?: boolean
|
||||
enableQOAutoQuote: boolean // 快速操作回复自动引用原消息
|
||||
listenLocalhost: boolean
|
||||
}
|
||||
|
||||
export interface CheckVersion {
|
||||
|
@@ -13,4 +13,22 @@ export function calcQQLevel(level: QQLevel) {
|
||||
export function getBuildVersion(): number {
|
||||
const version: string = globalThis.LiteLoader.versions.qqnt
|
||||
return +version.split('-')[1]
|
||||
}
|
||||
|
||||
/** 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象 */
|
||||
export function mergeNewProperties(newObj: any, oldObj: any) {
|
||||
Object.keys(newObj).forEach((key) => {
|
||||
// 如果老对象不存在当前属性,则直接复制
|
||||
if (!oldObj.hasOwnProperty(key)) {
|
||||
oldObj[key] = newObj[key]
|
||||
} else {
|
||||
// 如果老对象和新对象的当前属性都是对象,则递归合并
|
||||
if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
|
||||
mergeNewProperties(newObj[key], oldObj[key])
|
||||
} else if (typeof oldObj[key] === 'object' || typeof newObj[key] === 'object') {
|
||||
// 属性冲突,有一方不是对象,直接覆盖
|
||||
oldObj[key] = newObj[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@@ -272,4 +272,40 @@ export class NTQQMsgApi extends Service {
|
||||
return await invoke('nodeIKernelMsgService/getSingleMsg', [{ peer, msgSeq }, null])
|
||||
}
|
||||
}
|
||||
|
||||
async queryFirstMsgBySeq(peer: Peer, msgSeq: string) {
|
||||
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
|
||||
msgId: '0',
|
||||
msgTime: '0',
|
||||
msgSeq,
|
||||
params: {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid: [],
|
||||
filterMsgToTime: '0',
|
||||
filterMsgFromTime: '0',
|
||||
isReverseOrder: true,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
}
|
||||
}, null])
|
||||
}
|
||||
|
||||
async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[]) {
|
||||
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
|
||||
msgId: '0',
|
||||
msgTime: '0',
|
||||
msgSeq,
|
||||
params: {
|
||||
chatInfo: peer,
|
||||
filterMsgType: [],
|
||||
filterSendersUid,
|
||||
filterMsgToTime: filterMsgTime,
|
||||
filterMsgFromTime: filterMsgTime,
|
||||
isReverseOrder: true,
|
||||
isIncludeCurrent: true,
|
||||
pageLimit: 1,
|
||||
}
|
||||
}, null])
|
||||
}
|
||||
}
|
||||
|
@@ -334,4 +334,14 @@ export class NTQQUserApi extends Service {
|
||||
}
|
||||
return selfInfo.nick
|
||||
}
|
||||
|
||||
async setSelfStatus(status: number, extStatus: number, batteryStatus: number) {
|
||||
return await invoke('nodeIKernelMsgService/setStatus', [{
|
||||
statusReq: {
|
||||
status,
|
||||
extStatus,
|
||||
batteryStatus,
|
||||
}
|
||||
}, null])
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { RequestUtil } from '@/common/utils/request'
|
||||
import { Service, Context } from 'cordis'
|
||||
import { Dict } from 'cosmokit'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
@@ -49,56 +50,6 @@ interface WebApiGroupMemberRet {
|
||||
extmode: number
|
||||
}
|
||||
|
||||
export interface WebApiGroupNoticeFeed {
|
||||
u: number//发送者
|
||||
fid: string//fid
|
||||
pubt: number//时间
|
||||
msg: {
|
||||
text: string
|
||||
text_face: string
|
||||
title: string,
|
||||
pics?: {
|
||||
id: string,
|
||||
w: string,
|
||||
h: string
|
||||
}[]
|
||||
}
|
||||
type: number
|
||||
fn: number
|
||||
cn: number
|
||||
vn: number
|
||||
settings: {
|
||||
is_show_edit_card: number
|
||||
remind_ts: number
|
||||
tip_window_type: number
|
||||
confirm_required: number
|
||||
}
|
||||
read_num: number
|
||||
is_read: number
|
||||
is_all_confirm: number
|
||||
}
|
||||
|
||||
export interface WebApiGroupNoticeRet {
|
||||
ec: number
|
||||
em: string
|
||||
ltsm: number
|
||||
srv_code: number
|
||||
read_only: number
|
||||
role: number
|
||||
feeds: WebApiGroupNoticeFeed[]
|
||||
group: {
|
||||
group_id: number
|
||||
class_ext: number
|
||||
}
|
||||
sta: number,
|
||||
gln: number
|
||||
tst: number,
|
||||
ui: any
|
||||
server_time: number
|
||||
svrt: number
|
||||
ad: number
|
||||
}
|
||||
|
||||
interface GroupEssenceMsg {
|
||||
group_code: string
|
||||
msg_seq: number
|
||||
@@ -124,6 +75,30 @@ export interface GroupEssenceMsgRet {
|
||||
}
|
||||
}
|
||||
|
||||
interface SetGroupNoticeParams {
|
||||
groupCode: string
|
||||
content: string
|
||||
pinned: number
|
||||
type: number
|
||||
isShowEditCard: number
|
||||
tipWindowType: number
|
||||
confirmRequired: number
|
||||
picId: string
|
||||
imgWidth?: number
|
||||
imgHeight?: number
|
||||
}
|
||||
|
||||
interface SetGroupNoticeRet {
|
||||
ec: number
|
||||
em: string
|
||||
id: number
|
||||
ltsm: number
|
||||
new_fid: string
|
||||
read_only: number
|
||||
role: number
|
||||
srv_code: number
|
||||
}
|
||||
|
||||
export class NTQQWebApi extends Service {
|
||||
static inject = ['ntUserApi']
|
||||
|
||||
@@ -134,7 +109,7 @@ export class NTQQWebApi extends Service {
|
||||
async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
|
||||
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>()
|
||||
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
|
||||
const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
|
||||
const cookieStr = this.cookieToString(cookieObject)
|
||||
const retList: Promise<WebApiGroupMemberRet>[] = []
|
||||
const params = new URLSearchParams({
|
||||
st: '0',
|
||||
@@ -173,49 +148,47 @@ export class NTQQWebApi extends Service {
|
||||
}
|
||||
|
||||
genBkn(sKey: string) {
|
||||
sKey = sKey || '';
|
||||
let hash = 5381;
|
||||
|
||||
sKey = sKey || ''
|
||||
let hash = 5381
|
||||
for (let i = 0; i < sKey.length; i++) {
|
||||
const code = sKey.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
const code = sKey.charCodeAt(i)
|
||||
hash = hash + (hash << 5) + code
|
||||
}
|
||||
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
return (hash & 0x7FFFFFFF).toString()
|
||||
}
|
||||
|
||||
//实现未缓存 考虑2h缓存
|
||||
async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||
const getDataInternal = async (Internal_groupCode: string, Internal_type: number) => {
|
||||
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString();
|
||||
let res = '';
|
||||
let resJson;
|
||||
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString()
|
||||
let res = ''
|
||||
let resJson
|
||||
try {
|
||||
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr });
|
||||
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
|
||||
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': cookieStr })
|
||||
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/)
|
||||
if (match) {
|
||||
resJson = JSON.parse(match[1].trim());
|
||||
resJson = JSON.parse(match[1].trim())
|
||||
}
|
||||
if (Internal_type === 1) {
|
||||
return resJson?.talkativeList;
|
||||
return resJson?.talkativeList
|
||||
} else {
|
||||
return resJson?.actorList;
|
||||
return resJson?.actorList
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('获取当前群荣耀失败', url, e);
|
||||
this.ctx.logger.error('获取当前群荣耀失败', url, e)
|
||||
}
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
let HonorInfo: any = { group_id: groupCode };
|
||||
let HonorInfo: any = { group_id: groupCode }
|
||||
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
|
||||
const cookieStr = Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
|
||||
const cookieStr = this.cookieToString(cookieObject)
|
||||
|
||||
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 1);
|
||||
let RetInternal = await getDataInternal(groupCode, 1)
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取龙王信息失败');
|
||||
throw new Error('获取龙王信息失败')
|
||||
}
|
||||
HonorInfo.current_talkative = {
|
||||
user_id: RetInternal[0]?.uin,
|
||||
@@ -232,17 +205,17 @@ export class NTQQWebApi extends Service {
|
||||
description: talkative_ele?.desc,
|
||||
day_count: 0,
|
||||
nickname: talkative_ele?.name
|
||||
});
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.logger.error(e);
|
||||
this.ctx.logger.error(e)
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 2);
|
||||
let RetInternal = await getDataInternal(groupCode, 2)
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊之火失败');
|
||||
throw new Error('获取群聊之火失败')
|
||||
}
|
||||
HonorInfo.performer_list = [];
|
||||
for (const performer_ele of RetInternal) {
|
||||
@@ -251,54 +224,86 @@ export class NTQQWebApi extends Service {
|
||||
nickname: performer_ele?.name,
|
||||
avatar: performer_ele?.avatar,
|
||||
description: performer_ele?.desc
|
||||
});
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.logger.error(e);
|
||||
this.ctx.logger.error(e)
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 3);
|
||||
let RetInternal = await getDataInternal(groupCode, 3)
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取群聊炽焰失败');
|
||||
throw new Error('获取群聊炽焰失败')
|
||||
}
|
||||
HonorInfo.legend_list = [];
|
||||
HonorInfo.legend_list = []
|
||||
for (const legend_ele of RetInternal) {
|
||||
HonorInfo.legend_list.push({
|
||||
user_id: legend_ele?.uin,
|
||||
nickname: legend_ele?.name,
|
||||
avatar: legend_ele?.avatar,
|
||||
desc: legend_ele?.description
|
||||
});
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('获取群聊炽焰失败', e);
|
||||
this.ctx.logger.error('获取群聊炽焰失败', e)
|
||||
}
|
||||
}
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
try {
|
||||
let RetInternal = await getDataInternal(groupCode, 6);
|
||||
let RetInternal = await getDataInternal(groupCode, 6)
|
||||
if (!RetInternal) {
|
||||
throw new Error('获取快乐源泉失败');
|
||||
throw new Error('获取快乐源泉失败')
|
||||
}
|
||||
HonorInfo.emotion_list = [];
|
||||
HonorInfo.emotion_list = []
|
||||
for (const emotion_ele of RetInternal) {
|
||||
HonorInfo.emotion_list.push({
|
||||
user_id: emotion_ele?.uin,
|
||||
nickname: emotion_ele?.name,
|
||||
avatar: emotion_ele?.avatar,
|
||||
desc: emotion_ele?.description
|
||||
});
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.logger.error('获取快乐源泉失败', e);
|
||||
this.ctx.logger.error('获取快乐源泉失败', e)
|
||||
}
|
||||
}
|
||||
//冒尖小春笋好像已经被tx扬了
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
HonorInfo.strong_newbie_list = [];
|
||||
HonorInfo.strong_newbie_list = []
|
||||
}
|
||||
return HonorInfo;
|
||||
return HonorInfo
|
||||
}
|
||||
|
||||
async setGroupNotice(params: SetGroupNoticeParams): Promise<SetGroupNoticeRet> {
|
||||
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
|
||||
const settings = JSON.stringify({
|
||||
is_show_edit_card: params.isShowEditCard,
|
||||
tip_window_type: params.tipWindowType,
|
||||
confirm_required: params.confirmRequired
|
||||
})
|
||||
|
||||
return await RequestUtil.HttpGetJson<SetGroupNoticeRet>(
|
||||
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||
bkn: this.genBkn(cookieObject.skey),
|
||||
qid: params.groupCode,
|
||||
text: params.content,
|
||||
pinned: params.pinned.toString(),
|
||||
type: params.type.toString(),
|
||||
settings: settings,
|
||||
...(params.picId !== '' && {
|
||||
pic: params.picId,
|
||||
imgWidth: params.imgWidth?.toString(),
|
||||
imgHeight: params.imgHeight?.toString(),
|
||||
})
|
||||
})}`,
|
||||
'POST',
|
||||
'',
|
||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||
)
|
||||
}
|
||||
|
||||
private cookieToString(cookieObject: Dict) {
|
||||
return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
|
||||
}
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ declare module 'cordis' {
|
||||
}
|
||||
|
||||
class Core extends Service {
|
||||
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi']
|
||||
static inject = ['ntMsgApi', 'ntFriendApi', 'ntGroupApi']
|
||||
|
||||
constructor(protected ctx: Context, public config: Core.Config) {
|
||||
super(ctx, 'app', true)
|
||||
|
@@ -26,7 +26,7 @@ export const ReceiveCmdS = {
|
||||
CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan',
|
||||
MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete',
|
||||
SKEY_UPDATE: 'onSkeyUpdate',
|
||||
}
|
||||
} as const
|
||||
|
||||
export type ReceiveCmd = string
|
||||
|
||||
|
@@ -36,6 +36,7 @@ export interface Group {
|
||||
memberUid: string //"u_fbf8N7aeuZEnUiJAbQ9R8Q"
|
||||
}
|
||||
members: GroupMember[] // 原始数据是没有这个的,为了方便自己加了这个字段
|
||||
createTime: string
|
||||
}
|
||||
|
||||
export enum GroupMemberRole {
|
||||
|
@@ -480,6 +480,8 @@ export interface RawMessage {
|
||||
sourceMsgIsIncPic: boolean // 原消息是否有图片
|
||||
sourceMsgText: string
|
||||
replayMsgSeq: string // 源消息的msgSeq,可以通过这个找到源消息的msgId
|
||||
senderUidStr: string
|
||||
replyMsgTime: string
|
||||
}
|
||||
textElement: {
|
||||
atType: AtType
|
||||
|
37
src/onebot11/action/go-cqhttp/SendGroupNotice.ts
Normal file
37
src/onebot11/action/go-cqhttp/SendGroupNotice.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface Payload {
|
||||
group_id: number | string
|
||||
content: string
|
||||
image?: string
|
||||
pinned?: number | string //扩展
|
||||
confirm_required?: number | string //扩展
|
||||
}
|
||||
|
||||
export class SendGroupNotice extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.GoCQHTTP_SendGroupNotice
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const type = 1
|
||||
const isShowEditCard = 0
|
||||
const tipWindowType = 0
|
||||
const pinned = Number(payload.pinned ?? 0)
|
||||
const confirmRequired = Number(payload.confirm_required ?? 1)
|
||||
|
||||
const result = await this.ctx.ntWebApi.setGroupNotice({
|
||||
groupCode: payload.group_id.toString(),
|
||||
content: payload.content,
|
||||
pinned,
|
||||
type,
|
||||
isShowEditCard,
|
||||
tipWindowType,
|
||||
confirmRequired,
|
||||
picId: ''
|
||||
})
|
||||
if (result.ec !== 0) {
|
||||
throw new Error(`设置群公告失败, 错误信息: ${result.em}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@@ -2,24 +2,22 @@ import fs from 'node:fs'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
import { SendElementEntities } from '@/ntqqapi/entities'
|
||||
import { ChatType, SendFileElement } from '@/ntqqapi/types'
|
||||
import { SendFileElement } from '@/ntqqapi/types'
|
||||
import { uri2local } from '@/common/utils'
|
||||
import { Peer } from '@/ntqqapi/types'
|
||||
import { sendMsg } from '../../helper/createMessage'
|
||||
import { sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
|
||||
|
||||
interface Payload {
|
||||
user_id: number | string
|
||||
group_id?: number | string
|
||||
interface UploadGroupFilePayload {
|
||||
group_id: number | string
|
||||
file: string
|
||||
name: string
|
||||
folder?: string
|
||||
folder_id?: string
|
||||
}
|
||||
|
||||
export class UploadGroupFile extends BaseAction<Payload, null> {
|
||||
export class UploadGroupFile extends BaseAction<UploadGroupFilePayload, null> {
|
||||
actionName = ActionName.GoCQHTTP_UploadGroupFile
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
protected async _handle(payload: UploadGroupFilePayload): Promise<null> {
|
||||
let file = payload.file
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`
|
||||
@@ -29,31 +27,23 @@ export class UploadGroupFile extends BaseAction<Payload, null> {
|
||||
throw new Error(downloadResult.errMsg)
|
||||
}
|
||||
const sendFileEle = await SendElementEntities.file(this.ctx, downloadResult.path, payload.name, payload.folder_id)
|
||||
await sendMsg(this.ctx, {
|
||||
chatType: ChatType.group,
|
||||
peerUid: payload.group_id?.toString()!,
|
||||
}, [sendFileEle], [], true)
|
||||
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Group)
|
||||
await sendMsg(this.ctx, peer, [sendFileEle], [], true)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export class UploadPrivateFile extends BaseAction<Payload, null> {
|
||||
interface UploadPrivateFilePayload {
|
||||
user_id: number | string
|
||||
file: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export class UploadPrivateFile extends BaseAction<UploadPrivateFilePayload, null> {
|
||||
actionName = ActionName.GoCQHTTP_UploadPrivateFile
|
||||
|
||||
async getPeer(payload: Payload): Promise<Peer> {
|
||||
if (payload.user_id) {
|
||||
const peerUid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
|
||||
if (!peerUid) {
|
||||
throw `私聊${payload.user_id}不存在`
|
||||
}
|
||||
const isBuddy = await this.ctx.ntFriendApi.isBuddy(peerUid)
|
||||
return { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid }
|
||||
}
|
||||
throw '缺少参数 user_id'
|
||||
}
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
const peer = await this.getPeer(payload)
|
||||
protected async _handle(payload: UploadPrivateFilePayload): Promise<null> {
|
||||
const peer = await createPeer(this.ctx, payload, CreatePeerMode.Private)
|
||||
let file = payload.file
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`
|
||||
|
@@ -60,6 +60,8 @@ import { CreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder'
|
||||
import { DelGroupFolder } from './go-cqhttp/DelGroupFolder'
|
||||
import { GetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain'
|
||||
import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles'
|
||||
import { SetOnlineStatus } from './llonebot/SetOnlineStatus'
|
||||
import { SendGroupNotice } from './go-cqhttp/SendGroupNotice'
|
||||
|
||||
export function initActionMap(adapter: Adapter) {
|
||||
const actionHandlers = [
|
||||
@@ -71,6 +73,7 @@ export function initActionMap(adapter: Adapter) {
|
||||
new SetQQAvatar(adapter),
|
||||
new GetFriendWithCategory(adapter),
|
||||
new GetEvent(adapter),
|
||||
new SetOnlineStatus(adapter),
|
||||
// onebot11
|
||||
new SendLike(adapter),
|
||||
new GetMsg(adapter),
|
||||
@@ -126,7 +129,8 @@ export function initActionMap(adapter: Adapter) {
|
||||
new CreateGroupFileFolder(adapter),
|
||||
new DelGroupFolder(adapter),
|
||||
new GetGroupAtAllRemain(adapter),
|
||||
new GetGroupRootFiles(adapter)
|
||||
new GetGroupRootFiles(adapter),
|
||||
new SendGroupNotice(adapter)
|
||||
]
|
||||
const actionMap = new Map<string, BaseAction<any, any>>()
|
||||
for (const action of actionHandlers) {
|
||||
|
25
src/onebot11/action/llonebot/SetOnlineStatus.ts
Normal file
25
src/onebot11/action/llonebot/SetOnlineStatus.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
|
||||
interface Payload {
|
||||
status: number | string
|
||||
ext_status: number | string
|
||||
battery_status: number | string
|
||||
}
|
||||
|
||||
export class SetOnlineStatus extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.SetOnlineStatus
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const ret = await this.ctx.ntUserApi.setSelfStatus(
|
||||
Number(payload.status),
|
||||
Number(payload.ext_status),
|
||||
Number(payload.battery_status),
|
||||
)
|
||||
if (ret.result !== 0) {
|
||||
this.ctx.logger.error(ret)
|
||||
throw new Error('设置在线状态失败')
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@@ -21,45 +21,15 @@ import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostD
|
||||
import { Peer } from '@/ntqqapi/types/msg'
|
||||
import { MessageUnique } from '@/common/utils/messageUnique'
|
||||
import { selfInfo } from '@/common/globalVars'
|
||||
import { convertMessage2List, createSendElements, sendMsg } from '../../helper/createMessage'
|
||||
import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
|
||||
|
||||
export interface ReturnDataType {
|
||||
interface ReturnData {
|
||||
message_id: number
|
||||
}
|
||||
|
||||
export enum ContextMode {
|
||||
Normal = 0,
|
||||
Private = 1,
|
||||
Group = 2
|
||||
}
|
||||
|
||||
export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnData> {
|
||||
actionName = ActionName.SendMsg
|
||||
|
||||
private async createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
|
||||
// This function determines the type of message by the existence of user_id / group_id,
|
||||
// not message_type.
|
||||
// This redundant design of Ob11 here should be blamed.
|
||||
|
||||
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
|
||||
return {
|
||||
chatType: ChatType.group,
|
||||
peerUid: payload.group_id.toString(),
|
||||
}
|
||||
}
|
||||
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
|
||||
const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString())
|
||||
if (!uid) throw new Error('无法获取用户信息')
|
||||
const isBuddy = await this.ctx.ntFriendApi.isBuddy(uid)
|
||||
return {
|
||||
chatType: isBuddy ? ChatType.friend : ChatType.temp,
|
||||
peerUid: uid,
|
||||
guildId: isBuddy ? '' : payload.group_id?.toString() || ''
|
||||
}
|
||||
}
|
||||
throw new Error('请指定 group_id 或 user_id')
|
||||
}
|
||||
|
||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||
const messages = convertMessage2List(payload.message)
|
||||
const fmNum = this.getSpecialMsgNum(messages, OB11MessageDataType.node)
|
||||
@@ -84,13 +54,13 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
|
||||
protected async _handle(payload: OB11PostSendMsg) {
|
||||
let contextMode = ContextMode.Normal
|
||||
let contextMode = CreatePeerMode.Normal
|
||||
if (payload.message_type === 'group') {
|
||||
contextMode = ContextMode.Group
|
||||
contextMode = CreatePeerMode.Group
|
||||
} else if (payload.message_type === 'private') {
|
||||
contextMode = ContextMode.Private
|
||||
contextMode = CreatePeerMode.Private
|
||||
}
|
||||
const peer = await this.createContext(payload, contextMode)
|
||||
const peer = await createPeer(this.ctx, payload, contextMode)
|
||||
const messages = convertMessage2List(
|
||||
payload.message,
|
||||
payload.auto_escape === true || payload.auto_escape === 'true',
|
||||
|
@@ -23,6 +23,7 @@ export enum ActionName {
|
||||
GetFile = 'get_file',
|
||||
GetFriendsWithCategory = 'get_friends_with_category',
|
||||
GetEvent = 'get_event',
|
||||
SetOnlineStatus = 'set_online_status',
|
||||
// onebot 11
|
||||
SendLike = 'send_like',
|
||||
GetLoginInfo = 'get_login_info',
|
||||
@@ -78,5 +79,6 @@ export enum ActionName {
|
||||
GoCQHTTP_CreateGroupFileFolder = 'create_group_file_folder',
|
||||
GoCQHTTP_DelGroupFolder = 'delete_group_folder',
|
||||
GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
|
||||
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files'
|
||||
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
|
||||
GoCQHTTP_SendGroupNotice = '_send_group_notice',
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ declare module 'cordis' {
|
||||
}
|
||||
|
||||
class OneBot11Adapter extends Service {
|
||||
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi']
|
||||
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi', 'ntWebApi']
|
||||
|
||||
public messages: Map<string, RawMessage> = new Map()
|
||||
public startTime = 0
|
||||
@@ -50,7 +50,8 @@ class OneBot11Adapter extends Service {
|
||||
this.ob11Http = new OB11Http(ctx, {
|
||||
port: config.httpPort,
|
||||
token: config.token,
|
||||
actionMap
|
||||
actionMap,
|
||||
listenLocalhost: config.listenLocalhost
|
||||
})
|
||||
this.ob11HttpPost = new OB11HttpPost(ctx, {
|
||||
hosts: config.httpHosts,
|
||||
@@ -62,7 +63,8 @@ class OneBot11Adapter extends Service {
|
||||
port: config.wsPort,
|
||||
heartInterval: config.heartInterval,
|
||||
token: config.token,
|
||||
actionMap
|
||||
actionMap,
|
||||
listenLocalhost: config.listenLocalhost
|
||||
})
|
||||
this.ob11WebSocketReverseManager = new OB11WebSocketReverseManager(ctx, {
|
||||
hosts: config.wsHosts,
|
||||
@@ -110,10 +112,10 @@ class OneBot11Adapter extends Service {
|
||||
for (const notify of notifies) {
|
||||
try {
|
||||
const notifyTime = parseInt(notify.seq) / 1000
|
||||
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
|
||||
if (notifyTime < this.startTime) {
|
||||
continue
|
||||
}
|
||||
const flag = notify.group.groupCode + '|' + notify.seq + '|' + notify.type
|
||||
if ([GroupNotifyType.MEMBER_LEAVE_NOTIFY_ADMIN, GroupNotifyType.KICK_MEMBER_NOTIFY_ADMIN].includes(notify.type)) {
|
||||
this.ctx.logger.info('有成员退出通知', notify)
|
||||
const member1Uin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
|
||||
@@ -292,7 +294,7 @@ class OneBot11Adapter extends Service {
|
||||
}
|
||||
}
|
||||
// HTTP 端口变化,重启服务
|
||||
if (config.ob11.httpPort !== old.httpPort) {
|
||||
if ((config.ob11.httpPort !== old.httpPort || config.ob11.listenLocalhost !== old.listenLocalhost) && config.ob11.enableHttp) {
|
||||
await this.ob11Http.stop()
|
||||
this.ob11Http.start()
|
||||
}
|
||||
@@ -305,7 +307,7 @@ class OneBot11Adapter extends Service {
|
||||
}
|
||||
}
|
||||
// 正向 WebSocket 端口变化,重启服务
|
||||
if (config.ob11.wsPort !== old.wsPort) {
|
||||
if ((config.ob11.wsPort !== old.wsPort || config.ob11.listenLocalhost !== old.listenLocalhost) && config.ob11.enableWs) {
|
||||
await this.ob11WebSocket.stop()
|
||||
this.ob11WebSocket.start()
|
||||
llonebotError.wsServerError = ''
|
||||
|
@@ -51,8 +51,9 @@ class OB11Http {
|
||||
this.expressAPP.get('/', (req: Request, res: Response) => {
|
||||
res.send(`LLOneBot server 已启动`)
|
||||
})
|
||||
this.server = this.expressAPP.listen(this.config.port, '0.0.0.0', () => {
|
||||
this.ctx.logger.info(`HTTP server started 0.0.0.0:${this.config.port}`)
|
||||
const host = this.config.listenLocalhost ? '127.0.0.1' : '0.0.0.0'
|
||||
this.server = this.expressAPP.listen(this.config.port, host, () => {
|
||||
this.ctx.logger.info(`HTTP server started ${host}:${this.config.port}`)
|
||||
})
|
||||
llonebotError.httpServerError = ''
|
||||
} catch (e: any) {
|
||||
@@ -136,6 +137,7 @@ namespace OB11Http {
|
||||
port: number
|
||||
token?: string
|
||||
actionMap: Map<string, BaseAction<any, any>>
|
||||
listenLocalhost: boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,9 +21,14 @@ class OB11WebSocket {
|
||||
|
||||
public start() {
|
||||
if (this.wsServer) return
|
||||
this.ctx.logger.info(`WebSocket server started 0.0.0.0:${this.config.port}`)
|
||||
const host = this.config.listenLocalhost ? '127.0.0.1' : '0.0.0.0'
|
||||
this.ctx.logger.info(`WebSocket server started ${host}:${this.config.port}`)
|
||||
try {
|
||||
this.wsServer = new WebSocketServer({ port: this.config.port, maxPayload: 1024 * 1024 * 1024 })
|
||||
this.wsServer = new WebSocketServer({
|
||||
host,
|
||||
port: this.config.port,
|
||||
maxPayload: 1024 * 1024 * 1024
|
||||
})
|
||||
llonebotError.wsServerError = ''
|
||||
} catch (e: any) {
|
||||
llonebotError.wsServerError = '正向 WebSocket 服务启动失败, ' + e.toString()
|
||||
@@ -165,6 +170,7 @@ namespace OB11WebSocket {
|
||||
heartInterval: number
|
||||
token?: string
|
||||
actionMap: Map<string, BaseAction<any, any>>
|
||||
listenLocalhost: boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -43,7 +43,7 @@ import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNotice
|
||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'
|
||||
import { OB11BaseNoticeEvent } from './event/notice/OB11BaseNoticeEvent'
|
||||
import { OB11GroupEssenceEvent } from './event/notice/OB11GroupEssenceEvent'
|
||||
import { omit, isNullable } from 'cosmokit'
|
||||
import { omit, isNullable, pick } from 'cosmokit'
|
||||
import { Context } from 'cordis'
|
||||
import { selfInfo } from '@/common/globalVars'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
@@ -154,15 +154,18 @@ export namespace OB11Entities {
|
||||
guildId: ''
|
||||
}
|
||||
try {
|
||||
const { replayMsgSeq, replyMsgTime, senderUidStr } = replyElement
|
||||
const records = msg.records.find(msgRecord => msgRecord.msgId === replyElement.sourceMsgIdInRecords)
|
||||
if (!records) throw new Error('找不到回复消息')
|
||||
let replyMsg = (await ctx.ntMsgApi.getMsgsBySeqAndCount(peer, replyElement.replayMsgSeq, 1, true, true)).msgList[0]
|
||||
if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
|
||||
replyMsg = (await ctx.ntMsgApi.getSingleMsg(peer, replyElement.replayMsgSeq)).msgList[0]
|
||||
if (!records || !replyMsgTime || !senderUidStr) {
|
||||
throw new Error('找不到回复消息')
|
||||
}
|
||||
const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUidStr])
|
||||
const replyMsg = msgList.find(msg => msg.msgRandom === records.msgRandom)
|
||||
|
||||
// 284840486: 合并消息内侧 消息具体定位不到
|
||||
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
|
||||
throw new Error('回复消息消息验证失败')
|
||||
if (!replyMsg && msg.peerUin !== '284840486') {
|
||||
ctx.logger.info('queryMsgs', msgList.map(e => pick(e, ['msgSeq', 'msgRandom'])))
|
||||
throw new Error('回复消息验证失败')
|
||||
}
|
||||
messageSegment = {
|
||||
type: OB11MessageDataType.reply,
|
||||
@@ -171,7 +174,7 @@ export namespace OB11Entities {
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
ctx.logger.error('获取不到引用的消息', replyElement.replayMsgSeq, e.stack)
|
||||
ctx.logger.error('获取不到引用的消息', replyElement, e.stack)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -518,29 +521,27 @@ export namespace OB11Entities {
|
||||
}).parse(xmlElement.content)
|
||||
ctx.logger.info('收到表情回应我的消息', emojiLikeData)
|
||||
try {
|
||||
const senderUin = emojiLikeData.gtip.qq.jp
|
||||
const msgSeq = emojiLikeData.gtip.url.msgseq
|
||||
const emojiId = emojiLikeData.gtip.face.id
|
||||
const replyMsgList = (await ctx.ntMsgApi.getMsgsBySeqAndCount({
|
||||
const senderUin: string = emojiLikeData.gtip.qq.jp
|
||||
const msgSeq: string = emojiLikeData.gtip.url.msgseq
|
||||
const emojiId: string = emojiLikeData.gtip.face.id
|
||||
const peer = {
|
||||
chatType: ChatType.group,
|
||||
guildId: '',
|
||||
peerUid: msg.peerUid,
|
||||
}, msgSeq, 1, true, true))?.msgList
|
||||
}
|
||||
const replyMsgList = (await ctx.ntMsgApi.queryFirstMsgBySeq(peer, msgSeq)).msgList
|
||||
if (!replyMsgList?.length) {
|
||||
return
|
||||
}
|
||||
const likes = [
|
||||
{
|
||||
emoji_id: emojiId,
|
||||
count: 1,
|
||||
},
|
||||
]
|
||||
const shortId = MessageUnique.getShortIdByMsgId(replyMsgList[0].msgId)
|
||||
return new OB11GroupMsgEmojiLikeEvent(
|
||||
parseInt(msg.peerUid),
|
||||
parseInt(senderUin),
|
||||
shortId!,
|
||||
likes
|
||||
[{
|
||||
emoji_id: emojiId,
|
||||
count: 1,
|
||||
}]
|
||||
)
|
||||
} catch (e: any) {
|
||||
ctx.logger.error('解析表情回应消息失败', e.stack)
|
||||
@@ -768,6 +769,8 @@ export namespace OB11Entities {
|
||||
return {
|
||||
group_id: parseInt(group.groupCode),
|
||||
group_name: group.groupName,
|
||||
group_memo: group.remarkName,
|
||||
group_create_time: +group.createTime,
|
||||
member_count: group.memberCount,
|
||||
max_member_count: group.maxMember,
|
||||
}
|
||||
|
@@ -276,4 +276,34 @@ export async function sendMsg(
|
||||
deleteAfterSentFiles.map(path => fsPromise.unlink(path))
|
||||
return returnMsg
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreatePeerPayload {
|
||||
group_id?: string | number
|
||||
user_id?: string | number
|
||||
}
|
||||
|
||||
export enum CreatePeerMode {
|
||||
Normal = 0,
|
||||
Private = 1,
|
||||
Group = 2
|
||||
}
|
||||
|
||||
export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode: CreatePeerMode): Promise<Peer> {
|
||||
if ((mode === CreatePeerMode.Group || mode === CreatePeerMode.Normal) && payload.group_id) {
|
||||
return {
|
||||
chatType: ChatType.group,
|
||||
peerUid: payload.group_id.toString(),
|
||||
}
|
||||
}
|
||||
if ((mode === CreatePeerMode.Private || mode === CreatePeerMode.Normal) && payload.user_id) {
|
||||
const uid = await ctx.ntUserApi.getUidByUin(payload.user_id.toString())
|
||||
if (!uid) throw new Error('无法获取用户信息')
|
||||
const isBuddy = await ctx.ntFriendApi.isBuddy(uid)
|
||||
return {
|
||||
chatType: isBuddy ? ChatType.friend : ChatType.temp,
|
||||
peerUid: uid,
|
||||
}
|
||||
}
|
||||
throw new Error('请指定 group_id 或 user_id')
|
||||
}
|
@@ -1,12 +1,12 @@
|
||||
import { OB11Message, OB11MessageAt, OB11MessageData, OB11MessageDataType } from '../types'
|
||||
import { OB11FriendRequestEvent } from '../event/request/OB11FriendRequest'
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest'
|
||||
import { ChatType, GroupRequestOperateTypes, Peer } from '@/ntqqapi/types'
|
||||
import { convertMessage2List, createSendElements, sendMsg } from '../helper/createMessage'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import { GroupRequestOperateTypes } from '@/ntqqapi/types'
|
||||
import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../helper/createMessage'
|
||||
import { MessageUnique } from '@/common/utils/messageUnique'
|
||||
import { isNullable } from 'cosmokit'
|
||||
import { Context } from 'cordis'
|
||||
import { OB11Config } from '@/common/types'
|
||||
|
||||
interface QuickOperationPrivateMessage {
|
||||
reply?: string
|
||||
@@ -57,21 +57,14 @@ export async function handleQuickOperation(ctx: Context, event: QuickOperationEv
|
||||
|
||||
async function handleMsg(ctx: Context, msg: OB11Message, quickAction: QuickOperationPrivateMessage | QuickOperationGroupMessage) {
|
||||
const reply = quickAction.reply
|
||||
const ob11Config = getConfigUtil().getConfig().ob11
|
||||
const peer: Peer = {
|
||||
chatType: ChatType.friend,
|
||||
peerUid: msg.user_id.toString(),
|
||||
}
|
||||
if (msg.message_type == 'private') {
|
||||
peer.peerUid = (await ctx.ntUserApi.getUidByUin(msg.user_id.toString()))!
|
||||
if (msg.sub_type === 'group') {
|
||||
peer.chatType = ChatType.temp
|
||||
}
|
||||
}
|
||||
else {
|
||||
peer.chatType = ChatType.group
|
||||
peer.peerUid = msg.group_id?.toString()!
|
||||
const ob11Config: OB11Config = ctx.config
|
||||
let contextMode = CreatePeerMode.Normal
|
||||
if (msg.message_type === 'group') {
|
||||
contextMode = CreatePeerMode.Group
|
||||
} else if (msg.message_type === 'private') {
|
||||
contextMode = CreatePeerMode.Private
|
||||
}
|
||||
const peer = await createPeer(ctx, msg, contextMode)
|
||||
if (reply) {
|
||||
let replyMessage: OB11MessageData[] = []
|
||||
if (ob11Config.enableQOAutoQuote) {
|
||||
|
@@ -54,8 +54,10 @@ export interface OB11GroupMember {
|
||||
export interface OB11Group {
|
||||
group_id: number
|
||||
group_name: string
|
||||
member_count?: number
|
||||
max_member_count?: number
|
||||
group_memo: string
|
||||
group_create_time: number
|
||||
member_count: number
|
||||
max_member_count: number
|
||||
}
|
||||
|
||||
interface OB11Sender {
|
||||
|
@@ -166,7 +166,12 @@ async function onSettingWindowCreated(view: Element) {
|
||||
SettingItem(
|
||||
'快速操作回复自动引用原消息',
|
||||
null,
|
||||
SettingSwitch('ob11.enableQOAutoQuote', config.ob11.enableQOAutoQuote, { 'control-display-id': 'config-ob11-enableQOAutoQuote' }),
|
||||
SettingSwitch('ob11.enableQOAutoQuote', config.ob11.enableQOAutoQuote),
|
||||
),
|
||||
SettingItem(
|
||||
'HTTP、正向 WebSocket 服务仅监听 127.0.0.1',
|
||||
'而不是 0.0.0.0',
|
||||
SettingSwitch('ob11.listenLocalhost', config.ob11.listenLocalhost),
|
||||
),
|
||||
SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')),
|
||||
]),
|
||||
|
@@ -1 +1 @@
|
||||
export const version = '3.31.6'
|
||||
export const version = '3.31.7'
|
||||
|
Reference in New Issue
Block a user