nbpl 0.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.
nbpl/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """检测运行中的 Python 脚本,并打开对应的视频。"""
2
+
3
+ from .watcher import (
4
+ BILIBILI_URL,
5
+ YOUTUBE_URL,
6
+ ScriptProcess,
7
+ detect_country_code,
8
+ find_running_python_scripts,
9
+ maybe_open_for_current_python_script,
10
+ open_video_for_country,
11
+ run_once,
12
+ watch,
13
+ )
14
+
15
+ __all__ = [
16
+ "BILIBILI_URL",
17
+ "YOUTUBE_URL",
18
+ "ScriptProcess",
19
+ "detect_country_code",
20
+ "find_running_python_scripts",
21
+ "maybe_open_for_current_python_script",
22
+ "open_video_for_country",
23
+ "run_once",
24
+ "watch",
25
+ ]
nbpl/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
nbpl/cli.py ADDED
@@ -0,0 +1,25 @@
1
+ """nbpl 的命令行入口。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from .watcher import watch
8
+
9
+
10
+ def main() -> None:
11
+ parser = argparse.ArgumentParser(
12
+ description="检测到 Python 脚本运行时,按所在地区打开对应的视频。"
13
+ )
14
+ parser.add_argument(
15
+ "--interval",
16
+ type=float,
17
+ default=1.0,
18
+ help="进程检查间隔(秒),默认:1",
19
+ )
20
+ parser.add_argument(
21
+ "--country-code",
22
+ help="覆盖公网 IP 地区检测,适合测试,例如 CN",
23
+ )
24
+ args = parser.parse_args()
25
+ watch(interval=args.interval, country_code=args.country_code)
nbpl/watcher.py ADDED
@@ -0,0 +1,128 @@
1
+ """nbpl 的进程监控与地区检测逻辑。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ import time
8
+ import webbrowser
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Callable, Optional
12
+
13
+ BILIBILI_URL = "https://www.bilibili.com/video/BV1GJ411x7h7"
14
+ YOUTUBE_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
15
+ IP_API_URL = "https://ipapi.co/json/"
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ScriptProcess:
20
+ """一个关联到 Python 源文件的运行中 Python 解释器。"""
21
+
22
+ pid: int
23
+ script: str
24
+
25
+
26
+ def _is_python_interpreter(executable: str) -> bool:
27
+ name = Path(executable).name.lower()
28
+ return name.startswith("python") or name in {"py", "py.exe"}
29
+
30
+
31
+ def _script_from_cmdline(cmdline: list[str]) -> Optional[str]:
32
+ """从 Python 解释器的命令行中返回第一个直接传入的源文件。"""
33
+ if not cmdline or not _is_python_interpreter(cmdline[0]):
34
+ return None
35
+
36
+ for argument in cmdline[1:]:
37
+ if argument.lower().endswith((".py", ".pyw")):
38
+ return argument
39
+ return None
40
+
41
+
42
+ def find_running_python_scripts() -> list[ScriptProcess]:
43
+ """列出正在执行 Python 源文件的其他进程。"""
44
+ try:
45
+ import psutil
46
+ except ImportError as exc:
47
+ raise RuntimeError("nbpl 依赖 psutil,请重新安装本包") from exc
48
+
49
+ found: list[ScriptProcess] = []
50
+ this_pid = os.getpid()
51
+ for process in psutil.process_iter(["pid", "cmdline"]):
52
+ try:
53
+ info = process.info
54
+ pid = info["pid"]
55
+ if pid == this_pid:
56
+ continue
57
+ script = _script_from_cmdline(info.get("cmdline") or [])
58
+ if script:
59
+ found.append(ScriptProcess(pid=pid, script=script))
60
+ except (psutil.AccessDenied, psutil.NoSuchProcess, psutil.ZombieProcess):
61
+ continue
62
+ return found
63
+
64
+
65
+ def detect_country_code(timeout: float = 5.0) -> Optional[str]:
66
+ """返回公网 IP 的国家/地区代码;无法判断时返回 None。"""
67
+ try:
68
+ import requests
69
+ except ImportError as exc:
70
+ raise RuntimeError("nbpl 依赖 requests,请重新安装本包") from exc
71
+
72
+ try:
73
+ response = requests.get(IP_API_URL, timeout=timeout)
74
+ response.raise_for_status()
75
+ country_code = response.json().get("country_code")
76
+ return country_code.upper() if isinstance(country_code, str) else None
77
+ except (requests.RequestException, ValueError):
78
+ return None
79
+
80
+
81
+ def open_video_for_country(country_code: Optional[str]) -> str:
82
+ """按国家/地区代码打开并返回相应的视频 URL。"""
83
+ url = BILIBILI_URL if country_code == "CN" else YOUTUBE_URL
84
+ webbrowser.open(url)
85
+ return url
86
+
87
+
88
+ def maybe_open_for_current_python_script() -> Optional[str]:
89
+ """当前解释器执行 Python 源文件时自动打开视频。
90
+
91
+ 设置环境变量 NBPL_DISABLE_AUTO_OPEN=1 可关闭自动行为。
92
+ """
93
+ if os.environ.get("NBPL_DISABLE_AUTO_OPEN") == "1":
94
+ return None
95
+ script = sys.argv[0] if sys.argv else ""
96
+ if not script.lower().endswith((".py", ".pyw")):
97
+ return None
98
+ return open_video_for_country(detect_country_code())
99
+
100
+
101
+ def run_once(country_code: Optional[str] = None) -> Optional[str]:
102
+ """发现其他 Python 脚本正在运行时,仅打开一次视频。
103
+
104
+ 可传入 country_code 以方便测试;未传入时会通过公网 IP 检测。
105
+ """
106
+ if not find_running_python_scripts():
107
+ return None
108
+ return open_video_for_country(
109
+ detect_country_code() if country_code is None else country_code
110
+ )
111
+
112
+
113
+ def watch(
114
+ interval: float = 1.0,
115
+ country_code: Optional[str] = None,
116
+ on_open: Optional[Callable[[str], None]] = None,
117
+ ) -> None:
118
+ """等待 Python 脚本出现,打开视频后返回。"""
119
+ if interval <= 0:
120
+ raise ValueError("interval 必须大于零")
121
+
122
+ while True:
123
+ url = run_once(country_code=country_code)
124
+ if url:
125
+ if on_open:
126
+ on_open(url)
127
+ return
128
+ time.sleep(interval)
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: nbpl
3
+ Version: 0.1.0
4
+ Summary: 检测正在运行的 Python 脚本,并按地区打开对应视频。
5
+ Author: hekuo5310
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: psutil>=5.9
11
+ Requires-Dist: requests>=2.28
12
+ Dynamic: license-file
13
+
14
+ # nbpl
15
+
16
+ 安装 nbpl 后,不需要在任何脚本中导入它。它会通过 Python 的 sitecustomize 启动钩子,在执行 Python 源文件时自动使用系统默认浏览器打开一个视频:
17
+
18
+ | 公网 IP 所在国家/地区代码 | 打开的链接 |
19
+ | --- | --- |
20
+ | CN(中国大陆) | https://www.bilibili.com/video/BV1GJ411x7h7 |
21
+ | 其他代码,或定位失败 | https://www.youtube.com/watch?v=dQw4w9WgXcQ |
22
+
23
+ 地区查询使用 https://ipapi.co/json/。本包仅会读取进程命令行并发起一次 IP 地理位置查询;不会上传检测到的脚本路径。
24
+
25
+ ## 安装
26
+
27
+ ~~~bash
28
+ pip install nbpl
29
+ ~~~
30
+
31
+ 也可以直接从本仓库安装:
32
+
33
+ ~~~bash
34
+ pip install git+https://github.com/hekuo5310/nbpl.git
35
+ ~~~
36
+
37
+ ## 自动使用
38
+
39
+ 安装后,直接运行任意 Python 文件即可自动触发:
40
+
41
+ ~~~bash
42
+ python app.py
43
+ ~~~
44
+
45
+ 如果只想作为独立监控器使用,仍可运行:
46
+
47
+ ~~~bash
48
+ nbpl
49
+ ~~~
50
+
51
+ 独立监控器默认每秒检查一次;发现其他 Python 脚本后打开对应链接并退出。修改检查间隔:
52
+
53
+ ~~~bash
54
+ nbpl --interval 2
55
+ ~~~
56
+
57
+ 本地测试时可跳过 IP 查询,直接指定地区:
58
+
59
+ ~~~bash
60
+ nbpl --country-code CN
61
+ ~~~
62
+
63
+ ## 临时关闭自动触发
64
+
65
+ 为避免自动打开浏览器,可在运行命令前设置环境变量:
66
+
67
+ ~~~bash
68
+ NBPL_DISABLE_AUTO_OPEN=1 python app.py
69
+ ~~~
70
+
71
+ Windows PowerShell:
72
+
73
+ ~~~powershell
74
+ $env:NBPL_DISABLE_AUTO_OPEN = "1"; python app.py
75
+ ~~~
76
+
77
+ ## Python API
78
+
79
+ ~~~python
80
+ from nbpl import run_once, watch
81
+
82
+ run_once() # 仅当已有其他 Python 脚本在运行时才打开链接
83
+ watch(interval=1.0) # 等待脚本出现,打开链接后返回
84
+ ~~~
@@ -0,0 +1,11 @@
1
+ sitecustomize.py,sha256=CPEL8xVzOw_J_MYYCOKieZ3syNVaAGBStr8OiTgxZ0g,273
2
+ nbpl/__init__.py,sha256=S3DQWho0BkbavEr3nYIvAM4yuyXNYDGC2uEFe4iFkNg,542
3
+ nbpl/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
4
+ nbpl/cli.py,sha256=jQoxbkS5dxQI27O4SlvcQnOnfZImurPbAuRvv6USLLo,654
5
+ nbpl/watcher.py,sha256=jzjPXp2kOM2nKuf__9HfpIbg6-fpgMkN9KMf4ivWYgc,4081
6
+ nbpl-0.1.0.dist-info/licenses/LICENSE,sha256=nPn8ikjQJsLFY8JnSqwps8ud39e1GGiIax5cXA00R5w,1066
7
+ nbpl-0.1.0.dist-info/METADATA,sha256=YIVSKcFLQvwJyZ2965pa1hHg7rRRDHvNjk5YIRpuvr4,1948
8
+ nbpl-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
9
+ nbpl-0.1.0.dist-info/entry_points.txt,sha256=0S2Q9Oiyvjx-atubBXxeRpee2iF1RZC5oJYNTSscqRI,39
10
+ nbpl-0.1.0.dist-info/top_level.txt,sha256=gfBXdr3y2pLYiwRzq3o9dB3ZDtSLAWWVak2xTVoNFN4,19
11
+ nbpl-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nbpl = nbpl.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 牢废物
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ nbpl
2
+ sitecustomize
sitecustomize.py ADDED
@@ -0,0 +1,9 @@
1
+ """Python 启动钩子:无需在脚本中导入 nbpl。"""
2
+
3
+ try:
4
+ from nbpl.watcher import maybe_open_for_current_python_script
5
+
6
+ maybe_open_for_current_python_script()
7
+ except Exception:
8
+ # 启动钩子不能阻止用户的 Python 程序正常运行。
9
+ pass