spotifyexporter 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90f4fca8c67b5e0521ccefa47c8745cf58779b50dcdf5a7ceac73b12a939e354
4
- data.tar.gz: 53e47966eacae888046c5f84115b78c2e77759a343b5b48bd0db56a72698b4c8
3
+ metadata.gz: 3eaeddae7d738d0b86f4b4d90a25f21e3e90ec2cf29c344f4251cda5c39ee905
4
+ data.tar.gz: d2e4ae9c034968539b29c48a400e26ca7fec3cbc73d736027874dd70140ce617
5
5
  SHA512:
6
- metadata.gz: b616ff7bbabdc1eac578d9ae0707c405e42625a3b3b4b3ebb76413c8ff7101f9e95212cfac4f2571747ed40e219ed53ad01f6c7e5476e8342549ef0b79ae0e3e
7
- data.tar.gz: ce35290bf8c7ec6fa6135100126893b3ae91112591dec12d58ba47c6e308439830af2e0328b2a17a451ebf2627753c60ab643e931c0060d9d27623615b6a370b
6
+ metadata.gz: 2354553f5a001a04cc2f47f1b1e58716eb08bfa3034abacd18f6e8377ddaeb8eb5422342f78d8a1b01025b3489b5a39efe82be130f2df0c29b240b148a9a7459
7
+ data.tar.gz: c36f7a0602ab953982e3212ac0704374ab224a8772997b5ff4e11f7e0a4008710f1d119ff04ee6d4f11f76c1ef3e4f76149c79d88bfb7ad384e73ef6d5508b39
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- spotifyexporter (0.1.0)
5
- addressable (~> 2.8.0)
6
- rspotify (~> 2.1.1)
4
+ spotifyexporter (0.2.0)
5
+ playlist (~> 1.2.0)
6
+ rspotify (~> 2.11.1)
7
7
  thor (~> 1.2.1)
8
8
  tty-prompt (~> 0.23.1)
9
9
 
@@ -28,31 +28,43 @@ GEM
28
28
  mime-types (3.4.1)
29
29
  mime-types-data (~> 3.2015)
30
30
  mime-types-data (3.2022.0105)
31
+ mini_portile2 (2.8.0)
31
32
  multi_json (1.15.0)
32
33
  multi_xml (0.6.0)
33
34
  netrc (0.11.0)
35
+ nokogiri (1.13.6)
36
+ mini_portile2 (~> 2.8.0)
37
+ racc (~> 1.4)
38
+ nokogiri (1.13.6-arm64-darwin)
39
+ racc (~> 1.4)
34
40
  oauth2 (1.4.9)
35
41
  faraday (>= 0.17.3, < 3.0)
36
42
  jwt (>= 1.0, < 3.0)
37
43
  multi_json (~> 1.3)
38
44
  multi_xml (~> 0.5)
39
45
  rack (>= 1.2, < 3)
40
- omniauth (1.9.1)
46
+ omniauth (2.1.0)
41
47
  hashie (>= 3.4.6)
42
- rack (>= 1.6.2, < 3)
43
- omniauth-oauth2 (1.3.1)
44
- oauth2 (~> 1.0)
45
- omniauth (~> 1.2)
48
+ rack (>= 2.2.3)
49
+ rack-protection
50
+ omniauth-oauth2 (1.7.2)
51
+ oauth2 (~> 1.4)
52
+ omniauth (>= 1.9, < 3)
46
53
  parallel (1.22.1)
47
54
  parser (3.1.2.0)
48
55
  ast (~> 2.4.1)
49
56
  pastel (0.8.0)
50
57
  tty-color (~> 0.5)
58
+ playlist (1.2.0)
59
+ nokogiri
51
60
  pry (0.14.1)
52
61
  coderay (~> 1.1)
53
62
  method_source (~> 1.0)
54
63
  public_suffix (4.0.7)
64
+ racc (1.6.0)
55
65
  rack (2.2.3)
66
+ rack-protection (2.2.0)
67
+ rack
56
68
  rainbow (3.1.1)
