From 78042de95897c78df54684ab087e2cbe12d7985e Mon Sep 17 00:00:00 2001 From: byBlurr Date: Wed, 3 Feb 2021 15:28:20 +0000 Subject: [PATCH] Add project files. --- SpotifyShuffle.sln | 25 ++ SpotifyShuffle/Program.cs | 326 +++++++++++++++++++++++++++ SpotifyShuffle/SpotifyShuffle.csproj | 13 ++ 3 files changed, 364 insertions(+) create mode 100644 SpotifyShuffle.sln create mode 100644 SpotifyShuffle/Program.cs create mode 100644 SpotifyShuffle/SpotifyShuffle.csproj diff --git a/SpotifyShuffle.sln b/SpotifyShuffle.sln new file mode 100644 index 0000000..a9ba2b6 --- /dev/null +++ b/SpotifyShuffle.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpotifyShuffle", "SpotifyShuffle\SpotifyShuffle.csproj", "{440F6E9D-3D40-44BB-BC2C-580E16CE27B7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {440F6E9D-3D40-44BB-BC2C-580E16CE27B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {440F6E9D-3D40-44BB-BC2C-580E16CE27B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {440F6E9D-3D40-44BB-BC2C-580E16CE27B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {440F6E9D-3D40-44BB-BC2C-580E16CE27B7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {27410299-8DA9-4330-A0D3-6AE979686FD5} + EndGlobalSection +EndGlobal diff --git a/SpotifyShuffle/Program.cs b/SpotifyShuffle/Program.cs new file mode 100644 index 0000000..b632622 --- /dev/null +++ b/SpotifyShuffle/Program.cs @@ -0,0 +1,326 @@ +using SpotifyAPI.Web; +using SpotifyAPI.Web.Auth; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using static SpotifyAPI.Web.PlaylistRemoveItemsRequest; + +namespace SpotifyShuffle +{ + /// + /// A program to shuffle an entire Spotify playlist instead of using shuffle play. + /// + class Program + { + static void Main(string[] args) + { + Console.WriteLine("# Spotify Shuffle\nBecause Spotify likes to repeat songs on shuffle play...\n\n"); + new Program().AuthoriseAsync().GetAwaiter().GetResult(); + } + + private static readonly string CLIENT_ID = "9ffc2360c76f49cd8d7e0b2ac115a18f"; + private static SpotifyClient client; + private static EmbedIOAuthServer server; + + /// + /// Creates the server and handles the authorisation request + /// + private async Task AuthoriseAsync() + { + server = new EmbedIOAuthServer(new Uri("http://localhost:8888/callback"), 8888); + await server.Start(); + server.ImplictGrantReceived += OnImplicitGrantReceivedAsync; + + var request = new LoginRequest(server.BaseUri, CLIENT_ID, LoginRequest.ResponseType.Token) + { + Scope = new List { Scopes.PlaylistModifyPrivate, Scopes.PlaylistModifyPublic, Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative } + }; + BrowserUtil.Open(request.ToUri()); + + await Task.Delay(-1); // prevents the program from closing + } + + /// + /// Called when the user has been authorised + /// + private async Task OnImplicitGrantReceivedAsync(object sender, ImplictGrantResponse response) + { + await server.Stop(); + client = new SpotifyClient(response.AccessToken); + string user = (await client.UserProfile.Current()).Id; + + if (!String.IsNullOrEmpty(user) && !String.IsNullOrWhiteSpace(user)) + { + Paging playlists = await client.Playlists.GetUsers(user); + + if (playlists != null && playlists.Items != null) + { + ListPlaylists(user, playlists); + + try + { + Console.WriteLine("\nEnter ID of playlist to shuffle: "); + int playlistId = Convert.ToInt32(Console.ReadLine()); + Console.Clear(); + if (playlistId >= 0 && playlistId < playlists.Items.Count) + { + Log(LogType.Info, "Shuffle", "Shuffling, this may take a moment..."); + + string playlistUri = playlists.Items[playlistId].Uri.Split(':')[2]; + List> allTracks = new List>(); + List songs = new List(); + List songsToRemove = new List(); + int loops = (int)playlists.Items[playlistId].Tracks.Total / 100; + int remainder = (int)playlists.Items[playlistId].Tracks.Total % 100; + + await GetAllTracks(playlistUri, allTracks, loops); + int tracks = PopulateSongLists(allTracks, songs, songsToRemove); + + loops = tracks / 100; + remainder = tracks % 100; + Log(LogType.Info, "Shuffle", $"Tracks: {tracks}, Loops: {loops}, Remainder: {remainder}"); + + Log(LogType.Info, "Shuffle", "Shuffling the list..."); + List shuffled = Shuffle(songs); + if (shuffled.Count != songsToRemove.Count) throw new Exception($"For some reason there are not the same amount of songs in each list... Shuffled: {shuffled.Count}, Original: {songsToRemove.Count}"); + + await RemoveSongsFromPlaylist(playlistUri, songsToRemove, loops); + await Task.Delay(100); + await AddSongsToPlaylist(playlistUri, shuffled, loops); + + Log(LogType.Info, "Shuffle", "Playlist shuffle complete."); + } + } + catch (APIException apiEx) + { + Log(LogType.Error, apiEx.Response.StatusCode.ToString(), apiEx.Message); + } + catch (Exception ex) + { + Log(LogType.Error, ex.Source, ex.Message); + } + } + else + { + Log(LogType.Error, "Playlist", "No playlists found"); + } + } + else + { + Log(LogType.Error, "Playlist", "Invalid playlist ID"); + } + + + Console.WriteLine("\n\nPress any key to exit..."); + Console.ReadKey(); + Environment.Exit(0); + } + + /// + /// Gets the full list of tracks from a playlist + /// + /// Playlist to retreive the songs from + /// List to add the songs to + /// How many loops are needed to complete the task + private async Task GetAllTracks(string playlistUri, List> allTracks, int loops) + { + for (int i = 0; i <= loops; i++) + { + var toAdd = await client.Playlists.GetItems(playlistUri, new PlaylistGetItemsRequest() { Offset = i * 100 }); + allTracks.AddRange(toAdd.Items); + } + } + + /// + /// Lists the playlist to the user so they can select which playlist to shuffle + /// + /// The users id + /// The list of playlists + private void ListPlaylists(string user, Paging playlists) + { + Console.Clear(); + Console.WriteLine($"# List of Playlists [{user}]\n"); + for (int i = 0; i < playlists.Items.Count; i++) + { + if (playlists.Items[i].Tracks != null) + { + Console.WriteLine($"[ID: {i,3} ] {playlists.Items[i].Name} ({playlists.Items[i].Tracks.Total} tracks)"); + } + else Console.WriteLine(playlists.Items[i].Name + " [INVALID]"); + } + } + + /// + /// Populate the songs and songsToRemove lists using allTracks + /// + /// The full list of tracks to add from + /// The playlists uri list + /// The list of songs to remove + /// + private int PopulateSongLists(List> allTracks, List songs, List songsToRemove) + { + int tracks = 0; + + for (int i = allTracks.Count - 1; i >= 0; i--) + { + PlaylistTrack track = allTracks[i]; + if (track.Track != null) + { + bool local = false; + string uri = String.Empty; + + switch (track.Track.Type) + { + case ItemType.Track: + FullTrack t = (track.Track as FullTrack); + if (t.Uri.ToLower().Contains("local")) local = true; + else uri = t.Uri; + break; + case ItemType.Episode: + FullEpisode e = (track.Track as FullEpisode); + if (e.Uri.ToLower().Contains("local")) local = true; + else uri = e.Uri; + break; + } + if (!local) + { + songs.Add(new PlaylistRemoveItemsRequest.Item() { Uri = uri }); + songsToRemove.Add(new PlaylistRemoveItemsRequest.Item() { Uri = uri }); + tracks++; + } + else + { + Log(LogType.Warning, "Shuffle", "Found a local song. Skipping..."); + } + } + else Log(LogType.Warning, "Shuffle", "Found an unavailable song. Skipping..."); + } + + return tracks; + } + + /// + /// Shuffle the list of songs into a list of Uris + /// + /// List of songs to shuffle + /// List of strings representing the songs Uris + private List Shuffle(List songs) + { + List shuffled = new List(); + + Random rnd = new Random(); + + while (songs.Count > 0) + { + int i = rnd.Next(0, songs.Count); + shuffled.Add(songs[i].Uri); + songs.RemoveAt(i); + } + + return shuffled; + } + + /// + /// Remove the songs from the playlist + /// + /// The playlist to remove from + /// The songs to remove + /// How many loops of 100 this will take + private async Task RemoveSongsFromPlaylist(string playlistUri, List songsToRemove, int loops) + { + Log(LogType.Info, "Shuffle", "Removing songs from playlist..."); + for (int i = 0; i <= loops; i++) + { + if (i == loops) + { + if (songsToRemove.Count > 0) + { + var removeRequest = new PlaylistRemoveItemsRequest(); + removeRequest.Tracks = songsToRemove; + await client.Playlists.RemoveItems(playlistUri, removeRequest); + Log(LogType.Info, "Shuffle", $"Removed {songsToRemove.Count} songs"); + } + } + else + { + List songsToRemoveThisLoop = songsToRemove.GetRange(0, 100); + songsToRemove.RemoveRange(0, 100); + + var removeRequest = new PlaylistRemoveItemsRequest(); + removeRequest.Tracks = songsToRemoveThisLoop; + await client.Playlists.RemoveItems(playlistUri, removeRequest); + Log(LogType.Info, "Shuffle", "Removed 100 songs"); + } + await Task.Delay(50); + } + } + + /// + /// Add songs back to the playlist + /// + /// The playlist to add the songs to + /// The songs to add + /// How many loops of 100 this will take + private async Task AddSongsToPlaylist(string playlistUri, List songsToAdd, int loops) + { + Log(LogType.Info, "Shuffle", "Adding songs back in shuffled order..."); + for (int i = 0; i <= loops; i++) + { + if (i == loops) + { + if (songsToAdd.Count > 0) + { + var addRequest = new PlaylistAddItemsRequest(songsToAdd); + await client.Playlists.AddItems(playlistUri, addRequest); + Log(LogType.Info, "Shuffle", $"Added back {songsToAdd.Count} songs"); + } + } + else + { + List songsToAddThisLoop = songsToAdd.GetRange(0, 100); + songsToAdd.RemoveRange(0, 100); + + var addRequest = new PlaylistAddItemsRequest(songsToAddThisLoop); + await client.Playlists.AddItems(playlistUri, addRequest); + Log(LogType.Info, "Shuffle", "Added back 100 songs"); + } + await Task.Delay(50); + } + } + + /// + /// Create a console log + /// + /// Type of console message + /// The source of the message + /// The message + private void Log(LogType logType, string source, string message) + { + DateTime time = DateTime.Now; + + switch (logType) + { + case LogType.Error: + Console.ForegroundColor = ConsoleColor.Red; + break; + case LogType.Warning: + Console.ForegroundColor = ConsoleColor.Yellow; + break; + case LogType.Info: + Console.ForegroundColor = ConsoleColor.Gray; + break; + } + + Console.WriteLine($"{time} - [{logType.ToString(), 8}] {source, 15}: {message}"); + Console.ResetColor(); + } + } + + /// + /// The different log types + /// + public enum LogType + { + Warning, Error, Info + } +} diff --git a/SpotifyShuffle/SpotifyShuffle.csproj b/SpotifyShuffle/SpotifyShuffle.csproj new file mode 100644 index 0000000..928adf8 --- /dev/null +++ b/SpotifyShuffle/SpotifyShuffle.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp3.1 + + + + + + + +