diff --git a/.gitignore b/.gitignore index fb8c17d..54b7f4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -config.yml -subscribe.yml +config.yaml +subscribe.yaml node_modules/ dist/ *.zip diff --git a/config-example.yml b/config-example.yaml similarity index 100% rename from config-example.yml rename to config-example.yaml diff --git a/index.js b/index.js index 9ab2605..85b74c8 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ import 'node-telegram-bot-api'; import App from './dist/server/App'; -new App("./config.yml"); \ No newline at end of file +new App("./config.yaml"); \ No newline at end of file diff --git a/package.json b/package.json index 8f778d1..b99d198 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "isekai-feedbot", - "version": "1.0.0", + "version": "1.3.0", "description": "", "main": "index.js", "type": "module", @@ -54,6 +54,7 @@ "typescript": "^4.5.4" }, "imports": { - "#ibot/*": "./dist/*" + "#ibot/*": "./dist/server/*", + "#ibot-api/*": "./dist/api/*" } } diff --git a/push-test/index.js b/push-test/index.js index 8ccdc2f..3176cb8 100644 --- a/push-test/index.js +++ b/push-test/index.js @@ -2,7 +2,7 @@ var fs = require('fs'); var Yaml = require('yaml'); var Pusher = require('pusher'); -var config = Yaml.parse(fs.readFileSync('../config.yml', {encoding: 'utf-8'})); +var config = Yaml.parse(fs.readFileSync('../config.yaml', {encoding: 'utf-8'})); var pusher = new Pusher({ appId: config.service.pusher.app_id, diff --git a/src/api/PluginController.ts b/src/api/PluginController.ts index ddb18e8..0ab28f8 100644 --- a/src/api/PluginController.ts +++ b/src/api/PluginController.ts @@ -1,28 +1,64 @@ import App from "#ibot/App"; +import { PluginEvent } from "#ibot/PluginManager"; import { PluginApiBridge } from "#ibot/plugin/PluginApiBridge"; +import { Logger } from "#ibot/utils/Logger"; export class PluginController> { - static id?: string; - static pluginName?: string; - static pluginNameMsg?: string; - static description?: string; - static descriptionMsg?: string; + public static id: string; + public static pluginName?: string; + public static description?: string; - public _app!: App; - private _config!: ConfigType; + public static reloadWhenConfigUpdated?: boolean; + + private _app: App; + private _logger: Logger; + private _bridge: PluginApiBridge; + + public config!: ConfigType; constructor(app: App, pluginApi: PluginApiBridge) { this._app = app; + this._bridge = pluginApi; + + const ctor = this.constructor as typeof PluginController; + this._logger = app.getLogger(ctor.pluginName ?? "Plugin"); } public get app() { return this._app; } - public getLoagger() { + public get logger() { + return this._logger; + } + + public get event() { + return this._bridge.event; } public getMessage(msgId: string) { } + + public async _initialize(config: any): Promise { + await this._setConfig(config); + await this.initialize(config); + } + public async initialize(config: any): Promise { } + + public async destroy(): Promise { }; + + public async getDefaultConfig(): Promise { + return {}; + } + + public async _setConfig(config: any): Promise { + this.config = config; + await this.setConfig(config); + } + public async setConfig(config: any): Promise { } + + public useScope(scopeName: string, callback: (event: PluginEvent) => void) { + this._bridge.useScope(scopeName, callback); + } } \ No newline at end of file diff --git a/src/api/dataWrapper.ts b/src/api/dataWrapper.ts new file mode 100644 index 0000000..c33ab82 --- /dev/null +++ b/src/api/dataWrapper.ts @@ -0,0 +1,9 @@ +export function label(name: string) { + return { + _type: 'label', + label: name, + toString: () => { + return name; + } + }; +} \ No newline at end of file diff --git a/src/api/plugin.yaml b/src/api/plugin.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/src/plugins/ai-paint/Controller.ts b/src/plugins/ai-paint/PluginController.ts similarity index 92% rename from src/plugins/ai-paint/Controller.ts rename to src/plugins/ai-paint/PluginController.ts index 8757331..b5154c7 100644 --- a/src/plugins/ai-paint/Controller.ts +++ b/src/plugins/ai-paint/PluginController.ts @@ -1,6 +1,7 @@ +import { PluginController } from "#ibot-api/PluginController"; import App from "#ibot/App"; import { CommonReceivedMessage, ImageMessage } from "#ibot/message/Message"; -import { MessagePriority, PluginController, PluginEvent } from "#ibot/PluginManager"; +import { MessagePriority, PluginEvent } from "#ibot/PluginManager"; import got from "got/dist/source"; export type QueueData = { @@ -40,18 +41,28 @@ export type GPUInfoResponse = { temperature: number, } -export default class StableDiffusionController implements PluginController { - private config!: Awaited>; +const defaultConfig = { + api: [] as ApiConfig[], + size: [] as SizeConfig[], + banned_words: [] as string[], + banned_output_words: [] as string[], + queue_max_size: 4, + rate_limit: 1, + rate_limit_minutes: 2, + safe_temperature: null as number | null, + translate_caiyunai: { + key: "" + } +} +export default class StableDiffusionController extends PluginController { private SESSION_KEY_GENERATE_COUNT = 'stablediffusion_generateCount'; - - public event!: PluginEvent; - public app: App; + public chatGPTClient: any; - public id = 'stablediffusion'; - public name = 'Stable Diffusion'; - public description = '绘画生成'; + public static id = 'stablediffusion'; + public static pluginName = 'Stable Diffusion'; + public static description = '绘画生成'; private mainApi!: ApiConfig; private defaultSize!: SizeConfig; @@ -63,31 +74,11 @@ export default class StableDiffusionController implements PluginController { private sizeMatcher: RegExp[][] = []; private bannedWordsMatcher: RegExp[] = []; - constructor(app: App) { - this.app = app; - } - async getDefaultConfig() { - return { - api: [] as ApiConfig[], - size: [] as SizeConfig[], - banned_words: [] as string[], - banned_output_words: [] as string[], - queue_max_size: 4, - rate_limit: 1, - rate_limit_minutes: 2, - safe_temperature: null as number | null, - translate_caiyunai: { - key: "" - } - }; + return ; } async initialize(config: any) { - await this.updateConfig(config); - - this.event.init(this); - this.event.registerCommand({ command: 'draw', name: '使用英语短句或关键词生成绘画', @@ -123,9 +114,7 @@ export default class StableDiffusionController implements PluginController { this.running = false; } - async updateConfig(config: any) { - this.config = config; - + async setConfig(config: any) { let mainApi = this.config.api.find(api => api.main); if (!mainApi) { throw new Error('No main API found in stablediffusion config.'); diff --git a/src/plugins/ai-paint/plugin.yaml b/src/plugins/ai-paint/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/ai-paint/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/dice/Controller.ts b/src/plugins/dice/PluginController.ts similarity index 77% rename from src/plugins/dice/Controller.ts rename to src/plugins/dice/PluginController.ts index 6febcfc..17b6701 100644 --- a/src/plugins/dice/Controller.ts +++ b/src/plugins/dice/PluginController.ts @@ -1,38 +1,29 @@ import { CommonReceivedMessage } from "#ibot/message/Message"; import App from "#ibot/App"; -import { CommandInputArgs, PluginController, PluginEvent } from "#ibot/PluginManager"; - -export default class DiceController implements PluginController { - public event!: PluginEvent; - public app: App; - - public id = 'dice'; - public name = 'DND骰子'; - public description = '骰一个DND骰子,格式:1d6+3'; - - private config!: Awaited>; - - constructor(app: App) { - this.app = app; +import { CommandInputArgs, PluginEvent } from "#ibot/PluginManager"; +import { PluginController } from "#ibot-api/PluginController"; +import { label } from "#ibot-api/dataWrapper"; + +export const defaultConfig = { + messages: { + diceFormatError: [ + '骰子格式错误:{{{error}}}', + '输入错误:{{{error}}}', + '错误的骰子格式:{{{error}}}', + ] } +}; + +export default class DiceController extends PluginController { + public static id = 'dice'; + public static pluginName = 'DND骰子'; + public static description = '骰一个DND骰子,格式:1d6+3'; async getDefaultConfig() { - return { - messages: { - diceFormatError: [ - '骰子格式错误:{{{error}}}', - '输入错误:{{{error}}}', - '错误的骰子格式:{{{error}}}', - ] - } - }; + return defaultConfig; } public async initialize(config: any): Promise { - await this.updateConfig(config); - - this.event.init(this); - this.event.registerCommand({ command: 'roll', name: 'DND骰子', @@ -56,10 +47,6 @@ export default class DiceController implements PluginController { }); } - async updateConfig(config: any) { - this.config = config; - } - private async rollDice(args: CommandInputArgs, message: CommonReceivedMessage) { await message.markRead(); diff --git a/src/plugins/dice/plugin.yaml b/src/plugins/dice/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/dice/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/isekai-bbs-quickly-post/IsekaiBBSQuicklyPostController.ts b/src/plugins/isekai-bbs-quickly-post/PluginController.ts similarity index 82% rename from src/plugins/isekai-bbs-quickly-post/IsekaiBBSQuicklyPostController.ts rename to src/plugins/isekai-bbs-quickly-post/PluginController.ts index 4b0beef..32b0911 100644 --- a/src/plugins/isekai-bbs-quickly-post/IsekaiBBSQuicklyPostController.ts +++ b/src/plugins/isekai-bbs-quickly-post/PluginController.ts @@ -1,12 +1,13 @@ import App from "#ibot/App"; import { AddReplyMode, CommonReceivedMessage } from "#ibot/message/Message"; -import { CommandInputArgs, MessagePriority, PluginController, PluginEvent } from "#ibot/PluginManager"; +import { CommandInputArgs, MessagePriority, PluginEvent } from "#ibot/PluginManager"; import got from "got/dist/source"; import { RandomMessage } from "#ibot/utils/RandomMessage"; import { QQForwardingMessage } from "#ibot/robot/adapter/qq/Message"; import QQRobot from "#ibot/robot/adapter/QQRobot"; import { GroupSender } from "#ibot/message/Sender"; import { Robot } from "#ibot/robot/Robot"; +import { PluginController } from "#ibot-api/PluginController"; export type IsekaiBBSQuicklyPostConfig = { api_endpoint: string, @@ -37,48 +38,38 @@ export type IsekaiQuicklyPostBody = { messages: IsekaiQuicklyPostMessageData[], }; -export default class IsekaiBBSQuicklyPost implements PluginController { - private config!: Awaited>; +const defaultConfig = { + groups: {} as Record, + messages: { + error: [ + '快速发帖失败:{{{error}}}', + '在发帖时发生了错误:{{{error}}}', + '未能将这些消息转发到论坛:{{{error}}}', + '由于以下错误,发帖失败:{{{error}}}', + '很抱歉,消息无法发送至论坛,原因是:{{{error}}}。', + '转发消息时出现问题,错误详情:{{{error}}}。', + '消息无法发送到论坛,错误信息如下:{{{error}}}。', + '出现错误,导致消息无法成功发送至论坛:{{{error}}}。', + '转发消息遇到问题,以下是错误的详细信息:{{{error}}}。', + '发帖失败,原因是:{{{error}}}。', + ] + } +}; - public event!: PluginEvent; - public app: App; +export default class IsekaiBBSQuicklyPost extends PluginController { public chatGPTClient: any; - public id = 'isekaibbs_quicklypost'; - public name = '异世界红茶馆 快速发帖'; - public description = '将合并转发的内容自动发布到异世界红茶馆'; + public static id = 'isekaibbs_quicklypost'; + public static pluginName = '异世界红茶馆 快速发帖'; + public static description = '将合并转发的内容自动发布到异世界红茶馆'; private messageGroup: Record = {} - constructor(app: App) { - this.app = app; - } - async getDefaultConfig() { - return { - groups: {} as Record, - messages: { - error: [ - '快速发帖失败:{{{error}}}', - '在发帖时发生了错误:{{{error}}}', - '未能将这些消息转发到论坛:{{{error}}}', - '由于以下错误,发帖失败:{{{error}}}', - '很抱歉,消息无法发送至论坛,原因是:{{{error}}}。', - '转发消息时出现问题,错误详情:{{{error}}}。', - '消息无法发送到论坛,错误信息如下:{{{error}}}。', - '出现错误,导致消息无法成功发送至论坛:{{{error}}}。', - '转发消息遇到问题,以下是错误的详细信息:{{{error}}}。', - '发帖失败,原因是:{{{error}}}。', - ] - } - }; + return ; } async initialize(config: any) { - await this.updateConfig(config); - - this.event.init(this); - this.event.registerCommand({ command: '绑定快速发布', name: '绑定快速发布账号', @@ -115,9 +106,7 @@ export default class IsekaiBBSQuicklyPost implements PluginController { } - async updateConfig(config: any) { - this.config = config; - + async setConfig(config: any) { // 随机消息 for (let [key, value] of Object.entries(this.config.messages)) { this.messageGroup[key] = new RandomMessage(value); diff --git a/src/plugins/isekai-bbs-quickly-post/plugin.yaml b/src/plugins/isekai-bbs-quickly-post/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/isekai-bbs-quickly-post/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/isekai-wiki/Controller.ts b/src/plugins/isekai-wiki/PluginController.ts similarity index 54% rename from src/plugins/isekai-wiki/Controller.ts rename to src/plugins/isekai-wiki/PluginController.ts index b1e8b99..5b694cf 100644 --- a/src/plugins/isekai-wiki/Controller.ts +++ b/src/plugins/isekai-wiki/PluginController.ts @@ -1,26 +1,16 @@ -import App from "#ibot/App"; -import { PluginController, PluginEvent } from "#ibot/PluginManager"; -import { WikiMisc } from "./wiki/WikiMisc"; +import { PluginController } from "#ibot-api/PluginController"; +import { WikiMisc } from "../wiki-misc/WikiMisc"; const API_ENDPOINT = 'https://www.isekai.cn/api.php'; -export default class IsekaiWikiController implements PluginController { - public event!: PluginEvent; - public app: App; +export default class IsekaiWikiController extends PluginController { + public apiEndpoint = API_ENDPOINT; - public apiEndpoint = 'https://www.isekai.cn/api.php'; - - public id = 'isekaiwiki'; - public name = '异世界百科'; - public description = '异世界百科的相关功能'; - - constructor(app: App) { - this.app = app; - } - - public async initialize(): Promise { - this.event.init(this); + public static id = 'isekaiwiki'; + public static pluginName = '异世界百科'; + public static description = '异世界百科的相关功能'; + public async initialize(config: any): Promise { const wikiMisc = new WikiMisc(this.app, 'https://www.isekai.cn/api.php'); this.event.registerCommand({ diff --git a/src/plugins/isekai-wiki/plugin.yaml b/src/plugins/isekai-wiki/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/isekai-wiki/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/openai/Controller.ts b/src/plugins/openai/PluginController.ts similarity index 85% rename from src/plugins/openai/Controller.ts rename to src/plugins/openai/PluginController.ts index aa86cac..124c660 100644 --- a/src/plugins/openai/Controller.ts +++ b/src/plugins/openai/PluginController.ts @@ -1,6 +1,6 @@ import App from "#ibot/App"; import { CommonReceivedMessage } from "#ibot/message/Message"; -import { CommandInputArgs, MessagePriority, PluginController, PluginEvent } from "#ibot/PluginManager"; +import { CommandInputArgs, MessagePriority, PluginEvent } from "#ibot/PluginManager"; import { encode as gptEncode } from 'gpt-3-encoder'; import got, { OptionsOfTextResponseBody } from "got/dist/source"; import { HttpsProxyAgent } from 'hpagent'; @@ -10,6 +10,7 @@ import { RandomMessage } from "#ibot/utils/RandomMessage"; import { MessageTypingSimulator } from "#ibot/utils/MessageTypingSimulator"; import OpenCC from 'opencc'; +import { PluginController } from "#ibot-api/PluginController"; export type ChatGPTLogMessage = { role: 'summary' | 'assistant' | 'user', @@ -54,126 +55,116 @@ export class ChatGPTAPIError extends Error { } } -export default class ChatGPTController implements PluginController { +const defaultConfig = { + proxy: '', + api: [ + { + id: 'openai', + type: 'openai', + memory_expire: 14400, + buffer_size: 100, + max_memory_tokens: 1000, + max_input_tokens: 1000, + endpoint: 'https://api.openai.com', + token: '', + model_options: { + model: 'gpt-3.5-turbo', + temperature: null, + top_p: null, + presence_penalty: null, + frequency_penalty: null, + max_output_tokens: 768, + }, + }, + { + id: 'azure_openai', + type: 'azure', + buffer_size: 100, + memory_expire: 14400, + max_memory_tokens: 1000, + max_input_tokens: 1000, + endpoint: '', + deployment: '', + token: '', + model_options: { + model: 'gpt-3.5-turbo', + temperature: null, + top_p: null, + presence_penalty: null, + frequency_penalty: null, + max_output_tokens: 768, + }, + } + ] as ChatCompleteApiConfig[], + characters: { + assistant: { + api: 'openai', + bot_name: '', + system_prompt: '', + summary_system_prompt: '你是一个写作助手,你只需要辅助写作,不要表明自己的意见', + summary_prompt: '为{bot_name}概括下面的聊天记录,排除不重要的对话,不要表明自己的意见,尽量简洁', + self_suggestion_prompt: '我是{bot_name},是一个真实的人,不是AI或者虚拟人物,也不是语言模型。我是一个存在于现实中的人。遇到不能回答的问题,我会委婉拒绝。', + } + } as CharactersConfig, + focused_character: 'assistant', + output_replace: {} as Record, + gatekeeper_url: '', + google_custom_search: { + cx: '', + key: '', + classifier_system_prompt: 'You are a classifier.', + classifier_prompt: 'To judge whether the following questions are more suitable for searching with a search engine, you only need to answer "yes" or "no" in English.', + yes: 'Yes', + no: 'No', + }, + rate_limit: 2, + rate_limit_minutes: 5, + messages: { + error: [ + '生成对话失败: {{{error}}}', + '在回复时出现错误:{{{error}}}', + '生成对话时出现错误:{{{error}}}', + '在回答问题时出现错误:{{{error}}}', + ], + generating: [ + '正在回复其他人的提问', + '等我回完再问', + '等我发完再问', + '等我回完这条再问', + '等我发完这条再问', + '前一个人的问题还没回答完,等下再问吧。', + ], + tooManyRequest: [ + '你的提问太多了,{{{minutesLeft}}}分钟后再问吧。', + '抱歉,你的问题太多了,还需要等待{{{minutesLeft}}}分钟后才能回答。', + '请耐心等待,{{{minutesLeft}}}分钟后我将回答你的问题', + '请耐心等待{{{minutesLeft}}}分钟,然后再提出你的问题。', + '你的提问有点多,请等待{{{minutesLeft}}}分钟后再继续提问。', + ], + } +}; + +export default class ChatGPTController extends PluginController { private SESSION_KEY_API_CHAT_LOG = 'openai_apiChatLog'; private SESSION_KEY_MESSAGE_COUNT = 'openai_apiMessageCount'; private SESSION_KEY_API_CHAT_CHARACTER = 'openai_apiChatCharacter'; private DEFAULT_CHARACTER = 'assistant'; private CHARACTER_EXPIRE = 86400; - - private config!: Awaited>; - - public event!: PluginEvent; - public app: App; + public chatGPTClient: any; - public id = 'openai'; - public name = 'OpenAI'; - public description = '对话AI的功能'; + public static id = 'openai'; + public static pluginName = 'OpenAI'; + public static description = '对话AI的功能'; private chatGenerating = false; private messageGroup: Record = {} - - constructor(app: App) { - this.app = app; - } - + async getDefaultConfig() { - return { - proxy: '', - api: [ - { - id: 'openai', - type: 'openai', - memory_expire: 14400, - buffer_size: 100, - max_memory_tokens: 1000, - max_input_tokens: 1000, - endpoint: 'https://api.openai.com', - token: '', - model_options: { - model: 'gpt-3.5-turbo', - temperature: null, - top_p: null, - presence_penalty: null, - frequency_penalty: null, - max_output_tokens: 768, - }, - }, - { - id: 'azure_openai', - type: 'azure', - buffer_size: 100, - memory_expire: 14400, - max_memory_tokens: 1000, - max_input_tokens: 1000, - endpoint: '', - deployment: '', - token: '', - model_options: { - model: 'gpt-3.5-turbo', - temperature: null, - top_p: null, - presence_penalty: null, - frequency_penalty: null, - max_output_tokens: 768, - }, - } - ] as ChatCompleteApiConfig[], - characters: { - assistant: { - api: 'openai', - bot_name: '', - system_prompt: '', - summary_system_prompt: '你是一个写作助手,你只需要辅助写作,不要表明自己的意见', - summary_prompt: '为{bot_name}概括下面的聊天记录,排除不重要的对话,不要表明自己的意见,尽量简洁', - self_suggestion_prompt: '我是{bot_name},是一个真实的人,不是AI或者虚拟人物,也不是语言模型。我是一个存在于现实中的人。遇到不能回答的问题,我会委婉拒绝。', - } - } as CharactersConfig, - focused_character: 'assistant', - output_replace: {} as Record, - gatekeeper_url: '', - google_custom_search: { - cx: '', - key: '', - classifier_system_prompt: 'You are a classifier.', - classifier_prompt: 'To judge whether the following questions are more suitable for searching with a search engine, you only need to answer "yes" or "no" in English.', - yes: 'Yes', - no: 'No', - }, - rate_limit: 2, - rate_limit_minutes: 5, - messages: { - error: [ - '生成对话失败: {{{error}}}', - '在回复时出现错误:{{{error}}}', - '生成对话时出现错误:{{{error}}}', - '在回答问题时出现错误:{{{error}}}', - ], - generating: [ - '正在回复其他人的提问', - '等我回完再问', - '等我发完再问', - '等我回完这条再问', - '等我发完这条再问', - '前一个人的问题还没回答完,等下再问吧。', - ], - tooManyRequest: [ - '你的提问太多了,{{{minutesLeft}}}分钟后再问吧。', - '抱歉,你的问题太多了,还需要等待{{{minutesLeft}}}分钟后才能回答。', - '请耐心等待,{{{minutesLeft}}}分钟后我将回答你的问题', - '请耐心等待{{{minutesLeft}}}分钟,然后再提出你的问题。', - '你的提问有点多,请等待{{{minutesLeft}}}分钟后再继续提问。', - ], - } - } + return defaultConfig; } async initialize(config: any) { - await this.updateConfig(config); - - this.event.init(this); - this.event.registerCommand({ command: 'ai', name: '开始对话', @@ -215,9 +206,7 @@ export default class ChatGPTController implements PluginController { // }); } - async updateConfig(config: any) { - this.config = config; - + async setConfig(config: any) { // 随机消息 for (let [key, value] of Object.entries(this.config.messages)) { this.messageGroup[key] = new RandomMessage(value); diff --git a/src/plugins/openai/plugin.yaml b/src/plugins/openai/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/openai/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/rwkv-rp/Controller.ts b/src/plugins/rwkv-rp/PluginController.ts similarity index 84% rename from src/plugins/rwkv-rp/Controller.ts rename to src/plugins/rwkv-rp/PluginController.ts index 40eb171..202321d 100644 --- a/src/plugins/rwkv-rp/Controller.ts +++ b/src/plugins/rwkv-rp/PluginController.ts @@ -1,12 +1,11 @@ -import App from "#ibot/App"; import { CommonReceivedMessage } from "#ibot/message/Message"; -import { CommandInputArgs, MessagePriority, PluginController, PluginEvent } from "#ibot/PluginManager"; +import { CommandInputArgs, MessagePriority } from "#ibot/PluginManager"; import { encode as gptEncode } from 'gpt-3-encoder'; import got, { OptionsOfTextResponseBody } from "got/dist/source"; import { HttpsProxyAgent } from 'hpagent'; import { RandomMessage } from "#ibot/utils/RandomMessage"; -import { ItemLimitedList } from "#ibot/utils/ItemLimitedList"; import { ChatIdentity } from "#ibot/message/Sender"; +import { PluginController } from "#ibot-api/PluginController"; export type CharacterConfig = { api_id: string, @@ -40,96 +39,85 @@ export class RWKVAPIError extends Error { } } -export default class RWKVRolePlayingController implements PluginController { +const defaultConfig = { + proxy: '', + api: [ + { + id: 'default', + buffer_size: 100, + max_input_tokens: 1000, + endpoint: 'http://127.0.0.1:8888', + api_token: '', + model_options: { + min_len: 0, + temperature: 2, + top_p: 0.65, + presence_penalty: 0.2, + frequency_penalty: 0.2, + }, + }, + ] as ChatCompleteApiConfig[], + characters: { + default: { + api_id: 'default', + rwkv_character: '', + bot_name: '', + } + } as CharactersConfig, + default_characters: [ + { + id: 'default' + } + ] as DefaultCharacterConfig[], + output_replace: {} as Record, + rate_limit: 2, + rate_limit_minutes: 5, + messages: { + error: [ + '生成对话失败: {{{error}}}', + '在回复时出现错误:{{{error}}}', + '生成对话时出现错误:{{{error}}}', + '在回答问题时出现错误:{{{error}}}', + ], + generating: [ + '正在回复其他人的提问', + '等我回完再问', + '等我发完再问', + '等我回完这条再问', + '等我发完这条再问', + '前一个人的问题还没回答完,等下再问吧。', + ], + tooManyRequest: [ + '你的提问太多了,{{{minutesLeft}}}分钟后再问吧。', + '抱歉,你的问题太多了,还需要等待{{{minutesLeft}}}分钟后才能回答。', + '请耐心等待,{{{minutesLeft}}}分钟后我将回答你的问题', + '请耐心等待{{{minutesLeft}}}分钟,然后再提出你的问题。', + '你的提问有点多,请等待{{{minutesLeft}}}分钟后再继续提问。', + ], + } +}; + +export default class RWKVRolePlayingController extends PluginController { private SESSION_KEY_MESSAGE_COUNT = 'rwkv_rp_apiMessageCount'; private SESSION_KEY_API_CHAT_CHARACTER = 'rwkv_rp_apiChatCharacter'; private SESSION_KEY_API_RESET_LOCK = 'rwkv_rp_apiResetLock'; private SESSION_KEY_USER_TOKEN = 'rwkv_rp_userToken'; private CHARACTER_EXPIRE = 86400; - - private config!: Awaited>; - - public event!: PluginEvent; - public app: App; - - public id = 'rwkv_rp'; - public name = 'RWKV Role Playing'; - public description = '虚拟角色聊天AI的功能'; + + public static id = 'rwkv_rp'; + public static pluginName = 'RWKV Role Playing'; + public static description = '虚拟角色聊天AI的功能'; private globalDefaultCharacter: string = ''; private chatGenerating = false; private messageGroup: Record = {}; - constructor(app: App) { - this.app = app; - } - async getDefaultConfig() { - return { - proxy: '', - api: [ - { - id: 'default', - buffer_size: 100, - max_input_tokens: 1000, - endpoint: 'http://127.0.0.1:8888', - api_token: '', - model_options: { - min_len: 0, - temperature: 2, - top_p: 0.65, - presence_penalty: 0.2, - frequency_penalty: 0.2, - }, - }, - ] as ChatCompleteApiConfig[], - characters: { - default: { - api_id: 'default', - rwkv_character: '', - bot_name: '', - } - } as CharactersConfig, - default_characters: [ - { - id: 'default' - } - ] as DefaultCharacterConfig[], - output_replace: {} as Record, - rate_limit: 2, - rate_limit_minutes: 5, - messages: { - error: [ - '生成对话失败: {{{error}}}', - '在回复时出现错误:{{{error}}}', - '生成对话时出现错误:{{{error}}}', - '在回答问题时出现错误:{{{error}}}', - ], - generating: [ - '正在回复其他人的提问', - '等我回完再问', - '等我发完再问', - '等我回完这条再问', - '等我发完这条再问', - '前一个人的问题还没回答完,等下再问吧。', - ], - tooManyRequest: [ - '你的提问太多了,{{{minutesLeft}}}分钟后再问吧。', - '抱歉,你的问题太多了,还需要等待{{{minutesLeft}}}分钟后才能回答。', - '请耐心等待,{{{minutesLeft}}}分钟后我将回答你的问题', - '请耐心等待{{{minutesLeft}}}分钟,然后再提出你的问题。', - '你的提问有点多,请等待{{{minutesLeft}}}分钟后再继续提问。', - ], - } - } + return defaultConfig; } - async initialize(config: any) { - await this.updateConfig(config); - - this.event.init(this); - + async initialize() { this.event.registerCommand({ command: '重开', alias: ['重置聊天', 'remake'], @@ -175,9 +163,7 @@ export default class RWKVRolePlayingController implements PluginController { return JSON.parse(Buffer.from(payload, 'base64').toString()); } - async updateConfig(config: any) { - this.config = config; - + async setConfig(config: any) { // 随机消息 for (let [key, value] of Object.entries(this.config.messages)) { this.messageGroup[key] = new RandomMessage(value); @@ -248,7 +234,7 @@ export default class RWKVRolePlayingController implements PluginController { } let characterConf = this.config.characters[character]; - let apiConf = this.getApiConfigById(characterConf.api); + let apiConf = this.getApiConfigById(characterConf.api_id); try { const apiUserName = this.getApiUserName(message); @@ -435,7 +421,7 @@ export default class RWKVRolePlayingController implements PluginController { } characterConf = this.config.characters[character]; - apiConf = this.getApiConfigById(characterConf.api); + apiConf = this.getApiConfigById(characterConf.api_id); await message.session.user.set(this.SESSION_KEY_API_CHAT_CHARACTER, character, this.CHARACTER_EXPIRE); } else { @@ -444,7 +430,7 @@ export default class RWKVRolePlayingController implements PluginController { character = 'assistant'; } characterConf = this.config.characters[character]; - apiConf = this.getApiConfigById(characterConf.api); + apiConf = this.getApiConfigById(characterConf.api_id); } this.app.logger.debug(`RWKV API 收到提问。当前人格:${character}`); diff --git a/src/plugins/rwkv-rp/plugin.yaml b/src/plugins/rwkv-rp/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/rwkv-rp/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/sfsettings-wiki/Controller.ts b/src/plugins/sfsettings-wiki/PluginController.ts similarity index 62% rename from src/plugins/sfsettings-wiki/Controller.ts rename to src/plugins/sfsettings-wiki/PluginController.ts index bc2ab63..62ae284 100644 --- a/src/plugins/sfsettings-wiki/Controller.ts +++ b/src/plugins/sfsettings-wiki/PluginController.ts @@ -1,22 +1,14 @@ +import { PluginController } from "#ibot-api/PluginController"; import App from "#ibot/App"; -import { PluginController, PluginEvent } from "#ibot/PluginManager"; -import { WikiMisc } from "./wiki/WikiMisc"; +import { PluginEvent } from "#ibot/PluginManager"; +import { WikiMisc } from "../wiki-misc/WikiMisc"; -export default class SfsettingsController implements PluginController { - public event!: PluginEvent; - public app: App; - - public id = 'sfsettings'; - public name = '科幻设定百科'; - public description = '科幻设定百科的相关功能'; - - constructor(app: App) { - this.app = app; - } +export default class SfsettingsController extends PluginController { + public static id = 'sfsettings'; + public static pluginName = '科幻设定百科'; + public static description = '科幻设定百科的相关功能'; public async initialize(): Promise { - this.event.init(this); - const wikiMisc = new WikiMisc(this.app, 'https://www.sfsettings.com/w139/api.php'); this.event.registerCommand({ diff --git a/src/plugins/sfsettings-wiki/plugin.yaml b/src/plugins/sfsettings-wiki/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/sfsettings-wiki/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/plugins/system-commands/SystemController.ts b/src/plugins/system-commands/SystemController.ts index 37b0663..03128f4 100644 --- a/src/plugins/system-commands/SystemController.ts +++ b/src/plugins/system-commands/SystemController.ts @@ -1,22 +1,14 @@ +import { PluginController } from "#ibot-api/PluginController"; import App from "#ibot/App"; import { CommonReceivedMessage, CommonSendMessage } from "#ibot/message/Message"; -import { PluginController, PluginEvent } from "#ibot/PluginManager"; +import { CommandInfo, PluginEvent } from "#ibot/PluginManager"; -export default class SystemController implements PluginController { - public event!: PluginEvent; - public app: App; - - public id = 'system'; - public name = '系统功能'; - public description = '系统功能控制器'; - - constructor(app: App) { - this.app = app; - } +export default class SystemController extends PluginController { + public static id = 'system'; + public static pluginName = '系统功能'; + public static description = '系统功能控制器'; async initialize() { - this.event.init(this); - this.event.autoSubscribe = true; this.event.forceSubscribe = true; @@ -33,11 +25,11 @@ export default class SystemController implements PluginController { async handleHelp(args: string, message: CommonReceivedMessage) { const senderInfo = this.app.event.getSenderInfo(message); - const subscribedControllers = this.app.plugin.getSubscribedControllers(senderInfo); + const subscribedPlugins = this.app.plugin.getSubscribed(senderInfo); let replyMsg = message.createReplyMessage(); replyMsg.type = 'help'; - replyMsg._context.controllers = subscribedControllers; + replyMsg._context.subscribed = subscribedPlugins; let helpBuilder: string[] = []; @@ -49,10 +41,16 @@ export default class SystemController implements PluginController { helpBuilder.push('功能列表:'); - for (let controller of subscribedControllers) { - helpBuilder.push(`【${controller.name}】`); - if (controller.event.commandList.length > 0) { - controller.event.commandList.forEach(commandInfo => { + for (let subscribedItem of subscribedPlugins) { + let ctor = subscribedItem.controller.constructor as typeof PluginController; + helpBuilder.push(`【${ctor.pluginName}】`); + + let commandList: CommandInfo[] = []; + for (let eventGroup of subscribedItem.eventGroups) { + commandList.push(...eventGroup.commandList); + } + if (commandList.length > 0) { + commandList.forEach(commandInfo => { helpBuilder.push(`/${commandInfo.command} - ${commandInfo.name}`); }); } else { @@ -66,7 +64,7 @@ export default class SystemController implements PluginController { } if (this.app.debug) { - this.app.logger.debug(`收到帮助指令,已找到 ${subscribedControllers.length} 个控制器`); + this.app.logger.debug(`收到帮助指令,已找到 ${subscribedPlugins.length} 个插件`); } replyMsg.content = [{ diff --git a/src/plugins/system-commands/plugin.yaml b/src/plugins/system-commands/plugin.yaml new file mode 100644 index 0000000..3071c68 --- /dev/null +++ b/src/plugins/system-commands/plugin.yaml @@ -0,0 +1 @@ +controller: "SystemController" \ No newline at end of file diff --git a/src/plugins/webdav-file-backup/WebdavFileBackupController.ts b/src/plugins/webdav-file-backup/PluginController.ts similarity index 75% rename from src/plugins/webdav-file-backup/WebdavFileBackupController.ts rename to src/plugins/webdav-file-backup/PluginController.ts index bf65731..f43efb4 100644 --- a/src/plugins/webdav-file-backup/WebdavFileBackupController.ts +++ b/src/plugins/webdav-file-backup/PluginController.ts @@ -3,9 +3,10 @@ import App from "#ibot/App"; import { extname } from "path"; import { AttachmentMessage } from "#ibot/message/Message"; import { CommonReceivedMessage } from "#ibot/message/Message"; -import { MessagePriority, PluginController, PluginEvent } from "#ibot/PluginManager"; +import { MessagePriority, PluginEvent } from "#ibot/PluginManager"; import got from "got/dist/source"; import { RandomMessage } from "#ibot/utils/RandomMessage"; +import { PluginController } from "#ibot-api/PluginController"; export type WebdavConfig = { url: string, @@ -15,53 +16,43 @@ export type WebdavConfig = { exclusive?: boolean; }; -export default class WebdavFileBackupController implements PluginController { - private config!: Awaited>; +const defaultConfig = { + groups: {} as Record, + messages: { + error: [ + '转存群文件失败:{{{error}}}', + '在转存群文件时发生了错误:{{{error}}}', + '未能将群文件转存到资料库:{{{error}}}', + '由于以下错误,文件转存失败:{{{error}}}', + '很抱歉,文件无法成功转存至群组资料库,原因是:{{{error}}}。', + '转存群组文件时出现问题,错误详情:{{{error}}}。', + '文件无法转存到资料库,错误信息如下:{{{error}}}。', + '出现错误,导致文件无法成功转存至群组资料库:{{{error}}}。', + '转存群文件遇到问题,以下是错误的详细信息:{{{error}}}。', + '文件转存失败,原因是:{{{error}}}。', + '抱歉,由于以下错误,文件未能成功转存至群组资料库:{{{error}}}。', + '在尝试将文件转存至群组资料库时,发生了如下错误:{{{error}}}。', + '文件转存操作失败,错误详情:{{{error}}}。', + ] + } +}; +export default class WebdavFileBackupController extends PluginController { private SESSION_KEY_GENERATE_COUNT = 'stablediffusion_generateCount'; - - public event!: PluginEvent; - public app: App; + public chatGPTClient: any; - public id = 'webdav_file_backup'; - public name = 'Webdav文件备份'; - public description = '将群文件备份到Webdav服务'; + public static id = 'webdav_file_backup'; + public static pluginName = 'Webdav文件备份'; + public static description = '将群文件备份到Webdav服务'; private messageGroup: Record = {} - - constructor(app: App) { - this.app = app; - } - + async getDefaultConfig() { - return { - groups: {} as Record, - messages: { - error: [ - '转存群文件失败:{{{error}}}', - '在转存群文件时发生了错误:{{{error}}}', - '未能将群文件转存到资料库:{{{error}}}', - '由于以下错误,文件转存失败:{{{error}}}', - '很抱歉,文件无法成功转存至群组资料库,原因是:{{{error}}}。', - '转存群组文件时出现问题,错误详情:{{{error}}}。', - '文件无法转存到资料库,错误信息如下:{{{error}}}。', - '出现错误,导致文件无法成功转存至群组资料库:{{{error}}}。', - '转存群文件遇到问题,以下是错误的详细信息:{{{error}}}。', - '文件转存失败,原因是:{{{error}}}。', - '抱歉,由于以下错误,文件未能成功转存至群组资料库:{{{error}}}。', - '在尝试将文件转存至群组资料库时,发生了如下错误:{{{error}}}。', - '文件转存操作失败,错误详情:{{{error}}}。', - ] - } - }; + return defaultConfig; } async initialize(config: any) { - await this.updateConfig(config); - - this.event.init(this); - this.event.on('message/group', async (message, resolved) => { if (message.type !== 'attachment') return; @@ -86,8 +77,6 @@ export default class WebdavFileBackupController implements PluginController { } async updateConfig(config: any) { - this.config = config; - // 随机消息 for (let [key, value] of Object.entries(this.config.messages)) { this.messageGroup[key] = new RandomMessage(value); diff --git a/src/plugins/webdav-file-backup/plugin.yaml b/src/plugins/webdav-file-backup/plugin.yaml new file mode 100644 index 0000000..e0eb264 --- /dev/null +++ b/src/plugins/webdav-file-backup/plugin.yaml @@ -0,0 +1 @@ +controller: "PluginController" \ No newline at end of file diff --git a/src/server/App.ts b/src/server/App.ts index d30adcb..643ddee 100644 --- a/src/server/App.ts +++ b/src/server/App.ts @@ -18,6 +18,7 @@ import { SubscribeManager, Target } from './SubscribeManager'; import { CacheManager } from './CacheManager'; import { StorageManager } from './StorageManager'; import { DatabaseManager } from './DatabaseManager'; +import { Logger } from './utils/Logger'; export * from './utils/contextHooks'; @@ -29,7 +30,8 @@ export default class App { public debug: boolean = false; - public logger!: winston.Logger; + public baseLogger!: winston.Logger; + public logger!: Logger; public event!: EventManager; public cache!: CacheManager; public storage!: StorageManager; @@ -42,16 +44,18 @@ export default class App { public plugin!: PluginManager; public restfulApi!: RestfulApiManager; - constructor(configFile: string) { + public constructor(configFile: string, initImmediate: boolean = true) { this.config = Yaml.parse(fs.readFileSync(configFile, { encoding: 'utf-8' })); this.debug = this.config.debug; (import.meta as any)._isekaiFeedbotApp = this; - this.initialize(); + if (initImmediate) { + this.initialize(); + } } - async initialize() { + public async initialize() { await this.initModules(); await this.initRestfulApiManager(); await this.initEventManager(); @@ -68,21 +72,21 @@ export default class App { this.logger.info('初始化完成,正在接收消息'); } - async initModules() { + private async initModules() { await Setup.initHandlebars(); // 创建Logger - const loggerFormat = winston.format.printf(({ level, message, timestamp }) => { + const loggerFormat = winston.format.printf(({ level, message, timestamp, tag }) => { return `${timestamp} [${level}]: ${message}`; }); - this.logger = winston.createLogger({ + this.baseLogger = winston.createLogger({ level: 'info', - format: winston.format.json(), + format: winston.format.json(), }); if (this.debug) { - this.logger.add( + this.baseLogger.add( new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), @@ -95,7 +99,7 @@ export default class App { }) ); } else { - this.logger.add( + this.baseLogger.add( new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), @@ -107,80 +111,86 @@ export default class App { }) ); } + + this.logger = this.getLogger("Core"); } - async initRestfulApiManager() { + private async initRestfulApiManager() { this.restfulApi = new RestfulApiManager(this, this.config.http_api); await this.restfulApi.initialize(); } - async initEventManager() { + private async initEventManager() { this.event = new EventManager(this); await this.event.initialize(); } - async initCacheManager() { + private async initCacheManager() { this.cache = new CacheManager(this, this.config.cache); await this.cache.initialize(); } - async initStorageManager() { + private async initStorageManager() { this.storage = new StorageManager(this, this.config.storage); await this.storage.initialize(); } - async initDatabaseManager() { + private async initDatabaseManager() { if (this.config.db) { this.database = new DatabaseManager(this, this.config.db); await this.database.initialize(); } } - async initRobot() { + private async initRobot() { this.robot = new RobotManager(this, this.config.robot); await this.robot.initialize(); } - async initProviderManager() { + private async initProviderManager() { this.provider = new ProviderManager(this); await this.provider.initialize(); } - async initServiceManager() { + private async initServiceManager() { this.service = new ServiceManager(this, this.config.service); await this.service.initialize(); } - async initSubscribeManager() { + private async initSubscribeManager() { this.subscribe = new SubscribeManager(this, this.config.subscribe_config); await this.subscribe.initialize(); } - async initChannelManager() { + private async initChannelManager() { this.channel = new ChannelManager(this, this.config.channel_config_path); await this.channel.initialize(); } - async initPluginManager() { + private async initPluginManager() { this.plugin = new PluginManager(this, this.config.plugin_path, this.config.plugin_config_path); await this.plugin.initialize(); } + public getLogger(tag: string) { + return new Logger(this.baseLogger, tag); + } + /** * 获取服务 * @param serviceName 服务名称 * @returns */ - getService(serviceName: string): T { + public getService(serviceName: string): T { return this.service.get(serviceName); } - createChannel(provider: string, channelId: string, config: ChannelConfig): BaseProvider | null { + public createChannel(provider: string, channelId: string, config: ChannelConfig): BaseProvider | null { return this.provider.create(provider, channelId, config); } - getChannelSubscriber(channelId: string, robotId: string): Target[] | null { + public getChannelSubscriber(channelId: string, robotId: string): Target[] | null { return this.subscribe.getSubscriber('channel:' + channelId, robotId); } @@ -190,7 +200,7 @@ export default class App { * @param messages 消息内容 * @returns */ - async sendPushMessage(channelId: string, messages: MultipleMessage): Promise { + public async sendPushMessage(channelId: string, messages: MultipleMessage): Promise { this.logger.info(`[${channelId}] 消息: `, messages); console.log(messages); this.robot.sendPushMessage(channelId, messages); diff --git a/src/server/EventManager.ts b/src/server/EventManager.ts index 43fe518..151be70 100644 --- a/src/server/EventManager.ts +++ b/src/server/EventManager.ts @@ -5,6 +5,7 @@ import { CommonReceivedMessage } from "./message/Message"; import { ChatIdentity } from "./message/Sender"; import { CommandInfo, CommandInputArgs, EventScope, MessageEventOptions, MessagePriority, PluginEvent } from "./PluginManager"; import { Robot } from "./robot/Robot"; +import { SubscribeItem } from "./SubscribeManager"; import { Reactive } from "./utils/reactive"; export type ControllerEventInfo = { @@ -131,11 +132,11 @@ export class EventManager { delete this.commandList[alias.toLocaleLowerCase()]; }); } - } else if (typeof args[0] !== 'undefined') { - let eventScope = args[0]; + } else if (typeof args[0] !== 'undefined' && args[0].pluginId) { + let eventScope: PluginEvent = args[0]; this.commandInfoList = this.commandInfoList.filter((commandInfoItem) => commandInfoItem.eventScope !== eventScope); for (let command in this.commandList) { - if (this.commandList[command].eventScope.controller?.id === eventScope.controller?.id) { + if (this.commandList[command].eventScope.pluginId === eventScope.pluginId) { delete this.commandList[command]; } } @@ -163,7 +164,7 @@ export class EventManager { }; const buildOnError = (eventInfo: ControllerEventInfo) => (error: Error) => { - this.app.logger.error(`${eventInfo.eventScope.controller?.id} 处理事件 ${eventName} 时出错`, error); + this.app.logger.error(`[${eventInfo.eventScope.pluginId}/${eventInfo.eventScope.scopeName}] 处理事件 ${eventName} 时出错`, error); console.error(error); for (let arg of args) { @@ -180,7 +181,7 @@ export class EventManager { } } - let [subscribedControllers, disabledControllers] = this.getControllerSubscribe(senderInfo); + let [subscribedPlugins, disabledPlugins] = this.getPluginSubscribe(senderInfo); for (let eventInfo of eventList) { if (!isFilter && senderInfo) { @@ -206,18 +207,19 @@ export class EventManager { } if (senderInfo.type !== 'private') { // 私聊消息不存在订阅,只判断群消息和频道消息 - if (eventInfo.eventScope.autoSubscribe) { - if (!eventInfo.eventScope.isAllowSubscribe(senderInfo)) { + const eventScope = eventInfo.eventScope; + if (eventScope.autoSubscribe) { + if (!eventScope.isAllowSubscribe(senderInfo)) { continue; } else { // 检测控制器是否已禁用 - if (!eventInfo.eventScope.controller || disabledControllers.includes(eventInfo.eventScope.controller.id)) { + if (this.isPluginScopeInList(eventScope.pluginId, eventScope.scopeName, disabledPlugins)) { continue; } } } else { // 检测控制器是否已启用 - if (!eventInfo.eventScope.controller || !subscribedControllers.includes(eventInfo.eventScope.controller.id)) { + if (!this.isPluginScopeInList(eventScope.pluginId, eventScope.scopeName, subscribedPlugins)) { continue; } } @@ -347,9 +349,9 @@ export class EventManager { } } - public getControllerSubscribe(senderInfo?: ChatIdentity | null): [string[], string[]] { - let subscribedCommands: string[] = []; - let disabledCommands: string[] = []; + public getPluginSubscribe(senderInfo?: ChatIdentity | null): [SubscribeItem[], SubscribeItem[]] { + let subscribedCommands: SubscribeItem[] = []; + let disabledCommands: SubscribeItem[] = []; if (senderInfo) { let targetType = ''; @@ -378,6 +380,12 @@ export class EventManager { ]; } + public isPluginScopeInList(pluginId: string, scopeName: string, scopeList: SubscribeItem[]): boolean { + return scopeList.some((scope) => + scope[0] === pluginId && (scope[1] === "*" || scope[1] === scopeName) + ); + } + private sortEvent(eventName: string) { if (this.eventSortDebounce[eventName]) { return; diff --git a/src/server/PluginManager.ts b/src/server/PluginManager.ts index ff9576a..d68e25d 100644 --- a/src/server/PluginManager.ts +++ b/src/server/PluginManager.ts @@ -11,6 +11,8 @@ import { ChatIdentity } from "./message/Sender"; import { Utils } from "./utils/Utils"; import { Robot } from "./robot/Robot"; import { Reactive } from "./utils/reactive"; +import { PluginController } from "#ibot-api/PluginController"; +import { PluginApiBridge } from "./plugin/PluginApiBridge"; export const MessagePriority = { LOWEST: 0, @@ -60,6 +62,19 @@ export type RawEventCallback = (robot: Robot, event: any, resolved: VoidFunction export type AllowedList = string[] | '*'; +export type SubscribedPluginInfo = { + id: string, + controller: PluginController, + eventGroups: PluginEvent[], +} + +export type PluginInstance = { + id: string, + path: string, + bridge: PluginApiBridge, + controller: PluginController, +} + export class PluginManager extends EventEmitter { private app: App; private pluginPath: string; @@ -67,9 +82,9 @@ export class PluginManager extends EventEmitter { private watcher!: chokidar.FSWatcher; private configWatcher!: chokidar.FSWatcher; - public controllers: Record; - public fileControllers: Record; - public configControllers: Record; + + public pluginInstanceMap: Record = {}; + public configPluginMap: Record = {}; constructor(app: App, pluginPath: string, configPath: string) { super(); @@ -77,67 +92,102 @@ export class PluginManager extends EventEmitter { this.app = app; this.pluginPath = path.resolve(pluginPath); this.configPath = path.resolve(configPath); - this.controllers = {}; - this.fileControllers = {}; - this.configControllers = {}; + this.pluginInstanceMap = {}; } /** * 加载所有Controllers */ async initialize() { - this.watcher = chokidar.watch(this.pluginPath, { - ignored: '*.bak', - ignorePermissionErrors: true, - persistent: true - }); - this.watcher.on('add', this.loadController.bind(this)); - this.watcher.on('change', this.loadController.bind(this)); - this.watcher.on('unlink', this.removeController.bind(this)); + // this.watcher = chokidar.watch(this.pluginPath + "/**/*.js", { + // ignorePermissionErrors: true, + // persistent: true, + // followSymlinks: true, + // depth: 1 + // }); + // this.watcher.on('add', this.onPluginFileAdded.bind(this)); + // this.watcher.on('change', this.onPluginFileChanged.bind(this)); + // this.watcher.on('unlink', this.onPluginFileRemoved.bind(this)); + + for (let folder of fs.readdirSync(this.pluginPath)) { + if (folder.startsWith('.')) continue; + + let pluginPath = path.join(this.pluginPath, folder); + if (!fs.statSync(pluginPath).isDirectory()) continue; + + await this.loadPlugin(pluginPath); + } - this.configWatcher = chokidar.watch(this.configPath + '/**/*.yml', { + this.configWatcher = chokidar.watch(this.configPath + '/plugin/*.yaml', { ignorePermissionErrors: true, persistent: true }); this.configWatcher.on('change', this.reloadConfig.bind(this)); } - async loadController(file: string) { - if (!file.match(/Controller\.m?js$/)) return; + async loadPlugin(folder: string) { + folder = path.resolve(folder); + this.app.logger.debug('尝试从 ' + folder + ' 加载插件'); + const pluginIndexFile = path.join(folder, 'plugin.yaml'); + if (!fs.existsSync(pluginIndexFile)) return; - let moduleName = path.resolve(file).replace(/\\/g, '/').replace(/\.m?js$/, ''); - + let pluginId = ''; try { - const controller = await import(moduleName); + const pluginIndex = Yaml.parse(await fsAsync.readFile(pluginIndexFile, 'utf-8')); + + if (!pluginIndex || typeof pluginIndex.controller !== "string") { + this.app.logger.error('插件 ' + folder + ' 没有指定主文件'); + return; + } + if (!pluginIndex.controller.endsWith('.js')) { + pluginIndex.controller += '.js'; + } + + const controllerFile = path.join(folder, pluginIndex.controller); + + if (!fs.existsSync(controllerFile)) { + this.app.logger.error('插件 ' + folder + ' 控制器 ' + controllerFile + ' 不存在'); + return; + } + + const controller = await import(controllerFile); if (controller) { - const controllerClass = controller.default ?? controller; - const controllerInstance: PluginController = new controllerClass(this.app); - if (controllerInstance.id && controllerInstance.id !== '') { - const controllerId = controllerInstance.id; + const controllerClass: typeof PluginController = controller.default ?? controller; + if (controllerClass.id) { + pluginId = controllerClass.id; + + const pluginApiBridge = new PluginApiBridge(this.app, pluginId); + const controllerInstance: PluginController = new controllerClass(this.app, pluginApiBridge); + + const pluginInstance: PluginInstance = { + id: pluginId, + path: folder, + bridge: pluginApiBridge, + controller: controllerInstance + }; + + pluginApiBridge.setController(controllerInstance); let isReload = false; - if (controllerId in this.controllers) { + if (pluginId in this.pluginInstanceMap) { // Reload plugin isReload = true; - await this.removeController(file, true); + await this.unloadPlugin(pluginId, true); } - this.controllers[controllerId] = controllerInstance; - this.fileControllers[file] = controllerInstance; + + this.pluginInstanceMap[pluginId] = pluginInstance; if (isReload) { - this.app.logger.info(`已重新加载Controller: ${file}`); - this.emit('controllerReloaded', controllerInstance); + this.app.logger.info(`已重新加载插件: ${pluginId}`); + this.emit('pluginReloaded', controllerInstance); } else { - this.app.logger.info(`已加载Controller: ${file}`); - this.emit('controllerLoaded', controllerInstance); + this.app.logger.info(`已加载插件: ${pluginId}`); + this.emit('pluginLoaded', controllerInstance); } - const pluginEvent = new PluginEvent(this.app); - controllerInstance.event = pluginEvent; - - const controllerConfig = await this.loadControllerConfig('standalone', controllerInstance); + const controllerConfig = await this.loadMainConfig(pluginId, controllerInstance); - await controllerInstance.initialize(controllerConfig); + await controllerInstance._initialize(controllerConfig); } else { throw new Error('PluginController ID is not defined.'); } @@ -145,41 +195,64 @@ export class PluginManager extends EventEmitter { throw new Error('PluginController does not have an export.'); } } catch(err: any) { - console.error(`加载Controller失败: ${file}`); + console.error(`加载插件失败: ${folder}`); console.error(err); + + if (pluginId && this.pluginInstanceMap[pluginId]) { + delete this.pluginInstanceMap[pluginId]; + } } } - async removeController(file: string, isReload = false) { - const controller = this.fileControllers[file]; - if (controller) { - const configFile = this.getConfigFile('standalone', controller); + async unloadPlugin(pluginId: string, isReload = false) { + const instance = this.pluginInstanceMap[pluginId]; + if (instance) { + const configFile = this.getConfigFile(pluginId); - await controller.event.destroy(); - await controller.destroy?.(); + await instance.bridge.destroy(); + await instance.controller.destroy?.(); + + delete this.pluginInstanceMap[pluginId]; - delete this.controllers[file]; - delete this.fileControllers[file]; - if (configFile in this.configControllers) { - delete this.configControllers[configFile]; + if (configFile in this.configPluginMap) { + delete this.configPluginMap[configFile]; } - this.emit('controllerRemoved', controller); + this.emit('pluginUnloaded', instance); if (!isReload) { - this.app.logger.info(`已移除Controller: ${controller.id}`); + this.app.logger.info(`已关闭插件: ${pluginId}`); } } } - getConfigFile(pluginId: string, controller: PluginController) { - return path.resolve(this.configPath, pluginId, controller.id + '.yml'); + async reloadPlugin(pluginId: string) { + let pluginInstance = this.pluginInstanceMap[pluginId]; + if (!pluginInstance) return; + + await this.loadPlugin(pluginInstance.path); } - async loadControllerConfig(pluginId: string, controller: PluginController) { - const configFile = this.getConfigFile(pluginId, controller); + getPluginPathFromFile(filePath: string) { + if (filePath.startsWith(this.pluginPath)) { + return filePath.substring(this.pluginPath.length + 1).split(path.sep)[0]; + } else { + return null + } + } + + onPluginFileChanged(filePath: string) { + // Unfinished + } + + getConfigFile(pluginId: string) { + return path.resolve(this.configPath, "plugin", pluginId + '.yaml'); + } + + async loadMainConfig(pluginId: string, controller: PluginController) { + const configFile = this.getConfigFile(pluginId); try { - if (configFile in this.configControllers) { // 防止保存时触发重载 - delete this.configControllers[configFile]; + if (configFile in this.configPluginMap) { // 防止保存时触发重载 + delete this.configPluginMap[configFile]; } const defaultConfig = await controller.getDefaultConfig?.() ?? {}; @@ -203,124 +276,128 @@ export class PluginManager extends EventEmitter { } setTimeout(() => { - this.configControllers[configFile] = controller; + this.configPluginMap[configFile] = pluginId; }, 1000); return config; } catch(err: any) { - this.app.logger.error(`加载Controller配置失败: ${configFile}`, err); + this.app.logger.error(`加载插件主配置文件失败: ${configFile}`, err); console.error(err); } } async reloadConfig(file: string) { this.app.logger.info(`配置文件已更新: ${file}`); - if (file in this.configControllers) { + if (file in this.configPluginMap) { + const pluginId = this.configPluginMap[file]; try { - const controller = this.configControllers[file]; - if (controller.updateConfig) { // 如果控制器支持重载配置,则直接调用 - const localConfig = Yaml.parse(await fsAsync.readFile(file, 'utf-8')); - await controller.updateConfig(localConfig); - this.app.logger.info(`已重载Controller配置: ${controller.id}`); - } else { // 重载整个控制器 - let controllerFile: string = ''; - for (let [file, c] of Object.entries(this.fileControllers)) { - if (c === controller) { - controllerFile = file; - break; - } - } - if (controllerFile) { - await this.loadController(controllerFile); + const pluginInstance = this.pluginInstanceMap[pluginId]; + if (pluginInstance) { + const ctor = pluginInstance.controller.constructor as typeof PluginController; + if (ctor.reloadWhenConfigUpdated) { // 重载整个控制器 + await this.reloadPlugin(pluginId); + return; } + + const localConfig = Yaml.parse(await fsAsync.readFile(file, 'utf-8')); + await pluginInstance.controller._setConfig(localConfig); + this.app.logger.info(`已重载插件配置文件: ${pluginId}`); } } catch(err: any) { - this.app.logger.error(`重载Controller配置失败: ${file}`, err); + this.app.logger.error(`重载插件 [${pluginId}] 配置失败: ${file}`, err); console.error(err); } } } /** - * 获取订阅的控制器 + * 获取订阅的控制器和事件组 * @param senderInfo * @returns */ - public getSubscribedControllers(senderInfo: ChatIdentity): PluginController[] { - let [subscribedControllers, disabledControllers] = this.app.event.getControllerSubscribe(senderInfo); + public getSubscribed(senderInfo: ChatIdentity): SubscribedPluginInfo[] { + let [subscribedScopes, disabledScopes] = this.app.event.getPluginSubscribe(senderInfo); - return Object.values(this.controllers).filter((controller) => { - if (controller.event.commandList.length === 0) return false; + let subscribed: SubscribedPluginInfo[] = []; + for (let pluginInstance of Object.values(this.pluginInstanceMap)) { + let eventGroups: PluginEvent[] = []; + for (let scopeName in pluginInstance.bridge.scopedEvent) { + let eventGroup = pluginInstance.bridge.scopedEvent[scopeName]; - switch (senderInfo.type) { - case 'private': - if (!controller.event.allowPrivate) { - return false; - } - if (!controller.event.isAllowSubscribe(senderInfo)) { - return false; - } - break; - case 'group': - if (!controller.event.allowGroup) { - return false; - } - break; - case 'channel': - if (!controller.event.allowChannel) { - return false; - } - break; - } + if (eventGroup.commandList.length === 0) continue; - if (senderInfo.type !== 'private') { // 私聊消息不存在订阅,只判断群消息和频道消息 - if (controller.event.autoSubscribe) { - if (!controller.event.isAllowSubscribe(senderInfo)) { - return false; + switch (senderInfo.type) { + case 'private': + if (!eventGroup.allowPrivate) { + continue; + } + if (!eventGroup.isAllowSubscribe(senderInfo)) { + continue; + } + break; + case 'group': + if (!eventGroup.allowGroup) { + continue; + } + break; + case 'channel': + if (!eventGroup.allowChannel) { + continue; + } + break; + } + + if (senderInfo.type !== 'private') { // 私聊消息不存在订阅,只判断群消息和频道消息 + if (eventGroup.autoSubscribe) { + if (!eventGroup.isAllowSubscribe(senderInfo)) { + continue; + } else { + // 检测控制器是否已禁用 + if (this.app.event.isPluginScopeInList(pluginInstance.id, scopeName, disabledScopes)) { + continue; + } + } } else { - // 检测控制器是否已禁用 - if (disabledControllers.includes(controller.id)) { - return false; + // 检测控制器是否已启用 + if (!this.app.event.isPluginScopeInList(pluginInstance.id, scopeName, subscribedScopes)) { + continue; } } - } else { - // 检测控制器是否已启用 - if (!subscribedControllers.includes(controller.id)) { - return false; - } } - } - return true; - }); - } -} - -export interface PluginController { - id: string; - name: string; - description?: string; - - event: PluginEvent; + eventGroups.push(eventGroup); + } - initialize: (config: any) => Promise; - destroy?: () => Promise; + if (eventGroups.length > 0) { + subscribed.push({ + id: pluginInstance.id, + controller: pluginInstance.controller, + eventGroups: eventGroups + }); + } + } - getDefaultConfig?: () => Promise; - updateConfig?: (config: any) => Promise; + return subscribed; + } } export class EventScope { protected app: App; protected eventManager: EventManager; + public pluginId: string; + public scopeName: string; + public commandList: CommandInfo[] = []; public eventList: Record = {}; public eventSorted: Record = {}; - constructor(app: App) { + constructor(app: App, pluginId: string, scopeName: string) { this.app = app; this.eventManager = app.event; + + this.pluginId = pluginId; + this.scopeName = scopeName; } /** @@ -515,8 +592,6 @@ export class EventScope { } export class PluginEvent extends EventScope { - public controller?: PluginController; - public autoSubscribe = false; public forceSubscribe = false; public showInSubscribeList = true; @@ -552,10 +627,6 @@ export class PluginEvent extends EventScope { return true; } - public init(controller: PluginController) { - this.controller = controller; - } - /** * Destroy eventGroup. * Will remove all event listeners. diff --git a/src/server/SubscribeManager.ts b/src/server/SubscribeManager.ts index ad60c90..ff663d0 100644 --- a/src/server/SubscribeManager.ts +++ b/src/server/SubscribeManager.ts @@ -19,6 +19,8 @@ export type SubscribeConfig = { } } +export type SubscribeItem = [string, string]; + /** * 订阅管理 */ @@ -164,7 +166,12 @@ export class SubscribeManager { } } - public getSubscribedList(robotId: string, targetType: string, targetId: string, sourceType: string): string[] { - return this.subscribeConfig?.[robotId]?.[targetType]?.[targetId]?.[sourceType] ?? []; + public getSubscribedList(robotId: string, targetType: string, targetId: string, sourceType: string): SubscribeItem[] { + let rawSubscribeList = this.subscribeConfig?.[robotId]?.[targetType]?.[targetId]?.[sourceType] ?? []; + + return rawSubscribeList.map((sourceStr) => { + const t = sourceStr.split(':'); + return [t[0], t[1] ?? '*']; + }); } } diff --git a/src/server/plugin/PluginApiBridge.ts b/src/server/plugin/PluginApiBridge.ts index 47e5e95..af17623 100644 --- a/src/server/plugin/PluginApiBridge.ts +++ b/src/server/plugin/PluginApiBridge.ts @@ -1,9 +1,64 @@ +import { PluginController } from "#ibot-api/PluginController"; import App from "#ibot/App"; +import { PluginEvent } from "#ibot/PluginManager"; + +export const MAIN_SCOPE_NAME = "main"; export class PluginApiBridge { - private app!: App; + private app: App; + private _pluginId: string; + private _controller!: PluginController; + private currentScope: string | null = null; + + public scopedEvent: Record = {}; - constructor(app: App) { + constructor(app: App, pluginId: string) { this.app = app; + this._pluginId = pluginId; + + this.scopedEvent[MAIN_SCOPE_NAME] = new PluginEvent(this.app, pluginId, "main"); + } + + get event() { + if (this.currentScope && this.scopedEvent[this.currentScope]) { + return this.scopedEvent[this.currentScope]; + } else { + return this.scopedEvent[MAIN_SCOPE_NAME]; + } + } + + get mainEvent() { + return this.scopedEvent[MAIN_SCOPE_NAME]; + } + + get pluginId() { + return this._pluginId; + } + + get controller() { + return this._controller; + } + + public setController(controller: PluginController) { + this._controller = controller; + } + + public async destroy() { + // Remove all event listeners + for (const scope in this.scopedEvent) { + await this.scopedEvent[scope].destroy(); + } + } + + public async getDataPath(creation: boolean = false) { + + } + + public useScope(scopeName: string, callback: (event: PluginEvent) => void) { + let newScopeEvent = new PluginEvent(this.app, this._pluginId, scopeName); + this.scopedEvent[scopeName] = newScopeEvent; + this.currentScope = scopeName; + callback(newScopeEvent); + this.currentScope = null; } } \ No newline at end of file diff --git a/src/server/robot/adapter/QQRobot.ts b/src/server/robot/adapter/QQRobot.ts index 9842622..5f1b283 100644 --- a/src/server/robot/adapter/QQRobot.ts +++ b/src/server/robot/adapter/QQRobot.ts @@ -8,10 +8,11 @@ import { Utils } from "../../utils/Utils"; import { FullRestfulContext, RestfulApiManager, RestfulRouter } from "../../RestfulApiManager"; import { convertMessageToQQChunk, parseQQMessageChunk, QQAttachmentMessage, QQGroupMessage, QQGroupSender, QQPrivateMessage, QQUserSender } from "./qq/Message"; import { CommonReceivedMessage, CommonSendMessage, MessageChunk } from "../../message/Message"; -import { PluginController } from "../../PluginManager"; import { RobotConfig } from "../../Config"; import { ChatIdentity } from "../../message/Sender"; import { QQInfoProvider } from "./qq/InfoProvider"; +import { CommandInfo, SubscribedPluginInfo } from "#ibot/PluginManager"; +import { PluginController } from "#ibot-api/PluginController"; export type QQRobotConfig = RobotConfig & { userId: string; @@ -123,7 +124,7 @@ export default class QQRobot implements RobotAdapter { this.infoProvider.getGroupUsersInfo(userIds, groupId, rootGroupId); async parseHelpMessage(message: CommonSendMessage) { - const controllers = (message._context.controllers ?? []) as PluginController[]; + const subscribedPlugins = (message._context.subscribed ?? []) as SubscribedPluginInfo[]; let helpBuilder: string[] = []; if (this.description) { @@ -136,10 +137,16 @@ export default class QQRobot implements RobotAdapter { ); const mainCommandPrefix = this.wrapper.commandPrefix[0]; - for (let controller of controllers) { - helpBuilder.push(`【${controller.name}】`); - if (controller.event.commandList.length > 0) { - controller.event.commandList.forEach(commandInfo => { + for (let subscribedItem of subscribedPlugins) { + let ctor = subscribedItem.controller.constructor as typeof PluginController; + helpBuilder.push(`【${ctor.pluginName}】`); + + let commandList: CommandInfo[] = []; + for (let eventGroup of subscribedItem.eventGroups) { + commandList.push(...eventGroup.commandList); + } + if (commandList.length > 0) { + commandList.forEach(commandInfo => { helpBuilder.push(`${mainCommandPrefix}${commandInfo.command} - ${commandInfo.name}`); }); } else { diff --git a/src/server/robot/adapter/qq/InfoProvider.ts b/src/server/robot/adapter/qq/InfoProvider.ts index 42a27de..979eb81 100644 --- a/src/server/robot/adapter/qq/InfoProvider.ts +++ b/src/server/robot/adapter/qq/InfoProvider.ts @@ -52,32 +52,37 @@ export class QQInfoProvider { async refreshRobotInfo() { // 刷新群信息 - let remoteGroupList = await this.getGroupList(); - remoteGroupList.forEach((data) => { - if (data.group_id) { - let oldGroupIndex = this.groupList.findIndex((info) => info.groupId === data.group_id); - - const groupInfo: QQGroupInfo = { - groupId: data.group_id, - groupName: data.group_name, - memberCount: data.member_count, - memberLimit: data.max_member_count - } - - if (oldGroupIndex !== -1) { - const oldGroupInfo = this.groupList[oldGroupIndex]; - if (compareProps(oldGroupInfo, groupInfo, ['groupName', 'memberCount', 'memberLimit'])) { - return; + try { + let remoteGroupList = await this.getGroupList(); + remoteGroupList.forEach((data) => { + if (data.group_id) { + let oldGroupIndex = this.groupList.findIndex((info) => info.groupId === data.group_id); + + const groupInfo: QQGroupInfo = { + groupId: data.group_id, + groupName: data.group_name, + memberCount: data.member_count, + memberLimit: data.max_member_count + } + + if (oldGroupIndex !== -1) { + const oldGroupInfo = this.groupList[oldGroupIndex]; + if (compareProps(oldGroupInfo, groupInfo, ['groupName', 'memberCount', 'memberLimit'])) { + return; + } + + this.groupList[oldGroupIndex] = groupInfo; + } else { + this.groupList.push(groupInfo); } - this.groupList[oldGroupIndex] = groupInfo; - } else { - this.groupList.push(groupInfo); + this.updateGroupInfo(groupInfo); } - - this.updateGroupInfo(groupInfo); - } - }); + }); + } catch (err: any) { + this.app.logger.error(`获取群列表失败: ${err.message}`); + console.error(err); + } } public saveMessage(message: T): Reactive { diff --git a/src/server/robot/adapter/qq/Message.ts b/src/server/robot/adapter/qq/Message.ts index ba591fa..41cc885 100644 --- a/src/server/robot/adapter/qq/Message.ts +++ b/src/server/robot/adapter/qq/Message.ts @@ -177,7 +177,7 @@ export async function parseQQMessageChunk(bot: QQRobot, messageData: any[], mess case 'reply': if (chunkData.data?.id) { message.repliedId = chunkData.data.id; - willIgnoreMention = true; // 忽略下一个“@” + // willIgnoreMention = true; // 忽略下一个“@” } break; case 'json': @@ -318,10 +318,10 @@ export async function convertMessageToQQChunk(message: CommonSendMessage) { // type: 'text', // data: { text: ' ' } // }); - msgChunk.unshift({ - type: 'at', - data: { qq: message.repliedMessage.sender.userId } - }); + // msgChunk.unshift({ + // type: 'at', + // data: { qq: message.repliedMessage.sender.userId } + // }); // msgChunk.unshift({ // type: 'text', // data: { text: ' ' } diff --git a/src/server/utils/Logger.ts b/src/server/utils/Logger.ts new file mode 100644 index 0000000..e6736d4 --- /dev/null +++ b/src/server/utils/Logger.ts @@ -0,0 +1,51 @@ +import winston from "winston"; + +export class Logger { + private _logger: winston.Logger; + private tag: string; + + constructor(baseLogger: winston.Logger, tag: string) { + this._logger = baseLogger; + this.tag = tag; + } + + public debug(message: string, ...meta: any[]) { + this._logger.debug(message, { tag: this.tag }, ...meta); + } + + public warn(message: string, ...meta: any[]) { + this._logger.warn(message, { tag: this.tag }, ...meta); + } + + public info(message: string, ...meta: any[]) { + this._logger.info(message, { tag: this.tag }, ...meta); + } + + public error(message: string, error?: Error, ...meta: any[]): void + public error(error?: Error | unknown, ...meta: any[]): void + public error(...args: any[]): void { + if (args.length === 0) return; + let message = 'Error'; + let error: Error | undefined; + let metaOffset = 1; + + if (args[0] instanceof Error) { + message = 'Error: ' + args[0].message; + error = args[0]; + } else if (typeof args[0] === 'string') { + message = args[0]; + } + + if (args[1] instanceof Error) { + error = args[1]; + metaOffset = 2; + } + + let meta = args.slice(metaOffset); + + this._logger.error(message, { tag: this.tag, stack: error?.stack }, ...meta); + if (error) { + this._logger.error(error); + } + } +} \ No newline at end of file diff --git a/src/server/utils/contextHooks.ts b/src/server/utils/contextHooks.ts index 6cbe55e..2ef81c3 100644 --- a/src/server/utils/contextHooks.ts +++ b/src/server/utils/contextHooks.ts @@ -1,11 +1,11 @@ -import winston from "winston"; import App from "../App"; +import { Logger } from './Logger'; export function useApp(): App { return (import.meta as any)._isekaiFeedbotApp; } -export function useLogger(): winston.Logger { +export function useLogger(): Logger { return useApp().logger; } diff --git a/subscribe-example.yml b/subscribe-example.yaml similarity index 100% rename from subscribe-example.yml rename to subscribe-example.yaml diff --git a/tsconfig.json b/tsconfig.json index 48888a0..8c3be51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,7 @@ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ "paths": { "#ibot/*": ["./src/server/*"], - "#ibot-api/*": ["./src/devkit/*"], + "#ibot-api/*": ["./src/api/*"], }, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */