Add support for open.spotify.com links (#392)
* No longer necessary * Add support for open.spotify.com links * Reuse struct for insert command * Formatting
This commit is contained in:
@@ -65,6 +65,7 @@ mod queue;
|
||||
mod sharing;
|
||||
mod show;
|
||||
mod spotify;
|
||||
mod spotify_url;
|
||||
mod theme;
|
||||
mod track;
|
||||
mod traits;
|
||||
|
||||
@@ -59,6 +59,7 @@ use crate::events::{Event, EventManager};
|
||||
use crate::playable::Playable;
|
||||
use crate::queue;
|
||||
use crate::track::Track;
|
||||
|
||||
use rspotify::model::recommend::Recommendations;
|
||||
use rspotify::model::show::{FullEpisode, FullShow, Show, SimplifiedEpisode};
|
||||
|
||||
@@ -965,6 +966,7 @@ impl Spotify {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum URIType {
|
||||
Album,
|
||||
Artist,
|
||||
|
||||
102
src/spotify_url.rs
Normal file
102
src/spotify_url.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use crate::spotify::URIType;
|
||||
|
||||
use url::{Host, Url};
|
||||
|
||||
pub struct SpotifyURL {
|
||||
pub id: String,
|
||||
pub uri_type: URIType,
|
||||
}
|
||||
|
||||
impl SpotifyURL {
|
||||
fn new(id: &str, uri_type: URIType) -> SpotifyURL {
|
||||
SpotifyURL {
|
||||
id: id.to_string(),
|
||||
uri_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get media id and type from open.spotify.com url
|
||||
///
|
||||
/// ```
|
||||
/// let result = spotify_url::SpotifyURL::from_url("https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC").unwrap();
|
||||
/// assert_eq!(result.id, "4uLU6hMCjMI75M1A2tKUQC");
|
||||
/// assert_eq!(result.uri_type, URIType::Track);
|
||||
/// ```
|
||||
pub fn from_url(s: &str) -> Option<SpotifyURL> {
|
||||
let url = Url::parse(s).ok()?;
|
||||
if url.host() != Some(Host::Domain("open.spotify.com")) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut path_segments = url.path_segments()?;
|
||||
|
||||
let entity = path_segments.next()?;
|
||||
|
||||
let uri_type = match entity.to_lowercase().as_str() {
|
||||
"album" => Some(URIType::Album),
|
||||
"artist" => Some(URIType::Artist),
|
||||
"episode" => Some(URIType::Episode),
|
||||
"playlist" => Some(URIType::Playlist),
|
||||
"show" => Some(URIType::Show),
|
||||
"track" => Some(URIType::Track),
|
||||
"user" => {
|
||||
let _user_id = path_segments.next()?;
|
||||
let entity = path_segments.next()?;
|
||||
|
||||
if entity != "playlist" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(URIType::Playlist)
|
||||
}
|
||||
_ => None,
|
||||
}?;
|
||||
|
||||
let id = path_segments.next()?;
|
||||
|
||||
Some(SpotifyURL::new(id, uri_type))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::SpotifyURL;
|
||||
use crate::spotify::URIType;
|
||||
|
||||
#[test]
|
||||
fn test_urls() {
|
||||
let mut test_cases = HashMap::new();
|
||||
test_cases.insert(
|
||||
"https://open.spotify.com/playlist/1XFxe8bkTryTODn0lk4CNa?si=FfSpZ6KPQdieClZbwHakOQ",
|
||||
SpotifyURL::new("1XFxe8bkTryTODn0lk4CNa", URIType::Playlist),
|
||||
);
|
||||
test_cases.insert(
|
||||
"https://open.spotify.com/track/6fRJg3R90w0juYoCJXxj2d",
|
||||
SpotifyURL::new("6fRJg3R90w0juYoCJXxj2d", URIType::Track),
|
||||
);
|
||||
test_cases.insert(
|
||||
"https://open.spotify.com/user/~villainy~/playlist/0OgoSs65CLDPn6AF6tsZVg",
|
||||
SpotifyURL::new("0OgoSs65CLDPn6AF6tsZVg", URIType::Playlist),
|
||||
);
|
||||
test_cases.insert(
|
||||
"https://open.spotify.com/show/4MZfJbM2MXzZdPbv6gi5lJ",
|
||||
SpotifyURL::new("4MZfJbM2MXzZdPbv6gi5lJ", URIType::Show),
|
||||
);
|
||||
test_cases.insert(
|
||||
"https://open.spotify.com/episode/3QE6rfmjRaeqXSqeWcIWF6",
|
||||
SpotifyURL::new("3QE6rfmjRaeqXSqeWcIWF6", URIType::Episode),
|
||||
);
|
||||
test_cases.insert(
|
||||
"https://open.spotify.com/artist/6LEeAFiJF8OuPx747e1wxR",
|
||||
SpotifyURL::new("6LEeAFiJF8OuPx747e1wxR", URIType::Artist),
|
||||
);
|
||||
|
||||
for case in test_cases {
|
||||
let result = SpotifyURL::from_url(case.0).unwrap();
|
||||
assert_eq!(result.id, case.1.id);
|
||||
assert_eq!(result.uri_type, case.1.uri_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ use cursive::view::ScrollBase;
|
||||
use cursive::{Cursive, Printer, Rect, Vec2};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::album::Album;
|
||||
use crate::artist::Artist;
|
||||
use crate::command::{
|
||||
Command, GotoMode, JumpMode, MoveAmount, MoveMode, SortDirection, SortKey, TargetMode,
|
||||
@@ -28,7 +27,7 @@ use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||
use crate::ui::album::AlbumView;
|
||||
use crate::ui::artist::ArtistView;
|
||||
use crate::ui::contextmenu::ContextMenu;
|
||||
use regex::Regex;
|
||||
use crate::{album::Album, spotify::URIType, spotify_url::SpotifyURL};
|
||||
|
||||
pub type Paginator<I> = Box<dyn Fn(Arc<RwLock<Vec<I>>>) + Send + Sync>;
|
||||
|
||||
@@ -653,32 +652,28 @@ impl<I: ListItem + Clone> ViewExt for ListView<I> {
|
||||
|
||||
let spotify = self.queue.get_spotify();
|
||||
|
||||
let re =
|
||||
Regex::new(r"https?://open\.spotify\.com/(user/[^/]+/)?(\S+)/(\S+)(\?si=\S+)?")
|
||||
.unwrap();
|
||||
let captures = re.captures(&url);
|
||||
let url = SpotifyURL::from_url(&url);
|
||||
|
||||
if let Some(captures) = captures {
|
||||
let target: Option<Box<dyn ListItem>> = match &captures[2] {
|
||||
"track" => spotify
|
||||
.track(&captures[3])
|
||||
if let Some(url) = url {
|
||||
let target: Option<Box<dyn ListItem>> = match url.uri_type {
|
||||
URIType::Track => spotify
|
||||
.track(&url.id)
|
||||
.map(|track| Track::from(&track).as_listitem()),
|
||||
"album" => spotify
|
||||
.album(&captures[3])
|
||||
URIType::Album => spotify
|
||||
.album(&url.id)
|
||||
.map(|album| Album::from(&album).as_listitem()),
|
||||
"playlist" => spotify
|
||||
.playlist(&captures[3])
|
||||
URIType::Playlist => spotify
|
||||
.playlist(&url.id)
|
||||
.map(|playlist| Playlist::from(&playlist).as_listitem()),
|
||||
"artist" => spotify
|
||||
.artist(&captures[3])
|
||||
URIType::Artist => spotify
|
||||
.artist(&url.id)
|
||||
.map(|artist| Artist::from(&artist).as_listitem()),
|
||||
"episode" => spotify
|
||||
.episode(&captures[3])
|
||||
URIType::Episode => spotify
|
||||
.episode(&url.id)
|
||||
.map(|episode| Episode::from(&episode).as_listitem()),
|
||||
"show" => spotify
|
||||
.get_show(&captures[3])
|
||||
URIType::Show => spotify
|
||||
.get_show(&url.id)
|
||||
.map(|show| Show::from(&show).as_listitem()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let queue = self.queue.clone();
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::playlist::Playlist;
|
||||
use crate::queue::Queue;
|
||||
use crate::show::Show;
|
||||
use crate::spotify::{Spotify, URIType};
|
||||
use crate::spotify_url::SpotifyURL;
|
||||
use crate::track::Track;
|
||||
use crate::traits::{ListItem, ViewExt};
|
||||
use crate::ui::listview::{ListView, Pagination};
|
||||
@@ -380,22 +381,6 @@ impl SearchResultsView {
|
||||
|
||||
// is the query a Spotify URI?
|
||||
if let Some(uritype) = URIType::from_uri(&query) {
|
||||
// Clear the results if we are going to process a Spotify URI. We need
|
||||
// to do this since we are only calling the search function for the
|
||||
// given URI type which leaves the previous search results intact.
|
||||
let results_tracks = self.results_tracks.clone();
|
||||
*results_tracks.write().unwrap() = Vec::new();
|
||||
let results_albums = self.results_albums.clone();
|
||||
*results_albums.write().unwrap() = Vec::new();
|
||||
let results_artists = self.results_artists.clone();
|
||||
*results_artists.write().unwrap() = Vec::new();
|
||||
let results_playlists = self.results_playlists.clone();
|
||||
*results_playlists.write().unwrap() = Vec::new();
|
||||
let results_shows = self.results_shows.clone();
|
||||
*results_shows.write().unwrap() = Vec::new();
|
||||
let results_episodes = self.results_episodes.clone();
|
||||
*results_episodes.write().unwrap() = Vec::new();
|
||||
|
||||
match uritype {
|
||||
URIType::Track => {
|
||||
self.perform_search(
|
||||
@@ -452,6 +437,65 @@ impl SearchResultsView {
|
||||
self.tabs.move_focus_to(5);
|
||||
}
|
||||
}
|
||||
// Is the query a spotify URL?
|
||||
// https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC
|
||||
} else if let Some(url) = SpotifyURL::from_url(&query) {
|
||||
match url.uri_type {
|
||||
URIType::Track => {
|
||||
self.perform_search(
|
||||
Box::new(Self::get_track),
|
||||
&self.results_tracks,
|
||||
&url.id,
|
||||
None,
|
||||
);
|
||||
self.tabs.move_focus_to(0);
|
||||
}
|
||||
URIType::Album => {
|
||||
self.perform_search(
|
||||
Box::new(Self::get_album),
|
||||
&self.results_albums,
|
||||
&url.id,
|
||||
None,
|
||||
);
|
||||
self.tabs.move_focus_to(1);
|
||||
}
|
||||
URIType::Artist => {
|
||||
self.perform_search(
|
||||
Box::new(Self::get_artist),
|
||||
&self.results_artists,
|
||||
&url.id,
|
||||
None,
|
||||
);
|
||||
self.tabs.move_focus_to(2);
|
||||
}
|
||||
URIType::Playlist => {
|
||||
self.perform_search(
|
||||
Box::new(Self::get_playlist),
|
||||
&self.results_playlists,
|
||||
&url.id,
|
||||
None,
|
||||
);
|
||||
self.tabs.move_focus_to(3);
|
||||
}
|
||||
URIType::Show => {
|
||||
self.perform_search(
|
||||
Box::new(Self::get_show),
|
||||
&self.results_shows,
|
||||
&url.id,
|
||||
None,
|
||||
);
|
||||
self.tabs.move_focus_to(4);
|
||||
}
|
||||
URIType::Episode => {
|
||||
self.perform_search(
|
||||
Box::new(Self::get_episode),
|
||||
&self.results_episodes,
|
||||
&url.id,
|
||||
None,
|
||||
);
|
||||
self.tabs.move_focus_to(5);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.perform_search(
|
||||
Box::new(Self::search_track),
|
||||
|
||||
Reference in New Issue
Block a user