nonebot-plugin-savepic 0.2.0__tar.gz → 0.2.6__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.
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/PKG-INFO +30 -3
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/README.md +26 -2
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/__init__.py +340 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/ai_utils.py +66 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/config.py +35 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/countpic.py +22 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/error.py +24 -0
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/nonebot_plugin_savepic/ext_listener.py +5 -13
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/model.py +108 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/mvpic.py +145 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/networks/__init__.py +0 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/networks/resnet_big.py +242 -0
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/pic_sql.py +397 -0
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/nonebot_plugin_savepic/picture.py +40 -11
- nonebot_plugin_savepic-0.2.6/nonebot_plugin_savepic/randpic.py +47 -0
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/nonebot_plugin_savepic/rule.py +23 -4
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/pyproject.toml +3 -1
- nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/__init__.py +0 -190
- nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/config.py +0 -9
- nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/migrations/1a654e02179b_.py +0 -34
- nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/model.py +0 -26
- nonebot_plugin_savepic-0.2.0/nonebot_plugin_savepic/pic_sql.py +0 -219
- {nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nonebot-plugin-savepic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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,340 @@
|
|
|
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.event import GroupMessageEvent
|
|
9
|
+
from nonebot.adapters.onebot.v11.event import MessageEvent
|
|
10
|
+
from nonebot.matcher import Matcher
|
|
11
|
+
from nonebot.plugin import PluginMetadata
|
|
12
|
+
from nonebot.dependencies import Dependent
|
|
13
|
+
from sqlalchemy.exc import DBAPIError
|
|
14
|
+
from arclet.alconna import Alconna, Option, Args, CommandMeta, append
|
|
15
|
+
import os
|
|
16
|
+
import random
|
|
17
|
+
from typing import (
|
|
18
|
+
Any,
|
|
19
|
+
Union,
|
|
20
|
+
Callable,
|
|
21
|
+
Iterable,
|
|
22
|
+
Optional,
|
|
23
|
+
)
|
|
24
|
+
from nonebot.typing import (
|
|
25
|
+
T_State,
|
|
26
|
+
T_Handler,
|
|
27
|
+
)
|
|
28
|
+
from nonebot.consts import ARG_KEY
|
|
29
|
+
from nonebot.internal.params import Depends
|
|
30
|
+
from nonebot.internal.adapter import (
|
|
31
|
+
Bot,
|
|
32
|
+
Event,
|
|
33
|
+
Message,
|
|
34
|
+
MessageSegment,
|
|
35
|
+
MessageTemplate,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
require("nonebot_plugin_datastore")
|
|
40
|
+
|
|
41
|
+
from .error import (
|
|
42
|
+
SimilarPictureException,
|
|
43
|
+
SameNameException,
|
|
44
|
+
) # noqa: E402, E501
|
|
45
|
+
from .config import Config
|
|
46
|
+
from .config import WORDS
|
|
47
|
+
from .pic_sql import ( # noqa: E402
|
|
48
|
+
savepic,
|
|
49
|
+
rename,
|
|
50
|
+
delete,
|
|
51
|
+
regexp_pic,
|
|
52
|
+
listpic,
|
|
53
|
+
)
|
|
54
|
+
from .rule import PIC_AMDIN
|
|
55
|
+
from .rule import BLACK_GROUP
|
|
56
|
+
from .rule import GROUP_ADMIN
|
|
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 url_to_image
|
|
61
|
+
from .countpic import cpic
|
|
62
|
+
from .mvpic import INVALID_FILENAME_CHARACTERS
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__plugin_meta__ = PluginMetadata(
|
|
66
|
+
name="Savepic",
|
|
67
|
+
description="表情包保存",
|
|
68
|
+
usage="""用法:
|
|
69
|
+
/savepic <文件名> <图片>
|
|
70
|
+
/savepic -g <文件名> <全局图片>
|
|
71
|
+
/savepic -d <文件名> 删除图图片
|
|
72
|
+
/randpic <关键词> 随机图片
|
|
73
|
+
<文件名> 发送图片""",
|
|
74
|
+
config=Config,
|
|
75
|
+
homepage="https://github.com/Yan-Zero/nonebot-plugin-savepic",
|
|
76
|
+
type="application",
|
|
77
|
+
supported_adapters=["~onebot.v11"],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
p_config = get_plugin_config(Config)
|
|
81
|
+
repic = on_command("repic", priority=5, permission=BLACK_GROUP)
|
|
82
|
+
spic = on_command("savepic", priority=5, permission=BLACK_GROUP)
|
|
83
|
+
a_spic = Alconna(
|
|
84
|
+
"/savepic",
|
|
85
|
+
Option("-d", help_text="删除图片"),
|
|
86
|
+
Option("-g", help_text="全局"),
|
|
87
|
+
Option("-ac", help_text="允许相似碰撞"),
|
|
88
|
+
Args.filename[str],
|
|
89
|
+
meta=CommandMeta(description="保存图片,默认保存到本群"),
|
|
90
|
+
)
|
|
91
|
+
s_simpic = on_command("simpic", priority=5, permission=BLACK_GROUP)
|
|
92
|
+
s_listpic = on_command("listpic", priority=5, permission=BLACK_GROUP)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def got_random_prompt(
|
|
96
|
+
cls: Matcher,
|
|
97
|
+
key: str,
|
|
98
|
+
prompt: Optional[list[Union[str, Message, MessageSegment, MessageTemplate]]] = None,
|
|
99
|
+
parameterless: Optional[Iterable[Any]] = None,
|
|
100
|
+
) -> Callable[[T_Handler], T_Handler]:
|
|
101
|
+
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
|
102
|
+
|
|
103
|
+
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
|
|
104
|
+
如果 `key` 已存在则直接继续运行
|
|
105
|
+
|
|
106
|
+
参数:
|
|
107
|
+
key: 参数名
|
|
108
|
+
prompt: 在参数不存在时向用户发送的消息
|
|
109
|
+
parameterless: 非参数类型依赖列表
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
async def _key_getter(event: Event, matcher: "Matcher"):
|
|
113
|
+
matcher.set_target(ARG_KEY.format(key=key))
|
|
114
|
+
if matcher.get_target() == ARG_KEY.format(key=key):
|
|
115
|
+
matcher.set_arg(key, event.get_message())
|
|
116
|
+
return
|
|
117
|
+
if matcher.get_arg(key, ...) is not ...:
|
|
118
|
+
return
|
|
119
|
+
await matcher.reject("" if not prompt else random.choice(prompt))
|
|
120
|
+
|
|
121
|
+
_parameterless = (Depends(_key_getter), *(parameterless or ()))
|
|
122
|
+
|
|
123
|
+
def _decorator(func: T_Handler) -> T_Handler:
|
|
124
|
+
if cls.handlers and cls.handlers[-1].call is func:
|
|
125
|
+
func_handler = cls.handlers[-1]
|
|
126
|
+
new_handler = Dependent(
|
|
127
|
+
call=func_handler.call,
|
|
128
|
+
params=func_handler.params,
|
|
129
|
+
parameterless=Dependent.parse_parameterless(
|
|
130
|
+
tuple(_parameterless), cls.HANDLER_PARAM_TYPES
|
|
131
|
+
)
|
|
132
|
+
+ func_handler.parameterless,
|
|
133
|
+
)
|
|
134
|
+
cls.handlers[-1] = new_handler
|
|
135
|
+
else:
|
|
136
|
+
cls.append_handler(func, parameterless=_parameterless)
|
|
137
|
+
|
|
138
|
+
return func
|
|
139
|
+
|
|
140
|
+
return _decorator
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@repic.handle()
|
|
144
|
+
async def _(bot: Bot, event, args: V11Msg = CommandArg()):
|
|
145
|
+
reg = args.extract_plain_text().strip()
|
|
146
|
+
group_id = (
|
|
147
|
+
"globe"
|
|
148
|
+
if not isinstance(event, GroupMessageEvent)
|
|
149
|
+
else f"qq_group:{event.group_id}"
|
|
150
|
+
)
|
|
151
|
+
try:
|
|
152
|
+
pic = await regexp_pic(reg, group_id)
|
|
153
|
+
if pic:
|
|
154
|
+
await bot.send(event, V11Msg([pic.name, url_to_image(pic.url)]))
|
|
155
|
+
except DBAPIError as ex:
|
|
156
|
+
await repic.finish(
|
|
157
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex.orig}'
|
|
158
|
+
)
|
|
159
|
+
except Exception as ex:
|
|
160
|
+
await repic.finish(
|
|
161
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}'
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@s_listpic.handle()
|
|
166
|
+
async def _(bot: Bot, event, args: V11Msg = CommandArg()):
|
|
167
|
+
reg = args.extract_plain_text().strip().rsplit("\\page", maxsplit=1)
|
|
168
|
+
try:
|
|
169
|
+
if len(reg) > 1:
|
|
170
|
+
reg, pages = reg
|
|
171
|
+
else:
|
|
172
|
+
reg, pages = reg[0], 0
|
|
173
|
+
pages = int(pages)
|
|
174
|
+
except Exception as ex:
|
|
175
|
+
await repic.finish(
|
|
176
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}'
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
group_id = (
|
|
180
|
+
"globe"
|
|
181
|
+
if not isinstance(event, GroupMessageEvent)
|
|
182
|
+
else f"qq_group:{event.group_id}"
|
|
183
|
+
)
|
|
184
|
+
try:
|
|
185
|
+
pic = await listpic(reg, group_id, pages=pages)
|
|
186
|
+
if pic:
|
|
187
|
+
await bot.send(event, "\n".join(pic))
|
|
188
|
+
except DBAPIError as ex:
|
|
189
|
+
await repic.finish(
|
|
190
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex.orig}'
|
|
191
|
+
)
|
|
192
|
+
except Exception as ex:
|
|
193
|
+
await repic.finish(
|
|
194
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}'
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@spic.handle()
|
|
199
|
+
async def _(
|
|
200
|
+
bot: Bot,
|
|
201
|
+
matcher: Matcher,
|
|
202
|
+
event: GroupMessageEvent,
|
|
203
|
+
state: T_State,
|
|
204
|
+
):
|
|
205
|
+
command = a_spic.parse(event.message.extract_plain_text())
|
|
206
|
+
if not command.matched:
|
|
207
|
+
await spic.finish(str(command.error_info) + "\n\n" + a_spic.get_help())
|
|
208
|
+
|
|
209
|
+
state["savepiv_group"] = (
|
|
210
|
+
"globe"
|
|
211
|
+
if command.g and await PIC_AMDIN(bot, event)
|
|
212
|
+
else f"qq_group:{event.group_id}"
|
|
213
|
+
)
|
|
214
|
+
state["savepiv_warning"] = (
|
|
215
|
+
""
|
|
216
|
+
if not command.g or await PIC_AMDIN(bot, event)
|
|
217
|
+
else "\n\n你的 -g 选项没有用哟"
|
|
218
|
+
)
|
|
219
|
+
filename = command.filename
|
|
220
|
+
for c in INVALID_FILENAME_CHARACTERS:
|
|
221
|
+
filename = filename.replace(c, "-")
|
|
222
|
+
if not filename.endswith((".jpg", ".png", ".gif")):
|
|
223
|
+
filename += ".jpg"
|
|
224
|
+
state["savepiv_filename"] = filename
|
|
225
|
+
state["savepiv_ac"] = command.ac is not None
|
|
226
|
+
|
|
227
|
+
if command.d:
|
|
228
|
+
if not (await PIC_AMDIN(bot, event) or await GROUP_ADMIN(bot, event)):
|
|
229
|
+
await spic.finish(
|
|
230
|
+
random.choice(WORDS.get("permission denied", ["没有权限"]))
|
|
231
|
+
)
|
|
232
|
+
try:
|
|
233
|
+
await delete(filename, state["savepiv_group"])
|
|
234
|
+
except Exception as ex:
|
|
235
|
+
await spic.finish(str(ex))
|
|
236
|
+
await spic.finish("图片已删除" + state["savepiv_warning"])
|
|
237
|
+
|
|
238
|
+
picture = event.message.get("image")
|
|
239
|
+
if not picture and event.reply:
|
|
240
|
+
picture = event.reply.message.get("image")
|
|
241
|
+
if picture:
|
|
242
|
+
matcher.set_arg("picture", picture)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@got_random_prompt(spic, "picture", WORDS.get("wait for image", ["图呢"]))
|
|
246
|
+
async def _(state: T_State, picture: V11Msg = Arg()):
|
|
247
|
+
picture = picture.get("image")
|
|
248
|
+
if not picture:
|
|
249
|
+
await spic.finish(random.choice(WORDS.get("not image", ["6,这也不是图啊"])))
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
dir = await write_pic(picture[0].data["url"], p_config.savepic_dir)
|
|
253
|
+
except Exception as ex:
|
|
254
|
+
await spic.finish("存图失败。" + "\n" + str(ex))
|
|
255
|
+
try:
|
|
256
|
+
await savepic(
|
|
257
|
+
state["savepiv_filename"],
|
|
258
|
+
dir,
|
|
259
|
+
img2vec(await load_pic(dir), state["savepiv_filename"]),
|
|
260
|
+
state["savepiv_group"],
|
|
261
|
+
state["savepiv_ac"],
|
|
262
|
+
)
|
|
263
|
+
except SameNameException:
|
|
264
|
+
os.remove(dir)
|
|
265
|
+
await spic.finish(
|
|
266
|
+
random.choice(WORDS.get("name has been taken", ["文件名重复"]))
|
|
267
|
+
)
|
|
268
|
+
except SimilarPictureException as ex:
|
|
269
|
+
os.remove(dir)
|
|
270
|
+
try:
|
|
271
|
+
image = await load_pic(ex.url)
|
|
272
|
+
except Exception as exc:
|
|
273
|
+
await spic.finish(
|
|
274
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{exc}'
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
await spic.finish(
|
|
278
|
+
V11Msg(
|
|
279
|
+
[
|
|
280
|
+
random.choice(WORDS.get("similar picture", ["存在相似图片"])),
|
|
281
|
+
"\n\n" + ex.name,
|
|
282
|
+
f"\n(相似度:{'%.4g' % (min(ex.similarity * 100, 100.0))}%)\n",
|
|
283
|
+
V11Seg.image(file=image),
|
|
284
|
+
]
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
except Exception as ex:
|
|
288
|
+
os.remove(dir)
|
|
289
|
+
await spic.finish(f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}')
|
|
290
|
+
await spic.send(
|
|
291
|
+
random.choice(WORDS.get("save succeed", ["保存成功"]))
|
|
292
|
+
+ state["savepiv_warning"]
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@s_simpic.handle()
|
|
297
|
+
async def _(bot: Bot, event: MessageEvent, args: V11Msg = CommandArg()):
|
|
298
|
+
if True:
|
|
299
|
+
await s_simpic.finish("simpic 并未开启喵。")
|
|
300
|
+
|
|
301
|
+
picture = event.reply.message.get("image") if event.reply else None
|
|
302
|
+
if not picture:
|
|
303
|
+
await s_simpic.finish(
|
|
304
|
+
random.choice(
|
|
305
|
+
WORDS.get("missing image reference", ["没找到引用消息中的图片"])
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
vec = img2vec(await load_pic(picture[0].data["url"]))
|
|
309
|
+
ignore_diagonal = True
|
|
310
|
+
ignore_min = False
|
|
311
|
+
if args.extract_plain_text().strip() == "-n":
|
|
312
|
+
ignore_diagonal = False
|
|
313
|
+
elif args.extract_plain_text().strip() == "-i":
|
|
314
|
+
ignore_min = True
|
|
315
|
+
group_id = (
|
|
316
|
+
"globe"
|
|
317
|
+
if not isinstance(event, GroupMessageEvent)
|
|
318
|
+
else f"qq_group:{event.group_id}"
|
|
319
|
+
)
|
|
320
|
+
try:
|
|
321
|
+
sim, pic = await get_most_similar_pic(
|
|
322
|
+
vec, group_id, ignore_diagonal, ignore_min
|
|
323
|
+
)
|
|
324
|
+
if pic:
|
|
325
|
+
await bot.send(
|
|
326
|
+
event,
|
|
327
|
+
V11Msg(
|
|
328
|
+
[
|
|
329
|
+
V11Seg.reply(event.reply.message_id),
|
|
330
|
+
f"{pic.name}\n(相似性:{'%.4g' % (min(sim * 100, 100.0))}%)\n",
|
|
331
|
+
url_to_image(pic.url),
|
|
332
|
+
]
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
await s_simpic.send(random.choice(WORDS.get("not found", ["Nope."])))
|
|
337
|
+
except Exception as ex:
|
|
338
|
+
await s_simpic.finish(
|
|
339
|
+
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}'
|
|
340
|
+
)
|
|
@@ -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,35 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
WORDS = {}
|
|
7
|
+
for root, _, files in os.walk("words"):
|
|
8
|
+
for file in files:
|
|
9
|
+
if not file.endswith(".json"):
|
|
10
|
+
continue
|
|
11
|
+
with open(os.path.join(root, file), "r", encoding="utf-8") as f:
|
|
12
|
+
WORDS.update(json.load(f))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Config(BaseModel):
|
|
16
|
+
"""Plugin Config Here"""
|
|
17
|
+
|
|
18
|
+
savepic_admin: list[str] = []
|
|
19
|
+
savepic_dir: str = "savepic"
|
|
20
|
+
savepic_sqlurl: str
|
|
21
|
+
|
|
22
|
+
pinecone_apikey: str
|
|
23
|
+
pinecone_index: str = "savepic"
|
|
24
|
+
pinecone_environment: str
|
|
25
|
+
|
|
26
|
+
dashscope_api: str
|
|
27
|
+
simpic_enable: bool = False
|
|
28
|
+
simpic_model: str = "one-peach"
|
|
29
|
+
|
|
30
|
+
p_model_path: str = "networks/ckpt_epoch_100_rein.pth"
|
|
31
|
+
q_model_path: str = "networks/ckpt_epoch_100_rein.pth.qt.pth"
|
|
32
|
+
# 基于模型的相似度判断
|
|
33
|
+
|
|
34
|
+
embedding_sqlurl: str
|
|
35
|
+
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)
|
{nonebot_plugin_savepic-0.2.0 → nonebot_plugin_savepic-0.2.6}/nonebot_plugin_savepic/ext_listener.py
RENAMED
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
from nonebot import
|
|
1
|
+
from nonebot.plugin import on_endswith
|
|
2
2
|
from nonebot.adapters.onebot.v11.event import MessageEvent
|
|
3
3
|
from nonebot.internal.adapter.bot import Bot
|
|
4
4
|
from nonebot.adapters.onebot.v11.message import MessageSegment as V11Seg
|
|
5
5
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
|
6
|
+
from .pic_sql import select_pic
|
|
7
|
+
from .picture import load_pic
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async def endswith_pic(event: MessageEvent):
|
|
11
|
-
text = event.message.extract_plain_text().strip()
|
|
12
|
-
if " " in text:
|
|
13
|
-
return False
|
|
14
|
-
return text.endswith((".jpg", ".png", ".gif"))
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
pic_listen = on_message(rule=endswith_pic)
|
|
9
|
+
pic_listen = on_endswith((".jpg", ".png", ".gif"), priority=50, block=False)
|
|
18
10
|
|
|
19
11
|
|
|
20
12
|
@pic_listen.handle()
|
|
21
13
|
async def _(bot: Bot, event: MessageEvent):
|
|
22
|
-
name = event.
|
|
14
|
+
name = event.get_plaintext().strip()
|
|
23
15
|
group_id = (
|
|
24
16
|
"globe"
|
|
25
17
|
if not isinstance(event, GroupMessageEvent)
|