nonebot-plugin-savepic 0.4.1__tar.gz → 0.6.7__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 (28) hide show
  1. nonebot_plugin_savepic-0.4.1/README.md → nonebot_plugin_savepic-0.6.7/PKG-INFO +20 -47
  2. nonebot_plugin_savepic-0.6.7/README.md +68 -0
  3. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/__init__.py +171 -0
  4. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/command.py +138 -0
  5. {nonebot_plugin_savepic-0.4.1 → nonebot_plugin_savepic-0.6.7}/nonebot_plugin_savepic/config.py +11 -16
  6. {nonebot_plugin_savepic-0.4.1 → nonebot_plugin_savepic-0.6.7}/nonebot_plugin_savepic/core/error.py +12 -2
  7. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/core/fileio.py +43 -0
  8. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/core/sql.py +769 -0
  9. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/core/types.py +9 -0
  10. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/core/utils.py +136 -0
  11. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/fun.py +78 -0
  12. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/listpic.py +163 -0
  13. nonebot_plugin_savepic-0.6.7/nonebot_plugin_savepic/mvpic.py +141 -0
  14. {nonebot_plugin_savepic-0.4.1 → nonebot_plugin_savepic-0.6.7}/nonebot_plugin_savepic/rule.py +2 -2
  15. nonebot_plugin_savepic-0.6.7/pyproject.toml +49 -0
  16. nonebot_plugin_savepic-0.4.1/PKG-INFO +0 -141
  17. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/__init__.py +0 -258
  18. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/command.py +0 -179
  19. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/fileio.py +0 -92
  20. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/model.py +0 -207
  21. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/sql.py +0 -409
  22. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/core/utils.py +0 -1170
  23. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/listpic.py +0 -75
  24. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/model.py +0 -37
  25. nonebot_plugin_savepic-0.4.1/nonebot_plugin_savepic/mvpic.py +0 -134
  26. nonebot_plugin_savepic-0.4.1/pyproject.toml +0 -32
  27. {nonebot_plugin_savepic-0.4.1 → nonebot_plugin_savepic-0.6.7}/LICENSE +0 -0
  28. {nonebot_plugin_savepic-0.4.1 → nonebot_plugin_savepic-0.6.7}/nonebot_plugin_savepic/core/__init__.py +0 -0
@@ -1,3 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: nonebot-plugin-savepic
3
+ Version: 0.6.7
4
+ Summary: 保存表情包(语录)与随机出图
5
+ Author-Email: Yan <1964649083@qq.com>
6
+ License: MIT
7
+ Project-URL: homepage, https://github.com/Yan-Zero/nonebot-plugin-savepic
8
+ Requires-Python: <4.0,>=3.11
9
+ Requires-Dist: nonebot2<3.0.0,>=2.4.0
10
+ Requires-Dist: asyncpg<1.0.0,>=0.28.0
11
+ Requires-Dist: nonebot-adapter-onebot<3.0.0,>=2.4.6
12
+ Requires-Dist: numpy<3.0.0,>=2.1.3
13
+ Requires-Dist: httpx<1.0.0,>=0.28.0
14
+ Requires-Dist: nonebot-plugin-alconna>=0.59.4
15
+ Description-Content-Type: text/markdown
16
+
1
17
  <div align="center">
2
18
  <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>
3
19
  <br>
@@ -29,63 +45,25 @@ _✨ 一个存取图片的插件 ✨_
29
45
  重命名表情包,或者修改表情包所属的群域
30
46
 
31
47
  例如:
48
+
32
49
  ```
33
50
  /mvpic -l name.jpg -g waaaaa.gif
34
51
  ```
52
+
35
53
  就是把本群的 name.jpg 改成全局名为 waaaaa.gif 的表情包
36
54
 
37
55
  同理
56
+
38
57
  ```
39
58
  /mvpic -g waaaaa.gif -l waaaaa.gif
40
59
  ```
60
+
41
61
  就是从全局移到本群(接收到命令的群)
42
62
 
43
63
  ### 直接发送文件名
44
64
 
45
65
  发送文件名即可发送表情包
46
66
 
47
-
48
- ## 💿 安装
49
-
50
- <details open>
51
- <summary>使用 nb-cli 安装</summary>
52
- 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
53
-
54
- nb plugin install nonebot-plugin-savepic
55
-
56
- </details>
57
-
58
- <details>
59
- <summary>使用包管理器安装</summary>
60
- 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
61
-
62
- <details>
63
- <summary>pip</summary>
64
-
65
- pip install nonebot-plugin-savepic
66
- </details>
67
- <details>
68
- <summary>pdm</summary>
69
-
70
- pdm add nonebot-plugin-savepic
71
- </details>
72
- <details>
73
- <summary>poetry</summary>
74
-
75
- poetry add nonebot-plugin-savepic
76
- </details>
77
- <details>
78
- <summary>conda</summary>
79
-
80
- conda install nonebot-plugin-savepic
81
- </details>
82
-
83
- 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入
84
-
85
- plugins = ["nonebot_plugin_savepic"]
86
-
87
- </details>
88
-
89
67
  ## ⚙️ 配置
90
68
 
91
69
  在 nonebot2 项目的`.env`文件中添加下表中的必填配置
@@ -94,11 +72,6 @@ _✨ 一个存取图片的插件 ✨_
94
72
  |:-----:|:----:|:----:|:----:|
95
73
  | savepic_admin | 否 | 无 | 权限用户 |
96
74
  | savepic_dir | 否 | savepic | 图片本地保存位置 |
97
- | simpic_enable | 否 | False | 是否开启基于RNN模型的AI相似度检索功能,如果开启要填写 Pinecone 相关配置 |
98
- | pinecone_apikey | 否 | 无 | pinecone 的 Key |
99
- | pinecone_environment | 否 | 无 | pinecone 的 enviroment,是数据库的属性之一 |
100
- | pinecone_index | 否 | savepic | 同上。 |
101
- | dashscope_api | 否 | 无 | dashscope 的 APIKey。 用于开启基于语义的相似度检索功能 |
102
75
 
103
76
  ## 🎉 使用
104
77
 
@@ -0,0 +1,68 @@
1
+ <div align="center">
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>
3
+ <br>
4
+ <p><img src="https://github.com/A-kirami/nonebot-plugin-template/blob/resources/NoneBotPlugin.svg" width="240" alt="NoneBotPluginText"></p>
5
+ </div>
6
+
7
+ <div align="center">
8
+
9
+ # nonebot-plugin-savepic
10
+
11
+ _✨ 一个存取图片的插件 ✨_
12
+
13
+ </div>
14
+
15
+ ## 📖 介绍
16
+
17
+ 重写自 Fran 的 Savepic
18
+
19
+ ### savepic
20
+
21
+ 保存表情包
22
+
23
+ ### randpic
24
+
25
+ 抽取表情包
26
+
27
+ ### mvpic
28
+
29
+ 重命名表情包,或者修改表情包所属的群域
30
+
31
+ 例如:
32
+
33
+ ```
34
+ /mvpic -l name.jpg -g waaaaa.gif
35
+ ```
36
+
37
+ 就是把本群的 name.jpg 改成全局名为 waaaaa.gif 的表情包
38
+
39
+ 同理
40
+
41
+ ```
42
+ /mvpic -g waaaaa.gif -l waaaaa.gif
43
+ ```
44
+
45
+ 就是从全局移到本群(接收到命令的群)
46
+
47
+ ### 直接发送文件名
48
+
49
+ 发送文件名即可发送表情包
50
+
51
+ ## ⚙️ 配置
52
+
53
+ 在 nonebot2 项目的`.env`文件中添加下表中的必填配置
54
+
55
+ | 配置项 | 必填 | 默认值 | 说明 |
56
+ |:-----:|:----:|:----:|:----:|
57
+ | savepic_admin | 否 | 无 | 权限用户 |
58
+ | savepic_dir | 否 | savepic | 图片本地保存位置 |
59
+
60
+ ## 🎉 使用
61
+
62
+ ### 指令表
63
+
64
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
65
+ |:-----:|:----:|:----:|:----:|:----:|
66
+ | savepic | 群员 | 否 | 群聊 | 保存图片 |
67
+ | randpic | 群员 | 否 | 全部 | 随机图片 |
68
+ | mvpic | 管理员 | 否 | 群聊 | 重命名图片 |
@@ -0,0 +1,171 @@
1
+ from nonebot import on_command
2
+ from nonebot.params import CommandArg, Arg
3
+ from nonebot.adapters.onebot.v11.message import Message as V11Msg
4
+ from nonebot.adapters.onebot.v11.message import MessageSegment as V11Seg
5
+ from nonebot.adapters.onebot.v11.event import GroupMessageEvent
6
+ from nonebot.matcher import Matcher
7
+ from nonebot.plugin import PluginMetadata
8
+ from nonebot.typing import T_State
9
+ from arclet.alconna import Alconna, Args, Option, CommandMeta, Arparma
10
+ from nonebot_plugin_alconna import on_alconna, Image
11
+ from nonebot.internal.adapter import Bot
12
+
13
+ from .fun import cipr # noqa: F401
14
+ from .rule import PIC_ADMIN
15
+ from .config import Config, plugin_config
16
+ from .listpic import rkey
17
+ from .command import url_to_image
18
+ from .core.sql import savepic, regexp_pic
19
+ from .core.utils import img2vec
20
+ from .core.error import SameNameException
21
+ from .core.error import SimilarPictureException
22
+ from .core.fileio import write_pic, del_pic
23
+
24
+
25
+ __plugin_meta__ = PluginMetadata(
26
+ name="Savepic",
27
+ description="表情包保存",
28
+ usage="""用法:
29
+ /savepic <文件名> <图片>
30
+ /savepic -g <文件名> <全局图片>
31
+ /savepic -d <文件名> 删除图图片
32
+ /randpic <关键词> 随机图片
33
+ <文件名> 发送图片""",
34
+ config=Config,
35
+ homepage="https://github.com/Yan-Zero/nonebot-plugin-savepic",
36
+ type="application",
37
+ supported_adapters=set(["~onebot.v11"]),
38
+ )
39
+
40
+ repic = on_command("repic", priority=5)
41
+ spic = on_alconna(
42
+ Alconna(
43
+ "savepic",
44
+ Option("-g", help_text="全局"),
45
+ Option("-ac", help_text="允许相似碰撞"),
46
+ Args["filename", str],
47
+ Args["picture?", Image],
48
+ meta=CommandMeta(description="保存图片,默认保存到本群"),
49
+ ),
50
+ use_cmd_start=True,
51
+ skip_for_unmatch=False,
52
+ )
53
+
54
+
55
+ @repic.handle()
56
+ async def _(bot: Bot, event, args: V11Msg = CommandArg()):
57
+ reg = args.extract_plain_text().strip()
58
+ group_id = (
59
+ "globe"
60
+ if not isinstance(event, GroupMessageEvent)
61
+ else f"qq_group:{event.group_id}"
62
+ )
63
+ try:
64
+ if pic := await regexp_pic(reg, group_id):
65
+ await bot.send(
66
+ event, V11Msg([V11Seg.text(pic.name), await url_to_image(pic.url)])
67
+ )
68
+ except Exception as ex:
69
+ await repic.finish(f"出错了。{ex}")
70
+
71
+
72
+ @spic.handle()
73
+ async def _(
74
+ bot: Bot,
75
+ matcher: Matcher,
76
+ event: GroupMessageEvent,
77
+ state: T_State,
78
+ command: Arparma,
79
+ ):
80
+ if not command.matched:
81
+ await spic.finish(str(command.error_info))
82
+
83
+ filename = command.filename
84
+ if not isinstance(filename, str):
85
+ await spic.finish("文件名无效")
86
+
87
+ if not filename.endswith((".jpg", ".png", ".gif")):
88
+ filename += ".jpg"
89
+
90
+ state["savepiv_group"] = (
91
+ "globe"
92
+ if command.g and await PIC_ADMIN(bot, event)
93
+ else f"qq_group:{event.group_id}"
94
+ )
95
+
96
+ if command.g and not await PIC_ADMIN(bot, event):
97
+ await spic.send("你的 -g 选项没有用哟")
98
+
99
+ state["savepiv_filename"] = filename
100
+ state["savepiv_ac"] = command.ac is not None
101
+
102
+ picture = event.message.get("image")
103
+ if not picture and event.reply:
104
+ picture = event.reply.message.get("image")
105
+ if picture:
106
+ matcher.set_arg("picture", picture)
107
+
108
+
109
+ @spic.got("picture", "图呢?")
110
+ async def _(bot: Bot, state: T_State, event, picture: V11Msg = Arg()):
111
+ picture = picture.get("image")
112
+ if not picture:
113
+ await spic.finish("6,这也不是图啊")
114
+
115
+ try:
116
+ url = await rkey(
117
+ bot,
118
+ (
119
+ picture[0].data["url"]
120
+ if "url" in picture[0].data
121
+ else picture[0].data["file"]
122
+ ),
123
+ )
124
+ dir = await write_pic(url, plugin_config.savepic_dir)
125
+ except Exception as ex:
126
+ await spic.finish("存图失败。" + "\n" + str(ex))
127
+
128
+ try:
129
+ r = await savepic(
130
+ filename=state["savepiv_filename"],
131
+ url=dir,
132
+ scope=state["savepiv_group"],
133
+ uploader=f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}",
134
+ vec=await img2vec(url, state["savepiv_filename"]),
135
+ collision_allow=state["savepiv_ac"],
136
+ )
137
+ except SameNameException:
138
+ await del_pic(dir)
139
+ await spic.finish("文件名重复")
140
+ except SimilarPictureException as ex:
141
+ if ex.similarity >= 0.999:
142
+ await spic.finish(
143
+ V11Msg(
144
+ [
145
+ V11Seg.text("这图存过了,无法再保存" + "\n\n" + ex.name + "\n"),
146
+ await url_to_image(ex.url),
147
+ ]
148
+ )
149
+ )
150
+ await del_pic(dir)
151
+ await spic.finish(
152
+ V11Msg(
153
+ [
154
+ V11Seg.text(
155
+ "存在相似图片"
156
+ + "\n\n"
157
+ + ex.name
158
+ + f"\n(相似度:{'%.4g' % (min(ex.similarity * 100, 100.0))}%)\n"
159
+ ),
160
+ await url_to_image(ex.url),
161
+ ]
162
+ )
163
+ )
164
+ except Exception as ex:
165
+ await del_pic(dir)
166
+ await spic.finish(f"出错了。{ex}")
167
+
168
+ if r:
169
+ await spic.send(f"保存成功,但是名字为`{r}`")
170
+ else:
171
+ await spic.send("保存成功")
@@ -0,0 +1,138 @@
1
+ import traceback
2
+
3
+ from pathlib import Path
4
+ from nonebot import on_command
5
+ from nonebot.plugin import on_endswith
6
+ from nonebot.params import CommandArg
7
+ from nonebot.adapters import Bot
8
+ from nonebot.adapters import Message
9
+ from nonebot.adapters import Event
10
+ from nonebot.permission import SUPERUSER
11
+ from nonebot.adapters.onebot.v11.event import GroupMessageEvent as V11GME
12
+ from nonebot.adapters.onebot.v11.message import Message as V11Msg
13
+ from nonebot.adapters.onebot.v11.message import MessageSegment as V11Seg
14
+
15
+ from .listpic import rkey
16
+ from .core.sql import simpic, randpic, countpic, select_pic
17
+ from .core.utils import img2vec
18
+
19
+ cpic = on_command("countpic", priority=5)
20
+ rpic = on_command("randpic", priority=5)
21
+ s_simpic = on_command("simpic", priority=5)
22
+ pic_listen = on_endswith((".jpg", ".png", ".gif"), priority=50, block=False)
23
+ pic_clear = on_command("pic.clear", permission=SUPERUSER, priority=1, block=True)
24
+
25
+
26
+ async def url_to_image(url: str) -> V11Seg:
27
+ if url.startswith("http"):
28
+ return V11Seg.image(url)
29
+ return V11Seg.image(Path(url))
30
+
31
+
32
+ @cpic.handle()
33
+ async def _(bot: Bot, event: Event, args: Message = CommandArg()):
34
+ reg = args.extract_plain_text().strip()
35
+ group_id = (
36
+ "globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
37
+ )
38
+ try:
39
+ await bot.send(event, f"共查找到 {await countpic(reg, group_id)} 张图片。")
40
+ except Exception as ex:
41
+ await cpic.finish(f"出错了喵~\n\n{ex}")
42
+
43
+
44
+ @pic_listen.handle()
45
+ async def _(bot: Bot, event: Event):
46
+ name = event.get_plaintext().strip()
47
+ group_id = (
48
+ "globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
49
+ )
50
+ try:
51
+ if url := await select_pic(name, group_id):
52
+ await pic_listen.send(await url_to_image(url))
53
+ except Exception as ex:
54
+ await pic_listen.finish(str(ex))
55
+
56
+
57
+ @rpic.handle()
58
+ async def _(bot: Bot, event: Event, args: V11Msg = CommandArg()):
59
+ name = args.extract_plain_text().strip()
60
+ group_id = (
61
+ "globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
62
+ )
63
+ try:
64
+ pic, t = await randpic(name, group_id, True)
65
+ if not pic:
66
+ await rpic.send("404 Not Found.")
67
+ return
68
+ await rpic.send(
69
+ V11Msg(
70
+ [
71
+ V11Seg.text(pic.name + ("\n" + t if t else "")),
72
+ await url_to_image(pic.url),
73
+ ]
74
+ )
75
+ )
76
+ except Exception as ex:
77
+ with open("error.txt", "w+") as f:
78
+ f.write("\n")
79
+ traceback.print_exc(file=f)
80
+ await rpic.finish(str(ex))
81
+
82
+
83
+ @s_simpic.handle()
84
+ async def _(bot: Bot, event: Event, args: Message = CommandArg()):
85
+ picture = args.get("image")
86
+ if not picture and hasattr(event, "reply"):
87
+ picture = event.reply.message.get("image") if event.reply else None # type: ignore
88
+ if not picture:
89
+ picture = event.reply.message.get("mface") if event.reply else None # type: ignore
90
+ if not picture:
91
+ await s_simpic.finish("请发送图片后再使用该指令喵~")
92
+ try:
93
+ vec = await img2vec(
94
+ await rkey(bot, picture[0].data["url"]),
95
+ title=args.extract_plain_text().strip(),
96
+ )
97
+ if vec is None:
98
+ await s_simpic.finish("图片特征提取失败喵~")
99
+ group_id = (
100
+ "globe" if not isinstance(event, V11GME) else f"qq_group:{event.group_id}"
101
+ )
102
+ sim, pic = await simpic(vec, group_id)
103
+ except Exception as ex:
104
+ await s_simpic.finish(str(ex))
105
+
106
+ if pic:
107
+ ret = []
108
+ if event.reply: # type: ignore
109
+ ret.append(V11Seg.reply(event.reply.message_id)) # type: ignore
110
+ ret.append(f"{pic.name}\n(相似性:{'%.4g' % (min(sim * 100, 100.0))}%)")
111
+ ret.append(await url_to_image(pic.url))
112
+ await s_simpic.send(V11Msg(ret))
113
+ else:
114
+ await s_simpic.send("没有找到相似的图片喵~")
115
+
116
+
117
+ @pic_clear.handle()
118
+ async def _():
119
+ # 清理孤儿图片
120
+ from .core import sql
121
+
122
+ if not sql.POOL:
123
+ await pic_clear.finish("数据库未初始化")
124
+ await pic_clear.send("开始清理孤儿图片...")
125
+ img = set(Path("savepic").glob("*"))
126
+ async with sql.POOL.acquire() as conn, conn.transaction():
127
+ async for record in conn.cursor("SELECT name, url FROM picdata;"):
128
+ if not record["url"].startswith("savepic/"):
129
+ continue
130
+ p = Path(record["url"])
131
+ img.remove(p)
132
+ if img:
133
+ await pic_clear.send(f"发现 {len(img)} 张孤儿图片,正在删除...")
134
+ for p in img:
135
+ p.unlink(missing_ok=True)
136
+ await pic_clear.send("删除完成!")
137
+ else:
138
+ await pic_clear.send("没有发现孤儿图片!")
@@ -1,6 +1,6 @@
1
- from pydantic import Field
2
- from pydantic import BaseModel
1
+ from typing import Optional
3
2
  from nonebot import get_plugin_config
