nullbr


Namenullbr JSON
Version 1.0.3 PyPI version JSON
download
home_pageNone
SummaryPython SDK for Nullbr API - 用于访问 Nullbr API 的 Python SDK
upload_time2025-08-09 02:21:56
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
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

![PyPI - Version](https://img.shields.io/pypi/v/nullbr)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](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![PyPI - Version](https://img.shields.io/pypi/v/nullbr)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](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"
}
        
Elapsed time: 1.33663s