57
69
  rake (13.0.6)
58
70
  regexp_parser (2.4.0)
@@ -61,8 +73,9 @@ GEM
61
73
  mime-types (>= 1.16, < 4.0)
62
74
  netrc (~> 0.8)
63
75
  rexml (3.2.5)
64
- rspotify (2.1.1)
65
- omniauth-oauth2 (~> 1.3.1)
76
+ rspotify (2.11.1)
77
+ addressable (~> 2.8.0)
78
+ omniauth-oauth2 (>= 1.6)
66
79
  rest-client (~> 2.0.2)
67
80
  rubocop (1.29.1)
68
81
  parallel (~> 1.10)
@@ -5,9 +5,8 @@ require_relative "./config"
5
5
 
6
6
  module SpotifyExporter
7
7
  class AuthWorkflow
8
-
9
- #
10
- # Starts auth flow
8
+ #
9
+ # Starts auth flow
11
10
  #
12
11
  def start
13
12
  auth = prompt_for_auth
@@ -32,6 +32,6 @@ module SpotifyExporter
32
32
  end
33
33
 
34
34
  desc "playlists", "List playlists"
35
- subcommand 'playlists', Playlists
35
+ subcommand "playlists", Playlists
36
36
  end
37
37
  end
@@ -1,19 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thor"
2
4
  require "fileutils"
3
- require_relative '../config.rb'
4
- require_relative '../lib/spotify.rb'
5
+ require "playlist"
6
+ require_relative "../config"
7
+ require_relative "../lib/spotify"
5
8
 
6
9
  module SpotifyExporter
7
10
  class Playlists < Thor
8
11
  include ConfigDependant
9
12
 
13
+ SUPPORTED_PLAYLIST_FORMATS = %w[m3u pls xspf txt yml].freeze
14
+
10
15
  desc "ls", "Lists playlist"
11
16
  option :user, required: true, aliases: "-u"
12
17
  option :url, required: false, aliases: "-l"
13
- def ls
18
+ def ls
14
19
  pl = spotify.get_user_playlists(options.user)
15
20
 
16
- results = pl.map do |p|
21
+ results = pl.map do |p|
17
22
  r = p.name
18
23
 
19
24
  r << " | #{p.external_urls["spotify"]}" unless options.url.nil?
@@ -27,35 +32,138 @@ module SpotifyExporter
27
32
  desc "export", "Exports playlists"
28
33
  option :user, required: true, aliases: "-u"
29
34
  option :output_directory, required: true, aliases: "-o"
30
- option :all, required: false, aliases: "-a"
31
- def export
35
+ option :format, required: false, aliases: "-f", default: "txt"
36
+ def export
32
37
  FileUtils.mkdir_p(options.output_directory) unless Dir.exist? options.output_directory
38
+ validate_format!(options.format)
33
39
 
34
- pl = spotify.get_user_playlists(options.user)
40
+ spotify_playlists = spotify.get_user_playlists(options.user)
35
41
 
36
- # For each playlist
37
- pl.each do |p|
42
+ # For each playlist
43
+ spotify_playlists.each do |spotify_pl|
38
44
  # Remove non-alphanumeric characters
39
- sanitized_name = p.name.gsub(/[^[:alnum:]]/, "")
40
- playlist_file_name = File.join(options.output_directory, sanitized_name)
45
+ # Replace all spaces with underscores
46
+ sanitized_name = spotify_pl.name.gsub(/[^[:alnum:]^[:space:]]/, "").chomp
41
47
 
42
- # Create a file for the playlist
43
- File.open(playlist_file_name, "w") do |f|
48
+ playlist = Playlist.new(
49
+ title: sanitized_name,
50
+ description: spotify_pl.description,
51
+ creator: options.user,
52
+ # Use the first image
53
+ image: spotify_pl.images.first["url"]
54
+ )
44
55
 
