diff --git a/CMakeLists.txt b/CMakeLists.txt index 169c2a6..55b358c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_executable(spotify-downloader src/main.cpp + src/url_parser.cpp ) add_subdirectory(external/CLI11) diff --git a/src/main.cpp b/src/main.cpp index fdc0981..2c83557 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ +#include "url_parser.hpp" #include #include #include @@ -25,6 +26,15 @@ int main(int argc, char **argv) { if (cmd_download->parsed()) { std::cout << "Target URL: " << url << "\n"; + auto parsed = UrlParser::parse(url); + + if (!parsed) { + std::cerr << "Unsupported URL\n"; + return 1; + } + + std::cout << "Type: " << parsed->type << '\n'; + std::cout << "ID: " << parsed->id << '\n'; } return 0; diff --git a/src/url_parser.cpp b/src/url_parser.cpp new file mode 100644 index 0000000..e88067c --- /dev/null +++ b/src/url_parser.cpp @@ -0,0 +1,111 @@ +#include "url_parser.hpp" + +#include + +#include + +std::ostream &operator<<(std::ostream &os, UrlType type) { + switch (type) { + case UrlType::SpotifyTrack: + return os << "Spotify Track"; + + case UrlType::SpotifyPlaylist: + return os << "Spotify Playlist"; + + case UrlType::SpotifyAlbum: + return os << "Spotify Album"; + + case UrlType::SpotifyArtist: + return os << "Spotify Artist"; + + case UrlType::YoutubeVideo: + return os << "YouTube Video"; + + case UrlType::YoutubePlaylist: + return os << "YouTube Playlist"; + + default: + return os << "Unknown"; + } +} + +std::optional UrlParser::parse(const std::string &url) { + + std::smatch match; + + // Spotify + { + static const std::regex spotify( + R"(https?://open\.spotify\.com/(track|playlist|album|artist)/([A-Za-zA-Z0-9]+))"); + + if (std::regex_search(url, match, spotify)) { + + const auto resource = match[1].str(); + const auto id = match[2].str(); + + if (resource == "track") + return ParsedUrl{UrlType::SpotifyTrack, id}; + + if (resource == "playlist") + return ParsedUrl{UrlType::SpotifyPlaylist, id}; + + if (resource == "album") + return ParsedUrl{UrlType::SpotifyAlbum, id}; + + if (resource == "artist") + return ParsedUrl{UrlType::SpotifyArtist, id}; + } + } + + // Spotify URI + { + static const std::regex spotify_uri( + R"(spotify:(track|playlist|album|artist):([A-Za-z0-9]+))"); + + if (std::regex_search(url, match, spotify_uri)) { + + const auto resource = match[1].str(); + const auto id = match[2].str(); + + if (resource == "track") + return ParsedUrl{UrlType::SpotifyTrack, id}; + + if (resource == "playlist") + return ParsedUrl{UrlType::SpotifyPlaylist, id}; + + if (resource == "album") + return ParsedUrl{UrlType::SpotifyAlbum, id}; + + if (resource == "artist") + return ParsedUrl{UrlType::SpotifyArtist, id}; + } + } + + // YouTube Playlist + { + static const std::regex yt_playlist( + R"(https?://(?:www\.)?youtube\.com/playlist\?list=([^&]+))"); + + if (std::regex_search(url, match, yt_playlist)) + return ParsedUrl{UrlType::YoutubePlaylist, match[1].str()}; + } + + // YouTube Video + { + static const std::regex yt_video( + R"(https?://(?:www\.)?youtube\.com/watch\?.*v=([^&]+))"); + + if (std::regex_search(url, match, yt_video)) + return ParsedUrl{UrlType::YoutubeVideo, match[1].str()}; + } + + // youtu.be + { + static const std::regex yt_short(R"(https?://youtu\.be/([^?&]+))"); + + if (std::regex_search(url, match, yt_short)) + return ParsedUrl{UrlType::YoutubeVideo, match[1].str()}; + } + + return std::nullopt; +} diff --git a/src/url_parser.hpp b/src/url_parser.hpp new file mode 100644 index 0000000..c4edae0 --- /dev/null +++ b/src/url_parser.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +enum class UrlType { + SpotifyTrack, + SpotifyPlaylist, + SpotifyAlbum, + SpotifyArtist, + + YoutubeVideo, + YoutubePlaylist, + + Unknown +}; + +struct ParsedUrl { + UrlType type; + std::string id; +}; + +std::ostream &operator<<(std::ostream &os, UrlType type); + +class UrlParser { +public: + static std::optional parse(const std::string &url); +};