nonebot-plugin-savepic 0.1.3__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 (23) hide show
  1. nonebot_plugin_savepic-0.2.5/PKG-INFO +135 -0
  2. nonebot_plugin_savepic-0.1.3/PKG-INFO → nonebot_plugin_savepic-0.2.5/README.md +25 -21
  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.5/nonebot_plugin_savepic/ext_listener.py +26 -0
  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.5/nonebot_plugin_savepic/picture.py +77 -0
  14. nonebot_plugin_savepic-0.2.5/nonebot_plugin_savepic/randpic.py +47 -0
  15. {nonebot_plugin_savepic-0.1.3 → nonebot_plugin_savepic-0.2.5}/nonebot_plugin_savepic/rule.py +22 -5
  16. {nonebot_plugin_savepic-0.1.3 → nonebot_plugin_savepic-0.2.5}/pyproject.toml +8 -2
  17. nonebot_plugin_savepic-0.1.3/README.md +0 -84
  18. nonebot_plugin_savepic-0.1.3/nonebot_plugin_savepic/__init__.py +0 -157
  19. nonebot_plugin_savepic-0.1.3/nonebot_plugin_savepic/config.py +0 -7
  20. nonebot_plugin_savepic-0.1.3/nonebot_plugin_savepic/migrations/1a654e02179b_.py +0 -34
  21. nonebot_plugin_savepic-0.1.3/nonebot_plugin_savepic/model.py +0 -20
  22. nonebot_plugin_savepic-0.1.3/nonebot_plugin_savepic/pic_sql.py +0 -150
  23. {nonebot_plugin_savepic-0.1.3 → nonebot_plugin_savepic-0.2.5}/LICENSE +0 -0
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.1
2
+ Name: nonebot-plugin-savepic
3
+ Version: 0.2.5
4
+ Summary: 保存表情包(语录)与随机出图
5
+ Home-page: https://github.com/Yan-Zero/nonebot-plugin-savepic
6
+ License: MIT
7
+ Author: Yan
8
+ Author-email: 1964649083@qq.com
9
+ Requires-Python: >=3.10,<3.13
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Dist: arclet-alconna (>=1.7.14,<2.0.0)
16
+ Requires-Dist: asyncpg (>=0.28.0,<0.29.0)
17
+ Requires-Dist: dashscope (>=1.17.0,<2.0.0)
18
+ Requires-Dist: nonebot-adapter-onebot (>=2.2.3,<3.0.0)
19
+ Requires-Dist: nonebot-plugin-datastore (>=1.1.0,<2.0.0)
20
+ Requires-Dist: nonebot2 (>=2.0.1,<3.0.0)
21
+ Requires-Dist: numpy (>=1.25.2,<2.0.0)
22
+ Requires-Dist: pillow (>=10.0.0,<11.0.0)
23
+ Requires-Dist: pinecone-client (>=3.2.2,<4.0.0)
24
+ Requires-Dist: scipy (>=1.11.1,<2.0.0)
25
+ Description-Content-Type: text/markdown
26
+
27
+ <div align="center">
28
+ <a href="https://v2.nonebot.dev/store"><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/nbp_logo.png" width="180" height="180" alt="NoneBotPluginLogo"></a>
29
+ <br>
30
+ <p><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/NoneBotPlugin.svg" width="240" alt="NoneBotPluginText"></p>
31
+ </div>
32
+
33
+ <div align="center">
34
+
35
+ # nonebot-plugin-savepic
36
+
37
+ _✨ 一个存取图片的插件 ✨_
38
+
39
+ ## 📖 介绍
40
+
41
+ 重写自 Fran 的 Savepic
42
+
43
+ ### savepic
44
+
45
+ 保存表情包
46
+
47
+ ### randpic
48
+
49
+ 抽取表情包
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
+
67
+ ### 直接发送文件名
68
+
69
+ 发送文件名即可发送表情包
70
+
71
+
72
+ ## 💿 安装
73
+
74
+ <details open>
75
+ <summary>使用 nb-cli 安装</summary>
76
+ 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
77
+
78
+ nb plugin install nonebot-plugin-savepic
79
+
80
+ </details>
81
+
82
+ <details>
83
+ <summary>使用包管理器安装</summary>
84
+ 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
85
+
86
+ <details>
87
+ <summary>pip</summary>
88
+
89
+ pip install nonebot-plugin-savepic
90
+ </details>
91
+ <details>
92
+ <summary>pdm</summary>
93
+
94
+ pdm add nonebot-plugin-savepic
95
+ </details>
96
+ <details>
97
+ <summary>poetry</summary>
98
+
99
+ poetry add nonebot-plugin-savepic
100
+ </details>
101
+ <details>
102
+ <summary>conda</summary>
103
+
104
+ conda install nonebot-plugin-savepic
105
+ </details>
106
+
107
+ 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入
108
+
109
+ plugins = ["nonebot_plugin_savepic"]
110
+
111
+ </details>
112
+
113
+ ## ⚙️ 配置
114
+
115
+ 在 nonebot2 项目的`.env`文件中添加下表中的必填配置
116
+
117
+ | 配置项 | 必填 | 默认值 | 说明 |
118
+ |:-----:|:----:|:----:|:----:|
119
+ | savepic_admin | 否 | 无 | 权限用户 |
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。 用于开启基于语义的相似度检索功能 |
126
+
127
+ ## 🎉 使用
128
+ ### 指令表
129
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
130
+ |:-----:|:----:|:----:|:----:|:----:|
131
+ | savepic | 群员 | 否 | 群聊 | 保存图片 |
132
+ | randpic | 群员 | 否 | 全部 | 随机图片 |
133
+ | mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
134
+
135
+
@@ -1,22 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: nonebot-plugin-savepic
3
- Version: 0.1.3
4
- Summary: 保存表情包(语录)与随机出图
5
- Home-page: https://github.com/Yan-Zero/nonebot-plugin-savepic
6
- License: MIT
7
- Author: Yan
8
- Author-email: 1964649083@qq.com
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Requires-Dist: asyncpg (>=0.28.0,<0.29.0)
15
- Requires-Dist: nonebot-adapter-onebot (>=2.2.3,<3.0.0)
16
- Requires-Dist: nonebot-plugin-datastore (>=1.1.0,<2.0.0)
17
- Requires-Dist: nonebot2 (>=2.0.1,<3.0.0)
18
- Description-Content-Type: text/markdown
19
-
20
1
  <div align="center">