45
- # Write the name of each artist and song
46
- p.tracks.each do |t|
47
- artists = t.artists.map(&:name).join(", ")
56
+ # For each track in the Spotify playlist,
57
+ # create a new playlist track with title and contributors, then
58
+ # add it to the playlist.
59
+ spotify_pl.tracks.each do |spotify_track|
60
+ track = Playlist::Track.new(
61
+ title: spotify_track.name,
62
+ duration: spotify_track.duration_ms
63
+ )
48
64
 
49
- f.puts "#{artists} - #{t.name}"
65
+ spotify_track.artists.each do |artist|
66
+ track.add_contributor(name: artist.name)
50
67
  end
68
+
69
+ playlist.add_track(track)
51
70
  end
71
+
72
+ # Write the playlist to disk
73
+ save_playlist(playlist, options.format)
52
74
  end
53
75
  end
54
76
 
55
77
  private
78
+
79
+ #
80
+ # Saves the playlist in the requested format to disk
81
+ #
82
+ # @param playlist [Playlist]
83
+ # @param format [String]
84
+ #
85
+ def save_playlist(playlist, format)
86
+ # Sanitize the name for file system, remove spaces and
87
+ # replace with underscores
88
+ fname = playlist.title.gsub(/\s/, "_")
89
+
90
+ suffix = format
91
+ playlist_file_name = File.join(options.output_directory, "#{fname}.#{suffix}")
92
+
93
+ # Create a file for the playlist
94
+ File.open(playlist_file_name, "wb") do |f|
95
+ # Use the provided format to generate a playlist
96
+
97
+ generated_playlist_blob = case format.to_sym
98
+ when :m3u
99
+ Playlist::Format::M3U.generate(playlist)
100
+ when :pls
101
+ Playlist::Format::PLS.generate(playlist)
102
+ when :xspf
103
+ Playlist::Format::XSPF.generate(playlist)
104
+ when :yml
105
+ generate_yml_playlist(playlist)
106
+ else
107
+ # Use default generation
108
+ generate_txt_playlist(playlist)
109
+ end
110
+
111
+ f.write generated_playlist_blob
112
+ end
113
+ end
114
+
115
+ #
116
+ # Creates a txt playlist blob
117
+ #
118
+ def generate_txt_playlist(playlist)
119
+ <<~EOF
120
+ Title: #{playlist.title}
121
+ Creator: #{playlist.creator}
122
+ Description: #{playlist.description}
123
+ Image: #{playlist.image}
124
+
125
+ ---
126
+
127
+ #{playlist.tracks.map { |t| "#{t.artist} - #{t.title}" }.join("\n")}
128
+
129
+ EOF
130
+ end
131
+
132
+ #
133
+ # Creates a yaml playlist blob
134
+ #
135
+ # @param playlist [Playlist]
136
+ # @return [YAML]
137
+ #
138
+ def generate_yml_playlist(playlist)
139
+ tracks = playlist.tracks.map do |t|
140
+ { artist: t.artist, title: t.title }
141
+ end
142
+
143
+ {
144
+ title: playlist.title,
145
+ creator: playlist.creator,
146
+ description: playlist.description,
147
+ image: playlist.image,
148
+ tracks: tracks,
149
+ }.to_yaml
150
+ end
151
+
152
+ #
153
+ # Raises an error if invalid format supplied
154
+ #
155
+ # @raise ArgumentError
156
+ #
157
+ def validate_format!(format)
158
+ err = "invalid playlist format: #{format}. Must be one of: #{SUPPORTED_PLAYLIST_FORMATS.join(", ")}"
159
+
160
+ raise ArgumentError, err unless SUPPORTED_PLAYLIST_FORMATS.include?(format)
161
+ end
162
+
56
163
  #
57
- # SpotifyClient
164
+ # Client for Spotify Developer API
58
165
  #
166
+ # @return [SpotifyClient]
59
167
  def spotify
60
168
  return @spotify unless @spotify.nil?
61
169
 
@@ -4,13 +4,13 @@ require "fileutils"
4
4
  require "yaml"
5
5
  require "singleton"
6
6
  require "pry"
7
- require_relative './lib/injectable.rb'
7
+ require_relative "./lib/injectable"
8
8
 
