mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
444 lines
15 KiB
TypeScript
444 lines
15 KiB
TypeScript
import { Service, Context } from 'cordis'
|
|
import { OB11Entities } from './entities'
|
|
import {
|
|
GroupNotify,
|
|
GroupNotifyType,
|
|
RawMessage,
|
|
BuddyReqType,
|
|
Peer,
|
|
FriendRequest,
|
|
GroupMember,
|
|
GroupMemberRole,
|
|
GroupNotifyStatus
|
|
} from '../ntqqapi/types'
|
|
import { OB11GroupRequestEvent } from './event/request/OB11GroupRequest'
|
|
import { OB11FriendRequestEvent } from './event/request/OB11FriendRequest'
|
|
import { MessageUnique } from '../common/utils/messageUnique'
|
|
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from './event/notice/OB11GroupDecreaseEvent'
|
|
import { selfInfo } from '../common/globalVars'
|
|
import { OB11Config, Config as LLOBConfig } from '../common/types'
|
|
import { OB11WebSocket, OB11WebSocketReverseManager } from './connect/ws'
|
|
import { OB11Http, OB11HttpPost } from './connect/http'
|
|
import { OB11BaseEvent } from './event/OB11BaseEvent'
|
|
import { OB11Message } from './types'
|
|
import { OB11BaseMetaEvent } from './event/meta/OB11BaseMetaEvent'
|
|
import { postHttpEvent } from './helper/eventForHttp'
|
|
import { initActionMap } from './action'
|
|
import { llonebotError } from '../common/globalVars'
|
|
import { OB11GroupCardEvent } from './event/notice/OB11GroupCardEvent'
|
|
import { OB11GroupAdminNoticeEvent } from './event/notice/OB11GroupAdminNoticeEvent'
|
|
import { OB11ProfileLikeEvent } from './event/notice/OB11ProfileLikeEvent'
|
|
import { SysMsg } from '@/ntqqapi/proto/compiled'
|
|
|
|
declare module 'cordis' {
|
|
interface Context {
|
|
onebot: OneBot11Adapter
|
|
}
|
|
}
|
|
|
|
class OneBot11Adapter extends Service {
|
|
static inject = ['ntMsgApi', 'ntFileApi', 'ntFileCacheApi', 'ntFriendApi', 'ntGroupApi', 'ntUserApi', 'ntWindowApi', 'ntWebApi']
|
|
|
|
public messages: Map<string, RawMessage> = new Map()
|
|
public startTime = 0
|
|
private ob11WebSocket: OB11WebSocket
|
|
private ob11WebSocketReverseManager: OB11WebSocketReverseManager
|
|
private ob11Http: OB11Http
|
|
private ob11HttpPost: OB11HttpPost
|
|
|
|
constructor(public ctx: Context, public config: OneBot11Adapter.Config) {
|
|
super(ctx, 'onebot', true)
|
|
const actionMap = initActionMap(this)
|
|
this.ob11Http = new OB11Http(ctx, {
|
|
port: config.httpPort,
|
|
token: config.token,
|
|
actionMap,
|
|
listenLocalhost: config.listenLocalhost
|
|
})
|
|
this.ob11HttpPost = new OB11HttpPost(ctx, {
|
|
hosts: config.httpHosts,
|
|
heartInterval: config.heartInterval,
|
|
secret: config.httpSecret,
|
|
enableHttpHeart: config.enableHttpHeart
|
|
})
|
|
this.ob11WebSocket = new OB11WebSocket(ctx, {
|
|
port: config.wsPort,
|
|
heartInterval: config.heartInterval,
|
|
token: config.token,
|
|
actionMap,
|
|
listenLocalhost: config.listenLocalhost
|
|
})
|
|
this.ob11WebSocketReverseManager = new OB11WebSocketReverseManager(ctx, {
|
|
hosts: config.wsHosts,
|
|
heartInterval: config.heartInterval,
|
|
token: config.token,
|
|
actionMap
|
|
})
|
|
}
|
|
|
|
/** 缓存近期消息内容 */
|
|
public async addMsgCache(msg: RawMessage) {
|
|
const expire = this.config.msgCacheExpire * 1000
|
|
if (expire === 0) {
|
|
return
|
|
}
|
|
const id = msg.msgId
|
|
this.messages.set(id, msg)
|
|
setTimeout(() => {
|
|
this.messages.delete(id)
|
|
}, expire)
|
|
}
|
|
|
|
/** 获取近期消息内容 */
|
|
public getMsgCache(msgId: string) {
|
|
return this.messages.get(msgId)
|
|
}
|
|
|
|
public dispatch(event: OB11BaseEvent | OB11Message) {
|
|
if (this.config.enableWs) {
|
|
this.ob11WebSocket.emitEvent(event)
|
|
}
|
|
if (this.config.enableWsReverse) {
|
|
this.ob11WebSocketReverseManager.emitEvent(event)
|
|
}
|
|
if (this.config.enableHttpPost) {
|
|
this.ob11HttpPost.emitEvent(event)
|
|
}
|
|
if ((event as OB11BaseMetaEvent).meta_event_type !== 'heartbeat') {
|
|
// 不上报心跳
|
|
postHttpEvent(event)
|
|
}
|
|
}
|
|
|
|
private async handleGroupNotify(notifies: GroupNotify[]) {
|
|
for (const notify of notifies) {
|
|
try {
|
|
const notifyTime = parseInt(notify.seq) / 1000
|
|
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)
|
|
let operatorId = member1Uin
|
|
let subType: GroupDecreaseSubType = 'leave'
|
|
if (notify.user2.uid) {
|
|
// 是被踢的
|
|
const member2Uin = await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)
|
|
if (member2Uin) {
|
|
operatorId = member2Uin
|
|
}
|
|
subType = 'kick'
|
|
}
|
|
const event = new OB11GroupDecreaseEvent(
|
|
parseInt(notify.group.groupCode),
|
|
parseInt(member1Uin),
|
|
parseInt(operatorId),
|
|
subType,
|
|
)
|
|
this.dispatch(event)
|
|
}
|
|
else if (notify.type === GroupNotifyType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS && notify.status === GroupNotifyStatus.KUNHANDLE) {
|
|
this.ctx.logger.info('有加群请求')
|
|
const requestUin = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
|
|
const event = new OB11GroupRequestEvent(
|
|
parseInt(notify.group.groupCode),
|
|
parseInt(requestUin) || 0,
|
|
flag,
|
|
notify.postscript,
|
|
)
|
|
this.dispatch(event)
|
|
}
|
|
else if (notify.type === GroupNotifyType.INVITED_BY_MEMBER && notify.status === GroupNotifyStatus.KUNHANDLE) {
|
|
this.ctx.logger.info('收到邀请我加群通知')
|
|
const userId = await this.ctx.ntUserApi.getUinByUid(notify.user2.uid)
|
|
const event = new OB11GroupRequestEvent(
|
|
parseInt(notify.group.groupCode),
|
|
parseInt(userId) || 0,
|
|
flag,
|
|
notify.postscript,
|
|
undefined,
|
|
'invite'
|
|
)
|
|
this.dispatch(event)
|
|
}
|
|
else if (notify.type === GroupNotifyType.INVITED_NEED_ADMINI_STRATOR_PASS && notify.status === GroupNotifyStatus.KUNHANDLE) {
|
|
this.ctx.logger.info('收到群员邀请加群通知')
|
|
const userId = await this.ctx.ntUserApi.getUinByUid(notify.user1.uid)
|
|
const event = new OB11GroupRequestEvent(
|
|
parseInt(notify.group.groupCode),
|
|
parseInt(userId) || 0,
|
|
flag,
|
|
notify.postscript
|
|
)
|
|
this.dispatch(event)
|
|
}
|
|
} catch (e) {
|
|
this.ctx.logger.error('解析群通知失败', (e as Error).stack)
|
|
}
|
|
}
|
|
}
|
|
|
|
private handleMsg(msgList: RawMessage[]) {
|
|
for (const message of msgList) {
|
|
// 过滤启动之前的消息
|
|
if (parseInt(message.msgTime) < this.startTime / 1000) {
|
|
continue
|
|
}
|
|
const peer: Peer = {
|
|
chatType: message.chatType,
|
|
peerUid: message.peerUid
|
|
}
|
|
message.msgShortId = MessageUnique.createMsg(peer, message.msgId)
|
|
this.addMsgCache(message)
|
|
|
|
OB11Entities.message(this.ctx, message)
|
|
.then((msg) => {
|
|
if (!msg) {
|
|
return
|
|
}
|
|
if (!this.config.debug && msg.message.length === 0) {
|
|
return
|
|
}
|
|
const isSelfMsg = msg.user_id.toString() === selfInfo.uin
|
|
if (isSelfMsg && !this.config.reportSelfMessage) {
|
|
return
|
|
}
|
|
if (isSelfMsg) {
|
|
msg.target_id = parseInt(message.peerUin)
|
|
}
|
|
this.dispatch(msg)
|
|
})
|
|
.catch((e) => this.ctx.logger.error('constructMessage error: ', e.stack.toString()))
|
|
|
|
OB11Entities.groupEvent(this.ctx, message).then((groupEvent) => {
|
|
if (groupEvent) {
|
|
this.dispatch(groupEvent)
|
|
}
|
|
})
|
|
|
|
OB11Entities.privateEvent(this.ctx, message).then((privateEvent) => {
|
|
if (privateEvent) {
|
|
this.dispatch(privateEvent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private handleRecallMsg(message: RawMessage) {
|
|
const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId)
|
|
if (!oriMessageId) {
|
|
return
|
|
}
|
|
OB11Entities.recallEvent(this.ctx, message, oriMessageId).then((recallEvent) => {
|
|
if (recallEvent) {
|
|
this.dispatch(recallEvent)
|
|
}
|
|
})
|
|
}
|
|
|
|
private async handleFriendRequest(buddyReqs: FriendRequest[]) {
|
|
for (const req of buddyReqs) {
|
|
if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
|
|
continue
|
|
}
|
|
if (+req.reqTime < this.startTime / 1000) {
|
|
continue
|
|
}
|
|
let userId = 0
|
|
try {
|
|
const requesterUin = await this.ctx.ntUserApi.getUinByUid(req.friendUid)
|
|
userId = parseInt(requesterUin)
|
|
} catch (e) {
|
|
this.ctx.logger.error('获取加好友者QQ号失败', e)
|
|
}
|
|
const flag = req.friendUid + '|' + req.reqTime
|
|
const comment = req.extWords
|
|
const friendRequestEvent = new OB11FriendRequestEvent(
|
|
userId,
|
|
comment,
|
|
flag
|
|
)
|
|
this.dispatch(friendRequestEvent)
|
|
}
|
|
}
|
|
|
|
private async handleConfigUpdated(config: LLOBConfig) {
|
|
const old = this.config
|
|
this.ob11Http.updateConfig({
|
|
port: config.ob11.httpPort,
|
|
token: config.token,
|
|
})
|
|
this.ob11HttpPost.updateConfig({
|
|
hosts: config.ob11.httpHosts,
|
|
heartInterval: config.heartInterval,
|
|
secret: config.ob11.httpSecret,
|
|
enableHttpHeart: config.ob11.enableHttpHeart
|
|
})
|
|
this.ob11WebSocket.updateConfig({
|
|
port: config.ob11.wsPort,
|
|
heartInterval: config.heartInterval,
|
|
token: config.token,
|
|
})
|
|
this.ob11WebSocketReverseManager.updateConfig({
|
|
hosts: config.ob11.wsHosts,
|
|
heartInterval: config.heartInterval,
|
|
token: config.token,
|
|
})
|
|
// 判断是否启用或关闭 HTTP 服务
|
|
if (config.ob11.enableHttp !== old.enableHttp) {
|
|
if (!config.ob11.enableHttp) {
|
|
await this.ob11Http.stop()
|
|
} else {
|
|
this.ob11Http.start()
|
|
}
|
|
}
|
|
// HTTP 端口变化,重启服务
|
|
if ((config.ob11.httpPort !== old.httpPort || config.ob11.listenLocalhost !== old.listenLocalhost) && config.ob11.enableHttp) {
|
|
await this.ob11Http.stop()
|
|
this.ob11Http.start()
|
|
}
|
|
// 判断是否启用或关闭正向 WebSocket
|
|
if (config.ob11.enableWs !== old.enableWs) {
|
|
if (config.ob11.enableWs) {
|
|
this.ob11WebSocket.start()
|
|
} else {
|
|
await this.ob11WebSocket.stop()
|
|
}
|
|
}
|
|
// 正向 WebSocket 端口变化,重启服务
|
|
if ((config.ob11.wsPort !== old.wsPort || config.ob11.listenLocalhost !== old.listenLocalhost) && config.ob11.enableWs) {
|
|
await this.ob11WebSocket.stop()
|
|
this.ob11WebSocket.start()
|
|
llonebotError.wsServerError = ''
|
|
}
|
|
// 判断是否启用或关闭反向ws
|
|
if (config.ob11.enableWsReverse !== old.enableWsReverse) {
|
|
if (config.ob11.enableWsReverse) {
|
|
this.ob11WebSocketReverseManager.start()
|
|
} else {
|
|
this.ob11WebSocketReverseManager.stop()
|
|
}
|
|
}
|
|
// 判断反向 WebSocket 地址有变化
|
|
if (config.ob11.enableWsReverse) {
|
|
if (config.ob11.wsHosts.length !== old.wsHosts.length) {
|
|
this.ob11WebSocketReverseManager.stop()
|
|
this.ob11WebSocketReverseManager.start()
|
|
} else {
|
|
for (const newHost of config.ob11.wsHosts) {
|
|
if (!old.wsHosts.includes(newHost)) {
|
|
this.ob11WebSocketReverseManager.stop()
|
|
this.ob11WebSocketReverseManager.start()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (config.ob11.enableHttpHeart !== old.enableHttpHeart) {
|
|
this.ob11HttpPost.stop()
|
|
this.ob11HttpPost.start()
|
|
}
|
|
Object.assign(this.config, {
|
|
...config.ob11,
|
|
heartInterval: config.heartInterval,
|
|
token: config.token,
|
|
debug: config.debug,
|
|
reportSelfMessage: config.reportSelfMessage,
|
|
msgCacheExpire: config.msgCacheExpire,
|
|
musicSignUrl: config.musicSignUrl,
|
|
enableLocalFile2Url: config.enableLocalFile2Url,
|
|
ffmpeg: config.ffmpeg
|
|
})
|
|
}
|
|
|
|
private async handleGroupMemberInfoUpdated(groupCode: string, members: GroupMember[]) {
|
|
for (const member of members) {
|
|
const existMember = await this.ctx.ntGroupApi.getGroupMember(groupCode, member.uin)
|
|
if (existMember) {
|
|
if (member.cardName !== existMember.cardName) {
|
|
this.ctx.logger.info('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
|
|
this.dispatch(
|
|
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
|
|
)
|
|
} else if (member.role !== existMember.role) {
|
|
this.ctx.logger.info('有管理员变动通知')
|
|
const groupAdminNoticeEvent = new OB11GroupAdminNoticeEvent(
|
|
member.role == GroupMemberRole.admin ? 'set' : 'unset',
|
|
parseInt(groupCode),
|
|
parseInt(member.uin)
|
|
)
|
|
this.dispatch(groupAdminNoticeEvent)
|
|
}
|
|
Object.assign(existMember, member)
|
|
}
|
|
}
|
|
}
|
|
|
|
public start() {
|
|
this.startTime = Date.now()
|
|
if (this.config.enableWs) {
|
|
this.ob11WebSocket.start()
|
|
}
|
|
if (this.config.enableWsReverse) {
|
|
this.ob11WebSocketReverseManager.start()
|
|
}
|
|
if (this.config.enableHttp) {
|
|
this.ob11Http.start()
|
|
}
|
|
if (this.config.enableHttpPost) {
|
|
this.ob11HttpPost.start()
|
|
}
|
|
this.ctx.on('llonebot/config-updated', input => {
|
|
this.handleConfigUpdated(input)
|
|
})
|
|
this.ctx.on('nt/message-created', input => {
|
|
this.handleMsg(input)
|
|
})
|
|
this.ctx.on('nt/message-deleted', input => {
|
|
this.handleRecallMsg(input)
|
|
})
|
|
this.ctx.on('nt/message-sent', input => {
|
|
this.handleMsg([input])
|
|
})
|
|
this.ctx.on('nt/group-notify', input => {
|
|
this.handleGroupNotify(input)
|
|
})
|
|
this.ctx.on('nt/friend-request', input => {
|
|
this.handleFriendRequest(input)
|
|
})
|
|
this.ctx.on('nt/group-member-info-updated', input => {
|
|
this.handleGroupMemberInfoUpdated(input.groupCode, input.members)
|
|
})
|
|
this.ctx.on('nt/system-message-created', input => {
|
|
const sysMsg = SysMsg.SystemMessage.decode(input)
|
|
const { msgType, subType, subSubType } = sysMsg.msgSpec[0] ?? {}
|
|
if (msgType === 528 && subType === 39 && subSubType === 39) {
|
|
const tip = SysMsg.ProfileLikeTip.decode(sysMsg.bodyWrapper!.body!)
|
|
if (tip.msgType !== 0 || tip.subType !== 203) return
|
|
const detail = tip.content?.msg?.detail
|
|
if (!detail) return
|
|
const [times] = detail.txt?.match(/\d+/) ?? ['0']
|
|
const profileLikeEvent = new OB11ProfileLikeEvent(detail.uin!, detail.nickname!, +times)
|
|
this.dispatch(profileLikeEvent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
namespace OneBot11Adapter {
|
|
export interface Config extends OB11Config {
|
|
heartInterval: number
|
|
token: string
|
|
debug: boolean
|
|
reportSelfMessage: boolean
|
|
msgCacheExpire: number
|
|
musicSignUrl?: string
|
|
enableLocalFile2Url: boolean
|
|
ffmpeg?: string
|
|
}
|
|
}
|
|
|
|
export default OneBot11Adapter
|