21
2
  <a href="https://v2.nonebot.dev/store"><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/nbp_logo.png" width="180" height="180" alt="NoneBotPluginLogo"></a>
22
3
  <br>
@@ -41,10 +22,27 @@ _✨ 一个存取图片的插件 ✨_
41
22
 
42
23
  抽取表情包
43
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
+
44
41
  ### 直接发送文件名
45
42
 
46
43
  发送文件名即可发送表情包
47
44
 
45
+
48
46
  ## 💿 安装
49
47
 
50
48
  <details open>
@@ -94,11 +92,17 @@ _✨ 一个存取图片的插件 ✨_
94
92
  |:-----:|:----:|:----:|:----:|
95
93
  | savepic_admin | 否 | 无 | 权限用户 |
96
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。 用于开启基于语义的相似度检索功能 |
97
100
 
98
101
  ## 🎉 使用
99
102
  ### 指令表
100
103
  | 指令 | 权限 | 需要@ | 范围 | 说明 |
101
104
  |:-----:|:----:|:----:|:----:|:----:|
102
- | savepic | 群员 | 是 | 群聊 | 保存图片 |
103
- | randpic | 群员 | 是 | 全部 | 随机图片 |
105
+ | savepic | 群员 | 否 | 群聊 | 保存图片 |
106
+ | randpic | 群员 | 否 | 全部 | 随机图片 |
107
+ | mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
104
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"]