spotifyexporter 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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