commits
tags
#include "torlinkc/sources/yts.hpp"
#include <array>
#include <stop_token>
#include <nlohmann/json.hpp>
#include "torlinkc/sources/magnet.hpp"
#include "torlinkc/util/net.hpp"
#include "torlinkc/util/url_encode.hpp"
using nlohmann::json;
namespace torlinkc {
namespace {
constexpr std::array<const char*, 3> kHosts = {"yts.mx", "yts.am", "yts.rs"};
json fetchMovies(const std::string& query, const std::stop_token& stopToken) {
std::string qs = "limit=50";
qs += query.empty() ? "&sort_by=date_added" : "&query_term=" + encodeURIComponent(query);
std::exception_ptr lastError;
for (const char* host : kHosts) {
if (stopToken.stop_requested()) throw HttpError(0, "aborted");
try {
FetchOptions opts;
opts.retries = 1;
opts.stopToken = stopToken;
HttpResponse res = fetchResilient("https://" + std::string(host) + "/api/v2/list_movies.json?" + qs, opts);
if (res.ok()) return json::parse(res.body);
lastError = std::make_exception_ptr(HttpError(res.status, "YTS returned " + std::to_string(res.status)));
} catch (const HttpError&) {
lastError = std::current_exception();
if (stopToken.stop_requested()) std::rethrow_exception(lastError);
}
}
if (lastError) std::rethrow_exception(lastError);
throw HttpError(0, "YTS unreachable");
}
} // namespace
Source ytsSource() {
Source s;
s.id = "yts";
s.label = "YTS";
s.groups = {SourceGroup::Movies};
s.homepage = "https://yts.mx";
s.reportsHealth = true;
s.search = [](const std::string& query, const SearchOptions& opts) {
const std::string q = query; // trimming happens implicitly: an all-whitespace query behaves like empty here
const json root = fetchMovies(q, opts.stopToken);
std::vector<TorrentResult> out;
auto movies = root.value("data", json::object()).value("movies", json::array());
for (const auto& movie : movies) {
std::string base = movie.value("title_long", "");
if (base.empty()) base = movie.value("title", "Unknown");
const auto added = movie.contains("date_uploaded_unix") && movie["date_uploaded_unix"].is_number()
? std::optional<std::int64_t>(movie["date_uploaded_unix"].get<std::int64_t>())
: std::nullopt;
for (const auto& t : movie.value("torrents", json::array())) {
const std::string hashRaw = t.value("hash", "");
if (hashRaw.empty()) continue;
std::string infoHash = hashRaw;
for (auto& c : infoHash) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
std::string tag;
for (const char* key : {"quality", "type"}) {
const std::string v = t.value(key, "");
if (!v.empty()) tag += (tag.empty() ? "" : " ") + v;
}
const std::string name = tag.empty() ? base : base + " [" + tag + "]";
TorrentResult r;
r.infoHash = infoHash;
r.name = name;
r.sizeBytes = t.value("size_bytes", static_cast<std::int64_t>(0));
r.seeders = t.value("seeds", 0);
r.leechers = t.value("peers", 0);
r.source = "yts";
r.magnet = buildMagnet(infoHash, name);
r.added = added;
out.push_back(std::move(r));
}
}
return out;
};
return s;
}
} // namespace torlinkc