Compare commits

...

8 Commits

Author SHA1 Message Date
idranme
e5ab6134cd Merge pull request #441 from LLOneBot/dev
release: 3.33.6
2024-09-23 23:43:50 +08:00
idranme
a95ae44614 chore: v3.33.6 2024-09-23 23:36:10 +08:00
idranme
3dc9940ac9 feat 2024-09-23 23:34:52 +08:00
idranme
277e418cf3 refactor 2024-09-23 22:10:12 +08:00
idranme
24f09d485e Merge pull request #438 from LLOneBot/dev
release: 3.33.5
2024-09-22 21:31:55 +08:00
idranme
3394823719 chore: v3.33.5 2024-09-22 21:26:37 +08:00
idranme
afa06f0760 fix 2024-09-22 21:18:59 +08:00
idranme
4f9e465fb2 optimize 2024-09-22 20:37:20 +08:00
28 changed files with 246 additions and 280 deletions

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot",
"slug": "LLOneBot",
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发",
"version": "3.33.4",
"version": "3.33.6",
"icon": "./icon.webp",
"authors": [
{

View File

@@ -18,7 +18,6 @@
"license": "MIT",
"dependencies": {
"@minatojs/driver-sqlite": "^4.6.0",
"compressing": "^1.10.1",
"cordis": "^3.18.1",
"cors": "^2.8.5",
"cosmokit": "^1.6.2",

View File

@@ -190,26 +190,3 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
return { success: false, errMsg: '未知文件类型', fileName: '', path: '', isLocal: false }
}
export async function copyFolder(sourcePath: string, destPath: string) {
try {
const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true })
await fsPromise.mkdir(destPath, { recursive: true })
for (const entry of entries) {
const srcPath = path.join(sourcePath, entry.name)
const dstPath = path.join(destPath, entry.name)
if (entry.isDirectory()) {
await copyFolder(srcPath, dstPath)
} else {
try {
await fsPromise.copyFile(srcPath, dstPath)
} catch (error) {
console.error(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`)
// 这里可以决定是否要继续复制其他文件
}
}
}
} catch (error) {
console.error('复制文件夹时出错:', error)
}
}

View File

@@ -1,9 +1,8 @@
import path from 'node:path'
import compressing from 'compressing'
import { writeFile } from 'node:fs/promises'
import { version } from '../../version'
import { copyFolder, log, fetchFile } from '.'
import { PLUGIN_DIR, TEMP_DIR } from '../globalVars'
import { log, fetchFile } from '.'
import { TEMP_DIR } from '../globalVars'
const downloadMirrorHosts = ['https://ghp.ci/']
const releasesMirrorHosts = ['https://kkgithub.com']
@@ -27,49 +26,21 @@ export async function checkNewVersion() {
return { result: false, version: version }
}
export async function upgradeLLOneBot() {
export async function upgradeLLOneBot(): Promise<boolean> {
const latestVersion = await getRemoteVersion()
if (latestVersion && latestVersion != '') {
const downloadUrl = `https://github.com/LLOneBot/LLOneBot/releases/download/v${latestVersion}/LLOneBot.zip`
const filePath = path.join(TEMP_DIR, './update-' + latestVersion + '.zip')
let downloadSuccess = false
// 多镜像下载
for (const mirrorGithub of downloadMirrorHosts) {
try {
const res = await fetchFile(mirrorGithub + downloadUrl)
await writeFile(filePath, res.data)
downloadSuccess = true
break
return globalThis.LiteLoader.api.plugin.install(filePath)
} catch (e) {
log('llonebot upgrade error', e)
}
}
if (!downloadSuccess) {
log('llonebot upgrade error', 'download failed')
return false
}
const temp_ver_dir = path.join(TEMP_DIR, 'LLOneBot' + latestVersion)
const uncompressedPromise = async function () {
return new Promise<boolean>(resolve => {
compressing.zip
.uncompress(filePath, temp_ver_dir)
.then(() => {
resolve(true)
})
.catch(reason => {
log('llonebot upgrade failed, ', reason)
if (reason?.errno == -4082) {
resolve(true)
}
resolve(false)
})
})
}
const uncompressedResult = await uncompressedPromise()
// 复制文件
await copyFolder(temp_ver_dir, PLUGIN_DIR)
return uncompressedResult
}
return false
}

View File

@@ -1,8 +1,7 @@
import { Friend, FriendV2, SimpleInfo, CategoryFriend } from '../types'
import { Friend, FriendV2, SimpleInfo, CategoryFriend, BuddyListReqType } from '../types'
import { ReceiveCmdS } from '../hook'
import { invoke, NTMethod, NTClass } from '../ntcall'
import { getSession } from '@/ntqqapi/wrapper'
import { BuddyListReqType } from '../services'
import { Dict, pick } from 'cosmokit'
import { Service, Context } from 'cordis'

View File

@@ -65,32 +65,30 @@ export class NTQQGroupApi extends Service {
return result.result.infos
}
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
const groupCodeStr = groupCode.toString()
const memberUinOrUidStr = memberUinOrUid.toString()
if (!this.groupMembers.has(groupCodeStr)) {
async getGroupMember(groupCode: string, memberUinOrUid: string) {
if (!this.groupMembers.has(groupCode)) {
try {
// 更新群成员列表
this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
this.groupMembers.set(groupCode, await this.getGroupMembers(groupCode))
}
catch (e) {
return null
return
}
}
let members = this.groupMembers.get(groupCodeStr)!
let members = this.groupMembers.get(groupCode)!
const getMember = () => {
let member: GroupMember | undefined = undefined
if (isNumeric(memberUinOrUidStr)) {
member = Array.from(members.values()).find(member => member.uin === memberUinOrUidStr)
if (isNumeric(memberUinOrUid)) {
member = Array.from(members.values()).find(member => member.uin === memberUinOrUid)
} else {
member = members.get(memberUinOrUidStr)
member = members.get(memberUinOrUid)
}
return member
}
let member = getMember()
if (!member) {
this.groupMembers.set(groupCodeStr, await this.getGroupMembers(groupCodeStr))
members = this.groupMembers.get(groupCodeStr)!
this.groupMembers.set(groupCode, await this.getGroupMembers(groupCode))
members = this.groupMembers.get(groupCode)!
member = getMember()
}
return member
@@ -295,7 +293,7 @@ export class NTQQGroupApi extends Service {
cmdCB: (payload, result) => payload.fileInfo.reqId === result
}
)
return data.fileInfo.item
return data.fileInfo
}
async publishGroupBulletin(groupCode: string, req: PublishGroupBulletinReq) {

View File

@@ -267,6 +267,12 @@ export class NTQQMsgApi extends Service {
}
async generateMsgUniqueId(chatType: number) {
return await invoke('nodeIKernelMsgService/generateMsgUniqueId', [{ chatType }])
const uniqueId = await invoke('nodeIKernelMsgService/generateMsgUniqueId', [{ chatType }])
if (typeof uniqueId === 'string') {
return uniqueId
} else {
const random = Math.trunc(Math.random() * 100)
return `${Date.now()}${random}`
}
}
}

View File

@@ -1,9 +1,8 @@
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg, UserDetailSource, ProfileBizType } from '../types'
import { invoke } from '../ntcall'
import { User, UserDetailInfoByUin, UserDetailInfoByUinV2, UserDetailInfoListenerArg } from '../types'
import { getBuildVersion } from '@/common/utils'
import { getSession } from '@/ntqqapi/wrapper'
import { RequestUtil } from '@/common/utils/request'
import { UserDetailSource, ProfileBizType } from '../services'
import { Time } from 'cosmokit'
import { Service, Context } from 'cordis'
import { selfInfo } from '@/common/globalVars'
@@ -157,30 +156,24 @@ export class NTQQUserApi extends Service {
return uid
}
async getUidByUinV2(uin: string) {
const session = getSession()
if (session) {
let uid = (await session.getGroupService().getUidByUins([uin])).uids.get(uin)
if (uid) return uid
uid = (await session.getProfileService().getUidByUin('FriendsServiceImpl', [uin])).get(uin)
if (uid) return uid
uid = (await session.getUixConvertService().getUid([uin])).uidInfo.get(uin)
if (uid) return uid
} else {
let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uin: [uin] }])).uids.get(uin)
if (uid) return uid
uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }])).get(uin)
if (uid) return uid
uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
if (uid) return uid
async getUidByUinV2(uin: string, groupCode?: string) {
let uid = (await invoke('nodeIKernelGroupService/getUidByUins', [{ uin: [uin] }])).uids.get(uin)
if (uid) return uid
uid = (await invoke('nodeIKernelProfileService/getUidByUin', [{ callFrom: 'FriendsServiceImpl', uin: [uin] }])).get(uin)
if (uid) return uid
uid = (await invoke('nodeIKernelUixConvertService/getUid', [{ uins: [uin] }])).uidInfo.get(uin)
if (uid) return uid
const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid
if (!unveifyUid.includes('*')) return unveifyUid
if (groupCode) {
const member = await this.ctx.ntGroupApi.getGroupMember(groupCode, uin)
return member?.uid
}
const unveifyUid = (await this.getUserDetailInfoByUinV2(uin)).detail.uid //从QQ Native 特殊转换
if (unveifyUid.indexOf('*') == -1) return unveifyUid
}
async getUidByUin(uin: string) {
async getUidByUin(uin: string, groupCode?: string) {
if (getBuildVersion() >= 26702) {
return this.getUidByUinV2(uin)
return this.getUidByUinV2(uin, groupCode)
}
return this.getUidByUinV1(uin)
}
@@ -249,7 +242,7 @@ export class NTQQUserApi extends Service {
if (session) {
return await session.getTicketService().forceFetchClientKey('')
} else {
return await invoke('nodeIKernelTicketService/forceFetchClientKey', [{ domain: '' }, null])
return await invoke('nodeIKernelTicketService/forceFetchClientKey', [{ url: '' }, null])
}
}

View File

@@ -1,5 +1,6 @@
import ffmpeg from 'fluent-ffmpeg'
import faceConfig from './helper/face_config.json'
import pathLib from 'node:path'
import {
AtType,
ElementType,
@@ -132,7 +133,6 @@ export namespace SendElementEntities {
if (fileSize > 1024 * 1024 * maxMB) {
throw `视频过大,最大支持${maxMB}MB当前文件大小${fileSize}B`
}
const pathLib = require('path')
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
thumbDir = pathLib.dirname(thumbDir)
// log("thumb 目录", thumb)

View File

@@ -1,10 +1,6 @@
import { BuddyListReqType } from '@/ntqqapi/types'
import { GeneralCallResult } from './common'
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
export interface NodeIKernelBuddyService {
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {
data: {

View File

@@ -1,36 +1,10 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/ntqqapi/types'
import { ElementType, MessageElement, Peer, RawMessage, QueryMsgsParams, TmpChatInfoApi } from '@/ntqqapi/types'
import { GeneralCallResult } from './common'
export interface QueryMsgsParams {
chatInfo: Peer
filterMsgType: []
filterSendersUid: string[]
filterMsgFromTime: string
filterMsgToTime: string
pageLimit: number
isReverseOrder: boolean
isIncludeCurrent: boolean
}
export interface TmpChatInfoApi {
errMsg: string
result: number
tmpChatInfo?: TmpChatInfo
}
export interface TmpChatInfo {
chatType: number
fromNick: string
groupCode: string
peerUid: string
sessionType: number
sig: string
}
export interface NodeIKernelMsgService {
generateMsgUniqueId(chatType: number, time: string): string
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>
sendMsg(msgId: string, peer: Peer, msgElements: MessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>

View File

@@ -1,19 +1,6 @@
import { SimpleInfo } from '../types'
import { GeneralCallResult } from './common'
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}
export interface NodeIKernelProfileService {
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>

View File

@@ -1,4 +1,5 @@
import { GroupMemberRole } from './group'
import { GeneralCallResult } from '../services'
export interface GetFileListParam {
sortType: number
@@ -565,3 +566,25 @@ export interface OnGroupFileInfoUpdateParams {
nextIndex: number
reqId: number
}
export interface QueryMsgsParams {
chatInfo: Peer
filterMsgType: []
filterSendersUid: string[]
filterMsgFromTime: string
filterMsgToTime: string
pageLimit: number
isReverseOrder: boolean
isIncludeCurrent: boolean
}
export interface TmpChatInfoApi extends GeneralCallResult {
tmpChatInfo?: {
chatType: number
fromNick: string
groupCode: string
peerUid: string
sessionType: number
sig: string
}
}

View File

@@ -344,3 +344,21 @@ export interface UserDetailInfoByUin {
vipNameColorId: string
}
}
export enum BuddyListReqType {
KNOMAL,
KLETTER
}
export enum UserDetailSource {
KDB,
KSERVER
}
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
}

View File

@@ -1,11 +1,11 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '@/onebot11/types'
import { OnGroupFileInfoUpdateParams } from '@/ntqqapi/types'
interface Payload {
group_id: string | number
group_id: number | string
folder_id: string
file_count: string | number
}
interface Response {
@@ -17,19 +17,27 @@ export class GetGroupFilesByFolder extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
folder_id: Schema.string().required(),
file_count: Schema.union([Number, String]).default(50)
folder_id: Schema.string().required()
})
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: +payload.file_count,
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
folderId: payload.folder_id
})
const groupId = payload.group_id.toString()
const data: OnGroupFileInfoUpdateParams['item'] = []
let nextIndex: number | undefined
while (nextIndex !== 0) {
const res = await this.ctx.ntGroupApi.getGroupFileList(groupId, {
sortType: 1,
fileCount: 100,
startIndex: nextIndex ?? 0,
sortOrder: 2,
showOnlinedocFolder: 0,
folderId: payload.folder_id
})
data.push(...res.item)
nextIndex = res.nextIndex
}
return {
files: data.filter(item => item.fileInfo)
.map(item => {

View File

@@ -29,9 +29,8 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
protected async _handle(payload: Payload): Promise<Response> {
const { count, reverseOrder } = payload
const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() }
let msgList: RawMessage[] | undefined
// 包含 message_seq 0
if (!payload.message_seq) {
let msgList: RawMessage[]
if (!payload.message_seq || payload.message_seq === '0') {
msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +count)).msgList
} else {
const startMsgId = (await this.ctx.store.getMsgInfoByShortId(+payload.message_seq))?.msgId
@@ -40,12 +39,10 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
}
if (!msgList?.length) throw new Error('未找到消息')
if (reverseOrder) msgList.reverse()
await Promise.all(
msgList.map(async msg => {
msg.msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
})
)
const ob11MsgList = await Promise.all(msgList.map((msg) => OB11Entities.message(this.ctx, msg)))
for (const msg of msgList) {
msg.msgShortId = this.ctx.store.createMsgShortId({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)
}
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg)))
return { messages: filterNullable(ob11MsgList) }
}
}

View File

@@ -1,10 +1,10 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '../../types'
import { OnGroupFileInfoUpdateParams } from '@/ntqqapi/types'
interface Payload {
group_id: string | number
file_count: string | number
group_id: number | string
}
interface Response {
@@ -15,18 +15,26 @@ interface Response {
export class GetGroupRootFiles extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupRootFiles
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
file_count: Schema.union([Number, String]).default(50),
group_id: Schema.union([Number, String]).required()
})
async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1,
fileCount: +payload.file_count,
startIndex: 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
const groupId = payload.group_id.toString()
const data: OnGroupFileInfoUpdateParams['item'] = []
let nextIndex: number | undefined
while (nextIndex !== 0) {
const res = await this.ctx.ntGroupApi.getGroupFileList(groupId, {
sortType: 1,
fileCount: 100,
startIndex: nextIndex ?? 0,
sortOrder: 2,
showOnlinedocFolder: 0,
})
data.push(...res.item)
nextIndex = res.nextIndex
}
return {
files: data.filter(item => item.fileInfo)
.map(item => {

View File

@@ -1,22 +1,25 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { GroupRequestOperateTypes } from '@/ntqqapi/types'
import { ActionName } from '../types'
interface Payload {
flag: string
approve?: boolean | string
approve: boolean
reason?: string
}
export default class SetGroupAddRequest extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupAddRequest
payloadSchema = Schema.object({
flag: Schema.string().required(),
approve: Schema.boolean().default(true),
reason: Schema.string()
})
protected async _handle(payload: Payload): Promise<null> {
const flag = payload.flag.toString()
const approve = payload.approve?.toString() !== 'false'
await this.ctx.ntGroupApi.handleGroupRequest(
flag,
approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
payload.flag,
payload.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
payload.reason
)
return null

View File

@@ -1,26 +1,30 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { GroupMemberRole } from '@/ntqqapi/types'
import { ActionName } from '../types'
interface Payload {
group_id: number
user_id: number
group_id: number | string
user_id: number | string
enable: boolean
}
export default class SetGroupAdmin extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupAdmin
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
enable: Schema.boolean().default(true)
})
protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
const enable = payload.enable.toString() === 'true'
if (!member) {
throw `群成员${payload.user_id}不存在`
}
const groupCode = payload.group_id.toString()
const uin = payload.user_id.toString()
const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.setMemberRole(
payload.group_id.toString(),
member.uid,
enable ? GroupMemberRole.admin : GroupMemberRole.normal,
groupCode,
uid,
payload.enable ? GroupMemberRole.admin : GroupMemberRole.normal
)
return null
}

View File

@@ -1,22 +1,27 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number
user_id: number
group_id: number | string
user_id: number | string
duration: number
}
export default class SetGroupBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupBan
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
duration: Schema.number().default(30 * 60)
})
protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
if (!member) {
throw `群成员${payload.user_id}不存在`
}
await this.ctx.ntGroupApi.banMember(payload.group_id.toString(), [
{ uid: member.uid, timeStamp: parseInt(payload.duration.toString()) },
const groupCode = payload.group_id.toString()
const uin = payload.user_id.toString()
const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.banMember(groupCode, [
{ uid, timeStamp: payload.duration },
])
return null
}

View File

@@ -1,21 +1,26 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number
user_id: number
group_id: number | string
user_id: number | string
card: string
}
export default class SetGroupCard extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupCard
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
card: Schema.string().default('')
})
protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
if (!member) {
throw `群成员${payload.user_id}不存在`
}
await this.ctx.ntGroupApi.setMemberCard(payload.group_id.toString(), member.uid, payload.card || '')
const groupCode = payload.group_id.toString()
const uin = payload.user_id.toString()
const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.setMemberCard(groupCode, uid, payload.card)
return null
}
}

