├── LICENSE
├── README.md
├── nonebot_plugin_setu
├── __init__.py
├── dao
│ ├── __init__.py
│ ├── group_dao.py
│ ├── image_dao.py
│ └── user_dao.py
├── file_tools.py
├── getPic.py
├── proxies.py
├── setu_api.py
├── utils.py
└── withdraw.py
├── pyproject.toml
└── requirements.txt
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |

7 |
nonebot_plugin_setu
8 |
基于nonebot2、loliconApi的涩图插件
9 |
10 |

11 |

12 |

13 |

14 |
15 |
16 | ## 安装及更新
17 |
18 | - 安装
19 |
20 | ```bash
21 | nb plugin install nonebot_plugin_setu
22 | ```
23 | 或者
24 | ```bash
25 | pip install nonebot_plugin_setu
26 | ```
27 | 推荐使用`nb`进行安装
28 | - 更新
29 |
30 | ```bash
31 | nb plugin update nonebot_plugin_setu
32 | ```
33 | 或者
34 | ```bash
35 | pip install nonebot_plugin_setu -U
36 | ```
37 | - 导入插件
38 |
39 | 在`pyproject.toml`里的`[tool.nonebot]`中添加`plugins = ["nonebot_plugin_setu"]`
40 |
41 | **注**:如果你使用`nb`安装插件,则不需要设置此项
42 |
43 |
44 | ## 使用方式
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 首先运行一遍robot,然后在robot目录的data目录下修改setu_config.json配置文件,然后重启robot
54 |
55 | ### 添加配置
56 |
57 | - **在你的setu_config.json文件中修改如下配置:**
58 |
59 | SUPERUSERS = ["主人的qq号"],可添加多个
60 |
61 | PROXIES_HTTP = 'HTTP魔法地址(例如`http://127.0.0.1:7890`),这与你使用的魔法有关'
62 |
63 | PROXIES_SOCKS = 'SOCKS5魔法地址(例如`socks5://127.0.0.1:10808`),这与你使用的魔法有关'
64 |
65 | **注**:若没有魔法或者不会设置可不填
66 |
67 |
68 |
69 | ### 正式使用
70 |
71 | | 命令 | 举例 | 说明 |
72 | |----------------------------------|-----------------------------------|:-------------------------------------------------------------------------------------|
73 | | 下载涩图/色图+数量 | 下载涩图12345、下载色图12345 | 下载涩图:下载非涩涩图片;下载色图:下载色色图片 |
74 | | 涩图、setu、无内鬼、色图 | setu | 发送图片 |
75 | | @用户cd+时间(秒) | @张三cd12345 | 指定用户cd |
76 | | 群cd+时间(秒) | 群cd12345 | 指定群cd |
77 | | 开启/关闭在线发图 | 开启在线发图 | 在线发图开启之后,图片将不再从本地发送而是从网上下载后在线发送,不会占用服务器存储资源 |
78 | | 开启/关闭魔法 | 关闭魔法 | 魔法关闭之后,图片的下载以及在线发送将不再通过魔法而是通过镜像来完成,如果没有魔法或者不会设置推荐关闭 |
79 | | 涩图tagA和B和C(最多指定三个tag) | 涩图tag碧蓝航线、涩图tag公主连结和白丝 | 为了保证尽可能多地获取tag指定的内容,tag指定的图片都会在线获取而不从本地寻找,是否存储依然遵循在线发图开关 |
80 | | 撤回间隔+时间(秒) | 撤回间隔20、撤回间隔0 | 设置撤回间隔之后,机器人将会在指定间隔后撤回发送的图片,撤回间隔为0时,机器人将不会进行撤回。同时撤回间隔以群聊为单位,每个群都能设置不同的间隔,私聊将不会触发撤回操作 |
81 | | 涩图api、设置api地址+`服务器ip地址或域名:机器人端口` | 涩图api、设置api地址`123.456.789.0:8080` | 设置api并开放防火墙端口之后,就能把服务器中的图库数据转为api供他人调用,本地api调试请访问`http://localhost:机器人端口/setu/docs` |
82 | | 开启涩涩、开启私聊涩涩、关闭涩涩、关闭私聊涩涩 | 开启涩涩、开启私聊涩涩 | 开启涩涩之后,机器人将会发送色色图片,涩涩以群聊为单位,支持不同群是否开启 |
83 | | 涩图帮助 | 涩图帮助 | 获取命令列表 |
84 | | 涩图转发者名字 | 涩图转发者名字bot | 修改发送转发消息时的转发者名字 |
85 |
86 | #### 注:
87 |
88 | - 用户cd和群cd同时存在时,以用户cd为准
89 | - 群cd默认3600s
90 | - 开放api时请保证机器人监听的地址为0.0.0.0
91 | - 仅好友才能私聊发图,私聊发图时若未指定用户cd,则默认cd3600s
92 |
93 | ## TODO
94 |
95 | - [ ] 数据可视化
96 |
97 |
98 |
99 | ## 更新日志
100 |
101 | ### 2023/1/9
102 |
103 | - 修复无法捕获异常bug
104 | - 优化图片下载以及异常通知
105 | - 修复发送的图片为空白的bug
106 |
107 | ### 2022/12/27
108 |
109 | - 将图片发送方式改为转发
110 | - 新增修改转发者名字
111 |
112 | ### 2022/7/29[v1.1.2]
113 |
114 | - 增加帮助,修复下载图片失败bug
115 |
116 | ### 2022/4/2[v1.0.14]
117 |
118 | - 新增自动撤回
119 | - 新增自建图库与图库api
120 | - 新增涩涩模式
121 |
122 |
123 |
124 | ### 2022/3/19[v1.0.11]
125 |
126 | - 新增指定tag,可指定tag进行发图,tag最多指定三个
127 |
128 |
129 |
130 | ### 2022/3/17[v1.0.9]
131 |
132 | - 新增在线发图开关,图片可以在线发送而不占用服务器存储空间
133 | - 新增魔法开关,没有魔法也能够正常使用
134 |
135 | 注:旧版本用户请删除setu_config.json然后重新配置一遍
136 |
137 |
138 |
139 | ### 2022/3/13[v1.0.5]
140 |
141 | - 删除SETU_CD,修改cd配置,不再依赖userscd.json,转为依赖数据库文件
142 | - 添加用户cd和群cd,可由管理员进行指定和更改
143 | - 引入数据库存储图片信息,修改图片存储格式从jpg转为图片原本对应样式
144 |
145 | 注:旧版本用户请删除setu_config.json然后重新配置一遍
146 |
147 |
148 |
149 | ### 2022/3/9[v1.0.4]
150 |
151 | - 更改异常捕获范围,修复无法捕获异常的bug
152 |
153 |
154 |
155 | ### 2022/3/8[v1.0.3]
156 |
157 | - 删除配置:SETU_NUM,可下载指定数量的图片
158 | - 新增下载图片进度条
159 |
160 |
161 |
162 | ### 2022/3/5 [v1.0.1]
163 |
164 | - 支持nonebot[v2.0.0-beta2],请更新至最新版nonebot使用
165 | - 更改图片的名字为对应pid
166 | - 更改文件的配置方式,不再依赖.env文件
167 |
168 |
169 |
170 | ### 2022/1/26 [v1.0.0a1]
171 |
172 | - 支持nonebot[v2.0.0-beta1],beta1之前的请使用0.0.6版本
173 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/__init__.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import random
4 | import re
5 | from datetime import datetime
6 | from pathlib import Path
7 |
8 | import httpx
9 | from nonebot import get_driver
10 | from nonebot.adapters.onebot.v11 import Bot, Message, Event, MessageSegment
11 | from nonebot.exception import NoneBotException
12 | from nonebot.log import logger
13 | from nonebot.plugin import on_regex
14 |
15 | from .file_tools import Config
16 | from .dao.group_dao import GroupDao
17 | from .dao.image_dao import ImageDao
18 | from .dao.user_dao import UserDao
19 | from .getPic import get_url, down_pic
20 | from .setu_api import setu_api
21 | from .utils import send_forward_msg, get_file_num, img_num_detect
22 | from .withdraw import add_withdraw_job
23 |
24 | setu = on_regex("^涩图$|^setu$|^无内鬼$|^色图$|^涩图tag.+$")
25 | downLoad = on_regex(r"^下载涩图[1-9]\d*$|^下载色图[1-9]\d*$")
26 | user_cd = on_regex(r"^\[CQ:at,qq=[1-9][0-9]{4,10}\] cd\d+$")
27 | group_cd = on_regex(r"^群cd0$|^群cd[1-9]\d*$")
28 | online_switch = on_regex(r"^开启在线发图$|^关闭在线发图$")
29 | proxy_switch = on_regex(r"^开启魔法$|^关闭魔法$")
30 | api = on_regex(r"^涩图api$|^设置api地址.+$")
31 | withdraw_interval = on_regex(r"^撤回间隔0$|^撤回间隔[1-9]\d*$")
32 | r18_switch = on_regex(r"^开启涩涩$|^关闭涩涩$|^开启私聊涩涩$|^关闭私聊涩涩$")
33 | setu_help = on_regex(r"^涩图帮助$")
34 | msg_forward_name = on_regex(r"^涩图转发者名字.+$")
35 |
36 | super_user = Config().super_users
37 | driver = get_driver()
38 | driver.server_app.mount('/setu', setu_api, name='setu_plugin')
39 |
40 |
41 | @setu.handle()
42 | async def _(bot: Bot, event: Event):
43 | bot_name = Config().get_file_args(args_name='FORWARD_NAME')
44 | is_group_chat = hasattr(event, 'group_id')
45 | r18 = UserDao().get_r18_private_chat() \
46 | if not is_group_chat else GroupDao().get_group_r18(event.group_id)
47 | if img_num_detect(r18) == 0:
48 | await setu.finish('没有涩图啦,请下载或使用在线模式', at_sender=True)
49 | img_path = Path(f"loliconImages{'/r18' if r18 else ''}").resolve()
50 | images = os.listdir(img_path)
51 | if r18 == 0:
52 | images.remove('r18')
53 | file_name = '' if Config().online_switch else images[random.randint(0, len(images) - 1)]
54 | pid = re.sub(r'\D+', '', file_name)
55 | remain_time = 0 if event.get_user_id() in Config().super_users else UserDao().get_user_remain_time(event)
56 | if remain_time != 0:
57 | hour = int(remain_time / 3600)
58 | minute = int((remain_time / 60) % 60)
59 | await setu.finish(f'要等{hour}小时{minute}分钟才能再要涩图哦', at_sender=True)
60 | msg = event.get_plaintext()
61 | tag_flag = 0
62 | if bool(re.search(r"^涩图tag.+$", msg)):
63 | tag_flag = 1
64 | tags = re.sub(r'^涩图tag', '', msg).split('和')
65 | if len(tags) > 3:
66 | UserDao().delete_user_cd(event.get_user_id())
67 | await setu.finish('涩图tag最多只能有三个哦', at_sender=True)
68 | else:
69 | try:
70 | file_name = await get_url(num=1, tags=tags, online_switch=Config().online_switch, r18=r18)
71 | except httpx.HTTPError:
72 | UserDao().delete_user_cd(event.get_user_id())
73 | await setu.finish(message=Message('网络错误,请重试'), at_sender=True)
74 | except Exception as e:
75 | UserDao().delete_user_cd(event.get_user_id())
76 | await setu.finish(message=Message(f'{e}'), at_sender=True)
77 | if Config().online_switch == 0:
78 | pid = re.sub(r'\D+', '', file_name)
79 | if file_name == "":
80 | UserDao().delete_user_cd(event.get_user_id())
81 | await setu.finish('没有找到相关涩图,请更换tag', at_sender=True)
82 | interval = 0 if not is_group_chat else GroupDao().get_group_interval(event.group_id)
83 | try:
84 | if Config().online_switch == 1:
85 | img = file_name if tag_flag == 1 else await get_url(num=1, online_switch=1, r18=r18)
86 | message_list = [MessageSegment.image(img['base64']), f"https://pixiv.net/artworks/{img['pid']}"]
87 | msg_info = await send_forward_msg(bot, event, bot_name, bot.self_id, message_list, is_group_chat)
88 | else:
89 | message_list = [MessageSegment.image(f"file:///{img_path.joinpath(file_name)}"),
90 | f"https://pixiv.net/artworks/{pid}"]
91 | msg_info = await send_forward_msg(bot, event, bot_name, bot.self_id, message_list, is_group_chat)
92 | await add_withdraw_job(bot, **msg_info, withdraw_interval=interval)
93 | except httpx.HTTPError:
94 | UserDao().delete_user_cd(event.get_user_id())
95 | await setu.finish(message=Message('获取图片出错,本次涩图不计入cd'), at_sender=True)
96 | except NoneBotException as e:
97 | UserDao().delete_user_cd(event.get_user_id())
98 | logger.error(f"{type(e)}")
99 | await setu.finish(message=Message('机器人被风控了,本次涩图不计入cd'), at_sender=True)
100 | except Exception as e:
101 | UserDao().delete_user_cd(event.get_user_id())
102 | await setu.finish(message=Message(f"{e},本次涩图不计入cd"), at_sender=True)
103 |
104 |
105 | @msg_forward_name.handle()
106 | async def _(bot: Bot, event: Event):
107 | if event.get_user_id() in super_user:
108 | forward_name = re.sub(r"^涩图转发者名字", '', event.get_plaintext())
109 | Config.set_file_args('FORWARD_NAME', forward_name)
110 | await bot.send(message=f"修改涩图转发者名字为{forward_name}成功", event=event, at_sender=True)
111 | else:
112 | await msg_forward_name.send("只有主人才有权限哦", at_sender=True)
113 |
114 |
115 | @downLoad.handle()
116 | async def _(event: Event):
117 | num = int(re.search(r"\d+", event.get_plaintext()).group())
118 | if event.get_user_id() in super_user:
119 | try:
120 | r18 = 1 if event.get_plaintext().find('色图') != -1 else 0
121 | await downLoad.send(f"开始下载...")
122 | await get_url(num=num, online_switch=0, r18=r18)
123 | await downLoad.send(f"下载涩图成功,图库中涩图数量{get_file_num('loliconImages')}", at_sender=True)
124 | except httpx.HTTPError as e:
125 | await downLoad.send(f"下载时出现异常{str(type(e))}", at_sender=True)
126 | except Exception as e:
127 | await downLoad.send(f"{e}", at_sender=True)
128 | else:
129 | await downLoad.send('只有主人才有权限哦', at_sender=True)
130 |
131 |
132 | @user_cd.handle()
133 | async def _(event: Event):
134 | msg = event.get_message()
135 | user_id = event.get_user_id()
136 | if user_id in super_user:
137 | user_id = msg[0].get('data')['qq']
138 | cd = int(event.get_plaintext().replace(' cd', ''))
139 | user = UserDao().get_user_cd(user_id)
140 | if user is None:
141 | UserDao().add_user_cd(user_id, UserDao.datetime_to_seconds(datetime.now()), cd)
142 | else:
143 | UserDao().update_user_cd(user_id, '', cd)
144 | await user_cd.send(f'设置用户{user_id}的cd成功,cd时间为{cd}s', at_sender=True)
145 | else:
146 | await user_cd.send('只有主人才有权限哦', at_sender=True)
147 |
148 |
149 | @group_cd.handle()
150 | async def _(bot: Bot, event: Event):
151 | user_id = event.get_user_id()
152 | if user_id in super_user:
153 | cd = int(event.get_plaintext().replace('群cd', ''))
154 | if not hasattr(event, 'group_id'):
155 | await group_cd.send('请在群里使用', at_sender=True)
156 | group_id = GroupDao().get_group_cd(event.group_id)
157 | if group_id is None:
158 | GroupDao().set_group_cd(event.group_id, cd)
159 | else:
160 | GroupDao().update_group_cd(event.group_id, cd)
161 |
162 | await group_cd.send(f'设置群{event.group_id}的cd成功,cd时间为{cd}s', at_sender=True)
163 | else:
164 | await group_cd.send('只有主人才有权限哦', at_sender=True)
165 |
166 |
167 | @online_switch.handle()
168 | async def _(bot: Bot, event: Event):
169 | msg = event.get_plaintext()
170 | switch = 1 if msg == "开启在线发图" else 0
171 | if event.get_user_id() in super_user:
172 | Config.set_file_args('ONLINE_SWITCH', switch)
173 | await online_switch.send(f'{msg}成功')
174 | else:
175 | await online_switch.send('只有主人才有权限哦', at_sender=True)
176 |
177 |
178 | @proxy_switch.handle()
179 | async def _(bot: Bot, event: Event):
180 | msg = event.get_plaintext()
181 | switch = 1 if msg == "开启魔法" else 0
182 | if event.get_user_id() in super_user:
183 | Config.set_file_args('PROXIES_SWITCH', switch)
184 | await proxy_switch.send(f'{msg}成功')
185 | else:
186 | await proxy_switch.send('只有主人才有权限哦', at_sender=True)
187 |
188 |
189 | @withdraw_interval.handle()
190 | async def _(bot: Bot, event: Event):
191 | msg = event.get_plaintext()
192 | interval = int(msg.replace('撤回间隔', ''))
193 | if event.get_user_id() in super_user:
194 | if interval > 120:
195 | await withdraw_interval.send('间隔不能超过120s', at_sender=True)
196 | else:
197 | if not hasattr(event, 'group_id'):
198 | await withdraw_interval.finish("请在群里使用此功能")
199 | group_id = event.group_id
200 | GroupDao().set_or_update_group_interval(group_id=group_id, interval=interval)
201 | await withdraw_interval.send(f'设置群{group_id}撤回间隔{interval}s成功')
202 | else:
203 | await withdraw_interval.send('只有主人才有权限哦', at_sender=True)
204 |
205 |
206 | @api.handle()
207 | async def _(event: Event):
208 | msg = event.get_plaintext()
209 | if msg == '涩图api':
210 | if ImageDao().get_api() is None:
211 | await api.send(f'请设置api地址(格式:http://服务器公网ip或域名:机器人端口)')
212 | else:
213 | await api.send(
214 | f'涩图api已开启,请访问\n{ImageDao().get_api()}/setu/docs\n{ImageDao().get_api()}/setu/redoc\n查看api文档')
215 | else:
216 | if event.get_user_id() in super_user:
217 | address = re.sub('^设置api地址', '', msg)
218 | ImageDao().set_or_update_api(address)
219 | await api.send(f"设置api地址{address}成功")
220 | else:
221 | await api.send("只有主人才有权限哦", at_sender=True)
222 |
223 |
224 | @r18_switch.handle()
225 | async def _(event: Event):
226 | msg = event.get_plaintext()
227 | if event.get_user_id() in super_user:
228 | if msg == "开启涩涩" or msg == "关闭涩涩":
229 | if not hasattr(event, 'group_id'):
230 | await r18_switch.finish('私聊请使用开启/关闭私聊涩涩')
231 | GroupDao().set_or_update_group_r18(event.group_id, 1 if msg == "开启涩涩" else 0)
232 | await r18_switch.finish(f"群{event.group_id}{msg}成功")
233 | else:
234 | UserDao().set_or_update_r18(1 if msg == "开启私聊涩涩" else 0)
235 | await r18_switch.finish(f"{msg}成功")
236 | else:
237 | await r18_switch.finish('只有主人才有权限哦', at_sender=True)
238 |
239 | @setu_help.handle()
240 | async def _():
241 | import pkg_resources
242 | try:
243 | _dist: pkg_resources.Distribution = pkg_resources.get_distribution("nonebot_plugin_setu")
244 | _help = f'涩图插件版本:{_dist.version}\n' \
245 | '主人专用:\n' \
246 | '1、下载涩图:下载涩图(非r18)+数量,下载色图(r18)+数量,例如:下载涩图20,下载色图333\n' \
247 | '2、指定用户cd:@用户cd+时间(秒),例如:@张三cd123\n' + \
248 | '3、指定群cd:群cd+时间(秒),例如群cd123\n' \
249 | '4、指定图片是否存储:开启/关闭在线发图\n' \
250 | '5、指定获取图片是否使用代理:开启/关闭魔法\n' \
251 | '6、指定撤回间隔:撤回间隔+时间(秒),例如:撤回间隔123,撤回间隔为0时将不进行撤回\n' \
252 | '7、指定api地址:设置api地址+地址,例如:设置api地址123.456.789.111:8080\n' \
253 | '8、获取api地址:涩图api\n' \
254 | '9、开启/关闭涩涩:开启/关闭涩涩,开启/关闭私聊涩涩。用于指定是否开启r18\n' \
255 | '10、修改涩图转发者名字:涩图转发者名字+你要修改的名字,例如:涩图转发者名字bot\n' \
256 | '全员可用功能:\n' \
257 | '1、发送涩图:涩图、setu、无内鬼、色图' \
258 | '2、指定tag:涩图tagA(和B和C),最多指定三个tag'
259 | await setu_help.send(_help, at_sender=True)
260 | except Exception as e:
261 | logger.error(e)
262 | await setu_help.send(f'出错了,错误信息{e}', at_sender=True)
--------------------------------------------------------------------------------
/nonebot_plugin_setu/dao/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayanamiblhx/nonebot_plugin_setu/bd7165bf407f4574e1c172f1a3fa8be501b930f9/nonebot_plugin_setu/dao/__init__.py
--------------------------------------------------------------------------------
/nonebot_plugin_setu/dao/group_dao.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 |
3 |
4 | class GroupDao:
5 | def __init__(self):
6 | self.db_path = 'data/lolicon.db'
7 | self.conn = sqlite3.connect(self.db_path)
8 | self.cursor = self.conn.cursor()
9 | self.cursor.execute(
10 | 'create table if not exists group_cd(group_id text primary key, cd integer)')
11 | self.cursor.execute(
12 | 'create table if not exists group_interval(group_id text primary key, group_interval integer default 0)')
13 | self.cursor.execute(
14 | 'create table if not exists group_r18(group_id text primary key, r18 integer default 0)')
15 | self.conn.commit()
16 | self.conn.close()
17 |
18 | def get_group_cd(self, group_id):
19 | conn = sqlite3.connect(self.db_path)
20 | cursor = conn.cursor()
21 | cursor.execute(
22 | 'SELECT cd FROM group_cd WHERE group_id = ?',
23 | (group_id,)
24 | )
25 | result = cursor.fetchone()
26 | conn.close()
27 | return result[0] if result else None
28 |
29 | def set_group_cd(self, group_id, cd):
30 | conn = sqlite3.connect(self.db_path)
31 | cursor = conn.cursor()
32 | cursor.execute(
33 | 'INSERT OR IGNORE INTO group_cd (group_id, cd) VALUES (?, ?)',
34 | (group_id, cd)
35 | )
36 | conn.commit()
37 | conn.close()
38 |
39 | def update_group_cd(self, group_id, cd):
40 | conn = sqlite3.connect(self.db_path)
41 | cursor = conn.cursor()
42 | cursor.execute(
43 | 'UPDATE group_cd SET cd = ? WHERE group_id = ?',
44 | (cd, group_id)
45 | )
46 | conn.commit()
47 | conn.close()
48 |
49 | def get_group_interval(self, group_id):
50 | conn = sqlite3.connect(self.db_path)
51 | cursor = conn.cursor()
52 | cursor.execute(
53 | 'SELECT group_interval FROM group_interval WHERE group_id = ?',
54 | (group_id,)
55 | )
56 | result = cursor.fetchone()
57 | conn.close()
58 | return result[0] if result else None
59 |
60 | def set_or_update_group_interval(self, group_id, interval):
61 | conn = sqlite3.connect(self.db_path)
62 | cursor = conn.cursor()
63 | cursor.execute(
64 | 'INSERT OR IGNORE INTO group_interval (group_id, group_interval) VALUES (?, ?)',
65 | (group_id, interval)
66 | )
67 | cursor.execute(
68 | 'UPDATE group_interval SET group_interval = ? WHERE group_id = ?',
69 | (interval, group_id)
70 | )
71 | conn.commit()
72 | conn.close()
73 |
74 | def get_group_r18(self, group_id):
75 | conn = sqlite3.connect(self.db_path)
76 | cursor = conn.cursor()
77 | cursor.execute(
78 | 'SELECT r18 FROM group_r18 WHERE group_id = ?',
79 | (group_id,)
80 | )
81 | result = cursor.fetchone()
82 | conn.close()
83 | return result[0] if result else 0
84 |
85 | def set_or_update_group_r18(self, group_id, r18: int = 0):
86 | conn = sqlite3.connect(self.db_path)
87 | cursor = conn.cursor()
88 | cursor.execute(
89 | 'INSERT OR IGNORE INTO group_r18 (group_id, r18) VALUES (?, ?)',
90 | (group_id, r18)
91 | )
92 | cursor.execute(
93 | 'UPDATE group_r18 SET r18 = ? WHERE group_id = ?',
94 | (r18, group_id)
95 | )
96 | conn.commit()
97 | conn.close()
98 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/dao/image_dao.py:
--------------------------------------------------------------------------------
1 | import json
2 | import sqlite3
3 | from typing import List
4 |
5 |
6 | class ImageDao:
7 | def __init__(self):
8 | self.db_path = 'data/lolicon.db'
9 | self.conn = sqlite3.connect(self.db_path)
10 | self.cursor = self.conn.cursor()
11 | self.cursor.execute(
12 | 'create table if not exists lolicon_images(pid text primary key, uid text, title text,'
13 | ' author text, r18 text ,width integer,height integer,ext text,urls text,upload_date text)')
14 | self.cursor.execute(
15 | 'create table if not exists lolicon_tags('
16 | 'id integer primary key AUTOINCREMENT, pid text, tags text,unique (pid,tags))')
17 | self.cursor.execute(
18 | 'create table if not exists lolicon_api(id integer primary key AUTOINCREMENT, address text)')
19 | self.conn.commit()
20 | self.conn.close()
21 |
22 | def add_images(self, datas):
23 | conn = sqlite3.connect(self.db_path)
24 | cursor = conn.cursor()
25 | for data in datas:
26 | pid = data['pid']
27 | uid = data['uid']
28 | title = data['title']
29 | author = data['author']
30 | r18 = data['r18']
31 | width = data['width']
32 | height = data['height']
33 | ext = data['ext']
34 | urls = json.dumps(data['urls'])
35 | upload_date = data['uploadDate']
36 | sql = "INSERT OR IGNORE INTO lolicon_images(pid, uid, title, author, r18, width, height, ext, urls, upload_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
37 | cursor.execute(sql, (pid, uid, title, author, r18, width, height, ext, urls, upload_date))
38 | tags = data['tags']
39 | for tag in tags:
40 | sql = "INSERT OR IGNORE INTO lolicon_tags(pid, tags) VALUES (?, ?)"
41 | cursor.execute(sql, (pid, tag))
42 | conn.commit()
43 | conn.close()
44 |
45 | def get_images_by_pid(self, pid):
46 | conn = sqlite3.connect(self.db_path)
47 | cursor = conn.cursor()
48 | sql = f"select * from lolicon_images where pid='{pid}'"
49 | data = self.processing_data(conn, cursor, sql)
50 | return data
51 |
52 | def get_images_by_tags(self, tags: List[str], r18: bool = False, num: int = 1, **kwargs):
53 | conn = sqlite3.connect(self.db_path)
54 | cursor = conn.cursor()
55 | sql = "select * from lolicon_images where pid in (select pid from lolicon_tags"
56 | sql += f" where tags like '%{tags[0]}%'"
57 | for tag in tags[1:]:
58 | sql += f" or tags like '%{tag}%'"
59 | sql += f") group by pid having r18={r18} order by random() limit {num}"
60 | img_data = self.processing_data(conn, cursor, sql)
61 | return img_data
62 |
63 | @staticmethod
64 | def processing_data(conn, cursor, sql):
65 | cursor.execute(sql)
66 | img_infos = cursor.fetchall()
67 | img_description = cursor.description
68 | img_datas = {"data": []}
69 | for img_info in img_infos:
70 | data = {}
71 | for idx, col in enumerate(img_description):
72 | if col[0] == 'urls':
73 | data[col[0]] = json.loads(img_info[idx])
74 | elif col[0] == 'r18':
75 | data[col[0]] = img_info[idx] == '1'
76 | else:
77 | data[col[0]] = img_info[idx]
78 | tags = []
79 | sql = f"select tags from lolicon_tags where pid='{data['pid']}'"
80 | tag_infos = cursor.execute(sql).fetchall()
81 | for tag_info in tag_infos:
82 | tags.append(tag_info[0])
83 | data['tags'] = tags
84 | img_datas['data'].append(data)
85 | conn.close()
86 | return img_datas
87 |
88 | def get_top_tags(self):
89 | conn = sqlite3.connect(self.db_path)
90 | cursor = conn.cursor()
91 | sql = "select tags,count(*) as count from lolicon_tags group by tags order by count desc limit 10"
92 | cursor.execute(sql)
93 | top_tags = cursor.fetchall()
94 | data = []
95 | for tag in top_tags:
96 | data.append({'tags': tag[0], 'count': tag[1]})
97 | conn.close()
98 | return data
99 |
100 | def get_random_images(self, num: int, r18: bool = False):
101 | conn = sqlite3.connect(self.db_path)
102 | cursor = conn.cursor()
103 | sql = f"select * from lolicon_images where r18={r18} order by random() limit {num}"
104 | data = self.processing_data(conn, cursor, sql)
105 | return data
106 |
107 | def get_images_by_uid(self, uid, num: int = 1, r18: bool = False, **kwargs):
108 | conn = sqlite3.connect(self.db_path)
109 | cursor = conn.cursor()
110 | sql = f"select * from lolicon_images where uid='{uid}' and r18={r18} order by random() limit {num}"
111 | data = self.processing_data(conn, cursor, sql)
112 | return data
113 |
114 | def get_api(self):
115 | conn = sqlite3.connect(self.db_path)
116 | cursor = conn.cursor()
117 | sql = "select address from lolicon_api where id=1"
118 | cursor.execute(sql)
119 | data = cursor.fetchone()
120 | conn.close()
121 | return data[0] if data else None
122 |
123 | def set_or_update_api(self, api_address: str):
124 | conn = sqlite3.connect(self.db_path)
125 | cursor = conn.cursor()
126 | sql_insert = "INSERT OR IGNORE INTO lolicon_api VALUES (?, ?)"
127 | cursor.execute(sql_insert, (1, api_address))
128 | sql_update = "UPDATE lolicon_api SET address=? WHERE id=1"
129 | cursor.execute(sql_update, (api_address,))
130 | conn.commit()
131 | conn.close()
132 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/dao/user_dao.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import time
3 | from datetime import datetime
4 | from .group_dao import GroupDao
5 | from nonebot.adapters.onebot.v11 import Event
6 |
7 | DEFAULT_CD = 3600
8 |
9 |
10 | class UserDao:
11 | def __init__(self):
12 | self.db_path = 'data/lolicon.db'
13 | self.conn = sqlite3.connect(self.db_path)
14 | self.cursor = self.conn.cursor()
15 | self.cursor.execute(
16 | 'create table if not exists user_cd(user_id text primary key, last_time integer ,cd integer)')
17 | self.cursor.execute(
18 | 'create table if not exists r18_private_chat(id integer primary key AUTOINCREMENT, r18 integer default 0)')
19 | self.conn.commit()
20 | self.conn.close()
21 | pass
22 |
23 | @staticmethod
24 | def datetime_to_seconds(time_obj):
25 | return int(time_obj.timestamp())
26 |
27 | def get_user_cd(self, user_id):
28 | conn = sqlite3.connect(self.db_path)
29 | cursor = conn.cursor()
30 | cursor.execute(f"SELECT * FROM user_cd WHERE user_id = '{user_id}'")
31 | user = cursor.fetchone()
32 | if user is None:
33 | return None
34 | data = {}
35 | for idx, val in enumerate(cursor.description):
36 | data[val[0]] = user[idx]
37 | conn.close()
38 | return data
39 | pass
40 |
41 | def add_user_cd(self, user_id, last_time, cd):
42 | conn = sqlite3.connect(self.db_path)
43 | cursor = conn.cursor()
44 | cursor.execute("INSERT OR IGNORE INTO user_cd VALUES (?, ?, ?)", (user_id, last_time, cd))
45 | conn.commit()
46 | conn.close()
47 | pass
48 |
49 | def get_user_remain_time(self, event: Event):
50 | user_id = event.get_user_id()
51 | user_cd = self.get_user_cd(user_id)
52 | this_time = self.datetime_to_seconds(datetime.now())
53 | if not hasattr(event, 'group_id'):
54 | if user_cd is None:
55 | self.add_user_cd(user_id, this_time, -1)
56 | return 0
57 | else:
58 | cd = DEFAULT_CD if user_cd['cd'] == -1 else user_cd['cd']
59 | if this_time - user_cd['last_time'] < cd:
60 | return cd - (this_time - user_cd['last_time'])
61 | else:
62 | self.update_user_cd(user_id, this_time, user_cd['cd'])
63 | return 0
64 | else:
65 | group_id = event.group_id
66 | if user_cd is None:
67 | cd = GroupDao().get_group_cd(group_id)
68 | if cd is None:
69 | cd = DEFAULT_CD
70 | GroupDao().set_group_cd(group_id, cd)
71 | self.add_user_cd(user_id, this_time, -1)
72 | return 0
73 | cd = GroupDao().get_group_cd(group_id) if user_cd['cd'] == -1 else user_cd['cd']
74 | last_time = user_cd['last_time']
75 | time_diff = this_time - last_time
76 | if time_diff >= cd:
77 | self.update_user_cd(user_id, this_time, '')
78 | return 0
79 | else:
80 | return cd - time_diff
81 | pass
82 |
83 | def update_user_cd(self, user_id, last_time, cd):
84 | conn = sqlite3.connect(self.db_path)
85 | cursor = conn.cursor()
86 | if cd == '':
87 | cursor.execute(f"UPDATE user_cd SET last_time = '{last_time}' WHERE user_id = '{user_id}'")
88 | elif last_time == '':
89 | cursor.execute(f"UPDATE user_cd SET cd = '{cd}' WHERE user_id = '{user_id}'")
90 | else:
91 | cursor.execute(f"UPDATE user_cd SET last_time = '{last_time}', cd = '{cd}' WHERE user_id = '{user_id}'")
92 | conn.commit()
93 | conn.close()
94 | pass
95 |
96 | def delete_user_cd(self, user_id):
97 | conn = sqlite3.connect(self.db_path)
98 | cursor = conn.cursor()
99 | cursor.execute(f"UPDATE user_cd SET last_time = '0' WHERE user_id = '{user_id}'")
100 | conn.commit()
101 | conn.close()
102 | pass
103 |
104 | def get_r18_private_chat(self):
105 | conn = sqlite3.connect(self.db_path)
106 | cursor = conn.cursor()
107 | cursor.execute("SELECT * FROM r18_private_chat WHERE id = 1")
108 | r18 = cursor.fetchone()
109 | if r18 is None:
110 | return 0
111 | conn.close()
112 | return r18[1]
113 |
114 | def set_or_update_r18(self, r18: int = 0):
115 | conn = sqlite3.connect(self.db_path)
116 | cursor = conn.cursor()
117 | cursor.execute("INSERT OR IGNORE INTO r18_private_chat VALUES (?, ?)", (1, r18))
118 | cursor.execute("UPDATE r18_private_chat SET r18 = '{}' WHERE id = 1".format(r18))
119 | conn.commit()
120 | conn.close()
121 | pass
122 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/file_tools.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import sqlite3
4 | import pathlib
5 |
6 | setu_config = {
7 | 'SUPERUSERS': [""],
8 | 'PROXIES_HTTP': '',
9 | 'PROXIES_SOCKS': '',
10 | 'PROXIES_SWITCH': 0,
11 | 'ONLINE_SWITCH': 1,
12 | }
13 |
14 |
15 | class Config:
16 | def __init__(self):
17 | self.create_file()
18 | with open('data/setu_config.json', 'r', encoding='utf-8') as file:
19 | self.config = json.load(file)
20 | self.super_users = self.config['SUPERUSERS']
21 | self.proxies_http = self.config['PROXIES_HTTP']
22 | self.proxies_socks = self.config['PROXIES_SOCKS']
23 | self.proxies_switch = self.config['PROXIES_SWITCH']
24 | self.online_switch = self.config['ONLINE_SWITCH']
25 |
26 | @staticmethod
27 | def create_file():
28 | pathlib.Path('data').mkdir(parents=True, exist_ok=True)
29 | pathlib.Path('loliconImages/r18').mkdir(parents=True, exist_ok=True)
30 | if not os.path.exists('data/setu_config.json'):
31 | with open('data/setu_config.json', 'w', encoding='utf-8') as f:
32 | json.dump(setu_config, f, indent=4)
33 | if not os.path.exists('data/lolicon.db'):
34 | conn = sqlite3.connect('data/lolicon.db')
35 | conn.close()
36 |
37 | @staticmethod
38 | def get_file_args(args_name: str):
39 | with open('data/setu_config.json', 'r', encoding='utf-8') as file:
40 | content = json.load(file)
41 | if args_name not in content:
42 | content.update({args_name: ' '})
43 | with open('data/setu_config.json', 'w', encoding='utf-8') as file_new:
44 | json.dump(content, file_new, indent=4)
45 | return content[args_name]
46 |
47 | @staticmethod
48 | def set_file_args(args_name: str, args_value: str):
49 | with open('data/setu_config.json', 'r', encoding='utf-8') as file:
50 | setu_content = json.load(file)
51 | setu_content.update({args_name: args_value})
52 | with open('data/setu_config.json', 'w', encoding='utf-8') as file_new:
53 | json.dump(setu_content, file_new, indent=4)
54 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/getPic.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import json
3 | from io import BytesIO
4 |
5 | from httpx import AsyncClient, TimeoutException, HTTPError
6 | from nonebot.log import logger
7 | from tqdm import tqdm
8 |
9 | from .file_tools import Config
10 | from .dao.image_dao import ImageDao
11 | from .proxies import proxy_http, proxy_socks
12 |
13 |
14 | async def get_url(num: int, online_switch: int, tags: list = "", r18: int = 0):
15 | head = {
16 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
17 | "Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.36",
18 | }
19 | params = {
20 | "r18": r18,
21 | "size": 'regular',
22 | "tag": tags,
23 | }
24 | async with AsyncClient(proxies=None) as client:
25 | times = int(num / 20) + 1
26 | remain = num % 20
27 | datas = []
28 | i = 0
29 | while i < times:
30 | i += 1
31 | num = 20 if i != times else remain
32 | if num == 0:
33 | break
34 | req_url = f"https://api.lolicon.app/setu/v2?num={num}"
35 | flag = 0
36 | while True:
37 | try:
38 | flag += 1
39 | if flag > 10:
40 | raise Exception(f"获取api内容失败次数过多,请检查网络链接")
41 | res = await client.get(req_url, params=params, headers=head, timeout=10)
42 | if res.status_code == 200:
43 | break
44 | except TimeoutException as e:
45 | logger.error(f"获取loliconAPI内容超时{type(e)}")
46 | except HTTPError as e:
47 | logger.error(f"{type(e)}")
48 | raise e
49 | except Exception as e:
50 | logger.error(f"{e}")
51 | raise e
52 | res = json.loads(res.text)
53 | data = res['data']
54 | if not data:
55 | return ""
56 | datas.extend(data)
57 | ImageDao().add_images(datas)
58 | img = await down_pic(datas, online_switch, r18)
59 | return img
60 |
61 |
62 | async def down_pic(datas, online_switch: int, r18: int = 0):
63 | head = {
64 | 'referer': 'https://www.pixiv.net/',
65 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.36"
66 | }
67 | http = proxy_http if Config().proxies_switch else None
68 | socks = proxy_socks if Config().proxies_switch else None
69 | async with AsyncClient(proxies=http, transport=socks) as client:
70 | pbar = tqdm(datas, desc='Downloading', colour='green')
71 | tag_img = ""
72 | for data in datas:
73 | proxy_url = data['urls']['regular'].replace('i.pixiv.re', 'i.pximg.net')
74 | url = data['urls']['regular']
75 | url = proxy_url if Config().proxies_switch else url
76 | pid = data['pid']
77 | ext = data['ext']
78 | tag_img = str(pid) + "." + ext
79 | flag = 0
80 | while True:
81 | try:
82 | flag += 1
83 | if flag > 10:
84 | raise Exception(f"获取图片内容失败次数过多,请检查网络链接")
85 | response = await client.get(url=url, headers=head, timeout=10)
86 | if response.status_code == 200:
87 | break
88 | except TimeoutException as e:
89 | logger.error(f"获取图片内容超时: {type(e)}")
90 | except HTTPError as e:
91 | logger.error(f"{type(e)}")
92 | raise e
93 | except Exception as e:
94 | logger.error(f"{e}")
95 | raise e
96 | pbar.update(1)
97 | if online_switch == 1:
98 | img_info = {'pid': pid,
99 | 'base64': f"base64://{base64.b64encode(BytesIO(response.content).getvalue()).decode()}"}
100 | return img_info
101 | img_path = f"loliconImages/{'r18/' if r18 else ''}{pid}.{ext}"
102 | with open(img_path, 'wb') as f:
103 | f.write(response.content)
104 | pbar.close()
105 | return tag_img
106 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/proxies.py:
--------------------------------------------------------------------------------
1 | from httpx_socks import AsyncProxyTransport
2 | from nonebot.log import logger
3 |
4 | from .file_tools import Config
5 |
6 | proxy_config = Config()
7 |
8 | socks = proxy_config.get_file_args('PROXIES_SOCKS')
9 | http = proxy_config.get_file_args('PROXIES_HTTP')
10 | if socks != '':
11 | logger.info('已配置socks代理,http代理将被忽略')
12 | proxy_config.set_file_args('PROXIES_SWITCH', 1)
13 | proxy_socks = AsyncProxyTransport.from_url(socks)
14 | proxy_http = None
15 | elif http != '':
16 | logger.info('已配置http代理,socks代理将被忽略')
17 | proxy_config.set_file_args('PROXIES_SWITCH', 1)
18 | proxy_socks = None
19 | proxy_http = http
20 | else:
21 | logger.info('未配置代理')
22 | proxy_config.set_file_args('PROXIES_SWITCH', 0)
23 | proxy_socks = None
24 | proxy_http = None
25 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/setu_api.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 |
3 | import pkg_resources
4 | from fastapi import FastAPI, Query, Body
5 | from pydantic import BaseModel, Field
6 |
7 | from .dao.image_dao import ImageDao
8 |
9 | setu_api = FastAPI(
10 | title="涩图Api",
11 | version=pkg_resources.get_distribution("nonebot_plugin_setu").version,
12 | contact={
13 | "name": "nonebot_plugin_setu",
14 | "url": "https://github.com/ayanamiblhx/nonebot_plugin_setu"
15 | }
16 | )
17 |
18 |
19 | class SetuItem(BaseModel):
20 | pid: str = Field(None, max_length=20, description="pid", example="")
21 | uid: str = Field(None, max_length=20, description="uid", example="")
22 | num: Optional[int] = Field(1, ge=1, le=100, description="图片数量,默认为1,最大为100")
23 | proxy: bool = Field(False, description="是否使用代理,默认不使用")
24 | tags: Optional[List[str]] = Field(None, description="标签,最多3个", max_items=3, example=["碧蓝航线", "碧蓝幻想"])
25 | r18: bool = False
26 |
27 |
28 | @setu_api.get("/v1", tags=["获取涩图"], description="GET请求")
29 | async def get_img(pid: str = Query(None, max_length=20, description="pid"),
30 | uid: str = Query(None, max_length=20, description="uid"),
31 | num: Optional[int] = Query(1, ge=1, le=100, description="图片数量,默认为1,最大为100"),
32 | tags: Optional[List[str]] = Query(None, max_items=3, description="标签,最多3个"),
33 | proxy: bool = Query(False, description="是否使用代理地址,默认不使用"),
34 | r18: bool = False):
35 | if pid is not None and pid != "":
36 | datas = ImageDao().get_images_by_pid(pid=pid)
37 | elif uid is not None and uid != "":
38 | datas = ImageDao().get_images_by_uid(uid=uid, num=num, r18=r18)
39 | elif tags is not None and tags.__len__() > 0:
40 | datas = ImageDao().get_images_by_tags(tags=tags, r18=r18, num=num)
41 | else:
42 | datas = ImageDao().get_random_images(num=num, r18=r18)
43 | if proxy:
44 | for data in datas['data']:
45 | data['urls']['regular'] = data["urls"]['regular'].replace('i.pixiv.cat', 'i.pixiv.re')
46 | else:
47 | for data in datas['data']:
48 | data["urls"]['regular'] = data["urls"]['regular'].replace('i.pixiv.cat', 'i.pximg.net')
49 | return datas
50 |
51 |
52 | @setu_api.post("/v1", tags=["获取涩图"], description="POST请求")
53 | async def post_img(setu_item: SetuItem = Body(...)):
54 | if setu_item.pid is not None and setu_item.pid != "":
55 | datas = ImageDao().get_images_by_pid(pid=setu_item.pid)
56 | elif setu_item.uid is not None and setu_item.uid != "":
57 | datas = ImageDao().get_images_by_uid(**setu_item.dict())
58 | elif setu_item.tags is not None and setu_item.tags.__len__() > 0:
59 | datas = ImageDao().get_images_by_tags(**setu_item.dict())
60 | else:
61 | datas = ImageDao().get_random_images(num=setu_item.num, r18=setu_item.r18)
62 | if setu_item.proxy:
63 | for data in datas['data']:
64 | data['urls']['regular'] = data["urls"]['regular'].replace('i.pixiv.cat', 'i.pixiv.re')
65 | else:
66 | for data in datas['data']:
67 | data["urls"]['regular'] = data["urls"]['regular'].replace('i.pixiv.cat', 'i.pximg.net')
68 | return datas
69 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import List
3 |
4 | from nonebot.adapters.onebot.v11 import Bot, Message, Event
5 |
6 | from .file_tools import Config
7 |
8 |
9 | async def send_forward_msg(
10 | bot: Bot,
11 | event: Event,
12 | name: str,
13 | uin: str,
14 | msgs: List,
15 | is_group_chat: bool,
16 | ):
17 | def to_json(msg: Message):
18 | return {"type": "node", "data": {"name": name, "uin": uin, "content": msg}}
19 | messages = [to_json(msg) for msg in msgs]
20 | forward_api = 'send_private_forward_msg' if not is_group_chat else 'send_group_forward_msg'
21 | if is_group_chat:
22 | return await bot.call_api(forward_api, group_id=event.group_id, messages=messages)
23 | else:
24 | return await bot.call_api(forward_api, user_id=event.get_user_id(), messages=messages)
25 |
26 |
27 | def get_file_num(path):
28 | file_num = 0
29 | for root, dirs, files in os.walk(path):
30 | file_num += len(files)
31 | return file_num
32 |
33 |
34 | def img_num_detect(r18: int):
35 | if os.listdir('loliconImages').__len__() == 1 and not Config().online_switch:
36 | return 0
37 | elif os.listdir('loliconImages/r18').__len__() == 0 \
38 | and r18 == 1 and not Config().online_switch:
39 | return 0
40 | else:
41 | return 1
42 |
--------------------------------------------------------------------------------
/nonebot_plugin_setu/withdraw.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from nonebot.adapters.onebot.v11 import Bot
3 | from nonebot.log import logger
4 |
5 |
6 | async def add_withdraw_job(bot: Bot, message_id: int, withdraw_interval: int = 0, **kwargs):
7 | if withdraw_interval:
8 | tasks = []
9 | logger.info(f'添加撤回定时任务,撤回间隔:{withdraw_interval}秒')
10 | tasks.append(withdraw_msg(bot, message_id))
11 | await asyncio.sleep(withdraw_interval)
12 | asyncio.gather(*tasks).add_done_callback(lambda x: logger.info(f'撤回任务已完成,撤回消息id:{message_id}'))
13 |
14 |
15 | async def withdraw_msg(bot: Bot, message_id: int):
16 | await bot.delete_msg(message_id=message_id)
17 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "nonebot_plugin_setu"
3 | version = "1.1.11"
4 | description = "基于lolicon api的涩图插件"
5 | authors = ["ayanamiblhx <1196818079@qq.com>"]
6 | license = "Apache License 2.0"
7 | homepage = "https://github.com/ayanamiblhx/nonebot_plugin_setu"
8 | documentation = "https://github.com/ayanamiblhx/nonebot_plugin_setu/blob/main/README.md"
9 | readme="README.md"
10 |
11 | [tool.poetry.dependencies]
12 | python = "^3.8"
13 | httpx = "^0.23.0"
14 | httpx-socks = "^0.7.3"
15 | nonebot2 = "^2.0.0b2"
16 | nonebot-adapter-onebot = "^2.0.0b1"
17 | tqdm = "^4.61.0"
18 |
19 | [tool.poetry.dev-dependencies]
20 |
21 | [build-system]
22 | requires = ["poetry-core>=1.0.0"]
23 | build-backend = "poetry.core.masonry.api"
24 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi==0.75.0
2 | httpx==0.21.3
3 | httpx_socks==0.7.3
4 | nonebot==1.8.2
5 | pydantic==1.9.0
6 | tqdm==4.61.0
7 |
--------------------------------------------------------------------------------