nonebot-plugin-savepic 0.2.0__tar.gz → 0.2.5__tar.gz

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.
Files changed (22) hide show
  1. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/PKG-INFO +30 -3
  2. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/README.md +26 -2
  3. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/__init__.py +418 -0
  4. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/ai_utils.py +66 -0
  5. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/config.py +25 -0
  6. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/countpic.py +22 -0
  7. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/error.py +24 -0
  8. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/nonebot_plugin_savepic/ext_listener.py +5 -13
  9. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/model.py +108 -0
  10. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/networks/__init__.py +0 -0
  11. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/networks/resnet_big.py +242 -0
  12. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/pic_sql.py +397 -0
  13. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/nonebot_plugin_savepic/picture.py +40 -11
  14. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/randpic.py +47 -0
  15. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/nonebot_plugin_savepic/rule.py +21 -4
  16. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/pyproject.toml +3 -1
  17. nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/__init__.py +0 -190
  18. nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/config.py +0 -9
  19. nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/migrations/1a654e02179b_.py +0 -34
  20. nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/model.py +0 -26
  21. nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/pic_sql.py +0 -219
  22. {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.5}/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nonebot-plugin-savepic
3
- Version: 0.2.0
3
+ Version: 0.2.5
4
4
  Summary: 保存表情包(语录)与随机出图
5
5
  Home-page: https://github.com/Yan-Zero/nonebot-plugin-savepic
6
6
  License: MIT
@@ -11,13 +11,16 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
14
15
  Requires-Dist: arclet-alconna (>=1.7.14,<2.0.0)
15
16
  Requires-Dist: asyncpg (>=0.28.0,<0.29.0)
17
+ Requires-Dist: dashscope (>=1.17.0,<2.0.0)
16
18
  Requires-Dist: nonebot-adapter-onebot (>=2.2.3,<3.0.0)
17
19
  Requires-Dist: nonebot-plugin-datastore (>=1.1.0,<2.0.0)
18
20
  Requires-Dist: nonebot2 (>=2.0.1,<3.0.0)
19
21
  Requires-Dist: numpy (>=1.25.2,<2.0.0)
20
22
  Requires-Dist: pillow (>=10.0.0,<11.0.0)
23
+ Requires-Dist: pinecone-client (>=3.2.2,<4.0.0)
21
24
  Requires-Dist: scipy (>=1.11.1,<2.0.0)
22
25
  Description-Content-Type: text/markdown
23
26
 
@@ -45,10 +48,27 @@ _✨ 一个存取图片的插件 ✨_
45
48
 
46
49
  抽取表情包
47
50
 
51
+ ### mvpic
52
+
53
+ 重命名表情包,或者修改表情包所属的群域
54
+
55
+ 例如:
56
+ ```
57
+ /mvpic -l name.jpg -g waaaaa.gif
58
+ ```
59
+ 就是把本群的 name.jpg 改成全局名为 waaaaa.gif 的表情包
60
+
61
+ 同理
62
+ ```
63
+ /mvpic -g waaaaa.gif -l waaaaa.gif
64
+ ```
65
+ 就是从全局移到本群(接收到命令的群)
66
+
48
67
  ### 直接发送文件名
49
68
 
50
69
  发送文件名即可发送表情包
51
70
 
71
+
52
72
  ## 💿 安装
53
73
 
54
74
  <details open>
@@ -98,11 +118,18 @@ _✨ 一个存取图片的插件 ✨_
98
118
  |:-----:|:----:|:----:|:----:|
99
119
  | savepic_admin | 否 | 无 | 权限用户 |
100
120
  | savepic_dir | 否 | savepic | 图片本地保存位置 |
121
+ | simpic_enable | 否 | False | 是否开启基于RNN模型的AI相似度检索功能,如果开启要填写 Pinecone 相关配置 |
122
+ | pinecone_apikey | 否 | 无 | pinecone 的 Key |
123
+ | pinecone_environment | 否 | 无 | pinecone 的 enviroment,是数据库的属性之一 |
124
+ | pinecone_index | 否 | savepic | 同上。 |
125
+ | dashscope_api | 否 | 无 | dashscope 的 APIKey。 用于开启基于语义的相似度检索功能 |
101
126
 
