foxygit / Torlinkc Log in
commits tags

/src/sources/nyaa.cpp · 2.77 KB

raw
#include "torlinkc/sources/nyaa.hpp"

#include <regex>

#include "torlinkc/sources/magnet.hpp"
#include "torlinkc/sources/rss.hpp"
#include "torlinkc/util/date_parse.hpp"
#include "torlinkc/util/format.hpp"
#include "torlinkc/util/net.hpp"
#include "torlinkc/util/url_encode.hpp"

namespace torlinkc {

namespace {

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::string toLower(std::string s) {
  for (auto& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
  return s;
}

// [\s\S]*? stands in for JS's dotall `.*?` (std::regex has no dotall flag).
std::string tagValue(const std::string& item, const std::string& name) {
  const std::regex re("<" + name + R"(>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?</)" + name + ">");
  std::smatch m;
  if (!std::regex_search(item, m, re)) return "";
  return trim(m[1].str());
}

int toIntOrZero(const std::string& s) {
  try {
    return std::stoi(s);
  } catch (...) {
    return 0;
  }
}

}  // namespace

Source nyaaSource() {
  Source s;
  s.id = "nyaa";
  s.label = "Nyaa";
  s.groups = {SourceGroup::Anime};
  s.homepage = "https://nyaa.si";
  s.reportsHealth = true;
  s.search = [](const std::string& query, const SearchOptions& opts) {
    const std::string url = "https://nyaa.si/?page=rss&q=" + encodeURIComponent(trim(query)) + "&c=0_0&f=0";

    FetchOptions fetchOpts;
    fetchOpts.stopToken = opts.stopToken;
    HttpResponse res = fetchResilient(url, fetchOpts);
    if (!res.ok()) throw HttpError(res.status, "Nyaa returned " + std::to_string(res.status));

    std::vector<TorrentResult> out;
    const std::string marker = "<item>";
    std::size_t pos = res.body.find(marker);
    while (pos != std::string::npos) {
      const std::size_t start = pos + marker.size();
      const std::size_t next = res.body.find(marker, start);
      const std::string item = res.body.substr(start, next == std::string::npos ? std::string::npos : next - start);
      pos = next;

      const std::string infoHash = toLower(tagValue(item, "nyaa:infoHash"));
      const std::string name = unescapeEntities(tagValue(item, "title"));
      if (infoHash.empty() || name.empty()) continue;

      TorrentResult r;
      r.infoHash = infoHash;
      r.name = name;
      r.sizeBytes = parseSize(tagValue(item, "nyaa:size"));
      r.seeders = toIntOrZero(tagValue(item, "nyaa:seeders"));
      r.leechers = toIntOrZero(tagValue(item, "nyaa:leechers"));
      r.source = "nyaa";
      r.magnet = buildMagnet(infoHash, name);
      r.added = parseDateToUnixSeconds(tagValue(item, "pubDate"));
      out.push_back(std::move(r));
    }
    return out;
  };
  return s;
}

}  // namespace torlinkc