mioku-plugin-meme 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,10 +1,14 @@
1
- import type { AIService, ConfigService } from "mioku";
2
- import { definePlugin, type MiokiContext } from "mioki";
1
+ import {
2
+ definePlugin,
3
+ type MiokiContext,
4
+ getService,
5
+ Services,
6
+ } from "mioku";
3
7
  import { MEME_BASE_CONFIG } from "./configs/base";
4
8
  import { MEME_FILTER_CONFIG } from "./configs/filters";
5
9
  import { MemePluginRuntime, replyWithParts } from "./shared";
6
- import { resetMemeRuntimeState, setMemeRuntimeState } from "./runtime";
7
10
  import type { MemeBaseConfig, MemeFilterConfig } from "./types";
11
+ import { createMemeSkills } from "./skills/meme";
8
12
 
9
13
  function cloneConfig<T>(value: T): T {
10
14
  return JSON.parse(JSON.stringify(value)) as T;
@@ -42,8 +46,8 @@ const memePlugin = definePlugin({
42
46
  description: "基于 meme-generator API 的表情包制作插件",
43
47
 
44
48
  async setup(ctx: MiokiContext) {
45
- const configService = ctx.services?.config as ConfigService | undefined;
46
- const aiService = ctx.services?.ai as AIService | undefined;
49
+ const configService = getService(ctx, Services.Config);
50
+ const aiService = getService(ctx, Services.AI);
47
51
 
48
52
  let baseConfig = cloneConfig(MEME_BASE_CONFIG);
49
53
  let filterConfig = cloneConfig(MEME_FILTER_CONFIG);
@@ -123,7 +127,9 @@ const memePlugin = definePlugin({
123
127
  );
124
128
  }
125
129
 
126
- setMemeRuntimeState({ runtime });
130
+ if (aiService) {
131
+ for (const skill of createMemeSkills(runtime)) aiService.registerSkill(skill);
132
+ }
127
133
 
128
134
  const disposers: Array<() => void> = [];
129
135
  if (configService) {
@@ -141,7 +147,7 @@ const memePlugin = definePlugin({
141
147
  );
142
148
  }
143
149
 
144
- ctx.handle("message", async (event: any) => {
150
+ ctx.handle("message", async (event) => {
145
151
  const rawText = ctx.text(event)?.trim();
146
152
  if (!rawText) {
147
153
  return;
@@ -366,10 +372,8 @@ const memePlugin = definePlugin({
366
372
  });
367
373
 
368
374
  return () => {
369
- for (const dispose of disposers) {
370
- dispose();
371
- }
372
- resetMemeRuntimeState();
375
+ for (const dispose of disposers) dispose();
376
+ if (aiService) aiService.removeSkill("meme");
373
377
  ctx.logger.info("meme 插件已卸载");
374
378
  };
375
379
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-meme",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "基于 meme-generator API 的表情包制作插件",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -76,7 +76,7 @@
76
76
  "cmd": "/meme 更新",
77
77
  "desc": "刷新远端表情缓存",
78
78
  "usage": "/meme 更新",
79
- "role": "owner"
79
+ "role": "master"
80
80
  }
81
81
  ]
82
82
  }
package/skills/meme.ts ADDED
@@ -0,0 +1,149 @@
1
+ import type { AISkill, AITool } from "mioku";
2
+ import type { MemePluginRuntime } from "../shared";
3
+ import type { MemeImageSourceOptions } from "../types";
4
+
5
+ function parseImageSource(args: any): {
6
+ ok: boolean;
7
+ source?: MemeImageSourceOptions;
8
+ message?: string;
9
+ } {
10
+ const sourceType = String(args?.source_type || "").trim();
11
+ if (!sourceType) {
12
+ return {
13
+ ok: false,
14
+ message: "缺少 source_type。可选值: user_avatar 或 message_image。",
15
+ };
16
+ }
17
+
18
+ if (sourceType === "user_avatar") {
19
+ const qq = Number(args?.qq);
20
+ if (!Number.isFinite(qq) || qq <= 0) {
21
+ return {
22
+ ok: false,
23
+ message: "source_type=user_avatar 时必须提供有效的 qq。",
24
+ };
25
+ }
26
+ return {
27
+ ok: true,
28
+ source: {
29
+ type: "user_avatar",
30
+ qq,
31
+ },
32
+ };
33
+ }
34
+
35
+ if (sourceType === "message_image") {
36
+ const messageId = Number(args?.message_id);
37
+ if (!Number.isFinite(messageId) || messageId <= 0) {
38
+ return {
39
+ ok: false,
40
+ message: "source_type=message_image 时必须提供有效的 message_id。",
41
+ };
42
+ }
43
+ return {
44
+ ok: true,
45
+ source: {
46
+ type: "message_image",
47
+ messageId,
48
+ },
49
+ };
50
+ }
51
+
52
+ return {
53
+ ok: false,
54
+ message: "source_type 无效。只能是 user_avatar 或 message_image。",
55
+ };
56
+ }
57
+
58
+ export function createMemeSkills(runtime: MemePluginRuntime): AISkill[] {
59
+ return [
60
+ {
61
+ name: "meme",
62
+ description:
63
+ "搜索,制作和发送各种各样的整蛊群友的表情包,可以使用聊天中的图片或用户头像制作",
64
+ permission: "member",
65
+ tools: [
66
+ {
67
+ name: "search_memes",
68
+ description: "按关键词搜索可用的 meme 表情关键词",
69
+ parameters: {
70
+ type: "object",
71
+ properties: {
72
+ query: {
73
+ type: "string",
74
+ description: "搜索关键词,可以是中文或英文片段",
75
+ },
76
+ },
77
+ required: ["query"],
78
+ },
79
+ handler: async (args: any) => {
80
+ await runtime.ensureCache();
81
+ const hits = runtime.searchKeywords(String(args?.query || ""));
82
+ return hits.length > 0
83
+ ? hits.join("、")
84
+ : "没有找到匹配的 meme 关键词";
85
+ },
86
+ } as AITool,
87
+ {
88
+ name: "send_meme",
89
+ description:
90
+ "发送 meme。必须指定图片来源:用户头像(qq)或聊天图片(message_id)。",
91
+ parameters: {
92
+ type: "object",
93
+ properties: {
94
+ keyword: {
95
+ type: "string",
96
+ description: "要生成的 meme 关键词",
97
+ },
98
+ text: {
99
+ type: "string",
100
+ description: "文本内容,多段文本用 / 分隔",
101
+ },
102
+ args: {
103
+ type: "string",
104
+ description: "额外参数,例如 圆、左、循环 等",
105
+ },
106
+ source_type: {
107
+ type: "string",
108
+ description:
109
+ "图片来源类型,只能是 user_avatar 或 message_image",
110
+ enum: ["user_avatar", "message_image"],
111
+ },
112
+ qq: {
113
+ type: "number",
114
+ description: "source_type=user_avatar 时必填,头像对应 QQ 号",
115
+ },
116
+ message_id: {
117
+ type: "number",
118
+ description:
119
+ "source_type=message_image 时必填,包含图片的消息 message_id",
120
+ },
121
+ },
122
+ required: ["keyword", "source_type"],
123
+ },
124
+ handler: async (args: any, runtimeCtx?: any) => {
125
+ const ctx = runtimeCtx?.ctx;
126
+ const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
127
+ if (!ctx || !event) {
128
+ return "当前上下文不支持发送 meme";
129
+ }
130
+
131
+ const source = parseImageSource(args);
132
+ if (!source.ok) {
133
+ return source.message || "图片来源参数不合法";
134
+ }
135
+
136
+ const result = await runtime.generateByKeyword(ctx, event, {
137
+ keyword: String(args?.keyword || ""),
138
+ text: args?.text ? String(args.text) : "",
139
+ args: args?.args ? String(args.args) : "",
140
+ imageSource: source.source,
141
+ send: true,
142
+ });
143
+ return result.message;
144
+ },
145
+ } as AITool,
146
+ ],
147
+ },
148
+ ];
149
+ }
package/runtime.ts DELETED
@@ -1,26 +0,0 @@
1
- import type { MemePluginRuntime } from "./shared";
2
- import {
3
- getPluginRuntimeState,
4
- resetPluginRuntimeState,
5
- setPluginRuntimeState,
6
- } from "mioku";
7
-
8
- export interface MemeRuntimeState {
9
- runtime?: MemePluginRuntime;
10
- }
11
-
12
- const MEME_PLUGIN_NAME = "meme";
13
-
14
- export function setMemeRuntimeState(
15
- nextState: MemeRuntimeState,
16
- ): MemeRuntimeState {
17
- return setPluginRuntimeState(MEME_PLUGIN_NAME, nextState) as MemeRuntimeState;
18
- }
19
-
20
- export function getMemeRuntimeState(): MemeRuntimeState {
21
- return getPluginRuntimeState(MEME_PLUGIN_NAME) as MemeRuntimeState;
22
- }
23
-
24
- export function resetMemeRuntimeState(): void {
25
- resetPluginRuntimeState(MEME_PLUGIN_NAME);
26
- }
package/skills.ts DELETED
@@ -1,157 +0,0 @@
1
- import type { AISkill, AITool } from "mioku";
2
- import type { MemeImageSourceOptions } from "./types";
3
- import { getMemeRuntimeState } from "./runtime";
4
-
5
- function parseImageSource(args: any): {
6
- ok: boolean;
7
- source?: MemeImageSourceOptions;
8
- message?: string;
9
- } {
10
- const sourceType = String(args?.source_type || "").trim();
11
- if (!sourceType) {
12
- return {
13
- ok: false,
14
- message: "缺少 source_type。可选值: user_avatar 或 message_image。",
15
- };
16
- }
17
-
18
- if (sourceType === "user_avatar") {
19
- const qq = Number(args?.qq);
20
- if (!Number.isFinite(qq) || qq <= 0) {
21
- return {
22
- ok: false,
23
- message: "source_type=user_avatar 时必须提供有效的 qq。",
24
- };
25
- }
26
- return {
27
- ok: true,
28
- source: {
29
- type: "user_avatar",
30
- qq,
31
- },
32
- };
33
- }
34
-
35
- if (sourceType === "message_image") {
36
- const messageId = Number(args?.message_id);
37
- if (!Number.isFinite(messageId) || messageId <= 0) {
38
- return {
39
- ok: false,
40
- message: "source_type=message_image 时必须提供有效的 message_id。",
41
- };
42
- }
43
- return {
44
- ok: true,
45
- source: {
46
- type: "message_image",
47
- messageId,
48
- },
49
- };
50
- }
51
-
52
- return {
53
- ok: false,
54
- message: "source_type 无效。只能是 user_avatar 或 message_image。",
55
- };
56
- }
57
-
58
- const memeSkills: AISkill[] = [
59
- {
60
- name: "meme",
61
- description:
62
- "搜索,制作和发送各种各样的表情包,可以使用聊天中的图片或用户头像制作,如制作摸别人活其他的表情",
63
- permission: "member",
64
- tools: [
65
- {
66
- name: "search_memes",
67
- description: "按关键词搜索可用的 meme 表情关键词",
68
- parameters: {
69
- type: "object",
70
- properties: {
71
- query: {
72
- type: "string",
73
- description: "搜索关键词,可以是中文或英文片段",
74
- },
75
- },
76
- required: ["query"],
77
- },
78
- handler: async (args: any) => {
79
- const runtime = getMemeRuntimeState().runtime;
80
- if (!runtime) {
81
- return "meme 插件尚未初始化";
82
- }
83
-
84
- await runtime.ensureCache();
85
- const hits = runtime.searchKeywords(String(args?.query || ""));
86
- return hits.length > 0
87
- ? hits.join("、")
88
- : "没有找到匹配的 meme 关键词";
89
- },
90
- } as AITool,
91
- {
92
- name: "send_meme",
93
- description:
94
- "发送 meme。必须指定图片来源:用户头像(qq)或聊天图片(message_id)。",
95
- parameters: {
96
- type: "object",
97
- properties: {
98
- keyword: {
99
- type: "string",
100
- description: "要生成的 meme 关键词",
101
- },
102
- text: {
103
- type: "string",
104
- description: "文本内容,多段文本用 / 分隔",
105
- },
106
- args: {
107
- type: "string",
108
- description: "额外参数,例如 圆、左、循环 等",
109
- },
110
- source_type: {
111
- type: "string",
112
- description: "图片来源类型,只能是 user_avatar 或 message_image",
113
- enum: ["user_avatar", "message_image"],
114
- },
115
- qq: {
116
- type: "number",
117
- description: "source_type=user_avatar 时必填,头像对应 QQ 号",
118
- },
119
- message_id: {
120
- type: "number",
121
- description:
122
- "source_type=message_image 时必填,包含图片的消息 message_id",
123
- },
124
- },
125
- required: ["keyword", "source_type"],
126
- },
127
- handler: async (args: any, runtimeCtx?: any) => {
128
- const runtime = getMemeRuntimeState().runtime;
129
- const ctx = runtimeCtx?.ctx;
130
- const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
131
- if (!runtime) {
132
- return "meme 插件尚未初始化";
133
- }
134
- if (!ctx || !event) {
135
- return "当前上下文不支持发送 meme";
136
- }
137
-
138
- const source = parseImageSource(args);
139
- if (!source.ok) {
140
- return source.message || "图片来源参数不合法";
141
- }
142
-
143
- const result = await runtime.generateByKeyword(ctx, event, {
144
- keyword: String(args?.keyword || ""),
145
- text: args?.text ? String(args.text) : "",
146
- args: args?.args ? String(args.args) : "",
147
- imageSource: source.source,
148
- send: true,
149
- });
150
- return result.message;
151
- },
152
- } as AITool,
153
- ],
154
- },
155
- ];
156
-
157
- export default memeSkills;