commits
tags
#include "torlinkc/sources/subsplease.hpp"
#include <array>
#include <regex>
#include <nlohmann/json.hpp>
#include "torlinkc/sources/magnet.hpp"
#include "torlinkc/util/date_parse.hpp"
#include "torlinkc/util/net.hpp"
#include "torlinkc/util/url_encode.hpp"
using nlohmann::json;
namespace torlinkc {
namespace {
constexpr std::array<const char*, 3> kResPreference = {"1080", "720", "480"};
std::string trim(const std::string& s) {
auto first = s.find_first_not_of(" \t\r\n");
if (first == std::string::npos) return "";
auto last = s.find_last_not_of(" \t\r\n");
return s.substr(first, last - first + 1);
}
std::optional<json> pickBest(const json& downloads) {
for (const char* res : kResPreference) {
for (const auto& d : downloads) {
if (d.value("res", "") == res && !d.value("magnet", "").empty()) return d;
}
}
for (const auto& d : downloads) {
if (!d.value("magnet", "").empty()) return d;
}
return std::nullopt;
}
} // namespace
Source subspleaseSource() {
Source s;
s.id = "subsplease";
s.label = "SubsPlease";
s.groups = {SourceGroup::Anime};
s.homepage = "https://subsplease.org";
// The SubsPlease API has no swarm data; every result reports seeders: 0.
s.reportsHealth = false;
s.search = [](const std::string& query, const SearchOptions& opts) {
const std::string q = trim(query);
std::string qs = "tz=UTC";
qs += q.empty() ? "&f=latest" : "&f=search&s=" + encodeURIComponent(q);
FetchOptions fetchOpts;
fetchOpts.stopToken = opts.stopToken;
HttpResponse res = fetchResilient("https://subsplease.org/api/?" + qs, fetchOpts);
if (!res.ok()) throw HttpError(res.status, "SubsPlease returned " + std::to_string(res.status));
const json root = json::parse(res.body);
std::vector<TorrentResult> out;
if (!root.is_object()) return out;
static const std::regex kSizeRe(R"([?&]xl=(\d+))");
for (const auto& entry : root.items()) {
const json& e = entry.value();
const auto downloads = e.value("downloads", json::array());
auto dl = pickBest(downloads);
if (!dl) continue;
const std::string magnet = dl->value("magnet", "");
auto parsed = parseMagnet(magnet);
if (!parsed) continue;
const std::string show = e.value("show", "Unknown");
const std::string episode = e.value("episode", "");
const std::string resStr = dl->value("res", "?");
std::smatch m;
const std::int64_t sizeBytes = std::regex_search(magnet, m, kSizeRe) ? std::stoll(m[1].str()) : 0;
TorrentResult r;
r.infoHash = parsed->infoHash;
r.name = show + (episode.empty() ? "" : " - " + episode) + " [" + resStr + "p]";
r.sizeBytes = sizeBytes;
r.source = "subsplease";
r.magnet = parsed->magnet;
r.added = parseDateToUnixSeconds(e.value("release_date", ""));
out.push_back(std::move(r));
}
return out;
};
return s;
}
} // namespace torlinkc