3
+ from pydantic import BaseModel
4
4
 
5
5
 
6
6
  class Config(BaseModel):
@@ -10,16 +10,9 @@ class Config(BaseModel):
10
10
  savepic_dir: str = "savepic"
11
11
  savepic_sqlurl: str
12
12
 
13
- pinecone_apikey: str
14
- pinecone_index: str = "savepic"
15
- pinecone_environment: str
16
-
17
- embedding_sqlurl: str
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="")
13
+ embedding_key: str
14
+ embedding_url: str
15
+ embedding_model: str
23
16
 
24
17
  notfound_with_jpg: bool = True
25
18
  """ randpic 的时候,尝试带 .jpg 再度检索向量 """
@@ -30,9 +23,11 @@ class Config(BaseModel):
30
23
  max_page_in_listpic: int = 20
31
24
  """ 合并转发中所能显示的最大页数 """
32
25
 
33
- chat_mode: bool = False
34
- ocr_model: str = "gpt-4o-mini"
35
- local_sqlite_path: str = Field(default="sqlite+aiosqlite:///./savepic.db")
26
+ cache_sqlurl: Optional[str] = None
27
+ """ 额外的只读连接池地址,若不填写则与 savepic_sqlurl 共用 """
28
+
29
+ normalize_vector: Optional[str] = None
30
+ """ 向量归一化,路径指向一个 txt 文件,文件内是均值向量 """
36
31
 