9
9
  module SpotifyExporter
10
10
  module ConfigDependant
11
11
  include Injectable
12
12
 
13
- inject :config, -> { ConfigManager.instance.config }
13
+ inject :config, -> { ConfigManager.instance.config }
14
14
  end
15
15
 
16
16
  class AppConfig
@@ -49,7 +49,7 @@ module SpotifyExporter
49
49
 
50
50
  FILE_CLI_CONFIG = "config.yml"
51
51
 
52
- #
52
+ #
53
53
  # Loads and saves the credentials to the main configuration
54
54
  #
55
55
  # @return [Boolean]
@@ -63,7 +63,7 @@ module SpotifyExporter
63
63
  save_config!
64
64
  end
65
65
 
66
- #
66
+ #
67
67
  # Retrieves the AppConfig
68
68
  #
69
69
  # @return [AppConfig]
@@ -78,7 +78,7 @@ module SpotifyExporter
78
78
 
79
79
  #
80
80
  # Saves the currently in-memory configuration
81
- #
81
+ #
82
82
  # @return success [Boolean]
83
83
  #
84
84
  def save_config!
@@ -102,7 +102,7 @@ module SpotifyExporter
102
102
  end
103
103
 
104
104
  #
105
- # Reads the user file system configurations into the in-memory AppConfig
105
+ # Reads the user file system configurations into the in-memory AppConfig
106
106
  #
107
107
  def load_config_file
108
108
  initialize_config_file unless config_file_exists?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SpotifyExporter
2
4
  module Injectable
3
5
  def self.included(base)
@@ -1,26 +1,28 @@
1
- require 'rspotify'
2
- require 'addressable'
1
+ # frozen_string_literal: true
2
+
3
+ require "rspotify"
3
4
 
4
5
  class SpotifyClient
5
6
  def initialize(conf)
6
7
  RSpotify.authenticate(
7
- conf.spotify_client_id,
8
- conf.spotify_client_secret
8
+ conf.spotify_client_id,
9
+ conf.spotify_client_secret
9
10
  )
10
11
  end
11
- #
12
+
13
+ #
12
14
  # GETs a user
13
15
  #
14
- # @param uname [String]
16
+ # @param uname [String]
15
17
  #
16
18
  def get_user(uname)
17
19
  RSpotify::User.find(uname)
18
20
  end
19
21
 
20
- #
22
+ #
21
23
  # GETs all playlists for a user
22
24
  #
23
- # @param uname [String]
25
+ # @param uname [String]
24
26
  #
25
27
  def get_user_playlists(uname)
26
28
  u = get_user(uname)
@@ -28,11 +30,11 @@ class SpotifyClient
28
30
  playlists = []
29
31
  offset = 0
30
32
  last_pull_count = 0
31
-
33
+
32
34
  # You can only pull from API in increments of 50
33
35
  # if the last pull count is a multiple of 50
34
36
  # we know we can keep pulling, if not, stop
35
- while (last_pull_count % 50).zero? do
37
+ while (last_pull_count % 50).zero?
36
38
  p = u.playlists(limit: 50, offset: offset)
37
39
 
38
40
  playlists << p
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpotifyExporter
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
- spec.add_dependency "rspotify", "~> 2.1.1"
33
- spec.add_dependency "addressable", "~> 2.8.0"
32
+ spec.add_dependency "playlist", "~> 1.2.0"
33
+ spec.add_dependency "rspotify", "~> 2.11.1"
34
34
  spec.add_dependency "thor", "~> 1.2.1"
35
35
  spec.add_dependency "tty-prompt", "~> 0.23.1"
36
36
 
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spotifyexporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aquaflamingo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-21 00:00:00.000000000 Z
11
+ date: 2022-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspotify
14
+ name: playlist
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.1.1
19
+ version: 1.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.1.1
26
+ version: 1.2.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: addressable
28
+ name: rspotify
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.8.0
33
+ version: 2.11.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 2.8.0
40
+ version: 2.11.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: thor
43
43
  requirement: !ruby/object:Gem::Requirement