From 63a0576a8dd69b85fdb35c31f1ad5d682aa31d3e Mon Sep 17 00:00:00 2001 From: CakmLexi Date: Sun, 30 Jun 2024 23:01:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Ekarin.task=20karin.ha?= =?UTF-8?q?ndler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +- src/adapter/onebot/onebot11.ts | 6 +- src/core/karin.ts | 95 +++++++- src/core/plugin.app.ts | 11 +- src/core/plugin.loader.ts | 71 ++++-- src/core/plugin.ts | 11 +- src/db/index.ts | 8 +- src/db/level.ts | 2 + src/db/redis.ts | 14 +- src/event/event.handler.ts | 14 +- src/event/event.ts | 11 +- src/event/message.handler.ts | 26 +-- src/event/notice.handler.ts | 214 ++++++++++++++++++ src/event/notice.ts | 8 +- src/event/request.handler.ts | 120 ++++++++++ src/event/request.ts | 11 +- src/event/review.handler.ts | 36 +-- src/types/adapter.ts | 3 +- src/types/api.ts | 301 +++++++++++++++++++++++++ src/types/event.ts | 391 +++++++++++++-------------------- src/types/index.ts | 1 + src/types/plugin.ts | 8 +- src/utils/button.ts | 4 +- src/utils/config.ts | 20 +- src/utils/handler.ts | 17 +- 25 files changed, 1044 insertions(+), 365 deletions(-) create mode 100644 src/event/notice.handler.ts create mode 100644 src/event/request.handler.ts create mode 100644 src/types/api.ts diff --git a/package.json b/package.json index c712580c..9a5c29f6 100644 --- a/package.json +++ b/package.json @@ -38,12 +38,12 @@ ], "scripts": { "app": "node .", - "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json && npm run fix && npm run cp", - "cp": "cp ./lib/modules.d.ts ./modules.d.ts && cp ./lib/modules.js ./modules.js && cp ./src/utils/kritor-proto.d.ts ./lib/utils/kritor-proto.d.ts", + "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json && npm run fix:all && npm run cp", + "cp": "cp ./lib/modules.d.ts ./modules.d.ts && cp ./lib/modules.js ./modules.js", "delete": "pm2 delete ./config/config/pm2.yaml", "dev": "tsx ./lib/index.js --dev", "fix": "eslint lib/**/*.js --fix", - "fix:all": "eslint lib/**/*.js --fix && eslint src/**/*.ts --fix", + "fix:all": "eslint lib/**/*.js --fix && eslint lib/**/*.ts --fix", "init": "node lib/tools/install.js", "init:dev": "node lib/tools/install.js", "init:pack": "node lib/tools/install.js", diff --git a/src/adapter/onebot/onebot11.ts b/src/adapter/onebot/onebot11.ts index 8ee93fc8..dc708727 100644 --- a/src/adapter/onebot/onebot11.ts +++ b/src/adapter/onebot/onebot11.ts @@ -5,6 +5,7 @@ import { listener } from 'karin/core/listener' import { KarinAdapter } from 'karin/types/adapter' import { common, config, logger, segment } from 'karin/utils' import { KarinMessage, KarinNotice, KarinRequest } from 'karin/event' + import { Role, Scene, @@ -175,7 +176,10 @@ export class OneBot11 implements KarinAdapter { }) } - /** 处理事件 */ + /** + * 处理事件 + * - @param data ob11相关标准数据 + */ #event (data: OneBot11Event) { switch (data.post_type) { case 'meta_event': { diff --git a/src/core/karin.ts b/src/core/karin.ts index 9a8ad495..534de36c 100644 --- a/src/core/karin.ts +++ b/src/core/karin.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-dupe-class-members */ import PluginApp from './plugin.app' import { common } from 'karin/utils' import { KarinMessage } from 'karin/event/message' @@ -12,10 +11,6 @@ export interface OptionsCommand { * - 插件名称 不传则使用 插件名称:函数名 */ name?: string - /** - * - 插件描述 - */ - desc?: string /** * - 插件优先级 数字越小优先级越高 * @default 10000 @@ -72,6 +67,7 @@ export class Karin { * @param element - 字符串或者KarinElement、KarinElement数组 * @param options - 选项 */ + // eslint-disable-next-line no-dupe-class-members command (reg: string | RegExp, element: FncElement, options?: OptionsElement): PluginApps /** * - 快速构建命令 @@ -80,6 +76,7 @@ export class Karin { * @param options - 选项 * @returns - 返回插件对象 */ + // eslint-disable-next-line no-dupe-class-members command (reg: string | RegExp, second: FncFunction | FncElement, options: OptionsCommand | OptionsElement = {}): PluginApps { const Reg = typeof reg === 'string' ? new RegExp(reg) : reg @@ -103,7 +100,7 @@ export class Karin { case 'string': case 'number': case 'object': { - const element = common.makeMessage(typeof second === 'function' ? second : String(second)) + const element = common.makeMessage(typeof second === 'number' ? String(second) : second) const fnc = async (e: KarinMessage) => { if ('delay' in options && options.delay) await common.sleep(options.delay) @@ -123,4 +120,90 @@ export class Karin { } } } + + /** + * - 构建定时任务 + * @param name - 任务名称 + * @param cron - cron表达式 + * @param fnc - 执行函数 + * @param options - 选项 + */ + task (name: string, cron: string, fnc: Function, options?: { + /** + * - 任务插件名称 + */ + name?: string + /** + * - 任务优先级 + */ + priority?: number + /** + * - 是否打印日志 传递布尔值 + */ + log?: boolean | Function + }) { + if (!name) throw new Error('[task]: 缺少参数[name]') + if (!cron) throw new Error('[task]: 缺少参数[cron]') + if (!fnc) throw new Error('[task]: 缺少参数[fnc]') + + const data = { + name: options?.name || 'task', + priority: options?.priority, + task: [ + { + name, + cron, + fnc, + log: options?.log ?? true, + }, + ], + } + + return PluginApp(data) + } + + /** + * - 构建handler + * @param key - 事件key + * @param fnc - 函数实现 + * @param options - 选项 + */ + handler (key: string, fnc: ( + /** + * - 自定义参数 由调用方传递 + */ + args: any, + /** + * - 拒绝处理器 调用后则不再继续执行下一个处理器 + */ + reject: (msg?: string) => void + ) => Promise, options?: { + /** + * - 插件名称 + */ + name?: string + /** + * - handler优先级 + */ + priority?: number + }) { + if (!key) throw new Error('[handler]: 缺少参数[key]') + if (!fnc) throw new Error('[handler]: 缺少参数[fnc]') + + const priority = options?.priority || 10000 + + const data = { + name: options?.name || 'handler', + priority, + handler: [ + { + key, + fnc, + priority, + }, + ], + } + + return PluginApp(data) + } } diff --git a/src/core/plugin.app.ts b/src/core/plugin.app.ts index 03a77b1e..92171ef7 100644 --- a/src/core/plugin.app.ts +++ b/src/core/plugin.app.ts @@ -10,8 +10,11 @@ export interface PluginAppType { name?: string event?: PluginApps['event'] priority?: number - accept?: boolean + accept?: boolean | Function rule?: PluginApps['rule'] + task?: PluginApps['task'] + handler?: PluginApps['handler'] + button?: PluginApps['button'] } export default function PluginApp (options: PluginAppType): PluginApps { @@ -27,8 +30,8 @@ export default function PluginApp (options: PluginAppType): PluginApps { priority: options.priority || 10000, accept: options.accept ?? false, rule: options.rule || [], - task: [], - handler: [], - button: [], + task: options.task || [], + handler: options.handler || [], + button: options.button || [], } } diff --git a/src/core/plugin.loader.ts b/src/core/plugin.loader.ts index 3c4e2b6b..69e68a98 100644 --- a/src/core/plugin.loader.ts +++ b/src/core/plugin.loader.ts @@ -24,7 +24,14 @@ export const pluginLoader = new (class PluginLoader { * - 命令插件索引列表 */ ruleIds: Array + /** + * - 按钮插件索引列表 + */ buttonIds: Array + /** + * - accept插件索引列表 + */ + acceptIds: Array /** * - handler */ @@ -66,6 +73,7 @@ export const pluginLoader = new (class PluginLoader { this.PluginList = {} this.task = [] this.ruleIds = [] + this.acceptIds = [] this.buttonIds = [] this.handlerIds = {} } @@ -199,16 +207,19 @@ export const pluginLoader = new (class PluginLoader { const rule: { key: string, val: number }[] = [] const button: { key: string, val: number }[] = [] + const accept: { key: string, val: number }[] = [] Object.keys(this.PluginList).forEach(key => { taskCount += this.PluginList[key].task.length if (this.PluginList[key].rule.length) rule.push({ key, val: this.PluginList[key].priority }) if (this.PluginList[key].button.length) button.push({ key, val: this.PluginList[key].priority }) + if (this.PluginList[key].accept) accept.push({ key, val: this.PluginList[key].priority }) }) this.ruleIds = lodash.orderBy(rule, ['val'], ['asc']).map((v) => Number(v.key)) logger.debug('rule排序完成...') this.buttonIds = lodash.orderBy(button, ['val'], ['asc']).map((v) => Number(v.key)) logger.debug('button排序完成...') + this.acceptIds = lodash.orderBy(accept, ['val'], ['asc']).map((v) => Number(v.key)) if (!isPrint) return const PluginListKeys = Object.keys(this.PluginList) @@ -222,6 +233,7 @@ export const pluginLoader = new (class PluginLoader { logger.info(`[渲染器][${render.Apps.length}个] 加载完成`) logger.info(`[rule][${this.ruleIds.length}个] 加载完成`) logger.info(`[button][${this.buttonIds.length}个] 加载完成`) + logger.info(`[accept][${this.acceptIds.length}个] 加载完成`) logger.info(`[定时任务][${taskCount}个] 加载完成`) logger.info(`[Handler][Key:${handlerKeys.length}个][fnc:${handlerCount}个] 加载完成`) logger.info(logger.green('-----------')) @@ -242,12 +254,40 @@ export const pluginLoader = new (class PluginLoader { lodash.forEach(tmp, (App) => { const index = this.index this.index++ - if (typeof App === 'object') { - if (App?.file?.type !== 'function') return + if (typeof App === 'object' && App?.file?.type === 'function') { if (!App?.name) return logger.error(`[${dir}][${name}] 插件名称错误`) App.file.dir = dir App.file.name = name + + /** handler */ + handler.add(index + '', App) + + const task: PluginTask[] = [] + + /** 定时任务 */ + lodash.forEach(App.task, val => { + task.push({ + name: val.name, + cron: val.cron, + fnc: val.fnc, + log: val.log === false ? (log: string) => logger.debug(log) : (log: string) => logger.mark(log), + schedule: schedule.scheduleJob(val.cron, async () => { + try { + typeof val.log === 'function' && val.log(`[定时任务][${dir}][${val.name}] 开始执行`) + if (typeof val.fnc === 'function') await val.fnc() + typeof val.log === 'function' && val.log(`[定时任务][${dir}][${val.name}] 执行完毕`) + } catch (error) { + logger.error(`[定时任务][${dir}][${val.name}] 执行报错`) + logger.error(error) + } + }), + }) + }) + + App.task = task this.PluginList[index] = App + if (App.accept) this.acceptIds.push(index) + return true } if (typeof App !== 'function' || !App?.prototype?.constructor) return @@ -317,20 +357,15 @@ export const pluginLoader = new (class PluginLoader { }) }) + /** accept */ + if (Class.accept && typeof Class.accept === 'function') this.acceptIds.push(index) + /** handler */ handler.add(index + '', Class) /** 执行初始化 */ Class.init && Class.init() this.PluginList[index] = info - - // const Class = new App() - - /** 注册Handler */ - // if (!lodash.isEmpty(Class.handler)) handler.add({ name, dir, App, Class }) - - /** 注册按钮 */ - // if (!lodash.isEmpty(Class.button)) button.add({ name, dir, App, Class }) return true }) @@ -359,16 +394,22 @@ export const pluginLoader = new (class PluginLoader { * 卸载插件 */ uninstallApp (dir: dirName, name: fileName) { - // this.Apps = this.Apps.filter((v) => !(v.file.dir === dir && v.file.name === name)) - // this.uninstallTask(dir, name) - // button.del(dir, name) - // handler.del({ dir, name, key: '' }) + const index: string[] = [] Object.keys(this.PluginList).forEach(key => { const info = this.PluginList[key] /** 停止定时任务 */ info.task.forEach(val => val.schedule?.cancel()) - info.file.dir === dir && info.file.name === name && delete this.PluginList[key] + if (info.file.dir === dir && info.file.name === name) { + index.push(key) + delete this.PluginList[key] + } }) + + /** 删除handler */ + index.forEach(key => handler.del(key)) + + /** 重新排序 */ + this.orderBy() } /** diff --git a/src/core/plugin.ts b/src/core/plugin.ts index 215f91ae..199940c6 100644 --- a/src/core/plugin.ts +++ b/src/core/plugin.ts @@ -1,4 +1,4 @@ -import { PluginType, KarinElement, KarinNodeElement, EventType } from 'karin/types' +import { PluginType, KarinElement, KarinNodeElement, EventType, KarinNoticeEvent, KarinRequestEvent } from 'karin/types' /** * 插件基类 @@ -7,7 +7,7 @@ export class Plugin implements PluginType { // 类型 需要根据e中的event类型来确定 e!: EventType init?: () => void - accept?: (e: EventType) => Promise + accept?: (e: any) => Promise replyCallback!: PluginType['replyCallback'] /** @@ -235,3 +235,10 @@ export const stateArr: { fnc: string } } = {} + +/** + * 通知事件 插件类型 + */ +export interface ExtendedPlugin extends Plugin { + accept: (e: KarinNoticeEvent | KarinRequestEvent) => Promise +} diff --git a/src/db/index.ts b/src/db/index.ts index 94daf30a..290402b9 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,6 +1,2 @@ -import Redis from './redis' -import LevelDB from './level' -import { RedisClientType } from 'redis' - -export const level = new LevelDB() -export const redis: RedisClientType = await new Redis().start() as RedisClientType +export * from './level' +export * from './redis' diff --git a/src/db/level.ts b/src/db/level.ts index 677e6498..9152d944 100644 --- a/src/db/level.ts +++ b/src/db/level.ts @@ -36,3 +36,5 @@ export default class LevelDB extends Level { return await super.put(key, value as string) } } + +export const level = new LevelDB() diff --git a/src/db/redis.ts b/src/db/redis.ts index 763674d8..b1d23947 100644 --- a/src/db/redis.ts +++ b/src/db/redis.ts @@ -1,9 +1,9 @@ import { exec } from 'child_process' import RedisLevel from './redis_level' import { logger, config } from 'karin/utils' -import redis, { createClient, createCluster, RedisClientType } from 'redis' +import NodeRedis, { createClient, createCluster, RedisClientType } from 'redis' -export default class Redis { +class Redis { id: 'redis' RunCmd: string constructor () { @@ -14,7 +14,7 @@ export default class Redis { /** * redis实例化 */ - async start (): Promise<(redis.RedisClientType | string) | RedisLevel | false> { + async start (): Promise<(NodeRedis.RedisClientType | string) | RedisLevel | false> { const { host, port, username, password, db: database, cluster } = config.redis /** 集群模式 */ if (cluster && cluster.enable) { @@ -85,14 +85,12 @@ export default class Redis { /** * 连接 Redis 单例 - * @param {import("redis").RedisClientOptions} options - * @return {Promise<{status: 'ok', data: import("redis").RedisClientType} | {status: 'error', data: Error}>} */ - async connect (options: redis.RedisClientOptions): Promise<{ status: 'ok'; data: redis.RedisClientType } | { status: 'error'; data: string }> { + async connect (options: NodeRedis.RedisClientOptions): Promise<{ status: 'ok'; data: NodeRedis.RedisClientType } | { status: 'error'; data: string }> { const client = createClient(options) try { await client.connect() - return { status: 'ok', data: client as redis.RedisClientType } + return { status: 'ok', data: client as NodeRedis.RedisClientType } } catch (error) { return { status: 'error', data: error as string } } @@ -143,3 +141,5 @@ export default class Redis { }) } } + +export const redis: RedisClientType = await new Redis().start() as RedisClientType diff --git a/src/event/event.handler.ts b/src/event/event.handler.ts index 4aa52278..45d79266 100644 --- a/src/event/event.handler.ts +++ b/src/event/event.handler.ts @@ -1,20 +1,22 @@ import { review } from './review.handler' -import { KarinMessage } from './message' -import { KarinNotice } from './notice' -import { KarinRequest } from './request' import { listener } from 'karin/core' import { segment, common, logger, config } from 'karin/utils' -import { Event, Permission, SubEvent, GroupCfg } from 'karin/types' +import { Event, Permission, SubEvent, GroupCfg, KarinMessageEvent, KarinNoticeEvent, KarinRequestEvent } from 'karin/types' export default class EventHandler { - e: KarinMessage | KarinNotice | KarinRequest + e: KarinMessageEvent | KarinNoticeEvent | KarinRequestEvent config: GroupCfg | {} + /** + * - 是否打印群消息日志 + */ + GroupMsgPrint: boolean /** * 处理事件,加入自定义字段 */ - constructor (e: KarinMessage | KarinNotice | KarinRequest) { + constructor (e: KarinMessageEvent | KarinNoticeEvent | KarinRequestEvent) { this.e = e this.config = {} + this.GroupMsgPrint = false /** 加入e.bot */ Object.defineProperty(this.e, 'bot', { value: listener.getBot(this.e.self_id) }) if (this.e.group_id) this.config = config.group(this.e.group_id) diff --git a/src/event/event.ts b/src/event/event.ts index 7254d266..45b9ec0e 100644 --- a/src/event/event.ts +++ b/src/event/event.ts @@ -1,4 +1,4 @@ -import { KarinAdapter, Reply, replyCallback, Event, contact, Sender, SubEventForEvent } from 'karin/types' +import { KarinAdapter, Reply, replyCallback, Event, contact, Sender, EventToSubEvent } from 'karin/types' /** * - 事件基类 所有事件都继承自此类并且需要实现此类的所有属性 @@ -25,7 +25,7 @@ export class KarinEvent { /** * - 事件子类型 */ - sub_event: SubEventForEvent + sub_event: EventToSubEvent[Event] /** * - 事件ID */ @@ -84,6 +84,10 @@ export class KarinEvent { * - 存储器 由开发者自行调用 */ store: Map + /** + * - 原始消息 + */ + raw_message: string /** * - 回复函数 */ @@ -139,7 +143,7 @@ export class KarinEvent { /** * - 事件子类型 */ - sub_event: SubEventForEvent + sub_event: EventToSubEvent[Event] /** * - 事件ID */ @@ -163,6 +167,7 @@ export class KarinEvent { this.logFnc = '' this.logText = '' this.store = new Map() + this.raw_message = '' this.reply = (elements, options) => Promise.resolve({ message_id: '' }) this.replyCallback = (elements, options) => Promise.resolve({ message_id: '' }) this.bot = {} as KarinAdapter diff --git a/src/event/message.handler.ts b/src/event/message.handler.ts index 4cd58695..e948fed7 100644 --- a/src/event/message.handler.ts +++ b/src/event/message.handler.ts @@ -1,20 +1,20 @@ import lodash from 'lodash' import { review } from './review.handler' -import { KarinMessage } from './message' import EventHandler from './event.handler' import { logger, config } from 'karin/utils' +import { KarinMessageEvent } from 'karin/types' import { listener, Plugin, stateArr, pluginLoader } from 'karin/core' /** * 消息事件 */ export class MessageHandler extends EventHandler { - e: KarinMessage + e: KarinMessageEvent /** * - 是否打印群消息日志 */ GroupMsgPrint: boolean = false - constructor (e: KarinMessage) { + constructor (e: KarinMessageEvent) { super(e) this.e = e listener.emit('karin:count:recv', 1) @@ -65,20 +65,20 @@ export class MessageHandler extends EventHandler { /** 判断权限 */ if (!this.filterPermission(v.permission)) break a + /** 计算插件处理时间 */ + const start = Date.now() + listener.emit('karin:count:fnc', this.e.logFnc) + try { let res if (app.file.type === 'function' && typeof v.fnc === 'function') { - res = v.fnc(this.e) + res = await v.fnc(this.e) } else { const cla = new (app.file.Fnc as new () => Plugin)() cla.e = this.e - res = (cla[v.fnc as keyof typeof cla] as Function)(this.e) as Promise + res = await (cla[v.fnc as keyof typeof cla] as Function)(this.e) as Promise } - /** 计算插件处理时间 */ - const start = Date.now() - listener.emit('karin:count:fnc', this.e.logFnc) - res = await res this.GroupMsgPrint && typeof v.log === 'function' && v.log(this.e.self_id, `${logFnc} ${lodash.truncate(this.e.msg, { length: 80 })} 处理完成 ${logger.green(Date.now() - start + 'ms')}`) if (res !== false) break a } catch (error: any) { @@ -203,17 +203,15 @@ export class MessageHandler extends EventHandler { /** 前缀处理 */ this.e.group_id && 'GroupCD' in this.config && review.alias(this.e, this.config) - /** 主人 这里强制是因为yaml在自动保存QQ号的时候会强制化为数字 */ - const masterId = (Number(this.e.user_id) || String(this.e.user_id)) as string - if (config.master.includes(masterId)) { + /** 主人 */ + if (config.master.includes(String(this.e.user_id))) { this.e.isMaster = true this.e.isAdmin = true - } else if (config.admin.includes(masterId)) { + } else if (config.admin.includes(String(this.e.user_id))) { /** 管理员 */ this.e.isAdmin = true } - this.GroupMsgPrint = false if (this.e.contact.scene === 'private') { this.e.isPrivate = true this.e.logText = `[Private:${this.e.sender.nick || ''}(${this.e.user_id})]` diff --git a/src/event/notice.handler.ts b/src/event/notice.handler.ts new file mode 100644 index 00000000..108fc4ce --- /dev/null +++ b/src/event/notice.handler.ts @@ -0,0 +1,214 @@ +import { review } from './review.handler' +import EventHandler from './event.handler' +import { logger, config } from 'karin/utils' +import { KarinNoticeEvent } from 'karin/types' +import { ExtendedPlugin, pluginLoader } from 'karin/core' + +/** + * 通知事件 + */ +export default class NoticeHandler extends EventHandler { + e: KarinNoticeEvent + constructor (e: KarinNoticeEvent) { + super(e) + this.e = e + /** 事件处理 */ + if (this.review()) return + /** 处理回复 */ + this.reply() + /** raw */ + this.raw_message() + /** 处理消息 */ + this.deal() + } + + /** + * 处理事件 + */ + async deal () { + /** 主人 */ + if (config.master.includes(String(this.e.user_id))) { + this.e.isMaster = true + this.e.isAdmin = true + } else if (config.admin.includes(String(this.e.user_id))) { + /** 管理员 */ + this.e.isAdmin = true + } + + if (this.e.contact.scene === 'private') { + this.e.isPrivate = true + this.e.logText = `[Private:${this.e.sender.nick || ''}(${this.e.user_id})]` + logger.bot('info', this.e.self_id, `${logger.green('私聊通知: ')}[${this.e.user_id}(${this.e.sender.nick || ''})] ${this.e.raw_message}`) + } else if (this.e.contact.scene === 'group') { + this.e.isGroup = true + this.e.logText = `[Group:${this.e.group_id}-${this.e.user_id}(${this.e.sender.nick || ''})]` + this.GroupMsgPrint = review.GroupMsgPrint(this.e) + this.GroupMsgPrint && logger.bot('info', this.e.self_id, `${logger.green('群通知: ')}[${this.e.group_id}-${this.e.user_id}(${this.e.sender.nick || ''})] ${this.e.raw_message}`) + } else { + logger.bot('info', this.e.self_id, `未知来源通知事件:${JSON.stringify(this.e)}`) + } + + /* eslint-disable no-labels */ + a: + for (const index of pluginLoader.acceptIds) { + const app = pluginLoader.PluginList[index] + /** 判断事件 */ + if (!this.filtEvent(app.event)) continue + + /** 检查黑白名单插件 */ + if ('GroupCD' in this.config && !review.PluginEnable(app, this.config)) continue + + /** 日志方法字符串 */ + this.e.logFnc = `[${app.file.dir}][${app.name}][accept]` + const logFnc = logger.fnc(`[${app.name}][accept]`) + + /** 计算插件处理时间 */ + const start = Date.now() + + let res + + try { + if (typeof app.accept === 'function') { + res = await app.accept(this.e) + } else { + const cla = new (app.file.Fnc as unknown as new () => ExtendedPlugin)() + if (!cla.accept || typeof cla.accept !== 'function') continue + (cla.e) = this.e + res = await cla.accept(this.e) + } + + if (res !== false) { + this.GroupMsgPrint && logger.bot('info', this.e.self_id, `${logFnc} 处理完成 ${logger.green(Date.now() - start + 'ms')}`) + break a + } + } catch (error: any) { + logger.error(`${logFnc}`) + logger.error(error.stack || error.message || JSON.stringify(error)) + break a + } + } + } + + /** + * 构建原始消息 + */ + raw_message () { + switch (this.e.sub_event) { + /** 好友头像戳一戳 */ + case 'friend_poke': { + this.e.raw_message = '[好友戳一戳]: 戳了你一下' + break + } + /** 好友消息撤回 */ + case 'friend_recall': { + this.e.raw_message = `[好友消息撤回]: ${this.e.content.message_id}` + break + } + /** 私聊文件上传 */ + case 'friend_file_uploaded': { + const content = this.e.content + const { url } = content + this.e.raw_message = `[私聊文件上传]: ${url}` + break + } + /** 群头像戳一戳 */ + case 'group_poke': { + const { operator_uid, operator_uin, target_uid, target_uin } = this.e.content + this.e.raw_message = `[群戳一戳]: ${operator_uid || operator_uin} 戳了戳 ${target_uid || target_uin}` + break + } + /** 群消息撤回 */ + case 'group_recall': { + const { operator_uid, operator_uin, message_id } = this.e.content + this.e.raw_message = `[群消息撤回]: ${operator_uid || operator_uin} 撤回了一条消息 ${message_id}` + break + } + /** 群文件上传 */ + case 'group_file_uploaded': { + const { url, operator_uid, operator_uin } = this.e.content + this.e.raw_message = `[群文件上传]: ${operator_uid || operator_uin} 上传了 ${url}` + break + } + /** 群成员增加 */ + case 'group_member_increase': { + const { operator_uid, operator_uin, target_uid, target_uin, type } = this.e.content + this.e.raw_message = `[群成员新增]: ${operator_uid || operator_uin} ${type === 'invite' ? '邀请' : '同意'} ${target_uid || target_uin} 加入群聊` + break + } + /** 群成员减少 */ + case 'group_member_decrease': { + switch (this.e.content.type) { + case 'leave': { + const { target_uid, target_uin } = this.e.content + this.e.raw_message = `[群成员减少]: ${target_uid || target_uin} 主动退出群聊` + break + } + // 群成员被踢 + case 'kick': { + const { operator_uid, operator_uin, target_uid, target_uin } = this.e.content + this.e.raw_message = `[群成员减少]: ${operator_uid || operator_uin} 将 ${target_uid || target_uin} 踢出群聊` + break + } + // bot被踢 + case 'kick_me': { + const { operator_uid, operator_uin } = this.e.content + this.e.raw_message = `[群成员减少]: 机器人被移除群聊,操作人:${operator_uid || operator_uin}` + break + } + } + break + } + /** 群管理员变动 */ + case 'group_admin_changed': { + const { target_uid, target_uin, is_admin } = this.e.content + this.e.raw_message = `[群管理员变动]: ${target_uid || target_uin} 被${is_admin ? '设置' : '取消'}群管理员` + break + } + /** 群成员被禁言 */ + case 'group_member_ban': { + const { operator_uid, operator_uin, target_uid, target_uin, type } = this.e.content + this.e.raw_message = `[群成员禁言]: ${operator_uid || operator_uin} ${type === 'ban' ? '禁言' : '解禁'}了 ${target_uid || target_uin}` + break + } + /** 群签到 */ + case 'group_sign': { + const { target_uid, target_uin } = this.e.content + this.e.raw_message = `[群签到]: ${target_uid || target_uin}` + break + } + /** 群全员禁言 */ + case 'group_whole_ban': { + const { operator_uid, operator_uin, is_ban } = this.e.content + this.e.raw_message = `[群全员禁言]: ${operator_uid || operator_uin} ${is_ban ? '开启全员禁言' : '解除全员禁言'}` + break + } + /** 群名片改变 */ + case 'group_card_changed': { + const { operator_uid, operator_uin, target_uid, target_uin, new_card } = this.e.content + this.e.raw_message = `[群名片改变]: ${operator_uid || operator_uin} 修改了 ${target_uid || target_uin} 的名片为 ${new_card}` + break + } + /** 群成员专属头衔改变 */ + case 'group_member_unique_title_changed': { + const { target_uid, target_uin, title } = this.e.content + this.e.raw_message = `[群头衔更改]: ${target_uid || target_uin} 的专属头衔改变为 ${title}` + break + } + /** 群精华消息改变 */ + case 'group_essence_changed': { + const { operator_uid, operator_uin, target_uid, target_uin, message_id, is_set } = this.e.content + this.e.raw_message = `[群精华消息]: ${operator_uid || operator_uin} ${is_set ? `将${target_uid || target_uin}的消息${message_id}设置为精华消息` : `取消了${target_uid || target_uin}精华消息 ${message_id}`}` + break + } + /** 群表情回应 */ + case 'group_message_reaction': { + const { message_id, face_id } = this.e.content + this.e.raw_message = `[群表情回应]: ${this.e.user_id} 给消息 ${message_id} 回应了一个${face_id}的表情` + break + } + default: { + (this.e as any).raw_message = `[未知事件]: ${JSON.stringify(this.e)}` + } + } + } +} diff --git a/src/event/notice.ts b/src/event/notice.ts index 7ce287cb..bb71ed96 100644 --- a/src/event/notice.ts +++ b/src/event/notice.ts @@ -1,5 +1,5 @@ import { KarinEvent } from './event' -import { contact, Sender, SubEventForEvent, NoticeEvent } from 'karin/types' +import { contact, Sender, NoticeType, EventToSubEvent } from 'karin/types' /** * - 通知事件基类 @@ -43,11 +43,11 @@ export class KarinNotice extends KarinEvent { /** * 事件子类型 */ - sub_event: SubEventForEvent<'notice'> + sub_event: EventToSubEvent['notice'] /** * 事件对应的内容参数 */ - content: NoticeEvent> + content: NoticeType[EventToSubEvent['notice']] /** * 群ID */ @@ -60,5 +60,5 @@ export class KarinNotice extends KarinEvent { /** * - 事件对应的内容参数 */ - content: NoticeEvent> + content: NoticeType[EventToSubEvent['notice']] } diff --git a/src/event/request.handler.ts b/src/event/request.handler.ts new file mode 100644 index 00000000..0c484348 --- /dev/null +++ b/src/event/request.handler.ts @@ -0,0 +1,120 @@ +import { review } from './review.handler' +import EventHandler from './event.handler' +import { logger, config } from 'karin/utils' +import { KarinRequestEvent } from 'karin/types' +import { ExtendedPlugin, pluginLoader } from 'karin/core' + +/** + * 请求事件 + */ +export default class RequestHandler extends EventHandler { + e: KarinRequestEvent + constructor (e: KarinRequestEvent) { + super(e) + this.e = e + /** 事件处理 */ + if (this.review()) return + /** 处理回复 */ + this.reply() + /** raw */ + this.raw_message() + /** 处理消息 */ + this.deal() + } + + /** + * 处理事件 + */ + async deal () { + /** 主人 */ + if (config.master.includes(String(this.e.user_id))) { + this.e.isMaster = true + this.e.isAdmin = true + } else if (config.admin.includes(String(this.e.user_id))) { + /** 管理员 */ + this.e.isAdmin = true + } + + if (this.e.contact.scene === 'private') { + this.e.isPrivate = true + this.e.logText = `[Private:${this.e.sender.nick || ''}(${this.e.user_id})]` + logger.bot('info', this.e.self_id, `${logger.green('私聊请求: ')}[${this.e.user_id}(${this.e.sender.nick || ''})] ${this.e.raw_message}`) + } else if (this.e.contact.scene === 'group') { + this.e.isGroup = true + this.e.logText = `[Group:${this.e.group_id}-${this.e.user_id}(${this.e.sender.nick || ''})]` + this.GroupMsgPrint = review.GroupMsgPrint(this.e) + this.GroupMsgPrint && logger.bot('info', this.e.self_id, `${logger.green('群请求: ')}[${this.e.group_id}-${this.e.user_id}(${this.e.sender.nick || ''})] ${this.e.raw_message}`) + } else { + logger.bot('info', this.e.self_id, `未知来源请求事件:${JSON.stringify(this.e)}`) + } + + /* eslint-disable no-labels */ + a: + for (const index of pluginLoader.acceptIds) { + const app = pluginLoader.PluginList[index] + /** 判断事件 */ + if (!this.filtEvent(app.event)) continue + + /** 检查黑白名单插件 */ + if ('GroupCD' in this.config && !review.PluginEnable(app, this.config)) continue + + /** 日志方法字符串 */ + this.e.logFnc = `[${app.file.dir}][${app.name}][accept]` + const logFnc = logger.fnc(`[${app.name}][accept]`) + + /** 计算插件处理时间 */ + const start = Date.now() + + let res + + try { + if (typeof app.accept === 'function') { + res = await app.accept(this.e) + } else { + const cla = new (app.file.Fnc as unknown as new () => ExtendedPlugin)() + if (!cla.accept || typeof cla.accept !== 'function') continue + (cla.e) = this.e + res = await cla.accept(this.e) + } + + if (res !== false) { + this.GroupMsgPrint && logger.bot('info', this.e.self_id, `${logFnc} 处理完成 ${logger.green(Date.now() - start + 'ms')}`) + break a + } + } catch (error: any) { + logger.error(`${logFnc}`) + logger.error(error.stack || error.message || JSON.stringify(error)) + break a + } + } + } + + /** + * 构建原始消息 + */ + raw_message () { + switch (this.e.sub_event) { + /** 好友申请 */ + case 'friend_apply': { + const { applier_uid, applier_uin, message } = this.e.content + this.e.raw_message = `[好友申请]: ${applier_uid || applier_uin} 申请理由: ${message}` + break + } + /** 群申请 */ + case 'group_apply': { + const { group_id, applier_uid, applier_uin, inviter_uid, inviter_uin, reason } = this.e.content + this.e.raw_message = `[群申请]: ${group_id} 申请人: ${applier_uid || applier_uin} 邀请人: ${inviter_uid || inviter_uin} 理由: ${reason}` + break + } + /** 邀请入群 */ + case 'invited_group': { + const { group_id, inviter_uid, inviter_uin } = this.e.content + this.e.raw_message = `[邀请入群]: ${group_id} 邀请人: ${inviter_uid || inviter_uin}` + break + } + default: { + (this.e as any).raw_message = `[未知事件]: ${JSON.stringify(this.e)}` + } + } + } +} diff --git a/src/event/request.ts b/src/event/request.ts index 6c2740bc..9533e099 100644 --- a/src/event/request.ts +++ b/src/event/request.ts @@ -1,5 +1,5 @@ import { KarinEvent } from './event' -import { contact, Sender, SubEventForEvent, RequestEvent } from 'karin/types' +import { contact, Sender, EventToSubEvent, RequestType, KarinRequestEvent } from 'karin/types' /** * - 请求事件基类 @@ -43,22 +43,23 @@ export class KarinRequest extends KarinEvent { /** * 事件子类型 */ - sub_event: SubEventForEvent<'request'> + sub_event: EventToSubEvent['request'] /** * 事件对应的内容参数 */ - content: RequestEvent> + content: RequestType[EventToSubEvent['request']] /** * 群ID */ group_id?: string }) { super({ event: 'request', event_id, self_id, user_id, group_id, time, contact, sender, sub_event }) - this.content = content + // ... + this.content = content as unknown as KarinRequestEvent } /** * - 事件对应的内容参数 */ - content: RequestEvent> + content: KarinRequestEvent } diff --git a/src/event/review.handler.ts b/src/event/review.handler.ts index aba50c4b..57916682 100644 --- a/src/event/review.handler.ts +++ b/src/event/review.handler.ts @@ -67,14 +67,12 @@ export const review = new (class Handler { this.GroupEnable = e => { /** 白名单不为空 */ if (Array.isArray(this.Config.WhiteList.groups) && this.Config.WhiteList.groups.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return this.Config.WhiteList.groups.includes(group_id) + return this.Config.WhiteList.groups.includes(String(e.group_id)) } /** 白名单为空 检查黑名单是否为空 */ if (Array.isArray(this.Config.BlackList.groups) && this.Config.BlackList.groups.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return !this.Config.BlackList.groups.includes(group_id) + return !this.Config.BlackList.groups.includes(String(e.group_id)) } /** 黑白名单都为空 */ @@ -87,8 +85,7 @@ export const review = new (class Handler { if (this.App.WhiteList.groups) { this.GroupEnable = e => { if (Array.isArray(this.Config.WhiteList.groups) && this.Config.WhiteList.groups.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return this.Config.WhiteList.groups.includes(group_id) + return this.Config.WhiteList.groups.includes(String(e.group_id)) } return true } @@ -99,8 +96,7 @@ export const review = new (class Handler { if (this.App.BlackList.groups) { this.GroupEnable = e => { if (Array.isArray(this.Config.BlackList.groups) && this.Config.BlackList.groups.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return !this.Config.BlackList.groups.includes(group_id) + return !this.Config.BlackList.groups.includes(String(e.group_id)) } return true } @@ -120,14 +116,12 @@ export const review = new (class Handler { this.UserEnable = e => { /** 白名单不为空 */ if (Array.isArray(this.Config.WhiteList.users) && this.Config.WhiteList.users.length) { - const user_id = (Number(e.user_id) || String(e.user_id)) as string - return this.Config.WhiteList.users.includes(user_id) + return this.Config.WhiteList.users.includes(String(e.user_id)) } /** 白名单为空 检查黑名单是否为空 */ if (Array.isArray(this.Config.BlackList.users) && this.Config.BlackList.users.length) { - const user_id = (Number(e.user_id) || String(e.user_id)) as string - return !this.Config.BlackList.users.includes(user_id) + return !this.Config.BlackList.users.includes(String(e.user_id)) } /** 黑白名单都为空 */ @@ -140,8 +134,7 @@ export const review = new (class Handler { if (this.App.WhiteList.users) { this.UserEnable = e => { if (Array.isArray(this.Config.WhiteList.users) && this.Config.WhiteList.users.length) { - const user_id = (Number(e.user_id) || String(e.user_id)) as string - return this.Config.WhiteList.users.includes(user_id) + return this.Config.WhiteList.users.includes(String(e.user_id)) } return true } @@ -152,8 +145,7 @@ export const review = new (class Handler { if (this.App.BlackList.users) { this.UserEnable = e => { if (Array.isArray(this.Config.BlackList.users) && this.Config.BlackList.users.length) { - const user_id = (Number(e.user_id) || String(e.user_id)) as string - return !this.Config.BlackList.users.includes(user_id) + return !this.Config.BlackList.users.includes(String(e.user_id)) } return true } @@ -173,14 +165,12 @@ export const review = new (class Handler { this.GroupMsgPrint = e => { /** 白名单不为空 */ if (Array.isArray(this.Config.WhiteList.GroupMsgLog) && this.Config.WhiteList.GroupMsgLog.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return this.Config.WhiteList.GroupMsgLog.includes(group_id) + return this.Config.WhiteList.GroupMsgLog.includes(String(e.group_id)) } /** 白名单为空 检查黑名单是否为空 */ if (Array.isArray(this.Config.BlackList.GroupMsgLog) && this.Config.BlackList.GroupMsgLog.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return !this.Config.BlackList.GroupMsgLog.includes(group_id) + return !this.Config.BlackList.GroupMsgLog.includes(String(e.group_id)) } /** 黑白名单都为空 */ @@ -193,8 +183,7 @@ export const review = new (class Handler { if (this.App.WhiteList.GroupMsgLog) { this.GroupMsgPrint = e => { if (Array.isArray(this.Config.WhiteList.GroupMsgLog) && this.Config.WhiteList.GroupMsgLog.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return this.Config.WhiteList.GroupMsgLog.includes(group_id) + return this.Config.WhiteList.GroupMsgLog.includes(String(e.group_id)) } return true } @@ -205,8 +194,7 @@ export const review = new (class Handler { if (this.App.BlackList.GroupMsgLog) { this.GroupMsgPrint = e => { if (Array.isArray(this.Config.BlackList.GroupMsgLog) && this.Config.BlackList.GroupMsgLog.length) { - const group_id = (Number(e.group_id) || String(e.group_id)) as string - return !this.Config.BlackList.GroupMsgLog.includes(group_id) + return !this.Config.BlackList.GroupMsgLog.includes(String(e.group_id)) } return true } diff --git a/src/types/adapter.ts b/src/types/adapter.ts index fddaca03..e2eb50e1 100644 --- a/src/types/adapter.ts +++ b/src/types/adapter.ts @@ -1,7 +1,8 @@ import { WebSocket } from 'ws' import { IncomingMessage } from 'http' +import { contact } from './event' import { KarinElement, KarinNodeElement } from './element' -import { contact, PushMessageBody, EssenceMessageBody, FriendInfo, GroupInfo, GroupMemberInfo, GroupHonorInfo } from './event' +import { PushMessageBody, EssenceMessageBody, FriendInfo, GroupInfo, GroupMemberInfo, GroupHonorInfo } from './api' export interface KarinAdapter { /** diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 00000000..e75a1fe1 --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,301 @@ +import { contact, Sender, KarinElement } from './index' + +/** + * - 转发、历史消息返回的结构 + */ +export interface PushMessageBody { + /** + * - 消息发送时间 + */ + time: number + /** + * - 消息ID + */ + message_id: string + /** + * - 消息序列号 + */ + message_seq: number + /** + * - 消息来源 + */ + contact: contact + /** + * - 消息发送者 + */ + sender: Sender + /** + * - 消息元素 + */ + elements: Array +} + +/** + * - 精华消息返回的结构 + */ +export interface EssenceMessageBody { + /** + * - 群ID + */ + group_id: string + /** + * - 发送者uid + */ + sender_uid: string + /** + * - 发送者uin + */ + sender_uin: string + /** + * - 发送者昵称 + */ + sender_nick: string + /** + * - 操作者uid + */ + operator_uid: string + /** + * - 操作者uin + */ + operator_uin: string + /** + * - 操作者昵称 + */ + operator_nick: string + /** + * - 操作时间 + */ + operation_time: number + /** + * - 消息时间 + */ + message_time: number + /** + * - 消息ID + */ + message_id: string + /** + * - 消息序列号 + */ + message_seq: string + /** + * - 被设置的精华消息元素文本 + */ + json_elements: string +} + +/** + * - QQ好友信息 + */ +export interface FriendInfo { + /** + * - 用户UID + */ + uid: string + /** + * - 用户UIN + */ + uin: string + /** + * - qid + */ + qid: string + /** + * - 名称 + */ + nick: string + /** + * - 备注 + */ + remark?: string + /** + * - 用户等级 + */ + level?: number + /** + * - 生日 + */ + birthday?: string + /** + * - 登录天数 + */ + login_day?: number + /** + * - 点赞数 + */ + vote_cnt?: number + /** + * - 学校是否已核实 + */ + is_school_verified?: boolean + /** + * - 年龄 + * - 拓展字段 + */ + age?: number + /** + * - 性别 + * - 拓展字段 + */ + sex?: 'male' | 'female' | 'unknown' + ext?: { + /** + * 大会员 + */ + big_vip?: boolean + /** + * 好莱坞/腾讯视频会员 + */ + hollywood_vip?: boolean + /** + * QQ会员 + */ + qq_vip?: boolean + /** + * QQ超级会员 + */ + super_vip?: boolean + /** + * 是否已经赞过 + */ + voted?: boolean + } +} + +/** + * - QQ群信息 + */ +export interface GroupInfo { + /** + * - 群ID + */ + group_id: string + /** + * - 群名称 + */ + group_name: string + /** + * - 群备注 + */ + group_remark?: string + /** + * - 群主UID + */ + owner: string + /** + * - 群管理员UID列表 + */ + admins: Array + /** + * - 最大成员数 + */ + max_member_count: number + /** + * - 当前成员数 + */ + member_count: number + /** + * - 群UIN + */ + group_uin?: string +} + +/** + * - 群成员信息 + */ +export interface GroupMemberInfo { + /** + * - 用户UID + */ + uid: string + /** + * - 用户UIN + */ + uin: string + /** + * - 用户昵称 + */ + nick: string + /** + * - 年龄 + */ + age: number + /** + * - 群内头衔 + */ + unique_title: string + /** + * - 群内头衔过期时间 + */ + unique_title_expire_time: number + /** + * - 群名片 + */ + card?: string + /** + * - 加群时间 + */ + join_time?: number + /** + * - 最后活跃时间 + */ + last_active_time?: number + /** + * - 用户等级 + */ + level: number + /** + * - 禁言时间 + */ + shut_up_time: number + /** + * - 距离 + */ + distance?: number + /** + * - 荣誉列表 + */ + honors?: Array + /** + * - 是否好友 + */ + unfriendly?: boolean + /** + * - 是否可修改群名片 + */ + card_changeable?: boolean +} + +/** + * 群荣誉信息 + */ +export interface GroupHonorInfo { + /** + * - 荣誉成员uid + */ + uid: string + /** + * - 荣誉成员uin + */ + uin: string + /** + * - 荣誉成员昵称 + */ + nick: string + /** + * - 荣誉名称 + */ + honor_name: string + /** + * - 荣誉图标url + */ + avatar: string + /** + * - 荣誉id + */ + id: number + /** + * - 荣誉描述 + */ + description: string +} diff --git a/src/types/event.ts b/src/types/event.ts index 4c9b04d5..c3d8a554 100644 --- a/src/types/event.ts +++ b/src/types/event.ts @@ -1,5 +1,6 @@ +import { KarinAdapter } from './adapter' import { KarinElement } from './element' -import { KarinMessage, KarinNotice, KarinRequest } from 'karin/event' +import { Reply, replyCallback } from './reply' /** * - 事件类型 @@ -11,11 +12,6 @@ export type Event = 'message' | 'notice' | 'request' | 'meta_event' | 'message_s */ export type Scene = 'group' | 'private' | 'guild' | 'nearby' | 'stranger' | 'stranger_from_group' -/** - * - 事件子类型 - */ -export type SubEvent = 'group_message' | 'friend_message' | 'guild_message' | 'nearby' | 'stranger' | 'stranger_from_group' | 'friend_poke' | 'friend_recall' | 'friend_file_uploaded' | 'group_poke' | 'group_card_changed' | 'group_member_unique_title_changed' | 'group_essence_changed' | 'group_recall' | 'group_member_increase' | 'group_member_decrease' | 'group_admin_changed' | 'group_member_ban' | 'group_sign' | 'group_whole_ban' | 'group_file_uploaded' | 'friend_apply' | 'group_apply' | 'invited_group' - /** * - 类型映射 */ @@ -23,13 +19,14 @@ export type EventToSubEvent = { message: 'group_message' | 'friend_message' | 'guild_message' | 'nearby' | 'stranger' | 'stranger_from_group' notice: 'friend_poke' | 'friend_recall' | 'friend_file_uploaded' | 'group_poke' | 'group_card_changed' | 'group_member_unique_title_changed' | 'group_essence_changed' | 'group_recall' | 'group_member_increase' | 'group_member_decrease' | 'group_admin_changed' | 'group_member_ban' | 'group_sign' | 'group_whole_ban' | 'group_file_uploaded' | 'group_message_reaction' request: 'friend_apply' | 'group_apply' | 'invited_group' - meta_event: 'group_message' | 'friend_message' | 'guild_message' + meta_event: 'group_message' | 'friend_message' | 'guild_message' | 'nearby' | 'stranger' | 'stranger_from_group' + message_sent: 'group_message' | 'friend_message' | 'guild_message' | 'nearby' | 'stranger' | 'stranger_from_group' } /** - * - 事件子类型泛型 + * - 事件子类型 */ -export type SubEventForEvent = E extends keyof EventToSubEvent ? EventToSubEvent[E] : never +export type SubEvent = EventToSubEvent['message'] | EventToSubEvent['notice'] | EventToSubEvent['request'] /** * - 权限类型 @@ -84,7 +81,7 @@ export interface Sender { /** * - 通知事件类型 */ -export interface NoticeTytpe { +export interface NoticeType { /** * - 私聊戳一戳 */ @@ -530,10 +527,6 @@ export interface NoticeTytpe { is_set: boolean } } -/** - * - 通知事件泛型 - */ -export type NoticeEvent = E extends keyof NoticeTytpe ? NoticeTytpe[E] : never /** * - 请求事件类型 @@ -603,323 +596,235 @@ export interface RequestType { inviter_uin: string } } -/** - * - 请求事件泛型 - */ -export type RequestEvent = E extends keyof RequestType ? RequestType[E] : never /** - * - 转发、历史消息返回的结构 + * - 事件基类 */ -export interface PushMessageBody { - /** - * - 消息发送时间 - */ - time: number - /** - * - 消息ID - */ - message_id: string - /** - * - 消息序列号 - */ - message_seq: number - /** - * - 消息来源 - */ - contact: contact +export interface KarinEventType { /** - * - 消息发送者 - */ - sender: Sender + * - 机器人ID 请尽量使用UID + */ + self_id: string /** - * - 消息元素 + * - 用户ID */ - elements: Array -} - -/** - * - 精华消息返回的结构 - */ -export interface EssenceMessageBody { + user_id: string /** - * - 群ID + * - 群ID 仅在群聊中存在 + * @default '' */ group_id: string /** - * - 发送者uid + * - 事件类型 */ - sender_uid: string + event: Event /** - * - 发送者uin + * - 事件子类型 */ - sender_uin: string + sub_event: EventToSubEvent[Event] /** - * - 发送者昵称 + * - 事件ID */ - sender_nick: string + event_id: string /** - * - 操作者uid + * - 事件触发时间戳 */ - operator_uid: string - /** - * - 操作者uin - */ - operator_uin: string - /** - * - 操作者昵称 - */ - operator_nick: string - /** - * - 操作时间 - */ - operation_time: number + time: number /** - * - 消息时间 + * - 事件联系人信息 */ - message_time: number + contact: contact /** - * - 消息ID + * - 事件发送者信息 */ - message_id: string + sender: Sender /** - * - 消息序列号 + * - 是否为主人 + * @default false */ - message_seq: string + isMaster: boolean /** - * - 被设置的精华消息元素文本 + * - 是否为管理员 + * @default false */ - json_elements: string -} - -/** - * - QQ好友信息 - */ -export interface FriendInfo { + isAdmin: boolean /** - * - 用户UID + * - 是否为私聊 + * @default false */ - uid: string + isPrivate: boolean /** - * - 用户UIN + * - 是否为群聊 + * @default false */ - uin: string + isGroup: boolean /** - * - qid + * - 是否为频道 + * @default false */ - qid: string + isGuild: boolean /** - * - 名称 + * - 是否为群临时会话 + * @default false */ - nick: string + isGroupTemp: boolean /** - * - 备注 + * - 日志函数字符串 */ - remark?: string + logFnc: string /** - * - 用户等级 + * - 日志用户字符串 */ - level?: number + logText: string /** - * - 生日 + * - 存储器 由开发者自行调用 */ - birthday?: string + store: Map /** - * - 登录天数 + * - 标准含义是代表原始事件文本,在karin是指经过karin处理后的事件文本 */ - login_day?: number + raw_message: string /** - * - 点赞数 + * - 回复函数 */ - vote_cnt?: number + reply: Reply /** - * - 学校是否已核实 + * - 回复函数 由适配器实现,开发者不应该直接调用 */ - is_school_verified?: boolean + replyCallback: replyCallback /** - * - 年龄 - * - 拓展字段 + * - bot实现 */ - age?: number - /** - * - 性别 - * - 拓展字段 - */ - sex?: 'male' | 'female' | 'unknown' - ext?: { - /** - * 大会员 - */ - big_vip?: boolean - /** - * 好莱坞/腾讯视频会员 - */ - hollywood_vip?: boolean - /** - * QQ会员 - */ - qq_vip?: boolean - /** - * QQ超级会员 - */ - super_vip?: boolean - /** - * 是否已经赞过 - */ - voted?: boolean - } -} + bot: KarinAdapter -/** - * - QQ群信息 - */ -export interface GroupInfo { - /** - * - 群ID - */ - group_id: string - /** - * - 群名称 - */ - group_name: string - /** - * - 群备注 - */ - group_remark?: string - /** - * - 群主UID - */ - owner: string - /** - * - 群管理员UID列表 - */ - admins: Array - /** - * - 最大成员数 - */ - max_member_count: number - /** - * - 当前成员数 - */ - member_count: number - /** - * - 群UIN - */ - group_uin?: string } /** - * - 群成员信息 + * - 消息事件基类 */ -export interface GroupMemberInfo { - /** - * - 用户UID - */ - uid: string - /** - * - 用户UIN - */ - uin: string +export interface KarinMessageEvent extends KarinEventType { /** - * - 用户昵称 + * - 消息体 */ - nick: string + event: 'message' | 'message_sent' /** - * - 年龄 - */ - age: number + * - 消息ID + */ + message_id: string /** - * - 群内头衔 + * - 消息序列号 */ - unique_title: string + message_seq?: string /** - * - 群内头衔过期时间 + * - 原始消息文本 */ - unique_title_expire_time: number + raw_message: string /** - * - 群名片 + * - 消息体元素 */ - card?: string + elements: Array /** - * - 加群时间 + * - 框架处理后的文本 */ - join_time?: number + msg: string /** - * - 最后活跃时间 + * - 图片数组 */ - last_active_time?: number + image: Array /** - * - 用户等级 + * - AT数组 */ - level: number + at: Array /** - * - 禁言时间 + * - 是否AT机器人 */ - shut_up_time: number + atBot: boolean /** - * - 距离 + * - 是否AT全体 */ - distance?: number + atAll: boolean /** - * - 荣誉列表 + * - 文件元素 */ - honors?: Array + file: object /** - * - 是否好友 + * - 引用消息ID */ - unfriendly?: boolean + reply_id: string /** - * - 是否可修改群名片 + * - 消息别名 */ - card_changeable?: boolean + alias: string } /** - * 群荣誉信息 + * - 基础通知事件接口,包含所有共同属性 */ -export interface GroupHonorInfo { - /** - * - 荣誉成员uid - */ - uid: string - /** - * - 荣誉成员uin - */ - uin: string - /** - * - 荣誉成员昵称 - */ - nick: string - /** - * - 荣誉名称 - */ - honor_name: string - /** - * - 荣誉图标url - */ - avatar: string - /** - * - 荣誉id - */ - id: number - /** - * - 荣誉描述 - */ - description: string +export interface KarinNoticeEventBase extends KarinEventType { + event: 'notice' } +/** + * - 辅助类型,用于生成 KarinNoticeEvent 的联合类型 + */ +export type NoticeEvent = KarinNoticeEventBase & { + sub_event: T + content: NoticeType[T] +} + +/** + * - 通知事件基类 + */ +export type KarinNoticeEvent = NoticeEvent<'friend_poke'> + | NoticeEvent<'friend_recall'> + | NoticeEvent<'friend_file_uploaded'> + | NoticeEvent<'group_poke'> + | NoticeEvent<'group_card_changed'> + | NoticeEvent<'group_member_unique_title_changed'> + | NoticeEvent<'group_essence_changed'> + | NoticeEvent<'group_recall'> + | NoticeEvent<'group_member_increase'> + | NoticeEvent<'group_member_decrease'> + | NoticeEvent<'group_admin_changed'> + | NoticeEvent<'group_member_ban'> + | NoticeEvent<'group_sign'> + | NoticeEvent<'group_whole_ban'> + | NoticeEvent<'group_file_uploaded'> + | NoticeEvent<'group_message_reaction'> + +/** + * - 请求事件基类 + */ +export interface KarinRequestEventBase extends KarinEventType { + event: 'request' +} + +/** + * - 辅助类型,用于生成 KarinRequestEvent 的联合类型 + */ +export type RequestEvent = KarinRequestEventBase & { + sub_event: T + content: RequestType[T] +} + +/** + * - 请求事件基类 + */ +export type KarinRequestEvent = RequestEvent<'friend_apply'> + | RequestEvent<'group_apply'> + | RequestEvent<'invited_group'> + export interface EMap { - message: KarinMessage - notice: KarinNotice - request: KarinRequest - message_sent: KarinMessage + message: KarinMessageEvent + notice: KarinNoticeEvent + request: KarinRequestEvent + message_sent: KarinMessageEvent // 元事件不进入插件 不需要定义 meta_event: any } -export type E = EMap[T] -export type EType = KarinMessage | KarinNotice | KarinRequest +export type EType = KarinMessageEvent | KarinNoticeEvent | KarinRequestEvent /** * 根据accept函数是否存在来决定e的类型 */ -export type EventType = T extends { accept: (e: any) => Promise } ? KarinNotice | KarinRequest : KarinMessage +export type EventType = T extends { accept: (e: KarinNoticeEvent | KarinRequestEvent) => Promise } ? KarinNoticeEvent | KarinRequestEvent : KarinMessageEvent diff --git a/src/types/index.ts b/src/types/index.ts index 0ce1d882..a565fe9a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,3 +7,4 @@ export * from './onebots11' export * from './render' export * from './plugin' export * from './reply' +export * from './api' diff --git a/src/types/plugin.ts b/src/types/plugin.ts index d182a360..96133751 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -1,7 +1,7 @@ import schedule from 'node-schedule' import { KarinNodeElement } from './element' import { Reply, replyCallback } from './reply' -import { EventType, Event, Permission, SubEvent } from './event' +import { EventType, Event, Permission, SubEvent, KarinMessageEvent, KarinNoticeEvent, KarinRequestEvent } from './event' /** * - 插件根目录名称 @@ -92,7 +92,7 @@ export interface PluginHandler { /** * - handler的处理方法名称 */ - fnc: string + fnc: string | Function /** * - handler优先级 不填默认为主优先度 */ @@ -173,7 +173,7 @@ export interface PluginType { * - 上报事件 * - 根据上报中的event字段来获取e的事件类型 */ - e: EventType + e: KarinMessageEvent | KarinNoticeEvent | KarinRequestEvent /** * - 快速回复 */ @@ -273,7 +273,7 @@ export interface PluginApps { /** * - accept函数存在 */ - accept: boolean + accept: boolean | Function /** * - 命令规则 */ diff --git a/src/utils/button.ts b/src/utils/button.ts index eb427864..14d41bca 100644 --- a/src/utils/button.ts +++ b/src/utils/button.ts @@ -1,8 +1,8 @@ import logger from './logger' -import { KarinMessage } from 'karin/event/message' +import { KarinMessageEvent } from 'karin/types' import { pluginLoader as loader, Plugin } from 'karin/core' -export const button = async (e: KarinMessage) => { +export const button = async (e: KarinMessageEvent) => { const button = [] for (const v of loader.buttonIds) { const info = loader.PluginList[v] diff --git a/src/utils/config.ts b/src/utils/config.ts index 7e13e998..fdae95d2 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -95,7 +95,6 @@ export const config = new (class Cfg { /** * 主人列表 - * @returns {string[]} */ get master () { return this.Config.master || [] @@ -137,9 +136,24 @@ export const config = new (class Cfg { const config = this.getYaml('config', 'config', true) const defSet = this.getYaml('defSet', 'config', false) const data = { ...defSet, ...config } + const Config = { + ...data, + WhiteList: { + users: data.WhiteList.users.map((i: string | number) => String(i)), + groups: data.WhiteList.groups.map((i: string | number) => String(i)), + GroupMsgLog: data.WhiteList.GroupMsgLog.map((i: string | number) => String(i)), + }, + BlackList: { + users: data.BlackList.users.map((i: string | number) => String(i)), + groups: data.BlackList.groups.map((i: string | number) => String(i)), + GroupMsgLog: data.BlackList.GroupMsgLog.map((i: string | number) => String(i)), + }, + master: data.master.map((i: string | number) => String(i)), + admin: data.admin.map((i: string | number) => String(i)), + } /** 缓存 */ - this.change.set(key, data) - return data + this.change.set(key, Config) + return Config } /** diff --git a/src/utils/handler.ts b/src/utils/handler.ts index c9fc05ef..6d626299 100644 --- a/src/utils/handler.ts +++ b/src/utils/handler.ts @@ -1,6 +1,6 @@ import lodash from 'lodash' import logger from './logger' -import { EventType, PluginType } from 'karin/types' +import { EventType, PluginType, PluginApps } from 'karin/types' import { Plugin, pluginLoader as loader } from 'karin/core' /** @@ -12,7 +12,7 @@ export const handler = new (class EventHandler { * @param index 插件索引 * @param Class 插件类 */ - add (index: string, Class: PluginType) { + add (index: string, Class: PluginType | PluginApps) { lodash.forEach(Class.handler, val => { if (!val.key) logger.error(`[Handler][Add]: [${Class.name}] 缺少 key`) if (!val.fnc) logger.error(`[Handler][Add]: [${Class.name}] 缺少 fnc`) @@ -26,17 +26,10 @@ export const handler = new (class EventHandler { /** * 删除事件处理器 * 如果不传参数则删除所有处理器 + * @param index 插件索引 + * @param key 事件键 */ - del ({ index = '', key = '' }: { - /** - * 插件索引 - */ - index: string | '' - /** - * 事件键 - */ - key: string | '' - }) { + del (index = '', key = '') { /** 无参 */ if (!key && !index) { loader.handlerIds = {}