Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Created November 9, 2021 17:58
Show Gist options
  • Save ITotalJustice/7dd2fc20c8ed22caa35ea8c5dbe0a59e to your computer and use it in GitHub Desktop.
Save ITotalJustice/7dd2fc20c8ed22caa35ea8c5dbe0a59e to your computer and use it in GitHub Desktop.
funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
// the only none bloat struct that funi uses!
struct Account {
struct User {
std::uint32_t id;
std::string first_name;
std::string last_name;
std::string email;
std::string last_login_local;
std::string displayName;
std::string avatar;
std::string defaultLanguage;
std::string last_login;
std::string date_joined;
friend void from_json(const json& j, User& g);
};
std::string token;
std::string rlildup_cookie;
User user;
std::string user_region;
friend void from_json(const json& j, Account& g);
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
struct EpisodeEntry {
struct Item {
std::optional<SeasonID> seasonId;
std::string episodeNum;
EpisodeID episodeId;
TitleID titleId;
std::optional<std::string> seasonNum;
std::string episodeName;
friend void from_json(const json& j, Item& g);
};
float starRating;
std::string synopsis;
std::string mediaCategory;
std::string contentType;
std::vector<std::string> genres;
Item item;
auto GetEpisodeID() const noexcept { return this->item.episodeId; }
// auto GetSeasonID() const noexcept { return this->item.sea; }
friend void from_json(const json& j, EpisodeEntry& g);
};
struct EpisodeResult {
std::uint32_t count;
std::vector<EpisodeEntry> items;
int limit;
std::uint32_t offset;
std::uint32_t total;
auto Empty() const noexcept { return this->items.empty(); }
friend void from_json(const json& j, EpisodeResult& g);
};
} // namespace funi
#include "funapi.hpp"
#include "../nlohmann/json.hpp"
#include "../hlsparse/hlsparse.hpp"
#include "../../utils/logger.hpp"
#ifdef __SWITCH__
#include <sys/select.h>
#endif
#include <curl/curl.h>
#include <cassert>
// todo: save auth token
#include <fstream>
namespace funi {
static constexpr auto API_URL = "https://prod-api-funimationnow.dadcdigital.com/api/";
static auto DebugDump(const std::string& path, const std::string& data) -> void {
#if 0
std::ofstream fs{path};
if (fs.good()) {
fs << data;
}
#endif
}
#define GET_HELPER(type) \
if (auto result = this->Get(stk, cmd, data)) { \
DebugDump(#type + std::to_string(id) + ".json", *result); \
try { \
return json::parse(*result).get<type>(); \
} catch (std::exception& e) { \
LOG("[FUNI-THROW] in %s ID: %u : %s\n", __func__, id, e.what()); \
} \
} \
return {};
Funapi::Funapi() {
}
Funapi::~Funapi() {
}
auto Funapi::LoginAsync(std::stop_token stk, const std::string& user, const std::string& pass) -> bool {
static constexpr auto cmd = "auth/login/";
const auto data = "username=" + user + "&password=" + pass;
if (auto result = this->Post(stk, cmd, data)) {
DebugDump("funi_login.json", *result);
return true;
}
return false;
}
auto Funapi::LogoutAsync(std::stop_token stk) -> bool {
this->account = std::nullopt;
// static constexpr auto cmd = "auth/logout/";
// if (auto result = this->Post(cmd, data)) {
// return true;
// }
return false;
}
// auto Funapi::GetLibrary() -> std::optional<Episodes> {
// static constexpr auto cmd = "source/funimation/search/auto/";
// const auto data = "?unique=true&limit=" + std::to_string(limit) + "&q=" + q + "&offset=" + std::to_string(offset);
// if (auto result = this->Get(cmd, data)) {
// return json::parse(*result).get<Media>();
// DebugDump("funi_search.json", *result);
// }
// return {};
// }
auto Funapi::GetHistoryAsync(std::stop_token stk, int limit) -> std::optional<EpisodeResult> {
static constexpr auto cmd = "source/funimation/history/";
const auto data = "?unique=true&limit=" + std::to_string(limit);
if (auto result = this->Get(stk, cmd, data)) {
DebugDump("funi_history.json", *result);
return json::parse(*result).get<EpisodeResult>();
}
return {};
}
auto Funapi::GetQueueAsync(std::stop_token stk, int limit) -> std::optional<EpisodeResult> {
static constexpr auto cmd = "source/funimation/queue/";
const auto data = "?unique=true&limit=" + std::to_string(limit);
if (auto result = this->Get(stk, cmd, data)) {
DebugDump("funi_queue.json", *result);
return json::parse(*result).get<EpisodeResult>();
}
return {};
}
auto Funapi::ListGenresAsync(std::stop_token stk, int limit, std::uint32_t offset) -> std::optional<Genres> {
static constexpr auto cmd = "funimation/genres/";
const auto data = "?limit=" + std::to_string(limit) + "&offset=" + std::to_string(offset);
if (auto result = this->Get(stk, cmd, data)) {
DebugDump("genres.json", *result);
return json::parse(*result).get<Genres>();
}
return {};
}
auto Funapi::ListTitlesFromGenreAsync(std::stop_token stk, GenreID id, int limit, std::uint32_t offset) -> std::optional<GenresTitles> {
static constexpr auto cmd = "source/catalog/genres/";
const auto data = std::to_string(id) + "/?limit=" + std::to_string(limit) + "&offset=" + std::to_string(offset);
GET_HELPER(GenresTitles);
}
auto Funapi::ListTitlesFromGenre2Async(std::stop_token stk, GenreID id, int limit, std::uint32_t offset) -> std::optional<GenresTitles2> {
static constexpr auto cmd = "funimation/genres/";
const auto data = std::to_string(id) + "/?limit=" + std::to_string(limit) + "&offset=" + std::to_string(offset);
GET_HELPER(GenresTitles2);
}
auto Funapi::SearchAsync(std::stop_token stk, const std::string& q, int limit, uint32_t offset) -> std::optional<SearchResult> {
static constexpr auto cmd = "source/funimation/search/auto/";
const auto data = "?unique=true&limit=" + std::to_string(limit) + "&q=" + q + "&offset=" + std::to_string(offset);
if (auto result = this->Get(stk, cmd, data)) {
DebugDump("funi_search.json", *result);
return json::parse(*result).get<SearchResult>();
}
return {};
}
auto Funapi::GetTitleInfoAsync(std::stop_token stk, TitleID id) -> std::optional<TitleInfo> {
static constexpr auto cmd = "source/catalog/title/";
const auto data = std::to_string(id);
GET_HELPER(TitleInfo);
}
auto Funapi::ListEpisodesAsync(std::stop_token stk, TitleID id, std::optional<SeasonID> season, int limit) -> std::optional<EpisodeResult> {
static constexpr auto cmd = "funimation/episodes/";
const auto data = [&](){
if (season.has_value()) {
return "?season=" + std::to_string(*season) + "&limit=" + std::to_string(limit) + "&sort=order&sort_direction=ASC&title_id=" + std::to_string(id);
}
return "?limit=" + std::to_string(limit) + "&sort=order&sort_direction=ASC&title_id=" + std::to_string(id);
}();
GET_HELPER(EpisodeResult);
}
auto Funapi::GetEpisodeAsync(std::stop_token stk, EpisodeID id) -> std::optional<EpisodeMedia> {
static constexpr auto cmd = "source/catalog/episode/";
const auto data = std::to_string(id);
GET_HELPER(EpisodeMedia);
}
auto Funapi::GetShowExperienceAsync(std::stop_token stk, ExperienceID id) -> std::optional<ShowExperience> {
static constexpr auto cmd = "source/catalog/video/";
const auto data = std::to_string(id) + "/signed/";
GET_HELPER(ShowExperience);
}
auto Funapi::Get(std::stop_token stk, const std::string& cmd, const std::string& data) -> std::optional<std::string> {
const auto url = API_URL + cmd + data;
LOG("[FUNI-INFO] Get-URL: %s\n", url.c_str());
return this->curl.Download(
curl::Url{url},
curl::Header{
{"devicetype", "Android"},
{"Authorization", "Token cd2c113ad3a3fc7ac31b9d8a0fb7d140862c2ef4"}
},
curl::StopCallback{[stk](){
return stk.stop_requested();
}}
);
}
auto Funapi::Post(std::stop_token stk, const std::string& cmd, const std::string& data) -> std::optional<std::string> {
const auto url = API_URL + cmd;
// LOG("[FUNI-INFO] Post-URL: %s\n", url.c_str());
return this->curl.Download(
curl::Url{url},
curl::Fields{data},
curl::Header{
{"devicetype", "Android"},
{"Authorization", "Token cd2c113ad3a3fc7ac31b9d8a0fb7d140862c2ef4"}
},
curl::StopCallback{[stk](){
return stk.stop_requested();
}}
);
}
} // namespace funi
// very simple funimation api wrapper.
// funi api returns some of the most bloated json's i have ever seen.
// any request about *anything* returns *all* data linked to it.
// for example, look at a single entry in a history json, its 20kb.
// because of this, i've made much smaller (and sainer) structs that only
// contain useful data.
#pragma once
#include "account.hpp"
#include "episode.hpp"
#include "search.hpp"
#include "media.hpp"
#include "showexperience.hpp"
#include "titleinfo.hpp"
#include "language.hpp"
#include "genre.hpp"
#include "types.hpp"
#include "../curlwrapper/curl_wrapper.hpp"
#include <stop_token>
namespace funi {
class Funapi {
public:
enum class UrlType { HLS, MP4, ANY };
public:
Funapi();
~Funapi();
auto LoginAsync(std::stop_token token, const std::string& user, const std::string& pass) -> bool;
auto Login(const std::string& user, const std::string& pass) -> bool {
return this->LoginAsync(std::stop_token{}, user, pass);
}
auto LogoutAsync(std::stop_token token) -> bool;
auto Logout() -> bool {
return this->LogoutAsync(std::stop_token{});
}
// need auth!
// auto GetLibrary() -> std::optional<EpisodeResult>;
auto GetHistoryAsync(std::stop_token token, int limit = 30) -> std::optional<EpisodeResult>;
auto GetHistory(int limit = 30) -> std::optional<EpisodeResult> {
return this->GetHistoryAsync(std::stop_token{}, limit);
}
auto GetQueueAsync(std::stop_token token, int limit = 30) -> std::optional<EpisodeResult>;
auto GetQueue(int limit = 30) -> std::optional<EpisodeResult> {
return this->GetQueueAsync(std::stop_token{}, limit);
}
auto ListGenresAsync(std::stop_token token, int limit = -1, std::uint32_t offset = 0) -> std::optional<Genres>;
auto ListGenres(int limit = -1, std::uint32_t offset = 0) -> std::optional<Genres> {
return this->ListGenresAsync(std::stop_token{}, limit);
}
// seems to list a lot more titles, though does list those that aren't
// avaliable in matching regions...
auto ListTitlesFromGenreAsync(std::stop_token token, GenreID id, int limit = -1, std::uint32_t offset = 0) -> std::optional<GenresTitles>;
auto ListTitlesFromGenre(GenreID id, int limit = -1, std::uint32_t offset = 0) -> std::optional<GenresTitles> {
return this->ListTitlesFromGenreAsync(std::stop_token{}, id, limit, offset);
}
// returns less items, but seems to be better sorted by good anime, such
// that AOT and black clover are near the top.
// the json data is extremely bloated so will take longer to download,
// though this function should be preferred.
auto ListTitlesFromGenre2Async(std::stop_token token, GenreID id, int limit = -1, std::uint32_t offset = 0) -> std::optional<GenresTitles2>;
auto ListTitlesFromGenre2(GenreID id, int limit = -1, std::uint32_t offset = 0) -> std::optional<GenresTitles2> {
return this->ListTitlesFromGenre2Async(std::stop_token{}, id, limit, offset);
}
auto SearchAsync(std::stop_token token, const std::string& name, int limit = 25, std::uint32_t offset = 0) -> std::optional<SearchResult>;
auto Search(const std::string& name, int limit = 25, std::uint32_t offset = 0) -> std::optional<SearchResult> {
return this->SearchAsync(std::stop_token{}, name, limit, offset);
}
auto GetTitleInfoAsync(std::stop_token token, TitleID id) -> std::optional<TitleInfo>;
auto GetTitleInfo(TitleID id) -> std::optional<TitleInfo> {
return this->GetTitleInfoAsync(std::stop_token{}, id);
}
auto ListEpisodesAsync(std::stop_token token, TitleID id, std::optional<SeasonID> season = {}, int limit = -1) -> std::optional<EpisodeResult>;
auto ListEpisodes(TitleID id, std::optional<SeasonID> season = {}, int limit = -1) -> std::optional<EpisodeResult> {
return this->ListEpisodesAsync(std::stop_token{}, id, season, limit);
}
auto GetEpisodeAsync(std::stop_token token, EpisodeID id) -> std::optional<EpisodeMedia>;
auto GetEpisode(EpisodeID id) -> std::optional<EpisodeMedia> {
return this->GetEpisodeAsync(std::stop_token{}, id);
}
auto GetShowExperienceAsync(std::stop_token token, ExperienceID id) -> std::optional<ShowExperience>;
auto GetShowExperience(ExperienceID id) -> std::optional<ShowExperience> {
return this->GetShowExperienceAsync(std::stop_token{}, id);
}
// static auto GetMediaUri()
private:
auto Get(std::stop_token token, const std::string& cmd, const std::string& data) -> std::optional<std::string>;
auto Post(std::stop_token token, const std::string& cmd, const std::string& data) -> std::optional<std::string>;
private:
std::optional<Account> account;
curl::CurlWrapper curl;
};
} // namespace funi
#include "account.hpp"
#include "episode.hpp"
#include "media.hpp"
#include "search.hpp"
#include "showexperience.hpp"
#include "titleinfo.hpp"
#include "language.hpp"
#include "genre.hpp"
#include "../nlohmann/helper.hpp"
#define JSON_SET(name) j.at(#name).get_to(g.name)
#define JSON_SET_OP(name) if (j.contains(#name)) { j.at(#name).get_to(g.name); }
namespace funi {
void from_json(const json& j, Account::User& g) {
JSON_SET(id);
JSON_SET(first_name);
JSON_SET(last_name);
JSON_SET(email);
JSON_SET(last_login_local);
JSON_SET(displayName);
JSON_SET(avatar);
JSON_SET(defaultLanguage);
JSON_SET(last_login);
JSON_SET(date_joined);
}
void from_json(const json& j, Account& g) {
JSON_SET(token);
JSON_SET(rlildup_cookie);
JSON_SET(user);
JSON_SET(user_region);
}
void from_json(const json& j, EpisodeEntry::Item& g) {
JSON_SET(seasonId);
JSON_SET(episodeNum);
JSON_SET(episodeId);
JSON_SET(titleId);
JSON_SET(episodeName);
}
void from_json(const json& j, EpisodeEntry& g) {
JSON_SET(starRating);
JSON_SET(synopsis);
JSON_SET(mediaCategory);
JSON_SET(contentType);
JSON_SET(genres);
JSON_SET(item);
}
void from_json(const json& j, EpisodeResult& g) {
JSON_SET(count);
JSON_SET(limit);
JSON_SET(offset);
JSON_SET(items);
JSON_SET(total);
}
void from_json(const json& j, MediaInfo::Video& g) {
JSON_SET(codecId);
JSON_SET(container);
JSON_SET(frameRate);
JSON_SET(height);
JSON_SET(width);
}
void from_json(const json& j, MediaInfo& g) {
JSON_SET(format);
JSON_SET(frameHeight);
JSON_SET(frameWidth);
}
void from_json(const json& j, Language& g) {
JSON_SET(code);
JSON_SET(id);
JSON_SET(title);
}
void from_json(const json& j, MediaEntryChild& g) {
JSON_SET(id);
// JSON_SET(experienceType);
JSON_SET(ext);
JSON_SET(mediaType);
JSON_SET(status);
JSON_SET(languages);
// bug in nlohman json.
// there's a filepath field that sometimes exists.
// i think nlohman checks the first child struct for members
// and doesn't check the rest as it cannot find filepath at all!
JSON_SET(image);
static constexpr std::string_view sub_enc_filter = "/exp";
if (auto itr = g.image.find(sub_enc_filter); itr != g.image.npos) {
g.image.erase(itr, sub_enc_filter.size());
}
JSON_SET(seriesTitle);
JSON_SET(episodeTitle);
}
void from_json(const json& j, MediaEntry& g) {
JSON_SET(id);
JSON_SET(title);
JSON_SET(experienceType);
JSON_SET(ext);
JSON_SET(mediaType);
JSON_SET(status);
JSON_SET(languages);
JSON_SET(mediaChildren);
}
void from_json(const json& j, EpisodeMedia::Item& g) {
JSON_SET(id);
JSON_SET(title);
JSON_SET(number);
JSON_SET(description);
JSON_SET(media);
}
void from_json(const json& j, EpisodeMedia& g) {
JSON_SET(items);
}
void from_json(const json& j, Error& g) {
JSON_SET(code);
JSON_SET(detail);
JSON_SET(title);
}
void from_json(const json& j, ShowExperience::Item& g) {
JSON_SET(src);
JSON_SET(kind);
JSON_SET(isPromo);
JSON_SET(videoType);
JSON_SET(experienceId);
JSON_SET(showAds);
JSON_SET(id);
}
void from_json(const json& j, ShowExperience& g) {
JSON_SET_OP(items);
JSON_SET_OP(watchHistorySaveInterval);
}
void from_json(const json& j, SearchEntry& g) {
JSON_SET(languages);
JSON_SET(star_rating);
JSON_SET(title);
JSON_SET(synopsis);
// the ID for search is actually a string.
// because of the rest of the api using ints, i convert the
// id to a int.
// char id_buffer[11]{};
std::string id_buffer{12};
j.at("id").get_to(id_buffer);
g.id = std::stoul(id_buffer);
// g.id = std::strtoul(id_buffer, nullptr, 10);
}
void from_json(const json& j, SearchResult::Items& g) {
JSON_SET(hits);
JSON_SET(total);
}
void from_json(const json& j, SearchResult& g) {
JSON_SET(count);
JSON_SET(items);
JSON_SET(limit);
JSON_SET(queryId);
JSON_SET(offset);
}
void from_json(const json& j, TitleInfo::Item::ChildrenCounts& g) {
// different name for extras
if (j.contains("episodes")) {
j.at("episodes").get_to(g.episodes);
} else if (j.contains("episode")) {
j.at("episode").get_to(g.episodes);
}
JSON_SET_OP(extras); // not all have extras (SAO)
}
void from_json(const json& j, TitleInfo::Item::Children& g) {
JSON_SET(title);
JSON_SET(number);
JSON_SET(childCount);
JSON_SET(id);
JSON_SET(mediaCategory);
JSON_SET(order);
}
void from_json(const json& j, TitleInfo::Item& g) {
JSON_SET(id);
JSON_SET(title);
JSON_SET(childrenCounts);
JSON_SET(children);
JSON_SET(audio);
JSON_SET(type);
}
void from_json(const json& j, TitleInfo& g) {
JSON_SET(items);
}
void from_json(const json& j, GenresTitles::Item& g) {
JSON_SET(title);
JSON_SET(id);
JSON_SET(audio);
JSON_SET(subtitles);
}
void from_json(const json& j, GenresTitles& g) {
JSON_SET(id);
JSON_SET(title);
JSON_SET(description);
JSON_SET(items);
}
void from_json(const json& j, GenresTitles2::Item& g) {
JSON_SET(title);
JSON_SET(itemId);
}
void from_json(const json& j, GenresTitles2& g) {
JSON_SET(title);
JSON_SET(items);
JSON_SET(offset);
JSON_SET(id);
JSON_SET(limit);
JSON_SET(total);
}
void from_json(const json& j, GenreEntry& g) {
JSON_SET(description);
JSON_SET(id);
JSON_SET(name);
}
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
enum class Genre {
ACTION = 34,
COMEDY = 8,
DRAMA = 5,
FAN_SERVICE = 15,
FANTASY = 6,
HORROR = 9,
LIVE_ACTION = 47,
PSYCHOLOGICAL = 37,
ROMANCE = 33,
SCIFI = 35,
SHOUJO = 44,
SHOUNEN = 38,
SLICE_OF_LIFE = 26
};
struct GenresTitles {
struct Item {
std::string title;
TitleID id; // title_id
std::vector<std::string> audio; // langs
std::vector<std::string> subtitles; // langs
friend void from_json(const json& j, Item& g);
};
GenreID id;
std::string title;
std::string description;
std::vector<Item> items;
friend void from_json(const json& j, GenresTitles& g);
};
struct GenresTitles2 {
struct Item {
std::string title;
TitleID itemId; // title_id
friend void from_json(const json& j, Item& g);
};
std::string title;
std::vector<Item> items;
std::uint32_t offset;
GenreID id;
int limit;
std::uint32_t total;
friend void from_json(const json& j, GenresTitles2& g);
};
struct GenreEntry {
// std::string image;
std::string description;
GenreID id;
std::string name;
friend void from_json(const json& j, GenreEntry& g);
};
// if connection is okay, this cannot really fail
using Genres = std::vector<GenreEntry>;
} // namespace funi
#pragma once
#include "title_image.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
// not finished, but this struct is 100% bloat.
// all we need is name, type and experience id.
struct History {
struct Show {
struct TotalEpisodes {
std::uint32_t episode;
};
struct Synopsis {
std::string short_synopsis;
std::string medium_synopsis;
std::string long_synopsis;
};
std::string tx_date;
std::vector<std::string> genres;
float star_rating;
std::optional<std::string> minimum_episode;
std::optional<std::string> pri_msg;
std::string external_item_id;
std::string image;
std::string title;
TotalEpisodes total_episodes; // sounds like an array but its an obj
std::uint32_t episode_count;
TitleImage title_images;
std::vector<std::string> regions;
};
std::string content_type;
std::string external_ver_id;
Show show;
// std::string ;
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
enum class LanguageCode {
EN = 10,
JA = 23,
};
struct Language {
std::string code;
LanguageID id;
std::string title;
friend void from_json(const json& j, Language& g);
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "language.hpp"
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
#include <optional>
namespace funi {
enum class MediaVersion {
UNCUT,
SIMULCAST,
};
// media stream structs are custom
struct MediaStreamEntry {
MediaStreamEntry(const std::string& _url, const Language& _lang)
: url{_url}, language{_lang} {}
std::string url;
Language language;
};
struct MediaExperienceEntry {
MediaExperienceEntry(std::uint32_t _id, const Language& _lang)
: id{_id}, language{_lang} {}
ExperienceID id;
Language language;
};
struct MediaStreams {
MediaStreams(std::uint32_t _id, const Language& _lang)
: experience{_id, _lang} {}
MediaExperienceEntry experience;
std::vector<MediaStreamEntry> subtitles;
auto GetExperienceID() const noexcept { return this->experience.id; }
};
// not using atm
struct MediaInfo {
struct Video {
std::optional<std::string> codecId;
std::optional<std::string> container;
std::optional<std::string> frameRate;
std::optional<std::uint32_t> height;
std::optional<std::uint32_t> width;
friend void from_json(const json& j, Video& g);
};
std::string format;
std::optional<std::uint32_t> frameHeight;
std::optional<std::uint32_t> frameWidth;
friend void from_json(const json& j, MediaInfo& g);
};
// this is kinda like a variant, the fields depend of the mediaType
// eg, subs use filepath whilst audio streams use image
struct MediaEntryChild {
VideoID id; // used for video
// always the same as parent!
// std::string experienceType; // non-encrypted
std::string ext; // m3u8, srt
std::string mediaType; // experience, video, image, subtitle, audio
std::string status;
std::vector<Language> languages;
std::string image; // filepath doesn't always exist...so i use this instead
std::string seriesTitle;
std::string episodeTitle;
};
struct MediaEntry {
VideoID id; // used for video
std::string title; // numbers_lang
std::string experienceType; // non-encrypted
std::string ext; // m3u8
std::string mediaType; // experience, video, image, subtitle, audio
std::string status;
std::vector<Language> languages;
std::vector<MediaEntryChild> mediaChildren;
friend void from_json(const json& j, MediaEntry& g);
};
// bad name...
struct EpisodeMedia {
struct Item {
EpisodeID id;
std::string title;
std::string number;
std::string description;
std::vector<MediaEntry> media;
friend void from_json(const json& j, Item& g);
};
// should always be 1?
std::vector<Item> items;
auto GetEpisodeID() const noexcept { return this->items[0].id; }
auto Empty() const noexcept { return this->items.empty(); }
auto GetMediaStreams() const noexcept {
std::vector<MediaStreams> streams;
using namespace std::string_view_literals;
for (auto& item : this->items) {
for (auto& media : item.media) {
if (media.mediaType == "experience"sv && media.experienceType == "Non-Encrypted"sv) {
if (media.languages.empty()) {
continue;
}
auto& entry = streams.emplace_back(media.id, media.languages[0]);
for (auto& child : media.mediaChildren) {
if (child.mediaType == "subtitle"sv) {
if (child.languages.empty()) {
continue;
}
entry.subtitles.emplace_back(child.image, child.languages[0]);
}
}
}
}
}
return streams;
}
friend void from_json(const json& j, EpisodeMedia& g);
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
struct SearchEntry {
std::vector<std::string> languages;
float star_rating;
std::string title;
std::string synopsis;
std::string quality;
// this is actually a string, for some reason.
// i convert this to an int when parsing as the rest of
// the api uses ID's as ints, not strings...
TitleID id; // needed for getting episodes
friend void from_json(const json& j, SearchEntry& g);
};
struct SearchResult {
struct Items {
struct Hit {
};
std::vector<SearchEntry> hits;
std::uint32_t total;
friend void from_json(const json& j, Items& g);
};
std::uint32_t count;
Items items; // not an array, its an obj
std::string limit; // why is this a string???
std::string queryId; // ^^
std::string offset; // ^^
auto Empty() const noexcept { return this->items.hits.empty(); }
friend void from_json(const json& j, SearchResult& g);
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
struct Error {
int code;
std::string detail;
std::string title;
friend void from_json(const json& j, Error& g);
};
// should be a variant of items or errors
struct ShowExperience {
struct Item {
std::string src; // the url we want
std::string kind;
bool isPromo;
std::string videoType;
std::string experienceId;
bool showAds;
ExperienceID id; // showexperience
friend void from_json(const json& j, Item& g);
};
std::vector<Item> items;
std::uint32_t watchHistorySaveInterval;
std::vector<Error> errors; // only on errors
friend void from_json(const json& j, ShowExperience& g);
};
} // namespace funi
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
struct SubscriptionFeature {
struct SubProduct {
std::uint32_t id;
bool value;
std::string title;
};
std::uint32_t id;
std::string title;
std::string description;
std::string notification;
std::string handle;
bool active;
std::vector<SubProduct> subProducts;
std::uint32_t order;
std::string createdDatetime;
std::string modifiedDatetime;
std::vector<std::string> regions;
};
struct SubscriptionFeatures {
std::uint32_t count;
std::vector<SubscriptionFeature> items;
std::uint32_t total;
std::uint32_t offset;
};
} // namespace funi
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
struct TitleImage {
std::string showThumbnail;
std::string showBackgroundSite;
std::string showDetailHeaderDesktop;
std::string continueWatchingDesktop;
std::string showDetailHeroSite;
std::string appleHorizontalBannerShow;
std::string backgroundImageXbox_360;
std::string applePosterCover;
std::string showDetailBoxArtTablet;
std::string backgroundImageAppletvfiretv;
std::string newShowDetailHero;
std::string showDetailHeroDesktop;
std::string showKeyart;
std::string continueWatchingMobile;
std::string featuredSpotlightShowPhone;
std::string appleHorizontalBannerMovie;
std::string featuredSpotlightShowTablet;
std::string showDetailBoxArtPhone;
std::string appleSquareCover;
std::string backgroundVideo;
std::string showMasterKeyArt;
std::string newShowDetailHeroPhone;
std::string showDetailBoxArtXbox_360;
std::string showDetailHeaderMobile;
std::string showLogo;
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include <optional>
namespace funi {
struct TitleInfo {
struct Item {
struct ChildrenCounts {
// this is called episode for extras!!!
std::uint32_t episodes{}; // total num of episodes
std::uint32_t extras{}; // total num of extras
friend void from_json(const json& j, ChildrenCounts& g);
};
struct Children {
std::string title;
std::string number; // season number (99 for extras)
std::uint32_t childCount; // num ep this season
SeasonID id; // season ID?
std::string mediaCategory; // season, extras
float order;
friend void from_json(const json& j, Children& g);
};
TitleID id;
std::string title;
ChildrenCounts childrenCounts;
std::vector<Children> children;
std::string audio; // English, Japanese, Portuguese (Brazil)
std::string type; // tv_series (always)
friend void from_json(const json& j, Item& g);
};
std::vector<Item> items;
auto SeasonCount() const noexcept {
std::size_t season_max{};
for (auto&item : this->items) {
for (auto&child : item.children) {
if (child.mediaCategory != "season") {
continue;
}
auto season_num = std::stoull(child.number);
if (season_num < 90 && season_num > season_max) {
season_max = season_num;
}
}
}
return season_max;
}
auto Empty() const noexcept { return this->items.empty(); }
friend void from_json(const json& j, TitleInfo& g);
};
} // namespace funi
#pragma once
#include "../nlohmann/json_fwd.hpp"
#include <cstdint>
namespace funi {
using GenreID = std::uint32_t;
using TitleID = std::uint32_t;
using VideoID = std::uint32_t;
using SeasonID = std::uint32_t;
using EpisodeID = std::uint32_t;
using ExperienceID = std::uint32_t;
using LanguageID = std::uint32_t;
} // namespace funi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment