aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/DramaCool.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-09-04 05:01:36 +0200
committerdec05eba <dec05eba@protonmail.com>2022-09-04 08:44:51 +0200
commit87c8a2986d468a3fc897169c1b00fc4695e09d39 (patch)
treebfd1d39d84680389a2bd30c9e1cdde5e844a3a5b /src/plugins/DramaCool.cpp
parent84f501f5211f09a09fc5384bf15415d0d0445a96 (diff)
Add dramacool
Diffstat (limited to 'src/plugins/DramaCool.cpp')
-rw-r--r--src/plugins/DramaCool.cpp412
1 files changed, 412 insertions, 0 deletions
diff --git a/src/plugins/DramaCool.cpp b/src/plugins/DramaCool.cpp
new file mode 100644
index 0000000..9b419f9
--- /dev/null
+++ b/src/plugins/DramaCool.cpp
@@ -0,0 +1,412 @@
+#include "../../plugins/DramaCool.hpp"
+#include "../../include/Theme.hpp"
+#include "../../include/StringUtils.hpp"
+#include "../../include/M3U8.hpp"
+#include <json/value.h>
+#include <quickmedia/HtmlSearch.h>
+
+// TODO: Add bookmarks page, history, track watch progress, automatically go to next episode, subscribe, etc.
+
+namespace QuickMedia {
+ SearchResult DramaCoolSearchPage::search(const std::string &str, BodyItems &result_items) {
+ if(str.empty())
+ return SearchResult::OK;
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "x-requested-with: XMLHttpRequest" }
+ };
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, "https://dramacool.cr/search?keyword=" + url_param_encode(str) + "&type=movies", std::move(additional_args), true);
+ if(result != DownloadResult::OK) return download_result_to_search_result(result);
+
+ if(!json_root.isArray())
+ return SearchResult::ERR;
+
+ for(const Json::Value &json_item : json_root) {
+ if(!json_item.isObject())
+ continue;
+
+ const Json::Value &name_json = json_item["name"];
+ const Json::Value &status_json = json_item["status"];
+ const Json::Value &cover_url_json = json_item["cover"];
+ const Json::Value &url_json = json_item["url"];
+
+ if(!name_json.isString() || !url_json.isString())
+ continue;
+
+ auto body_item = BodyItem::create(name_json.asString());
+ if(status_json.isString()) {
+ body_item->set_description(status_json.asString());
+ body_item->set_description_color(get_theme().faded_text_color);
+ }
+ if(cover_url_json.isString()) {
+ body_item->thumbnail_url = "https://imagecdn.me/" + url_param_encode(cover_url_json.asString());
+ body_item->thumbnail_size = { 150, 225 };
+ }
+ body_item->url = "https://dramacool.cr" + url_json.asString();
+
+ result_items.push_back(std::move(body_item));
+ }
+
+ return SearchResult::OK;
+ }
+
+ static bool node_get_inner_text(const QuickMediaHtmlChildNode *node, QuickMediaStringView &str) {
+ if(!node || !node->node.is_tag || !node->node.first_child || node->node.first_child->node.is_tag)
+ return false;
+
+ str = node->node.first_child->node.name;
+ return true;
+ }
+
+ PluginResult DramaCoolSearchPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) {
+ std::string website_data;
+ DownloadResult result = download_to_string(args.url, website_data, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ BodyItems result_items;
+
+ QuickMediaHtmlSearch html_search;
+ int res = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size());
+ if(res != 0)
+ return PluginResult::ERR;
+
+ quickmedia_html_find_nodes_xpath(&html_search, "//div[class='content']//a",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ auto *result_items = (BodyItems*)userdata;
+ QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href");
+ if(!href.data || !memmem(href.data, href.size, ".html", 5))
+ return 0;
+
+ QuickMediaHtmlChildNode *sub_node = node->node->first_child;
+ if(!sub_node)
+ return 0;
+
+ QuickMediaHtmlChildNode *title_node = sub_node->next;
+ if(!title_node)
+ return 0;
+
+ QuickMediaHtmlChildNode *time_node = title_node->next;
+ if(!time_node)
+ return 0;
+
+ QuickMediaStringView sub_str;
+ QuickMediaStringView title_str;
+ QuickMediaStringView time_str;
+ if(!node_get_inner_text(sub_node, sub_str) || !node_get_inner_text(title_node, title_str) || !node_get_inner_text(time_node, time_str))
+ return 0;
+
+ std::string title(title_str.data, title_str.size);
+ html_unescape_sequences(title);
+
+ std::string time(time_str.data, time_str.size);
+ html_unescape_sequences(time);
+
+ const bool is_subbed = sub_str.size == 3 && memcmp(sub_str.data, "SUB", 3) == 0;
+
+ auto body_item = BodyItem::create(std::move(title));
+ body_item->set_description((is_subbed ? "Subbed" : "Raw") + std::string(" • ") + time);
+ body_item->set_description_color(get_theme().faded_text_color);
+ body_item->url = "https://dramacool.cr" + std::string(href.data, href.size);
+ result_items->push_back(std::move(body_item));
+
+ return 0;
+ }, &result_items);
+
+ quickmedia_html_search_deinit(&html_search);
+
+ auto body = create_body();
+ body->set_items(std::move(result_items));
+ result_tabs.push_back(Tab{ std::move(body), std::make_unique<DramaCoolEpisodesPage>(program), create_search_bar("Search...", SEARCH_DELAY_FILTER) });
+ return PluginResult::OK;
+ }
+
+ struct VideoSources {
+ //std::string streamsss;
+ std::string streamtape;
+ std::string mixdrop;
+ std::string mp4upload;
+ };
+
+ static bool dembed_extract_video_source(const std::string &website_data, const std::string &video_source_url, std::string &video_source) {
+ size_t st_start = website_data.find(video_source_url);
+ if(st_start == std::string::npos)
+ return false;
+
+ st_start += video_source_url.size();
+ size_t st_end = website_data.find("\"", st_start);
+ if(st_end == std::string::npos)
+ return false;
+
+ video_source = "https://" + video_source_url + website_data.substr(st_start, st_end - st_start);
+ return true;
+ }
+
+ 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, "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);
+ }
+
+ // TODO: Re-add. It's broken right now (because of the json_url has incorrect value I guess)
+ /*
+ static bool streamsss_extract_video_url(Page *page, const std::string &streamsss_url, std::string &url) {
+ size_t url_end = streamsss_url.find("/e");
+ if(url_end == std::string::npos)
+ return false;
+
+ size_t id_start = streamsss_url.find("e/");
+ if(id_start == std::string::npos)
+ return false;
+
+ id_start += 2;
+ size_t id_end = streamsss_url.find("?", id_start);
+ if(id_end == std::string::npos)
+ id_end = streamsss_url.size();
+
+ const std::string url_base = streamsss_url.substr(0, url_end);
+ const std::string id = streamsss_url.substr(id_start, id_end - id_start);
+
+ std::ostringstream id_hex;
+ id_hex << std::hex;
+ for(size_t i = 0; i < id.size(); ++i)
+ id_hex << (int)(unsigned char)id[i];
+
+ const std::string json_url = url_base + "/sources43/566d337678566f743674494a7c7c" + id_hex.str() + "7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362";
+
+ Json::Value json_root;
+ DownloadResult result = page->download_json(json_root, json_url, {}, true);
+ if(result != DownloadResult::OK)
+ return false;
+
+ if(!json_root.isObject())
+ return false;
+
+ const Json::Value &stream_data_json = json_root["stream_data"];
+ if(!stream_data_json.isObject())
+ return false;
+
+ const Json::Value &file_json = stream_data_json["file"];
+ if(!file_json.isString())
+ return false;
+
+ // TODO: Record resolution and duration
+ std::string website_data;
+ result = download_to_string(file_json.asString(), website_data, {}, true);
+ if(result != DownloadResult::OK)
+ return false;
+
+ url = M3U8Stream::get_highest_resolution_stream(m3u8_get_streams(website_data)).url;
+ return true;
+ }
+ */
+ static bool streamtape_extract_video_url(const std::string &website_data, std::string &url) {
+ size_t id_start = website_data.find("getElementById('robotlink')");
+ if(id_start == std::string::npos)
+ return false;
+
+ id_start += 27;
+ id_start = website_data.find("id=", id_start);
+ if(id_start == std::string::npos)
+ return false;
+
+ id_start += 3;
+ size_t id_end = website_data.find("'", id_start);
+ if(id_end == std::string::npos)
+ return false;
+
+ url = "https://streamtape.com/get_video?id=" + website_data.substr(id_start, id_end - id_start);
+ return true;
+ }
+
+ static bool mixdrop_extract_mdcore_script(const std::string &website_data, const std::string &prefix, std::string &mdcore_script) {
+ size_t script_start = website_data.find(prefix);
+ if(script_start == std::string::npos)
+ return false;
+
+ script_start += prefix.size();
+ size_t script_end = website_data.find("\"", script_start);
+ if(script_end == std::string::npos)
+ return false;
+
+ mdcore_script = website_data.substr(script_start, script_end - script_start);
+ return true;
+ }
+
+ static bool mixdrop_extract_mdcore_parts(const std::string &website_data, std::vector<std::string> &parts) {
+ size_t mdcore_start = website_data.find(",'|");
+ if(mdcore_start == std::string::npos) {
+ mdcore_start = website_data.find("MDCore|");
+ if(mdcore_start == std::string::npos)
+ return false;
+ } else {
+ mdcore_start += 2;
+ }
+
+ size_t mdcore_end = website_data.find("'", mdcore_start);
+ if(mdcore_end == std::string::npos)
+ return false;
+
+ std::string mdcore_str = website_data.substr(mdcore_start, mdcore_end - mdcore_start);
+ string_split(mdcore_str, '|', [&parts](const char *str, size_t size) {
+ parts.emplace_back(str, size);
+ return true;
+ });
+
+ return true;
+ }
+
+ static int mdcore_number_get(char c) {
+ if(c >= '0' && c <= '9')
+ return c - '0';
+ else if(c >= 'a' && c <= 'z')
+ return 10 + (c - 'a');
+ else
+ return -1;
+ }
+
+ static bool mixdrop_extract_video_url(const std::string &website_data, const std::string &prefix, std::string &url) {
+ std::string mdcore_script;
+ if(!mixdrop_extract_mdcore_script(website_data, prefix, mdcore_script))
+ return false;
+
+ std::vector<std::string> mdcore_parts;
+ if(!mixdrop_extract_mdcore_parts(website_data, mdcore_parts))
+ return false;
+
+ for(size_t i = 0; i < mdcore_script.size();) {
+ char c = mdcore_script[i];
+ int index = mdcore_number_get(c);
+
+ if(index >= 0) {
+ ++i;
+ while(i < mdcore_script.size() && ((mdcore_script[i] >= '0' && mdcore_script[i] <= '9') || (mdcore_script[i] >= 'a' && mdcore_script[i] <= 'z'))) {
+ // 36 = 0-9 + a-z
+ index = (index * 36) + mdcore_number_get(mdcore_script[i]);
+ ++i;
+ }
+ } else {
+ url += c;
+ ++i;
+ continue;
+ }
+
+ if(index >= (int)mdcore_parts.size() || mdcore_parts[index].empty())
+ url += c;
+ else
+ url += mdcore_parts[index];
+ }
+
+ if(string_starts_with(url, "//"))
+ url = "http:" + url;
+
+ return true;
+ }
+
+ PluginResult DramaCoolEpisodesPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) {
+ std::string website_data;
+ DownloadResult result = download_to_string(args.url, website_data, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ std::string video_sources_url;
+
+ QuickMediaHtmlSearch html_search;
+ int res = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size());
+ if(res != 0)
+ return PluginResult::ERR;
+
+ quickmedia_html_find_nodes_xpath(&html_search, "//div",
+ [](QuickMediaMatchNode *node, void *userdata) {
+ auto *video_sources_url = (std::string*)userdata;
+ QuickMediaStringView klass = quickmedia_html_node_get_attribute_value(node->node, "class");
+ if(!klass.data || !memmem(klass.data, klass.size, "watch_video", 11))
+ return 0;
+
+ QuickMediaHtmlChildNode *iframe_node = node->node->first_child;
+ if(!iframe_node || !iframe_node->node.is_tag || iframe_node->node.name.size != 6 || memcmp(iframe_node->node.name.data, "iframe", 6) != 0)
+ return 0;
+
+ QuickMediaStringView src = quickmedia_html_node_get_attribute_value(&iframe_node->node, "src");
+ if(!src.data)
+ return 0;
+
+ video_sources_url->assign(src.data, src.size);
+ return 1;
+ }, &video_sources_url);
+
+ quickmedia_html_search_deinit(&html_search);
+
+ if(video_sources_url.empty())
+ return PluginResult::ERR;
+
+ if(string_starts_with(video_sources_url, "//"))
+ video_sources_url = "https:" + video_sources_url;
+
+ result = download_to_string(video_sources_url, website_data, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ // TODO: Extract all video sources and allow the user to select which one to use.
+ // Streamtape or mixdrop may not be available or if it's available it may not load properly
+ // or the quality may be worse or slower than other sources.
+ // We also want to load the high resolution version of the video.
+ // TODO: Make videos sources work even when captions are not embedded.
+ VideoSources video_sources;
+ dembed_extract_video_sources(website_data, video_sources);
+
+ std::string video_url;
+ std::string referer;
+
+ if(!video_sources.streamtape.empty() && video_url.empty()) {
+ result = download_to_string(video_sources.streamtape, website_data, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+ streamtape_extract_video_url(website_data, video_url);
+
+ if(!video_url.empty())
+ referer = "https://streamtape.com";
+ }
+
+ if(!video_sources.mixdrop.empty() && video_url.empty()) {
+ result = download_to_string(video_sources.mixdrop, website_data, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+ mixdrop_extract_video_url(website_data, "0.f=\"", video_url);
+
+ if(!video_url.empty())
+ referer = "https://mixdrop.co";
+ }
+
+ if(!video_sources.mp4upload.empty() && video_url.empty()) {
+ result = download_to_string(video_sources.mp4upload, website_data, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+ // mp4upload uses the same algorithm as mixdrop but with different format
+ mixdrop_extract_video_url(website_data, "2.25(\"", video_url);
+
+ if(!video_url.empty())
+ referer = "https://www.mp4upload.com";
+ }
+
+ if(video_url.empty())
+ return PluginResult::ERR;
+
+ /*
+ if(!video_sources.streamsss.empty() && video_url.empty()) {
+ streamsss_extract_video_url(this, video_sources.streamsss, video_url);
+ }
+ */
+
+ result_tabs.push_back(Tab{ nullptr, std::make_unique<DramaCoolVideoPage>(program, std::move(video_url), args.title, std::move(referer)), nullptr });
+
+ return PluginResult::OK;
+ }
+
+ PluginResult DramaCoolVideoPage::load(const SubmitArgs&, VideoInfo &video_info, std::string &err_str) {
+ video_info.title = title;
+ video_info.channel_url.clear();
+ video_info.duration = 0.0;
+ video_info.chapters.clear();
+ video_info.referer = referer;
+ err_str.clear();
+ return PluginResult::OK;
+ }
+} \ No newline at end of file