LLOneBot/src/satori/adapter.ts
2024-11-14 15:24:46 +08:00

194 lines
5.8 KiB
TypeScript

import * as NT from '@/ntqqapi/types'
import { omit } from 'cosmokit'
import { Event } from '@satorijs/protocol'
import { Service, Context } from 'cordis'
import { SatoriConfig } from '@/common/types'
import { SatoriServer } from './server'
import { selfInfo } from '@/common/globalVars'
import { ObjectToSnake } from 'ts-case-convert'
import { isDeepStrictEqual } from 'node:util'
import { parseMessageCreated, parseMessageDeleted } from './event/message'
import { parseGuildAdded, parseGuildRemoved, parseGuildRequest } from './event/guild'
import { parseGuildMemberAdded, parseGuildMemberRemoved, parseGuildMemberRequest } from './event/member'
import { parseFriendRequest } from './event/user'
declare module 'cordis' {
interface Context {
satori: SatoriAdapter
}
}
class SatoriAdapter extends Service {
static inject = [
'ntMsgApi', 'ntFileApi', 'ntFileCacheApi',
'ntFriendApi', 'ntGroupApi', 'ntUserApi',
'ntWindowApi', 'ntWebApi', 'store',
]
private counter: number
private selfId: string
private server: SatoriServer
constructor(public ctx: Context, public config: SatoriAdapter.Config) {
super(ctx, 'satori', true)
this.counter = 0
this.selfId = selfInfo.uin
this.server = new SatoriServer(ctx, config)
}
async handleMessage(input: NT.RawMessage) {
if (
input.msgType === 5 &&
input.subMsgType === 8 &&
input.elements[0]?.grayTipElement?.groupElement?.type === 1 &&
input.elements[0].grayTipElement.groupElement.memberUid === selfInfo.uid
) {
// 自身主动申请
return await parseGuildAdded(this, input)
}
else if (
input.msgType === 5 &&
input.subMsgType === 12 &&
input.elements[0]?.grayTipElement?.xmlElement?.templId === '10179' &&
input.elements[0].grayTipElement.xmlElement.templParam.get('invitee') === selfInfo.uin
) {
// 自身被邀请
return await parseGuildAdded(this, input)
}
else if (
input.msgType === 5 &&
input.subMsgType === 8 &&
input.elements[0]?.grayTipElement?.groupElement?.type === 3
) {
// 自身被踢出
return await parseGuildRemoved(this, input)
}
else if (
input.msgType === 5 &&
input.subMsgType === 8 &&
input.elements[0]?.grayTipElement?.groupElement?.type === 1
) {
// 他人主动申请
return await parseGuildMemberAdded(this, input)
}
else if (
input.msgType === 5 &&
input.subMsgType === 12 &&
input.elements[0]?.grayTipElement?.xmlElement?.templId === '10179'
) {
// 他人被邀请
return await parseGuildMemberAdded(this, input)
}
else if (
input.msgType === 5 &&
input.subMsgType === 12 &&
input.elements[0]?.grayTipElement?.jsonGrayTipElement?.busiId === '19217'
) {
// 机器人被邀请
return await parseGuildMemberAdded(this, input, true)
}
else if (
input.msgType === 5 &&
input.subMsgType === 12 &&
input.elements[0]?.grayTipElement?.xmlElement?.templId === '10382'
) {
// 机器人被表情回应
}
else {
// 普通的消息
return await parseMessageCreated(this, input)
}
}
async handleGroupNotify(input: NT.GroupNotify, doubt: boolean) {
if (
input.type === NT.GroupNotifyType.InvitedByMember &&
input.status === NT.GroupNotifyStatus.Unhandle
) {
// 自身被邀请,需自身同意
return await parseGuildRequest(this, input)
}
else if (
input.type === NT.GroupNotifyType.MemberLeaveNotifyAdmin ||
input.type === NT.GroupNotifyType.KickMemberNotifyAdmin
) {
// 他人主动退出或被踢
return await parseGuildMemberRemoved(this, input)
}
else if (
input.type === NT.GroupNotifyType.RequestJoinNeedAdminiStratorPass &&
input.status === NT.GroupNotifyStatus.Unhandle
) {
// 他人主动申请,需管理员同意
return await parseGuildMemberRequest(this, input, doubt)
}
else if (
input.type === NT.GroupNotifyType.InvitedNeedAdminiStratorPass &&
input.status === NT.GroupNotifyStatus.Unhandle
) {
// 他人被邀请,需管理员同意
return await parseGuildMemberRequest(this, input, doubt)
}
}
start() {
this.server.start()
this.ctx.on('nt/message-created', async input => {
const event = await this.handleMessage(input)
.catch(e => this.ctx.logger.error(e))
event && this.server.dispatch(event)
})
this.ctx.on('nt/group-notify', async input => {
const { doubt, notify } = input
const event = await this.handleGroupNotify(notify, doubt)
.catch(e => this.ctx.logger.error(e))
event && this.server.dispatch(event)
})
this.ctx.on('nt/message-deleted', async input => {
const event = await parseMessageDeleted(this, input)
.catch(e => this.ctx.logger.error(e))
event && this.server.dispatch(event)
})
this.ctx.on('nt/friend-request', async input => {
const event = await parseFriendRequest(this, input)
.catch(e => this.ctx.logger.error(e))
event && this.server.dispatch(event)
})
this.ctx.on('llob/config-updated', async input => {
const old = omit(this.config, ['ffmpeg'])
if (!isDeepStrictEqual(old, input.satori)) {
await this.server.stop()
this.server.updateConfig(input.satori)
this.server.start()
}
Object.assign(this.config, {
...input.satori,
ffmpeg: input.ffmpeg
})
})
}
event(type: string, data: Partial<ObjectToSnake<Event>>): ObjectToSnake<Event> {
return {
id: ++this.counter,
type,
self_id: this.selfId,
platform: 'llonebot',
timestamp: Date.now(),
...data
}
}
}
namespace SatoriAdapter {
export interface Config extends SatoriConfig {
ffmpeg?: string
}
}
export default SatoriAdapter