37
32
 
38
- plugin_config = get_plugin_config(Config)
33
+ plugin_config: Config = get_plugin_config(Config)
@@ -1,9 +1,10 @@
1
1
  class SameNameException(Exception):
2
- def __init__(self, name):
2
+ def __init__(self, name, scope):
3
3
  self.name = name
4
+ self.scope = scope
4
5
 
5
6
  def __str__(self):
6
- return "Name {} already exists".format(self.name)
7
+ return "Name {} already exists in scope {}".format(self.name, self.scope)
7
8
 
8
9
 
9
10
  class SimilarPictureException(Exception):
@@ -22,3 +23,12 @@ class NoPictureException(Exception):
22
23
 
23
24
  def __str__(self):
24
25
  return "Picture {} not found".format(self.name)
26
+
27
+
28
+ class PermissionException(Exception):
29
+ def __init__(self, name, reason="No permission"):
30
+ self.name = name
31
+ self.reason = reason
32
+
33
+ def __str__(self):
34
+ return "No permission to access {}: {}".format(self.name, self.reason)
@@ -0,0 +1,43 @@
1
+ import httpx
2
+ import hashlib
3
+ import pathlib
4
+
5
+
6
+ async def del_pic(url: str | pathlib.Path):
7
+ if isinstance(url, pathlib.Path):
8
+ url = url.as_posix()
9
+ if url.startswith("http"):
10
+ return
11
+ if isinstance(url, str):
12
+ _ = pathlib.Path(url)
13
+ else:
14
+ _ = url
15
+ if _.exists():
16
+ _.unlink()
17
+
18
+
19
+ async def load_pic(url: str) -> bytes:
20
+ if url.startswith("http"):
21
+ async with httpx.AsyncClient() as client:
22
+ resp = await client.get(url)
23
+ resp.raise_for_status()
24
+ return resp.content
25
+
26
+ if pathlib.Path(url).exists():
27
+ with open(url, "rb") as f:
28
+ return f.read()
29
+ raise Exception(f"不支持的 URL\n{url}")
30
+
31
+
32
+ async def write_pic(url: str, des_dir: str | None = None) -> str:
33
+ if not des_dir:
34
+ des_dir = "savepic"
35
+ path = pathlib.Path(des_dir)
36
+ path.mkdir(parents=True, exist_ok=True)
37
+
38
+ byte = await load_pic(url)
39
+ file = path / hashlib.sha256(byte).hexdigest()
40
+
41
+ with open(file, "wb") as f:
42
+ f.write(byte)
43
+ return file.as_posix()