Name | nullbr JSON |
Version |
1.0.3
JSON |
| download |
home_page | None |
Summary | Python SDK for Nullbr API - 用于访问 Nullbr API 的 Python SDK |
upload_time | 2025-08-09 02:21:56 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.9 |
license | MIT |
keywords |
api
movies
nullbr
sdk
tmdb
tv
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# nullbr

[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
Python SDK for Nullbr API - 用于访问 Nullbr API 的 Python SDK
## 功能特性
- 🔍 搜索电影、电视剧、合集和人物
- 🎬 获取电影详细信息和资源链接(115网盘、磁力、ed2k、video)
- 📺 获取电视剧详细信息和资源链接(115网盘、磁力、ed2k、video)
- 📚 获取合集信息和资源链接
- 🎯 支持剧集季度和单集资源获取
- 🛠️ 完整的命令行工具支持
- 🔒 MIT 许可证
## 安装
### 使用 uv 安装(推荐)
```bash
uv add nullbr
```
### 使用 pip 安装
```bash
pip install nullbr
```
### 从源码安装
```bash
git clone https://github.com/iLay1678/nullbr-python.git
cd nullbr
uv sync
uv pip install -e .
```
## 快速开始
### 基本用法
```python
from nullbr import NullbrSDK
# 初始化SDK
sdk = NullbrSDK(
app_id="your_app_id",
api_key="your_api_key" # 可选,某些操作需要
)
# 搜索电影
results = sdk.search("复仇者联盟")
for item in results.items:
print(f"{item.title} ({item.media_type}) - 评分: {item.vote_average}")
# 获取电影详细信息
movie = sdk.get_movie(299536) # 复仇者联盟4的TMDB ID
print(f"电影名称: {movie.title}")
print(f"评分: {movie.vote}")
print(f"上映日期: {movie.release_date}")
# 获取电影ed2k资源
if movie.has_ed2k:
ed2k_resources = sdk.get_movie_ed2k(299536)
for resource in ed2k_resources.ed2k:
print(f"资源: {resource.name}")
print(f"大小: {resource.size}")
print(f"分辨率: {resource.resolution}")
print(f"中文字幕: {'是' if resource.zh_sub else '否'}")
```
### 命令行使用
#### 方式一:使用全局命令(推荐)
安装后可直接使用 `nullbr` 命令:
```bash
# 搜索
nullbr --app-id YOUR_APP_ID search "复仇者联盟"
# 获取电影信息
nullbr --app-id YOUR_APP_ID movie 299536
# 获取电视剧信息
nullbr --app-id YOUR_APP_ID tv 1396
# 获取电影ed2k资源
nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-ed2k 78
# 获取剧集单集ed2k资源
nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-ed2k 1396 1 3
# 获取电影video资源(m3u8/http)
nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-video 78
# 获取剧集单集video资源(m3u8/http)
nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-video 1396 3 4
```
#### 方式二:使用Python模块
```bash
# 搜索
python -m nullbr --app-id YOUR_APP_ID search "复仇者联盟"
# 获取电影信息
python -m nullbr --app-id YOUR_APP_ID movie 299536
# 获取电视剧信息
python -m nullbr --app-id YOUR_APP_ID tv 1396
# 获取电影ed2k资源
python -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-ed2k 78
# 获取剧集单集ed2k资源
python -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-ed2k 1396 1 3
# 获取电影video资源(m3u8/http)
python -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-video 78
# 获取剧集单集video资源(m3u8/http)
python -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-video 1396 3 4
```
#### 方式三:使用uv运行
```bash
# 搜索
uv run nullbr --app-id YOUR_APP_ID search "复仇者联盟"
# 获取电影信息
uv run nullbr --app-id YOUR_APP_ID movie 299536
# 或者使用Python模块方式
uv run python -m nullbr --app-id YOUR_APP_ID search "复仇者联盟"
```
## API 参考
### NullbrSDK
主要的SDK类,提供所有API方法。
#### 初始化
```python
sdk = NullbrSDK(
app_id="your_app_id", # 必需:您的APP ID
api_key="your_api_key", # 可选:某些资源操作需要
base_url="https://api.nullbr.eu.org" # 默认API地址
)
```
#### 搜索功能
##### search(query, page=1)
搜索电影、电视剧、合集和人物
**参数:**
- `query` (str): 搜索关键词
- `page` (int): 页码,默认为1
**返回:** `SearchResponse` 对象
```python
{
"page": 1,
"total_pages": 10,
"total_results": 200,
"items": [
{
"media_type": "movie",
"tmdbid": 299536,
"poster": "https://image.tmdb.org/t/p/w154/poster.jpg",
"title": "复仇者联盟4:终局之战",
"overview": "电影简介...",
"vote_average": 8.4,
"release_date": "2019-04-24",
"rank": 1
}
]
}
```
##### get_list(listid, page=1)
获取列表详细信息
**参数:**
- `listid` (int): 列表ID
- `page` (int): 页码,默认为1
**返回:** `ListResponse` 对象
#### 电影功能
##### get_movie(tmdbid)
获取电影详细信息
**参数:**
- `tmdbid` (int): 电影的TMDB ID
**返回:** `MovieResponse` 对象
```python
{
"id": 299536,
"poster": "https://image.tmdb.org/t/p/w154/poster.jpg",
"title": "复仇者联盟4:终局之战",
"overview": "电影简介...",
"vote": 8.4,
"release_date": "2019-04-24",
"has_115": True, # 是否有115网盘资源
"has_magnet": True, # 是否有磁力链接
"has_ed2k": True, # 是否有ed2k链接
"has_video": False # 是否有在线视频
}
```
##### get_movie_115(tmdbid, page=1)
获取电影115网盘资源(**需要API Key**)
**参数:**
- `tmdbid` (int): 电影的TMDB ID
- `page` (int): 页码,默认为1
**返回:** `Movie115Response` 对象
```python
{
"id": 299536,
"media_type": "movie",
"page": 1,
"total_page": 3,
"items": [
{
"title": "复仇者联盟4:终局之战 (2019)",
"size": "4.2 GB",
"share_link": "https://115.com/s/sw1a2b3c4d5"
}
]
}
```
##### get_movie_magnet(tmdbid)
获取电影磁力资源
**参数:**
- `tmdbid` (int): 电影的TMDB ID
**返回:** `MovieMagnetResponse` 对象
```python
{
"id": 299536,
"media_type": "movie",
"magnet": [
{
"name": "Avengers.Endgame.2019.2160p.BluRay.x265.mkv",
"size": "15.2 GB",
"magnet": "magnet:?xt=urn:btih:...",
"resolution": "2160p",
"source": "Blu-ray",
"quality": ["4K", "HDR"],
"zh_sub": 1 # 1: 有中文字幕, 0: 无中文字幕
}
]
}
```
##### get_movie_ed2k(tmdbid)
获取电影ed2k资源
**参数:**
- `tmdbid` (int): 电影的TMDB ID
**返回:** `MovieEd2kResponse` 对象
```python
{
"id": 78,
"media_type": "movie",
"ed2k": [
{
"name": "Blade.Runner.1982.2160p.UHD.BluRay.Remux.HEVC.HDR.mkv",
"size": "44.69 GB",
"ed2k": "ed2k://|file|Blade.Runner.1982.2160p.UHD.BluRay.Remux.HEVC.HDR.mkv|47982811361|DBF0FC2DE8A047ABD35A1CDA29CAB5E6|/",
"resolution": "2160p",
"source": "Ultra HD Blu-ray",
"quality": ["Remux", "HDR10", "HD"],
"zh_sub": 1 # 1: 有中文字幕, 0: 无中文字幕
}
]
}
```
**资源说明:**
- 返回最多8个ed2k资源
- 按是否包含中文字幕分类,从大到小排序
- 优先返回5个最大的有中文字幕资源
- 再返回3个最大的无中文字幕资源
**参数:**
- `tmdbid` (int): 电影的TMDB ID
**返回:** `MovieVideoResponse` 对象
```python
{
"id": 78,
"media_type": "movie",
"video": [
{
"name": "1",
"type": "m3u8",
"link": "https://m3u8.heimuertv.com/play/82d683b472b04e0292b41d05a739f4e1.m3u8"
},
{
"name": "HD中字",
"type": "http",
"link": "https://dow7.lzidw.com/20221016/17423_a0c8e991/银翼杀手.Blade.Runner.1982.国语.mp4"
},
{
"name": "HD中字",
"type": "m3u8",
"link": "https://vip1.lz-cdn1.com/20221016/17423_a0c8e991/index.m3u8"
}
]
}
```
**资源说明:**
- 包含m3u8和http两种类型的video资源
- m3u8适用于流媒体播放
- http为直链下载
#### 电视剧功能
##### get_tv(tmdbid)
获取电视剧详细信息
**参数:**
- `tmdbid` (int): 电视剧的TMDB ID
**返回:** `TVResponse` 对象
```python
{
"id": 1396,
"poster": "https://image.tmdb.org/t/p/w154/poster.jpg",
"title": "绝命毒师",
"overview": "剧集简介...",
"vote": 9.5,
"release_date": "2008-01-20",
"number_of_seasons": 5,
"has_115": True,
"has_magnet": True,
"has_ed2k": True,
"has_video": False
}
```
##### get_tv_115(tmdbid, page=1)
获取电视剧115网盘资源(**需要API Key**)
**参数:**
- `tmdbid` (int): 电视剧的TMDB ID
- `page` (int): 页码,默认为1
**返回:** `TV115Response` 对象
##### get_tv_season(tmdbid, season_number)
获取电视剧单季详细信息
**参数:**
- `tmdbid` (int): 电视剧的TMDB ID
- `season_number` (int): 季数
**返回:** `TVSeasonResponse` 对象
```python
{
"tv_show_id": 1396,
"season_number": 1,
"name": "第一季",
"overview": "第一季简介...",
"air_date": "2008-01-20",
"poster": "https://image.tmdb.org/t/p/w154/season_poster.jpg",
"episode_count": 7,
"vote_average": 9.0,
"has_magnet": True
}
```
##### get_tv_season_magnet(tmdbid, season_number)
获取电视剧季磁力资源
**参数:**
- `tmdbid` (int): 电视剧的TMDB ID
- `season_number` (int): 季数
**返回:** `TVSeasonMagnetResponse` 对象
##### get_tv_episode_ed2k(tmdbid, season_number, episode_number)
获取剧集单集ed2k资源
**参数:**
- `tmdbid` (int): 电视剧的TMDB ID
- `season_number` (int): 季数
- `episode_number` (int): 集数
**返回:** `TVEpisodeEd2kResponse` 对象
```python
{
"tv_show_id": 1396,
"season_number": 1,
"episode_number": 3,
"media_type": "tv",
"ed2k": [
{
"name": "绝命毒师.第二季.2009.EP03.BD1080P.X264.AAC.English.CHS-ENG.BDYS.mp4",
"size": "4.83 GB",
"ed2k": "ed2k://|file|绝命毒师.第二季.2009.EP03.BD1080P.X264.AAC.English.CHS-ENG.BDYS.mp4|5182481944|594856F827D5F9553576428B8E29DE6A|/",
"resolution": null,
"source": null,
"quality": null,
"zh_sub": 1
}
]
}
```
##### get_tv_episode_video(tmdbid, season_number, episode_number)
获取剧集单集video资源(m3u8/http)
**参数:**
- `tmdbid` (int): 剧集的TMDB ID
- `season_number` (int): 季数
- `episode_number` (int): 集数
**返回:** `TVEpisodeVideoResponse` 对象
```python
{
"tv_show_id": 1396,
"season_number": 3,
"episode_number": 4,
"media_type": "tv",
"video": [
{
"type": "m3u8",
"name": "4",
"link": "https://m3u8.heimuertv.com/play/e8146ae6dbb14a22a00c841cd3c016b2.m3u8"
},
{
"type": "http",
"name": "第04集",
"link": "https://dow4.lzidw.com/20220521/13365_133713b3/绝命毒师S0304.mp4"
},
{
"type": "m3u8",
"name": "第04集",
"link": "https://vip.lz-cdn4.com/20220521/13365_133713b3/index.m3u8"
}
]
}
```
#### 合集功能
##### get_collection(tmdbid)
获取电影合集详细信息
**参数:**
- `tmdbid` (int): 合集的TMDB ID
**返回:** `CollectionResponse` 对象
```python
{
"id": 86311,
"poster": "https://image.tmdb.org/t/p/w154/collection_poster.jpg",
"title": "复仇者联盟系列",
"overview": "合集简介...",
"vote": "8.1",
"release_date": "2012-04-25",
"has_115": True,
"items": [
{
"media_type": "movie",
"tmdbid": 24428,
"poster": "https://image.tmdb.org/t/p/w154/poster1.jpg",
"title": "复仇者联盟",
"overview": "电影简介...",
"vote_average": 7.7,
"release_date": "2012-04-25"
}
]
}
```
##### get_collection_115(tmdbid, page=1)
获取电影合集115网盘资源(**需要API Key**)
**参数:**
- `tmdbid` (int): 合集的TMDB ID
- `page` (int): 页码,默认为1
**返回:** `Collection115Response` 对象
## 命令行工具
### 可用命令
| 命令 | 描述 | 参数 | 需要API Key |
|------|------|------|-------------|
| `search` | 搜索媒体内容 | `<query> [--page]` | ❌ |
| `movie` | 获取电影信息 | `<tmdbid>` | ❌ |
| `tv` | 获取电视剧信息 | `<tmdbid>` | ❌ |
| `movie-ed2k` | 获取电影ed2k资源 | `<tmdbid>` | ✅ |
| `tv-episode-ed2k` | 获取剧集单集ed2k资源 | `<tmdbid> <season> <episode>` | ✅ |
| `movie-video` | 获取电影video资源 | `<tmdbid>` | ✅ |
| `tv-episode-video` | 获取剧集单集video资源 | `<tmdbid> <season> <episode>` | ✅ |
### 使用示例
```bash
# 设置环境变量(推荐)
export NULLBR_APP_ID="your_app_id"
export NULLBR_API_KEY="your_api_key"
# 使用全局命令(推荐)
nullbr --app-id $NULLBR_APP_ID search "权力的游戏"
nullbr --app-id $NULLBR_APP_ID movie 299536
nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY movie-ed2k 78
nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY tv-episode-ed2k 1396 1 3
# 或者使用Python模块方式
python -m nullbr --app-id $NULLBR_APP_ID search "权力的游戏"
python -m nullbr --app-id $NULLBR_APP_ID movie 299536
python -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY movie-ed2k 78
python -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY tv-episode-ed2k 1396 1 3
python -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY movie-video 78
python -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY tv-episode-video 1396 3 4
```
### 输出格式
所有命令输出标准JSON格式,方便程序处理:
```bash
# 使用全局命令
nullbr --app-id YOUR_APP_ID search "复仇者联盟" | jq '.items[0].title'
# 或者使用Python模块
python -m nullbr --app-id YOUR_APP_ID search "复仇者联盟" | jq '.items[0].title'
```
## 数据模型
### 基础模型
#### MediaItem
媒体项目基础信息
```python
{
"media_type": str, # "movie", "tv", "collection", "person"
"tmdbid": int, # TMDB ID
"poster": str, # 海报URL
"title": str, # 标题
"overview": str, # 简介
"vote_average": float, # 评分
"release_date": str, # 发布日期
"rank": int # 搜索排名(仅搜索结果)
}
```
### 响应模型
#### SearchResponse
搜索结果响应
```python
{
"page": int, # 当前页码
"total_pages": int, # 总页数
"total_results": int, # 总结果数
"items": [MediaItem] # 媒体项目列表
}
```
#### ListResponse
列表响应
```python
{
"id": int, # 列表ID
"name": str, # 列表名称
"description": str, # 列表描述
"updated_dt": str, # 更新时间
"page": int, # 当前页码
"total_page": int, # 总页数
"items": [MediaItem] # 媒体项目列表
}
```
#### MovieResponse
电影信息响应
```python
{
"id": int, # 电影ID
"poster": str, # 海报URL
"title": str, # 电影标题
"overview": str, # 电影简介
"vote": float, # 评分
"release_date": str, # 上映日期
"has_115": bool, # 是否有115网盘资源
"has_magnet": bool, # 是否有磁力链接
"has_ed2k": bool, # 是否有ed2k链接
"has_video": bool # 是否有在线视频
}
```
#### TVResponse
电视剧信息响应
```python
{
"id": int, # 电视剧ID
"poster": str, # 海报URL
"title": str, # 电视剧标题
"overview": str, # 电视剧简介
"vote": float, # 评分
"release_date": str, # 首播日期
"number_of_seasons": int, # 季数
"has_115": bool, # 是否有115网盘资源
"has_magnet": bool, # 是否有磁力链接
"has_ed2k": bool, # 是否有ed2k链接
"has_video": bool # 是否有在线视频
}
```
### 资源模型
#### Movie115Item / TV115Item
115网盘资源项目
```python
{
"title": str, # 资源标题
"size": str, # 文件大小
"share_link": str # 分享链接
}
```
#### MovieMagnetItem
磁力资源项目
```python
{
"name": str, # 文件名
"size": str, # 文件大小
"magnet": str, # 磁力链接
"resolution": str, # 分辨率
"source": str, # 来源(Blu-ray, Web等)
"quality": str | [str], # 质量标签
"zh_sub": int # 中文字幕(1有0无)
}
```
#### MovieEd2kItem
ed2k资源项目
```python
{
"name": str, # 文件名
"size": str, # 文件大小
"ed2k": str, # ed2k链接
"resolution": str, # 分辨率
"source": str | null, # 来源
"quality": str | [str] | null, # 质量标签
"zh_sub": int # 中文字幕(1有0无)
}
```
#### MovieVideoItem
video资源项目
```python
{
"name": str, # 资源名称
"type": str, # 资源类型("m3u8" 或 "http")
"link": str # 资源链接
}
```
## 完整示例
```python
import os
from nullbr import NullbrSDK
def main():
# 初始化SDK
sdk = NullbrSDK(
app_id=os.getenv("NULLBR_APP_ID"),
api_key=os.getenv("NULLBR_API_KEY")
)
# 搜索电影
print("=== 搜索电影 ===")
results = sdk.search("银翼杀手")
for item in results.items[:3]:
print(f"{item.title} ({item.release_date}) - 评分: {item.vote_average}")
# 获取电影详情
print("\n=== 电影详情 ===")
movie = sdk.get_movie(78) # 银翼杀手
print(f"标题: {movie.title}")
print(f"简介: {movie.overview}")
print(f"评分: {movie.vote}")
print(f"有ed2k资源: {movie.has_ed2k}")
# 获取ed2k资源
if movie.has_ed2k:
print("\n=== ed2k资源 ===")
ed2k_resources = sdk.get_movie_ed2k(78)
for i, resource in enumerate(ed2k_resources.ed2k[:3], 1):
print(f"{i}. {resource.name}")
print(f" 大小: {resource.size}")
print(f" 分辨率: {resource.resolution}")
print(f" 来源: {resource.source}")
print(f" 中文字幕: {'是' if resource.zh_sub else '否'}")
print()
# 获取剧集单集ed2k资源
print("=== 剧集单集ed2k资源 ===")
tv_ed2k = sdk.get_tv_episode_ed2k(1396, 1, 1) # 绝命毒师 S01E01
for resource in tv_ed2k.ed2k[:2]:
print(f"剧集: 第{tv_ed2k.season_number}季第{tv_ed2k.episode_number}集")
print(f"文件: {resource.name}")
print(f"大小: {resource.size}")
print(f"中文字幕: {'是' if resource.zh_sub else '否'}")
print()
# 获取电影video资源
print("=== 电影video资源 ===")
movie_video = sdk.get_movie_video(78) # 银翼杀手
for resource in movie_video.video[:3]:
print(f"名称: {resource.name}")
print(f"类型: {resource.type}")
print(f"链接: {resource.link[:50]}...")
print()
# 获取剧集单集video资源
print("=== 剧集单集video资源 ===")
tv_video = sdk.get_tv_episode_video(1396, 3, 4) # 绝命毒师 S03E04
for resource in tv_video.video[:2]:
print(f"剧集: 第{tv_video.season_number}季第{tv_video.episode_number}集")
print(f"名称: {resource.name}")
print(f"类型: {resource.type}")
print(f"链接: {resource.link[:50]}...")
print()
if __name__ == "__main__":
main()
```
## 错误处理
SDK会抛出以下异常:
- `httpx.HTTPError`: API请求失败
- `ValueError`: 参数错误(如缺少API Key)
```python
try:
movie = sdk.get_movie(299536)
except httpx.HTTPError as e:
print(f"API请求失败: {e}")
except ValueError as e:
print(f"参数错误: {e}")
```
## 开发
### 安装开发依赖
```bash
uv sync --dev
```
### 代码格式化和检查
```bash
# 格式化代码
uv run ruff format nullbr/
# 检查和修复代码
uv run ruff check --fix nullbr/
# 仅检查代码
uv run ruff check nullbr/
```
### 运行测试
```bash
uv run pytest
```
### 构建发布
```bash
uv build
```
## 许可证
本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。
## 贡献
欢迎提交Issue和Pull Request!
### 贡献指南
1. Fork 项目
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
## 更新日志
### v1.0.3
- ✅ 添加了电影video资源获取功能(m3u8/http)
- ✅ 添加了剧集单集video资源获取功能(m3u8/http)
- ✅ 添加了剧集单集ed2k资源获取功能
- ✅ 修复了CLI命令行使用问题,添加了`__main__.py`
- ✅ 优化了类型注解,使用Python 3.9+的内置泛型
- ✅ 完善了CLI命令行工具
- ✅ 增强了文档和示例代码
### v1.0.0
- 🎉 初始版本发布
- ✅ 支持搜索、电影、电视剧、合集信息获取
- ✅ 支持115、磁力、ed2k资源获取
- ✅ 提供完整的命令行工具
Raw data
{
"_id": null,
"home_page": null,
"name": "nullbr",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": "iLay1678 <ifwangs400@gmail.com>",
"keywords": "api, movies, nullbr, sdk, tmdb, tv",
"author": null,
"author_email": "iLay1678 <ifwangs400@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/d5/11/3e6a202bbaa488898f240e6bee51cbfaf398ef546be345ec0939a31ddcd4/nullbr-1.0.3.tar.gz",
"platform": null,
"description": "# nullbr\n\n\n[](https://opensource.org/licenses/MIT)\n[](https://www.python.org/downloads/)\n\nPython SDK for Nullbr API - \u7528\u4e8e\u8bbf\u95ee Nullbr API \u7684 Python SDK\n\n## \u529f\u80fd\u7279\u6027\n\n- \ud83d\udd0d \u641c\u7d22\u7535\u5f71\u3001\u7535\u89c6\u5267\u3001\u5408\u96c6\u548c\u4eba\u7269\n- \ud83c\udfac \u83b7\u53d6\u7535\u5f71\u8be6\u7ec6\u4fe1\u606f\u548c\u8d44\u6e90\u94fe\u63a5\uff08115\u7f51\u76d8\u3001\u78c1\u529b\u3001ed2k\u3001video\uff09\n- \ud83d\udcfa \u83b7\u53d6\u7535\u89c6\u5267\u8be6\u7ec6\u4fe1\u606f\u548c\u8d44\u6e90\u94fe\u63a5\uff08115\u7f51\u76d8\u3001\u78c1\u529b\u3001ed2k\u3001video\uff09\n- \ud83d\udcda \u83b7\u53d6\u5408\u96c6\u4fe1\u606f\u548c\u8d44\u6e90\u94fe\u63a5\n- \ud83c\udfaf \u652f\u6301\u5267\u96c6\u5b63\u5ea6\u548c\u5355\u96c6\u8d44\u6e90\u83b7\u53d6\n- \ud83d\udee0\ufe0f \u5b8c\u6574\u7684\u547d\u4ee4\u884c\u5de5\u5177\u652f\u6301\n- \ud83d\udd12 MIT \u8bb8\u53ef\u8bc1\n\n## \u5b89\u88c5\n\n### \u4f7f\u7528 uv \u5b89\u88c5\uff08\u63a8\u8350\uff09\n\n```bash\nuv add nullbr\n```\n\n### \u4f7f\u7528 pip \u5b89\u88c5\n\n```bash\npip install nullbr\n```\n\n### \u4ece\u6e90\u7801\u5b89\u88c5\n\n```bash\ngit clone https://github.com/iLay1678/nullbr-python.git\ncd nullbr\nuv sync\nuv pip install -e .\n```\n\n## \u5feb\u901f\u5f00\u59cb\n\n### \u57fa\u672c\u7528\u6cd5\n\n```python\nfrom nullbr import NullbrSDK\n\n# \u521d\u59cb\u5316SDK\nsdk = NullbrSDK(\n app_id=\"your_app_id\",\n api_key=\"your_api_key\" # \u53ef\u9009\uff0c\u67d0\u4e9b\u64cd\u4f5c\u9700\u8981\n)\n\n# \u641c\u7d22\u7535\u5f71\nresults = sdk.search(\"\u590d\u4ec7\u8005\u8054\u76df\")\nfor item in results.items:\n print(f\"{item.title} ({item.media_type}) - \u8bc4\u5206: {item.vote_average}\")\n\n# \u83b7\u53d6\u7535\u5f71\u8be6\u7ec6\u4fe1\u606f\nmovie = sdk.get_movie(299536) # \u590d\u4ec7\u8005\u8054\u76df4\u7684TMDB ID\nprint(f\"\u7535\u5f71\u540d\u79f0: {movie.title}\")\nprint(f\"\u8bc4\u5206: {movie.vote}\")\nprint(f\"\u4e0a\u6620\u65e5\u671f: {movie.release_date}\")\n\n# \u83b7\u53d6\u7535\u5f71ed2k\u8d44\u6e90\nif movie.has_ed2k:\n ed2k_resources = sdk.get_movie_ed2k(299536)\n for resource in ed2k_resources.ed2k:\n print(f\"\u8d44\u6e90: {resource.name}\")\n print(f\"\u5927\u5c0f: {resource.size}\")\n print(f\"\u5206\u8fa8\u7387: {resource.resolution}\")\n print(f\"\u4e2d\u6587\u5b57\u5e55: {'\u662f' if resource.zh_sub else '\u5426'}\")\n```\n\n### \u547d\u4ee4\u884c\u4f7f\u7528\n\n#### \u65b9\u5f0f\u4e00\uff1a\u4f7f\u7528\u5168\u5c40\u547d\u4ee4\uff08\u63a8\u8350\uff09\n\n\u5b89\u88c5\u540e\u53ef\u76f4\u63a5\u4f7f\u7528 `nullbr` \u547d\u4ee4\uff1a\n\n```bash\n# \u641c\u7d22\nnullbr --app-id YOUR_APP_ID search \"\u590d\u4ec7\u8005\u8054\u76df\"\n\n# \u83b7\u53d6\u7535\u5f71\u4fe1\u606f\nnullbr --app-id YOUR_APP_ID movie 299536\n\n# \u83b7\u53d6\u7535\u89c6\u5267\u4fe1\u606f\nnullbr --app-id YOUR_APP_ID tv 1396\n\n# \u83b7\u53d6\u7535\u5f71ed2k\u8d44\u6e90\nnullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-ed2k 78\n\n# \u83b7\u53d6\u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90\nnullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-ed2k 1396 1 3\n\n# \u83b7\u53d6\u7535\u5f71video\u8d44\u6e90\uff08m3u8/http\uff09\nnullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-video 78\n\n# \u83b7\u53d6\u5267\u96c6\u5355\u96c6video\u8d44\u6e90\uff08m3u8/http\uff09\nnullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-video 1396 3 4\n```\n\n#### \u65b9\u5f0f\u4e8c\uff1a\u4f7f\u7528Python\u6a21\u5757\n\n```bash\n# \u641c\u7d22\npython -m nullbr --app-id YOUR_APP_ID search \"\u590d\u4ec7\u8005\u8054\u76df\"\n\n# \u83b7\u53d6\u7535\u5f71\u4fe1\u606f\npython -m nullbr --app-id YOUR_APP_ID movie 299536\n\n# \u83b7\u53d6\u7535\u89c6\u5267\u4fe1\u606f\npython -m nullbr --app-id YOUR_APP_ID tv 1396\n\n# \u83b7\u53d6\u7535\u5f71ed2k\u8d44\u6e90\npython -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-ed2k 78\n\n# \u83b7\u53d6\u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90\npython -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-ed2k 1396 1 3\n\n# \u83b7\u53d6\u7535\u5f71video\u8d44\u6e90\uff08m3u8/http\uff09\npython -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY movie-video 78\n\n# \u83b7\u53d6\u5267\u96c6\u5355\u96c6video\u8d44\u6e90\uff08m3u8/http\uff09\npython -m nullbr --app-id YOUR_APP_ID --api-key YOUR_API_KEY tv-episode-video 1396 3 4\n```\n\n#### \u65b9\u5f0f\u4e09\uff1a\u4f7f\u7528uv\u8fd0\u884c\n\n```bash\n# \u641c\u7d22\nuv run nullbr --app-id YOUR_APP_ID search \"\u590d\u4ec7\u8005\u8054\u76df\"\n\n# \u83b7\u53d6\u7535\u5f71\u4fe1\u606f\nuv run nullbr --app-id YOUR_APP_ID movie 299536\n\n# \u6216\u8005\u4f7f\u7528Python\u6a21\u5757\u65b9\u5f0f\nuv run python -m nullbr --app-id YOUR_APP_ID search \"\u590d\u4ec7\u8005\u8054\u76df\"\n```\n\n## API \u53c2\u8003\n\n### NullbrSDK\n\n\u4e3b\u8981\u7684SDK\u7c7b\uff0c\u63d0\u4f9b\u6240\u6709API\u65b9\u6cd5\u3002\n\n#### \u521d\u59cb\u5316\n\n```python\nsdk = NullbrSDK(\n app_id=\"your_app_id\", # \u5fc5\u9700\uff1a\u60a8\u7684APP ID\n api_key=\"your_api_key\", # \u53ef\u9009\uff1a\u67d0\u4e9b\u8d44\u6e90\u64cd\u4f5c\u9700\u8981\n base_url=\"https://api.nullbr.eu.org\" # \u9ed8\u8ba4API\u5730\u5740\n)\n```\n\n#### \u641c\u7d22\u529f\u80fd\n\n##### search(query, page=1)\n\n\u641c\u7d22\u7535\u5f71\u3001\u7535\u89c6\u5267\u3001\u5408\u96c6\u548c\u4eba\u7269\n\n**\u53c2\u6570\uff1a**\n- `query` (str): \u641c\u7d22\u5173\u952e\u8bcd\n- `page` (int): \u9875\u7801\uff0c\u9ed8\u8ba4\u4e3a1\n\n**\u8fd4\u56de\uff1a** `SearchResponse` \u5bf9\u8c61\n\n```python\n{\n \"page\": 1,\n \"total_pages\": 10,\n \"total_results\": 200,\n \"items\": [\n {\n \"media_type\": \"movie\",\n \"tmdbid\": 299536,\n \"poster\": \"https://image.tmdb.org/t/p/w154/poster.jpg\",\n \"title\": \"\u590d\u4ec7\u8005\u8054\u76df4\uff1a\u7ec8\u5c40\u4e4b\u6218\",\n \"overview\": \"\u7535\u5f71\u7b80\u4ecb...\",\n \"vote_average\": 8.4,\n \"release_date\": \"2019-04-24\",\n \"rank\": 1\n }\n ]\n}\n```\n\n##### get_list(listid, page=1)\n\n\u83b7\u53d6\u5217\u8868\u8be6\u7ec6\u4fe1\u606f\n\n**\u53c2\u6570\uff1a**\n- `listid` (int): \u5217\u8868ID\n- `page` (int): \u9875\u7801\uff0c\u9ed8\u8ba4\u4e3a1\n\n**\u8fd4\u56de\uff1a** `ListResponse` \u5bf9\u8c61\n\n#### \u7535\u5f71\u529f\u80fd\n\n##### get_movie(tmdbid)\n\n\u83b7\u53d6\u7535\u5f71\u8be6\u7ec6\u4fe1\u606f\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u5f71\u7684TMDB ID\n\n**\u8fd4\u56de\uff1a** `MovieResponse` \u5bf9\u8c61\n\n```python\n{\n \"id\": 299536,\n \"poster\": \"https://image.tmdb.org/t/p/w154/poster.jpg\",\n \"title\": \"\u590d\u4ec7\u8005\u8054\u76df4\uff1a\u7ec8\u5c40\u4e4b\u6218\",\n \"overview\": \"\u7535\u5f71\u7b80\u4ecb...\",\n \"vote\": 8.4,\n \"release_date\": \"2019-04-24\",\n \"has_115\": True, # \u662f\u5426\u6709115\u7f51\u76d8\u8d44\u6e90\n \"has_magnet\": True, # \u662f\u5426\u6709\u78c1\u529b\u94fe\u63a5\n \"has_ed2k\": True, # \u662f\u5426\u6709ed2k\u94fe\u63a5\n \"has_video\": False # \u662f\u5426\u6709\u5728\u7ebf\u89c6\u9891\n}\n```\n\n##### get_movie_115(tmdbid, page=1)\n\n\u83b7\u53d6\u7535\u5f71115\u7f51\u76d8\u8d44\u6e90\uff08**\u9700\u8981API Key**\uff09\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u5f71\u7684TMDB ID\n- `page` (int): \u9875\u7801\uff0c\u9ed8\u8ba4\u4e3a1\n\n**\u8fd4\u56de\uff1a** `Movie115Response` \u5bf9\u8c61\n\n```python\n{\n \"id\": 299536,\n \"media_type\": \"movie\",\n \"page\": 1,\n \"total_page\": 3,\n \"items\": [\n {\n \"title\": \"\u590d\u4ec7\u8005\u8054\u76df4\uff1a\u7ec8\u5c40\u4e4b\u6218 (2019)\",\n \"size\": \"4.2 GB\",\n \"share_link\": \"https://115.com/s/sw1a2b3c4d5\"\n }\n ]\n}\n```\n\n##### get_movie_magnet(tmdbid)\n\n\u83b7\u53d6\u7535\u5f71\u78c1\u529b\u8d44\u6e90\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u5f71\u7684TMDB ID\n\n**\u8fd4\u56de\uff1a** `MovieMagnetResponse` \u5bf9\u8c61\n\n```python\n{\n \"id\": 299536,\n \"media_type\": \"movie\",\n \"magnet\": [\n {\n \"name\": \"Avengers.Endgame.2019.2160p.BluRay.x265.mkv\",\n \"size\": \"15.2 GB\",\n \"magnet\": \"magnet:?xt=urn:btih:...\",\n \"resolution\": \"2160p\",\n \"source\": \"Blu-ray\",\n \"quality\": [\"4K\", \"HDR\"],\n \"zh_sub\": 1 # 1: \u6709\u4e2d\u6587\u5b57\u5e55, 0: \u65e0\u4e2d\u6587\u5b57\u5e55\n }\n ]\n}\n```\n\n##### get_movie_ed2k(tmdbid)\n\n\u83b7\u53d6\u7535\u5f71ed2k\u8d44\u6e90\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u5f71\u7684TMDB ID\n\n**\u8fd4\u56de\uff1a** `MovieEd2kResponse` \u5bf9\u8c61\n\n```python\n{\n \"id\": 78,\n \"media_type\": \"movie\",\n \"ed2k\": [\n {\n \"name\": \"Blade.Runner.1982.2160p.UHD.BluRay.Remux.HEVC.HDR.mkv\",\n \"size\": \"44.69 GB\",\n \"ed2k\": \"ed2k://|file|Blade.Runner.1982.2160p.UHD.BluRay.Remux.HEVC.HDR.mkv|47982811361|DBF0FC2DE8A047ABD35A1CDA29CAB5E6|/\",\n \"resolution\": \"2160p\",\n \"source\": \"Ultra HD Blu-ray\",\n \"quality\": [\"Remux\", \"HDR10\", \"HD\"],\n \"zh_sub\": 1 # 1: \u6709\u4e2d\u6587\u5b57\u5e55, 0: \u65e0\u4e2d\u6587\u5b57\u5e55\n }\n ]\n}\n```\n\n**\u8d44\u6e90\u8bf4\u660e\uff1a**\n- \u8fd4\u56de\u6700\u591a8\u4e2aed2k\u8d44\u6e90\n- \u6309\u662f\u5426\u5305\u542b\u4e2d\u6587\u5b57\u5e55\u5206\u7c7b\uff0c\u4ece\u5927\u5230\u5c0f\u6392\u5e8f\n- \u4f18\u5148\u8fd4\u56de5\u4e2a\u6700\u5927\u7684\u6709\u4e2d\u6587\u5b57\u5e55\u8d44\u6e90\n- \u518d\u8fd4\u56de3\u4e2a\u6700\u5927\u7684\u65e0\u4e2d\u6587\u5b57\u5e55\u8d44\u6e90\n\n\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u5f71\u7684TMDB ID\n\n**\u8fd4\u56de\uff1a** `MovieVideoResponse` \u5bf9\u8c61\n\n```python\n{\n \"id\": 78,\n \"media_type\": \"movie\",\n \"video\": [\n {\n \"name\": \"1\",\n \"type\": \"m3u8\",\n \"link\": \"https://m3u8.heimuertv.com/play/82d683b472b04e0292b41d05a739f4e1.m3u8\"\n },\n {\n \"name\": \"HD\u4e2d\u5b57\",\n \"type\": \"http\",\n \"link\": \"https://dow7.lzidw.com/20221016/17423_a0c8e991/\u94f6\u7ffc\u6740\u624b.Blade.Runner.1982.\u56fd\u8bed.mp4\"\n },\n {\n \"name\": \"HD\u4e2d\u5b57\",\n \"type\": \"m3u8\",\n \"link\": \"https://vip1.lz-cdn1.com/20221016/17423_a0c8e991/index.m3u8\"\n }\n ]\n}\n```\n\n**\u8d44\u6e90\u8bf4\u660e\uff1a**\n- \u5305\u542bm3u8\u548chttp\u4e24\u79cd\u7c7b\u578b\u7684video\u8d44\u6e90\n- m3u8\u9002\u7528\u4e8e\u6d41\u5a92\u4f53\u64ad\u653e\n- http\u4e3a\u76f4\u94fe\u4e0b\u8f7d\n\n#### \u7535\u89c6\u5267\u529f\u80fd\n\n##### get_tv(tmdbid)\n\n\u83b7\u53d6\u7535\u89c6\u5267\u8be6\u7ec6\u4fe1\u606f\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u89c6\u5267\u7684TMDB ID\n\n**\u8fd4\u56de\uff1a** `TVResponse` \u5bf9\u8c61\n\n```python\n{\n \"id\": 1396,\n \"poster\": \"https://image.tmdb.org/t/p/w154/poster.jpg\",\n \"title\": \"\u7edd\u547d\u6bd2\u5e08\",\n \"overview\": \"\u5267\u96c6\u7b80\u4ecb...\",\n \"vote\": 9.5,\n \"release_date\": \"2008-01-20\",\n \"number_of_seasons\": 5,\n \"has_115\": True,\n \"has_magnet\": True,\n \"has_ed2k\": True,\n \"has_video\": False\n}\n```\n\n##### get_tv_115(tmdbid, page=1)\n\n\u83b7\u53d6\u7535\u89c6\u5267115\u7f51\u76d8\u8d44\u6e90\uff08**\u9700\u8981API Key**\uff09\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u89c6\u5267\u7684TMDB ID\n- `page` (int): \u9875\u7801\uff0c\u9ed8\u8ba4\u4e3a1\n\n**\u8fd4\u56de\uff1a** `TV115Response` \u5bf9\u8c61\n\n##### get_tv_season(tmdbid, season_number)\n\n\u83b7\u53d6\u7535\u89c6\u5267\u5355\u5b63\u8be6\u7ec6\u4fe1\u606f\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u89c6\u5267\u7684TMDB ID\n- `season_number` (int): \u5b63\u6570\n\n**\u8fd4\u56de\uff1a** `TVSeasonResponse` \u5bf9\u8c61\n\n```python\n{\n \"tv_show_id\": 1396,\n \"season_number\": 1,\n \"name\": \"\u7b2c\u4e00\u5b63\",\n \"overview\": \"\u7b2c\u4e00\u5b63\u7b80\u4ecb...\",\n \"air_date\": \"2008-01-20\",\n \"poster\": \"https://image.tmdb.org/t/p/w154/season_poster.jpg\",\n \"episode_count\": 7,\n \"vote_average\": 9.0,\n \"has_magnet\": True\n}\n```\n\n##### get_tv_season_magnet(tmdbid, season_number)\n\n\u83b7\u53d6\u7535\u89c6\u5267\u5b63\u78c1\u529b\u8d44\u6e90\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u89c6\u5267\u7684TMDB ID\n- `season_number` (int): \u5b63\u6570\n\n**\u8fd4\u56de\uff1a** `TVSeasonMagnetResponse` \u5bf9\u8c61\n\n##### get_tv_episode_ed2k(tmdbid, season_number, episode_number)\n\n\u83b7\u53d6\u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u7535\u89c6\u5267\u7684TMDB ID\n- `season_number` (int): \u5b63\u6570\n- `episode_number` (int): \u96c6\u6570\n\n**\u8fd4\u56de\uff1a** `TVEpisodeEd2kResponse` \u5bf9\u8c61\n\n```python\n{\n \"tv_show_id\": 1396,\n \"season_number\": 1,\n \"episode_number\": 3,\n \"media_type\": \"tv\",\n \"ed2k\": [\n {\n \"name\": \"\u7edd\u547d\u6bd2\u5e08.\u7b2c\u4e8c\u5b63.2009.EP03.BD1080P.X264.AAC.English.CHS-ENG.BDYS.mp4\",\n \"size\": \"4.83 GB\",\n \"ed2k\": \"ed2k://|file|\u7edd\u547d\u6bd2\u5e08.\u7b2c\u4e8c\u5b63.2009.EP03.BD1080P.X264.AAC.English.CHS-ENG.BDYS.mp4|5182481944|594856F827D5F9553576428B8E29DE6A|/\",\n \"resolution\": null,\n \"source\": null,\n \"quality\": null,\n \"zh_sub\": 1\n }\n ]\n}\n```\n\n##### get_tv_episode_video(tmdbid, season_number, episode_number)\n\n\u83b7\u53d6\u5267\u96c6\u5355\u96c6video\u8d44\u6e90\uff08m3u8/http\uff09\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u5267\u96c6\u7684TMDB ID\n- `season_number` (int): \u5b63\u6570\n- `episode_number` (int): \u96c6\u6570\n\n**\u8fd4\u56de\uff1a** `TVEpisodeVideoResponse` \u5bf9\u8c61\n\n```python\n{\n \"tv_show_id\": 1396,\n \"season_number\": 3,\n \"episode_number\": 4,\n \"media_type\": \"tv\",\n \"video\": [\n {\n \"type\": \"m3u8\",\n \"name\": \"4\",\n \"link\": \"https://m3u8.heimuertv.com/play/e8146ae6dbb14a22a00c841cd3c016b2.m3u8\"\n },\n {\n \"type\": \"http\",\n \"name\": \"\u7b2c04\u96c6\",\n \"link\": \"https://dow4.lzidw.com/20220521/13365_133713b3/\u7edd\u547d\u6bd2\u5e08S0304.mp4\"\n },\n {\n \"type\": \"m3u8\",\n \"name\": \"\u7b2c04\u96c6\",\n \"link\": \"https://vip.lz-cdn4.com/20220521/13365_133713b3/index.m3u8\"\n }\n ]\n}\n```\n\n#### \u5408\u96c6\u529f\u80fd\n\n##### get_collection(tmdbid)\n\n\u83b7\u53d6\u7535\u5f71\u5408\u96c6\u8be6\u7ec6\u4fe1\u606f\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u5408\u96c6\u7684TMDB ID\n\n**\u8fd4\u56de\uff1a** `CollectionResponse` \u5bf9\u8c61\n\n```python\n{\n \"id\": 86311,\n \"poster\": \"https://image.tmdb.org/t/p/w154/collection_poster.jpg\",\n \"title\": \"\u590d\u4ec7\u8005\u8054\u76df\u7cfb\u5217\",\n \"overview\": \"\u5408\u96c6\u7b80\u4ecb...\",\n \"vote\": \"8.1\",\n \"release_date\": \"2012-04-25\",\n \"has_115\": True,\n \"items\": [\n {\n \"media_type\": \"movie\",\n \"tmdbid\": 24428,\n \"poster\": \"https://image.tmdb.org/t/p/w154/poster1.jpg\",\n \"title\": \"\u590d\u4ec7\u8005\u8054\u76df\",\n \"overview\": \"\u7535\u5f71\u7b80\u4ecb...\",\n \"vote_average\": 7.7,\n \"release_date\": \"2012-04-25\"\n }\n ]\n}\n```\n\n##### get_collection_115(tmdbid, page=1)\n\n\u83b7\u53d6\u7535\u5f71\u5408\u96c6115\u7f51\u76d8\u8d44\u6e90\uff08**\u9700\u8981API Key**\uff09\n\n**\u53c2\u6570\uff1a**\n- `tmdbid` (int): \u5408\u96c6\u7684TMDB ID\n- `page` (int): \u9875\u7801\uff0c\u9ed8\u8ba4\u4e3a1\n\n**\u8fd4\u56de\uff1a** `Collection115Response` \u5bf9\u8c61\n\n## \u547d\u4ee4\u884c\u5de5\u5177\n\n### \u53ef\u7528\u547d\u4ee4\n\n| \u547d\u4ee4 | \u63cf\u8ff0 | \u53c2\u6570 | \u9700\u8981API Key |\n|------|------|------|-------------|\n| `search` | \u641c\u7d22\u5a92\u4f53\u5185\u5bb9 | `<query> [--page]` | \u274c |\n| `movie` | \u83b7\u53d6\u7535\u5f71\u4fe1\u606f | `<tmdbid>` | \u274c |\n| `tv` | \u83b7\u53d6\u7535\u89c6\u5267\u4fe1\u606f | `<tmdbid>` | \u274c |\n| `movie-ed2k` | \u83b7\u53d6\u7535\u5f71ed2k\u8d44\u6e90 | `<tmdbid>` | \u2705 |\n| `tv-episode-ed2k` | \u83b7\u53d6\u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90 | `<tmdbid> <season> <episode>` | \u2705 |\n| `movie-video` | \u83b7\u53d6\u7535\u5f71video\u8d44\u6e90 | `<tmdbid>` | \u2705 |\n| `tv-episode-video` | \u83b7\u53d6\u5267\u96c6\u5355\u96c6video\u8d44\u6e90 | `<tmdbid> <season> <episode>` | \u2705 |\n\n### \u4f7f\u7528\u793a\u4f8b\n\n```bash\n# \u8bbe\u7f6e\u73af\u5883\u53d8\u91cf\uff08\u63a8\u8350\uff09\nexport NULLBR_APP_ID=\"your_app_id\"\nexport NULLBR_API_KEY=\"your_api_key\"\n\n# \u4f7f\u7528\u5168\u5c40\u547d\u4ee4\uff08\u63a8\u8350\uff09\nnullbr --app-id $NULLBR_APP_ID search \"\u6743\u529b\u7684\u6e38\u620f\"\nnullbr --app-id $NULLBR_APP_ID movie 299536\nnullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY movie-ed2k 78\nnullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY tv-episode-ed2k 1396 1 3\n\n# \u6216\u8005\u4f7f\u7528Python\u6a21\u5757\u65b9\u5f0f\npython -m nullbr --app-id $NULLBR_APP_ID search \"\u6743\u529b\u7684\u6e38\u620f\"\npython -m nullbr --app-id $NULLBR_APP_ID movie 299536\npython -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY movie-ed2k 78\npython -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY tv-episode-ed2k 1396 1 3\npython -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY movie-video 78\npython -m nullbr --app-id $NULLBR_APP_ID --api-key $NULLBR_API_KEY tv-episode-video 1396 3 4\n```\n\n### \u8f93\u51fa\u683c\u5f0f\n\n\u6240\u6709\u547d\u4ee4\u8f93\u51fa\u6807\u51c6JSON\u683c\u5f0f\uff0c\u65b9\u4fbf\u7a0b\u5e8f\u5904\u7406\uff1a\n\n```bash\n# \u4f7f\u7528\u5168\u5c40\u547d\u4ee4\nnullbr --app-id YOUR_APP_ID search \"\u590d\u4ec7\u8005\u8054\u76df\" | jq '.items[0].title'\n\n# \u6216\u8005\u4f7f\u7528Python\u6a21\u5757\npython -m nullbr --app-id YOUR_APP_ID search \"\u590d\u4ec7\u8005\u8054\u76df\" | jq '.items[0].title'\n```\n\n## \u6570\u636e\u6a21\u578b\n\n### \u57fa\u7840\u6a21\u578b\n\n#### MediaItem\n\u5a92\u4f53\u9879\u76ee\u57fa\u7840\u4fe1\u606f\n```python\n{\n \"media_type\": str, # \"movie\", \"tv\", \"collection\", \"person\"\n \"tmdbid\": int, # TMDB ID\n \"poster\": str, # \u6d77\u62a5URL\n \"title\": str, # \u6807\u9898\n \"overview\": str, # \u7b80\u4ecb\n \"vote_average\": float, # \u8bc4\u5206\n \"release_date\": str, # \u53d1\u5e03\u65e5\u671f\n \"rank\": int # \u641c\u7d22\u6392\u540d\uff08\u4ec5\u641c\u7d22\u7ed3\u679c\uff09\n}\n```\n\n### \u54cd\u5e94\u6a21\u578b\n\n#### SearchResponse\n\u641c\u7d22\u7ed3\u679c\u54cd\u5e94\n```python\n{\n \"page\": int, # \u5f53\u524d\u9875\u7801\n \"total_pages\": int, # \u603b\u9875\u6570\n \"total_results\": int, # \u603b\u7ed3\u679c\u6570\n \"items\": [MediaItem] # \u5a92\u4f53\u9879\u76ee\u5217\u8868\n}\n```\n\n#### ListResponse\n\u5217\u8868\u54cd\u5e94\n```python\n{\n \"id\": int, # \u5217\u8868ID\n \"name\": str, # \u5217\u8868\u540d\u79f0\n \"description\": str, # \u5217\u8868\u63cf\u8ff0\n \"updated_dt\": str, # \u66f4\u65b0\u65f6\u95f4\n \"page\": int, # \u5f53\u524d\u9875\u7801\n \"total_page\": int, # \u603b\u9875\u6570\n \"items\": [MediaItem] # \u5a92\u4f53\u9879\u76ee\u5217\u8868\n}\n```\n\n#### MovieResponse\n\u7535\u5f71\u4fe1\u606f\u54cd\u5e94\n```python\n{\n \"id\": int, # \u7535\u5f71ID\n \"poster\": str, # \u6d77\u62a5URL\n \"title\": str, # \u7535\u5f71\u6807\u9898\n \"overview\": str, # \u7535\u5f71\u7b80\u4ecb\n \"vote\": float, # \u8bc4\u5206\n \"release_date\": str, # \u4e0a\u6620\u65e5\u671f\n \"has_115\": bool, # \u662f\u5426\u6709115\u7f51\u76d8\u8d44\u6e90\n \"has_magnet\": bool, # \u662f\u5426\u6709\u78c1\u529b\u94fe\u63a5\n \"has_ed2k\": bool, # \u662f\u5426\u6709ed2k\u94fe\u63a5\n \"has_video\": bool # \u662f\u5426\u6709\u5728\u7ebf\u89c6\u9891\n}\n```\n\n#### TVResponse\n\u7535\u89c6\u5267\u4fe1\u606f\u54cd\u5e94\n```python\n{\n \"id\": int, # \u7535\u89c6\u5267ID\n \"poster\": str, # \u6d77\u62a5URL\n \"title\": str, # \u7535\u89c6\u5267\u6807\u9898\n \"overview\": str, # \u7535\u89c6\u5267\u7b80\u4ecb\n \"vote\": float, # \u8bc4\u5206\n \"release_date\": str, # \u9996\u64ad\u65e5\u671f\n \"number_of_seasons\": int, # \u5b63\u6570\n \"has_115\": bool, # \u662f\u5426\u6709115\u7f51\u76d8\u8d44\u6e90\n \"has_magnet\": bool, # \u662f\u5426\u6709\u78c1\u529b\u94fe\u63a5\n \"has_ed2k\": bool, # \u662f\u5426\u6709ed2k\u94fe\u63a5\n \"has_video\": bool # \u662f\u5426\u6709\u5728\u7ebf\u89c6\u9891\n}\n```\n\n### \u8d44\u6e90\u6a21\u578b\n\n#### Movie115Item / TV115Item\n115\u7f51\u76d8\u8d44\u6e90\u9879\u76ee\n```python\n{\n \"title\": str, # \u8d44\u6e90\u6807\u9898\n \"size\": str, # \u6587\u4ef6\u5927\u5c0f\n \"share_link\": str # \u5206\u4eab\u94fe\u63a5\n}\n```\n\n#### MovieMagnetItem\n\u78c1\u529b\u8d44\u6e90\u9879\u76ee\n```python\n{\n \"name\": str, # \u6587\u4ef6\u540d\n \"size\": str, # \u6587\u4ef6\u5927\u5c0f\n \"magnet\": str, # \u78c1\u529b\u94fe\u63a5\n \"resolution\": str, # \u5206\u8fa8\u7387\n \"source\": str, # \u6765\u6e90\uff08Blu-ray, Web\u7b49\uff09\n \"quality\": str | [str], # \u8d28\u91cf\u6807\u7b7e\n \"zh_sub\": int # \u4e2d\u6587\u5b57\u5e55\uff081\u67090\u65e0\uff09\n}\n```\n\n#### MovieEd2kItem\ned2k\u8d44\u6e90\u9879\u76ee\n```python\n{\n \"name\": str, # \u6587\u4ef6\u540d\n \"size\": str, # \u6587\u4ef6\u5927\u5c0f\n \"ed2k\": str, # ed2k\u94fe\u63a5\n \"resolution\": str, # \u5206\u8fa8\u7387\n \"source\": str | null, # \u6765\u6e90\n \"quality\": str | [str] | null, # \u8d28\u91cf\u6807\u7b7e\n \"zh_sub\": int # \u4e2d\u6587\u5b57\u5e55\uff081\u67090\u65e0\uff09\n}\n```\n\n#### MovieVideoItem\nvideo\u8d44\u6e90\u9879\u76ee\n```python\n{\n \"name\": str, # \u8d44\u6e90\u540d\u79f0\n \"type\": str, # \u8d44\u6e90\u7c7b\u578b\uff08\"m3u8\" \u6216 \"http\"\uff09\n \"link\": str # \u8d44\u6e90\u94fe\u63a5\n}\n```\n\n## \u5b8c\u6574\u793a\u4f8b\n\n```python\nimport os\nfrom nullbr import NullbrSDK\n\ndef main():\n # \u521d\u59cb\u5316SDK\n sdk = NullbrSDK(\n app_id=os.getenv(\"NULLBR_APP_ID\"),\n api_key=os.getenv(\"NULLBR_API_KEY\")\n )\n \n # \u641c\u7d22\u7535\u5f71\n print(\"=== \u641c\u7d22\u7535\u5f71 ===\")\n results = sdk.search(\"\u94f6\u7ffc\u6740\u624b\")\n for item in results.items[:3]:\n print(f\"{item.title} ({item.release_date}) - \u8bc4\u5206: {item.vote_average}\")\n \n # \u83b7\u53d6\u7535\u5f71\u8be6\u60c5\n print(\"\\n=== \u7535\u5f71\u8be6\u60c5 ===\")\n movie = sdk.get_movie(78) # \u94f6\u7ffc\u6740\u624b\n print(f\"\u6807\u9898: {movie.title}\")\n print(f\"\u7b80\u4ecb: {movie.overview}\")\n print(f\"\u8bc4\u5206: {movie.vote}\")\n print(f\"\u6709ed2k\u8d44\u6e90: {movie.has_ed2k}\")\n \n # \u83b7\u53d6ed2k\u8d44\u6e90\n if movie.has_ed2k:\n print(\"\\n=== ed2k\u8d44\u6e90 ===\")\n ed2k_resources = sdk.get_movie_ed2k(78)\n for i, resource in enumerate(ed2k_resources.ed2k[:3], 1):\n print(f\"{i}. {resource.name}\")\n print(f\" \u5927\u5c0f: {resource.size}\")\n print(f\" \u5206\u8fa8\u7387: {resource.resolution}\")\n print(f\" \u6765\u6e90: {resource.source}\")\n print(f\" \u4e2d\u6587\u5b57\u5e55: {'\u662f' if resource.zh_sub else '\u5426'}\")\n print()\n \n # \u83b7\u53d6\u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90\n print(\"=== \u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90 ===\")\n tv_ed2k = sdk.get_tv_episode_ed2k(1396, 1, 1) # \u7edd\u547d\u6bd2\u5e08 S01E01\n for resource in tv_ed2k.ed2k[:2]:\n print(f\"\u5267\u96c6: \u7b2c{tv_ed2k.season_number}\u5b63\u7b2c{tv_ed2k.episode_number}\u96c6\")\n print(f\"\u6587\u4ef6: {resource.name}\")\n print(f\"\u5927\u5c0f: {resource.size}\")\n print(f\"\u4e2d\u6587\u5b57\u5e55: {'\u662f' if resource.zh_sub else '\u5426'}\")\n print()\n \n # \u83b7\u53d6\u7535\u5f71video\u8d44\u6e90\n print(\"=== \u7535\u5f71video\u8d44\u6e90 ===\")\n movie_video = sdk.get_movie_video(78) # \u94f6\u7ffc\u6740\u624b\n for resource in movie_video.video[:3]:\n print(f\"\u540d\u79f0: {resource.name}\")\n print(f\"\u7c7b\u578b: {resource.type}\")\n print(f\"\u94fe\u63a5: {resource.link[:50]}...\")\n print()\n \n # \u83b7\u53d6\u5267\u96c6\u5355\u96c6video\u8d44\u6e90\n print(\"=== \u5267\u96c6\u5355\u96c6video\u8d44\u6e90 ===\")\n tv_video = sdk.get_tv_episode_video(1396, 3, 4) # \u7edd\u547d\u6bd2\u5e08 S03E04\n for resource in tv_video.video[:2]:\n print(f\"\u5267\u96c6: \u7b2c{tv_video.season_number}\u5b63\u7b2c{tv_video.episode_number}\u96c6\")\n print(f\"\u540d\u79f0: {resource.name}\")\n print(f\"\u7c7b\u578b: {resource.type}\")\n print(f\"\u94fe\u63a5: {resource.link[:50]}...\")\n print()\n\nif __name__ == \"__main__\":\n main()\n```\n\n## \u9519\u8bef\u5904\u7406\n\nSDK\u4f1a\u629b\u51fa\u4ee5\u4e0b\u5f02\u5e38\uff1a\n\n- `httpx.HTTPError`: API\u8bf7\u6c42\u5931\u8d25\n- `ValueError`: \u53c2\u6570\u9519\u8bef\uff08\u5982\u7f3a\u5c11API Key\uff09\n\n```python\ntry:\n movie = sdk.get_movie(299536)\nexcept httpx.HTTPError as e:\n print(f\"API\u8bf7\u6c42\u5931\u8d25: {e}\")\nexcept ValueError as e:\n print(f\"\u53c2\u6570\u9519\u8bef: {e}\")\n```\n\n## \u5f00\u53d1\n\n### \u5b89\u88c5\u5f00\u53d1\u4f9d\u8d56\n\n```bash\nuv sync --dev\n```\n\n### \u4ee3\u7801\u683c\u5f0f\u5316\u548c\u68c0\u67e5\n\n```bash\n# \u683c\u5f0f\u5316\u4ee3\u7801\nuv run ruff format nullbr/\n\n# \u68c0\u67e5\u548c\u4fee\u590d\u4ee3\u7801\nuv run ruff check --fix nullbr/\n\n# \u4ec5\u68c0\u67e5\u4ee3\u7801\nuv run ruff check nullbr/\n```\n\n### \u8fd0\u884c\u6d4b\u8bd5\n\n```bash\nuv run pytest\n```\n\n### \u6784\u5efa\u53d1\u5e03\n\n```bash\nuv build\n```\n\n## \u8bb8\u53ef\u8bc1\n\n\u672c\u9879\u76ee\u91c7\u7528 MIT \u8bb8\u53ef\u8bc1\u3002\u8be6\u60c5\u8bf7\u89c1 [LICENSE](LICENSE) \u6587\u4ef6\u3002\n\n## \u8d21\u732e\n\n\u6b22\u8fce\u63d0\u4ea4Issue\u548cPull Request\uff01\n\n### \u8d21\u732e\u6307\u5357\n\n1. Fork \u9879\u76ee\n2. \u521b\u5efa\u7279\u6027\u5206\u652f (`git checkout -b feature/AmazingFeature`)\n3. \u63d0\u4ea4\u66f4\u6539 (`git commit -m 'Add some AmazingFeature'`)\n4. \u63a8\u9001\u5230\u5206\u652f (`git push origin feature/AmazingFeature`)\n5. \u5f00\u542f Pull Request\n\n## \u66f4\u65b0\u65e5\u5fd7\n\n### v1.0.3\n\n- \u2705 \u6dfb\u52a0\u4e86\u7535\u5f71video\u8d44\u6e90\u83b7\u53d6\u529f\u80fd\uff08m3u8/http\uff09\n- \u2705 \u6dfb\u52a0\u4e86\u5267\u96c6\u5355\u96c6video\u8d44\u6e90\u83b7\u53d6\u529f\u80fd\uff08m3u8/http\uff09\n- \u2705 \u6dfb\u52a0\u4e86\u5267\u96c6\u5355\u96c6ed2k\u8d44\u6e90\u83b7\u53d6\u529f\u80fd\n- \u2705 \u4fee\u590d\u4e86CLI\u547d\u4ee4\u884c\u4f7f\u7528\u95ee\u9898\uff0c\u6dfb\u52a0\u4e86`__main__.py`\n- \u2705 \u4f18\u5316\u4e86\u7c7b\u578b\u6ce8\u89e3\uff0c\u4f7f\u7528Python 3.9+\u7684\u5185\u7f6e\u6cdb\u578b\n- \u2705 \u5b8c\u5584\u4e86CLI\u547d\u4ee4\u884c\u5de5\u5177\n- \u2705 \u589e\u5f3a\u4e86\u6587\u6863\u548c\u793a\u4f8b\u4ee3\u7801\n\n### v1.0.0\n\n- \ud83c\udf89 \u521d\u59cb\u7248\u672c\u53d1\u5e03\n- \u2705 \u652f\u6301\u641c\u7d22\u3001\u7535\u5f71\u3001\u7535\u89c6\u5267\u3001\u5408\u96c6\u4fe1\u606f\u83b7\u53d6\n- \u2705 \u652f\u6301115\u3001\u78c1\u529b\u3001ed2k\u8d44\u6e90\u83b7\u53d6\n- \u2705 \u63d0\u4f9b\u5b8c\u6574\u7684\u547d\u4ee4\u884c\u5de5\u5177",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python SDK for Nullbr API - \u7528\u4e8e\u8bbf\u95ee Nullbr API \u7684 Python SDK",
"version": "1.0.3",
"project_urls": {
"Bug Tracker": "https://github.com/iLay1678/nullbr_python/issues",
"Documentation": "https://github.com/iLay1678/nullbr_python/blob/main/README.md",
"Homepage": "https://github.com/iLay1678/nullbr_python",
"Repository": "https://github.com/iLay1678/nullbr_python"
},
"split_keywords": [
"api",
" movies",
" nullbr",
" sdk",
" tmdb",
" tv"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "db74e822f076f9ca6f44a3983aec1fcc7cff8d9685d1b126e917abe2f6048c9b",
"md5": "1bc7bf36d0209270a4af9bc2c3cf3b97",
"sha256": "9e58c3240c7507b46170bf0016a547099415fab2105c0761881837bfd52827d3"
},
"downloads": -1,
"filename": "nullbr-1.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "1bc7bf36d0209270a4af9bc2c3cf3b97",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 15205,
"upload_time": "2025-08-09T02:21:54",
"upload_time_iso_8601": "2025-08-09T02:21:54.906918Z",
"url": "https://files.pythonhosted.org/packages/db/74/e822f076f9ca6f44a3983aec1fcc7cff8d9685d1b126e917abe2f6048c9b/nullbr-1.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "d5113e6a202bbaa488898f240e6bee51cbfaf398ef546be345ec0939a31ddcd4",
"md5": "50abd2842842ce1c47a7030ac49108bb",
"sha256": "57642dcc3ce60b62739271171fa3326bf40778b8e82ced128ebbe1357b17a2f7"
},
"downloads": -1,
"filename": "nullbr-1.0.3.tar.gz",
"has_sig": false,
"md5_digest": "50abd2842842ce1c47a7030ac49108bb",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 51614,
"upload_time": "2025-08-09T02:21:56",
"upload_time_iso_8601": "2025-08-09T02:21:56.102506Z",
"url": "https://files.pythonhosted.org/packages/d5/11/3e6a202bbaa488898f240e6bee51cbfaf398ef546be345ec0939a31ddcd4/nullbr-1.0.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-09 02:21:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "iLay1678",
"github_project": "nullbr_python",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "nullbr"
}