指南
1. 入门指南
AniRena 是一个专注于动漫、漫画、音频及相关媒体的种子索引网站。无需账户即可浏览和下载种子。上传种子、在团队发帖或使用 API 需要账户。
顶部导航栏提供对网站主要区域的访问:
- 首页 — 种子列表和搜索页面。
- 上传 — 提交新种子(需要登录)。
- 指南 — 此页面。
- 统计 — 全站统计(种子、节点、随时间变化的上传量)。
- 团队 — 发布团队目录。
- RSS — 最新上传的 RSS 订阅,可按分类筛选。
您的账户菜单(登录后位于右上角)会打开个人资料面板,您可以在此调整设置、管理安全选项以及访问 API 密钥。
2. 浏览与搜索
首页按上传日期列出所有种子。使用顶部搜索栏筛选结果。
基本搜索
在搜索栏中输入任意词语并按回车(或点击搜索图标)。查询激活时,结果按相关性排序。
搜索运算符
以下运算符可与普通查询组合使用:
| 运算符 | 示例 | 效果 |
|---|---|---|
user:"name" | user:"SubsPlease" | 仅显示该用户上传的种子。 |
点击种子列表中的上传者名称会自动为您执行用户搜索。
分类与子分类
使用分类选择器(搜索栏旁边的网格图标)将结果限制在某一分类。可用分类有:
- 动漫
- 漫画/韩漫/漫画书
- 音频
- 文学
- 真人影视
- 图片
- 软件
- 成人内容
- 其他
每个分类都有子分类(例如动漫下有 RAW、字幕/配音、音乐视频),可在分类弹窗中选择。
排序与筛选
种子列表的列标题可点击以按该列排序(升序或降序)。可用排序列:日期、名称、大小、完成下载数。注意:做种数和下载数是从 Redis 实时获取的,不能用于排序。
语言筛选
使用语言选择器(旗帜图标)仅显示标有特定语言的种子。
RSS 订阅
位于 /rss 的 RSS 订阅提供最新上传内容。添加 ?category=anime(或其他分类代号)可筛选订阅。大多数种子客户端支持直接从此 URL 进行 RSS 自动下载。
3. 下载种子
点击任意种子名称打开详情面板。在那里您可以:
- 下载 .torrent — 直接保存 .torrent 文件。直接 URL 是
/torrents/{id}.torrent - 磁力链接 — 通过磁力 URI 协议直接在您的种子客户端中打开。URL 是
/torrents/{id}/magnet
详情面板还显示种子描述、文件列表、Tracker 列表以及做种/下载数量。
旧版下载链接
旧版 AniRena 下载链接仍受支持,并使用旧版 ID 自动重定向到正确的 .torrent 文件: /dl/{old_id}
推荐的 BitTorrent 客户端
任何现代 BitTorrent 客户端都可以使用。以下客户端是推荐的,并完全支持 BitTorrent v2 / 混合 torrent:
4. 创建账户
注册
点击导航栏中的「注册」。选择用户名、提供邮箱地址并设置密码(强制最小长度)。您必须阅读并接受网站条款才能创建账户。
邮箱激活
注册后,验证邮件将发送到您的地址。点击邮件中的链接激活您的账户。如果未收到,请使用登录页面上的「激活您的账户」链接申请新代码。
密码找回
如果您忘记密码,请在登录页面点击「忘记密码」并输入您的邮箱地址。找回链接将发送给您。该链接为一次性使用,并在短时间后过期。
5. 上传种子
在导航栏中点击「上传」。您必须以活跃且未被封禁的账户登录。上传页面有两个标签页:
上传标签——提交现有 .torrent 文件
拖放或选择 .torrent 文件。加载后,填写字段:
| 字段 | 必填 | 描述 |
|---|---|---|
| 种子文件 | 是 | 要上传的 .torrent 文件。 |
| 名称 | 否 | 覆盖种子显示名称。如果留空,则使用种子文件中嵌入的名称。 |
| 分类 | 是 | 内容分类(动漫、漫画、音频等)。 |
| 子分类 | 否 | 分类内更具体的类型(例如 RAW、字幕/配音)。 |
| 语言 | 否 | 描述内容语言的一个或多个语言标签。 |
| 团队 | 否 | 将此发布关联到您所在的团队。 |
| 描述 | 否 | 显示在种子详情页面的 Markdown 格式描述(最多 65535 个字符)。 |
| 私有 | 否 | 在种子中设置私有标志,禁用 DHT/PEX。适用于仅限 Tracker 的种子。 |
| 公告URL | 否 | 覆盖或添加主 Tracker Announce URL。 |
| 额外 Tracker | 否 | 从种子文件读取。上传时无法修改 — 如需自定义 Tracker 列表,请使用"创建"选项卡。 |
| 评论 | 否 | 覆盖文件中嵌入的种子评论字段。 |
您的种子必须在公告列表中包含至少一个 AniRena Tracker URL(任意层级均可)。网站会在上传时检查此项,并拒绝不包含 AniRena Tracker 的种子。如果您在创建种子时忘记添加 AniRena Tracker,请先上传,然后从网站重新下载——下载的文件将自动包含正确的 Tracker。
创建标签——构建新种子
「创建」标签让您通过在浏览器中直接指定文件路径、Tracker URL 和其他种子参数,从头生成新的 .torrent。生成的种子提交时使用与上方相同的元数据字段。
内容审核
上传会自动检查封禁内容模式列表(名称、文件名、描述)。与封禁模式匹配的种子将被拒绝。重复种子(相同 info hash)也会被拒绝。
6. 您的账户
点击右上角您的用户名打开个人资料面板。它分为可折叠的部分:
设置
更改界面主题、字体大小、配色方案、界面语言和种子相关显示偏好。更改会自动保存。
密码
输入当前密码和新密码两次。验证码将发送到您的注册邮箱,必须输入以确认更改。如果启用了双重验证,还需要您的 TOTP 代码。
双重验证(2FA)
使用任意验证器应用(例如 Google Authenticator、Aegis、Bitwarden)启用基于 TOTP 的双重验证。启用 2FA 时:
- 在您的验证器应用中扫描二维码(或手动输入密钥)。
- 输入应用中显示的 6 位数代码以确认设置。
- 保存显示的恢复代码——这些是一次性代码,在您丢失设备时用于恢复访问。
要禁用 2FA,请输入您当前的 TOTP 代码并确认。
活跃会话
查看所有当前活跃的登录会话,包括浏览器、操作系统、IP 地址和最后活跃时间。点击任何不认识的会话上的「撤销」。您也可以一次撤销所有会话以从所有设备退出登录。
API 密钥
生成个人 API 密钥,用于通过 AniRena API 以编程方式上传种子。点击「生成密钥」创建一个——完整密钥在生成后立即显示一次。请安全存储;之后将不再完整显示。使用「撤销」永久使密钥失效。
删除账户
申请账户删除将启动 30 天宽限期。您的账户将立即禁用,30 天后永久删除。您可以在该窗口期内随时通过登录并点击「取消删除」来取消删除。
7. AniRena API
AniRena 提供了一个 JSON API,让您可以使用个人 API 密钥以编程方式上传种子。API 与网页界面应用相同的规则:封禁检查、速率限制和站点模式限制均适用。每次 API 上传都会记录在审计日志中。
认证
API 采用两步认证流程。首先将永久 API 密钥换取一个短期 Bearer Token,然后在每次 API 请求的 Authorization 请求头中传入该 Token。
您的 API 密钥可在「您的账户 > API 密钥」中找到。请妥善保密 — 任何持有密钥的人都可以获取 Bearer Token 并代您上传。如有泄露,请立即撤销并重新生成。
第一步 — 获取 Bearer Token
/api/v1/auth/token向 Token 端点发送 POST 请求,在 Authorization 请求头中传入您的 API 密钥。无需请求体。
Authorization: ApiKey <your-api-key>
Token 响应
{
"token": "<bearer-token>",
"token_type": "Bearer",
"expires_in": 3600
}令牌生命周期
Bearer 令牌自签发起最长 3600 秒内保持有效,并且可以在每次调用中重复使用,直至过期。令牌过期时,通过 POST /api/v1/auth/token 创建一个新令牌。出于向后兼容性考虑,每个响应仍会在 X-New-Token 标头中返回当前令牌。
X-New-Token: <next-bearer-token>
单次请求登录(支持双重验证)
/api/v1/auth/login在一次请求中使用用户名或邮箱和密码进行身份验证,并直接获取 bearer 令牌。如果你的账户已启用双重验证,请在 totp_code 中提供当前验证器代码(或在 recovery_code 中提供恢复代码)。可选地将 new_api_key 设为 true,以在同一响应中同时生成一个全新的永久 API 密钥。
请求正文
{
"login": "username or email",
"password": "your-password",
"totp_code": "123456", // 若已启用双重验证则必填(6 位数字)
"recovery_code": "", // totp_code 的替代项
"new_api_key": false // 设为 true 可同时生成新的 API 密钥
}Token 响应
{
"ok": true,
"token": "<bearer-token>",
"token_type": "Bearer",
"expires_in": 3600,
"api_key": "<new-api-key>" // 仅当 new_api_key 为 true 时才存在
}bearer 令牌的用法与从 /api/v1/auth/token 获取的完全相同。仅当 new_api_key 为 true 时才返回 api_key 字段——请立即保存,因为它只显示一次并会替换任何先前的密钥。
示例 — 登录并(可选)获取新的 API 密钥
# pip install requests import requests BASE_URL = "https://www.anirena.com" # One request: authenticate (with 2FA if enabled) and get a bearer token. # Set new_api_key=True to also receive a brand-new permanent API key. resp = requests.post( f"{BASE_URL}/api/v1/auth/login", json={ "login": "your-username", # username or email "password": "your-password", "totp_code": "123456", # omit if 2FA is not enabled "new_api_key": True, # optional }, ) resp.raise_for_status() data = resp.json() token = data["token"] # use as: Authorization: Bearer <token> if "api_key" in data: print("New API key — store it now:", data["api_key"])
// Built-in fetch — requires Node.js 18+ const BASE_URL = "https://www.anirena.com"; // One request: authenticate (with 2FA if enabled) and get a bearer token. // Set new_api_key:true to also receive a brand-new permanent API key. const resp = await fetch(`${BASE_URL}/api/v1/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ login: "your-username", // username or email password: "your-password", totp_code: "123456", // omit if 2FA is not enabled new_api_key: true, // optional }), }); const data = await resp.json(); const token = data.token; // use as: Authorization: Bearer <token> if (data.api_key) console.log("New API key — store it now:", data.api_key);
// Requires: curl extension (enabled by default in PHP 8+) <?php define("BASE_URL", "https://www.anirena.com"); // One request: authenticate (with 2FA if enabled) and get a bearer token. // Set new_api_key => true to also receive a brand-new permanent API key. $ch = curl_init(BASE_URL . "/api/v1/auth/login"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "login" => "your-username", // username or email "password" => "your-password", "totp_code" => "123456", // omit if 2FA is not enabled "new_api_key" => true, // optional ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); $token = $data["token"]; // use as: Authorization: Bearer <token> if (isset($data["api_key"])) echo "New API key — store it now: " . $data["api_key"];
// Cargo.toml: // serde_json = "1" // reqwest = { version = "0.12", features = ["blocking", "json"] } const BASE_URL: &str = "https://www.anirena.com"; fn main() { let client = reqwest::blocking::Client::new(); // One request: authenticate (with 2FA if enabled) and get a bearer token. // Set new_api_key:true to also receive a brand-new permanent API key. let data: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/auth/login")) .json(&serde_json::json!({ "login": "your-username", // username or email "password": "your-password", "totp_code": "123456", // omit if 2FA is not enabled "new_api_key": true, // optional })) .send().unwrap() .json().unwrap(); let token = data["token"].as_str().unwrap(); // Authorization: Bearer <token> if let Some(key) = data["api_key"].as_str() { println!("New API key — store it now: {key}"); } }
第二步 — 上传种子
/api/v1/torrents发送一个包含 Bearer Token 的普通 JSON POST 请求,Token 放在 Authorization 请求头中。
请求正文
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
torrent | string | 是 | .torrent 文件内容的 Base64 编码。 |
category | string | 是 | 分类代号:anime、manga、audio、literature、live、pictures、software、hentai、other。 |
name | string | 否 | 覆盖种子显示名称。 |
sub_category | string | 否 | 子分类代号(例如 raw、sub-audio)。必须属于所选分类。 |
languages | string[] | 否 | BCP 47 语言代码数组(例如 en、ja)。 |
group_id | string | 否 | 您所在团队的 UUID,用于关联此发布。 |
description | string | 否 | Markdown 格式的发布描述(最多 65535 个字符)。 |
comment | string | 否 | 覆盖种子嵌入的评论字段。 |
is_private | boolean | 否 | 设置为 true 以在种子中启用私有标志。 |
comments_enabled | boolean | 否 | 允许对此种子发表评论。默认为 true(已启用)。 |
anime_id | string | 否 | 要关联到此种子的动画条目的UUID。通过 GET /api/v1/anime/search 获取UUID。如果UUID与任何已知条目不匹配,则返回400。 |
announce | string | 否 | 覆盖或添加主 Announce URL。 |
trackers | string | 否 | 额外 Tracker URL 的换行分隔列表。URL 之间空行创建新的 Tracker 层级。 |
test | boolean | 否 | 设置为 true 进行干运行:请求会被完整验证,但不会保存种子文件。在正式提交前,用它来验证您的数据是否正确。 |
干运行成功响应 — 200 OK
{
"ok": true,
"test": true,
"name": "My Torrent Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null
}可用语言代码
abaaafaksqamarar-001anhyasavaeayazbmbaeubebnbhbibsbrbgmyyuecachcenyzhzh-HKzh-Hanszh-SGzh-TWcucvkwcocrhrcsdadvnlnl-BEdzenen-USeoeteefofjfilfifrfr-CAffgllgkadede-ATelgnguhthahehzhihohuisioigidiaieiuikgaitjajvklknkrkskkkmkirwrnkvkgkokjkukylolalvlilnltlulbmkmgmsmlmtgvmimrmhmnnanvngnendsenonbnnocorojomospipsfaplptpt-BRpaqurormrusmsgsascgdsrsr-Latnsniisdsiskslsonrsteses-419es-MXsuswsssvtltytgtatttethbotitotstntrtktwukuruguzvevivowacyfywoxhyiyozazu请求示例
# pip install requests import base64, pathlib, requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://www.anirena.com" # Step 1: exchange API key for a short-lived bearer token auth = requests.post( f"{BASE_URL}/api/v1/auth/token", headers={"Authorization": f"ApiKey {API_KEY}"}, ) auth.raise_for_status() token = auth.json()["token"] # Step 2: upload — plain JSON with the bearer token torrent_b64 = base64.b64encode(pathlib.Path("file.torrent").read_bytes()).decode() resp = requests.post( f"{BASE_URL}/api/v1/torrents", json={ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "description": "# My Release\n\nRelease notes here.", "is_private": False, }, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() token = resp.headers.get("X-New-Token", token) # save for next request print(data["id"], data["name"]) # torrent UUID and title
// Built-in modules only — requires Node.js 18+ (for global fetch) const fs = require("fs"); const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://www.anirena.com"; // Step 1: exchange API key for a short-lived bearer token let authResp = await fetch(`${BASE_URL}/api/v1/auth/token`, { method: "POST", headers: { Authorization: `ApiKey ${API_KEY}` }, }); let { token } = await authResp.json(); // Step 2: upload — plain JSON with the bearer token const torrentB64 = fs.readFileSync("file.torrent").toString("base64"); const resp = await fetch(`${BASE_URL}/api/v1/torrents`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ torrent: torrentB64, category: "anime", sub_category: "raw", languages: ["ja"], description: "# My Release\n\nRelease notes here.", is_private: false, }), }); const data = await resp.json(); token = resp.headers.get("x-new-token") ?? token; // save for next request console.log(data.id, data.name); // torrent UUID and title
// Requires: curl extension (enabled by default in PHP 8+) <?php define("API_KEY", "YOUR_API_KEY"); define("BASE_URL", "https://www.anirena.com"); // Step 1: exchange API key for a short-lived bearer token $ch = curl_init(BASE_URL . "/api/v1/auth/token"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: ApiKey " . API_KEY], ]); $token = json_decode(curl_exec($ch), true)["token"]; curl_close($ch); // Step 2: upload — plain JSON with the bearer token $torrentB64 = base64_encode(file_get_contents("file.torrent")); $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "torrent" => $torrentB64, "category" => "anime", "sub_category" => "raw", "languages" => ["ja"], "description" => "# My Release\n\nRelease notes here.", "is_private" => false, ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer " . $token, ], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($respHeaders as $h) { // save new token for next request if (stripos($h, "X-New-Token:") === 0) $token = trim(substr($h, strlen("X-New-Token:"))); } echo $data["id"] . " " . $data["name"]; // torrent UUID and title
// Cargo.toml: // base64 = "0.22" // serde_json = "1" // reqwest = { version = "0.12", features = ["blocking", "json"] } use base64::{engine::general_purpose::STANDARD as B64, Engine}; const API_KEY: &str = "YOUR_API_KEY"; const BASE_URL: &str = "https://www.anirena.com"; fn main() { let client = reqwest::blocking::Client::new(); // Step 1: exchange API key for a short-lived bearer token let auth: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/auth/token")) .header("Authorization", format!("ApiKey {API_KEY}")) .send().unwrap().json().unwrap(); let mut token = auth["token"].as_str().unwrap().to_string(); // Step 2: upload — plain JSON with the bearer token let torrent_b64 = B64.encode(std::fs::read("file.torrent").unwrap()); let resp = client .post(format!("{BASE_URL}/api/v1/torrents")) .header("Authorization", format!("Bearer {token}")) .json(&serde_json::json!({ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "description": "# My Release\n\nRelease notes here.", "is_private": false })) .send().unwrap(); if let Some(t) = resp.headers().get("x-new-token") { token = t.to_str().unwrap().to_string(); // save for next request } let data: serde_json::Value = resp.json().unwrap(); println!("{} {}", data["id"], data["name"]); // torrent UUID and title }
成功响应——200 OK
{
"ok": true,
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Torrent Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null
}错误响应
| HTTP 状态码 | 含义 |
|---|---|
400 | 无效的请求正文或缺少必填字段。 |
401 | Bearer Token 缺失、已过期或已被轮换。请通过 POST /api/v1/auth/token 重新认证。 |
403 | 账户被封禁、禁用或 IP 被封锁。 |
409 | 重复种子——相同 info hash 已存在。 |
422 | 种子文件无法解析或未通过验证(封禁模式、无效结构)。 |
429 | 超出速率限制。请在窗口重置后重试。 |
503 | 网站处于维护或只读模式。 |
速率限制
API 上传受独立于网页界面的可配置速率限制约束。限制和窗口由网站管理员设置。超出速率限制时,API 返回 429 Too Many Requests。限制按 API 密钥计算。
使用 torrent-builder 生成种子文件
torrent-builder 是一个基于 libtorrent-rasterbar 构建的开源 CLI 工具,可让您从命令行创建 BitTorrent v1、v2 和 hybrid .torrent 文件。它与 AniRena upload API 完美配合 — 在本地生成文件,然后直接 POST 到 tracker。 cantalupo555/torrent-builder.
从源代码构建
需要 CMake >= 3.28.3 和 libtorrent-rasterbar >= 2.0.11。克隆仓库并用 CMake 构建:
# Install system dependencies sudo apt-get install build-essential cmake libtorrent-rasterbar-dev # Clone & build git clone https://github.com/cantalupo555/torrent-builder.git cd torrent-builder mkdir build && cd build cmake .. && cmake --build .
# Install dependencies via Homebrew brew install cmake libtorrent-rasterbar # Clone & build git clone https://github.com/cantalupo555/torrent-builder.git cd torrent-builder mkdir build && cd build cmake .. && cmake --build .
主要参数
| 字段 | 描述 |
|---|---|
--path | 要打包的文件或目录的路径(必填)。 |
--output | 输出 .torrent 文件名(必填)。 |
--version | BitTorrent 格式 — 1 = v1,2 = v2,3 = hybrid(默认:3)。 |
--tracker | 添加 tracker announce URL。重复该参数可添加多个 tracker。 |
--comment | 在种子中嵌入元数据注释字符串。 |
--private | 设置私有标志,将分发限制为仅限列出的 tracker。 |
--piece-size | 分片大小,单位 KB(16-32768)。留空则自动选择。 |
-i | 启动逐步交互式配置模式。 |
端到端工作流程:构建 -> 上传
以下示例使用 torrent-builder 构建 hybrid 种子,然后通过 AniRena API 进行身份验证,并在单个脚本中上传结果。
# pip install requests import base64, subprocess, requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://www.anirena.com" # Step 1: build the torrent with torrent-builder # --version 1=v1 2=v2 3=hybrid (default) subprocess.run([ "./torrent-builder/build/torrent_builder", "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", # hybrid "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ], check=True) # Step 2: authenticate token = requests.post( f"{BASE_URL}/api/v1/auth/token", headers={"Authorization": f"ApiKey {API_KEY}"}, ).json()["token"] # Step 3: upload torrent_b64 = base64.b64encode(open("my_release.torrent", "rb").read()).decode() resp = requests.post( f"{BASE_URL}/api/v1/torrents", json={ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "comments_enabled": True, }, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() print(data["id"], data["name"])
// Built-in modules — Node.js 18+ const fs = require("fs"); const { execFileSync } = require("child_process"); const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://www.anirena.com"; // Step 1: build the torrent (--version 1=v1, 2=v2, 3=hybrid) execFileSync("./torrent-builder/build/torrent_builder", [ "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ]); // Step 2: authenticate let { token } = await (await fetch(`${BASE_URL}/api/v1/auth/token`, { method: "POST", headers: { Authorization: `ApiKey ${API_KEY}` }, })).json(); // Step 3: upload const torrentB64 = fs.readFileSync("my_release.torrent").toString("base64"); const resp = await fetch(`${BASE_URL}/api/v1/torrents`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ torrent: torrentB64, category: "anime", sub_category: "raw", languages: ["ja"], comments_enabled: true, }), }); const data = await resp.json(); console.log(data.id, data.name);
// PHP 8+ with curl and proc_open <?php define("API_KEY", "YOUR_API_KEY"); define("BASE_URL", "https://www.anirena.com"); // Step 1: build the torrent (version: 1=v1, 2=v2, 3=hybrid) exec(implode(" ", array_map("escapeshellarg", [ "./torrent-builder/build/torrent_builder", "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ]))); // Step 2: authenticate $ch = curl_init(BASE_URL . "/api/v1/auth/token"); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: ApiKey " . API_KEY]]); $token = json_decode(curl_exec($ch), true)["token"]; curl_close($ch); // Step 3: upload $torrentB64 = base64_encode(file_get_contents("my_release.torrent")); $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "torrent" => $torrentB64, "category" => "anime", "sub_category" => "raw", "languages" => ["ja"], "comments_enabled" => true, ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => ["Content-Type: application/json", "Authorization: Bearer " . $token], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); echo $data["id"] . " " . $data["name"];
// Cargo.toml: // base64 = "0.22" // serde_json = "1" // reqwest = { version = "0.12", features = ["blocking", "json"] } use base64::{engine::general_purpose::STANDARD as B64, Engine}; use std::process::Command; const API_KEY: &str = "YOUR_API_KEY"; const BASE_URL: &str = "https://www.anirena.com"; fn main() { // Step 1: build the torrent (--version 1=v1, 2=v2, 3=hybrid) Command::new("./torrent-builder/build/torrent_builder") .args([ "--path", "/data/my_release", "--output", "my_release.torrent", "--version", "3", "--tracker", "udp://open.tracker.gg:6969/announce", "--comment", "My Release", "--creator", "--creation-date", ]) .status().expect("torrent_builder failed"); let client = reqwest::blocking::Client::new(); // Step 2: authenticate let auth: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/auth/token")) .header("Authorization", format!("ApiKey {API_KEY}")) .send().unwrap().json().unwrap(); let token = auth["token"].as_str().unwrap().to_string(); // Step 3: upload let torrent_b64 = B64.encode(std::fs::read("my_release.torrent").unwrap()); let resp: serde_json::Value = client .post(format!("{BASE_URL}/api/v1/torrents")) .header("Authorization", format!("Bearer {token}")) .json(&serde_json::json!({ "torrent": torrent_b64, "category": "anime", "sub_category": "raw", "languages": ["ja"], "comments_enabled": true, })) .send().unwrap().json().unwrap(); println!("{} {}", resp["id"], resp["name"]); }
搜索种子元数据
/api/v1/torrents/search发送一个普通 JSON POST 请求,以使用与网站相同的搜索和筛选选项获取种子列表。.torrent 文件本身不会被返回 — 请使用正常的下载路由获取。
# pip install requests (token already obtained — see upload example) resp = requests.post( f"{BASE_URL}/api/v1/torrents/search", json={"q": "Sword Art Online", "category": "anime", "per_page": 25}, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() token = resp.headers.get("X-New-Token", token) # save for next request for t in data["torrents"]: print(t["title"], "-", t["magnet"])
// token already obtained — see upload example const resp = await fetch(`${BASE_URL}/api/v1/torrents/search`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ q: "Sword Art Online", category: "anime", per_page: 25 }), }); const data = await resp.json(); token = resp.headers.get("x-new-token") ?? token; // save for next request data.torrents.forEach(t => console.log(t.title, "-", t.magnet));
// token already obtained — see upload example $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents/search"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ "q" => "Sword Art Online", "category" => "anime", "per_page" => 25 ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer " . $token, ], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($respHeaders as $h) { // save new token for next request if (stripos($h, "X-New-Token:") === 0) $token = trim(substr($h, strlen("X-New-Token:"))); } foreach ($data["torrents"] as $t) { echo $t["title"] . " - " . $t["magnet"] . "\n"; }
// client and token already obtained — see upload example let resp = client .post(format!("{BASE_URL}/api/v1/torrents/search")) .header("Authorization", format!("Bearer {token}")) .json(&serde_json::json!({"q": "Sword Art Online", "category": "anime", "per_page": 25})) .send().unwrap(); if let Some(t) = resp.headers().get("x-new-token") { token = t.to_str().unwrap().to_string(); // save for next request } let data: serde_json::Value = resp.json().unwrap(); for t in data["torrents"].as_array().unwrap() { println!("{} - {}", t["title"], t["magnet"]); }
搜索参数
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
q | string | 否 | 自由文本搜索。支持 group:slug、group:"Name"、user:name 前缀。 |
category | string | 否 | 分类代号(例如"anime")。 |
sub_category | string | 否 | 子分类代号(例如"raw")。 |
languages | string[] | 否 | BCP 47 语言代码数组(例如 en、ja)。 |
sort | string | 否 | 排序字段:date(默认)、size、seeders、leechers、completed、title。 |
order | string | 否 | 排序方向:desc(默认)或 asc。 |
page | integer | 否 | 页码,从 1 开始(默认 1)。 |
per_page | integer | 否 | 每页结果数,1–250(默认 50)。 |
hide_adult | boolean | 否 | 排除成人分类种子。普通用户默认为 true。 |
show_dead | boolean | 否 | 当为 false(默认)时,超过死种子宽限期且没有活跃做种者的种子将被排除。设置为 true 以包含它们。 |
响应
{
"total": 1234,
"page": 1,
"per_page": 50,
"total_pages": 25,
"from": 1,
"to": 50,
"torrents": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Release Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null,
"size_fmt": "1.4 GB",
"completed": 42,
"seeders": 10,
"leechers": 3,
"languages": ["ja", "en"],
"comment_count": 7,
"created_at": "2024-01-15 12:34",
"cat_slug": "anime",
"sub_slug": "raw",
"group_name": null,
"uploader": "username",
"magnet": "magnet:?xt=urn:btih:..."
}
]
}comment_count — 此种子文件中未删除评论的数量。
搜索速率限制
搜索请求受独立的可配置速率限制约束(默认每个 API 密钥每 60 秒 60 次请求)。超出限制返回 429 Too Many Requests。工作人员账户不受限制。
获取种子详情
/api/v1/torrent/{id}获取单个种子的完整元数据——包括搜索端点未返回的字段,例如 Markdown 描述、嵌入的 .torrent 注释、带每个文件大小的文件列表,以及完整的 Tracker 层级布局。可用时,从 Tracker 实时读取做种和下载者数量。
响应
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Release Title",
"info_hash_v1": "aabbccddeeff...",
"info_hash_v2": null,
"size_fmt": "1.4 GB",
"completed": 42,
"seeders": 10,
"leechers": 3,
"ext_seeders": 128,
"ext_leechers": 14,
"created_at": "2024-01-15 12:34",
"torrent_created": "2024-01-15 12:30",
"created_by_client": "mktorrent 1.1",
"cat_name": "Anime",
"cat_slug": "anime",
"sub_name": "Raw",
"sub_slug": "raw",
"group_name": null,
"group_slug": null,
"uploader": "username",
"uploader_id": "...",
"description": "# My Release\n\nRelease notes here.",
"comment": "",
"is_private": false,
"magnet": "magnet:?xt=urn:btih:...",
"languages": [
{ "code": "ja", "name": "Japanese", "country_code": "jp" }
],
"tracker_tiers": [
{ "tier": 0, "urls": ["udp://tracker.example.org:6969/announce"] }
],
"files": [
{ "path": "My Release/episode-01.mkv", "size": 1503238553 }
],
"comments_enabled": true,
"comments_locked": false,
"comment_count": 7
}seeders, leechers — 来自内部 Tracker 的实时计数;当 Tracker 存储中没有此 info hash 的条目或无法访问时,两者均报告为 0。
ext_seeders, ext_leechers — 为此种子抓取的任何一个外部 Tracker 报告的最高做种者和下载者数量。跟踪同一群体的 Tracker 会重叠,因此使用最大值而非总和;当没有 Tracker 拥有该 info hash 的抓取数据时,两者均报告为 0。
错误响应
| HTTP 状态码 | 含义 |
|---|---|
400 | 种子 ID 必须是 36 个字符的带连字符 UUID 或 32 个字符的纯十六进制字符串。 |
401 | Bearer Token 缺失、已过期或已被轮换。请通过 POST /api/v1/auth/token 重新认证。 |
404 | 未找到种子。 |
429 | 超出速率限制。请在窗口重置后重试。 |
503 | 网站处于维护或只读模式。 |
获取种子评论
/api/v1/torrents/{id}/comments获取种子的分页评论。每页评论数由服务器 .env 文件中的 COMMENT_PER_PAGE 设置控制(默认 20)。只有启用了评论的种子才会返回结果 — 其他所有种子返回 403。
查询参数
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
page | integer | 否 | 页码,从 1 开始(默认 1)。 |
# pip install requests (token already obtained — see upload example) TORRENT_ID = "550e8400-e29b-41d4-a716-446655440000" resp = requests.get( f"{BASE_URL}/api/v1/torrents/{TORRENT_ID}/comments", params={"page": 1}, headers={"Authorization": f"Bearer {token}"}, ) resp.raise_for_status() data = resp.json() token = resp.headers.get("X-New-Token", token) # save for next request for c in data["comments"]: print(c["username"], "-", c["body"])
// token already obtained — see upload example const TORRENT_ID = "550e8400-e29b-41d4-a716-446655440000"; const resp = await fetch(`${BASE_URL}/api/v1/torrents/${TORRENT_ID}/comments?page=1`, { headers: { Authorization: `Bearer ${token}` }, }); const data = await resp.json(); token = resp.headers.get("x-new-token") ?? token; // save for next request data.comments.forEach(c => console.log(c.username, "-", c.body));
// token already obtained — see upload example $torrentId = "550e8400-e29b-41d4-a716-446655440000"; $respHeaders = []; $ch = curl_init(BASE_URL . "/api/v1/torrents/{$torrentId}/comments?page=1"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADERFUNCTION => function($ch, $h) use (&$respHeaders) { $respHeaders[] = $h; return strlen($h); }, CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $token], ]); $data = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($respHeaders as $h) { // save new token for next request if (stripos($h, "X-New-Token:") === 0) $token = trim(substr($h, strlen("X-New-Token:"))); } foreach ($data["comments"] as $c) { echo $c["username"] . " - " . $c["body"] . "\n"; }
// client and token already obtained — see upload example let torrent_id = "550e8400-e29b-41d4-a716-446655440000"; let resp = client .get(format!("{BASE_URL}/api/v1/torrents/{torrent_id}/comments?page=1")) .header("Authorization", format!("Bearer {token}")) .send().unwrap(); if let Some(t) = resp.headers().get("x-new-token") { token = t.to_str().unwrap().to_string(); // save for next request } let data: serde_json::Value = resp.json().unwrap(); for c in data["comments"].as_array().unwrap() { println!("{} - {}", c["username"], c["body"]); }
响应
{
"torrent_id": "550e8400-e29b-41d4-a716-446655440000",
"page": 1,
"per_page": 20,
"total": 45,
"total_pages": 3,
"comments": [
{
"id": "...",
"user_id": "...",
"username": "uploader",
"role": "user",
"author_banned": false,
"body": "Great release!",
"created_at": "2024-01-15 12:34:00",
"edited_at": null,
"edited_by_username": null,
"deleted_at": null
}
]
}当评论作者被封禁或评论已被删除时,body 字段为空字符串。author_banned 标志指示适用的情况。
错误响应
| HTTP 状态码 | 含义 |
|---|---|
401 | Bearer Token 缺失、已过期或已被轮换。请通过 POST /api/v1/auth/token 重新认证。 |
403 | 此种子的评论已被禁用。 |
404 | 未找到种子。 |
503 | 网站处于维护或只读模式。 |
搜索动漫条目
/api/v1/anime/search?q=<query>通过标题查找动画条目以获取其UUID。UUID可作为anime_id传入上传请求体,以在上传时将种子与动画条目关联,也可在上传后与 PUT /api/torrents/{id}/anime 配合使用。无需身份验证。受与种子搜索相同的速率限制约束(默认每IP每60秒60次请求)。
# pip install requests (no authentication required)
resp = requests.get(
f"{BASE_URL}/api/v1/anime/search",
params={"q": "Sword Art Online", "page": 1, "per_page": 10},
)
resp.raise_for_status()
for item in resp.json()["results"]:
print(item["id"], "-", item["title"])// No authentication required
const resp = await fetch(
`${BASE_URL}/api/v1/anime/search?q=${encodeURIComponent("Sword Art Online")}&page=1&per_page=10`
);
const data = await resp.json();
data.results.forEach(item => console.log(item.id, "-", item.title));// No authentication required
$ch = curl_init(BASE_URL . "/api/v1/anime/search?" . http_build_query([
"q" => "Sword Art Online", "page" => 1, "per_page" => 10,
]));
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($data["results"] as $item) {
echo $item["id"] . " - " . $item["title"] . "\n";
}// No authentication required
let data: serde_json::Value = reqwest::blocking::get(
format!("{BASE_URL}/api/v1/anime/search?q=Sword+Art+Online&page=1&per_page=10")
).unwrap().json().unwrap();
for item in data["results"].as_array().unwrap() {
println!("{} - {}", item["id"], item["title"]);
}查询参数
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
q | string | 是 | 标题搜索字符串(必填)。与标题和同义词匹配。 |
page | integer | 否 | 页码,从 1 开始(默认 1)。 |
per_page | integer | 否 | 每页结果数,1–50(默认 10)。 |
响应
{
"total": 42,
"page": 1,
"per_page": 10,
"total_pages": 5,
"results": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Sword Art Online",
"anime_type": "TV",
"episodes": 25,
"status": "FINISHED",
"season": "FALL",
"season_year": 2012,
"picture": "https://cdn.myanimelist.net/images/anime/...",
"thumbnail": "https://cdn.myanimelist.net/images/anime/...",
"duration_secs": 1440
}
]
}列出和获取群组
/api/v1/groups返回公开群组的分页列表(已启用且未锁定)。需要 Bearer 令牌身份验证。
/api/v1/groups/{id_or_slug}通过数字 ID 或 slug 返回单个公开群组。如果群组被禁用或锁定,则返回 404。
查询参数(仅列表)
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
q | string | 否 | 按群组名称过滤(可选,子字符串匹配)。 |
page | integer | 否 | 页码(默认 1)。 |
per_page | integer | 否 | 每页结果数,1–100(默认 20)。 |
sort | string | 否 | 排序列:name | slug | members | torrents | created(默认 name)。 |
order | string | 否 | 排序方向:asc 或 desc(默认 asc)。 |
响应(列表)
{
"total": 12,
"page": 1,
"per_page": 20,
"total_pages": 1,
"groups": [
{
"id": 1,
"name": "SubsPlease",
"slug": "subsplease",
"subdomain_slug": "subsplease",
"description": "Weekly simulcast batches.",
"owner": "admin",
"member_count": 42,
"torrent_count": 1337,
"created_at": "2024-01-15 12:34"
}
]
}响应(单个)
{
"id": 1,
"name": "SubsPlease",
"slug": "subsplease",
"subdomain_slug": "subsplease",
"description": "Weekly simulcast batches.",
"owner": "admin",
"member_count": 42,
"torrent_count": 1337,
"created_at": "2024-01-15 12:34"
}错误响应
| HTTP 状态码 | 含义 |
|---|---|
401 | Bearer Token 缺失、已过期或已被轮换。请通过 POST /api/v1/auth/token 重新认证。 |
404 | 群组未找到或无法公开访问。 |
429 | 超出速率限制。请在窗口重置后重试。 |
503 | 网站处于维护或只读模式。 |
8. 捐款
如果您希望支持AniRena并帮助支付我们服务器和服务的托管费用,欢迎向以下任一加密货币钱包发送捐款:
bc1qy2h3ddq6ak5damvnf4r5vu3ydehhxrcq8gllwn0xCbaFe03832F95F86AF2536d52710e78C63b62Cd33ucetj2XDGHQg9PVRPMxerNi7c6kX7GJkjQNg9yjwGegLbpt61yX3RjGtB1Ef8vgVz6Hr6baQsTjVk任何捐款,无论大小,都深受感激,并直接用于维持AniRena的运营。感谢您的支持!
9. 软件
AniRena Player 是一款免费桌面应用,可让您直接从本站索引的种子中流式播放视频,无需等待完整下载完成。只需粘贴磁力链接或打开 .torrent 文件,一旦有足够数据可用,播放就会立即开始。
两个版本都完全独立运行 — 所有依赖项均已打包在可执行文件内。无需安装程序,无需配置运行时——下载即可运行。
安装程序 (.exe)。在应用内自我更新。
便携式单一文件,无需安装。Linux 上唯一支持应用内自动更新的格式。
安装: sudo apt install ./<file>.deb — 通过 apt 或重新下载更新,应用内不更新。
安装: sudo dnf install ./<file>.rpm — 通过 dnf 或重新下载更新,应用内不更新。
在 64 位 ARM Android 设备(大多数现代手机/平板电脑)上侧载。通过下载新的 APK 进行更新。