-
Notifications
You must be signed in to change notification settings - Fork 9
feat: initialize qq plugin #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
576af89
fd2390e
a24aa58
9bd9720
56e748a
8218fd4
bc61a1e
761b173
4e19d21
07cc029
212a925
8862b46
a43c1bf
4e813b0
592a07c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| services: | ||
| napcat: | ||
| image: mlikiowa/napcat-docker:latest | ||
| container_name: napcat | ||
| restart: always | ||
| # mac_address: 02:42:ac:11:00:02 # 添加MAC地址固化配置 | ||
| environment: | ||
| - NAPCAT_UID=${NAPCAT_UID} | ||
| - NAPCAT_GID=${NAPCAT_GID} | ||
|
|
||
| ports: | ||
| - 3001:3001 | ||
| - 6096:6096 | ||
| - 6097:6097 | ||
| - 6099:6099 | ||
|
|
||
| volumes: | ||
| - ../.data/napcat/config:/app/napcat/config | ||
| - ../.data/ntqq:/app/.config/QQ | ||
| networks: | ||
| - ema-network | ||
|
|
||
| networks: | ||
| ema-network: | ||
| driver: bridge |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # 插件 | ||
|
|
||
| 插件是 Ema 的扩展机制,可以通过插件来扩展 Ema 的功能。目前支持的插件有: | ||
|
|
||
|
|
||
| ## 插件配置 | ||
|
|
||
| 目前只有一个环境变量 `EMA_PLUGINS`,用于配置插件列表,多个插件用逗号分隔。例如: | ||
|
|
||
| ```bash | ||
| EMA_PLUGINS=qq | ||
| ``` | ||
|
|
||
| ## 添加插件 | ||
|
|
||
| 插件的命名必须以`ema-plugin-`开头,例如 `ema-plugin-discord`。插件的开发可以通过以下步骤进行: | ||
|
|
||
| 1. 创建一个插件包,例如 `ema-plugin-discord`。 | ||
| 2. 在 [`ema-ui/package.json`](/packages/ema-ui/package.json) 的 `peerDependencies` 中添加一行: | ||
|
|
||
| ```jsonc | ||
| { | ||
| "peerDependencies": { | ||
| // PNPM 工作空间依赖 | ||
| "ema-plugin-discord": "workspace:*", | ||
| // 或外部包依赖 | ||
| "ema-plugin-discord": "^1.0.0", | ||
| }, | ||
| } | ||
| ``` | ||
Myriad-Dreamin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 3. 重启服务器。 | ||
|
|
||
| ## 插件开发 | ||
|
|
||
| 插件的根文件需要导出 `Plugin` 符号: | ||
|
|
||
| ```ts | ||
| import type { EmaPluginProvider, Server } from "ema"; | ||
| export const Plugin: EmaPluginProvider = class { | ||
| static name = "QQ"; | ||
| constructor(private readonly server: Server) {} | ||
| start(): Promise<void> { | ||
| console.log("[ema-qq] started", !!this.server.chat); | ||
| return Promise.resolve(); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| 根据编译错误的指引实现 `ema-plugin-discord` 包。 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| { | ||
| "name": "ema-plugin-qq", | ||
| "version": "0.1.0", | ||
| "type": "module", | ||
| "keywords": [ | ||
| "qq" | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/EmaFanClub/EverMemoryArchive.git", | ||
| "directory": "packages/ema-plugin-qq" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/EmaFanClub/EverMemoryArchive/issues" | ||
| }, | ||
| "dependencies": { | ||
| "ema": "workspace:*", | ||
| "node-napcat-ts": "^0.4.20" | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "types": "./src/index.ts", | ||
| "default": "./src/index.ts" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import type { AgentEventContent, EmaPluginProvider, Server } from "ema"; | ||
| import { NCWebsocket, type GroupMessage } from "node-napcat-ts"; | ||
| import { execSync } from "child_process"; | ||
|
|
||
| const gitRev = getGitRev(); | ||
| const gitRemote = getGitRemote(); | ||
|
|
||
| const napcat = new NCWebsocket( | ||
| { | ||
| protocol: "ws", | ||
| host: "172.19.0.2", | ||
| port: 6097, | ||
| accessToken: process.env.NAPCAT_ACCESS_TOKEN, | ||
| // 是否需要在触发 socket.error 时抛出错误, 默认关闭 | ||
| throwPromise: true, | ||
| // ↓ 自动重连(可选) | ||
| reconnection: { | ||
| enable: true, | ||
| attempts: 10, | ||
| delay: 5000, | ||
| }, | ||
| // ↓ 是否开启 DEBUG 模式 | ||
| }, | ||
| true, | ||
| ); | ||
|
|
||
| export const Plugin: EmaPluginProvider = class { | ||
| static name = "QQ"; | ||
| constructor(private readonly server: Server) {} | ||
| async start(): Promise<void> { | ||
| const replyPat = process.env.NAPCAT_REPLY_PATTERN; | ||
| if (!replyPat) { | ||
| throw new Error("NAPCAT_REPLY_PATTERN is not set"); | ||
| } | ||
|
|
||
| await napcat.connect(); | ||
|
|
||
| const actor = await this.server.getActor(1, 1); | ||
|
|
||
| interface GroupMessageTask { | ||
| message: GroupMessage; | ||
| } | ||
|
|
||
| let taskId = 0; | ||
| const tasks: Record<number, GroupMessageTask> = {}; | ||
| const messageCache = new Map<number, string>(); | ||
|
|
||
| actor.subscribe((response) => { | ||
| console.log("[ema-qq] actor response", response); | ||
| for (const event of response.events) { | ||
| console.log("[ema-qq] actor event", event); | ||
| if (event.type === "runFinished") { | ||
| const runFinishedEvent = | ||
| event.content as AgentEventContent<"runFinished">; | ||
| console.log("[ema-qq] actor run finished", runFinishedEvent); | ||
| if (runFinishedEvent.ok) { | ||
| const task = tasks[runFinishedEvent.metadata.taskId]; | ||
| if (!task) { | ||
| console.error( | ||
| "[ema-qq] task not found", | ||
| runFinishedEvent.metadata.taskId, | ||
| ); | ||
| continue; | ||
| } | ||
| const message = task.message; | ||
| const msg = runFinishedEvent.msg | ||
| .trim() | ||
| .replaceAll( | ||
| gitRev, | ||
| `[${gitRev}]( ${gitRemote}/commit/${gitRev} )`, | ||
| ); | ||
| message.quick_action( | ||
| [ | ||
| { | ||
| type: "text", | ||
| data: { | ||
| text: ` ${msg}`, | ||
| }, | ||
| }, | ||
| ], | ||
| true, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| napcat.on("message.group", async (message) => { | ||
| // { type: 'reply', data: [Object] }, | ||
| console.log("[ema-qq] group message"); | ||
| console.log("[ema-qq] group message", message); | ||
|
|
||
| if (!message.raw_message.includes(replyPat)) { | ||
| console.log("message ignored"); | ||
| return; | ||
| } | ||
| let replyContext = ""; | ||
| const reply = message.message.find((m) => m.type === "reply"); | ||
| if (reply) { | ||
| const replyId = Number.parseInt(reply?.data.id); | ||
| if (replyId && !Number.isNaN(replyId)) { | ||
| const cached = messageCache.get(replyId); | ||
| if (cached) { | ||
| replyContext = cached; | ||
| } else { | ||
| const msg = await napcat.get_msg({ message_id: replyId }); | ||
| if (msg) { | ||
| replyContext = msg.raw_message; | ||
| messageCache.set(replyId, replyContext); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| messageCache.set(message.message_id, message.raw_message); | ||
|
|
||
| const id = taskId++; | ||
| tasks[id] = { message }; | ||
|
|
||
| let contentList = []; | ||
| if (replyContext) { | ||
| contentList.push(`注意:这则消息是在回复:<Reply>`); | ||
| contentList.push(replyContext); | ||
| contentList.push(`</Reply>`); | ||
| } | ||
| contentList.push(message.raw_message); | ||
| // current time | ||
| contentList.push(`当前时间:<Time>${new Date().toLocaleString()}</Time>`); | ||
| contentList.push(`GitRev(分支):<Rev>${gitRev}</Rev>`); | ||
|
|
||
| const content = contentList.join("\n"); | ||
|
|
||
| actor.work({ | ||
| metadata: { taskId: id }, | ||
| inputs: [{ kind: "text", content }], | ||
| }); | ||
| }); | ||
|
|
||
| return Promise.resolve(); | ||
| } | ||
| }; | ||
|
|
||
| function getGitRev(): string { | ||
| // git commit hash | ||
| return execSync("git rev-parse HEAD").toString().trim(); | ||
| } | ||
|
|
||
| function getGitRemote(): string { | ||
| return execSync("git remote get-url origin").toString().trim(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ESNext", | ||
| "module": "ESNext", | ||
| "moduleResolution": "bundler", | ||
| "strict": true, | ||
| "verbatimModuleSyntax": true, | ||
| "experimentalDecorators": true, | ||
| "skipLibCheck": true, | ||
| "types": ["vitest/globals", "node"], | ||
| "outDir": "./dist" | ||
| }, | ||
| "include": ["src/**/*"], | ||
| "exclude": ["node_modules"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { getServer } from "@/app/api/shared-server"; | ||
| import { loadPlugins } from "@/plugin"; | ||
|
|
||
| getServer() | ||
| .then(loadPlugins) | ||
| .catch((error) => { | ||
| console.error("Failed to load plugins:", error); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| export async function register() { | ||
| if (process.env.NEXT_RUNTIME === "nodejs") { | ||
| await import("./instrumentation-node"); | ||
| } | ||
|
|
||
| if (process.env.NEXT_RUNTIME === "edge") { | ||
| console.warn("Edge runtime is not supported yet"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,77 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { EmaPluginModule, Server } from "ema"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as fs from "fs"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Loads plugins by environment variable `EMA_PLUGINS` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param server - The server instance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function loadPlugins(server: Server): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Myriad-Dreamin marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+4
to
+8
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Comma-separated list of plugins to load | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @example | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * EMA_PLUGINS=qq,discord | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const enabledPlugins = new Set( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (process.env.EMA_PLUGINS ?? "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .split(",") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((name) => name.trim()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((name) => name.length > 0), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await Promise.all( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getPluginModules() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((name) => enabledPlugins.has(name)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(async (name: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const m: EmaPluginModule = await import(`ema-plugin-${name}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (m.Plugin.name in server.plugins) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`Plugin ${m.Plugin.name} already loaded`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const plugin = new m.Plugin(server); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.plugins[m.Plugin.name] = plugin; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`Failed to load plugin package "${name}":`, error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Myriad-Dreamin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+38
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const plugins = Object.entries(server.plugins); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await Promise.all( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plugins.map(async ([name, plugin]) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!plugin) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await plugin.start(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`Failed to start plugin "${name}":`, error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+54
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |
| * Loads plugins by environment variable `EMA_PLUGINS` | |
| * @param server - The server instance | |
| */ | |
| export async function loadPlugins(server: Server): Promise<void> { | |
| /** | |
| * Comma-separated list of plugins to load | |
| * | |
| * @example | |
| * EMA_PLUGINS=qq,discord | |
| */ | |
| const enabledPlugins = new Set( | |
| (process.env.EMA_PLUGINS ?? "") | |
| .split(",") | |
| .map((name) => name.trim()) | |
| .filter((name) => name.length > 0), | |
| ); | |
| await Promise.all( | |
| getPluginModules() | |
| .filter((name) => enabledPlugins.has(name)) | |
| .map(async (name: string) => { | |
| try { | |
| const m: EmaPluginModule = await import(`ema-plugin-${name}`); | |
| if (m.Plugin.name in server.plugins) { | |
| throw new Error(`Plugin ${m.Plugin.name} already loaded`); | |
| } | |
| const plugin = new m.Plugin(server); | |
| server.plugins[m.Plugin.name] = plugin; | |
| } catch (error) { | |
| console.error(`Failed to load plugin package "${name}":`, error); | |
| return; | |
| } | |
| }), | |
| ); | |
| const plugins = Object.entries(server.plugins); | |
| await Promise.all( | |
| plugins.map(async ([name, plugin]) => { | |
| if (!plugin) { | |
| return; | |
| } | |
| try { | |
| return await plugin.start(); | |
| } catch (error) { | |
| console.error(`Failed to start plugin "${name}":`, error); | |
| } | |
| }), | |
| ); | |
| const initializingServers = new WeakMap<Server, Promise<void>>(); | |
| const initializedServers = new WeakSet<Server>(); | |
| /** | |
| * Loads plugins by environment variable `EMA_PLUGINS` | |
| * @param server - The server instance | |
| */ | |
| export async function loadPlugins(server: Server): Promise<void> { | |
| if (initializedServers.has(server)) { | |
| return; | |
| } | |
| const existingInitialization = initializingServers.get(server); | |
| if (existingInitialization) { | |
| await existingInitialization; | |
| return; | |
| } | |
| const initializationPromise = (async () => { | |
| /** | |
| * Comma-separated list of plugins to load | |
| * | |
| * @example | |
| * EMA_PLUGINS=qq,discord | |
| */ | |
| const enabledPlugins = new Set( | |
| (process.env.EMA_PLUGINS ?? "") | |
| .split(",") | |
| .map((name) => name.trim()) | |
| .filter((name) => name.length > 0), | |
| ); | |
| await Promise.all( | |
| getPluginModules() | |
| .filter((name) => enabledPlugins.has(name)) | |
| .map(async (name: string) => { | |
| try { | |
| const m: EmaPluginModule = await import(`ema-plugin-${name}`); | |
| if (m.Plugin.name in server.plugins) { | |
| throw new Error(`Plugin ${m.Plugin.name} already loaded`); | |
| } | |
| const plugin = new m.Plugin(server); | |
| server.plugins[m.Plugin.name] = plugin; | |
| } catch (error) { | |
| console.error(`Failed to load plugin package "${name}":`, error); | |
| return; | |
| } | |
| }), | |
| ); | |
| const plugins = Object.entries(server.plugins); | |
| await Promise.all( | |
| plugins.map(async ([name, plugin]) => { | |
| if (!plugin) { | |
| return; | |
| } | |
| try { | |
| return await plugin.start(); | |
| } catch (error) { | |
| console.error(`Failed to start plugin "${name}":`, error); | |
| } | |
| }), | |
| ); | |
| })(); | |
| initializingServers.set(server, initializationPromise); | |
| try { | |
| await initializationPromise; | |
| initializedServers.add(server); | |
| } finally { | |
| initializingServers.delete(server); | |
| } |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new plugin loading functionality in loadPlugins and plugin infrastructure lacks test coverage. According to the custom guideline in the project (see CONTRIBUTING.md and existing tests in packages/ema/src/server.spec.ts), new features should include test cases. Consider adding tests for: plugin discovery from package.json, plugin loading with EMA_PLUGINS env var, error handling for missing/invalid plugins, and plugin lifecycle (start method).
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect JSDoc description. The comment states "Finds all plugin modules in the ema-ui package.json", but the function actually reads from the ema-ui package directory relative to the current file using import.meta.url. The description should clarify that it reads from the ema-ui package's package.json file.
| * Finds all plugin modules in the `ema-ui` package.json | |
| * Finds all plugin modules declared in the `ema-ui` package's package.json file, | |
| * which is read from the ema-ui package directory relative to this module via `import.meta.url`. |
Uh oh!
There was an error while loading. Please reload this page.