ipclick 0.3.0__tar.gz → 0.4.0__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.

Potentially problematic release.


This version of ipclick might be problematic. Click here for more details.

Files changed (79) hide show
  1. ipclick-0.3.0/README.md → ipclick-0.4.0/PKG-INFO +150 -16
  2. ipclick-0.3.0/PKG-INFO → ipclick-0.4.0/README.md +104 -62
  3. {ipclick-0.3.0 → ipclick-0.4.0}/pyproject.toml +13 -9
  4. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/__init__.py +1 -1
  5. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/browser_engines.py +140 -43
  6. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/drission_adapter.py +38 -14
  7. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/niquests_adapter.py +62 -19
  8. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/registry.py +48 -15
  9. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cli/main.py +17 -2
  10. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/forwarder.py +137 -2
  11. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/node.py +9 -0
  12. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/pool.py +52 -0
  13. ipclick-0.4.0/src/ipclick/cluster/probe.py +242 -0
  14. ipclick-0.4.0/src/ipclick/components.py +239 -0
  15. ipclick-0.4.0/src/ipclick/config_loader/placeholders.py +81 -0
  16. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/configs/default_config.toml +16 -2
  17. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task.proto +29 -0
  18. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task_pb2.py +11 -7
  19. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task_pb2.pyi +22 -0
  20. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/task_pb2_grpc.py +46 -0
  21. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/secrets.py +34 -6
  22. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/server.py +97 -11
  23. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/services/task_service.py +122 -21
  24. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/trace.py +75 -0
  25. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/log_util.py +3 -4
  26. ipclick-0.4.0/src/ipclick/utils/module_probe.py +105 -0
  27. ipclick-0.4.0/src/ipclick/utils/path_util.py +89 -0
  28. ipclick-0.4.0/src/ipclick/web/assets.py +657 -0
  29. ipclick-0.4.0/src/ipclick/web/curl_parser.py +274 -0
  30. ipclick-0.4.0/src/ipclick/web/installer.py +469 -0
  31. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/pages.py +327 -39
  32. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/server.py +98 -18
  33. ipclick-0.4.0/src/ipclick/web/templates.py +1354 -0
  34. ipclick-0.3.0/src/ipclick/utils/path_util.py +0 -37
  35. ipclick-0.3.0/src/ipclick/web/templates.py +0 -966
  36. {ipclick-0.3.0 → ipclick-0.4.0}/LICENSE +0 -0
  37. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/__main__.py +0 -0
  38. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/__init__.py +0 -0
  39. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/base.py +0 -0
  40. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/browser_adapter.py +0 -0
  41. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/browser_settings.py +0 -0
  42. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/curl_cffi_adapter.py +0 -0
  43. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/adapters/settings.py +0 -0
  44. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/aio.py +0 -0
  45. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/auth.py +0 -0
  46. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cli/__init__.py +0 -0
  47. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/__init__.py +0 -0
  48. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/balancer.py +0 -0
  49. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/client.py +0 -0
  50. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/discovery.py +0 -0
  51. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/status_page.py +0 -0
  52. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/cluster/tokens.py +0 -0
  53. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/compression.py +0 -0
  54. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/__init__.py +0 -0
  55. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/dotenv.py +0 -0
  56. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/loader.py +0 -0
  57. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/config_loader/writer.py +0 -0
  58. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/configs/__init__.py +0 -0
  59. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/__init__.py +0 -0
  60. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/models.py +0 -0
  61. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/__init__.py +0 -0
  62. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/proto/generate.py +0 -0
  63. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/dto/response.py +0 -0
  64. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/exceptions.py +0 -0
  65. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/factory.py +0 -0
  66. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/health.py +0 -0
  67. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/limiter.py +0 -0
  68. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/py.typed +0 -0
  69. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/resume.py +0 -0
  70. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/sdk.py +0 -0
  71. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/services/__init__.py +0 -0
  72. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/tls.py +0 -0
  73. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/__init__.py +0 -0
  74. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/config_util.py +0 -0
  75. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/secure_util.py +0 -0
  76. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/utils/url_util.py +0 -0
  77. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/__init__.py +0 -0
  78. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/auth.py +0 -0
  79. {ipclick-0.3.0 → ipclick-0.4.0}/src/ipclick/web/editable.py +0 -0
@@ -1,3 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: ipclick
3
+ Version: 0.4.0
4
+ Summary: IPClick Tool
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: 元气码农少女酱
8
+ Author-email: 1276162429@qq.com
9
+ Requires-Python: >=3.11
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Provides-Extra: camoufox
17
+ Provides-Extra: drissionpage
18
+ Provides-Extra: linux
19
+ Provides-Extra: niquests
20
+ Provides-Extra: patchright
21
+ Provides-Extra: playwright
22
+ Provides-Extra: win
23
+ Requires-Dist: camoufox (>=0.5.4,<1.0.0) ; extra == "camoufox"
24
+ Requires-Dist: camoufox (>=0.5.4,<1.0.0) ; extra == "linux"
25
+ Requires-Dist: click (>=8.4.2,<9.0.0)
26
+ Requires-Dist: curl-cffi (>=0.16.0,<0.17.0)
27
+ Requires-Dist: drissionpage (>=4.1.1,<5.0.0) ; extra == "drissionpage"
28
+ Requires-Dist: drissionpage (>=4.1.1,<5.0.0) ; extra == "win"
29
+ Requires-Dist: fake-useragent (>=2.2.0,<3.0.0)
30
+ Requires-Dist: grpcio (>=1.83.0,<2.0.0)
31
+ Requires-Dist: grpcio-health-checking (>=1.83.0,<2.0.0)
32
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
33
+ Requires-Dist: niquests (>=3.21.0,<4.0.0) ; extra == "niquests"
34
+ Requires-Dist: patchright (>=1.61.2,<2.0.0) ; extra == "linux"
35
+ Requires-Dist: patchright (>=1.61.2,<2.0.0) ; extra == "patchright"
36
+ Requires-Dist: playwright (>=1.60.0,<2.0.0) ; extra == "playwright"
37
+ Requires-Dist: protobuf (>=6.33.2,<8.0.0)
38
+ Requires-Dist: python-box[all] (>=7.4.1,<8.0.0)
39
+ Requires-Dist: typing-extensions (>=4.16.0,<5.0.0)
40
+ Requires-Dist: uuid-utils (>=0.17.0,<0.18.0)
41
+ Project-URL: Bug Tracker, https://github.com/yuanqimanong/IPClick/issues
42
+ Project-URL: Homepage, https://github.com/yuanqimanong/IPClick
43
+ Project-URL: Repository, https://github.com/yuanqimanong/IPClick
44
+ Description-Content-Type: text/markdown
45
+
1
46
  # IPClick
