nonebot-plugin-bittorrents 1.1.0__py3-none-any.whl

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.
@@ -0,0 +1,13 @@
1
+ from nonebot.plugin import PluginMetadata
2
+ from .main import Config
3
+
4
+ __plugin_meta__ = PluginMetadata(
5
+ name="BitTorrent磁力搜索",
6
+ description="适配NoneBot的磁力搜索插件,通过机器人帮你寻找电影、软件或者学习资料",
7
+ usage="bt (排序方式) [关键词]",
8
+ type="application",
9
+ homepage="https://github.com/NightDust981989/nonebot-plugin-BitTorrents",
10
+ config=Config,
11
+ )
12
+
13
+ from . import main
@@ -0,0 +1,289 @@
1
+ import re
2
+ import base64
3
+ import urllib.parse
4
+ from typing import List, Dict
5
+ from dataclasses import dataclass
6
+
7
+ import httpx
8
+ from bs4 import BeautifulSoup
9
+ from nonebot import on_command
10
+ from nonebot.adapters import Message
11
+ from nonebot.params import CommandArg
12
+
13
+ try:
14
+ from pydantic import BaseSettings
15
+ except ImportError:
16
+ from pydantic_settings import BaseSettings
17
+
18
+ # ========== 1. 配置映射类(从配置文件读取参数) ==========
19
+ @dataclass
20
+ class MagnetConfig:
21
+ """磁力搜索配置类"""
22
+ base_url: str = "https://clg2.clgapp1.xyz" # 站点基础地址
23
+ search_path: str = "/cllj.php" # 搜索接口路径
24
+ max_results: int = 3 # 最大返回结果数
25
+ request_timeout: int = 15 # 请求超时时间(秒)
26
+ captcha_cookies: Dict[str, str] = None # 验证Cookie(固定值)
27
+
28
+ def __post_init__(self):
29
+ # 初始化固定验证Cookie
30
+ self.captcha_cookies = {
31
+ "sssfwz2": "qwsdsddsdsdse",
32
+ "aywcUid": "lwgkvwDiYQ_20211009155217"
33
+ }
34
+ # 处理base_url结尾的/(统一格式:不带结尾/)
35
+ if self.base_url.endswith("/"):
36
+ self.base_url = self.base_url.rstrip("/")
37
+ # 处理search_path开头的/(统一格式:带开头/)
38
+ if not self.search_path.startswith("/"):
39
+ self.search_path = f"/{self.search_path}"
40
+
41
+ # ========== 2. 核心工具类 ==========
42
+ class MagnetUtils:
43
+ @staticmethod
44
+ def decrypt_base64(encrypted_str: str) -> str:
45
+ """Base64解密"""
46
+ try:
47
+ encrypted_str = encrypted_str.ljust(len(encrypted_str) + (4 - len(encrypted_str) % 4) % 4, '=')
48
+ decoded = base64.b64decode(encrypted_str).decode('utf-8', errors='ignore')
49
+ return urllib.parse.unquote(decoded)
50
+ except Exception as e:
51
+ print(f"Base64解密失败:{str(e)}")
52
+ return ""
53
+
54
+ @staticmethod
55
+ def get_full_url(base_url: str, relative_url: str) -> str:
56
+ """拼接完整URL"""
57
+ if relative_url.startswith("http"):
58
+ return relative_url
59
+ if relative_url.startswith("./"):
60
+ return f"{base_url}/{relative_url[2:]}"
61
+ if relative_url.startswith("/"):
62
+ return f"{base_url}{relative_url}"
63
+ return f"{base_url}/{relative_url}"
64
+
65
+ @staticmethod
66
+ def get_sort_param(sort_keyword: str) -> str:
67
+ """
68
+ 排序关键词
69
+ """
70
+ sort_mapping = {
71
+ "相关度": "",
72
+ "大小": "length",
73
+ "文件大小": "length",
74
+ "热门": "hot",
75
+ "热门程度": "hot",
76
+ "时间": "time",
77
+ "最新": "time",
78
+ }
79
+ # 提高鲁棒性
80
+ sort_keyword = sort_keyword.strip().lower()
81
+ for key, value in sort_mapping.items():
82
+ if key.lower() == sort_keyword:
83
+ return value
84
+ # 匹配不到返回空
85
+ return ""
86
+
87
+ # ========== 3. 核心搜索服务 ==========
88
+ class MagnetSearchService:
89
+ def __init__(self, config: MagnetConfig):
90
+ self.config = config
91
+ self.client: httpx.AsyncClient = None
92
+
93
+ async def _init_client(self):
94
+ """初始化客户端:使用配置文件的超时时间"""
95
+ headers = {
96
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
97
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
98
+ "Accept-Language": "zh-CN,zh;q=0.9",
99
+ "Origin": self.config.base_url,
100
+ "Content-Type": "application/x-www-form-urlencoded",
101
+ "Referer": self.config.base_url
102
+ }
103
+
104
+ self.client = httpx.AsyncClient(
105
+ headers=headers,
106
+ cookies=self.config.captcha_cookies,
107
+ timeout=self.config.request_timeout, # 从配置读取超时时间
108
+ follow_redirects=False, # 关闭自动重定向
109
+ verify=False
110
+ )
111
+
112
+ async def search(self, keyword: str, sort_param: str = "") -> List[str]:
113
+ """搜索逻辑:使用配置文件的站点/接口/结果数"""
114
+ await self._init_client()
115
+ results = []
116
+
117
+ try:
118
+ # ========== 构造搜索URL ==========
119
+ search_url = f"{self.config.base_url}{self.config.search_path}?name={urllib.parse.quote(keyword)}"
120
+ # 拼接排序参数
121
+ if sort_param:
122
+ search_url += f"&sort={sort_param}"
123
+ print(f"GET请求:{search_url}")
124
+
125
+ # 发起请求
126
+ response = await self.client.get(search_url)
127
+ print(f"响应状态码:{response.status_code}")
128
+
129
+ # 提取原始响应
130
+ raw_html = response.text
131
+
132
+ # ========== 解密原始响应 ==========
133
+ encrypt_match = re.search(r"window\.atob\('([^']+)'", raw_html)
134
+ if not encrypt_match:
135
+ print(f"未找到window.atob加密串")
136
+ return []
137
+
138
+ decrypted_html = MagnetUtils.decrypt_base64(encrypt_match.group(1))
139
+
140
+
141
+ # ========== 提取xq.php链接(使用配置) ==========
142
+ soup = BeautifulSoup(decrypted_html, "lxml")
143
+ result_container = soup.find("ul", id="Search_list_wrapper")
144
+ if not result_container:
145
+ print(f"解密后仍无搜索结果容器")
146
+ return []
147
+
148
+ detail_links = []
149
+ processed_urls = set()
150
+ # 遍历结果:最多取配置的max_results条
151
+ for idx, li in enumerate(result_container.find_all("li")):
152
+ if idx >= self.config.max_results: # 从配置读取最大结果数
153
+ break
154
+ if li.find("ul", class_="pagination"):
155
+ continue
156
+
157
+ link_tag = li.find("a", href=re.compile(r"xq\.php\?key="))
158
+ if not link_tag:
159
+ continue
160
+
161
+ full_url = MagnetUtils.get_full_url(self.config.base_url, link_tag.get("href"))
162
+ if full_url in processed_urls:
163
+ continue
164
+ processed_urls.add(full_url)
165
+
166
+ # 提取基础信息
167
+ title = link_tag.text.strip() or f"搜索结果{idx+1}"
168
+ size = re.search(r"文件大小:([0-9.]+ [GMK]B)", li.text)
169
+ size = size.group(1).strip() if size else "未知大小"
170
+ create_time = re.search(r"创建时间:(\d{4}-\d{2}-\d{2})", li.text)
171
+ create_time = create_time.group(1).strip() if create_time else "未知时间"
172
+
173
+ detail_links.append({
174
+ "url": full_url,
175
+ "title": title,
176
+ "size": size,
177
+ "create_time": create_time
178
+ })
179
+
180
+ if not detail_links:
181
+ return []
182
+
183
+ # ========== 解析详情页 ==========
184
+ for link in detail_links:
185
+ try:
186
+ detail_resp = await self.client.get(link["url"], follow_redirects=False)
187
+ detail_raw = detail_resp.text
188
+
189
+ # 解密详情页
190
+ detail_encrypt = re.search(r"window\.atob\('([^']+)'", detail_raw)
191
+ detail_html = detail_raw
192
+ if detail_encrypt:
193
+ detail_html = MagnetUtils.decrypt_base64(detail_encrypt.group(1))
194
+
195
+ # 提取磁力链接 - 保持原始逻辑,使用全局soup对象
196
+ magnet_link = None
197
+ magnet_a = soup.find("a", href=re.compile(r"magnet:\?xt=urn:btih:"))
198
+ if magnet_a:
199
+ magnet_link = magnet_a.get("href").strip()
200
+ if not magnet_link:
201
+ magnet_match = re.search(r"magnet:\?xt=urn:btih:[a-fA-F0-9]{40,}[^\"']*", detail_html)
202
+ if magnet_match:
203
+ magnet_link = magnet_match.group().strip()
204
+
205
+ # 构造结果
206
+ results.append(
207
+ f"标题:{link['title']}\n"
208
+ f"磁力链接:{magnet_link or '未提取到'}\n"
209
+ f"文件大小:{link['size']}\n"
210
+ f"收录时间:{link['create_time']}"
211
+ )
212
+ except Exception as e:
213
+ results.append(f"标题:{link['title']}\n解析失败:{str(e)[:30]}\n文件大小:{link['size']}")
214
+
215
+ except Exception as e:
216
+ print(f"搜索异常:{str(e)}")
217
+ results = [f"搜索失败:{str(e)[:50]}"]
218
+ finally:
219
+ if self.client:
220
+ await self.client.aclose()
221
+
222
+ return results
223
+
224
+ # ========== 4. 初始化配置和服务 ==========
225
+ # 从 nonebot 配置中获取参数,如果不存在则使用默认值
226
+ from nonebot import get_driver
227
+
228
+ class Config(BaseSettings):
229
+ magnet_base_url: str = "https://clg2.clgapp1.xyz"
230
+ magnet_search_path: str = "/cllj.php"
231
+ magnet_max_results: int = 3
232
+ magnet_request_timeout: int = 15
233
+
234
+ class Config:
235
+ extra = "allow"
236
+
237
+ driver = get_driver()
238
+ global_config = driver.config
239
+ plugin_config = Config(**global_config.dict())
240
+
241
+ # 初始化配置类
242
+ magnet_config = MagnetConfig(
243
+ base_url=plugin_config.magnet_base_url,
244
+ search_path=plugin_config.magnet_search_path,
245
+ max_results=plugin_config.magnet_max_results,
246
+ request_timeout=plugin_config.magnet_request_timeout
247
+ )
248
+ search_service = MagnetSearchService(magnet_config)
249
+
250
+ print(f"磁力搜索插件初始化完成,使用站点:{magnet_config.base_url}{magnet_config.search_path}")
251
+
252
+ # ========== 5. 注册命令处理器 ==========
253
+ bt_cmd = on_command("bt", aliases=set(), priority=10, block=True)
254
+
255
+ @bt_cmd.handle()
256
+ async def handle_bt_command(args: Message = CommandArg()):
257
+ message = str(args).strip()
258
+ args_list = message.split()
259
+
260
+ # 检查参数是否足够
261
+ if not message or len(args_list) < 1:
262
+ await bt_cmd.finish("用法:bt (排序方式) [关键词]\n示例:bt 热门 安达与岛村")
263
+
264
+ # 解析排序参数和关键词
265
+ sort_keyword = ""
266
+ keyword = ""
267
+ if len(args_list) == 1:
268
+ # 默认相关度
269
+ keyword = args_list[0]
270
+ else:
271
+ # 有排序参数
272
+ sort_keyword = args_list[0]
273
+ keyword = " ".join(args_list[1:])
274
+
275
+ # 转换排序关键词为sort
276
+ sort_param = MagnetUtils.get_sort_param(sort_keyword)
277
+ # 执行搜索
278
+ results = await search_service.search(keyword, sort_param)
279
+
280
+ if not results:
281
+ # 无结果
282
+ await bt_cmd.finish("未找到相关磁力链接")
283
+ else:
284
+ # 有结果时拼接完整内容
285
+ result_text = f"共找到 {len(results)} 条有效结果:\n"
286
+ for idx, res in enumerate(results, 1):
287
+ result_text += f"\n===== 结果 {idx} =====\n{res}"
288
+
289
+ await bt_cmd.finish(result_text)
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: nonebot-plugin-bittorrents
3
+ Version: 1.1.0
4
+ Summary: 适配NoneBot的磁力搜索插件,通过机器人帮你寻找电影、软件或者学习资料
5
+ Author: NightDust981989
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/NightDust981989/nonebot-plugin-BitTorrents
8
+ Project-URL: repository, https://github.com/NightDust981989/nonebot-plugin-BitTorrents
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: httpx
12
+ Requires-Dist: beautifulsoup4
13
+ Requires-Dist: lxml
14
+ Requires-Dist: pydantic
15
+ Requires-Dist: pydantic-settings
16
+ Requires-Dist: nonebot2
17
+ Requires-Dist: nonebot-adapter-onebot
18
+ Dynamic: license-file
19
+
20
+ # NoneBot 磁力搜索插件
21
+ 适配磁力狗的磁力链接搜索插件,支持更新站点地址、返回结果数等配置,纯静态HTTP请求实现。
22
+
23
+ ## 核心特性
24
+ 1. 突破磁力狗重定向反爬:关闭自动重定向,提取原始搜索结果
25
+ 2. 配置完全自定义:支持修改站点地址、接口路径、超时时间等
26
+ 3. 自动解密:复刻站点window.atob+unescape加密逻辑
27
+ 4. 轻量无依赖:仅需基础HTTP/解析库,无需Playwright/Selenium
28
+
29
+ ## 环境要求
30
+ - NoneBot 插件运行环境
31
+ - 网络可访问目标磁力狗站点
32
+
33
+ ## 部署方法
34
+ 直接到插件商店安装即可
35
+
36
+ ## 配置文件修改
37
+ 在 .env 文件即可更改
38
+
39
+ ## 配置项说明
40
+ | 配置项 | 说明 | 默认值 |
41
+ |-----------------|----------------------------------------------------------------------|---------------------------------|
42
+ | base_url | 磁力狗站点基础地址,支持更换备用域名(如站点失效时)| https://clg2.clgapp1.xyz |
43
+ | search_path | 搜索接口路径,正常无需更改 | /cllj.php |
44
+ | max_results | 单次搜索返回的最大结果数,建议 3-5 条(过多会增加解密耗时)| 3 |
45
+ | request_timeout | HTTP 请求超时时间(秒),详情页解密需耗时,建议 15-20 秒 | 15 |
46
+
47
+ ## 使用方法
48
+ ### 发送指令:
49
+ bt (排序方式) [搜索关键词]
50
+ ### 示例:
51
+ bt 热门 安达与岛村
52
+
53
+ ## 说明
54
+ ### 排序方式
55
+ - 相关度:按相关度从高到低排序(默认)
56
+ - 热门/热门程度:按热门度从高到低排序
57
+ - 时间/最新:按时间从新到旧排序
58
+ - 大小/文件大小:按文件大小从大到小排序
59
+
60
+ 当原站点失效时,请访问 https://ciligoufabuye3.xyz 或发邮件至 clgdzzh@gmail.com 更新地址
61
+
62
+ ## 许可证
63
+ 本插件遵循和NoneBot相同的许可证
@@ -0,0 +1,7 @@
1
+ nonebot_plugin_BitTorrents/__init__.py,sha256=6_mXiShqWgEHL8arAgiJasJ2GuWoWliNlgkqsEfrkI8,455
2
+ nonebot_plugin_BitTorrents/main.py,sha256=3aR7YGALf6CekOKXfgcIT_SflMlD56tydyUXmfvlIRA,11379
3
+ nonebot_plugin_bittorrents-1.1.0.dist-info/licenses/LICENSE,sha256=p9pwUXEeHWD47I05Xq0xtlq25WAfRxhiQWYOh8i438s,1078
4
+ nonebot_plugin_bittorrents-1.1.0.dist-info/METADATA,sha256=KxKczqW3AbeV8fwOcP5NuK656PmxbQ_vpdA-l8oEmeE,2775
5
+ nonebot_plugin_bittorrents-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ nonebot_plugin_bittorrents-1.1.0.dist-info/top_level.txt,sha256=0W_CZpfc6iIWG9FR22AHqTTRLYDPDzzGmnDb7FwE0Ag,27
7
+ nonebot_plugin_bittorrents-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2020 NoneBot Team
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files (the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ nonebot_plugin_BitTorrents