Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Created November 9, 2021 17:57
Show Gist options
  • Save ITotalJustice/5e67aaecfed32095a4bb54f068d9a50d to your computer and use it in GitHub Desktop.
Save ITotalJustice/5e67aaecfed32095a4bb54f068d9a50d to your computer and use it in GitHub Desktop.
crapi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
#include <optional>
namespace cr {
struct AccountData {
unsigned user_id;
std::string username;
std::string email;
std::optional<std::string> first_name;
std::optional<std::string> last_name;
bool is_premium;
std::optional<bool> is_publisher;
std::optional<std::string> access_type;
std::optional<std::string> created_timestamp;
std::string auth;
std::optional<std::string> expires;
friend void from_json(const json& j, AccountData& g);
};
struct Account {
AccountData data;
bool error;
std::string code;
friend void from_json(const json& j, Account& g);
};
} // namespace cr
#pragma once
#include "image.hpp"
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
#include <optional>
namespace cr {
struct CategoriesData final {
std::string tag;
std::string label;
friend void from_json(const json& j, CategoriesData& g);
};
struct CategoriesType final {
std::vector<CategoriesData> data;
std::string title;
friend void from_json(const json& j, CategoriesType& g);
};
struct Categories final {
std::vector<CategoriesType> data;
std::string code;
bool error;
friend void from_json(const json& j, Categories& g);
};
} // namescape cr
#pragma once
#include "image.hpp"
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
namespace cr {
struct CollectionData final {
std::string collection_id;
std::string series_id;
std::string name;
std::optional<std::string> description;
std::optional<std::string> media_type;
std::string season;
std::optional<bool> complete;
std::optional<Image> landscape_image;
std::optional<Image> portrait_image;
std::optional<std::string> availability_notes;
std::optional<std::string> created_timestamp;
friend void from_json(const json& j, CollectionData& g);
};
struct Collection final {
std::vector<CollectionData> data;
std::string code;
bool error;
friend void from_json(const json& j, Collection& g);
};
} // namespace cr
#include "crapi.hpp"
#include "../nlohmann/json.hpp"
#include "../hlsparse/hlsparse.hpp"
#include "../../utils/logger.hpp"
// todo: save auth token
#include <fstream>
namespace cr {
static constexpr auto API_HOST = "https://api.crunchyroll.com";
static constexpr auto API_MANGA_HOST = "https://api-manga.crunchyroll.com";
static constexpr auto RPC_API_HOST = "https://www.crunchyroll.com";
static constexpr auto CMD_SESSION = "start_session";
static constexpr auto CMD_LOGIN = "login";
static constexpr auto CMD_LOGOUT = "logout";
static constexpr auto CMD_ADD_QUEUE = "add_to_queue";
static constexpr auto CMD_GET_QUEUE = "queue";
static constexpr auto CMD_REMOVE_QUEUE = "remove_from_queue";
static constexpr auto CMD_RECENTLY_WATCHED = "recently_watched";
static constexpr auto CMD_AUTOCOMPLETE = "autocomplete";
static constexpr auto CMD_CATEGORIES = "categories";
static constexpr auto CMD_LIST_COLLECTION = "list_collections";
static constexpr auto CMD_LIST_SERIES = "list_series";
static constexpr auto CMD_LIST_MEDIA = "list_media";
static constexpr auto CMD_INFO = "info";
static constexpr auto MANGA_CMD_CR_AUTHENTICATE = "cr_authenticate";
static constexpr auto MANGE_CMD_CHAPTERS = "chapters";
static constexpr auto MANGE_CMD_LIST_CHAPTERS = "list_chapters";
static constexpr auto MANGE_CMD_LIST_CHAPTER = "list_chapter";
Crapi::Crapi() {
}
Crapi::~Crapi() {
}
auto Crapi::StartSessionAsync(std::stop_token stk, const std::string& device_id, const std::string& device_type, const std::string& access_token) -> bool {
auto data = "&device_id=" + device_id + "&device_type=" + device_type + "&access_token=" + access_token;
if (auto result = this->Get(stk, CMD_SESSION, std::move(data), true)) {
auto parsed_session = json::parse(*result).get<Session>();
if (parsed_session.error) {
LOG("[CRAPI] got error when getting session: %s\n", parsed_session.code.c_str());
return false;
}
this->session_id = std::move(parsed_session.data);
return true;
}
LOG("[CRAPI] failed to get session data\n");
return false;
}
// NOTE: not sure why i commented this out...did this not work?
// the api is correct so it should work
// auto Crapi::Login(const std::string& user, const std::string& pass) -> bool {
// auto data = "&account=" + user + "&password=" + pass;
// auto result = this->Post(CMD_LOGIN, std::move(data));
// return true;
// }
// auto Crapi::Logout() -> bool {
// if (!this->auth) {
// return false;
// }
// auto data = "&auth=" + this->auth.value().auth;
// auto result = this->Post(CMD_LOGOUT, std::move(data));
// return true;
// }
// auto Crapi::ListCategories() -> Categories {
// auto data = std::string{"&media_type="} + MEDIA_TYPE_ANIME;
// auto result = this->Get(CMD_CATEGORIES, std::move(data));
// }
auto Crapi::SearchAsync(std::stop_token stk, const std::string& search, std::uint32_t offset, int limit) -> std::optional<Series> {
auto data = "q=" + search + "&offset=" + std::to_string(offset) + "&limit=" + std::to_string(limit);
if (auto result = this->Get(stk, CMD_AUTOCOMPLETE, std::move(data))) {
return json::parse(*result).get<Series>();
}
LOG("[CRAPI-ERROR] failed to get ListSeries() data\n");
return {};
}
auto Crapi::ListCollectionsAsync(std::stop_token stk, const SeriesID& id, std::uint32_t offset, int limit, const std::string& sort, const std::string& locale) -> std::optional<Collection> {
auto data = "series_id=" + id + "&offset=" + std::to_string(offset) + "&limit=" + std::to_string(limit) + "&sort=" + sort + "&locale=" + locale;
if (auto result = this->Get(stk, CMD_LIST_COLLECTION, std::move(data))) {
return json::parse(*result).get<Collection>();
}
LOG("[CRAPI-ERROR] failed to get ListCollections() data\n");
return {};
}
auto Crapi::ListSeriesAsync(std::stop_token stk, const std::string& filter, std::uint32_t offset, int limit, const std::string& sort, const std::string& locale) -> std::optional<Series> {
auto data = "filter=" + filter + "&offset=" + std::to_string(offset) + "&limit=" + std::to_string(limit) + "&sort=" + sort + "&locale=" + locale + "&media_type=" + MEDIA_TYPE_ANIME;
if (auto result = this->Get(stk, CMD_LIST_SERIES, std::move(data))) {
return json::parse(*result).get<Series>();
}
LOG("[CRAPI-ERROR] failed to get ListSeries() data\n");
return {};
}
auto Crapi::ListMediaAsync(std::stop_token stk, const std::string& type, const std::string& id, std::uint32_t offset, int limit, const std::string& sort, const std::string& locale) -> std::optional<Media> {
auto data = type + '=' + id + "&offset=" + std::to_string(offset) + "&limit=" + std::to_string(limit) + "&sort=" + sort + "&locale=" + locale + "&fields=" + "media.stream_data,media.collection_id,media.collection_name,media.stream_data,media.available,media.episode_number,media.duration,media.series_name,media.premium_only,media.name";
if (auto result = this->Get(stk, CMD_LIST_MEDIA, std::move(data))) {
return json::parse(*result).get<Media>();
}
LOG("[CRAPI-ERROR] failed to get ListMedia() data\n");
return {};
}
auto Crapi::GetStreamUrl(const MediaData& media) -> std::optional<std::string> {
// check if we have any streams
if (!media.stream_data || media.stream_data->streams.empty()) {
LOG("[CRAPI] no streams found for media %s\n", media.name.c_str());
return {};
}
// for some reason, CR master url's are all the same,
// so we can pick any of them.
const auto& master_url = media.stream_data->streams[0].url;
return Crapi::GetStreamUrl(master_url);
}
auto Crapi::GetStreamUrl(const std::string& master_url) -> std::optional<std::string> {
// sanity check!
if (master_url.empty()) {
LOG("[CRAPI] master url is empty!\n");
return {};
}
// use a new curl handle for this as it will trash the dns cache of our api handle.
curl::CurlWrapper curl_wrapper;
if (const auto data = curl_wrapper.Download(curl::Url{master_url})) {
if (auto parsed_hls = hls::ParseMaster(*data); !parsed_hls.empty()) {
// todo: we return the first stream found, but we should let the user
// pass in an option for which stream they want.
// or maybe return all the streams and let the user pick which one they want.
// NOTE: for some reason, on the switch, some url's load *much* slower than others
// ive checked the common slow urls and they seem to be using cdn=akamai-prod.
// because of this, try and find the first stream that isn't cdn=akamai-prod.
// this is usually array[0] or array [1], ive tested around 1000+ episodes
// and thats always the case.
// NOTE2: however, cr often changes their url structure. a few months ago
// they used a vip system where there was 3 links per quality setting.
// VIP, COMMON1, COMMON2.
// vip was found in the url, common i just made up but i assumed it was used
// for non premium members.
// so when cr changes their api again, things will get slow if the urls
// aren't filtered out!
// i should add a config file with the urls to filter out and let users
// change it when stuff breaks.
// NOTE3: it seems that every link is avaliable on any cdn, because of this
// i'll just force it on the fastest cdn (for switch)
static constexpr std::array cdns{
std::string_view{"cdn=cloudfront-prod"},
std::string_view{"cdn=ll-prod"},
std::string_view{"cdn=akamai-prod"} // slow, lags on switch
};
auto& selected_stream = parsed_hls[0];
auto& url = selected_stream.url;
// replace cdn
if (auto it = url.find_last_of("cdn="); it != url.npos) {
return url.replace(it, url.size() - 1, cdns[1]);
} else {
LOG("[CRAPI-WARN] unable to find cdn= key, api changed again!!!\n");
return selected_stream.url;
}
}
}
return {};
}
auto Crapi::Get(std::stop_token stk, const std::string& cmd, std::string data, bool new_session) -> std::optional<std::string> {
if (!new_session && !this->session_id) {
return {};
}
if (!new_session) {
data += "&session_id=" + this->session_id.value().session_id;
}
const auto url = std::string{API_HOST} + '/' + cmd + ".0.json?" + data;
LOG("[CRAPI-INFO] get-url: %s\n", url.c_str());
return this->curl.Download(
curl::Url{url},
curl::StopCallback{[stk](){
return stk.stop_requested();
}}
);
}
auto Crapi::Post(std::stop_token stk, const std::string& cmd, std::string data, bool new_session) -> std::optional<std::string> {
if (!new_session && !this->session_id) {
return {};
}
if (!new_session) {
data += "&session_id=" + this->session_id.value().session_id;
}
const auto url = std::string{API_HOST} + '/' + cmd + ".0.json";
return this->curl.Download(
curl::Url{url},
curl::Fields{data},
curl::StopCallback{[stk](){
return stk.stop_requested();
}}
);
}
} // namespace cr
// basic cr-api wrapper. MIT license
#pragma once
#include "account.hpp"
#include "categories.hpp"
#include "collection.hpp"
#include "image.hpp"
#include "media.hpp"
#include "queue.hpp"
#include "series.hpp"
#include "session.hpp"
#include "../curlwrapper/curl_wrapper.hpp"
#include <cstdint>
#include <string>
#include <optional>
#include <stop_token>
namespace cr {
/*
* Device ID can be random. The same deviceID can be used. Do CR ban ID's?
*/
static constexpr auto DEFAULT_DEVICE_ID = "QzmrIu1MEaT9tvrlQLrYBZAEsFjZOX1o";
/*
* Device type. I don't think is really matters. Probably more-so for statistics.
*/
static constexpr auto DEVICE_TYPE_ANDROID = "com.crunchyroll.crunchyroid";
static constexpr auto DEVICE_TYPE_IPHONE = "com.crunchyroll.iphone";
static constexpr auto DEVICE_TYPE_DESKTOP = "com.crunchyroll.desktop";
static constexpr auto DEFAULT_DEVICE_TYPE = DEVICE_TYPE_ANDROID;
/*
* Access tokens. These are obtained from the applications.
*/
static constexpr auto ACCESS_TOKEN_NEW = "WveH9VkPLrXvuNm";
static constexpr auto ACCESS_TOKEN_OLD_1 = "QWjz212GspMHH9h";
static constexpr auto ACCESS_TOKEN_OLD_2 = "LNDJgOit5yaRIWN";
static constexpr auto DEFAULT_ACCESS_TOKEN = ACCESS_TOKEN_NEW;
/*
* Location. Used for when getting a session and getting media.
*/
static constexpr auto LOCALE_GB = "enGB";
static constexpr auto LOCALE_US = "enUS";
static constexpr auto LOCALE_ESLA = "esLA";
static constexpr auto LOCALE_ES = "esES";
static constexpr auto LOCALE_PTBR = "ptBR";
static constexpr auto LOCALE_PT = "ptPT";
static constexpr auto LOCALE_FR = "frFR";
static constexpr auto LOCALE_DE = "deDE";
static constexpr auto LOCALE_ARME = "arME";
static constexpr auto LOCALE_IT = "itIT";
static constexpr auto LOCALE_RU = "ruRU";
static constexpr auto DEFAULT_LOCALE = LOCALE_GB;
/*
* Sorting when using any list command.
*/
static constexpr auto SORT_ASCENDING = "asc";
static constexpr auto SORT_DESCENDING = "dec";
/*
* Media type.
*/
static constexpr auto MEDIA_TYPE_ANIME = "anime";
static constexpr auto MEDIA_TYPE_DRAMA = "drama";
static constexpr auto MEDIA_TYPE_BOTH = "anime|drama";
/*
* Stream quality
*/
static constexpr auto QUALITY_ADAPTIVE = "adaptive";
static constexpr auto QUALITY_LOW = "low";
static constexpr auto QUALITY_MEDIUM = "mid";
static constexpr auto QUALITY_HIGH = "high";
static constexpr auto QUALITY_ULTRA = "ultra";
/*
* Used for list_series
*/
static constexpr auto FILTER_ALPHA = "alpha";
static constexpr auto FILTER_FEATURED = "featured";
static constexpr auto FILTER_NEWEST = "newest";
static constexpr auto FILTER_POPULAR = "popular";
static constexpr auto FILTER_PREFIX = "prefix:";
static constexpr auto FILTER_SIMULCAST = "simulcast";
static constexpr auto FILTER_TAG = "tag:";
static constexpr auto FILTER_UPDATED = "updated";
/*
* Used for list_catagories
*/
static constexpr auto FILTER_GENRES = "genres";
static constexpr auto FILTER_SEASONS = "seasons";
class Crapi final {
public:
using CollectionID = std::string;
using SeriesID = std::string;
using MediaID = std::string;
public:
Crapi();
~Crapi();
// auto info is saved internally
auto StartSessionAsync(std::stop_token token, const std::string& device_id = DEFAULT_DEVICE_ID, const std::string& device_type = DEFAULT_DEVICE_TYPE, const std::string& access_token = DEFAULT_ACCESS_TOKEN) -> bool;
auto StartSession(const std::string& device_id = DEFAULT_DEVICE_ID, const std::string& device_type = DEFAULT_DEVICE_TYPE, const std::string& access_token = DEFAULT_ACCESS_TOKEN) -> bool {
return this->StartSessionAsync(std::stop_token{}, device_id, device_type, access_token);
}
// auto Login(const std::string& user, const std::string& pass) -> bool;
// auto Logout() -> bool;
// auto ListCategories() -> Categories;
auto SearchAsync(std::stop_token token, const std::string& search, std::uint32_t offset = 0, int limit = 15) -> std::optional<Series>;
auto Search(const std::string& search, std::uint32_t offset = 0, int limit = 15) -> std::optional<Series> {
return this->SearchAsync(std::stop_token{}, search, offset, limit);
}
auto ListCollectionsAsync(std::stop_token token, const SeriesID& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Collection>;
auto ListCollections(const SeriesID& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Collection> {
return this->ListCollectionsAsync({}, id, offset, limit, sort, locale);
}
auto ListSeriesAsync(std::stop_token token, const std::string& filter, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series>;
auto ListSeries(const std::string& filter, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync({}, filter, offset, limit, sort, locale);
}
auto ListSeriesAlpha(std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeries(FILTER_ALPHA, offset, limit, sort, locale);
}
auto ListSeriesFeatured(std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeries(FILTER_FEATURED, offset, limit, sort, locale);
}
auto ListSeriesNewest(std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeries(FILTER_NEWEST, offset, limit, sort, locale);
}
auto ListSeriesPopular(std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeries(FILTER_POPULAR, offset, limit, sort, locale);
}
auto ListSeriesSimulcast(std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeries(FILTER_SIMULCAST, offset, limit, sort, locale);
}
auto ListSeriesUpdated(std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeries(FILTER_UPDATED, offset, limit, sort, locale);
}
auto ListSeriesAlphaAsync(std::stop_token token, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync(token, FILTER_ALPHA, offset, limit, sort, locale);
}
auto ListSeriesFeaturedAsync(std::stop_token token, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync(token, FILTER_FEATURED, offset, limit, sort, locale);
}
auto ListSeriesNewestAsync(std::stop_token token, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync(token, FILTER_NEWEST, offset, limit, sort, locale);
}
auto ListSeriesPopularAsync(std::stop_token token, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync(token, FILTER_POPULAR, offset, limit, sort, locale);
}
auto ListSeriesSimulcastAsync(std::stop_token token, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync(token, FILTER_SIMULCAST, offset, limit, sort, locale);
}
auto ListSeriesUpdatedAsync(std::stop_token token, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Series> {
return this->ListSeriesAsync(token, FILTER_UPDATED, offset, limit, sort, locale);
}
auto ListMediaAsync(std::stop_token token, const std::string& type, const std::string& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Media>;
auto ListMedia(const std::string& type, const std::string& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Media> {
return this->ListMediaAsync({}, type, id, offset, limit, sort, locale);
}
auto ListMediaFromCollectionID(const CollectionID& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Media> {
return this->ListMedia("collection_id", id, offset, limit, sort, locale);
}
auto ListMediaFromSeriesID(const SeriesID& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Media> {
return this->ListMedia("collection_id", id, offset, limit, sort, locale);
}
auto ListMediaFromCollectionIDAsync(std::stop_token token, const CollectionID& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Media> {
return this->ListMediaAsync(token, "collection_id", id, offset, limit, sort, locale);
}
auto ListMediaFromSeriesIDAsync(std::stop_token token, const SeriesID& id, std::uint32_t offset = 0, int limit = 15, const std::string& sort = SORT_ASCENDING, const std::string& locale = DEFAULT_LOCALE) -> std::optional<Media> {
return this->ListMediaAsync(token, "collection_id", id, offset, limit, sort, locale);
}
// auto GetCollectionInfo(CollectionID id) -> bool;
// auto GetSeriesInfo(SeriesID id) -> bool;
// auto GetMediaInfo(MediaID id) -> bool;
// helper for getting the stream.
static auto GetStreamUrl(const MediaData& media) -> std::optional<std::string>;
static auto GetStreamUrl(const std::string& master_url) -> std::optional<std::string>;
private:
// auto InternalStartSession(const std::string& device_id, const std::string& device_type, const std::string& access_token) -> std::optional<Session>;
// auto InternalLogin(const std::string& user, const std::string& pass) -> std::optional<Account>;
// auto InternalLogout() -> bool;
auto Get(std::stop_token stk, const std::string& cmd, std::string data, bool new_session = false) -> std::optional<std::string>;
auto Post(std::stop_token stk, const std::string& cmd, std::string data, bool new_session = false) -> std::optional<std::string>;
private:
std::optional<SessionData> session_id;
std::optional<AccountData> auth;
curl::CurlWrapper curl;
};
} // namespace cr
// todo: change this to rapidjson over nlohmann as i'm using rapidjson
// in most other apps.
#include "account.hpp"
#include "categories.hpp"
#include "collection.hpp"
#include "image.hpp"
#include "media.hpp"
#include "queue.hpp"
#include "series.hpp"
#include "session.hpp"
#include "../nlohmann/helper.hpp"
namespace cr {
void from_json(const json& j, AccountData& g) {
nlohmann::JSON_SET(j, "user_id", g.user_id);
nlohmann::JSON_SET(j, "username", g.username);
nlohmann::JSON_SET(j, "email", g.email);
nlohmann::JSON_SET(j, "first_name", g.first_name);
nlohmann::JSON_SET(j, "last_name",g.last_name);
nlohmann::JSON_SET(j, "is_premium", g.is_premium);
nlohmann::JSON_SET(j, "is_publisher", g.is_publisher);
nlohmann::JSON_SET(j, "access_type", g.access_type);
nlohmann::JSON_SET(j, "created_timestamp", g.created_timestamp);
nlohmann::JSON_SET(j, "auth", g.auth);
nlohmann::JSON_SET(j, "expires", g.expires);
}
void from_json(const json& j, cr::Account& g) {
j.at("data").get_to(g.data);
g.error = j["error"];
g.code = j["code"];
}
void from_json(const json& j, cr::SessionData& g) {
j.at("session_id").get_to(g.session_id);
if (j.contains<std::string>("country_code")) j.at("country_code").get_to(g.country_code);
if (j.contains<std::string>("ip")) j.at("ip").get_to(g.ip);
if (j.contains<std::string>("device_type")) j.at("device_type").get_to(g.device_type);
if (j.contains<std::string>("device_id")) j.at("device_id").get_to(g.device_id);
// if (j.contains<std::string>("user")) j.at("user").get_to(g.user);
// if (j.contains<std::string>("auth")) j.at("auth").get_to(g.auth);
// if (j.contains<std::string>("expires")) j.at("expires").get_to(g.expires);
// if (j.contains<std::string>("version")) j.at("version").get_to(g.version);
}
void from_json(const json& j, cr::Session& g) {
j.at("data").get_to(g.data);
g.error = j["error"];
g.code = j["code"];
}
void from_json(const json& j, CategoriesData& g) {
// nlohmann::JSON_SET(j, "tag",g.tag);
// nlohmann::JSON_SET(j, "label",g.label);
}
void from_json(const json& j, CategoriesType& g) {
// nlohmann::JSON_SET(j, "title", g.title);
}
void from_json(const json& j, Categories& g) {
auto data = j.at("data");
data.get_to(g.data);
// if (data.is_object()) {
// data.get_to(g.data);
// } else {
// data.get_to(g.data);
// }
j.at("error").get_to(g.error);
j.at("code").get_to(g.code);
}
void from_json(const json& j, Image& g) {
// nlohmann::JSON_SET(j, "thumb_url", g.thumb_url);
// nlohmann::JSON_SET(j, "small_url", g.small_url);
// nlohmann::JSON_SET(j, "medium_url", g.medium_url);
// nlohmann::JSON_SET(j, "large_url", g.large_url);
// nlohmann::JSON_SET(j, "full_url", g.full_url);
// nlohmann::JSON_SET(j, "wide_url", g.wide_url);
// nlohmann::JSON_SET(j, "widestar_url", g.widestar_url);
// nlohmann::JSON_SET(j, "fwide_url", g.fwide_url);
// nlohmann::JSON_SET(j, "fwidestar_url", g.fwidestar_url);
// nlohmann::JSON_SET(j, "width", g.width);
// nlohmann::JSON_SET(j, "height", g.height);
}
void from_json(const json& j, CollectionData& g) {
nlohmann::JSON_SET(j, "collection_id", g.collection_id);
nlohmann::JSON_SET(j, "series_id", g.series_id);
nlohmann::JSON_SET(j, "name", g.name);
nlohmann::JSON_SET(j, "description", g.description);
nlohmann::JSON_SET(j, "media_type", g.media_type);
nlohmann::JSON_SET(j, "season", g.season);
nlohmann::JSON_SET(j, "complete", g.complete);
// nlohmann::JSON_SET(j, "landscape_image", g.landscape_image);
// nlohmann::JSON_SET(j, "portrait_image", g.portrait_image);
// nlohmann::JSON_SET(j, "availability_notes", g.availability_notes);
// nlohmann::JSON_SET(j, "created_timestamp", g.created_timestamp);
}
void from_json(const json& j, Collection& g) {
if (j.contains("data")) {
auto data = j.at("data");
if (data.is_object()) {
data.get_to(g.data);
} else if (data.is_array()) {
data.get_to(g.data);
} else {
throw;
}
}
g.error = j["error"];
g.code = j["code"];
}
void from_json(const json& j, SeriesData& g) {
nlohmann::JSON_SET(j, "series_id", g.series_id);
nlohmann::JSON_SET(j, "url", g.url);
nlohmann::JSON_SET(j, "name", g.name);
// nlohmann::JSON_SET(j, "media_type", g.media_type);
// nlohmann::JSON_SET(j, "landscape_image", g.landscape_image);
// nlohmann::JSON_SET(j, "portrait_image", g.portrait_image);
nlohmann::JSON_SET(j, "description", g.description);
}
void from_json(const json& j, Series& g) {
if (j.contains("data")) {
auto data = j.at("data");
if (data.is_object()) {
data.get_to(g.data);
} else if (data.is_array()) {
data.get_to(g.data);
} else {
throw;
}
}
g.error = j["error"];
g.code = j["code"];
}
void from_json(const json& j, cr::Stream& g) {
nlohmann::JSON_SET(j, "quality", g.quality);
nlohmann::JSON_SET(j, "time", g.time);
nlohmann::JSON_SET(j, "url", g.url);
}
void from_json(const json& j, cr::StreamData& g) {
nlohmann::JSON_SET(j, "hardsub_lang", g.hardsub_lang);
nlohmann::JSON_SET(j, "audio_lang", g.audio_lang);
nlohmann::JSON_SET(j, "format", g.format);
nlohmann::JSON_SET(j, "streams", g.streams);
}
void from_json(const json& j, cr::MediaData& g) {
nlohmann::JSON_SET(j, "media_id", g.media_id);
nlohmann::JSON_SET(j, "collection_id", g.collection_id);
nlohmann::JSON_SET(j, "series_id", g.series_id);
// nlohmann::JSON_SET(j, "media_type", g.media_type);
nlohmann::JSON_SET(j, "episode_number", g.episode_number);
nlohmann::JSON_SET(j, "name", g.name);
// nlohmann::JSON_SET(j, "description", g.description);
// nlohmann::JSON_SET(j, "screenshot_image", g.screenshot_image);
// nlohmann::JSON_SET(j, "bif_url", g.bif_url);
// nlohmann::JSON_SET(j, "url", g.url);
// nlohmann::JSON_SET(j, "clip", g.clip);
nlohmann::JSON_SET(j, "available", g.available);
nlohmann::JSON_SET(j, "premium_available", g.premium_available);
// nlohmann::JSON_SET(j, "free_available", g.free_available);
// nlohmann::JSON_SET(j, "available_time", g.available_time);
// nlohmann::JSON_SET(j, "unavailable_time", g.unavailable_time);
// nlohmann::JSON_SET(j, "premium_available_time", g.premium_available_time);
// nlohmann::JSON_SET(j, "premium_unavailable_time", g.premium_unavailable_time);
// nlohmann::JSON_SET(j, "free_available_time", g.free_available_time);
// nlohmann::JSON_SET(j, "free_unavailable_time", g.free_unavailable_time);
// nlohmann::JSON_SET(j, "availability_notes", g.availability_notes);
// nlohmann::JSON_SET(j, "created", g.created);
// nlohmann::JSON_SET(j, "duration", g.duration);
// nlohmann::JSON_SET(j, "playhead", g.playhead);
// nlohmann::JSON_SET(j, "series_name", g.series_name);
// nlohmann::JSON_SET(j, "collection_name", g.collection_name);
// nlohmann::JSON_SET(j, "premium_only", g.premium_only);
nlohmann::JSON_SET(j, "stream_data", g.stream_data);
}
void from_json(const json& j, cr::Media& g) {
if (j.contains("data")) {
auto data = j.at("data");
if (data.is_object()) {
data.get_to(g.data);
} else if (data.is_array()) {
data.get_to(g.data);
} else {
throw;
}
}
g.error = j["error"];
g.code = j["code"];
}
} // namespace cr
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
#include <optional>
namespace cr {
struct Image final {
enum class ImageType {
THUMB, SMALL, MEDIUM, LARGE, FULL, WIDE, WIDESTAR, FWIDE, FWIDESTAR
};
auto ImageUrl(ImageType type) const -> std::optional<std::string> {
switch (type) {
case ImageType::THUMB: return thumb_url;
case ImageType::SMALL: return small_url;
case ImageType::MEDIUM: return medium_url;
case ImageType::LARGE: return large_url;
case ImageType::FULL: return full_url;
case ImageType::WIDE: return wide_url;
case ImageType::WIDESTAR: return widestar_url;
case ImageType::FWIDE: return fwide_url;
case ImageType::FWIDESTAR: return fwidestar_url;
default: return {};
}
// return std::nullopt;
}
std::optional<std::string> thumb_url;
std::optional<std::string> small_url;
std::optional<std::string> medium_url;
std::optional<std::string> large_url;
std::optional<std::string> full_url;
std::optional<std::string> wide_url;
std::optional<std::string> widestar_url;
std::optional<std::string> fwide_url;
std::optional<std::string> fwidestar_url;
std::string width;
std::string height;
friend void from_json(const json& j, Image& g);
};
} // namespace cr
/*
#include <future>
// https://stackoverflow.com/a/27033822
auto media = new cr::Media();
auto task = std::async(std::launch::async, static_cast<bool(cr::Media::*)(const std::string&)>(&cr::Media::parse), media, data);
auto result = task.get();
*/
#pragma once
#include "image.hpp"
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
#include <optional>
#include <variant>
namespace cr {
struct Stream final {
std::string quality;
std::string time;
std::string url;
friend void from_json(const json& j, Stream& g);
};
struct StreamData final {
std::optional<std::string> hardsub_lang; // can be NULL (for some reason).
std::optional<std::string> audio_lang;
std::string format;
std::vector<Stream> streams;
friend void from_json(const json& j, StreamData& g);
};
struct MediaData final {
std::string media_id;
std::string collection_id;
std::string series_id;
std::optional<std::string> media_type;
std::string episode_number;
std::string name;
std::optional<std::string> description;
std::optional<Image> screenshot_image;
std::optional<std::string> bif_url;
std::optional<std::string> url;
// bool clip;
bool available;
bool premium_available;
// bool free_available;
// std::optional<std::string> available_time;
// std::optional<std::string> unavailable_time;
// std::optional<std::string> premium_available_time;
// std::optional<std::string> premium_unavailable_time;
// std::optional<std::string> free_available_time;
// std::optional<std::string> free_unavailable_time;
// std::optional<std::string> availability_notes;
std::optional<std::string> created;
std::optional<unsigned> duration; // seconds.
std::optional<unsigned> playhead; // seconds.
std::optional<std::string> series_name;
std::optional<std::string> collection_name;
bool premium_only;
std::optional<StreamData> stream_data;
friend void from_json(const json& j, MediaData& g);
};
struct Media final {
std::vector<MediaData> data;
std::string code;
bool error;
friend void from_json(const json& j, Media& g);
};
} // namespace cr
#pragma once
#include "media.hpp"
#include "series.hpp"
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
#include <optional>
namespace cr {
struct QueueData {
std::optional<unsigned> ordering;
std::optional<unsigned> queue_entry_id;
std::optional<unsigned> last_watched_media_playhead;
std::optional<unsigned> most_likely_media_playhead;
std::optional<unsigned> playhead;
std::optional<Media> last_watched_media;
std::optional<Media> most_likely_media;
std::optional<Series> series;
};
struct Queue {
std::vector<QueueData> data;
std::string code;
bool error;
};
} // namespace cr
#pragma once
#include "image.hpp"
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
namespace cr {
enum class SearchType {
ALPHA,
FEATURED,
NEWEST,
POPULAR,
SIMULCAST,
UPDATED
};
struct SeriesData final {
std::string series_id;
std::optional<std::string> url;
std::string name;
std::optional<std::string> media_type;
std::optional<Image> landscape_image;
std::optional<Image> portrait_image;
std::optional<std::string> description;
friend void from_json(const json& j, SeriesData& g);
};
struct Series final {
std::vector<SeriesData> data;
std::string code;
bool error;
friend void from_json(const json& j, Series& g);
};
}; // namescape cr
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include <vector>
#include <string>
#include <optional>
namespace cr {
struct SessionData {
std::string session_id;
std::string country_code;
std::string ip;
std::string device_type;
std::string device_id;
std::optional<std::string> user;
std::optional<std::string> auth;
std::optional<std::string> expires;
std::optional<std::string> version;
friend void from_json(const json& j, SessionData& g);
};
struct Session {
SessionData data;
bool error;
std::string code;
friend void from_json(const json& j, Session& g);
};
} // namespace cr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment