Created
November 9, 2021 17:57
-
-
Save ITotalJustice/5e67aaecfed32095a4bb54f068d9a50d to your computer and use it in GitHub Desktop.
crapi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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