102
127
  ## 🎉 使用
103
128
  ### 指令表
104
129
  | 指令 | 权限 | 需要@ | 范围 | 说明 |
105
130
  |:-----:|:----:|:----:|:----:|:----:|
106
- | savepic | 群员 | 是 | 群聊 | 保存图片 |
107
- | randpic | 群员 | 是 | 全部 | 随机图片 |
131
+ | savepic | 群员 | 否 | 群聊 | 保存图片 |
132
+ | randpic | 群员 | 否 | 全部 | 随机图片 |
133
+ | mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
134
+
108
135
 
@@ -22,10 +22,27 @@ _✨ 一个存取图片的插件 ✨_
22
22
 
23
23
  抽取表情包
24
24
 
25
+ ### mvpic
26
+
27
+ 重命名表情包,或者修改表情包所属的群域
28
+
29
+ 例如:
30
+ ```
31
+ /mvpic -l name.jpg -g waaaaa.gif
32
+ ```
33
+ 就是把本群的 name.jpg 改成全局名为 waaaaa.gif 的表情包
34
+
35
+ 同理
36
+ ```
37
+ /mvpic -g waaaaa.gif -l waaaaa.gif
38
+ ```
39
+ 就是从全局移到本群(接收到命令的群)
40
+
25
41
  ### 直接发送文件名
26
42
 
27
43
  发送文件名即可发送表情包
28
44
 
45
+
29
46
  ## 💿 安装
30
47
 
31
48
  <details open>
@@ -75,10 +92,17 @@ _✨ 一个存取图片的插件 ✨_
75
92
  |:-----:|:----:|:----:|:----:|
76
93
  | savepic_admin | 否 | 无 | 权限用户 |
77
94
  | savepic_dir | 否 | savepic | 图片本地保存位置 |
95
+ | simpic_enable | 否 | False | 是否开启基于RNN模型的AI相似度检索功能,如果开启要填写 Pinecone 相关配置 |
96
+ | pinecone_apikey | 否 | 无 | pinecone 的 Key |
97
+ | pinecone_environment | 否 | 无 | pinecone 的 enviroment,是数据库的属性之一 |
98
+ | pinecone_index | 否 | savepic | 同上。 |
99
+ | dashscope_api | 否 | 无 | dashscope 的 APIKey。 用于开启基于语义的相似度检索功能 |
78
100
 
79
101
  ## 🎉 使用
80
102
  ### 指令表
81
103
  | 指令 | 权限 | 需要@ | 范围 | 说明 |
82
104
  |:-----:|:----:|:----:|:----:|:----:|
83
- | savepic | 群员 | 是 | 群聊 | 保存图片 |
84
- | randpic | 群员 | 是 | 全部 | 随机图片 |
105
+ | savepic | 群员 | 否 | 群聊 | 保存图片 |
106
+ | randpic | 群员 | 否 | 全部 | 随机图片 |
107
+ | mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
108
+
@@ -0,0 +1,418 @@
1
+ from pathlib import Path
2
+ from nonebot import require
3
+ from nonebot import get_plugin_config
4
+ from nonebot import on_command
5
+ from nonebot.params import CommandArg, Arg
6
+ from nonebot.adapters.onebot.v11.message import Message as V11Msg
7
+ from nonebot.adapters.onebot.v11.message import MessageSegment as V11Seg
8
+ from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN
9
+ from nonebot.adapters.onebot.v11.event import GroupMessageEvent
10
+ from nonebot.adapters.onebot.v11.event import MessageEvent
11
+ from nonebot.matcher import Matcher
12
+ from nonebot.plugin import PluginMetadata
13
+ from nonebot.dependencies import Dependent
14
+ from sqlalchemy.exc import DBAPIError
15
+ from arclet.alconna import Alconna, Option, Args, CommandMeta, append
16
+ import os
17
+ import random
18
+ import json
19
+ from typing import (
20
+ Any,
21
+ Union,
22
+ Callable,
23
+ Iterable,
24
+ Optional,
25
+ )
26
+ from nonebot.typing import (
27
+ T_State,
28
+ T_Handler,
29
+ )
30
+ from nonebot.consts import ARG_KEY
31
+ from nonebot.internal.params import Depends
32
+ from nonebot.internal.adapter import (
33
+ Bot,
34
+ Event,
35
+ Message,
36
+ MessageSegment,
37
+ MessageTemplate,
38
+ )
39
+
40
+
41
+ require("nonebot_plugin_datastore")
42
+
43
+ from .error import (
44
+ NoPictureException,
45
+ SimilarPictureException,
46
+ SameNameException,
47
+ ) # noqa: E402, E501
48
+ from .config import Config # noqa: E402
49
+ from .pic_sql import ( # noqa: E402
50
+ savepic,
51
+ rename,
52
+ delete,
53
+ regexp_pic,
54
+ listpic,
55
+ )
56
+ from .rule import PIC_AMDIN, BLACK_GROUP
57
+ from .ext_listener import pic_listen # noqa: E402, F401
58
+ from .picture import write_pic, load_pic # noqa: E402
59
+ from .ai_utils import img2vec
60
+ from .randpic import rpic
61
+ from .countpic import cpic
62
+
63
+
64
+ def url_to_image(url: str):
65
+ if url.startswith("http"):
66
+ return V11Seg.image(url)
67
+ return V11Seg.image(Path(url))
68
+
69
+
70
+ words = {}
71
+ for root, _, files in os.walk("words"):
72
+ for file in files:
73
+ if not file.endswith(".json"):
74
+ continue
75
+ with open(os.path.join(root, file), "r", encoding="utf-8") as f:
76
+ words.update(json.load(f))
77
+
78
+ __plugin_meta__ = PluginMetadata(
79
+ name="Savepic",
80
+ description="表情包保存",
81
+ usage="""用法:
82
+ /savepic <文件名> <图片>
83
+ /savepic -g <文件名> <全局图片>
84
+ /savepic -d <文件名> 删除图图片
85
+ /randpic <关键词> 随机图片
86
+ <文件名> 发送图片""",
87
+ config=Config,
88
+ homepage="https://github.com/Yan-Zero/nonebot-plugin-savepic",
89
+ type="application",
90
+ supported_adapters=["~onebot.v11"],
91
+ )
92
+ INVALID_FILENAME_CHARACTERS = r'\/:*?"<>|'
93
+
94
+ p_config = get_plugin_config(Config)
95
+ repic = on_command("repic", priority=5, permission=BLACK_GROUP)
96
+ spic = on_command("savepic", priority=5, permission=BLACK_GROUP)
97
+ a_spic = Alconna(
98
+ "/savepic",
99
+ Option("-d", help_text="删除图片"),
100
+ Option("-g", help_text="全局"),
101
+ Option("-ac", help_text="允许相似碰撞"),
102
+ Args.filename[str],
103
+ meta=CommandMeta(description="保存图片,默认保存到本群"),
104
+ )
105
+ s_mvpic = on_command("mvpic", priority=5, permission=BLACK_GROUP)
106
+ a_mvpic = Alconna(
107
+ "/mvpic",
108
+ Option("-l", args=Args.filename[str], help_text="本地图片", action=append),
109
+ Option("-g", args=Args.filename[str], help_text="全局图片", action=append),
110
+ meta=CommandMeta(description="重命名图片,按照参数先后判断"),
111
+ )
112
+ s_simpic = on_command("simpic", priority=5, permission=BLACK_GROUP)
113
+ s_listpic = on_command("listpic", priority=5, permission=BLACK_GROUP)
114
+
115
+
116
+ def got_random_prompt(
117
+ cls: Matcher,
118
+ key: str,
119
+ prompt: Optional[list[Union[str, Message, MessageSegment, MessageTemplate]]] = None,
120
+ parameterless: Optional[Iterable[Any]] = None,
121
+ ) -> Callable[[T_Handler], T_Handler]:
122
+ """装饰一个函数来指示 NoneBot 获取一个参数 `key`
123
+
124
+ 当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
125
+ 如果 `key` 已存在则直接继续运行
126
+
127
+ 参数:
128
+ key: 参数名
129
+ prompt: 在参数不存在时向用户发送的消息
130
+ parameterless: 非参数类型依赖列表
131
+ """
132
+
133
+ async def _key_getter(event: Event, matcher: "Matcher"):
134
+ matcher.set_target(ARG_KEY.format(key=key))
135
+ if matcher.get_target() == ARG_KEY.format(key=key):
136
+ matcher.set_arg(key, event.get_message())
137
+ return
138
+ if matcher.get_arg(key, ...) is not ...:
139
+ return
140
+ await matcher.reject("" if not prompt else random.choice(prompt))
141
+
142
+ _parameterless = (Depends(_key_getter), *(parameterless or ()))
143
+
144
+ def _decorator(func: T_Handler) -> T_Handler:
145
+ if cls.handlers and cls.handlers[-1].call is func:
146
+ func_handler = cls.handlers[-1]
147
+ new_handler = Dependent(
148
+ call=func_handler.call,
149
+ params=func_handler.params,
150
+ parameterless=Dependent.parse_parameterless(
151
+ tuple(_parameterless), cls.HANDLER_PARAM_TYPES
152
+ )
153
+ + func_handler.parameterless,
154
+ )
155
+ cls.handlers[-1] = new_handler
156
+ else:
157
+ cls.append_handler(func, parameterless=_parameterless)
158
+
159
+ return func
160
+
161
+ return _decorator
162
+
163
+
164
+ @repic.handle()
165
+ async def _(bot: Bot, event, args: V11Msg = CommandArg()):
166
+ reg = args.extract_plain_text().strip()
167
+ group_id = (
168
+ "globe"
169
+ if not isinstance(event, GroupMessageEvent)
170
+ else f"qq_group:{event.group_id}"
171
+ )
172
+ try:
173
+ pic = await regexp_pic(reg, group_id)
174
+ if pic:
175
+ await bot.send(event, V11Msg([pic.name, url_to_image(pic.url)]))
176
+ except DBAPIError as ex:
177
+ await repic.finish(
178
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex.orig}'
179
+ )
180
+ except Exception as ex:
181
+ await repic.finish(
182
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex}'
183
+ )
184
+
185
+
186
+ @s_listpic.handle()
187
+ async def _(bot: Bot, event, args: V11Msg = CommandArg()):
188
+ reg = args.extract_plain_text().strip().rsplit("\\page", maxsplit=1)
189
+ try:
190
+ if len(reg) > 1:
191
+ reg, pages = reg
192
+ else:
193
+ reg, pages = reg[0], 0
194
+ pages = int(pages)
195
+ except Exception as ex:
196
+ await repic.finish(
197
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex}'
198
+ )
199
+
200
+ group_id = (
201
+ "globe"
202
+ if not isinstance(event, GroupMessageEvent)
203
+ else f"qq_group:{event.group_id}"
204
+ )
205
+ try:
206
+ pic = await listpic(reg, group_id, pages=pages)
207
+ if pic:
208
+ await bot.send(event, "\n".join(pic))
209
+ except DBAPIError as ex:
210
+ await repic.finish(
211
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex.orig}'
212
+ )
213
+ except Exception as ex:
214
+ await repic.finish(
215
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex}'
216
+ )
217
+
218
+
219
+ @spic.handle()
220
+ async def _(
221
+ bot: Bot,
222
+ matcher: Matcher,
223
+ event: GroupMessageEvent,
224
+ state: T_State,
225
+ ):
226
+ command = a_spic.parse(event.message.extract_plain_text())
227
+ if not command.matched:
228
+ await spic.finish(str(command.error_info) + "\n\n" + a_spic.get_help())
229
+
230
+ state["savepiv_group"] = (
231
+ "globe"
232
+ if command.g and await PIC_AMDIN(bot, event)
233
+ else f"qq_group:{event.group_id}"
234
+ )
235
+ state["savepiv_warning"] = (
236
+ ""
237
+ if not command.g or await PIC_AMDIN(bot, event)
238
+ else "\n\n你的 -g 选项没有用哟"
239
+ )
240
+ filename = command.filename
241
+ for c in INVALID_FILENAME_CHARACTERS:
242
+ filename = filename.replace(c, "-")
243
+ if not filename.endswith((".jpg", ".png", ".gif")):
244
+ filename += ".jpg"
245
+ state["savepiv_filename"] = filename
246
+ state["savepiv_ac"] = command.ac is not None
247
+
248
+ if command.d:
249
+ if not (await PIC_AMDIN(bot, event) or await GROUP_ADMIN(bot, event)):
250
+ await spic.finish(
251
+ random.choice(words.get("permission denied", ["没有权限"]))
252
+ )
253
+ try:
254
+ await delete(filename, state["savepiv_group"])
255
+ except Exception as ex:
256
+ await spic.finish(str(ex))
257
+ await spic.finish("图片已删除" + state["savepiv_warning"])
258
+
259
+ picture = event.message.get("image")
260
+ if not picture and event.reply:
261
+ picture = event.reply.message.get("image")
262
+ if picture:
263
+ matcher.set_arg("picture", picture)
264
+
265
+
266
+ @got_random_prompt(spic, "picture", words.get("wait for image", ["图呢"]))
267
+ async def _(state: T_State, picture: V11Msg = Arg()):
268
+ picture = picture.get("image")
269
+ if not picture:
270
+ await spic.finish(random.choice(words.get("not image", ["6,这也不是图啊"])))
271
+
272
+ try:
273
+ dir = await write_pic(picture[0].data["url"], p_config.savepic_dir)
274
+ except Exception as ex:
275
+ await spic.finish("存图失败。" + "\n" + str(ex))
276
+ try:
277
+ await savepic(
278
+ state["savepiv_filename"],
279
+ dir,
280
+ img2vec(await load_pic(dir), state["savepiv_filename"]),
281
+ state["savepiv_group"],
282
+ state["savepiv_ac"],
283
+ )
284
+ except SameNameException:
285
+ os.remove(dir)
286
+ await spic.finish(
287
+ random.choice(words.get("name has been taken", ["文件名重复"]))
288
+ )
289
+ except SimilarPictureException as ex:
290
+ os.remove(dir)
291
+ try:
292
+ image = await load_pic(ex.url)
293
+ except Exception as exc:
294
+ await spic.finish(
295
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{exc}'
296
+ )
297
+
298
+ await spic.finish(
299
+ V11Msg(
300
+ [
301
+ random.choice(words.get("similar picture", ["存在相似图片"])),
302
+ "\n\n" + ex.name,
303
+ f"\n(相似度:{'%.4g' % (min(ex.similarity * 100, 100.0))}%)\n",
304
+ V11Seg.image(file=image),
305
+ ]
306
+ )
307
+ )
308
+ except Exception as ex:
309
+ os.remove(dir)
310
+ await spic.finish(f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex}')
311
+ await spic.send(
312
+ random.choice(words.get("save succeed", ["保存成功"]))
313
+ + state["savepiv_warning"]
314
+ )
315
+
316
+
317
+ @s_mvpic.handle()
318
+ async def _(
319
+ bot: Bot,
320
+ event: GroupMessageEvent,
321
+ ):
322
+ if not (await PIC_AMDIN(bot, event) or await GROUP_ADMIN(bot, event)):
323
+ await spic.finish(random.choice(words.get("permission denied", ["没有权限"])))
324
+
325
+ cmd = a_mvpic.parse(event.message.extract_plain_text())
326
+ if not cmd.matched:
327
+ await s_mvpic.finish(str(cmd.error_info) + "\n\n" + a_mvpic.get_help())
328
+ options = cmd.options
329
+ if not options:
330
+ await s_mvpic.finish("文件名呢?" + "\n\n" + a_mvpic.get_help())
331
+
332
+ sg = "globe" if list(options.keys())[0] == "g" else f"qq_group:{event.group_id}"
333
+ if len(options["g" if sg == "globe" else "l"].args.get("filename", [])) >= 2:
334
+ dg = sg
335
+ else:
336
+ dg = "globe" if sg != "globe" else f"qq_group:{event.group_id}"
337
+ if ("g" if dg == "globe" else "l") not in options:
338
+ await s_mvpic.finish("至多只有一个文件名哦?" + "\n\n" + a_mvpic.get_help())
339
+
340
+ if (not await PIC_AMDIN(bot, event)) and await GROUP_ADMIN(bot, event):
341
+ if sg == "globe":
342
+ await spic.finish("管理员不能改全局名称哦~")
343
+ if dg == "globe":
344
+ await spic.finish("管理员不能改全局名称哦~")
345
+
346
+ sname = options["g" if sg == "globe" else "l"].args["filename"][0]
347
+ dname = options["g" if dg == "globe" else "l"].args["filename"][
348
+ 1 if sg == dg else 0
349
+ ]
350
+ for c in INVALID_FILENAME_CHARACTERS:
351
+ sname = sname.replace(c, "-")
352
+ dname = dname.replace(c, "-")
353
+ if not sname.endswith((".jpg", ".png", ".gif")):
354
+ sname += ".jpg"
355
+ if not dname.endswith((".jpg", ".png", ".gif")):
356
+ dname += ".jpg"
357
+
358
+ try:
359
+ await rename(sname, dname, sg, dg)
360
+ except NoPictureException as ex:
361
+ await spic.finish(
362
+ random.choice(words.get("not found", [ex.name + " 没有找到哦"]))
363
+ + f"\n{ex.name} 没有找到哦"
364
+ )
365
+ except SameNameException:
366
+ await spic.finish(
367
+ random.choice(words.get("name has been taken", ["文件名重复"]))
368
+ )
369
+ except Exception as ex:
370
+ await spic.finish(f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex}')
371
+ await spic.finish(random.choice(words.get("rename succeed", ["图片已重命名"])))
372
+
373
+
374
+ @s_simpic.handle()
375
+ async def _(bot: Bot, event: MessageEvent, args: V11Msg = CommandArg()):
376
+ if True:
377
+ await s_simpic.finish("simpic 并未开启喵。")
378
+
379
+ picture = event.reply.message.get("image") if event.reply else None
380
+ if not picture:
381
+ await s_simpic.finish(
382
+ random.choice(
383
+ words.get("missing image reference", ["没找到引用消息中的图片"])
384
+ )
385
+ )
386
+ vec = img2vec(await load_pic(picture[0].data["url"]))
387
+ ignore_diagonal = True
388
+ ignore_min = False
389
+ if args.extract_plain_text().strip() == "-n":
390
+ ignore_diagonal = False
391
+ elif args.extract_plain_text().strip() == "-i":
392
+ ignore_min = True
393
+ group_id = (
394
+ "globe"
395
+ if not isinstance(event, GroupMessageEvent)
396
+ else f"qq_group:{event.group_id}"
397
+ )
398
+ try:
399
+ sim, pic = await get_most_similar_pic(
400
+ vec, group_id, ignore_diagonal, ignore_min
401
+ )
402
+ if pic:
403
+ await bot.send(
404
+ event,
405
+ V11Msg(
406
+ [
407
+ V11Seg.reply(event.reply.message_id),
408
+ f"{pic.name}\n(相似性:{'%.4g' % (min(sim * 100, 100.0))}%)\n",
409
+ url_to_image(pic.url),
410
+ ]
411
+ ),
412
+ )
413
+ else:
414
+ await s_simpic.send(random.choice(words.get("not found", ["Nope."])))
415
+ except Exception as ex:
416
+ await s_simpic.finish(
417
+ f'{random.choice(words.get("error", ["出错了喵~"]))}\n\n{ex}'
418
+ )
@@ -0,0 +1,66 @@
1
+ import tempfile
2
+ import hashlib
3
+ import pathlib
4
+
5
+ from nonebot import get_plugin_config
6
+ from dashscope import MultiModalEmbedding
7
+ from dashscope import TextEmbedding
8
+ from http import HTTPStatus
9
+
10
+ from .config import Config
11
+ from .picture import _emo_same
12
+
13
+
14
+ plugin_config = get_plugin_config(Config)
15
+
16
+
17
+ def file2vec(path: pathlib.Path, title: str = None) -> list:
18
+ input = [
19
+ {
20
+ "factor": 5,
21
+ "image": path.absolute().as_uri(),
22
+ },
23
+ ]
24
+ if title:
25
+ input.append(
26
+ {
27
+ "factor": 1,
28
+ "text": title,
29
+ },
30
+ )
31
+ resp = MultiModalEmbedding.call(
32
+ model=MultiModalEmbedding.Models.multimodal_embedding_one_peace_v1,
33
+ input=input,
34
+ auto_truncation=True,
35
+ )
36
+ if resp.status_code != HTTPStatus.OK:
37
+ raise RuntimeError("Dashscope API Error")
38
+ return resp.output["embedding"]
39
+
40
+
41
+ def img2vec(img: bytes, title: str = None) -> list:
42
+ """1536 D"""
43
+ return None
44
+
45
+ if plugin_config.simpic_model.lower() == "one-peach":
46
+ path = pathlib.Path(tempfile.gettempdir) / "img2vec"
47
+ if not path.exists():
48
+ path.mkdir()
49
+ path /= hashlib.sha256(img).hexdigest() + ".png"
50
+ with open(path, "wb+") as f:
51
+ f.write(img)
52
+
53
+ return file2vec(path=path, title=title)
54
+
55
+ if not _emo_same:
56
+ return None
57
+ return _emo_same.quantify_tolist(img)
58
+
59
+
60
+ def word2vec(word: str) -> list[float]:
61
+ resp = TextEmbedding.call(
62
+ model=TextEmbedding.Models.text_embedding_v2, input=word, text_type="query"
63
+ )
64
+ if resp.status_code != HTTPStatus.OK:
65
+ raise RuntimeError("Dashscope API Error")
66
+ return resp.output["embeddings"][0]["embedding"]
@@ -0,0 +1,25 @@
1
+ from pydantic import BaseModel
2
+ from enum import Enum
3
+
4
+
5
+ class Config(BaseModel):
6
+ """Plugin Config Here"""
7
+
8
+ savepic_admin: list[str] = []
9
+ savepic_dir: str = "savepic"
10
+ savepic_sqlurl: str
11
+
12
+ pinecone_apikey: str
13
+ pinecone_index: str = "savepic"
14
+ pinecone_environment: str
15
+
16
+ dashscope_api: str
17
+ simpic_enable: bool = False
18
+ simpic_model: str = "one-peach"
19
+
20
+ p_model_path: str = "networks/ckpt_epoch_100_rein.pth"
21
+ q_model_path: str = "networks/ckpt_epoch_100_rein.pth.qt.pth"
22
+ # 基于模型的相似度判断
23
+
24
+ embedding_sqlurl: str
25
+ black_group: list[str]
@@ -0,0 +1,22 @@
1
+ from nonebot import on_command
2
+ from nonebot.params import CommandArg
3
+ from nonebot.adapters.onebot.v11.event import GroupMessageEvent as V11GME
4
+ from sqlalchemy.exc import DBAPIError
5
+ from .rule import BLACK_GROUP
6
+ from .pic_sql import countpic
7
+
8
+ cpic = on_command("countpic", priority=5, permission=BLACK_GROUP)
9
+
10
+
11
+ @cpic.handle()
12
+ async def _(bot, event, args=CommandArg()):
13
+ reg = args.extract_plain_text().strip()
14
+ group_id = (
15
+ "globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
16
+ )
17
+ try:
18
+ await bot.send(event, f"共查找到 {await countpic(reg, group_id)} 张图片。")
19
+ except DBAPIError as ex:
20
+ await cpic.finish(f"出错了喵~\n\n{ex.orig}")
21
+ except Exception as ex:
22
+ await cpic.finish(f"出错了喵~\n\n{ex}")
@@ -0,0 +1,24 @@
1
+ class SameNameException(Exception):
2
+ def __init__(self, name):
3
+ self.name = name
4
+
5
+ def __str__(self):
6
+ return "Name {} already exists".format(self.name)
7
+
8
+
9
+ class SimilarPictureException(Exception):
10
+ def __init__(self, name, similarity: float, url: str):
11
+ self.name = name
12
+ self.similarity = max(min(1, similarity), 0)
13
+ self.url = url
14
+
15
+ def __str__(self):
16
+ return "Picture {} is similar to {}%".format(self.name, self.similarity * 100)
17
+
18
+
19
+ class NoPictureException(Exception):
20
+ def __init__(self, name):
21
+ self.name = name
22
+
23
+ def __str__(self):
24
+ return "Picture {} not found".format(self.name)