2
47
 
3
48
  ![IPClick Logo](https://raw.githubusercontent.com/yuanqimanong/IPClick/master/docs/logo.png)
@@ -40,7 +85,9 @@ IPClick 是一个轻量级、高性能的分布式 HTTP 请求代理工具,基
40
85
  - **链路记录**:每个请求记一条(谁执行的、重试几次、排队多久),内存环形缓冲 + 可选 SQLite,零第三方依赖
41
86
  - **SSRF 防护**:服务端对目标 URL 做协议白名单与内网/元数据地址拦截
42
87
  - **Web 管理端**:`ipclick run --web` 起一个带登录的界面——总览看板、实时请求流、
43
- 「试一试」就地发请求看源码、配置改完写回 `ipclick.toml`、集群节点增删
88
+ 「试一试」就地发请求看源码(支持粘贴 curl、可点名目标节点)、配置改完写回
89
+ `ipclick.toml`、集群节点增删(保存即生效)与就地测连通性 / 鉴权、可选组件的
90
+ 装卸。左中右三栏布局 + 明暗主题切换,前端零构建链路
44
91
  - **命令行工具**:提供便捷的 CLI 工具,支持快速启动服务和查看配置
45
92
  - **Docker 支持**:多阶段构建、非 root 运行的镜像
46
93
  - **完整类型标注**:随包提供 `py.typed`,下游可直接享受类型检查
@@ -83,6 +130,9 @@ playwright install chromium # playwright
83
130
  只做第一步的话,`ipclick config-info` 与 Web 端会明确显示 **「包已装,浏览器本体未就绪」**,
84
131
  请求会直接报 `FAILED_PRECONDITION` 并告诉你该跑哪条命令。
85
132
 
133
+ 上面这些也可以**直接在 Web 端点按钮完成**(0.4 起):`/components` 页列出全部五个
134
+ 可选组件的两级安装状态,就地装 / 卸、下载浏览器本体,装完不用重启进程。
135
+
86
136
  > **刻意不让它在请求里自动下载。** camoufox 的 API 默认行为是"缺本体就当场下"
87
137
  > (`camoufox_path(download_if_missing=True)`),而那一刻已经在 gRPC 的请求处理线程上:
88
138
  > 第一个请求会卡着下 1 GB 然后超时,超时返回后下载还在后台跑,并发的多个首请求
@@ -142,6 +192,9 @@ ipclick run --config /path/to/ipclick.toml
142
192
 
143
193
  # 显示详细日志
144
194
  ipclick run --verbose
195
+
196
+ # 同一目录起第二个实例:gRPC 与 Web 两个端口都要岔开
197
+ ipclick run --port 9528 --web-port 9531 -w
145
198
  ```
146
199
 
147
200
  初始化配置(推荐):
@@ -220,20 +273,41 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
220
273
 
221
274
  或写进 `.env`,或配 `[WEB].username` / `password`。
222
275
 
223
- #### 五个页面
276
+ #### 六个页面
277
+
278
+ 0.4 布局大改版:**左侧导航 + 主内容 + 右侧状态栏**的 CSS Grid,右上角有
279
+ **跟随系统 / 亮 / 暗** 三态主题切换(记在 `localStorage`,刷新不丢)。
224
280
 
225
281
  | 页面 | 干什么 |
226
282
  |---|---|
227
- | **总览** `/` | 吞吐、成功率、在途与峰值、各适配器耗时与流量、状态码分布、引擎安装状态、集群拓扑、最近 12 条请求 |
228
- | **请求流** `/trace` | **实时**(3 秒刷新)看请求打进来。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
229
- | **试一试** `/test` | 填一个网址就地发一次请求,看链路信息与返回的**源码** |
230
- | **配置** `/config` | 白名单内的行为配置可改,**写回 `ipclick.toml`** |
231
- | **节点** `/nodes` | 集群节点的增删改,同样写回 `ipclick.toml` |
283
+ | **总览** `/` | 吞吐、成功率、在途与峰值、状态码分布、各适配器耗时与流量、集群拓扑、最近请求;右栏常驻服务端 / 安全 / 链路 / 限流 / 组件状态 |
284
+ | **请求流** `/trace` | **实时**看请求打进来。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
285
+ | **试一试** `/test` | 填一个网址(或直接**粘一条 curl**)就地发一次请求,看链路信息与返回的**源码**;可**点名**打到某个节点 |
286
+ | **组件** `/components` | 五个可选 extras 的两级安装状态,可以就地**安装 / 卸载 / 下载浏览器本体** |
287
+ | **配置** `/config` | 白名单内的行为配置可改,**写回 `ipclick.toml`**;可一键**生成凭据** |
288
+ | **节点** `/nodes` | 集群节点的增删改(**保存即生效**,不用重启)+ 就地**测试连接** |
232
289
 
233
290
  「试一试」走的是本进程 `TaskService` 的**同一条**代码路径——SSRF 准入、限流、
234
291
  以及开了转发时的分发全都照常生效,请求也会像真实请求一样出现在请求流里。
235
292
  另写一条只在页面上成立的路径毫无意义:那验证的就不是线上行为了。
236
293
 
294
+ **从 curl 导入**:浏览器 DevTools 里对着请求「复制为 cURL」,粘进输入框就能自动
295
+ 填好 URL / 方法 / 请求头 / 请求体。认不出的参数会**明确列出来**——静默丢掉一个
296
+ `-F` 比不支持它更糟。
297
+
298
+ **指定目标节点**:开了服务端转发时,「试一试」多一个"目标节点"下拉,选中后跳过
299
+ 负载均衡强制打到那一台。验证新加的机器配对没有,不用再反复点、靠轮询碰运气。
300
+
301
+ **测试连接**:`/nodes` 每行一个按钮,只验**连通性**与**集群内部鉴权**,不发业务
302
+ 请求。失败时区分"连不上"(查进程和网络)和"鉴权不通过"(核对各节点 `.env` 里的
303
+ `IPCLICK_CLUSTER_SECRET`)——这两种的排查方向完全相反。底层是 0.4 新增的
304
+ `Ping` RPC;对着 0.3 的节点调会收到 `UNIMPLEMENTED`,那恰恰说明鉴权是通的,
305
+ 页面会如实这么说,不会误报成鉴权失败。
306
+
307
+ **生成凭据**:`/config` 可以为鉴权令牌、Web 密码、集群共享密钥各生成一个随机值,
308
+ **只显示一次**(服务端不保存、不写进任何文件,取完即弃),并明确区分"本机独有"
309
+ 和"必须复制到所有其他节点的 `.env`"——集群共享密钥每台各自生成一个就全对不上了。
310
+
237
311
  #### 能改什么、不能改什么
238
312
 
239
313
  - **能改**(写回 toml,保留注释与格式,改动前留 `.bak`):超时、重试、按 host
@@ -241,20 +315,33 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
241
315
  - **不能改**:`[SECURITY]` 全部(令牌、TLS、SSRF 三个开关)、Web 自己的登录凭据、
242
316
  集群共享密钥与各节点 token、`[BROWSER].allow_scripts`。这些只展示,要改请编辑
243
317
  文件 / `.env` 后重启。
244
- - **不装东西**:引擎的安装状态只**展示**,附上安装命令让人自己去那台机器上跑。
245
- 让网页能执行安装命令等于给它一个任意命令执行的入口。
318
+ - **能装依赖**(0.4 放开,此前刻意不给):`/components` 可以装 / 卸 IPClick 自己
319
+ 声明的那五个 extras,以及下载浏览器本体。包名走**白名单常量**(表单里传来的
320
+ extra 必须能在清单里查到),命令以列表形式交给 `subprocess`(`shell=False`),
321
+ 并**绑定当前解释器**——不依赖 PATH 上的 `pip`,装到别的环境去比装不上更糟。
322
+ `pip` 和 `uv pip` 两条路自动探测(uv 建的 venv 默认不装 pip),两者都没有时
323
+ 明确报错并给出手动命令。长任务(`camoufox fetch` 要下约 1 GB)跑在后台,
324
+ 页面轮询状态。**卸载只卸 Python 包**,浏览器本体不动——界面会把它的路径和
325
+ 体积摆出来,让人自己决定,从网页递归删一个 GB 级目录风险和收益不成比例。
246
326
 
247
327
  这条线是刻意划的:这个服务能代任意 URL 发请求,一个能从网页关掉内网拦截、改掉
248
- 令牌的管理端,等于给自己装了个跳板。而调超时、加节点这类操作改错了也只是性能
249
- 或可用性问题,可逆、可见。
328
+ 令牌的管理端,等于给自己装了个跳板。而调超时、加节点、装一个自己声明过的可选
329
+ 依赖,这类操作改错了也只是性能或可用性问题,可逆、可见。
250
330
 
251
331
  写回是**定点文本替换**而不是"读成 dict 再整体 dump":那份配置里几乎每一项都带着
252
332
  解释(为什么默认是这个值、配错了什么症状),整体 dump 会把它们全抹掉。所以只换
253
333
  被改动那一行等号右边的值,行尾注释都保留。写入用临时文件 + `os.replace`,
254
334
  断电不会留下半个配置文件。
255
335
 
256
- 页面里**一行 JavaScript 都没有**,自动刷新用 `<meta refresh>`,CSP 收到
257
- `default-src 'none'`。
336
+ 前端**没有构建链路**:没有模板引擎、框架、打包工具,也没有任何外部资源。
337
+ 布局是纯 CSS Grid,交互是几十行原生 JS——0.3 时页面里一行 JS 都没有,0.4 加了
338
+ 三件没有 JS 就做不好的事:手动切主题、装依赖的状态轮询、请求流的**局部刷新**
339
+ (`<meta refresh>` 每 3 秒重载整页,滚动位置丢失、正在填的过滤条件被冲掉、
340
+ 页面白闪;现在只换那一块的 HTML,而且仍然由服务端渲染,不在 JS 里复制一份逻辑)。
341
+
342
+ CSP 里 `script-src` 用的是那两段内联脚本的 **sha256 哈希**,不是
343
+ `'unsafe-inline'`——脚本是源码里的常量,哈希能精确放行它们,同时让任何注入进来的
344
+ `<script>` 执行不了。
258
345
 
259
346
  安全措施:会话 cookie 带 `HttpOnly` + `SameSite=Strict`,所有写操作校验 CSRF
260
347
  token,登录失败按来源 IP 限速(5 次后锁定 5 分钟,锁定期间正确密码也不放行),
@@ -842,6 +929,7 @@ IPClick/
842
929
  │ ├── health.py # grpc.health.v1 健康检查
843
930
  │ ├── trace.py # 链路记录(内存环形缓冲 + 可选 SQLite)
844
931
  │ ├── compression.py # 请求压缩策略(自动化脚本压缩收益最大)
932
+ │ ├── components.py # 五个可选 extras 的清单、两级安装状态、分类
845
933
  │ ├── limiter.py # 按 host 的并发与 QPS 闸门
846
934
  │ ├── exceptions.py # 异常层次
847
935
  │ ├── py.typed # 类型标注标记
@@ -863,17 +951,22 @@ IPClick/
863
951
  │ │ ├── forwarder.py # ForwardingTaskService(服务端转发)
864
952
  │ │ ├── tokens.py # 由共享密钥派生每节点独立令牌
865
953
  │ │ ├── discovery.py # static / dns 节点发现
954
+ │ │ ├── probe.py # 节点探测:连得上吗、鉴权配对吗
866
955
  │ │ └── status_page.py # 只读状态页
867
- │ ├── web/ # Web 管理端(仅标准库,页面里没有 JS)
868
- │ │ ├── server.py # HTTP 服务、会话、CSRF、路由
956
+ │ ├── web/ # Web 管理端(仅标准库,无前端构建链路)
957
+ │ │ ├── server.py # HTTP 服务、会话、CSRF、路由、CSP
869
958
  │ │ ├── pages.py # 各页面的数据与操作
870
959
  │ │ ├── templates.py # HTML 渲染(每处插值都要过 esc)
960
+ │ │ ├── assets.py # CSS 与 JS(CSP 用脚本哈希放行)
961
+ │ │ ├── installer.py # 装 / 卸可选组件(白名单 + 后台任务)
962
+ │ │ ├── curl_parser.py # 把 curl 命令解析成「试一试」表单
871
963
  │ │ ├── editable.py # 可编辑配置项白名单
872
964
  │ │ └── auth.py # 凭据、会话、登录限速
873
965
  │ ├── cli/ # 命令行工具
874
966
  │ ├── config_loader/ # 配置加载
875
967
  │ │ ├── loader.py # 优先级与环境变量覆盖
876
968
  │ │ ├── dotenv.py # .env 解析
969
+ │ │ ├── placeholders.py # 路径里的 {port} 占位符
877
970
  │ │ └── writer.py # 写回 toml(定点替换,保留注释)
878
971
  │ ├── configs/ # 默认配置文件
879
972
  │ ├── dto/ # 数据传输对象
@@ -882,6 +975,7 @@ IPClick/
882
975
  │ │ └── proto/ # Protobuf 定义与生成脚本
883
976
  │ ├── services/ # gRPC 服务实现
884
977
  │ └── utils/ # 工具模块(日志、配置、URL 安全校验等)
978
+ │ └── module_probe.py # 不执行模块代码的"装没装"探测(find_spec)
885
979
  ├── docker/ # Docker 相关文件
886
980
  ├── examples/ # 示例代码
887
981
  ├── tests/ # 测试代码
@@ -987,7 +1081,7 @@ healthy, status = check_health("10.0.0.1:9527", timeout=3)
987
1081
  [TRACE]
988
1082
  memory_size = 500 # 内存环形缓冲,始终开启,零磁盘
989
1083
  sqlite_enabled = false # 落盘(默认关)。开了 Web 端才能查历史与跨天统计
990
- sqlite_path = "ipclick-trace.db"
1084
+ sqlite_path = "ipclick-trace.{port}.db" # {port} = 运行时实际监听端口
991
1085
  retention_days = 30 # 超期的每小时清一次;0 = 永久
992
1086
  only_errors = false # 只记失败请求(成功量极大时省磁盘)
993
1087
  record_url = true # 关掉后只记 host
@@ -1022,6 +1116,45 @@ print(resp.raw.trace.queued_ms) # 在限流闸门里排了多久
1022
1116
  > 爆炸),所以回答不了"我刚才那个请求为什么 403"。而这个库的使用场景恰恰是后者。
1023
1117
  > 聚合数字这边也有——进程内计数器实时累加,代价只是重启归零。
1024
1118
 
1119
+ #### 同一目录起多个实例:`{port}` 占位符
1120
+
1121
+ `sqlite_path` 与 `[LOG].output` 的路径值支持 `{port}` 占位符,替换成**运行时实际
1122
+ 生效**的监听端口(`--port` 覆盖之后的那个,不是配置文件里的原始值):
1123
+
1124
+ ```toml
1125
+ [TRACE]
1126
+ sqlite_path = "ipclick-trace.{port}.db" # -> ipclick-trace.9528.db
1127
+
1128
+ [LOG]
1129
+ output = "logs/{port}/app.log" # 占位符在路径中间也行
1130
+ ```
1131
+
1132
+ 不这么做的话,同一目录用 `--port` 起的多个实例会**静默**写同一个库、抢同一个日志
1133
+ 文件:不报错、不提示,用户以为在看单个实例的数据,实际是多进程合并的结果。
1134
+ (真撞上了也会打一条明确的告警,但那是兜底,不是解法。)
1135
+
1136
+ 用占位符而不是在代码里"自动加端口后缀"是刻意的:新用户从模板拿到的默认值就带
1137
+ `{port}`,天然按端口分离;已有部署里自己写死的路径**一个字符都不会被改**,否则
1138
+ 旧日志和 trace 数据看起来就像丢了。而且这条规则写在 toml 里是**看得见的**。
1139
+
1140
+ Web 端口也有了对应的命令行覆盖(gRPC 端口一直有 `--port`,Web 端此前没有):
1141
+
1142
+ ```bash
1143
+ ipclick run --port 9528 --web-port 9531 -w
1144
+ ```
1145
+
1146
+ #### `[LOG].output` 的两种写法
1147
+
1148
+ ```toml
1149
+ [LOG]
1150
+ output = "stdout" # 只打控制台(默认)
1151
+ output = "logs/app.log" # 写这个文件(没有扩展名时自动补 .log)
1152
+ output = "logs/" # 以斜杠结尾 = 写进这个**目录**,文件名用 ipclick.log
1153
+ ```
1154
+
1155
+ 最后一种在 0.3 会被静默改写成同级的 `logs.log`(目录名被当成缺扩展名的文件名),
1156
+ 0.4 修了。
1157
+
1025
1158
  ### 安全配置
1026
1159
 
1027
1160
  服务端会代替调用方请求任意 URL。**若监听在公网或不可信网络,请开启内网拦截**,
@@ -1238,3 +1371,4 @@ uv run python src/ipclick/dto/proto/generate.py
1238
1371
  本项目采用 [MIT License](https://github.com/yuanqimanong/IPClick/blob/master/LICENSE) 开源许可证。
1239
1372
 
1240
1373
  Copyright (c) 2025 元气码农少女酱
1374
+
@@ -1,48 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: ipclick
3
- Version: 0.3.0
4
- Summary: IPClick Tool
5
- License: MIT
6
- License-File: LICENSE
7
- Author: 元气码农少女酱
8
- Author-email: 1276162429@qq.com
9
- Requires-Python: >=3.11
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Programming Language :: Python :: 3.14
16
- Provides-Extra: camoufox
17
- Provides-Extra: drissionpage
18
- Provides-Extra: linux
19
- Provides-Extra: niquests
20
- Provides-Extra: patchright
21
- Provides-Extra: playwright
22
- Provides-Extra: win
23
- Requires-Dist: camoufox (>=0.5.0,<1.0.0) ; extra == "camoufox"
24
- Requires-Dist: camoufox (>=0.5.0,<1.0.0) ; extra == "linux"
25
- Requires-Dist: click (>=8.4.2,<9.0.0)
26
- Requires-Dist: curl-cffi (>=0.16.0,<0.17.0)
27
- Requires-Dist: drissionpage (>=4.1.0,<5.0.0) ; extra == "drissionpage"
28
- Requires-Dist: drissionpage (>=4.1.0,<5.0.0) ; extra == "win"
29
- Requires-Dist: fake-useragent (>=2.2.0,<3.0.0)
30
- Requires-Dist: grpcio (>=1.83.0,<2.0.0)
31
- Requires-Dist: grpcio-health-checking (>=1.83.0,<2.0.0)
32
- Requires-Dist: loguru (>=0.7.3,<0.8.0)
33
- Requires-Dist: niquests (>=3.14.0,<4.0.0) ; extra == "niquests"
34
- Requires-Dist: patchright (>=1.52.0,<2.0.0) ; extra == "linux"
35
- Requires-Dist: patchright (>=1.52.0,<2.0.0) ; extra == "patchright"
36
- Requires-Dist: playwright (>=1.49.0,<2.0.0) ; extra == "playwright"
37
- Requires-Dist: protobuf (>=6.33.2,<8.0.0)
38
- Requires-Dist: python-box[all] (>=7.4.1,<8.0.0)
39
- Requires-Dist: typing-extensions (>=4.15.0,<5.0.0)
40
- Requires-Dist: uuid-utils (>=0.17.0,<0.18.0)
41
- Project-URL: Bug Tracker, https://github.com/yuanqimanong/IPClick/issues
42
- Project-URL: Homepage, https://github.com/yuanqimanong/IPClick
43
- Project-URL: Repository, https://github.com/yuanqimanong/IPClick
44
- Description-Content-Type: text/markdown
45
-
46
1
  # IPClick
47
2
 
48
3
  ![IPClick Logo](https://raw.githubusercontent.com/yuanqimanong/IPClick/master/docs/logo.png)
@@ -85,7 +40,9 @@ IPClick 是一个轻量级、高性能的分布式 HTTP 请求代理工具,基
85
40
  - **链路记录**:每个请求记一条(谁执行的、重试几次、排队多久),内存环形缓冲 + 可选 SQLite,零第三方依赖
86
41
  - **SSRF 防护**:服务端对目标 URL 做协议白名单与内网/元数据地址拦截
87
42
  - **Web 管理端**:`ipclick run --web` 起一个带登录的界面——总览看板、实时请求流、
88
- 「试一试」就地发请求看源码、配置改完写回 `ipclick.toml`、集群节点增删
43
+ 「试一试」就地发请求看源码(支持粘贴 curl、可点名目标节点)、配置改完写回
44
+ `ipclick.toml`、集群节点增删(保存即生效)与就地测连通性 / 鉴权、可选组件的
45
+ 装卸。左中右三栏布局 + 明暗主题切换,前端零构建链路
89
46
  - **命令行工具**:提供便捷的 CLI 工具,支持快速启动服务和查看配置
90
47
  - **Docker 支持**:多阶段构建、非 root 运行的镜像
91
48
  - **完整类型标注**:随包提供 `py.typed`,下游可直接享受类型检查
@@ -128,6 +85,9 @@ playwright install chromium # playwright
128
85
  只做第一步的话,`ipclick config-info` 与 Web 端会明确显示 **「包已装,浏览器本体未就绪」**,
129
86
  请求会直接报 `FAILED_PRECONDITION` 并告诉你该跑哪条命令。
130
87
 
88
+ 上面这些也可以**直接在 Web 端点按钮完成**(0.4 起):`/components` 页列出全部五个
89
+ 可选组件的两级安装状态,就地装 / 卸、下载浏览器本体,装完不用重启进程。
90
+
131
91
  > **刻意不让它在请求里自动下载。** camoufox 的 API 默认行为是"缺本体就当场下"
132
92
  > (`camoufox_path(download_if_missing=True)`),而那一刻已经在 gRPC 的请求处理线程上:
133
93
  > 第一个请求会卡着下 1 GB 然后超时,超时返回后下载还在后台跑,并发的多个首请求
@@ -187,6 +147,9 @@ ipclick run --config /path/to/ipclick.toml
187
147
 
188
148
  # 显示详细日志
189
149
  ipclick run --verbose
150
+
151
+ # 同一目录起第二个实例:gRPC 与 Web 两个端口都要岔开
152
+ ipclick run --port 9528 --web-port 9531 -w
190
153
  ```
191
154
 
192
155
  初始化配置(推荐):
@@ -265,20 +228,41 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
265
228
 
266
229
  或写进 `.env`,或配 `[WEB].username` / `password`。
267
230
 
268
- #### 五个页面
231
+ #### 六个页面
232
+
233
+ 0.4 布局大改版:**左侧导航 + 主内容 + 右侧状态栏**的 CSS Grid,右上角有
234
+ **跟随系统 / 亮 / 暗** 三态主题切换(记在 `localStorage`,刷新不丢)。
269
235
 
270
236
  | 页面 | 干什么 |
271
237
  |---|---|
272
- | **总览** `/` | 吞吐、成功率、在途与峰值、各适配器耗时与流量、状态码分布、引擎安装状态、集群拓扑、最近 12 条请求 |
273
- | **请求流** `/trace` | **实时**(3 秒刷新)看请求打进来。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
274
- | **试一试** `/test` | 填一个网址就地发一次请求,看链路信息与返回的**源码** |
275
- | **配置** `/config` | 白名单内的行为配置可改,**写回 `ipclick.toml`** |
276
- | **节点** `/nodes` | 集群节点的增删改,同样写回 `ipclick.toml` |
238
+ | **总览** `/` | 吞吐、成功率、在途与峰值、状态码分布、各适配器耗时与流量、集群拓扑、最近请求;右栏常驻服务端 / 安全 / 链路 / 限流 / 组件状态 |
239
+ | **请求流** `/trace` | **实时**看请求打进来。按状态 / 适配器 / URL 过滤,看目标站点排行与按天趋势 |
240
+ | **试一试** `/test` | 填一个网址(或直接**粘一条 curl**)就地发一次请求,看链路信息与返回的**源码**;可**点名**打到某个节点 |
241
+ | **组件** `/components` | 五个可选 extras 的两级安装状态,可以就地**安装 / 卸载 / 下载浏览器本体** |
242
+ | **配置** `/config` | 白名单内的行为配置可改,**写回 `ipclick.toml`**;可一键**生成凭据** |
243
+ | **节点** `/nodes` | 集群节点的增删改(**保存即生效**,不用重启)+ 就地**测试连接** |
277
244
 
278
245
  「试一试」走的是本进程 `TaskService` 的**同一条**代码路径——SSRF 准入、限流、
279
246
  以及开了转发时的分发全都照常生效,请求也会像真实请求一样出现在请求流里。
280
247
  另写一条只在页面上成立的路径毫无意义:那验证的就不是线上行为了。
281
248
 
249
+ **从 curl 导入**:浏览器 DevTools 里对着请求「复制为 cURL」,粘进输入框就能自动
250
+ 填好 URL / 方法 / 请求头 / 请求体。认不出的参数会**明确列出来**——静默丢掉一个
251
+ `-F` 比不支持它更糟。
252
+
253
+ **指定目标节点**:开了服务端转发时,「试一试」多一个"目标节点"下拉,选中后跳过
254
+ 负载均衡强制打到那一台。验证新加的机器配对没有,不用再反复点、靠轮询碰运气。
255
+
256
+ **测试连接**:`/nodes` 每行一个按钮,只验**连通性**与**集群内部鉴权**,不发业务
257
+ 请求。失败时区分"连不上"(查进程和网络)和"鉴权不通过"(核对各节点 `.env` 里的
258
+ `IPCLICK_CLUSTER_SECRET`)——这两种的排查方向完全相反。底层是 0.4 新增的
259
+ `Ping` RPC;对着 0.3 的节点调会收到 `UNIMPLEMENTED`,那恰恰说明鉴权是通的,
260
+ 页面会如实这么说,不会误报成鉴权失败。
261
+
262
+ **生成凭据**:`/config` 可以为鉴权令牌、Web 密码、集群共享密钥各生成一个随机值,
263
+ **只显示一次**(服务端不保存、不写进任何文件,取完即弃),并明确区分"本机独有"
264
+ 和"必须复制到所有其他节点的 `.env`"——集群共享密钥每台各自生成一个就全对不上了。
265
+
282
266
  #### 能改什么、不能改什么
283
267
 
284
268
  - **能改**(写回 toml,保留注释与格式,改动前留 `.bak`):超时、重试、按 host
@@ -286,20 +270,33 @@ IPCLICK_WEB_USER=ops IPCLICK_WEB_PASSWORD=... ipclick run -w
286
270
  - **不能改**:`[SECURITY]` 全部(令牌、TLS、SSRF 三个开关)、Web 自己的登录凭据、
287
271
  集群共享密钥与各节点 token、`[BROWSER].allow_scripts`。这些只展示,要改请编辑
288
272
  文件 / `.env` 后重启。
289
- - **不装东西**:引擎的安装状态只**展示**,附上安装命令让人自己去那台机器上跑。
290
- 让网页能执行安装命令等于给它一个任意命令执行的入口。
273
+ - **能装依赖**(0.4 放开,此前刻意不给):`/components` 可以装 / 卸 IPClick 自己
274
+ 声明的那五个 extras,以及下载浏览器本体。包名走**白名单常量**(表单里传来的
275
+ extra 必须能在清单里查到),命令以列表形式交给 `subprocess`(`shell=False`),
276
+ 并**绑定当前解释器**——不依赖 PATH 上的 `pip`,装到别的环境去比装不上更糟。
277
+ `pip` 和 `uv pip` 两条路自动探测(uv 建的 venv 默认不装 pip),两者都没有时
278
+ 明确报错并给出手动命令。长任务(`camoufox fetch` 要下约 1 GB)跑在后台,
279
+ 页面轮询状态。**卸载只卸 Python 包**,浏览器本体不动——界面会把它的路径和
280
+ 体积摆出来,让人自己决定,从网页递归删一个 GB 级目录风险和收益不成比例。
291
281
 
292
282
  这条线是刻意划的:这个服务能代任意 URL 发请求,一个能从网页关掉内网拦截、改掉
293
- 令牌的管理端,等于给自己装了个跳板。而调超时、加节点这类操作改错了也只是性能
294
- 或可用性问题,可逆、可见。
283
+ 令牌的管理端,等于给自己装了个跳板。而调超时、加节点、装一个自己声明过的可选
284
+ 依赖,这类操作改错了也只是性能或可用性问题,可逆、可见。
295
285
 
296
286
  写回是**定点文本替换**而不是"读成 dict 再整体 dump":那份配置里几乎每一项都带着
297
287
  解释(为什么默认是这个值、配错了什么症状),整体 dump 会把它们全抹掉。所以只换
298
288
  被改动那一行等号右边的值,行尾注释都保留。写入用临时文件 + `os.replace`,
299
289
  断电不会留下半个配置文件。
300
290
 
301
- 页面里**一行 JavaScript 都没有**,自动刷新用 `<meta refresh>`,CSP 收到
302
- `default-src 'none'`。
291
+ 前端**没有构建链路**:没有模板引擎、框架、打包工具,也没有任何外部资源。
292
+ 布局是纯 CSS Grid,交互是几十行原生 JS——0.3 时页面里一行 JS 都没有,0.4 加了
293
+ 三件没有 JS 就做不好的事:手动切主题、装依赖的状态轮询、请求流的**局部刷新**
294
+ (`<meta refresh>` 每 3 秒重载整页,滚动位置丢失、正在填的过滤条件被冲掉、
295
+ 页面白闪;现在只换那一块的 HTML,而且仍然由服务端渲染,不在 JS 里复制一份逻辑)。
296
+
297
+ CSP 里 `script-src` 用的是那两段内联脚本的 **sha256 哈希**,不是
298
+ `'unsafe-inline'`——脚本是源码里的常量,哈希能精确放行它们,同时让任何注入进来的
299
+ `<script>` 执行不了。
303
300
 
304
301
  安全措施:会话 cookie 带 `HttpOnly` + `SameSite=Strict`,所有写操作校验 CSRF
305
302
  token,登录失败按来源 IP 限速(5 次后锁定 5 分钟,锁定期间正确密码也不放行),
@@ -887,6 +884,7 @@ IPClick/
887
884
  │ ├── health.py # grpc.health.v1 健康检查
888
885
  │ ├── trace.py # 链路记录(内存环形缓冲 + 可选 SQLite)
889
886
  │ ├── compression.py # 请求压缩策略(自动化脚本压缩收益最大)
887
+ │ ├── components.py # 五个可选 extras 的清单、两级安装状态、分类
890
888
  │ ├── limiter.py # 按 host 的并发与 QPS 闸门
891
889
  │ ├── exceptions.py # 异常层次
892
890
  │ ├── py.typed # 类型标注标记
@@ -908,17 +906,22 @@ IPClick/
908
906
  │ │ ├── forwarder.py # ForwardingTaskService(服务端转发)
909
907
  │ │ ├── tokens.py # 由共享密钥派生每节点独立令牌
910
908
  │ │ ├── discovery.py # static / dns 节点发现
909
+ │ │ ├── probe.py # 节点探测:连得上吗、鉴权配对吗
911
910
  │ │ └── status_page.py # 只读状态页
912
- │ ├── web/ # Web 管理端(仅标准库,页面里没有 JS)
913
- │ │ ├── server.py # HTTP 服务、会话、CSRF、路由
911
+ │ ├── web/ # Web 管理端(仅标准库,无前端构建链路)
912
+ │ │ ├── server.py # HTTP 服务、会话、CSRF、路由、CSP
914
913
  │ │ ├── pages.py # 各页面的数据与操作
915
914
  │ │ ├── templates.py # HTML 渲染(每处插值都要过 esc)
915
+ │ │ ├── assets.py # CSS 与 JS(CSP 用脚本哈希放行)
916
+ │ │ ├── installer.py # 装 / 卸可选组件(白名单 + 后台任务)
917
+ │ │ ├── curl_parser.py # 把 curl 命令解析成「试一试」表单
916
918
  │ │ ├── editable.py # 可编辑配置项白名单
917
919
  │ │ └── auth.py # 凭据、会话、登录限速
918
920
  │ ├── cli/ # 命令行工具
919
921
  │ ├── config_loader/ # 配置加载
920
922
  │ │ ├── loader.py # 优先级与环境变量覆盖
921
923
  │ │ ├── dotenv.py # .env 解析
924
+ │ │ ├── placeholders.py # 路径里的 {port} 占位符
922
925
  │ │ └── writer.py # 写回 toml(定点替换,保留注释)
923
926
  │ ├── configs/ # 默认配置文件
924
927
  │ ├── dto/ # 数据传输对象
@@ -927,6 +930,7 @@ IPClick/
927
930
  │ │ └── proto/ # Protobuf 定义与生成脚本
928
931
  │ ├── services/ # gRPC 服务实现
929
932
  │ └── utils/ # 工具模块(日志、配置、URL 安全校验等)
933
+ │ └── module_probe.py # 不执行模块代码的"装没装"探测(find_spec)
930
934
  ├── docker/ # Docker 相关文件
931
935
  ├── examples/ # 示例代码
932
936
  ├── tests/ # 测试代码
@@ -1032,7 +1036,7 @@ healthy, status = check_health("10.0.0.1:9527", timeout=3)
1032
1036
  [TRACE]
1033
1037
  memory_size = 500 # 内存环形缓冲,始终开启,零磁盘
1034
1038
  sqlite_enabled = false # 落盘(默认关)。开了 Web 端才能查历史与跨天统计
1035
- sqlite_path = "ipclick-trace.db"
1039
+ sqlite_path = "ipclick-trace.{port}.db" # {port} = 运行时实际监听端口
1036
1040
  retention_days = 30 # 超期的每小时清一次;0 = 永久
1037
1041
  only_errors = false # 只记失败请求(成功量极大时省磁盘)
1038
1042
  record_url = true # 关掉后只记 host
@@ -1067,6 +1071,45 @@ print(resp.raw.trace.queued_ms) # 在限流闸门里排了多久
1067
1071
  > 爆炸),所以回答不了"我刚才那个请求为什么 403"。而这个库的使用场景恰恰是后者。