View File

@@ -1,21 +1,26 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number
user_id: number
group_id: number | string
user_id: number | string
reject_add_request: boolean
}
export default class SetGroupKick extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupKick
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
user_id: Schema.union([Number, String]).required(),
reject_add_request: Schema.boolean().default(false)
})
protected async _handle(payload: Payload): Promise<null> {
const member = await this.ctx.ntGroupApi.getGroupMember(payload.group_id, payload.user_id)
if (!member) {
throw `群成员${payload.user_id}不存在`
}
await this.ctx.ntGroupApi.kickMember(payload.group_id.toString(), [member.uid], !!payload.reject_add_request)
const groupCode = payload.group_id.toString()
const uin = payload.user_id.toString()
const uid = await this.ctx.ntUserApi.getUidByUin(uin, groupCode)
if (!uid) throw new Error('无法获取用户信息')
await this.ctx.ntGroupApi.kickMember(groupCode, [uid], payload.reject_add_request)
return null
}
}

View File

@@ -1,20 +1,19 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number
is_dismiss: boolean
group_id: number | string
is_dismiss?: boolean
}
export default class SetGroupLeave extends BaseAction<Payload, void> {
export default class SetGroupLeave extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupLeave
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) {
try {
await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString())
} catch (e) {
this.ctx.logger.error('退群失败', e)
throw e
}
await this.ctx.ntGroupApi.quitGroup(payload.group_id.toString())
return null
}
}

View File

@@ -1,13 +1,17 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number
group_id: number | string
group_name: string
}
export default class SetGroupName extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupName
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
group_name: Schema.string().required()
})
protected async _handle(payload: Payload): Promise<null> {
await this.ctx.ntGroupApi.setGroupName(payload.group_id.toString(), payload.group_name)

View File

@@ -1,17 +1,20 @@
import { BaseAction } from '../BaseAction'
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
group_id: number
group_id: number | string
enable: boolean
}
export default class SetGroupWholeBan extends BaseAction<Payload, null> {
actionName = ActionName.SetGroupWholeBan
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required(),
enable: Schema.boolean().default(true)
})
protected async _handle(payload: Payload): Promise<null> {
const enable = payload.enable.toString() === 'true'
await this.ctx.ntGroupApi.banGroup(payload.group_id.toString(), enable)
await this.ctx.ntGroupApi.banGroup(payload.group_id.toString(), payload.enable)
return null
}
}

View File

@@ -12,8 +12,6 @@ import { handleQuickOperation, QuickOperationEvent } from '../helper/quickOperat
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
import { Dict } from 'cosmokit'
type RegisterHandler = (res: Response, payload: unknown) => Promise<unknown>
class OB11Http {
private readonly expressAPP: Express
private server?: http.Server
@@ -25,7 +23,6 @@ class OB11Http {
this.expressAPP.use(express.urlencoded({ extended: true, limit: '5000mb' }))
this.expressAPP.use((req, res, next) => {
// 兼容处理没有带content-type的请求
// log("req.headers['content-type']", req.headers['content-type'])
req.headers['content-type'] = 'application/json'
const originalJson = express.json({ limit: '5000mb' })
// 调用原始的express.json()处理器
@@ -37,12 +34,8 @@ class OB11Http {
next()
})
})
setTimeout(() => {
for (const [actionName, action] of config.actionMap) {
this.registerRouter('post', actionName, (res, payload) => action.handle(payload))
this.registerRouter('get', actionName, (res, payload) => action.handle(payload))
}
}, 0)
this.expressAPP.use((req, res, next) => this.authorize(req, res, next))
this.expressAPP.use((req, res, next) => this.handleRequest(req, res, next))
}
public start() {
@@ -85,6 +78,8 @@ class OB11Http {
private authorize(req: Request, res: Response, next: () => void) {
const serverToken = this.config.token
if (!serverToken) return next()
let clientToken = ''
const authHeader = req.get('authorization')
if (authHeader) {
@@ -99,36 +94,31 @@ class OB11Http {
this.ctx.logger.info('receive http url token', clientToken)
}
if (serverToken && clientToken !== serverToken) {
return res.status(403).send(JSON.stringify({ message: 'token verify failed!' }))
if (clientToken !== serverToken) {
return res.status(403).json({ message: 'token verify failed!' })
}
next()
}
private registerRouter(method: 'post' | 'get', url: string, handler: RegisterHandler) {
if (!url.startsWith('/')) {
url = '/' + url
private async handleRequest(req: Request, res: Response, next: () => void) {
if (req.path === '/') return next()
let payload = req.body
if (req.method === 'GET') {
payload = req.query
} else if (req.query) {
payload = { ...req.query, ...req.body }
}
if (!this.expressAPP[method]) {
const err = `LLOneBot server register router failed${method} not exist`
this.ctx.logger.error(err)
throw err
}
this.expressAPP[method](url, this.authorize.bind(this), async (req: Request, res: Response) => {
let payload = req.body
if (method == 'get') {
payload = req.query
} else if (req.query) {
payload = { ...req.query, ...req.body }
}
this.ctx.logger.info('收到 HTTP 请求', url, payload)
this.ctx.logger.info('收到 HTTP 请求', req.url, payload)
const action = this.config.actionMap.get(req.path.replaceAll('/', ''))
if (action) {
try {
res.send(await handler(res, payload))
res.json(await action.handle(payload))
} catch (e) {
res.send(OB11Response.error((e as Error).stack!.toString(), 200))
res.json(OB11Response.error((e as Error).stack!.toString(), 200))
}
})
} else {
res.status(404).json(OB11Response.error('API 不存在', 404))
}
}
}

View File

@@ -109,25 +109,19 @@ export namespace OB11Entities {
let name: string | undefined
if (element.textElement.atType == AtType.atAll) {
qq = 'all'
}
else {
const { atNtUid, content } = element.textElement
let atQQ = element.textElement.atUid
if (!atQQ || atQQ === '0') {
const atMember = await ctx.ntGroupApi.getGroupMember(msg.peerUin, atNtUid)
if (atMember) {
atQQ = atMember.uin
}
}
if (atQQ) {
qq = atQQ
name = content.replace('@', '')
} else {
const { atNtUid, atUid, content } = element.textElement
if (atUid && atUid !== '0') {
qq = atUid
} else {
qq = await ctx.ntUserApi.getUinByUid(atNtUid)
}
name = content.replace('@', '')
}
messageSegment = {
type: OB11MessageDataType.at,
data: {
qq: qq!,
qq,
name
}
}

View File

@@ -1 +1 @@
export const version = '3.33.4'
export const version = '3.33.6'