aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-03-26 16:45:41 +0100
committerdec05eba <dec05eba@protonmail.com>2021-03-26 16:45:41 +0100
commitda827778f8c5d2f0cfc56b297099ba58454c38ed (patch)
tree9e17efe65eca94a23374aa8ea00da0da50d45bfe
parentd96e65b2abf2a569a4be4c160fa30a504abdb2fc (diff)
Add soundcloud
-rw-r--r--README.md4
-rw-r--r--TODO3
-rw-r--r--icons/soundcloud_launcher.pngbin0 -> 4101 bytes
-rw-r--r--images/soundcloud_logo.pngbin0 -> 5951 bytes
-rw-r--r--include/Body.hpp7
-rw-r--r--include/QuickMedia.hpp2
-rw-r--r--launcher/QuickMedia-soundcloud.desktop9
-rw-r--r--plugins/ImageBoard.hpp2
-rw-r--r--plugins/Matrix.hpp2
-rw-r--r--plugins/Page.hpp2
-rw-r--r--plugins/Pornhub.hpp5
-rw-r--r--plugins/Soundcloud.hpp56
-rw-r--r--plugins/Spotify.hpp5
-rw-r--r--plugins/Youtube.hpp4
-rw-r--r--src/Body.cpp1
-rw-r--r--src/QuickMedia.cpp42
-rw-r--r--src/plugins/Pornhub.cpp8
-rw-r--r--src/plugins/Soundcloud.cpp287
-rw-r--r--src/plugins/Spotify.cpp4
-rw-r--r--src/plugins/Youtube.cpp10
20 files changed, 422 insertions, 31 deletions
diff --git a/README.md b/README.md
index 5c2436a..771d1d4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# QuickMedia
A dmenu-inspired native client for web services.
-Currently supported web services: `youtube`, `spotify (podcasts)`, `nyaa.si`, `manganelo`, `mangatown`, `mangadex`, `4chan`, `matrix` and _others_.\
+Currently supported web services: `youtube`, `spotify (podcasts)`, `soundcloud`, `nyaa.si`, `manganelo`, `mangatown`, `mangadex`, `4chan`, `matrix` and _others_.\
**Note:** Manganelo doesn't work when used with TOR.\
**Note:** Posting comments on 4chan doesn't work when used with TOR. However browsing works.\
**Note:** TOR system service needs to be running (`systemctl start tor.service`) when using `--tor` option.\
@@ -11,7 +11,7 @@ Cache is stored under `$HOME/.cache/quickmedia`.
```
usage: QuickMedia <plugin> [--tor] [--use-system-mpv-config] [--dir <directory>]
OPTIONS:
- plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, youtube, spotify, nyaa.si, matrix, file-manager or pipe
+ plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or pipe
--no-video Only play audio when playing a video. Disabled by default
--tor Use tor. Disabled by default
--use-system-mpv-config Use system mpv config instead of no config. Disabled by default
diff --git a/TODO b/TODO
index 827a84e..fe5b750 100644
--- a/TODO
+++ b/TODO
@@ -158,4 +158,5 @@ Sort reactions by timestamp.
Check what happens with xsrf_token if comments are not fetched for a long time. Does it time out? if so do we need to refetch the video page to get the new token?.
Add support for comments in live youtube videos, api is at: https://www.youtube.com/youtubei/v1/live_chat/get_live_chat?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8.
Make video visible when reading comments (youtube).
-Convert nyaa.si date from unix date to local time. \ No newline at end of file
+Convert nyaa.si date from unix date to local time.
+When ui is scaled then the predicated thumbnail size will be wrong since its scaled in Body but not in the plugins where they are requested. \ No newline at end of file
diff --git a/icons/soundcloud_launcher.png b/icons/soundcloud_launcher.png
new file mode 100644
index 0000000..baa1840
--- /dev/null
+++ b/icons/soundcloud_launcher.png
Binary files differ
diff --git a/images/soundcloud_logo.png b/images/soundcloud_logo.png
new file mode 100644
index 0000000..a314bb3
--- /dev/null
+++ b/images/soundcloud_logo.png
Binary files differ
diff --git a/include/Body.hpp b/include/Body.hpp
index 5e68b5b..cc11664 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -33,6 +33,12 @@ namespace QuickMedia {
CIRCLE
};
+ // TODO: Remove and create an Userdata class instead to replace the void* userdata in BodyItem
+ class BodyItemExtra {
+ public:
+ virtual ~BodyItemExtra() = default;
+ };
+
struct Reaction {
std::unique_ptr<Text> text;
void *userdata = nullptr;
@@ -146,6 +152,7 @@ namespace QuickMedia {
ThumbnailMaskType thumbnail_mask_type = ThumbnailMaskType::NONE;
sf::Vector2i thumbnail_size;
std::vector<Reaction> reactions; // TODO: Move to a different body item type
+ std::shared_ptr<BodyItemExtra> extra; // TODO: Remove
private:
// TODO: Clean up these strings when set in text, and get_title for example should return |title_text.getString()|
// TODO: Use sf::String instead, removes the need to convert to utf32 every time the text is dirty (for example when resizing window)
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 21b451d..51a97b4 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -101,7 +101,7 @@ namespace QuickMedia {
void page_loop_render(sf::RenderWindow &window, std::vector<Tab> &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters);
using PageLoopSubmitHandler = std::function<void(const std::vector<Tab> &new_tabs)>;
void page_loop(std::vector<Tab> &tabs, int start_tab_index = 0, PageLoopSubmitHandler after_submit_handler = nullptr);
- void video_content_page(VideoPage *video_page, std::string video_url, std::string video_title, bool download_if_streaming_fails);
+ void video_content_page(VideoPage *video_page, std::string video_title, bool download_if_streaming_fails);
// Returns -1 to go to previous chapter, 0 to stay on same chapter and 1 to go to next chapter
int image_page(MangaImagesPage *images_page, Body *chapters_body);
void image_continuous_page(MangaImagesPage *images_page);
diff --git a/launcher/QuickMedia-soundcloud.desktop b/launcher/QuickMedia-soundcloud.desktop
new file mode 100644
index 0000000..2b25fec
--- /dev/null
+++ b/launcher/QuickMedia-soundcloud.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Name=QuickMedia Soundcloud
+GenericName=Soundcloud player
+Comment=Soundcloud music player
+Icon=/usr/share/quickmedia/icons/soundcloud_launcher.png
+Exec=QuickMedia soundcloud --no-video
+Terminal=false
+Keywords=soundcloud;player;quickmedia;music;
diff --git a/plugins/ImageBoard.hpp b/plugins/ImageBoard.hpp
index c65b269..bd47bec 100644
--- a/plugins/ImageBoard.hpp
+++ b/plugins/ImageBoard.hpp
@@ -21,6 +21,7 @@ namespace QuickMedia {
std::unique_ptr<LazyFetchPage> create_channels_page(Program*, const std::string&) override {
return nullptr;
}
+ std::string get_url() override { return video_url; }
virtual PluginResult login(const std::string &token, const std::string &pin, std::string &response_msg);
virtual PostResult post_comment(const std::string &captcha_id, const std::string &comment) = 0;
virtual const std::string& get_pass_id();
@@ -28,5 +29,6 @@ namespace QuickMedia {
const std::string board_id;
const std::string thread_id;
const std::vector<std::string> cached_media_urls;
+ std::string video_url;
};
} \ No newline at end of file
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 7028e4d..26ad926 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -420,6 +420,8 @@ namespace QuickMedia {
std::unique_ptr<LazyFetchPage> create_channels_page(Program*, const std::string&) override {
return nullptr;
}
+ std::string get_url() override { return url; }
+ std::string url;
};
class MatrixChatPage : public Page {
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 44526db..f1ef893 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -63,6 +63,7 @@ namespace QuickMedia {
virtual sf::Vector2i get_thumbnail_max_size() { return sf::Vector2i(480, 360); };
Program *program;
+ std::shared_ptr<BodyItem> submit_body_item; // TODO: Remove this
};
enum class TrackResult {
@@ -106,5 +107,6 @@ namespace QuickMedia {
virtual std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) = 0;
// Return nullptr if the service doesn't support channels page
virtual std::unique_ptr<LazyFetchPage> create_channels_page(Program *program, const std::string &channel_url) = 0;
+ virtual std::string get_url() = 0;
};
} \ No newline at end of file
diff --git a/plugins/Pornhub.hpp b/plugins/Pornhub.hpp
index 5c3f835..87e33da 100644
--- a/plugins/Pornhub.hpp
+++ b/plugins/Pornhub.hpp
@@ -21,7 +21,7 @@ namespace QuickMedia {
class PornhubVideoPage : public VideoPage {
public:
- PornhubVideoPage(Program *program) : VideoPage(program) {}
+ PornhubVideoPage(Program *program, const std::string &url) : VideoPage(program), url(url) {}
const char* get_title() const override { return ""; }
BodyItems get_related_media(const std::string &url, std::string &channel_url) override;
std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override;
@@ -29,5 +29,8 @@ namespace QuickMedia {
std::unique_ptr<LazyFetchPage> create_channels_page(Program*, const std::string&) override {
return nullptr;
}
+ std::string get_url() override { return url; }
+ private:
+ std::string url;
};
} \ No newline at end of file
diff --git a/plugins/Soundcloud.hpp b/plugins/Soundcloud.hpp
new file mode 100644
index 0000000..4962c04
--- /dev/null
+++ b/plugins/Soundcloud.hpp
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "Page.hpp"
+
+namespace QuickMedia {
+ class SoundcloudPage : public Page {
+ public:
+ SoundcloudPage(Program *program) : Page(program) {}
+ virtual ~SoundcloudPage() = default;
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
+ };
+
+ class SoundcloudSearchPage : public SoundcloudPage {
+ public:
+ SoundcloudSearchPage(Program *program) : SoundcloudPage(program) {}
+ const char* get_title() const override { return "Search"; }
+ bool search_is_filter() override { return false; }
+ SearchResult search(const std::string &str, BodyItems &result_items) override;
+ PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
+ private:
+ std::string query_urn;
+ };
+
+ class SoundcloudUserPage : public SoundcloudPage {
+ public:
+ SoundcloudUserPage(Program *program, const std::string &username, const std::string &userpage_url, std::string next_href) : SoundcloudPage(program), username(username), userpage_url(userpage_url), next_href(std::move(next_href)), current_page(0) {}
+ const char* get_title() const override { return username.c_str(); }
+ PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
+ private:
+ PluginResult get_continuation_page(BodyItems &result_items);
+ private:
+ std::string username;
+ std::string userpage_url;
+ std::string next_href;
+ int current_page;
+ };
+
+ class SoundcloudPlaylistPage : public SoundcloudPage {
+ public:
+ SoundcloudPlaylistPage(Program *program, const std::string &playlist_name) : SoundcloudPage(program), playlist_name(playlist_name) {}
+ const char* get_title() const override { return playlist_name.c_str(); }
+ private:
+ std::string playlist_name;
+ };
+
+ class SoundcloudAudioPage : public VideoPage {
+ public:
+ SoundcloudAudioPage(Program *program, const std::string &url) : VideoPage(program), url(url) {}
+ const char* get_title() const override { return ""; }
+ std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *, const std::string &, const std::string &) override { return nullptr; }
+ std::unique_ptr<LazyFetchPage> create_channels_page(Program *, const std::string &) override { return nullptr; }
+ std::string get_url() override { return url; }
+ private:
+ std::string url;
+ };
+} \ No newline at end of file
diff --git a/plugins/Spotify.hpp b/plugins/Spotify.hpp
index 9cdd2af..89f8f3d 100644
--- a/plugins/Spotify.hpp
+++ b/plugins/Spotify.hpp
@@ -38,9 +38,12 @@ namespace QuickMedia {
class SpotifyAudioPage : public VideoPage {
public:
- SpotifyAudioPage(Program *program) : VideoPage(program) {}
+ SpotifyAudioPage(Program *program, const std::string &url) : VideoPage(program), url(url) {}
const char* get_title() const override { return ""; }
std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *, const std::string &, const std::string &) override { return nullptr; }
std::unique_ptr<LazyFetchPage> create_channels_page(Program *, const std::string &) override { return nullptr; }
+ std::string get_url() override { return url; }
+ private:
+ std::string url;
};
} \ No newline at end of file
diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp
index f8a5d5f..4691f04 100644
--- a/plugins/Youtube.hpp
+++ b/plugins/Youtube.hpp
@@ -75,15 +75,17 @@ namespace QuickMedia {
class YoutubeVideoPage : public VideoPage {
public:
- YoutubeVideoPage(Program *program) : VideoPage(program) {}
+ YoutubeVideoPage(Program *program, const std::string &url) : VideoPage(program), url(url) {}
const char* get_title() const override { return ""; }
BodyItems get_related_media(const std::string &url, std::string &channel_url) override;
std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override;
std::unique_ptr<Page> create_comments_page(Program *program) override;
std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program, const std::string &video_url, const std::string &video_title) override;
std::unique_ptr<LazyFetchPage> create_channels_page(Program *program, const std::string &channel_url) override;
+ std::string get_url() override { return url; }
private:
std::string xsrf_token;
std::string comments_continuation_token;
+ std::string url;
};
} \ No newline at end of file
diff --git a/src/Body.cpp b/src/Body.cpp
index de924da..e4d4e97 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -98,6 +98,7 @@ namespace QuickMedia {
title_color = other.title_color;
author_color = other.author_color;
description_color = other.description_color;
+ extra = other.extra;
return *this;
}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index cb570fc..ff78735 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -9,6 +9,7 @@
#include "../plugins/Matrix.hpp"
#include "../plugins/Pleroma.hpp"
#include "../plugins/Spotify.hpp"
+#include "../plugins/Soundcloud.hpp"
#include "../plugins/FileManager.hpp"
#include "../plugins/Pipe.hpp"
#include "../include/Scale.hpp"
@@ -453,7 +454,7 @@ namespace QuickMedia {
static void usage() {
fprintf(stderr, "usage: QuickMedia <plugin> [--tor] [--no-video] [--use-system-mpv-config] [--dir <directory>]\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube, spotify, nyaa.si, matrix, file-manager or pipe\n");
+ fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube, spotify, soundcloud, nyaa.si, matrix, file-manager or pipe\n");
fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n");
fprintf(stderr, " --tor Use tor. Disabled by default\n");
fprintf(stderr, " --use-system-mpv-config Use system mpv config instead of no config. Disabled by default\n");
@@ -497,6 +498,10 @@ namespace QuickMedia {
plugin_name = argv[i];
plugin_logo_path = resources_root + "images/spotify_logo.png";
no_video = true;
+ } else if(strcmp(argv[i], "soundcloud") == 0) {
+ plugin_name = argv[i];
+ plugin_logo_path = resources_root + "images/soundcloud_logo.png";
+ no_video = true;
} else if(strcmp(argv[i], "pornhub") == 0) {
plugin_name = argv[i];
plugin_logo_path = resources_root + "images/pornhub_logo.png";
@@ -689,6 +694,9 @@ namespace QuickMedia {
} else if(strcmp(plugin_name, "spotify") == 0) {
auto search_body = create_body();
tabs.push_back(Tab{std::move(search_body), std::make_unique<SpotifyPodcastSearchPage>(this), create_search_bar("Search...", 250)});
+ } else if(strcmp(plugin_name, "soundcloud") == 0) {
+ auto search_body = create_body();
+ tabs.push_back(Tab{std::move(search_body), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)});
} else if(strcmp(plugin_name, "mastodon") == 0 || strcmp(plugin_name, "pleroma") == 0) {
auto pleroma = std::make_shared<Pleroma>();
auto search_body = create_body();
@@ -1106,11 +1114,12 @@ namespace QuickMedia {
std::function<void()> submit_handler;
submit_handler = [this, &submit_handler, &after_submit_handler, &json_chapters, &tabs, &tab_associated_data, &selected_tab, &loop_running, &redraw]() {
- BodyItem *selected_item = tabs[selected_tab].body->get_selected();
+ auto selected_item = tabs[selected_tab].body->get_selected_shared();
if(!selected_item)
return;
std::vector<Tab> new_tabs;
+ tabs[selected_tab].page->submit_body_item = selected_item;
PluginResult submit_result = tabs[selected_tab].page->submit(selected_item->get_title(), selected_item->url, new_tabs);
if(submit_result != PluginResult::OK) {
// TODO: Show the exact cause of error (get error message from curl).
@@ -1135,11 +1144,14 @@ namespace QuickMedia {
tabs[selected_tab].body = std::move(new_tabs[0].body);
else
loop_running = false;
+ tabs[selected_tab].page->submit_body_item = nullptr;
return;
}
- if(new_tabs.empty())
+ if(new_tabs.empty()) {
+ tabs[selected_tab].page->submit_body_item = nullptr;
return;
+ }
if(after_submit_handler)
after_submit_handler(new_tabs);
@@ -1151,7 +1163,7 @@ namespace QuickMedia {
hide_virtual_keyboard();
if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::MANGA_IMAGES) {
- select_episode(selected_item, false);
+ select_episode(selected_item.get(), false);
Body *chapters_body = tabs[selected_tab].body.get();
chapters_body->filter_search_fuzzy(""); // Needed (or not really) to go to the next chapter when reaching the last page of a chapter
MangaImagesPage *manga_images_page = static_cast<MangaImagesPage*>(new_tabs[0].page.get());
@@ -1188,9 +1200,9 @@ namespace QuickMedia {
image_board_thread_page(static_cast<ImageBoardThreadPage*>(new_tabs[0].page.get()), new_tabs[0].body.get());
} else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) {
current_page = PageType::VIDEO_CONTENT;
- video_content_page(static_cast<VideoPage*>(new_tabs[0].page.get()), selected_item->url, selected_item->get_title(), false);
+ video_content_page(static_cast<VideoPage*>(new_tabs[0].page.get()), selected_item->get_title(), false);
} else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::CHAT) {
- body_set_selected_item(tabs[selected_tab].body.get(), selected_item);
+ body_set_selected_item(tabs[selected_tab].body.get(), selected_item.get());
current_page = PageType::CHAT;
current_chat_room = matrix->get_room_by_id(selected_item->url);
@@ -1225,6 +1237,7 @@ namespace QuickMedia {
json_chapters = &chapters_json;
}
+ tabs[selected_tab].page->submit_body_item = nullptr;
redraw = true;
hide_virtual_keyboard();
};
@@ -1727,7 +1740,7 @@ namespace QuickMedia {
#define CLEANMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask|Mod5Mask))
- void Program::video_content_page(VideoPage *video_page, std::string video_url, std::string video_title, bool download_if_streaming_fails) {
+ void Program::video_content_page(VideoPage *video_page, std::string video_title, bool download_if_streaming_fails) {
sf::Clock time_watched_timer;
bool added_recommendations = false;
bool video_loaded = false;
@@ -1735,6 +1748,7 @@ namespace QuickMedia {
const bool is_matrix = strcmp(plugin_name, "matrix") == 0;
PageType previous_page = pop_page_stack();
+ std::string video_url = video_page->get_url();
bool video_url_is_local = false;
if(download_if_streaming_fails) {
@@ -2824,7 +2838,8 @@ namespace QuickMedia {
current_page = PageType::VIDEO_CONTENT;
watched_videos.clear();
// TODO: Use real title
- video_content_page(thread_page, selected_item->attached_content_url, "No title.webm", true);
+ thread_page->video_url = selected_item->attached_content_url;
+ video_content_page(thread_page, "No title.webm", true);
redraw = true;
} else {
if(downloading_image && load_image_future.valid())
@@ -4117,7 +4132,8 @@ namespace QuickMedia {
watched_videos.clear();
current_page = PageType::VIDEO_CONTENT;
// TODO: Add title
- video_content_page(video_page.get(), url, "No title", false);
+ video_page->url = url;
+ video_content_page(video_page.get(), "No title", false);
redraw = true;
} else {
const char *launch_program = "xdg-open";
@@ -4186,8 +4202,7 @@ namespace QuickMedia {
if(selected_item_message) {
MessageType message_type = selected_item_message->type;
- std::string *selected_url = &selected->url;
- if(!selected_url->empty()) {
+ if(!selected->url.empty()) {
if(message_type == MessageType::VIDEO || message_type == MessageType::IMAGE || message_type == MessageType::AUDIO) {
page_stack.push(PageType::CHAT);
watched_videos.clear();
@@ -4196,13 +4211,14 @@ namespace QuickMedia {
bool prev_no_video = no_video;
no_video = is_audio;
// TODO: Add title
- video_content_page(video_page.get(), *selected_url, "No title", message_type == MessageType::VIDEO || message_type == MessageType::AUDIO);
+ video_page->url = selected->url;
+ video_content_page(video_page.get(), "No title", message_type == MessageType::VIDEO || message_type == MessageType::AUDIO);
no_video = prev_no_video;
redraw = true;
return true;
}
- launch_url(*selected_url);
+ launch_url(selected->url);
return true;
}
}
diff --git a/src/plugins/Pornhub.cpp b/src/plugins/Pornhub.cpp
index b4e908a..74b66b6 100644
--- a/src/plugins/Pornhub.cpp
+++ b/src/plugins/Pornhub.cpp
@@ -140,13 +140,13 @@ namespace QuickMedia {
return search_result_to_plugin_result(get_videos_in_page(url, is_tor_enabled(), result_items));
}
- PluginResult PornhubSearchPage::submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) {
- result_tabs.push_back(Tab{nullptr, std::make_unique<PornhubVideoPage>(program), nullptr});
+ PluginResult PornhubSearchPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{nullptr, std::make_unique<PornhubVideoPage>(program, url), nullptr});
return PluginResult::OK;
}
- PluginResult PornhubRelatedVideosPage::submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) {
- result_tabs.push_back(Tab{nullptr, std::make_unique<PornhubVideoPage>(program), nullptr});
+ PluginResult PornhubRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{nullptr, std::make_unique<PornhubVideoPage>(program, url), nullptr});
return PluginResult::OK;
}
diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp
new file mode 100644
index 0000000..cfb6011
--- /dev/null
+++ b/src/plugins/Soundcloud.cpp
@@ -0,0 +1,287 @@
+#include "../../plugins/Soundcloud.hpp"
+#include "../../include/NetUtils.hpp"
+#include "../../include/StringUtils.hpp"
+#include "../../include/Scale.hpp"
+
+namespace QuickMedia {
+ static std::string client_id = "Na04L87fnpWDMVCCW2ngWldN4JMoLTAc";
+
+ class SoundcloudPlaylist : public BodyItemExtra {
+ public:
+ BodyItems tracks;
+ };
+
+ // Return empty string if transcoding files are not found
+ static std::string get_best_transcoding_audio_url(const Json::Value &media_json) {
+ const Json::Value &transcodings_json = media_json["transcodings"];
+ if(transcodings_json.isArray() && !transcodings_json.empty() && transcodings_json[0].isObject()) {
+ const Json::Value &transcoding_url = transcodings_json[0]["url"];
+ if(transcoding_url.isString())
+ return transcoding_url.asString();
+ }
+ return "";
+ }
+
+ static std::string duration_to_descriptive_string(int64_t seconds) {
+ seconds /= 1000;
+ time_t minutes = seconds / 60;
+ time_t hours = minutes / 60;
+
+ std::string str;
+ if(hours >= 1) {
+ str = std::to_string(hours) + " hour" + (hours == 1 ? "" : "s");
+ seconds -= (hours * 60 * 60);
+ }
+
+ if(minutes >= 1) {
+ if(!str.empty())
+ str += ", ";
+ str += std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s");
+ seconds -= (minutes * 60);
+ }
+
+ if(!str.empty() || seconds > 0) {
+ if(!str.empty())
+ str += ", ";
+ str += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
+ }
+
+ return str;
+ }
+
+ static std::string collection_item_get_duration(const Json::Value &item_json) {
+ const Json::Value &full_duration_json = item_json["full_duration"];
+ if(full_duration_json.isInt64())
+ return duration_to_descriptive_string(full_duration_json.asInt64());
+
+ const Json::Value &duration_json = item_json["duration"];
+ if(duration_json.isInt64())
+ return duration_to_descriptive_string(duration_json.asInt64());
+
+ return "";
+ }
+
+ static std::shared_ptr<BodyItem> parse_collection_item(const Json::Value &item_json) {
+ std::string title;
+
+ const Json::Value &title_json = item_json["title"];
+ const Json::Value &username_json = item_json["username"];
+ if(title_json.isString())
+ title = title_json.asString();
+ else if(username_json.isString())
+ title = username_json.asString();
+ else
+ return nullptr;
+
+ auto body_item = BodyItem::create(std::move(title));
+ std::string description;
+
+ const Json::Value &media_json = item_json["media"];
+ if(media_json.isObject())
+ body_item->url = get_best_transcoding_audio_url(media_json);
+
+ if(body_item->url.empty()) {
+ const Json::Value &tracks_json = item_json["tracks"];
+ if(tracks_json.isArray()) {
+ auto playlist = std::make_shared<SoundcloudPlaylist>();
+ for(const Json::Value &track_json : tracks_json) {
+ if(!track_json.isObject())
+ continue;
+
+ auto track = parse_collection_item(track_json);
+ if(track)
+ playlist->tracks.push_back(std::move(track));
+ }
+
+ description = "Playlist with " + std::to_string(playlist->tracks.size()) + " track" + (playlist->tracks.size() == 1 ? "" : "s");
+ body_item->extra = std::move(playlist);
+ body_item->url = "track";
+ }
+ }
+
+ if(body_item->url.empty()) {
+ const Json::Value &id_json = item_json["id"];
+ if(id_json.isInt64())
+ body_item->url = "https://api-v2.soundcloud.com/stream/users/" + std::to_string(id_json.asInt64());
+ }
+
+ const Json::Value &artwork_url_json = item_json["artwork_url"];
+ const Json::Value &avatar_url_json = item_json["avatar_url"];
+ if(artwork_url_json.isString()) {
+ // For larger thumbnails
+ /*
+ if(strstr(artwork_url_json.asCString(), "-large") != 0) {
+ std::string artwork_url = artwork_url_json.asString();
+ string_replace_all(artwork_url, "-large", "-t200x200");
+ body_item->thumbnail_url = std::move(artwork_url);
+ body_item->thumbnail_size.x = 200;
+ body_item->thumbnail_size.y = 200;
+ } else {
+ body_item->thumbnail_url = artwork_url_json.asString();
+ body_item->thumbnail_size.x = 100;
+ body_item->thumbnail_size.y = 100;
+ }
+ */
+ body_item->thumbnail_url = artwork_url_json.asString();
+ body_item->thumbnail_size.x = 100;
+ body_item->thumbnail_size.y = 100;
+ } else if(avatar_url_json.isString()) {
+ body_item->thumbnail_url = avatar_url_json.asString();
+ body_item->thumbnail_size.x = 100;
+ body_item->thumbnail_size.y = 100;
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ }
+
+ std::string duration_str = collection_item_get_duration(item_json);
+ if(!duration_str.empty()) {
+ if(!description.empty())
+ description += '\n';
+ description += std::move(duration_str);
+ }
+
+ body_item->set_description(std::move(description));
+ return body_item;
+ }
+
+ static PluginResult parse_user_page(const Json::Value &json_root, BodyItems &result_items, std::string &next_href) {
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &next_href_json = json_root["next_href"];
+ if(next_href_json.isString())
+ next_href = next_href_json.asString();
+
+ const Json::Value &collection_json = json_root["collection"];
+ if(!collection_json.isArray())
+ return PluginResult::ERR;
+
+ for(const Json::Value &item_json : collection_json) {
+ if(!item_json.isObject())
+ continue;
+
+ const Json::Value &track_json = item_json["track"];
+ const Json::Value &playlist_json = item_json["playlist"];
+ if(track_json.isObject()) {
+ auto body_item = parse_collection_item(track_json);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ } else if(playlist_json.isObject()) {
+ auto body_item = parse_collection_item(playlist_json);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult SoundcloudPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ if(url.empty())
+ return PluginResult::ERR;
+
+ if(url == "track") {
+ auto body = create_body();
+ body->items = static_cast<SoundcloudPlaylist*>(submit_body_item->extra.get())->tracks;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<SoundcloudPlaylistPage>(program, title), nullptr});
+ } else if(url.find("/stream/users/") != std::string::npos) {
+ std::string query_url = url + "?client_id=" + client_id + "&limit=20&offset=0&linked_partitioning=1&app_version=1616689516&app_locale=en";
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, query_url, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ auto body = create_body();
+ std::string next_href;
+ PluginResult pr = parse_user_page(json_root, body->items, next_href);
+ if(pr != PluginResult::OK) return pr;
+
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<SoundcloudUserPage>(program, title, url, std::move(next_href)), nullptr});
+ } else {
+ std::string query_url = url + "?client_id=" + client_id;
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, query_url, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &url_json = json_root["url"];
+ if(!url_json.isString())
+ return PluginResult::ERR;
+
+ result_tabs.push_back(Tab{create_body(), std::make_unique<SoundcloudAudioPage>(program, url_json.asString()), nullptr});
+ }
+
+ return PluginResult::OK;
+ }
+
+ SearchResult SoundcloudSearchPage::search(const std::string &str, BodyItems &result_items) {
+ query_urn.clear();
+ PluginResult result = get_page(str, 0, result_items);
+ if(result != PluginResult::OK)
+ return SearchResult::ERR;
+ return SearchResult::OK;
+ }
+
+ PluginResult SoundcloudSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
+ std::string url = "https://api-v2.soundcloud.com/search?q=";
+ url += url_param_encode(str);
+ url += "&variant_ids=2227&facet=model&client_id=" + client_id + "&limit=20&offset=" + std::to_string(page * 20) + "&linked_partitioning=1&app_version=1616689516&app_locale=en";
+ if(!query_urn.empty())
+ url += "&query_url=" + url_param_encode(query_urn);
+ else if(page > 0)
+ return PluginResult::OK;
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, url, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &query_urn_json = json_root["query_urn"];
+ if(query_urn_json.isString())
+ query_urn = query_urn_json.asString();
+
+ const Json::Value &collection_json = json_root["collection"];
+ if(!collection_json.isArray())
+ return PluginResult::ERR;
+
+ for(const Json::Value &item_json : collection_json) {
+ if(!item_json.isObject())
+ continue;
+
+ const Json::Value &kind_json = item_json["kind"];
+ if(!kind_json.isString())
+ continue;
+
+ if(strcmp(kind_json.asCString(), "user") == 0 || strcmp(kind_json.asCString(), "track") == 0 || strcmp(kind_json.asCString(), "playlist") == 0) {
+ auto body_item = parse_collection_item(item_json);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ PluginResult SoundcloudUserPage::get_page(const std::string&, int page, BodyItems &result_items) {
+ while(current_page < page) {
+ PluginResult plugin_result = get_continuation_page(result_items);
+ if(plugin_result != PluginResult::OK) return plugin_result;
+ ++current_page;
+ }
+ return PluginResult::OK;
+ }
+
+ PluginResult SoundcloudUserPage::get_continuation_page(BodyItems &result_items) {
+ if(next_href.empty())
+ return PluginResult::OK;
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, next_href + "&client_id=" + client_id, {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+ return parse_user_page(json_root, result_items, next_href);
+ }
+} \ No newline at end of file
diff --git a/src/plugins/Spotify.cpp b/src/plugins/Spotify.cpp
index 14f9831..f56ed6c 100644
--- a/src/plugins/Spotify.cpp
+++ b/src/plugins/Spotify.cpp
@@ -274,8 +274,8 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult SpotifyEpisodeListPage::submit(const std::string &, const std::string &, std::vector<Tab> &result_tabs) {
- result_tabs.push_back(Tab{nullptr, std::make_unique<SpotifyAudioPage>(program), nullptr});
+ PluginResult SpotifyEpisodeListPage::submit(const std::string &, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{nullptr, std::make_unique<SpotifyAudioPage>(program, url), nullptr});
return PluginResult::OK;
}
} \ No newline at end of file
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 99227d5..3813068 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -572,7 +572,7 @@ namespace QuickMedia {
// TODO: Make all pages (for all services) lazy fetch in a similar manner!
result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", 350)});
} else {
- result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program), nullptr});
+ result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr});
}
return PluginResult::OK;
}
@@ -1099,10 +1099,10 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult YoutubeChannelPage::submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) {
+ PluginResult YoutubeChannelPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
if(url.empty())
return PluginResult::OK;
- result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program), nullptr});
+ result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr});
return PluginResult::OK;
}
@@ -1126,8 +1126,8 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult YoutubeRelatedVideosPage::submit(const std::string&, const std::string&, std::vector<Tab> &result_tabs) {
- result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program), nullptr});
+ PluginResult YoutubeRelatedVideosPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr});
return PluginResult::OK;
}