1068
1072
  > 聚合数字这边也有——进程内计数器实时累加,代价只是重启归零。
1069
1073
 
1074
+ #### 同一目录起多个实例:`{port}` 占位符
1075
+
1076
+ `sqlite_path` 与 `[LOG].output` 的路径值支持 `{port}` 占位符,替换成**运行时实际
1077
+ 生效**的监听端口(`--port` 覆盖之后的那个,不是配置文件里的原始值):
1078
+
1079
+ ```toml
1080
+ [TRACE]
1081
+ sqlite_path = "ipclick-trace.{port}.db" # -> ipclick-trace.9528.db
1082
+
1083
+ [LOG]
1084
+ output = "logs/{port}/app.log" # 占位符在路径中间也行
1085
+ ```
1086
+
1087
+ 不这么做的话,同一目录用 `--port` 起的多个实例会**静默**写同一个库、抢同一个日志
1088
+ 文件:不报错、不提示,用户以为在看单个实例的数据,实际是多进程合并的结果。
1089
+ (真撞上了也会打一条明确的告警,但那是兜底,不是解法。)
1090
+
1091
+ 用占位符而不是在代码里"自动加端口后缀"是刻意的:新用户从模板拿到的默认值就带
1092
+ `{port}`,天然按端口分离;已有部署里自己写死的路径**一个字符都不会被改**,否则
1093
+ 旧日志和 trace 数据看起来就像丢了。而且这条规则写在 toml 里是**看得见的**。
1094
+
1095
+ Web 端口也有了对应的命令行覆盖(gRPC 端口一直有 `--port`,Web 端此前没有):
1096
+
1097
+ ```bash
1098
+ ipclick run --port 9528 --web-port 9531 -w
1099
+ ```
1100
+
1101
+ #### `[LOG].output` 的两种写法
1102
+
1103
+ ```toml
1104
+ [LOG]
1105
+ output = "stdout" # 只打控制台(默认)
1106
+ output = "logs/app.log" # 写这个文件(没有扩展名时自动补 .log)
1107
+ output = "logs/" # 以斜杠结尾 = 写进这个**目录**,文件名用 ipclick.log
1108
+ ```
1109
+
1110
+ 最后一种在 0.3 会被静默改写成同级的 `logs.log`(目录名被当成缺扩展名的文件名),
1111
+ 0.4 修了。
1112
+
1070
1113
  ### 安全配置
1071
1114
 
1072
1115
  服务端会代替调用方请求任意 URL。**若监听在公网或不可信网络,请开启内网拦截**,
@@ -1283,4 +1326,3 @@ uv run python src/ipclick/dto/proto/generate.py
1283
1326
  本项目采用 [MIT License](https://github.com/yuanqimanong/IPClick/blob/master/LICENSE) 开源许可证。
1284
1327
 
1285
1328
  Copyright (c) 2025 元气码农少女酱
1286
-
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipclick"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "IPClick Tool"
5
5
  authors = [
6
6
  { name = "元气码农少女酱", email = "1276162429@qq.com" }
@@ -18,7 +18,7 @@ dependencies = [
18
18
  "loguru>=0.7.3,<0.8.0",
19
19
  "python-box[all]>=7.4.1,<8.0.0",
20
20
  "uuid-utils>=0.17.0,<0.18.0",
21
- "typing-extensions>=4.15.0,<5.0.0",
21
+ "typing-extensions>=4.16.0,<5.0.0",
22
22
  ]
23
23
 
24
24
  [project.optional-dependencies]
@@ -26,25 +26,29 @@ dependencies = [
26
26
  # 另一个可选的是 niquests —— requests 的 drop-in 替代,额外支持 HTTP/2 与 HTTP/3。
27
27
  # (httpx 曾经也在这里,0.3.0 移除:它和 niquests 能力重叠,
28
28
  # 而 niquests 还多支持 HTTP/3,维护两套等价的 HTTP 适配器不划算。)
29
- niquests = ["niquests>=3.14.0,<4.0.0"]
29
+ niquests = ["niquests>=3.21.0,<4.0.0"]
30
30
 
31
31
  # 浏览器渲染引擎。装完还要各自准备浏览器本体:
32
32
  # camoufox python -m camoufox fetch
33
33
  # patchright patchright install chromium
34
34
  # playwright playwright install chromium
35
35
  # drissionpage 用本机已装的 Chrome/Chromium,无需下载
36
- camoufox = ["camoufox>=0.5.0,<1.0.0"]
37
- patchright = ["patchright>=1.52.0,<2.0.0"]
38
- drissionpage = ["drissionpage>=4.1.0,<5.0.0"]
39
- playwright = ["playwright>=1.49.0,<2.0.0"]
36
+ camoufox = ["camoufox>=0.5.4,<1.0.0"]
37
+ patchright = ["patchright>=1.61.2,<2.0.0"]
38
+ drissionpage = ["drissionpage>=4.1.1,<5.0.0"]
39
+ # ⚠️ 上限**必须**留在 camoufox 能接受的范围内:camoufox 0.5.4 自己钉着
40
+ # playwright<1.61,把这里的下限提到 1.62 会让 [camoufox] 和 [playwright]
41
+ # 直接互斥(uv 会报 unsatisfiable)。而 camoufox 是 Linux 上的默认引擎,
42
+ # 为了追一个更新的 playwright 把它挤掉不划算。
43
+ playwright = ["playwright>=1.60.0,<2.0.0"]
40
44
 
41
45
  # 按平台打包好的组合。刻意不提供 [all]——把四个浏览器内核全装上去
42
46
  # 有 70+ 个包和上 G 的浏览器本体,而一台机器只会用其中一个。
43
47
  # curl_cffi 是核心依赖,不在这里重复声明(重复写还会引入版本区间打架的风险)。
44
48
  # win : DrissionPage 直接驱动本机已装的 Chrome,Windows 上最省事
45
49
  # linux : Camoufox / Patchright 自带无头浏览器,适合服务器
46
- win = ["drissionpage>=4.1.0,<5.0.0"]
47
- linux = ["camoufox>=0.5.0,<1.0.0", "patchright>=1.52.0,<2.0.0"]
50
+ win = ["drissionpage>=4.1.1,<5.0.0"]
51
+ linux = ["camoufox>=0.5.4,<1.0.0", "patchright>=1.61.2,<2.0.0"]
48
52
 
49
53
  [project.urls]
50
54
  "Homepage" = "https://github.com/yuanqimanong/IPClick"
@@ -29,7 +29,7 @@ try:
29
29
  __version__ = importlib.metadata.version("ipclick")
30
30
  __author__ = importlib.metadata.metadata("ipclick")["Author"]
31
31
  except importlib.metadata.PackageNotFoundError: # pragma: no cover - 仅在未安装时走到
32
- __version__ = "0.3.0"
32
+ __version__ = "0.4.0"
33
33
  __author__ = "元气码农少女酱"
34
34
 
35
35