Metadata-Version: 2.4
Name: snsdk
Version: 1.1.0
Summary: ShitNovel API Python SDK — A comprehensive Python client for the ShitNovel REST API
Author-email: ShitNovel <shitnovel@163.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/shitnovel/snsdk
Project-URL: Documentation, https://github.com/shitnovel/snsdk/blob/main/API文档.md
Project-URL: Repository, https://github.com/shitnovel/snsdk
Keywords: shitnovel,sdk,api,novel,client,小说,网文
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Dynamic: license-file

# snsdk — ShitNovel API Python SDK

[![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

一个功能完整的 ShitNovel API Python 客户端 SDK，覆盖小说、章节、评论、用户、作者、书架、阅读历史、作者创作、许可证及 PAT 管理等全部 API 接口。

## 功能特性

- **全接口覆盖**：小说、章节、分卷、评论、用户、书架、阅读历史、作者创作、许可证管理、PAT 管理
- **双轨认证**：支持 Personal Access Token (PAT) 和 Session Cookie 两种认证方式
- **类型安全**：所有 API 响应均映射为强类型 dataclass 模型，支持 camelCase → snake_case 自动转换
- **分页支持**：内置 `PaginatedResult` 分页封装
- **错误处理**：统一 `ShitNovelError` 异常，包含 HTTP 状态码与业务错误码
- **Python 3.9+**：支持 Python 3.9 及以上版本
- **零配置**：仅依赖 `requests` 库，无需复杂依赖

## 安装

```bash
pip install snsdk
```

## 快速开始

```python
import snsdk

# 创建客户端（使用 PAT 认证）
client = snsdk.ShitNovelClient(
    base_url="https://api.shitnovel.com",
    token="snp_your_pat_token_here"
)

# 获取小说列表
novels = client.list_novels(channel=snsdk.Channel.MALE, page=0, size=20)
for item in novels.items:
    print(f"{item.title} - {item.author_name}")

# 获取小说详情
novel = client.get_novel(1)
print(f"《{novel.title}》共 {novel.chapter_count} 章")

# 搜索小说
results = client.search("斗破苍穹")

# 获取排行榜
rankings = client.get_rank(snsdk.RankType.HOT)

# 获取章节详情
chapter = client.get_chapter(100)
print(chapter.content)
```

## 认证

### PAT（Personal Access Token）

```python
client = snsdk.ShitNovelClient(
    base_url="https://api.shitnovel.com",
    token="snp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
)
```

### Session Cookie

```python
client = snsdk.ShitNovelClient(
    base_url="https://api.shitnovel.com",
    cookie="SHITNOVEL_SESSION=your_session_cookie"
)
```

## API 接口示例

### 小说模块

```python
# 列表 + 筛选
client.list_novels(
    channel=snsdk.Channel.MALE,              # 男频/女频
    category_id=5,                            # 分类 ID
    status=snsdk.NovelStatus.SERIALIZING,    # 连载中
    sort="hot",                               # 排序：update/hot/favorite/word/new
    page=0, size=20
)

# 搜索
client.search("关键词", category_id=5, page=0, size=20)

# 排行榜
client.get_rank(snsdk.RankType.HOT)   # hot/favorite/new/complete/word

# 分类 & 标签
client.list_categories(channel=snsdk.Channel.MALE)
client.get_hot_tags(limit=30)
```

### 章节模块

```python
# 小说目录（分卷结构）
catalog = client.get_novel_catalog(novel_id=1)
for vol in catalog.volumes:
    print(f"卷 {vol.sort}: {vol.title} ({vol.chapterCount} 章)")

# 章节列表（分页）
chapters = client.list_chapters(novel_id=1, volume_id=10, page=0, size=50)

# 章节详情
chapter = client.get_chapter(chapter_id=100)
print(chapter.content)
```

### 评论模块

```python
# 小说评论
comments = client.list_novel_comments(novel_id=1, page=0, size=10)

# 章节评论
comments = client.list_chapter_comments(chapter_id=100, page=0, size=10)

# 发表评论
comment = client.create_comment(
    target_type=snsdk.CommentType.NOVEL,
    target_id=1,
    content="这部小说写得太好了！"
)

# 点赞/取消点赞
client.like_comment(comment_id=100)
client.unlike_comment(comment_id=100)

# 删除评论
client.delete_comment(comment_id=100)
```

### 用户模块

```python
# 当前用户信息
me = client.get_me()
print(f"你好，{me.nickname}！")

# 修改个人资料
client.update_me(nickname="新昵称", bio="新简介")

# 用户统计
stats = client.get_my_stats()

# 书架
shelf = client.list_bookshelf(page=0, size=20)
client.add_to_bookshelf(novel_id=1)
client.remove_from_bookshelf(novel_id=1)
client.batch_remove_bookshelf(novel_ids=[1, 2, 3])

# 阅读历史
history = client.get_history(page=0, size=20)
client.record_read_progress(novel_id=1, chapter_id=100)
```

### 公开用户资料

```python
# 获取指定用户公开资料（无需认证）
user = client.get_user(user_id=100)
print(f"{user.nickname} - {user.role}")
print(f"作品数: {user.stats.novel_count}")

# 获取指定用户的公开作品列表
works = client.list_user_works(user_id=100, page=0, size=12)

# 获取指定用户的公开书架
shelf = client.list_user_bookshelf(user_id=100, page=0, size=20)
```

### 作者创作

```python
# 我的小说列表
my_novels = client.list_my_novels(page=0, size=20)

# 创建小说
new_novel = client.create_novel(
    title="我的新小说",
    category_id=5,
    channel=snsdk.Channel.MALE,
    description="这是一部关于...的小说",
    tags=["玄幻", "热血"]
)

# 更新小说
client.update_novel(
    novel_id=new_novel.id,
    title="修改后的书名",
    status=snsdk.NovelStatus.COMPLETED
)

# 删除小说
client.delete_novel(novel_id=new_novel.id)

# 我的章节列表
my_chapters = client.list_my_chapters(novel_id=1, page=0, size=50)

# 创建章节
chapter = client.create_chapter(
    novel_id=1,
    title="第3章 新的开始",
    content="这里是章节正文内容...",
    volume_id=10,
    sort=3,
    status=snsdk.ChapterStatus.DRAFT
)

# 更新章节（含状态变更/许可证绑定）
client.update_chapter(
    chapter_id=chapter.id,
    title="第3章 修订版",
    status=snsdk.ChapterStatus.PUBLISHED,
    license_id=1,
    license_version=1
)

# 删除章节
client.delete_chapter(chapter_id=chapter.id)
```

### 许可证管理

```python
# 获取预定义许可证列表（无需认证）
predefined = client.list_predefined_licenses()
print(predefined[0].name)  # 如 "CC BY 4.0"

# 获取许可证详情
license = client.get_license(license_id=1)

# 获取指定版本详情
version = client.get_license_version(license_id=1, version=1)

# 搜索许可证
results = client.search_licenses(
    type=snsdk.LicenseType.PREDEFINED,
    status=snsdk.LicenseStatus.ACTIVE,
    keyword="CC",
    page=0, size=20
)

# 按哈希查询版本
version = client.get_license_by_hash("sha3_384_hash_value")

# 获取我的自定义许可证
my_licenses = client.list_my_licenses()

# 创建自定义许可证
new_lic = client.create_license(
    name="我的许可证",
    content="本作品仅供学习交流，禁止商业用途。",
    change_note="初始版本"
)

# 修改许可证元数据
client.update_license(
    license_id=new_lic.id,
    name="更新后的名称",
    status=snsdk.LicenseStatus.ACTIVE
)

# 创建许可证新版本
new_ver = client.create_license_version(
    license_id=new_lic.id,
    content="更新后的许可证正文...",
    change_note="修订内容"
)

# 许可证状态变更
client.update_license_status(
    license_id=new_lic.id,
    status=snsdk.LicenseStatus.LOCKED
)

# 绑定小说级许可证
client.bind_novel_license(
    novel_id=1,
    license_id=1,
    license_version=1  # 可选
)

# 绑定章节级许可证
client.bind_chapter_license(
    chapter_id=100,
    license_id=1
)

# 查询章节的许可证引用历史
refs = client.get_chapter_license_refs(chapter_id=100)

# 查询许可证的引用章节
refs = client.get_license_refs_for_license(license_id=1)

# 按哈希查询许可证引用
refs = client.get_license_refs_by_hash("sha3_384_hash_value")
```

### PAT 管理

```python
# 列出 PAT（仅 Session Cookie 认证）
pats = client.list_pats()

# 创建 PAT（仅 Session Cookie 认证）
new_pat = client.create_pat(
    name="我的 APP",
    scopes=["novels:read", "user:read", "comments:write"]
)
print(f"新 Token: {new_pat.token}")  # 仅显示一次！

# 撤销指定 PAT（仅 Session Cookie 认证）
client.revoke_pat(pat_id=1)

# 撤销自身 PAT（PAT 认证，用于客户端注销）
client.revoke_self()
```

## 枚举常量

### Channel（频道）

| 值 | 说明 |
|------|------|
| `MALE` | 男频 |
| `FEMALE` | 女频 |

### NovelStatus（小说状态）

| 值 | 说明 |
|------|------|
| `SERIALIZING` | 连载中 |
| `COMPLETED` | 已完结 |
| `HIATUS` | 暂停连载 |

### ChapterStatus（章节状态）

| 值 | 说明 |
|------|------|
| `DRAFT` | 草稿 |
| `PUBLISHED` | 已发布 |
| `SCHEDULED` | 定时发布 |

### CommentType（评论类型）

| 值 | 说明 |
|------|------|
| `NOVEL` | 书评 |
| `CHAPTER` | 章评 |

### UserRole（用户角色）

| 值 | 说明 |
|------|------|
| `USER` | 普通用户 |
| `AUTHOR` | 作者 |
| `ADMIN` | 管理员 |

### RankType（排行榜类型）

| 值 | 说明 |
|------|------|
| `HOT` | 热门榜 |
| `FAVORITE` | 收藏榜 |
| `NEW` | 新书榜 |
| `COMPLETE` | 完结榜 |
| `WORD` | 字数榜 |

### LicenseType（许可证类型）

| 值 | 说明 |
|------|------|
| `PREDEFINED` | 预定义许可证（系统内置） |
| `CUSTOM` | 自定义许可证（用户创建） |

### LicenseStatus（许可证状态）

| 值 | 说明 |
|------|------|
| `ACTIVE` | 活跃（可使用、可修改） |
| `INACTIVE` | 停用（不可用于新发布） |
| `LOCKED` | 锁定（不可修改，但可用于新发布） |

## 错误处理

```python
try:
    novel = client.get_novel(99999)
except snsdk.ShitNovelError as e:
    print(f"错误 [{e.code}]: {e.message} (HTTP {e.status})")
    # e.code: 业务错误码
    # e.status: HTTP 状态码
```

### 常见错误码

| 错误码 | 说明 |
|--------|------|
| 10001 | 参数无效 |
| 10002 | 资源不存在 |
| 20001 | 未认证 |
| 20002 | Token 无效或已撤销 |
| 20004 | 权限不足 |
| 20006 | 不支持的认证方式 |

## 类型模型

### 核心 DTO

| DTO | 说明 |
|-----|------|
| `Novel` | 小说完整信息（含 licenseId/licenseVersion） |
| `NovelListItem` | 小说列表项（精简版） |
| `Author` | 作者信息 |
| `Chapter` | 章节元数据（不含正文） |
| `ChapterDetail` | 章节详情（含正文） |
| `Comment` | 评论（支持两级） |
| `UserMe` | 当前用户信息 |
| `BookshelfItem` | 书架项（含阅读进度） |
| `ReadHistoryItem` | 阅读历史项 |
| `Volume` | 分卷 |
| `Category` | 分类 |
| `Tag` | 标签 |

### 公开用户 DTO

| DTO | 说明 |
|-----|------|
| `PublicUser` | 公开用户资料（含嵌套 PublicUserStats） |
| `PublicUserStats` | 公开用户统计（novelCount/totalClicks/totalFavorites 等） |
| `PublicWork` | 公开作品（不含敏感字段） |
| `PublicBookshelfItem` | 公开书架项（不含阅读进度） |

### 许可证 DTO

| DTO | 说明 |
|-----|------|
| `License` | 许可证元数据（code/type/status 等） |
| `LicenseVersion` | 许可证版本（hash/contentKey/changeNote） |
| `ChapterLicenseRef` | 章节许可证引用快照（反欺诈追溯） |

### 响应 DTO

| DTO | 说明 |
|-----|------|
| `PaginatedResult` | 分页封装（items/total/page/size） |
| `NovelCreated` | 创建小说响应 |
| `NovelUpdated` | 更新小说响应 |
| `ChapterCreated` | 创建章节响应 |
| `ChapterUpdated` | 更新章节响应 |
| `CommentCreated` | 创建评论响应 |
| `CommentLiked` | 点赞状态响应 |
| `LicenseCreated` | 创建许可证响应 |
| `LicenseVersionCreated` | 创建许可证版本响应 |
| `LicenseStatusUpdated` | 许可证状态变更响应 |
| `NovelLicenseBound` | 小说许可证绑定响应 |
| `ChapterLicenseBound` | 章节许可证绑定响应 |
| `PatCreated` | 创建 PAT 响应（含 token） |
| `PatRevoked` | 撤销 PAT 响应 |

## 客户端方法速查表

### 小说相关

| 方法 | 说明 | 需认证 |
|------|------|--------|
| `list_novels(channel, category_id, status, sort, page, size)` | 小说列表 | 否 |
| `get_novel(id)` | 小说详情 | 否 |
| `search(q, category_id, status, channel, sort, page, size)` | 搜索小说 | 否 |
| `get_rank(rank_type)` | 排行榜 | 否 |
| `list_categories(channel)` | 分类列表 | 否 |
| `get_hot_tags(limit)` | 热门标签 | 否 |
| `get_author(id)` | 作者信息 | 否 |
| `get_author_novels(id, exclude_novel_id, page, size)` | 作者的其他作品 | 否 |

### 章节相关

| 方法 | 说明 | 需认证 |
|------|------|--------|
| `get_novel_catalog(novel_id)` | 小说目录（分卷结构） | 否 |
| `list_chapters(novel_id, volume_id, page, size)` | 章节列表 | 否 |
| `get_chapter(id)` | 章节详情 | 否 |
| `list_volumes(novel_id)` | 分卷列表 | 否 |

### 评论相关

| 方法 | 说明 | 需认证 |
|------|------|--------|
| `list_novel_comments(novel_id, root_only, parent_id, sort, page, size)` | 小说评论列表 | 否 |
| `list_chapter_comments(chapter_id, root_only, parent_id, sort, page, size)` | 章节评论列表 | 否 |
| `get_comment(id)` | 评论详情 | 否 |
| `create_comment(target_type, target_id, content, parent_id)` | 发表评论 | 是 |
| `delete_comment(id)` | 删除评论 | 是 |
| `like_comment(id)` | 点赞评论 | 是 |
| `unlike_comment(id)` | 取消点赞 | 是 |

### 用户相关

| 方法 | 说明 | 需认证 |
|------|------|--------|
| `get_me()` | 当前用户信息 | 是 |
| `update_me(nickname, avatar, bio)` | 修改个人资料 | 是 |
| `get_my_stats()` | 用户统计 | 是 |
| `list_bookshelf(sort, page, size)` | 书架列表 | 是 |
| `add_to_bookshelf(novel_id)` | 加入书架 | 是 |
| `remove_from_bookshelf(novel_id)` | 从书架移除 | 是 |
| `batch_remove_bookshelf(novel_ids)` | 批量移除 | 是 |
| `get_history(page, size)` | 阅读历史 | 是 |
| `record_read_progress(novel_id, chapter_id)` | 记录阅读进度 | 是 |
| `get_user(user_id)` | 公开用户资料 | 否 |
| `list_user_works(user_id, page, size)` | 公开用户作品 | 否 |
| `list_user_bookshelf(user_id, page, size)` | 公开用户书架 | 否 |

### 作者创作

| 方法 | 说明 | 需 Scope |
|------|------|----------|
| `list_my_novels(status, page, size)` | 我的小说列表 | `author:read` |
| `create_novel(title, category_id, channel, description, cover, tags)` | 创建小说 | `author:write` |
| `update_novel(novel_id, title, category_id, description, cover, status, tags)` | 更新小说 | `author:write` |
| `delete_novel(novel_id)` | 删除小说 | `author:write` |
| `list_my_chapters(novel_id, status, page, size)` | 我的章节列表 | `author:read` |
| `create_chapter(novel_id, title, content, volume_id, sort, status)` | 创建章节 | `author:write` |
| `update_chapter(chapter_id, title, content, volume_id, sort, status, publish_time, license_id, license_version)` | 更新章节 | `author:write` |
| `delete_chapter(chapter_id)` | 删除章节 | `author:write` |

### 许可证管理

| 方法 | 说明 | 需认证 |
|------|------|--------|
| `list_predefined_licenses()` | 预定义许可证列表 | 否 |
| `get_license(id)` | 许可证详情 | 否 |
| `get_license_version(license_id, version)` | 版本详情 | 否 |
| `get_license_by_hash(hash)` | 按哈希查询版本 | 否 |
| `search_licenses(type, status, keyword, hash, page, size)` | 搜索许可证 | 否 |
| `list_my_licenses()` | 我的自定义许可证 | `author:read` |
| `create_license(name, content, change_note)` | 创建自定义许可证 | `author:write` |
| `update_license(license_id, name, status)` | 修改许可证元数据 | `author:write` |
| `create_license_version(license_id, content, change_note)` | 创建新版本 | `author:write` |
| `update_license_status(license_id, status)` | 状态变更 | `author:write` |
| `bind_novel_license(novel_id, license_id, license_version)` | 绑定小说许可证 | `author:write` |
| `bind_chapter_license(chapter_id, license_id, license_version)` | 绑定章节许可证 | `author:write` |
| `get_chapter_license_refs(chapter_id)` | 章节引用历史 | 否 |
| `get_license_refs_for_license(license_id)` | 许可证引用章节 | 否 |
| `get_license_refs_by_hash(hash)` | 按哈希查询引用 | 否 |

### PAT 管理

| 方法 | 说明 | 认证方式 |
|------|------|----------|
| `list_pats()` | 列出 PAT | Session Cookie |
| `create_pat(name, scopes, expires_at)` | 创建 PAT | Session Cookie |
| `revoke_pat(id)` | 撤销指定 PAT | Session Cookie |
| `revoke_self()` | 撤销自身 PAT | PAT |

## 兼容性导入

为保持向后兼容，仍支持通过 `shitnovel` 模块导入：

```python
from shitnovel import ShitNovelClient, Novel, Chapter
```

## 文档

完整的 API 文档请参见 [API文档.md](API文档.md)。

## 许可证

本项目基于 [MIT License](LICENSE) 开源。
