aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-05-28 18:04:10 +0200
committerdec05eba <dec05eba@protonmail.com>2021-05-28 18:04:10 +0200
commit89c204bc473ba761d52be25f279d01af9237ef30 (patch)
treec7b49a19091649b8843c5deed021731c4cbb1018
parent3ccd8021b7fdc6028a9eb6db1f265572759dca26 (diff)
Fix download, remove spotify (which now requires an account)
-rw-r--r--README.md6
-rw-r--r--icons/spotify_launcher.pngbin7423 -> 0 bytes
-rw-r--r--images/spotify_logo.pngbin10877 -> 0 bytes
-rw-r--r--launcher/QuickMedia.desktop4
-rw-r--r--launcher/QuickMedia_tabbed.desktop2
-rw-r--r--plugins/Spotify.hpp49
-rw-r--r--src/DownloadUtils.cpp8
-rw-r--r--src/QuickMedia.cpp8
-rw-r--r--src/plugins/Spotify.cpp290
9 files changed, 12 insertions, 355 deletions
diff --git a/README.md b/README.md
index 9500505..b205b22 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)`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `mangadex`, `readm`, `onimanga`, `4chan`, `matrix`, `saucenao` and _others_.\
+Currently supported web services: `youtube`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `mangadex`, `readm`, `onimanga`, `4chan`, `matrix`, `saucenao` and _others_.\
**Note:** file-manager is early in progress.\
Config data, including manga progress is stored under `$HOME/.config/quickmedia`.\
Cache is stored under `$HOME/.cache/quickmedia`.
@@ -8,7 +8,7 @@ Cache is stored under `$HOME/.cache/quickmedia`.
```
usage: quickmedia <plugin> [--use-system-mpv-config] [--dir <directory>] [-e <window>]
OPTIONS:
- plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, youtube, spotify, soundcloud, nyaa.si, matrix, saucenao, file-manager or stdin
+ plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, youtube, soundcloud, nyaa.si, matrix, saucenao, file-manager or stdin
--no-video Only play audio when playing a video. Disabled by default
--use-system-mpv-config Use system mpv config instead of no config. Disabled by default
--upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default
@@ -90,7 +90,7 @@ Note that at the moment, cached images will not be scaled with the dpi. Images d
### Optional
`noto-fonts-cjk` needs to be installed to view chinese, japanese and korean characters.\
`mpv` needs to be installed to play videos.\
-`youtube-dl` needs to be installed to play youtube music/video or spotify podcasts.\
+`youtube-dl` needs to be installed to play youtube music/videos.\
`libnotify` which provides `notify-send` needs to be installed to show notifications (on Linux and other systems that uses d-bus notification system).\
[automedia](https://git.dec05eba.com/AutoMedia/) needs to be installed when tracking manga with `Ctrl + T`.\
`waifu2x-ncnn-vulkan` needs to be installed when using the `--upscale-images` or `--upscale-images-always` option.\
diff --git a/icons/spotify_launcher.png b/icons/spotify_launcher.png
deleted file mode 100644
index b3abe61..0000000
--- a/icons/spotify_launcher.png
+++ /dev/null
Binary files differ
diff --git a/images/spotify_logo.png b/images/spotify_logo.png
deleted file mode 100644
index a389f79..0000000
--- a/images/spotify_logo.png
+++ /dev/null
Binary files differ
diff --git a/launcher/QuickMedia.desktop b/launcher/QuickMedia.desktop
index 2089dc8..89849db 100644
--- a/launcher/QuickMedia.desktop
+++ b/launcher/QuickMedia.desktop
@@ -2,7 +2,7 @@
Type=Application
Name=QuickMedia
GenericName=QuickMedia
-Comment=A dmenu-inspired native client for web services. Currently supported web services: youtube, spotify (podcasts), soundcloud, nyaa.si, manganelo, mangatown, mangadex, 4chan, matrix and others
+Comment=A dmenu-inspired native client for web services. Currently supported web services: youtube, soundcloud, nyaa.si, manganelo, mangatown, mangadex, 4chan, matrix and others
Exec=quickmedia launcher
Terminal=false
-Keywords=4chan;manga;matrix;nyaa;torrent;soundcloud;spotify;podcast;youtube;music;quickmedia;
+Keywords=4chan;manga;matrix;nyaa;torrent;soundcloud;podcast;youtube;music;quickmedia;
diff --git a/launcher/QuickMedia_tabbed.desktop b/launcher/QuickMedia_tabbed.desktop
index 409e84f..72100a4 100644
--- a/launcher/QuickMedia_tabbed.desktop
+++ b/launcher/QuickMedia_tabbed.desktop
@@ -5,4 +5,4 @@ GenericName=QuickMedia tabbed
Comment=Launch QuickMedia with tabs
Exec=tabbed -c -k quickmedia launcher -e
Terminal=false
-Keywords=4chan;manga;matrix;nyaa;torrent;soundcloud;spotify;podcast;youtube;music;quickmedia;
+Keywords=4chan;manga;matrix;nyaa;torrent;soundcloud;podcast;youtube;music;quickmedia;
diff --git a/plugins/Spotify.hpp b/plugins/Spotify.hpp
deleted file mode 100644
index 66cc992..0000000
--- a/plugins/Spotify.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#pragma once
-
-#include "Page.hpp"
-
-namespace QuickMedia {
- class SpotifyPage : public Page {
- public:
- SpotifyPage(Program *program);
- virtual ~SpotifyPage() = default;
- protected:
- DownloadResult download_json_error_retry(Json::Value &json_root, const std::string &url, std::vector<CommandArg> additional_args);
- private:
- PluginResult update_token();
- private:
- std::string access_token;
- };
-
- class SpotifyPodcastSearchPage : public SpotifyPage {
- public:
- SpotifyPodcastSearchPage(Program *program) : SpotifyPage(program) {}
- const char* get_title() const override { return "Podcasts"; }
- 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;
- PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
- };
-
- class SpotifyEpisodeListPage : public SpotifyPage {
- public:
- SpotifyEpisodeListPage(Program *program, const std::string &url) : SpotifyPage(program), url(url) {}
- const char* get_title() const override { return "Episodes"; }
- bool search_is_filter() override { return true; }
- PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override;
- PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
- private:
- std::string url;
- };
-
- class SpotifyAudioPage : public VideoPage {
- public:
- 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<Page> 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/src/DownloadUtils.cpp b/src/DownloadUtils.cpp
index b6d21b9..eb8fa63 100644
--- a/src/DownloadUtils.cpp
+++ b/src/DownloadUtils.cpp
@@ -223,9 +223,11 @@ namespace QuickMedia {
}
bool download_async_gui(const std::string &url, const std::string &file_manager_start_dir, bool use_youtube_dl, bool no_video) {
- char quickmedia_path[PATH_MAX];
- if(readlink("/proc/self/exe", quickmedia_path, sizeof(quickmedia_path)) == -1)
- strcpy(quickmedia_path, "quickmedia");
+ // TODO: Fix this not working when installed to /usr/bin/quickmedia for some reason
+ //char quickmedia_path[PATH_MAX];
+ //if(readlink("/proc/self/exe", quickmedia_path, sizeof(quickmedia_path)) == -1)
+ // strcpy(quickmedia_path, "quickmedia");
+ const char *quickmedia_path = "quickmedia";
std::vector<const char*> args = { quickmedia_path, "download", "-u", url.c_str(), "--dir", file_manager_start_dir.c_str() };
if(use_youtube_dl) args.push_back("--youtube-dl");
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index c2dbf1c..db03aac 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -8,7 +8,6 @@
#include "../plugins/Fourchan.hpp"
#include "../plugins/NyaaSi.hpp"
#include "../plugins/Matrix.hpp"
-#include "../plugins/Spotify.hpp"
#include "../plugins/Soundcloud.hpp"
#include "../plugins/FileManager.hpp"
#include "../plugins/Pipe.hpp"
@@ -68,7 +67,6 @@ static const std::pair<const char*, const char*> valid_plugins[] = {
std::make_pair("readm", "readm_logo.png"),
std::make_pair("manga", nullptr),
std::make_pair("youtube", "yt_logo_rgb_dark_small.png"),
- std::make_pair("spotify", "spotify_logo.png"),
std::make_pair("soundcloud", "soundcloud_logo.png"),
std::make_pair("pornhub", "pornhub_logo.png"),
std::make_pair("spankbang", "spankbang_logo.png"),
@@ -314,7 +312,7 @@ namespace QuickMedia {
static void usage() {
fprintf(stderr, "usage: quickmedia <plugin> [--no-video] [--use-system-mpv-config] [--dir <directory>] [-e <window>]\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, youtube, spotify, soundcloud, nyaa.si, matrix, saucenao, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n");
+ fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, youtube, soundcloud, nyaa.si, matrix, saucenao, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n");
fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n");
fprintf(stderr, " --use-system-mpv-config Use system mpv config instead of no config. Disabled by default\n");
fprintf(stderr, " --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default\n");
@@ -1020,7 +1018,6 @@ namespace QuickMedia {
pipe_body->items.push_back(create_launcher_body_item("Nyaa.si", "nyaa.si", resources_root + "icons/nyaa_si_launcher.png"));
pipe_body->items.push_back(create_launcher_body_item("SauceNAO", "saucenao", ""));
pipe_body->items.push_back(create_launcher_body_item("Soundcloud", "soundcloud", resources_root + "icons/soundcloud_launcher.png"));
- pipe_body->items.push_back(create_launcher_body_item("Spotify", "spotify", resources_root + "icons/spotify_launcher.png"));
pipe_body->items.push_back(create_launcher_body_item("YouTube", "youtube", resources_root + "icons/yt_launcher.png"));
pipe_body->items.push_back(create_launcher_body_item("YouTube (audio only)", "youtube-audio", resources_root + "icons/yt_launcher.png"));
tabs.push_back(Tab{std::move(pipe_body), std::make_unique<PipePage>(this, "Select plugin to launch"), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
@@ -1148,9 +1145,6 @@ namespace QuickMedia {
auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://xhamster.com/", sf::Vector2i(240, 135));
add_xhamster_handlers(search_page.get());
tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 500)});
- } else if(strcmp(plugin_name, "spotify") == 0) {
- tabs.push_back(Tab{create_body(), std::make_unique<SpotifyPodcastSearchPage>(this), create_search_bar("Search...", 350)});
- no_video = true;
} else if(strcmp(plugin_name, "soundcloud") == 0) {
tabs.push_back(Tab{create_body(), std::make_unique<SoundcloudSearchPage>(this), create_search_bar("Search...", 500)});
no_video = true;
diff --git a/src/plugins/Spotify.cpp b/src/plugins/Spotify.cpp
deleted file mode 100644
index d41446b..0000000
--- a/src/plugins/Spotify.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-#include "../../plugins/Spotify.hpp"
-#include "../../include/NetUtils.hpp"
-#include "../../include/Utils.hpp"
-#include "../../include/Scale.hpp"
-
-namespace QuickMedia {
- SpotifyPage::SpotifyPage(Program *program) : Page(program) {
- Path spotify_cache_path = get_cache_dir().join("spotify").join("access_token");
- if(file_get_content(spotify_cache_path, access_token) != 0)
- access_token.clear();
- }
-
- DownloadResult SpotifyPage::download_json_error_retry(Json::Value &json_root, const std::string &url, std::vector<CommandArg> additional_args) {
- if(access_token.empty()) {
- PluginResult update_token_res = update_token();
- if(update_token_res != PluginResult::OK) return DownloadResult::ERR;
- }
-
- std::string authorization = "authorization: Bearer " + access_token;
- additional_args.push_back({ "-H", authorization.c_str() });
-
- std::string err_msg;
- DownloadResult result = download_json(json_root, url, additional_args, true, &err_msg);
- if(result != DownloadResult::OK) return result;
-
- if(!json_root.isObject())
- return DownloadResult::ERR;
-
- const Json::Value &error_json = json_root["error"];
- if(error_json.isObject()) {
- const Json::Value &status_json = error_json["status"];
- if(status_json.isInt() && status_json.asInt() == 401) {
- fprintf(stderr, "Spotify access token expired, requesting a new token...\n");
- PluginResult update_token_res = update_token();
- if(update_token_res != PluginResult::OK) return DownloadResult::ERR;
-
- authorization = "authorization: Bearer " + access_token;
- additional_args.back().value = authorization.c_str();
- DownloadResult result = download_json(json_root, url, additional_args, true);
- if(result != DownloadResult::OK) return result;
-
- if(!json_root.isObject())
- return DownloadResult::ERR;
- }
- }
-
- return DownloadResult::OK;
- }
-
- PluginResult SpotifyPage::update_token() {
- std::string url = "https://open.spotify.com/get_access_token?reason=transport&productType=web_player";
-
- std::vector<CommandArg> additional_args = {
- { "-H", "authority: application/json" },
- { "-H", "Referer: open.spotify.com" },
- { "-H", "app-platform: WebPlayer" },
- { "-H", "spotify-app-version: 1.1.53.594.g8178ce9b" }
- };
-
- Json::Value json_root;
- DownloadResult result = download_json(json_root, url, std::move(additional_args), true);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isObject())
- return PluginResult::ERR;
-
- const Json::Value &access_token_json = json_root["accessToken"];
- if(!access_token_json.isString())
- return PluginResult::ERR;
-
- access_token = access_token_json.asString();
- Path spotify_cache_dir = get_cache_dir().join("spotify");
- if(create_directory_recursive(spotify_cache_dir) == 0)
- fprintf(stderr, "Failed to create spotify cache directory\n");
- spotify_cache_dir.join("access_token");
- file_overwrite_atomic(spotify_cache_dir, access_token);
- return PluginResult::OK;
- }
-
- static void image_sources_set_thumbnail(BodyItem *body_item, const Json::Value &sources_list_json) {
- if(!sources_list_json.isArray())
- return;
-
- for(const Json::Value &source_json : sources_list_json) {
- if(!source_json.isObject())
- continue;
-
- const Json::Value &width_json = source_json["width"];
- const Json::Value &height_json = source_json["height"];
- const Json::Value &url_json = source_json["url"];
- if(!width_json.isInt() || !height_json.isInt() || !url_json.isString())
- continue;
-
- if(height_json.asInt() >= 200 && height_json.asInt() <= 350) {
- body_item->thumbnail_url = url_json.asString();
- body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
- body_item->thumbnail_size.x = width_json.asInt();
- body_item->thumbnail_size.y = height_json.asInt();
- body_item->thumbnail_size = clamp_to_size(body_item->thumbnail_size, sf::Vector2i(150, 150));
- return;
- }
- }
- }
-
- SearchResult SpotifyPodcastSearchPage::search(const std::string &str, BodyItems &result_items) {
- PluginResult result = get_page(str, 0, result_items);
- if(result != PluginResult::OK)
- return SearchResult::ERR;
- return SearchResult::OK;
- }
-
- PluginResult SpotifyPodcastSearchPage::get_page(const std::string &str, int page, BodyItems &result_items) {
- std::string url = "https://api.spotify.com/v1/search?query=";
- url += url_param_encode(str);
- url += "&type=show&include_external=audio&market=US&offset=" + std::to_string(page * 10) + "&limit=10";
-
- std::vector<CommandArg> additional_args = {
- { "-H", "accept: application/json" },
- { "-H", "Referer: https://open.spotify.com/" },
- { "-H", "app-platform: WebPlayer" },
- { "-H", "spotify-app-version: 1.1.53.594.g8178ce9b" }
- };
-
- Json::Value json_root;
- DownloadResult result = download_json_error_retry(json_root, url, additional_args);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isObject())
- return PluginResult::ERR;
-
- const Json::Value &shows_json = json_root["shows"];
- if(!shows_json.isObject())
- return PluginResult::ERR;
-
- const Json::Value &items_json = shows_json["items"];
- if(!items_json.isArray())
- return PluginResult::ERR;
-
- for(const Json::Value &item_json : items_json) {
- if(!item_json.isObject())
- continue;
-
- const Json::Value &name_json = item_json["name"];
- const Json::Value &uri_json = item_json["uri"];
- if(!name_json.isString() || !uri_json.isString())
- continue;
-
- auto body_item = BodyItem::create(name_json.asString());
- body_item->url = uri_json.asString();
-
- const Json::Value &publisher_json = item_json["publisher"];
- if(publisher_json.isString()) {
- body_item->set_description(publisher_json.asString());
- body_item->set_description_color(sf::Color(179, 179, 179));
- }
-
- image_sources_set_thumbnail(body_item.get(), item_json["images"]);
- result_items.push_back(std::move(body_item));
- }
-
- return PluginResult::OK;
- }
-
- PluginResult SpotifyPodcastSearchPage::submit(const std::string &, const std::string &url, std::vector<Tab> &result_tabs) {
- auto body = create_body();
- auto episode_list_page = std::make_unique<SpotifyEpisodeListPage>(program, url);
- PluginResult result = episode_list_page->get_page("", 0, body->items);
- if(result != PluginResult::OK)
- return result;
-
- result_tabs.push_back(Tab{std::move(body), std::move(episode_list_page), nullptr});
- return result;
- }
-
- static std::string unix_time_to_local_time_str(time_t unix_time) {
- struct tm time_tm;
- localtime_r(&unix_time, &time_tm);
- char time_str[128] = {0};
- strftime(time_str, sizeof(time_str) - 1, "%Y-%m-%d %H:%M", &time_tm);
- return time_str;
- }
-
- PluginResult SpotifyEpisodeListPage::get_page(const std::string &, int page, BodyItems &result_items) {
- std::string request_url = "https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryShowEpisodes&variables=";
- request_url += url_param_encode("{\"uri\":\"" + url + "\",\"offset\":" + std::to_string(page * 50) + ",\"limit\":50}");
- request_url += "&extensions=";
- request_url += url_param_encode("{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"e0e5ce27bd7748d2c59b4d44ba245a8992a05be75d6fabc3b20753fc8857444d\"}}");
-
- std::vector<CommandArg> additional_args = {
- { "-H", "authority: api-partner.spotify.com" },
- { "-H", "accept: application/json" },
- { "-H", "Referer: https://open.spotify.com/" },
- { "-H", "app-platform: WebPlayer" },
- { "-H", "spotify-app-version: 1.1.54.40.g75ab4382" }
- };
-
- Json::Value json_root;
- DownloadResult result = download_json_error_retry(json_root, request_url, additional_args);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isObject())
- return PluginResult::ERR;
-
- const Json::Value &data_json = json_root["data"];
- if(!data_json.isObject())
- return PluginResult::ERR;
-
- const Json::Value &podcast_json = data_json["podcast"];
- if(!podcast_json.isObject())
- return PluginResult::ERR;
-
- const Json::Value &episodes_json = podcast_json["episodes"];
- if(!episodes_json.isObject())
- return PluginResult::ERR;
-
- const Json::Value &episode_items_json = episodes_json["items"];
- if(!episode_items_json.isArray())
- return PluginResult::ERR;
-
- for(const Json::Value &episode_item_json : episode_items_json) {
- if(!episode_item_json.isObject())
- continue;
-
- const Json::Value &episode_json = episode_item_json["episode"];
- if(!episode_json.isObject())
- continue;
-
- const Json::Value &name_json = episode_json["name"];
- if(!name_json.isString())
- continue;
-
- const Json::Value &sharing_info_json = episode_json["sharingInfo"];
- if(!sharing_info_json.isObject())
- continue;
-
- const Json::Value &share_url_json = sharing_info_json["shareUrl"];
- if(!share_url_json.isString())
- continue;
-
- auto body_item = BodyItem::create(name_json.asString());
- body_item->url = share_url_json.asString();
-
- std::string description;
- std::string time;
-
- const Json::Value &description_json = episode_json["description"];
- if(description_json.isString())
- description += description_json.asString();
-
- const Json::Value &release_data_json = episode_json["releaseDate"];
- if(release_data_json.isObject()) {
- const Json::Value &iso_string_json = release_data_json["isoString"];
- if(iso_string_json.isString())
- time += unix_time_to_local_time_str(iso_utc_to_unix_time(iso_string_json.asCString()));
- }
-
- const Json::Value &duration_json = episode_json["duration"];
- if(duration_json.isObject()) {
- const Json::Value &total_ms_json = duration_json["totalMilliseconds"];
- if(total_ms_json.isInt()) {
- if(!time.empty())
- time += " • ";
- time += std::to_string(total_ms_json.asInt() / 1000 / 60) + " min";
- }
- }
-
- if(!time.empty()) {
- if(!description.empty())
- description += '\n';
- description += std::move(time);
- }
-
- body_item->set_description(std::move(description));
- body_item->set_description_color(sf::Color(179, 179, 179));
-
- const Json::Value &cover_art_json = episode_json["coverArt"];
- if(cover_art_json.isObject())
- image_sources_set_thumbnail(body_item.get(), cover_art_json["sources"]);
-
- result_items.push_back(std::move(body_item));
- }
-
- return PluginResult::OK;
- }
-
- 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