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 +4 -4
- data/Gemfile.lock +23 -10
- data/lib/spotifyexporter/auth.rb +2 -3
- data/lib/spotifyexporter/cli.rb +1 -1
- data/lib/spotifyexporter/command/playlists.rb +126 -18
- data/lib/spotifyexporter/config.rb +6 -6
- data/lib/spotifyexporter/lib/injectable.rb +2 -0
- data/lib/spotifyexporter/lib/spotify.rb +12 -10
- data/lib/spotifyexporter/version.rb +1 -1
- data/spotifyexporter.gemspec +2 -2
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3eaeddae7d738d0b86f4b4d90a25f21e3e90ec2cf29c344f4251cda5c39ee905
|
4
|
+
data.tar.gz: d2e4ae9c034968539b29c48a400e26ca7fec3cbc73d736027874dd70140ce617
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
5
|
-
|
6
|
-
rspotify (~> 2.
|
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.
|
46
|
+
omniauth (2.1.0)
|
41
47
|
hashie (>= 3.4.6)
|
42
|
-
rack (>=
|
43
|
-
|
44
|
-
|
45
|
-
|
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.
|
65
|
-
|
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)
|
data/lib/spotifyexporter/auth.rb
CHANGED
data/lib/spotifyexporter/cli.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "thor"
|
2
4
|
require "fileutils"
|
3
|
-
|
4
|
-
require_relative
|
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 :
|
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
|
-
|
40
|
+
spotify_playlists = spotify.get_user_playlists(options.user)
|
35
41
|
|
36
|
-
# For each playlist
|
37
|
-
|
42
|
+
# For each playlist
|
43
|
+
spotify_playlists.each do |spotify_pl|
|
38
44
|
# Remove non-alphanumeric characters
|
39
|
-
|
40
|
-
|
45
|
+
# Replace all spaces with underscores
|
46
|
+
sanitized_name = spotify_pl.name.gsub(/[^[:alnum:]^[:space:]]/, "").chomp
|
41
47
|
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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,26 +1,28 @@
|
|
1
|
-
|
2
|
-
|
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?
|
37
|
+
while (last_pull_count % 50).zero?
|
36
38
|
p = u.playlists(limit: 50, offset: offset)
|
37
39
|
|
38
40
|
playlists << p
|
data/spotifyexporter.gemspec
CHANGED
@@ -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 "
|
33
|
-
spec.add_dependency "
|
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.
|
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-
|
11
|
+
date: 2022-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: playlist
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
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.
|
26
|
+
version: 1.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspotify
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.
|
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.
|
40
|
+
version: 2.11.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: thor
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|