nonebot-plugin-savepic 0.3.0__tar.gz → 0.4.1__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.3.0 → nonebot_plugin_savepic-0.4.1}/PKG-INFO +17 -11
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/README.md +4 -1
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/__init__.py +70 -117
- nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/command.py +179 -0
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/config.py +14 -20
- nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/fileio.py +92 -0
- nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/model.py +207 -0
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/pic_sql.py → nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/sql.py +121 -114
- nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/utils.py +1170 -0
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/listpic.py +10 -19
- nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/model.py +37 -0
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/mvpic.py +11 -22
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/rule.py +1 -24
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/pyproject.toml +13 -8
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/ai_utils.py +0 -66
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/countpic.py +0 -22
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/ext_listener.py +0 -26
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/model.py +0 -108
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/networks/resnet_big.py +0 -242
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/picture.py +0 -77
- nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/randpic.py +0 -47
- {nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/LICENSE +0 -0
- {nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic/networks → nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core}/__init__.py +0 -0
- {nonebot_plugin_savepic-0.3.0/nonebot_plugin_savepic → nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core}/error.py +0 -0
|
@@ -1,27 +1,30 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: nonebot-plugin-savepic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: 保存表情包(语录)与随机出图
|
|
5
|
-
Home-page: https://github.com/Yan-Zero/nonebot-plugin-savepic
|
|
6
5
|
License: MIT
|
|
7
6
|
Author: Yan
|
|
8
7
|
Author-email: 1964649083@qq.com
|
|
9
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
10
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
10
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
14
|
Requires-Dist: arclet-alconna (>=1.7.14,<2.0.0)
|
|
16
15
|
Requires-Dist: asyncpg (>=0.28.0,<0.29.0)
|
|
17
16
|
Requires-Dist: dashscope (>=1.17.0,<2.0.0)
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist: nonebot-
|
|
20
|
-
Requires-Dist: nonebot2 (>=2.0
|
|
21
|
-
Requires-Dist: numpy (>=1.
|
|
17
|
+
Requires-Dist: httpx (>=0.27.2,<0.28.0)
|
|
18
|
+
Requires-Dist: nonebot-adapter-onebot (>=2.4.6,<3.0.0)
|
|
19
|
+
Requires-Dist: nonebot2 (>=2.4.0,<3.0.0)
|
|
20
|
+
Requires-Dist: numpy (>=2.1.3,<3.0.0)
|
|
21
|
+
Requires-Dist: openai (>=1.54.4,<2.0.0)
|
|
22
22
|
Requires-Dist: pillow (>=10.0.0,<11.0.0)
|
|
23
23
|
Requires-Dist: pinecone-client (>=3.2.2,<4.0.0)
|
|
24
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
25
|
+
Requires-Dist: scipy (>=1.14.1,<2.0.0)
|
|
26
|
+
Requires-Dist: sqlalchemy (>=2.0.36,<3.0.0)
|
|
27
|
+
Project-URL: Homepage, https://github.com/Yan-Zero/nonebot-plugin-savepic
|
|
25
28
|
Description-Content-Type: text/markdown
|
|
26
29
|
|
|
27
30
|
<div align="center">
|
|
@@ -36,6 +39,8 @@ Description-Content-Type: text/markdown
|
|
|
36
39
|
|
|
37
40
|
_✨ 一个存取图片的插件 ✨_
|
|
38
41
|
|
|
42
|
+
</div>
|
|
43
|
+
|
|
39
44
|
## 📖 介绍
|
|
40
45
|
|
|
41
46
|
重写自 Fran 的 Savepic
|
|
@@ -125,11 +130,12 @@ _✨ 一个存取图片的插件 ✨_
|
|
|
125
130
|
| dashscope_api | 否 | 无 | dashscope 的 APIKey。 用于开启基于语义的相似度检索功能 |
|
|
126
131
|
|
|
127
132
|
## 🎉 使用
|
|
133
|
+
|
|
128
134
|
### 指令表
|
|
135
|
+
|
|
129
136
|
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
|
130
137
|
|:-----:|:----:|:----:|:----:|:----:|
|
|
131
138
|
| savepic | 群员 | 否 | 群聊 | 保存图片 |
|
|
132
139
|
| randpic | 群员 | 否 | 全部 | 随机图片 |
|
|
133
140
|
| mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
|
|
134
141
|
|
|
135
|
-
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
_✨ 一个存取图片的插件 ✨_
|
|
12
12
|
|
|
13
|
+
</div>
|
|
14
|
+
|
|
13
15
|
## 📖 介绍
|
|
14
16
|
|
|
15
17
|
重写自 Fran 的 Savepic
|
|
@@ -99,10 +101,11 @@ _✨ 一个存取图片的插件 ✨_
|
|
|
99
101
|
| dashscope_api | 否 | 无 | dashscope 的 APIKey。 用于开启基于语义的相似度检索功能 |
|
|
100
102
|
|
|
101
103
|
## 🎉 使用
|
|
104
|
+
|
|
102
105
|
### 指令表
|
|
106
|
+
|
|
103
107
|
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
|
104
108
|
|:-----:|:----:|:----:|:----:|:----:|
|
|
105
109
|
| savepic | 群员 | 否 | 群聊 | 保存图片 |
|
|
106
110
|
| randpic | 群员 | 否 | 全部 | 随机图片 |
|
|
107
111
|
| mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
|
|
108
|
-
|
{nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/__init__.py
RENAMED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from nonebot import require
|
|
3
1
|
from nonebot import get_plugin_config
|
|
4
2
|
from nonebot import on_command
|
|
5
3
|
from nonebot.params import CommandArg, Arg
|
|
4
|
+
from nonebot.adapters.onebot import utils
|
|
5
|
+
from nonebot.adapters.onebot.v11 import GROUP_ADMIN
|
|
6
6
|
from nonebot.adapters.onebot.v11.message import Message as V11Msg
|
|
7
7
|
from nonebot.adapters.onebot.v11.message import MessageSegment as V11Seg
|
|
8
8
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
|
|
9
|
-
from nonebot.adapters.onebot.v11.event import MessageEvent
|
|
10
9
|
from nonebot.matcher import Matcher
|
|
11
10
|
from nonebot.plugin import PluginMetadata
|
|
12
11
|
from nonebot.dependencies import Dependent
|
|
13
12
|
from sqlalchemy.exc import DBAPIError
|
|
14
13
|
from arclet.alconna import Alconna, Option, Args, CommandMeta
|
|
15
|
-
import os
|
|
16
14
|
import random
|
|
17
15
|
from typing import (
|
|
18
16
|
Any,
|
|
@@ -35,30 +33,20 @@ from nonebot.internal.adapter import (
|
|
|
35
33
|
MessageTemplate,
|
|
36
34
|
)
|
|
37
35
|
|
|
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
|
-
delete,
|
|
50
|
-
regexp_pic,
|
|
51
|
-
)
|
|
52
36
|
from .rule import PIC_AMDIN
|
|
53
|
-
from .rule import BLACK_GROUP
|
|
54
|
-
from .rule import GROUP_ADMIN
|
|
55
|
-
from .picture import write_pic, load_pic
|
|
56
|
-
from .ai_utils import img2vec
|
|
57
|
-
from .randpic import url_to_image
|
|
58
37
|
from .mvpic import INVALID_FILENAME_CHARACTERS
|
|
59
|
-
from .
|
|
60
|
-
from .
|
|
61
|
-
from .
|
|
38
|
+
from .config import Config
|
|
39
|
+
from .config import plugin_config
|
|
40
|
+
from .listpic import UPLOADER
|
|
41
|
+
from .command import url_to_image
|
|
42
|
+
from .core.sql import savepic
|
|
43
|
+
from .core.sql import delete
|
|
44
|
+
from .core.sql import regexp_pic
|
|
45
|
+
from .core.utils import img2vec
|
|
46
|
+
from .core.utils import ocr as ocr_image
|
|
47
|
+
from .core.error import SameNameException
|
|
48
|
+
from .core.error import SimilarPictureException
|
|
49
|
+
from .core.fileio import write_pic, load_pic, del_pic
|
|
62
50
|
|
|
63
51
|
|
|
64
52
|
__plugin_meta__ = PluginMetadata(
|
|
@@ -76,18 +64,17 @@ __plugin_meta__ = PluginMetadata(
|
|
|
76
64
|
supported_adapters=["~onebot.v11"],
|
|
77
65
|
)
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
spic = on_command("savepic", priority=5, permission=BLACK_GROUP)
|
|
67
|
+
repic = on_command("repic", priority=5)
|
|
68
|
+
spic = on_command("savepic", priority=5)
|
|
82
69
|
a_spic = Alconna(
|
|
83
70
|
"/savepic",
|
|
84
71
|
Option("-d", help_text="删除图片"),
|
|
85
72
|
Option("-g", help_text="全局"),
|
|
86
73
|
Option("-ac", help_text="允许相似碰撞"),
|
|
74
|
+
Option("-nocr", help_text="跳过图片OCR"),
|
|
87
75
|
Args.filename[str],
|
|
88
76
|
meta=CommandMeta(description="保存图片,默认保存到本群"),
|
|
89
77
|
)
|
|
90
|
-
s_simpic = on_command("simpic", priority=5, permission=BLACK_GROUP)
|
|
91
78
|
|
|
92
79
|
|
|
93
80
|
def got_random_prompt(
|
|
@@ -96,17 +83,6 @@ def got_random_prompt(
|
|
|
96
83
|
prompt: Optional[list[Union[str, Message, MessageSegment, MessageTemplate]]] = None,
|
|
97
84
|
parameterless: Optional[Iterable[Any]] = None,
|
|
98
85
|
) -> Callable[[T_Handler], T_Handler]:
|
|
99
|
-
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
|
100
|
-
|
|
101
|
-
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,
|
|
102
|
-
如果 `key` 已存在则直接继续运行
|
|
103
|
-
|
|
104
|
-
参数:
|
|
105
|
-
key: 参数名
|
|
106
|
-
prompt: 在参数不存在时向用户发送的消息
|
|
107
|
-
parameterless: 非参数类型依赖列表
|
|
108
|
-
"""
|
|
109
|
-
|
|
110
86
|
async def _key_getter(event: Event, matcher: "Matcher"):
|
|
111
87
|
matcher.set_target(ARG_KEY.format(key=key))
|
|
112
88
|
if matcher.get_target() == ARG_KEY.format(key=key):
|
|
@@ -151,13 +127,9 @@ async def _(bot: Bot, event, args: V11Msg = CommandArg()):
|
|
|
151
127
|
if pic:
|
|
152
128
|
await bot.send(event, V11Msg([pic.name, url_to_image(pic.url)]))
|
|
153
129
|
except DBAPIError as ex:
|
|
154
|
-
await repic.finish(
|
|
155
|
-
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex.orig}'
|
|
156
|
-
)
|
|
130
|
+
await repic.finish(f"出错了。{ex.orig}")
|
|
157
131
|
except Exception as ex:
|
|
158
|
-
await repic.finish(
|
|
159
|
-
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}'
|
|
160
|
-
)
|
|
132
|
+
await repic.finish(f"出错了。{ex}")
|
|
161
133
|
|
|
162
134
|
|
|
163
135
|
@spic.handle()
|
|
@@ -190,15 +162,24 @@ async def _(
|
|
|
190
162
|
state["savepiv_ac"] = command.ac is not None
|
|
191
163
|
|
|
192
164
|
if command.d:
|
|
193
|
-
if not (
|
|
194
|
-
await
|
|
195
|
-
|
|
165
|
+
if not (
|
|
166
|
+
await PIC_AMDIN(bot, event)
|
|
167
|
+
or await GROUP_ADMIN(bot, event)
|
|
168
|
+
or UPLOADER.get(
|
|
169
|
+
state["savepiv_filename"] + ":" + state["savepiv_group"], None
|
|
196
170
|
)
|
|
171
|
+
== f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"
|
|
172
|
+
):
|
|
173
|
+
await spic.finish("没有权限")
|
|
197
174
|
try:
|
|
198
175
|
await delete(filename, state["savepiv_group"])
|
|
199
176
|
except Exception as ex:
|
|
200
177
|
await spic.finish(str(ex))
|
|
201
178
|
await spic.finish("图片已删除" + state["savepiv_warning"])
|
|
179
|
+
if command.nocr:
|
|
180
|
+
state["savepiv_nocr"] = False
|
|
181
|
+
else:
|
|
182
|
+
state["savepiv_nocr"] = True
|
|
202
183
|
|
|
203
184
|
picture = event.message.get("image")
|
|
204
185
|
if not picture and event.reply:
|
|
@@ -207,42 +188,61 @@ async def _(
|
|
|
207
188
|
matcher.set_arg("picture", picture)
|
|
208
189
|
|
|
209
190
|
|
|
210
|
-
@
|
|
211
|
-
async def _(state: T_State, picture: V11Msg = Arg()):
|
|
191
|
+
@spic.got("picture", ["图呢"])
|
|
192
|
+
async def _(bot: Bot, state: T_State, event, picture: V11Msg = Arg()):
|
|
212
193
|
picture = picture.get("image")
|
|
213
194
|
if not picture:
|
|
214
|
-
await spic.finish(
|
|
195
|
+
await spic.finish("6,这也不是图啊")
|
|
215
196
|
|
|
216
197
|
try:
|
|
217
|
-
dir = await write_pic(picture[0].data["url"],
|
|
198
|
+
dir = await write_pic(picture[0].data["url"], plugin_config.savepic_dir)
|
|
199
|
+
img = await load_pic(dir)
|
|
200
|
+
if state["savepiv_nocr"]:
|
|
201
|
+
try:
|
|
202
|
+
ocr = await ocr_image(img)
|
|
203
|
+
except Exception:
|
|
204
|
+
ocr = {}
|
|
205
|
+
|
|
206
|
+
if "text" in ocr:
|
|
207
|
+
ocr = ocr["text"]
|
|
208
|
+
else:
|
|
209
|
+
ocr = await bot.ocr_image(image=utils.f2s(img))
|
|
210
|
+
if ocr and "texts" in ocr:
|
|
211
|
+
r = ""
|
|
212
|
+
for d in ocr["texts"]:
|
|
213
|
+
r += d["text"]
|
|
214
|
+
ocr = r.strip()
|
|
215
|
+
else:
|
|
216
|
+
ocr = ""
|
|
217
|
+
if len(set(ocr)) <= 10:
|
|
218
|
+
ocr = ""
|
|
219
|
+
else:
|
|
220
|
+
ocr = ""
|
|
218
221
|
except Exception as ex:
|
|
222
|
+
await del_pic(dir)
|
|
219
223
|
await spic.finish("存图失败。" + "\n" + str(ex))
|
|
224
|
+
|
|
220
225
|
try:
|
|
221
226
|
await savepic(
|
|
222
227
|
state["savepiv_filename"],
|
|
223
228
|
dir,
|
|
224
|
-
|
|
229
|
+
await img2vec(img, ocr),
|
|
225
230
|
state["savepiv_group"],
|
|
226
231
|
state["savepiv_ac"],
|
|
227
232
|
)
|
|
228
233
|
except SameNameException:
|
|
229
|
-
|
|
230
|
-
await spic.finish(
|
|
231
|
-
random.choice(WORDS.get("name has been taken", ["文件名重复"]))
|
|
232
|
-
)
|
|
234
|
+
await del_pic(dir)
|
|
235
|
+
await spic.finish("文件名重复")
|
|
233
236
|
except SimilarPictureException as ex:
|
|
234
|
-
|
|
237
|
+
await del_pic(dir)
|
|
235
238
|
try:
|
|
236
239
|
image = await load_pic(ex.url)
|
|
237
240
|
except Exception as exc:
|
|
238
|
-
await spic.finish(
|
|
239
|
-
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{exc}'
|
|
240
|
-
)
|
|
241
|
-
|
|
241
|
+
await spic.finish(f"出错了。{exc}")
|
|
242
242
|
await spic.finish(
|
|
243
243
|
V11Msg(
|
|
244
244
|
[
|
|
245
|
-
|
|
245
|
+
"存在相似图片",
|
|
246
246
|
"\n\n" + ex.name,
|
|
247
247
|
f"\n(相似度:{'%.4g' % (min(ex.similarity * 100, 100.0))}%)\n",
|
|
248
248
|
V11Seg.image(file=image),
|
|
@@ -250,56 +250,9 @@ async def _(state: T_State, picture: V11Msg = Arg()):
|
|
|
250
250
|
)
|
|
251
251
|
)
|
|
252
252
|
except Exception as ex:
|
|
253
|
-
|
|
254
|
-
await spic.finish(f
|
|
255
|
-
await spic.send(
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
await del_pic(dir)
|
|
254
|
+
await spic.finish(f"出错了。{ex}")
|
|
255
|
+
await spic.send("保存成功" + state["savepiv_warning"])
|
|
256
|
+
UPLOADER[state["savepiv_filename"] + ":" + state["savepiv_group"]] = (
|
|
257
|
+
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"
|
|
258
258
|
)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
@s_simpic.handle()
|
|
262
|
-
async def _(bot: Bot, event: MessageEvent, args: V11Msg = CommandArg()):
|
|
263
|
-
if True:
|
|
264
|
-
await s_simpic.finish("simpic 并未开启喵。")
|
|
265
|
-
|
|
266
|
-
picture = event.reply.message.get("image") if event.reply else None
|
|
267
|
-
if not picture:
|
|
268
|
-
await s_simpic.finish(
|
|
269
|
-
random.choice(
|
|
270
|
-
WORDS.get("missing image reference", ["没找到引用消息中的图片"])
|
|
271
|
-
)
|
|
272
|
-
)
|
|
273
|
-
vec = img2vec(await load_pic(picture[0].data["url"]))
|
|
274
|
-
ignore_diagonal = True
|
|
275
|
-
ignore_min = False
|
|
276
|
-
if args.extract_plain_text().strip() == "-n":
|
|
277
|
-
ignore_diagonal = False
|
|
278
|
-
elif args.extract_plain_text().strip() == "-i":
|
|
279
|
-
ignore_min = True
|
|
280
|
-
group_id = (
|
|
281
|
-
"globe"
|
|
282
|
-
if not isinstance(event, GroupMessageEvent)
|
|
283
|
-
else f"qq_group:{event.group_id}"
|
|
284
|
-
)
|
|
285
|
-
try:
|
|
286
|
-
sim, pic = await get_most_similar_pic(
|
|
287
|
-
vec, group_id, ignore_diagonal, ignore_min
|
|
288
|
-
)
|
|
289
|
-
if pic:
|
|
290
|
-
await bot.send(
|
|
291
|
-
event,
|
|
292
|
-
V11Msg(
|
|
293
|
-
[
|
|
294
|
-
V11Seg.reply(event.reply.message_id),
|
|
295
|
-
f"{pic.name}\n(相似性:{'%.4g' % (min(sim * 100, 100.0))}%)\n",
|
|
296
|
-
url_to_image(pic.url),
|
|
297
|
-
]
|
|
298
|
-
),
|
|
299
|
-
)
|
|
300
|
-
else:
|
|
301
|
-
await s_simpic.send(random.choice(WORDS.get("not found", ["Nope."])))
|
|
302
|
-
except Exception as ex:
|
|
303
|
-
await s_simpic.finish(
|
|
304
|
-
f'{random.choice(WORDS.get("error", ["出错了喵~"]))}\n\n{ex}'
|
|
305
|
-
)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
import asyncio
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from sqlalchemy.exc import DBAPIError
|
|
5
|
+
from nonebot import on_command
|
|
6
|
+
from nonebot.plugin import on_endswith
|
|
7
|
+
from nonebot.params import CommandArg
|
|
8
|
+
from nonebot.adapters import Bot
|
|
9
|
+
from nonebot.adapters import Message
|
|
10
|
+
from nonebot.adapters import Event
|
|
11
|
+
from nonebot.adapters.onebot import utils
|
|
12
|
+
from nonebot.adapters.onebot.v11.event import GroupMessageEvent as V11GME
|
|
13
|
+
from nonebot.adapters.onebot.v11.message import MessageSegment as V11Seg
|
|
14
|
+
from nonebot.adapters.onebot.v11.message import Message as V11Msg
|
|
15
|
+
|
|
16
|
+
from .config import plugin_config
|
|
17
|
+
from .core.sql import simpic
|
|
18
|
+
from .core.sql import randpic
|
|
19
|
+
from .core.sql import countpic
|
|
20
|
+
from .core.sql import select_pic
|
|
21
|
+
from .core.utils import img2vec
|
|
22
|
+
from .core.utils import word2vec
|
|
23
|
+
from .core.utils import ocr as ocr_image
|
|
24
|
+
from .core.fileio import load_pic
|
|
25
|
+
|
|
26
|
+
cpic = on_command("countpic", priority=5)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cpic.handle()
|
|
30
|
+
async def _(bot: Bot, event: Event, args: Message = CommandArg()):
|
|
31
|
+
reg = args.extract_plain_text().strip()
|
|
32
|
+
group_id = (
|
|
33
|
+
"globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
|
|
34
|
+
)
|
|
35
|
+
try:
|
|
36
|
+
await bot.send(event, f"共查找到 {await countpic(reg, group_id)} 张图片。")
|
|
37
|
+
except DBAPIError as ex:
|
|
38
|
+
await cpic.finish(f"出错了喵~\n\n{ex.orig}")
|
|
39
|
+
except Exception as ex:
|
|
40
|
+
await cpic.finish(f"出错了喵~\n\n{ex}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
pic_listen = on_endswith((".jpg", ".png", ".gif"), priority=50, block=False)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pic_listen.handle()
|
|
47
|
+
async def _(bot: Bot, event: Event):
|
|
48
|
+
name = event.get_plaintext().strip()
|
|
49
|
+
group_id = (
|
|
50
|
+
"globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
|
|
51
|
+
)
|
|
52
|
+
try:
|
|
53
|
+
pic = await select_pic(name, group_id)
|
|
54
|
+
if pic:
|
|
55
|
+
file_ = await load_pic(pic.url)
|
|
56
|
+
await bot.send(event, V11Seg.image(file=file_))
|
|
57
|
+
except Exception as ex:
|
|
58
|
+
await pic_listen.finish(str(ex))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
rpic = on_command("randpic", priority=5)
|
|
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
|
+
@rpic.handle()
|
|
71
|
+
async def _(bot, event, args: V11Msg = CommandArg()):
|
|
72
|
+
name = args.extract_plain_text().strip()
|
|
73
|
+
group_id = (
|
|
74
|
+
"globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
|
|
75
|
+
)
|
|
76
|
+
try:
|
|
77
|
+
pic, t = await randpic(name, group_id, True)
|
|
78
|
+
if not pic:
|
|
79
|
+
await rpic.send("404 Not Found.")
|
|
80
|
+
return
|
|
81
|
+
await bot.send(
|
|
82
|
+
event,
|
|
83
|
+
V11Msg(
|
|
84
|
+
[
|
|
85
|
+
pic.name,
|
|
86
|
+
"\n" + t if t else "",
|
|
87
|
+
url_to_image(pic.url),
|
|
88
|
+
]
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
except Exception as ex:
|
|
92
|
+
with open("error.txt", "w+") as f:
|
|
93
|
+
f.write("\n")
|
|
94
|
+
traceback.print_exc(file=f)
|
|
95
|
+
await rpic.finish(str(ex))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
s_simpic = on_command("simpic", priority=5)
|
|
99
|
+
|
|
100
|
+
__lock__ = asyncio.Lock()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@s_simpic.handle()
|
|
104
|
+
async def _(bot: Bot, event: Event, args: Message = CommandArg()):
|
|
105
|
+
if __lock__.locked():
|
|
106
|
+
await s_simpic.finish("相似图片搜索中,请稍后再试喵~")
|
|
107
|
+
if plugin_config.simpic_model not in ["ViT/16-Bfloat16-Modify"]:
|
|
108
|
+
await s_simpic.finish("当前配置的模型不支持相似图片搜索喵~")
|
|
109
|
+
try:
|
|
110
|
+
picture = args.get("image")
|
|
111
|
+
if not picture:
|
|
112
|
+
picture = event.reply.message.get("image") if event.reply else None
|
|
113
|
+
if not picture:
|
|
114
|
+
await s_simpic.finish("请发送图片后再使用该指令喵~")
|
|
115
|
+
img = await load_pic(picture[0].data["url"])
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
ocr = await ocr_image(img)
|
|
119
|
+
except Exception:
|
|
120
|
+
ocr = {}
|
|
121
|
+
|
|
122
|
+
if "text" in ocr:
|
|
123
|
+
ocr = ocr["text"]
|
|
124
|
+
else:
|
|
125
|
+
ocr = await bot.ocr_image(image=utils.f2s(img))
|
|
126
|
+
if ocr and "texts" in ocr:
|
|
127
|
+
r = ""
|
|
128
|
+
for d in ocr["texts"]:
|
|
129
|
+
r += d["text"]
|
|
130
|
+
ocr = r.strip()
|
|
131
|
+
else:
|
|
132
|
+
ocr = ""
|
|
133
|
+
if len(set(ocr)) <= 10:
|
|
134
|
+
ocr = ""
|
|
135
|
+
async with __lock__:
|
|
136
|
+
vec = await img2vec(img, ocr)
|
|
137
|
+
group_id = (
|
|
138
|
+
"globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
|
|
139
|
+
)
|
|
140
|
+
sim, pic = await simpic(vec, group_id, True)
|
|
141
|
+
except Exception as ex:
|
|
142
|
+
await s_simpic.finish(str(ex))
|
|
143
|
+
|
|
144
|
+
if pic:
|
|
145
|
+
ret = []
|
|
146
|
+
if event.reply:
|
|
147
|
+
ret.append(V11Seg.reply(event.reply.message_id))
|
|
148
|
+
ret.append(f"{pic.name}\n(相似性:{'%.4g' % (min(sim * 100, 100.0))}%)")
|
|
149
|
+
ret.append(url_to_image(pic.url))
|
|
150
|
+
await s_simpic.send(V11Msg(ret))
|
|
151
|
+
else:
|
|
152
|
+
await s_simpic.send("没有找到相似的图片喵~")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
clip = on_command("clip", priority=5)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@clip.handle()
|
|
159
|
+
async def _(bot: Bot, event: Event, args: Message = CommandArg()):
|
|
160
|
+
if plugin_config.simpic_model not in ["ViT/16-Bfloat16-Modify"]:
|
|
161
|
+
await s_simpic.finish("当前配置的模型不支持相似图片搜索喵~")
|
|
162
|
+
name = args.extract_plain_text().strip()
|
|
163
|
+
group_id = (
|
|
164
|
+
"globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
|
|
165
|
+
)
|
|
166
|
+
try:
|
|
167
|
+
sim, pic = await simpic(await word2vec(name), group_id, True)
|
|
168
|
+
except Exception as ex:
|
|
169
|
+
await s_simpic.finish(str(ex))
|
|
170
|
+
|
|
171
|
+
if pic:
|
|
172
|
+
ret = []
|
|
173
|
+
if event.reply:
|
|
174
|
+
ret.append(V11Seg.reply(event.reply.message_id))
|
|
175
|
+
ret.append(f"{pic.name}\n(相似性:{'%.4g' % (min(sim * 100, 100.0))}%)")
|
|
176
|
+
ret.append(url_to_image(pic.url))
|
|
177
|
+
await s_simpic.send(V11Msg(ret))
|
|
178
|
+
else:
|
|
179
|
+
await s_simpic.send("没有找到相似的图片喵~")
|
{nonebot_plugin_savepic-0.3.0 → nonebot_plugin_savepic-0.4.1}/nonebot_plugin_savepic/config.py
RENAMED
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import json
|
|
1
|
+
from pydantic import Field
|
|
3
2
|
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))
|
|
3
|
+
from nonebot import get_plugin_config
|
|
13
4
|
|
|
14
5
|
|
|
15
6
|
class Config(BaseModel):
|
|
@@ -23,16 +14,12 @@ class Config(BaseModel):
|
|
|
23
14
|
pinecone_index: str = "savepic"
|
|
24
15
|
pinecone_environment: str
|
|
25
16
|
|
|
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
17
|
embedding_sqlurl: str
|
|
35
|
-
|
|
18
|
+
openai_apikey: str
|
|
19
|
+
openai_baseurl: str
|
|
20
|
+
embedding_model: str = "BAAI/bge-m3"
|
|
21
|
+
simpic_model: str = Field(default="")
|
|
22
|
+
p_model_path: str = Field(default="")
|
|
36
23
|
|
|
37
24
|
notfound_with_jpg: bool = True
|
|
38
25
|
""" randpic 的时候,尝试带 .jpg 再度检索向量 """
|
|
@@ -42,3 +29,10 @@ class Config(BaseModel):
|
|
|
42
29
|
""" listpic 的时候合并转发 """
|
|
43
30
|
max_page_in_listpic: int = 20
|
|
44
31
|
""" 合并转发中所能显示的最大页数 """
|
|
32
|
+
|
|
33
|
+
chat_mode: bool = False
|
|
34
|
+
ocr_model: str = "gpt-4o-mini"
|
|
35
|
+
local_sqlite_path: str = Field(default="sqlite+aiosqlite:///./savepic.db")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
plugin_config = get_plugin_config(Config)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import pathlib
|
|
3
|
+
import httpx
|
|
4
|
+
import sqlalchemy as sa
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.ext.asyncio.session import AsyncSession
|
|
7
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
8
|
+
from nonebot import get_driver
|
|
9
|
+
from ..model import PicLife
|
|
10
|
+
from ..config import plugin_config
|
|
11
|
+
|
|
12
|
+
# 异步连接到 plugin_config.local_sqlite_path
|
|
13
|
+
_async_database = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@get_driver().on_startup
|
|
17
|
+
async def init_db():
|
|
18
|
+
# check if the table exists
|
|
19
|
+
global _async_database
|
|
20
|
+
_async_database = create_async_engine(
|
|
21
|
+
plugin_config.local_sqlite_path,
|
|
22
|
+
)
|
|
23
|
+
try:
|
|
24
|
+
metadata = sa.MetaData()
|
|
25
|
+
sa.Table(
|
|
26
|
+
"piclife",
|
|
27
|
+
metadata,
|
|
28
|
+
sa.Column("url", sa.Text, primary_key=True),
|
|
29
|
+
sa.Column("life", sa.Integer, default=0),
|
|
30
|
+
)
|
|
31
|
+
async with _async_database.begin() as conn:
|
|
32
|
+
await conn.run_sync(metadata.create_all)
|
|
33
|
+
except Exception as ex:
|
|
34
|
+
print(ex)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def del_pic(url: str | pathlib.Path):
|
|
38
|
+
if isinstance(url, pathlib.Path):
|
|
39
|
+
url = url.as_posix()
|
|
40
|
+
if url.startswith("http"):
|
|
41
|
+
return
|
|
42
|
+
if isinstance(url, str):
|
|
43
|
+
_ = pathlib.Path(url)
|
|
44
|
+
else:
|
|
45
|
+
_ = url
|
|
46
|
+
|
|
47
|
+
async with AsyncSession(_async_database) as session:
|
|
48
|
+
if life := await session.get(PicLife, url):
|
|
49
|
+
if life.life > 0:
|
|
50
|
+
life.life -= 1
|
|
51
|
+
await session.commit()
|
|
52
|
+
return
|
|
53
|
+
await session.delete(life)
|
|
54
|
+
await session.commit()
|
|
55
|
+
if _.exists():
|
|
56
|
+
_.unlink()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def load_pic(url: str) -> bytes:
|
|
60
|
+
if url.startswith("http"):
|
|
61
|
+
async with httpx.AsyncClient() as client:
|
|
62
|
+
resp = await client.get(url)
|
|
63
|
+
resp.raise_for_status()
|
|
64
|
+
return resp.content
|
|
65
|
+
|
|
66
|
+
if pathlib.Path(url).exists():
|
|
67
|
+
with open(url, "rb") as f:
|
|
68
|
+
return f.read()
|
|
69
|
+
raise Exception(f"不支持的 URL\n{url}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def write_pic(url: str, des_dir: str = None) -> str:
|
|
73
|
+
if not des_dir:
|
|
74
|
+
des_dir = "savepic"
|
|
75
|
+
path = pathlib.Path(des_dir)
|
|
76
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
byte = await load_pic(url)
|
|
79
|
+
file = path / hashlib.sha256(byte).hexdigest()
|
|
80
|
+
|
|
81
|
+
async with AsyncSession(_async_database) as session:
|
|
82
|
+
if life := await session.get(PicLife, file.as_posix()):
|
|
83
|
+
life.life += 1
|
|
84
|
+
await session.commit()
|
|
85
|
+
return file.as_posix()
|
|
86
|
+
life = PicLife(url=file.as_posix())
|
|
87
|
+
session.add(life)
|
|
88
|
+
await session.commit()
|
|
89
|
+
|
|
90
|
+
with open(file, "wb") as f:
|
|
91
|
+
f.write(byte)
|
|
92
|
+
return file.as_posix()
|