Руководство
1. Начало работы
AniRena — индекс торрентов, ориентированный на аниме, мангу, аудио и связанные медиа. Вы можете просматривать и скачивать торренты без аккаунта. Аккаунт необходим для загрузки торрентов, публикаций в группах или использования API.
Панель навигации вверху обеспечивает доступ к основным разделам сайта:
- Главная — список торрентов и страница поиска.
- Загрузить — отправить новый торрент (требуется вход).
- Руководство — эта страница.
- Статистика — общесайтовая статистика (торренты, пиры, загрузки со временем).
- Группы — каталог групп выпуска.
- RSS — RSS-лента последних загрузок, фильтруемая по категориям.
Меню аккаунта (в правом верхнем углу при входе) открывает панель профиля, где можно настроить параметры, управлять параметрами безопасности и получить доступ к API-ключу.
2. Просмотр и поиск
Главная страница показывает все торренты в порядке даты загрузки. Используйте строку поиска вверху для фильтрации результатов.
Базовый поиск
Введите слова в строку поиска и нажмите Enter (или кликните значок поиска). При активном запросе результаты ранжируются по релевантности.
Операторы поиска
Следующие операторы можно комбинировать с обычным запросом:
| Оператор | Пример | Эффект |
|---|---|---|
user:"name" | user:"SubsPlease" | Показывать только торренты, загруженные этим пользователем. |
Клик на имя загрузчика в списке торрентов автоматически выполняет поиск пользователя.
Категории и подкатегории
Используйте селектор категорий (значок сетки рядом со строкой поиска) для ограничения результатов одной категорией.
- Аниме
- Манга/Манхва/Комикс
- Аудио
- Литература
- Живое действие
- Изображения
- Программное обеспечение
- Хентай
- Другое
Каждая категория имеет подкатегории (например, Аниме: RAW, Субтитры/Аудио, Музыкальное видео), выбираемые в модальном окне категорий.
Сортировка и фильтры
Заголовки столбцов в списке торрентов кликабельны для сортировки по этому столбцу. Примечание: сидеры и личеры — это живые значения из Redis, их нельзя использовать для сортировки.
Языковой фильтр
Используйте селектор языка (значок флага) для отображения только торрентов с конкретным языком.
RSS-лента
RSS-лента по адресу /rss предоставляет последние загрузки. Добавьте ?category=anime (или другой slug категории) для фильтрации.
3. Скачивание торрентов
Нажмите на название торрента для открытия панели подробностей. Там вы можете:
- Скачать .torrent — сохраняет файл .torrent напрямую. Прямой URL:
/torrents/{id}.torrent - Магнет-ссылка — открывается напрямую в торрент-клиенте через протокол URI магнета. URL:
/torrents/{id}/magnet
Панель подробностей также показывает описание торрента, список файлов, список трекеров и количество сидеров/личеров.
Устаревшие ссылки для скачивания
Старые ссылки AniRena по-прежнему поддерживаются и автоматически перенаправляют к нужному .torrent файлу по устаревшему ID: /dl/{old_id}
Рекомендуемые клиенты BitTorrent
Подойдёт любой современный клиент BitTorrent. Приведённые ниже клиенты рекомендованы и полностью поддерживают BitTorrent v2 / гибридные торренты:
4. Создание аккаунта
Регистрация
Нажмите «Регистрация» в панели навигации. Выберите имя пользователя, укажите email и установите пароль.
Активация email
После регистрации на ваш адрес будет отправлено письмо. Нажмите ссылку для активации аккаунта.
Восстановление пароля
Если вы забыли пароль, нажмите «Забыли пароль» на странице входа и введите email.
5. Загрузка торрентов
Перейдите в раздел «Загрузить» в панели навигации. Требуется вход с активным незаблокированным аккаунтом.
Вкладка «Загрузить» — отправить существующий .torrent файл
Перетащите или выберите .torrent файл. После загрузки заполните поля:
| Поле | Обязательное | Описание |
|---|---|---|
| Torrent-файл | Да | Загружаемый .torrent файл. |
| Название | Нет | Заменить отображаемое название торрента. |
| Категория | Да | Категория контента (Аниме, Манга, Аудио и т.д.). |
| Подкатегория | Нет | Более конкретный тип в рамках категории. |
| Языки | Нет | Один или несколько языковых тегов. |
| Группа | Нет | Связать этот релиз с группой, членом которой вы являетесь. |
| Описание | Нет | Описание в формате Markdown (макс. 65535 символов). |
| Приватный | Нет | Установить флаг приватности, отключив DHT/PEX. |
| URL анонсера | Нет | Заменить или добавить основной URL анонсера трекера. |
| Дополнительные трекеры | Нет | Считывается из файла торрента. Нельзя изменить при загрузке — используйте вкладку «Создать», чтобы настроить список трекеров. |
| Комментарий | Нет | Заменить поле комментария торрента. |
Ваш торрент должен содержать хотя бы один URL трекера AniRena в списке announce (в любом уровне). Сайт проверяет это при загрузке и отклонит торренты, не включающие трекер AniRena. Если вы создали торрент без добавления трекера AniRena, загрузите его, а затем скачайте заново с сайта — скачанный файл будет автоматически содержать правильные трекеры.
Вкладка «Создать» — создать новый торрент
Вкладка «Создать» позволяет создать новый .torrent с нуля, указав пути к файлам, URL трекеров и другие параметры прямо в браузере.
Модерация
Загрузки автоматически проверяются по списку запрещённых шаблонов контента. Торренты, совпадающие с запрещённым шаблоном, отклоняются. Дублирующиеся торренты (тот же info hash) также отклоняются.
6. Ваш аккаунт
Нажмите на ваше имя пользователя в правом верхнем углу для открытия панели профиля.
Настройки
Изменяйте тему интерфейса, размер шрифта, цветовую схему, язык интерфейса и предпочтения отображения торрентов.
Пароль
Введите текущий пароль и новый пароль дважды. Код подтверждения отправляется на зарегистрированный email.
Двухфакторная аутентификация (2FA)
Включите двухфакторную аутентификацию на основе TOTP с помощью любого приложения-аутентификатора. При включении 2FA:
- Отсканируйте QR-код (или введите секрет вручную) в приложении-аутентификаторе.
- Введите 6-значный код из приложения для подтверждения настройки.
- Сохраните показанные коды восстановления — они одноразовые для восстановления доступа.
Для отключения 2FA введите текущий TOTP-код и подтвердите.
Активные сессии
Просматривайте все активные сессии входа. Нажмите «Отозвать» для любой незнакомой сессии.
API-ключ
Создайте персональный API-ключ для программной загрузки торрентов через API AniRena.
Удалить аккаунт
Запрос на удаление аккаунта начинает 30-дневный льготный период. Вы можете отменить удаление в любое время в течение этого периода.
7. API AniRena
AniRena предоставляет JSON API для программной загрузки торрентов с использованием персонального API-ключа.
Аутентификация
API использует двухшаговый процесс аутентификации. Сначала обменяйте постоянный ключ API на краткосрочный bearer-токен, затем передавайте этот токен в заголовке Authorization каждого запроса API.
Ваш ключ API доступен в разделе Ваш аккаунт > Ключ API. Держите его в тайне — любой, кто им владеет, может получать bearer-токены и загружать файлы от вашего имени. В случае компрометации немедленно отзовите его и создайте новый.
Шаг 1 — Получить bearer-токен
/api/v1/auth/tokenОтправьте POST-запрос на конечную точку токена с вашим ключом API в заголовке Authorization. Тело запроса не требуется.
Authorization: ApiKey <your-api-key>
Ответ с токеном
{
"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>
Вход одним запросом (с 2FA)
/api/v1/auth/loginПройдите аутентификацию по имени пользователя или электронной почте и паролю в одном запросе и получите bearer-токен напрямую. Если у вашей учётной записи включена 2FA, укажите текущий код из приложения-аутентификатора в totp_code (или код восстановления в recovery_code). При желании установите new_api_key в true, чтобы в том же ответе также создать совершенно новый постоянный API-ключ.
Тело запроса
{
"login": "username or email",
"password": "your-password",
"totp_code": "123456", // обязательно, если включена 2FA (6 цифр)
"recovery_code": "", // альтернатива totp_code
"new_api_key": false // установите true, чтобы создать новый API-ключ
}Ответ с токеном
{
"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. Поле api_key возвращается только когда new_api_key равно true — сохраните его сразу, так как оно показывается лишь один раз и заменяет любой предыдущий ключ.
Пример — вход и (опционально) получение нового ключа 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}"); } }
Шаг 2 — Загрузить торрент
/api/v1/torrentsОтправьте простой JSON POST-запрос с bearer-токеном в заголовке Authorization.
Тело запроса
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
torrent | string | Да | Содержимое .torrent файла в кодировке Base64. |
category | string | Да | Slug категории: anime, manga, audio, literature, live, pictures, software, hentai, other. |
name | string | Нет | Заменить отображаемое название торрента. |
sub_category | string | Нет | Slug подкатегории (например, 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 записи аниме для привязки к этому торренту. Получите UUID через GET /api/v1/anime/search. Возвращает 400, если UUID не соответствует ни одной известной записи. |
announce | string | Нет | Заменить или добавить основной URL анонсера. |
trackers | string | Нет | Список дополнительных URL трекеров, разделённых переносами строк. |
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-токен. Повторно аутентифицируйтесь через POST /api/v1/auth/token. |
403 | Аккаунт заблокирован, отключён или IP заблокирован. |
409 | Дублирующийся торрент — тот же info hash уже существует. |
422 | Torrent-файл не удалось разобрать или он не прошёл проверку. |
429 | Превышен лимит запросов. Повторите после сброса окна. |
503 | Сайт в режиме обслуживания или только для чтения. |
Ограничение частоты запросов
Загрузки через API подчиняются настраиваемому лимиту запросов, отдельному от веб-интерфейса. Лимит задаётся на API-ключ.
Создание файлов торрента с помощью torrent-builder
torrent-builder — это CLI-инструмент с открытым исходным кодом, построенный на libtorrent-rasterbar, позволяющий создавать файлы .torrent в форматах BitTorrent v1, v2 и hybrid из командной строки. Он идеально сочетается с AniRena upload API — создайте файл локально, затем отправьте его через POST прямо на трекер. 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 | Добавить URL анонса трекера. Повторите флаг для добавления нескольких трекеров. |
--comment | Встроить строку комментария метаданных в торрент. |
--private | Установить флаг приватности для ограничения раздачи только перечисленными трекерами. |
--piece-size | Размер части в КБ (16-32768). Оставьте без значения для автоматического выбора. |
-i | Запустить пошаговый интерактивный режим настройки. |
Сквозной рабочий процесс: сборка -> загрузка
Приведённые ниже примеры создают hybrid-торрент с помощью torrent-builder, затем выполняют аутентификацию через 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 | Нет | Slug категории (например, "anime"). |
sub_category | string | Нет | Slug подкатегории (например, "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 — Количество неудалённых комментариев к этому торренту.
Ограничение частоты поисковых запросов
Поисковые запросы подчиняются отдельному настраиваемому лимиту (по умолчанию 60 запросов за 60 секунд на API-ключ). Аккаунты сотрудников освобождены от ограничения.
Получить сведения о торренте
/api/v1/torrent/{id}Получает полные метаданные одного торрента — включая поля, которые конечная точка поиска пропускает, такие как описание в Markdown, встроенный комментарий .torrent, список файлов с размером каждого и полный макет уровней трекеров. При наличии данных живые счётчики сидеров и личеров считываются из трекера.
Ответ
{
"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 — Живые счётчики из внутреннего трекера; оба возвращают 0, когда в хранилище трекера нет записи для этого info hash или оно недоступно.
ext_seeders, ext_leechers — Максимальное число сидеров и личеров, сообщённое любым отдельным внешним трекером, для которого выполнен scrape по этому торренту. Трекеры, отслеживающие один и тот же рой, пересекаются, поэтому используется максимум, а не сумма; оба возвращают 0, когда ни у одного трекера нет данных scrape для этого info hash.
Ответы с ошибками
| HTTP-статус | Значение |
|---|---|
400 | Идентификатор торрента должен быть UUID из 36 символов с дефисами или 32-символьной шестнадцатеричной строкой без дефисов. |
401 | Отсутствует, истёк срок действия или уже ротирован bearer-токен. Повторно аутентифицируйтесь через POST /api/v1/auth/token. |
404 | Торрент не найден. |
429 | Превышен лимит запросов. Повторите после сброса окна. |
503 | Сайт в режиме обслуживания или только для чтения. |
Получение комментариев к торренту
/api/v1/torrents/{id}/commentsПолучить постраничные комментарии к торренту. Количество комментариев на странице задаётся параметром COMMENT_PER_PAGE в файле .env сервера (по умолчанию 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-токен. Повторно аутентифицируйтесь через POST /api/v1/auth/token. |
403 | Комментарии для этого торрента отключены. |
404 | Торрент не найден. |
503 | Сайт в режиме обслуживания или только для чтения. |
Поиск записей аниме
/api/v1/anime/search?q=<query>Ищите записи аниме по названию для получения их UUID. UUID можно передать как anime_id в теле загрузки, чтобы привязать торрент к записи аниме во время загрузки, или использовать с PUT /api/torrents/{id}/anime после загрузки. Аутентификация не требуется. Подчиняется тому же ограничению скорости, что и поиск торрентов (по умолчанию 60 запросов за 60 секунд на IP).
# 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 token.
/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-токен. Повторно аутентифицируйтесь через POST /api/v1/auth/token. |
404 | Группа не найдена или не является публично доступной. |
429 | Превышен лимит запросов. Повторите после сброса окна. |
503 | Сайт в режиме обслуживания или только для чтения. |
8. Пожертвования
Если вы хотите поддержать AniRena и помочь покрыть расходы на хостинг наших серверов и сервисов, вы можете отправить пожертвование на один из следующих криптовалютных кошельков:
bc1qy2h3ddq6ak5damvnf4r5vu3ydehhxrcq8gllwn0xCbaFe03832F95F86AF2536d52710e78C63b62Cd33ucetj2XDGHQg9PVRPMxerNi7c6kX7GJkjQNg9yjwGegLbpt61yX3RjGtB1Ef8vgVz6Hr6baQsTjVkЛюбое пожертвование, большое или маленькое, высоко ценится и идёт непосредственно на поддержание работы AniRena. Спасибо за вашу поддержку!
9. Программное обеспечение
AniRena Player — это бесплатное настольное приложение, позволяющее транслировать видео прямо из торрентов, проиндексированных на этом сайте, без необходимости ждать завершения полной загрузки. Просто вставьте magnet-ссылку или откройте файл .torrent — воспроизведение начнётся, как только будет достаточно данных.
Обе сборки полностью автономны — все зависимости упакованы внутрь исполняемого файла. Не нужен установщик или среда выполнения — просто скачайте и запустите.
Установщик (.exe). Обновляется автоматически внутри приложения.
Образ диска (.dmg) для Mac с Apple Silicon (M1 и новее). Обновляется автоматически внутри приложения.
Образ диска (.dmg) для Mac с Intel. Обновляется автоматически внутри приложения.
Портативный одиночный файл, установка не требуется. Единственный формат Linux с автообновлением внутри приложения.
Установка: sudo apt install ./<file>.deb — обновляется через apt или повторной загрузкой, не внутри приложения.
Установка: sudo dnf install ./<file>.rpm — обновляется через dnf или повторной загрузкой, не внутри приложения.
Установите вручную на 64-битные ARM-устройства Android (большинство современных телефонов / планшетов). Обновляется загрузкой нового APK.
Старые версии
Установите вручную на 32-битные ARM-устройства Android (старые телефоны / планшеты). Обновляется загрузкой нового APK.