From 7d3a13e15eeb4a1824256c8bb24ba2e35514e354 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 11 Aug 2024 15:37:13 +0200 Subject: dramacool: support asianload backend (fixes many videos), retry getting video duration (fixes video progress sometimes failing to save) --- src/plugins/DramaCool.cpp | 190 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) (limited to 'src/plugins/DramaCool.cpp') diff --git a/src/plugins/DramaCool.cpp b/src/plugins/DramaCool.cpp index de5357a..f513df3 100644 --- a/src/plugins/DramaCool.cpp +++ b/src/plugins/DramaCool.cpp @@ -3,9 +3,25 @@ #include "../../include/StringUtils.hpp" #include "../../include/M3U8.hpp" #include "../../plugins/utils/WatchProgress.hpp" +#include "../../external/cppcodec/base64_rfc4648.hpp" + +#define AES256 1 +#define CBC 1 +#define ECB 0 +#define CTR 0 + +extern "C" { +#include "utils/aes.h" +} + #include +#include #include +// Keys are from: https://github.com/henry-richard7/shows-flix/blob/main/lib/Scraper/vidstream_scraper.dart +#define ASIANLOAD_KEY "93422192433952489752342908585752" +#define ASIANLOAD_IV "9262859232435825" + // TODO: Add bookmarks page, history, track watch progress, automatically go to next episode, subscribe, etc. namespace QuickMedia { @@ -125,6 +141,7 @@ namespace QuickMedia { struct VideoSources { //std::string streamsss; + std::string asianload; std::string streamtape; std::string mixdrop; std::string mp4upload; @@ -147,6 +164,7 @@ namespace QuickMedia { static void dembed_extract_video_sources(const std::string &website_data, VideoSources &video_sources) { //dembed_extract_video_source(website_data, "streamsss.net", video_sources.streamsss); + dembed_extract_video_source(website_data, "asianbxkiun.pro/embedplus?id=", video_sources.asianload); dembed_extract_video_source(website_data, "streamtape.com", video_sources.streamtape); dembed_extract_video_source(website_data, "mixdrop.co", video_sources.mixdrop); dembed_extract_video_source(website_data, "www.mp4upload.com", video_sources.mp4upload); @@ -201,6 +219,7 @@ namespace QuickMedia { if(result != DownloadResult::OK) return false; + // TODO: get the resolution that is lower or equal to the height we want url = M3U8Stream::get_highest_resolution_stream(m3u8_get_streams(website_data)).url; return true; } @@ -400,6 +419,173 @@ namespace QuickMedia { return true; } + static std::string url_extract_param(const std::string &url, const std::string ¶m_key) { + std::string value; + const std::string param = param_key + "="; + size_t start_index = url.find(param); + if(start_index == std::string::npos) + return value; + + start_index += param.size(); + size_t end_index = url.find('&', start_index); + if(end_index == std::string::npos) { + end_index = url.find('"', start_index); + if(end_index == std::string::npos) + return value; + } + + value = url.substr(start_index, end_index - start_index); + return value; + } + + static size_t align_up(size_t value, size_t alignment) { + size_t v = value / alignment; + if(value % alignment != 0) + v++; + if(v == 0) + v = 1; + return v * alignment; + } + + // |key| should be a multiple of AES_KEYLEN (32) and |iv| should be a multiple of AES_BLOCKLEN (16) + static std::string aes_cbc_encrypt_base64(const std::string &str, const uint8_t *key, const uint8_t *iv) { + std::string result; + + const size_t input_size = align_up(str.size(), AES_BLOCKLEN); + uint8_t *input = (uint8_t*)malloc(input_size); + if(!input) + return result; + + memcpy(input, str.data(), str.size()); + + // PKCS#7 padding + const int num_padded_bytes = input_size - str.size(); + memset(input + str.size(), num_padded_bytes, num_padded_bytes); + + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, key, iv); + AES_CBC_encrypt_buffer(&ctx, input, input_size); + + std::string input_data_str((const char*)input, input_size); + result = cppcodec::base64_rfc4648::encode(input_data_str); + free(input); + + return result; + } + + static std::string aes_cbc_decrypt(const std::string &str, const uint8_t *key, const uint8_t *iv) { + std::string result; + + const size_t input_size = align_up(str.size(), AES_BLOCKLEN); + uint8_t *input = (uint8_t*)malloc(input_size); + if(!input) + return result; + + memcpy(input, str.data(), str.size()); + + // PKCS#7 padding + const int num_padded_bytes = input_size - str.size(); + memset(input + str.size(), num_padded_bytes, num_padded_bytes); + + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, key, iv); + AES_CBC_decrypt_buffer(&ctx, input, input_size); + + result.assign((const char*)input, str.size()); + free(input); + + return result; + } + + static std::string asianload_decrypt_response_get_hls_url(const Json::Value &json_result) { + std::string url; + if(!json_result.isObject()) + return url; + + const Json::Value &data_json = json_result["data"]; + if(!data_json.isString()) + return url; + + std::string data_raw = cppcodec::base64_rfc4648::decode(data_json.asString()); + const std::string input = aes_cbc_decrypt(data_raw, (const uint8_t*)ASIANLOAD_KEY, (const uint8_t*)ASIANLOAD_IV); + + Json::CharReaderBuilder json_builder; + std::unique_ptr json_reader(json_builder.newCharReader()); + std::string json_errors; + Json::Value result; + if(!json_reader->parse(input.data(), input.data() + data_raw.size(), &result, &json_errors)) { + fprintf(stderr, "asianload_decrypt_response_get_hls_url error: %s\n", json_errors.c_str()); + return url; + } + + if(!result.isObject()) + return url; + + const Json::Value &source_json = result["source"]; + if(!source_json.isArray()) + return url; + + // The json data also contains backup (source_bk) and tracks (vtt), but we ignore those for now + for(const Json::Value &item_json : source_json) { + if(!item_json.isObject()) + continue; + + const Json::Value &file_json = item_json["file"]; + const Json::Value &type_json = item_json["type"]; + if(!file_json.isString() || !type_json.isString()) + continue; + + if(strcmp(type_json.asCString(), "hls") != 0) + continue; + + url = file_json.asString(); + break; + } + + return url; + } + + static std::string hls_url_remove_filename(const std::string &hls_url) { + std::string result; + size_t index = hls_url.rfind('/'); + if(index == std::string::npos) + return result; + result = hls_url.substr(0, index); + return result; + } + + static std::string asianload_get_best_quality_stream(const std::string &hls_url) { + std::string url; + std::string website_data; + DownloadResult result = download_to_string(hls_url, website_data, {}, true); + if(result != DownloadResult::OK) + return url; + + // TODO: get the resolution that is lower or equal to the height we want + url = M3U8Stream::get_highest_resolution_stream(m3u8_get_streams(website_data)).url; + return hls_url_remove_filename(hls_url) + "/" + url; + } + + static void asianload_get_video_url(Page *page, const std::string &asianload_url, std::string &video_url) { + const std::string id = url_extract_param(asianload_url, "id"); + const std::string token = url_extract_param(asianload_url, "token"); + const std::string bla = aes_cbc_encrypt_base64(id, (const uint8_t*)ASIANLOAD_KEY, (const uint8_t*)ASIANLOAD_IV); + + const int64_t expires = (int64_t)time(NULL) + (60LL * 60LL); // current time + 1 hour, in seconds + const std::string url = "https://asianbxkiun.pro/encrypt-ajax.php?id=" + bla + "&token=" + token + "&expires=" + std::to_string(expires) + "&mip=0.0.0.0&refer=https://asianc.sh/&op=2&alias=" + id; + + Json::Value json_result; + DownloadResult result = page->download_json(json_result, url, {{ "-H", "x-requested-with: XMLHttpRequest" }}, true); + if(result != DownloadResult::OK) + return; + + const std::string hls_url = asianload_decrypt_response_get_hls_url(json_result); + if(hls_url.empty()) + return; + + video_url = asianload_get_best_quality_stream(hls_url); + } + PluginResult DramaCoolEpisodesPage::submit(const SubmitArgs &args, std::vector &result_tabs) { std::string website_data; DownloadResult result = download_to_string(args.url, website_data, {}, true); @@ -453,6 +639,10 @@ namespace QuickMedia { std::string video_url; std::string referer; + if(!video_sources.asianload.empty() && video_url.empty()) { + asianload_get_video_url(this, video_sources.asianload, video_url); + } + if(!video_sources.streamtape.empty() && video_url.empty()) { result = download_to_string(video_sources.streamtape, website_data, {}, true); if(result == DownloadResult::OK) { -- cgit v1.2.3