audio_addict 0.1.10 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/bin/radio +1 -0
- data/lib/audio_addict.rb +8 -8
- data/lib/audio_addict/api.rb +13 -13
- data/lib/audio_addict/auto_properties.rb +1 -1
- data/lib/audio_addict/cache.rb +2 -4
- data/lib/audio_addict/channel.rb +13 -13
- data/lib/audio_addict/cli.rb +11 -11
- data/lib/audio_addict/commands/api.rb +8 -9
- data/lib/audio_addict/commands/base.rb +4 -6
- data/lib/audio_addict/commands/channels.rb +5 -6
- data/lib/audio_addict/commands/config.rb +14 -15
- data/lib/audio_addict/commands/download.rb +70 -0
- data/lib/audio_addict/commands/history.rb +4 -4
- data/lib/audio_addict/commands/log.rb +10 -11
- data/lib/audio_addict/commands/login.rb +4 -5
- data/lib/audio_addict/commands/now.rb +6 -7
- data/lib/audio_addict/commands/playlist.rb +9 -13
- data/lib/audio_addict/commands/set.rb +7 -13
- data/lib/audio_addict/commands/vote.rb +9 -10
- data/lib/audio_addict/config.rb +3 -4
- data/lib/audio_addict/exceptions.rb +6 -7
- data/lib/audio_addict/extensions/file.rb +2 -2
- data/lib/audio_addict/inspectable.rb +1 -1
- data/lib/audio_addict/log.rb +2 -3
- data/lib/audio_addict/radio.rb +13 -6
- data/lib/audio_addict/track.rb +7 -3
- data/lib/audio_addict/version.rb +2 -2
- data/lib/audio_addict/youtube.rb +41 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 426a2b37a0f6f0d2f7a0bc151aeb481630ea6c2449065e1d154e87d39af3af36
|
4
|
+
data.tar.gz: 42ef3f68930178372a1bbcc2c9694bce20da72ee65672421c14b672821afa24d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 656a86ab1ff70b214af607befecf191bba4de6fc33e74f8c674c938f8732525e9cba98827962f1a86c0fb0ede9340e9b8e78f3f546dc367f6a89d2594f51b9e8
|
7
|
+
data.tar.gz: 33a50eac62b23ad09ed0cf8e5a5d6afa93a082765f44a377050d8105d50501c39788ae7e28b1e7963132589df31ac5539e0f5e7618a207ea30becd35a2a3a09a
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@ AudioAddict Command Line
|
|
2
2
|
==================================================
|
3
3
|
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/audio_addict.svg)](https://badge.fury.io/rb/audio_addict)
|
5
|
-
[![Build Status](https://
|
5
|
+
[![Build Status](https://github.com/DannyBen/audio_addict/workflows/Test/badge.svg)](https://github.com/DannyBen/audio_addict/actions?query=workflow%3ATest)
|
6
6
|
[![Maintainability](https://api.codeclimate.com/v1/badges/91e1a8251b771881bf6b/maintainability)](https://codeclimate.com/github/DannyBen/audio_addict/maintainability)
|
7
7
|
|
8
8
|
---
|
@@ -41,11 +41,13 @@ Features
|
|
41
41
|
- [RadioTunes]
|
42
42
|
- [JazzRadio]
|
43
43
|
- [ClassicalRadio]
|
44
|
+
- [ZenRadio]
|
44
45
|
- View list of channels
|
45
46
|
- View currently playing track
|
46
47
|
- Vote on the currently playing track
|
47
48
|
- Save a log of a all your liked tracks
|
48
49
|
- Generate playlists (requires a premium account)
|
50
|
+
- Download songs from Youtube (using youtube-dl)
|
49
51
|
|
50
52
|
|
51
53
|
Usage
|
@@ -68,6 +70,7 @@ Commands:
|
|
68
70
|
playlist Generate playlists
|
69
71
|
config Manage local configuration
|
70
72
|
log Manage local like log
|
73
|
+
download Download songs from YouTube
|
71
74
|
api Make direct calls to the AudioAddict API
|
72
75
|
|
73
76
|
```
|
@@ -80,3 +83,4 @@ Commands:
|
|
80
83
|
[RadioTunes]: http://www.radiotunes.com
|
81
84
|
[JazzRadio]: http://www.jazzradio.com
|
82
85
|
[ClassicalRadio]: http://www.classicalradio.com
|
86
|
+
[ZenRadio]: http://www.zenradio.com
|
data/bin/radio
CHANGED
data/lib/audio_addict.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "requires"
|
2
|
+
require "byebug" if ENV["BYEBUG"]
|
3
3
|
requires \
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
"audio_addict/exceptions",
|
5
|
+
"audio_addict/cache",
|
6
|
+
"audio_addict/inspectable",
|
7
|
+
"audio_addict/auto_properties",
|
8
|
+
"audio_addict/commands/base",
|
9
|
+
"audio_addict"
|
data/lib/audio_addict/api.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
require
|
1
|
+
require "httparty"
|
2
2
|
|
3
3
|
module AudioAddict
|
4
4
|
class API
|
5
5
|
include HTTParty
|
6
6
|
include Cache
|
7
7
|
|
8
|
-
base_uri
|
8
|
+
base_uri "https://api.audioaddict.com/v1"
|
9
|
+
debug_output $stderr if ENV['AUDIO_ADDICT_DEBUG']
|
9
10
|
|
10
11
|
attr_accessor :network
|
11
12
|
|
@@ -15,22 +16,22 @@ module AudioAddict
|
|
15
16
|
|
16
17
|
def login(username, password)
|
17
18
|
session = session(username, password)
|
18
|
-
Config.session_key = session[
|
19
|
-
Config.listen_key = session[
|
20
|
-
Config.email = session[
|
21
|
-
Config.premium = session[
|
19
|
+
Config.session_key = session["key"]
|
20
|
+
Config.listen_key = session["member"]["listen_key"]
|
21
|
+
Config.email = session["member"]["email"]
|
22
|
+
Config.premium = session["member"]["user_type"] == "premium"
|
22
23
|
Config.save
|
23
24
|
end
|
24
25
|
|
25
|
-
def get(path, args={})
|
26
|
+
def get(path, args = {})
|
26
27
|
response http.get "/#{network}/#{path}", headers: headers, body: args
|
27
28
|
end
|
28
29
|
|
29
|
-
def post(path, args={})
|
30
|
+
def post(path, args = {})
|
30
31
|
response http.post "/#{network}/#{path}", headers: headers, body: args
|
31
32
|
end
|
32
33
|
|
33
|
-
def delete(path, args={})
|
34
|
+
def delete(path, args = {})
|
34
35
|
response http.delete "/#{network}/#{path}", headers: headers, body: args
|
35
36
|
end
|
36
37
|
|
@@ -39,13 +40,13 @@ module AudioAddict
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def basic_auth
|
42
|
-
http.basic_auth
|
43
|
+
http.basic_auth "streams", "diradio"
|
43
44
|
end
|
44
45
|
|
45
46
|
def session(username, password)
|
46
47
|
params = { member_session: { username: username, password: password } }
|
47
48
|
basic_auth
|
48
|
-
response http.post "/#{network}/member_sessions", body: params
|
49
|
+
response http.post "/#{network || "di"}/member_sessions", body: params
|
49
50
|
end
|
50
51
|
|
51
52
|
def session_key
|
@@ -56,7 +57,7 @@ module AudioAddict
|
|
56
57
|
Config.listen_key
|
57
58
|
end
|
58
59
|
|
59
|
-
|
60
|
+
private
|
60
61
|
|
61
62
|
def response(httparty_response)
|
62
63
|
raise APIError.new httparty_response unless httparty_response.success?
|
@@ -70,6 +71,5 @@ module AudioAddict
|
|
70
71
|
def headers
|
71
72
|
{ "X-Session-Key" => session_key }
|
72
73
|
end
|
73
|
-
|
74
74
|
end
|
75
75
|
end
|
data/lib/audio_addict/cache.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "lightly"
|
2
2
|
|
3
3
|
module AudioAddict
|
4
4
|
module Cache
|
5
|
-
|
6
5
|
def cache
|
7
6
|
@cache ||= Lightly.new life: cache_life, dir: cache_dir
|
8
7
|
end
|
@@ -12,7 +11,7 @@ module AudioAddict
|
|
12
11
|
end
|
13
12
|
|
14
13
|
def cache_life!
|
15
|
-
Config.cache_life ||
|
14
|
+
Config.cache_life || "6h"
|
16
15
|
end
|
17
16
|
|
18
17
|
def cache_dir
|
@@ -22,6 +21,5 @@ module AudioAddict
|
|
22
21
|
def cache_dir!
|
23
22
|
Config.cache_dir || "#{Dir.home}/.audio_addict/cache"
|
24
23
|
end
|
25
|
-
|
26
24
|
end
|
27
25
|
end
|
data/lib/audio_addict/channel.rb
CHANGED
@@ -3,11 +3,11 @@ module AudioAddict
|
|
3
3
|
include Cache
|
4
4
|
include AutoProperties
|
5
5
|
include Inspectable
|
6
|
-
|
6
|
+
|
7
7
|
attr_reader :radio
|
8
8
|
|
9
9
|
def initialize(radio, properties)
|
10
|
-
@radio, @properties
|
10
|
+
@radio, @properties = radio, properties
|
11
11
|
end
|
12
12
|
|
13
13
|
def inspectable
|
@@ -17,8 +17,8 @@ module AudioAddict
|
|
17
17
|
def active?
|
18
18
|
# Seems like each network has a different way of marking inactive channels.
|
19
19
|
# This is where we normalize it
|
20
|
-
return false if !properties[
|
21
|
-
return false if name[0] ==
|
20
|
+
return false if !properties["asset_id"]
|
21
|
+
return false if name[0] == "X" and key[0] != "x"
|
22
22
|
return true
|
23
23
|
end
|
24
24
|
|
@@ -36,15 +36,15 @@ module AudioAddict
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def similar_channels
|
39
|
-
similar = properties[
|
39
|
+
similar = properties["similar_channels"]
|
40
40
|
return [] unless similar
|
41
|
-
ids = similar.map { |s| s[
|
41
|
+
ids = similar.map { |s| s["similar_channel_id"] }
|
42
42
|
radio.search_by_id ids
|
43
43
|
end
|
44
44
|
|
45
45
|
def vote(direction = :up, track: nil)
|
46
|
-
track ||= current_track
|
47
|
-
endpoint = "tracks/#{track}/vote/#{id}"
|
46
|
+
track ||= current_track
|
47
|
+
endpoint = "tracks/#{track.id}/vote/#{id}"
|
48
48
|
|
49
49
|
if direction == :delete
|
50
50
|
radio.api.delete endpoint
|
@@ -52,16 +52,16 @@ module AudioAddict
|
|
52
52
|
radio.api.post "#{endpoint}/#{direction}"
|
53
53
|
end
|
54
54
|
|
55
|
-
log_like if direction == :up and Config.like_log
|
55
|
+
log_like track if direction == :up and Config.like_log
|
56
56
|
end
|
57
57
|
|
58
|
-
|
58
|
+
private
|
59
59
|
|
60
|
-
def log_like
|
61
|
-
|
60
|
+
def log_like(track = nil)
|
61
|
+
track ||= current_track
|
62
|
+
message = "#{radio.name} :: #{name} :: #{track.artist} :: #{track.title}"
|
62
63
|
file = Config.like_log
|
63
64
|
File.append file, message unless File.contains? file, message
|
64
65
|
end
|
65
|
-
|
66
66
|
end
|
67
67
|
end
|
data/lib/audio_addict/cli.rb
CHANGED
@@ -4,19 +4,19 @@ module AudioAddict
|
|
4
4
|
router = MisterBin::Runner.new version: VERSION,
|
5
5
|
header: "AudioAddict Radio Utilities"
|
6
6
|
|
7
|
-
router.route
|
8
|
-
router.route
|
9
|
-
router.route
|
10
|
-
router.route
|
11
|
-
router.route
|
12
|
-
router.route
|
13
|
-
router.route
|
14
|
-
router.route
|
15
|
-
router.route
|
16
|
-
router.route
|
7
|
+
router.route "login", to: Commands::LoginCmd
|
8
|
+
router.route "set", to: Commands::SetCmd
|
9
|
+
router.route "channels", to: Commands::ChannelsCmd
|
10
|
+
router.route "now", to: Commands::NowCmd
|
11
|
+
router.route "history", to: Commands::HistoryCmd
|
12
|
+
router.route "vote", to: Commands::VoteCmd
|
13
|
+
router.route "playlist", to: Commands::PlaylistCmd
|
14
|
+
router.route "config", to: Commands::ConfigCmd
|
15
|
+
router.route "log", to: Commands::LogCmd
|
16
|
+
router.route "download", to: Commands::DownloadCmd
|
17
|
+
router.route "api", to: Commands::APICmd
|
17
18
|
|
18
19
|
router
|
19
20
|
end
|
20
21
|
end
|
21
|
-
|
22
22
|
end
|
@@ -10,9 +10,9 @@ module AudioAddict
|
|
10
10
|
|
11
11
|
param "ENDPOINT", "API endpoint path"
|
12
12
|
|
13
|
-
example "radio channels"
|
14
|
-
example "radio get track_history/channel/1"
|
15
|
-
example "radio post tracks/1/vote/2/up"
|
13
|
+
example "radio api channels"
|
14
|
+
example "radio api get track_history/channel/1"
|
15
|
+
example "radio api post tracks/1/vote/2/up"
|
16
16
|
|
17
17
|
def run
|
18
18
|
needs :network
|
@@ -20,22 +20,21 @@ module AudioAddict
|
|
20
20
|
puts response.to_yaml
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
private
|
24
24
|
|
25
25
|
def api_method
|
26
|
-
return :post if args[
|
27
|
-
return :delete if args[
|
26
|
+
return :post if args["post"]
|
27
|
+
return :delete if args["delete"]
|
28
28
|
return :get
|
29
29
|
end
|
30
30
|
|
31
31
|
def endpoint
|
32
|
-
args[
|
32
|
+
args["ENDPOINT"]
|
33
33
|
end
|
34
34
|
|
35
35
|
def api
|
36
36
|
@api ||= API.new current_network
|
37
37
|
end
|
38
|
-
|
39
38
|
end
|
40
39
|
end
|
41
|
-
end
|
40
|
+
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "mister_bin"
|
2
|
+
require "colsole"
|
3
|
+
require "tty/prompt"
|
4
4
|
|
5
5
|
module AudioAddict
|
6
6
|
module Commands
|
7
7
|
class Base < MisterBin::Command
|
8
|
-
|
9
8
|
def needs(*config_keys)
|
10
9
|
missing = []
|
11
10
|
config_keys.each do |key|
|
@@ -34,7 +33,6 @@ module AudioAddict
|
|
34
33
|
def prompt
|
35
34
|
@prompt ||= TTY::Prompt.new
|
36
35
|
end
|
37
|
-
|
38
36
|
end
|
39
37
|
end
|
40
|
-
end
|
38
|
+
end
|
@@ -22,19 +22,19 @@ module AudioAddict
|
|
22
22
|
|
23
23
|
say "!undgrn!#{radio.name}\n"
|
24
24
|
|
25
|
-
search = args[
|
25
|
+
search = args["SEARCH"]
|
26
26
|
|
27
27
|
channels = search ? radio.search(search) : radio.channels
|
28
|
-
|
28
|
+
|
29
29
|
channels = channels.values
|
30
|
-
if args[
|
30
|
+
if args["--info"]
|
31
31
|
show_verbose channels
|
32
32
|
else
|
33
33
|
show_compact channels
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
|
37
|
+
private
|
38
38
|
|
39
39
|
def show_verbose(channels)
|
40
40
|
channels.each do |channel|
|
@@ -61,7 +61,6 @@ module AudioAddict
|
|
61
61
|
say "!txtblu!#{channel.key.rjust 25} !txtgrn!#{channel.name.strip}"
|
62
62
|
end
|
63
63
|
end
|
64
|
-
|
65
64
|
end
|
66
65
|
end
|
67
|
-
end
|
66
|
+
end
|
@@ -34,21 +34,21 @@ module AudioAddict
|
|
34
34
|
example "radio config get listen_key"
|
35
35
|
|
36
36
|
def get_command
|
37
|
-
key = args[
|
37
|
+
key = args["KEY"].to_sym
|
38
38
|
value = Config.properties[key]
|
39
39
|
say value ? "!txtgrn!#{value}" : "!txtred!<Unset>"
|
40
40
|
end
|
41
41
|
|
42
42
|
def set_command
|
43
|
-
key = args[
|
44
|
-
value = args[
|
43
|
+
key = args["KEY"].to_sym
|
44
|
+
value = args["VALUE"]
|
45
45
|
Config.properties[key] = value
|
46
46
|
Config.save
|
47
47
|
say "!txtgrn!#{key}=#{value}"
|
48
48
|
end
|
49
49
|
|
50
50
|
def del_command
|
51
|
-
key = args[
|
51
|
+
key = args["KEY"].to_sym
|
52
52
|
Config.delete key
|
53
53
|
Config.save
|
54
54
|
say "!txtgrn!Deleted"
|
@@ -64,7 +64,7 @@ module AudioAddict
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def edit_command
|
67
|
-
editor = ENV[
|
67
|
+
editor = ENV["EDITOR"] || "vi"
|
68
68
|
system "#{editor} #{Config.path}"
|
69
69
|
end
|
70
70
|
|
@@ -77,14 +77,14 @@ module AudioAddict
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def check_command
|
80
|
-
errors
|
80
|
+
errors = verify_and_show_keys required_keys, critical: true
|
81
81
|
warnings = verify_and_show_keys optional_keys
|
82
82
|
|
83
83
|
say "Done. #{errors} errors, #{warnings} warnings."
|
84
84
|
errors > 0 ? 1 : 0
|
85
85
|
end
|
86
86
|
|
87
|
-
|
87
|
+
private
|
88
88
|
|
89
89
|
def verify_and_show_keys(keys, critical: false)
|
90
90
|
problems = 0
|
@@ -115,20 +115,19 @@ module AudioAddict
|
|
115
115
|
|
116
116
|
def required_keys
|
117
117
|
{
|
118
|
-
email:
|
119
|
-
session_key:
|
120
|
-
listen_key:
|
121
|
-
network:
|
122
|
-
channel:
|
118
|
+
email: "login",
|
119
|
+
session_key: "login",
|
120
|
+
listen_key: "login",
|
121
|
+
network: "set",
|
122
|
+
channel: "set",
|
123
123
|
}
|
124
124
|
end
|
125
125
|
|
126
126
|
def optional_keys
|
127
127
|
{
|
128
|
-
like_log:
|
128
|
+
like_log: "config set like_log PATH",
|
129
129
|
}
|
130
130
|
end
|
131
|
-
|
132
131
|
end
|
133
132
|
end
|
134
|
-
end
|
133
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class DownloadCmd < Base
|
4
|
+
summary "Download songs from YouTube"
|
5
|
+
|
6
|
+
help "This command uses youtube-dl to download the currently playing song, or songs from your like-log."
|
7
|
+
|
8
|
+
usage "radio download current [--count N]"
|
9
|
+
usage "radio download log [--lines N --count N]"
|
10
|
+
usage "radio download search QUERY [--count N]"
|
11
|
+
usage "radio download --help"
|
12
|
+
|
13
|
+
option "-l --lines N", "Number of log lines to download [default: 1]"
|
14
|
+
option "-c --count N", "Number of YouTube search results to download [default: 1]"
|
15
|
+
|
16
|
+
param "QUERY", "YouTube search query"
|
17
|
+
|
18
|
+
command "current", "Download the currently playing song"
|
19
|
+
command "log", "Download the last N songs from the like-log"
|
20
|
+
command "search", "Download any song matching the Youtube search query"
|
21
|
+
|
22
|
+
example "radio download current"
|
23
|
+
example "radio download current --count 3"
|
24
|
+
example "radio download log --lines 2 --count 3"
|
25
|
+
example "radio download search 'Brimstone, Bright Shadow' -c2"
|
26
|
+
|
27
|
+
def current_command
|
28
|
+
needs :network, :channel
|
29
|
+
count = args['--count']
|
30
|
+
|
31
|
+
say "!txtblu!Downloading !txtrst!: ... "
|
32
|
+
|
33
|
+
track = current_channel.current_track
|
34
|
+
query = track.search_string
|
35
|
+
|
36
|
+
resay "!txtblu!Downloading !txtgrn!: #{query}"
|
37
|
+
|
38
|
+
Youtube.new(query).get count
|
39
|
+
end
|
40
|
+
|
41
|
+
def log_command
|
42
|
+
needs :like_log
|
43
|
+
count = args['--count']
|
44
|
+
lines = args['--lines']&.to_i
|
45
|
+
|
46
|
+
data = log.data[-lines..-1]
|
47
|
+
data.each do |line|
|
48
|
+
network, channel, artist, song = line.split(" :: ")
|
49
|
+
query = "#{artist}, #{song}"
|
50
|
+
say "\n!txtblu!Downloading !txtgrn!: #{query}"
|
51
|
+
Youtube.new(query).get count
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def search_command
|
56
|
+
query = args['QUERY']
|
57
|
+
count = args['--count']
|
58
|
+
|
59
|
+
say "\n!txtblu!Downloading !txtgrn!: #{query}"
|
60
|
+
Youtube.new(query).get count
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def log
|
66
|
+
@log ||= Log.new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AudioAddict
|
2
2
|
module Commands
|
3
3
|
class HistoryCmd < Base
|
4
|
-
summary "Show track history for the current channel"
|
4
|
+
summary "Show track history for the current channel"
|
5
5
|
|
6
6
|
help "This command shows the last few tracks that were playing on the currently active channel in reverse order (top track is the most recent)."
|
7
7
|
|
@@ -11,13 +11,13 @@ module AudioAddict
|
|
11
11
|
def run
|
12
12
|
needs :network, :channel
|
13
13
|
say "!undgrn!#{radio.name} > #{current_channel.name}"
|
14
|
-
say
|
14
|
+
say ""
|
15
15
|
tracks.each do |track|
|
16
16
|
say "!txtgrn! #{track.artist.rjust max_artist_len}!txtrst! : !txtblu!#{track.title}"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
private
|
21
21
|
|
22
22
|
def tracks
|
23
23
|
@tracks ||= current_channel.track_history
|
@@ -28,4 +28,4 @@ module AudioAddict
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|
@@ -32,13 +32,13 @@ module AudioAddict
|
|
32
32
|
|
33
33
|
def show_command
|
34
34
|
needs :like_log
|
35
|
-
query = args[
|
35
|
+
query = args["SEARCH"]
|
36
36
|
puts query ? log.search(query) : log.data
|
37
37
|
end
|
38
38
|
|
39
39
|
def tail_command
|
40
40
|
needs :like_log
|
41
|
-
lines = args[
|
41
|
+
lines = args["--lines"].to_i
|
42
42
|
puts log.data[-lines..-1]
|
43
43
|
end
|
44
44
|
|
@@ -52,10 +52,10 @@ module AudioAddict
|
|
52
52
|
tree = log.tree
|
53
53
|
|
54
54
|
say ""
|
55
|
-
network = prompt.select "Network:", tree.keys, marker:
|
56
|
-
channel = prompt.select "Channel:", tree[network].keys, marker:
|
57
|
-
artist
|
58
|
-
|
55
|
+
network = prompt.select "Network:", tree.keys, symbols: { marker: ">" }, filter: true, per_page: 10
|
56
|
+
channel = prompt.select "Channel:", tree[network].keys, symbols: { marker: ">" }, filter: true, per_page: page_size
|
57
|
+
artist = prompt.select "Artist:", tree[network][channel].keys, symbols: { marker: ">" }, filter: true, per_page: page_size
|
58
|
+
|
59
59
|
say "Songs:"
|
60
60
|
tree[network][channel][artist].each { |song| say "- !txtgrn!#{song}" }
|
61
61
|
say ""
|
@@ -65,8 +65,8 @@ module AudioAddict
|
|
65
65
|
|
66
66
|
def tree_command
|
67
67
|
yaml = log.tree.to_yaml
|
68
|
-
filename = args[
|
69
|
-
|
68
|
+
filename = args["--save"]
|
69
|
+
|
70
70
|
if filename
|
71
71
|
File.write filename, yaml
|
72
72
|
say "!txtgrn!Saved #{filename}"
|
@@ -75,7 +75,7 @@ module AudioAddict
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
-
|
78
|
+
private
|
79
79
|
|
80
80
|
def log
|
81
81
|
@log ||= Log.new
|
@@ -84,7 +84,6 @@ module AudioAddict
|
|
84
84
|
def page_size
|
85
85
|
@page_size ||= detect_terminal_size[1] - 4
|
86
86
|
end
|
87
|
-
|
88
87
|
end
|
89
88
|
end
|
90
|
-
end
|
89
|
+
end
|
@@ -15,16 +15,16 @@ module AudioAddict
|
|
15
15
|
say "!txtylw!You are already logged in as !undylw!#{Config.email}"
|
16
16
|
proceed = prompt.yes? "Login again?"
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
login_prompt if proceed
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
private
|
23
23
|
|
24
24
|
def login_prompt
|
25
25
|
user = prompt.ask "Username :", default: Config.email
|
26
26
|
pass = prompt.mask "Password :"
|
27
|
-
|
27
|
+
|
28
28
|
if user and pass
|
29
29
|
say "Logging in... "
|
30
30
|
radio.api.login user, pass
|
@@ -33,7 +33,6 @@ module AudioAddict
|
|
33
33
|
say "!txtred!Cancelled"
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
37
36
|
end
|
38
37
|
end
|
39
|
-
end
|
38
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AudioAddict
|
2
2
|
module Commands
|
3
3
|
class NowCmd < Base
|
4
|
-
summary "Show network, channel and playing track"
|
4
|
+
summary "Show network, channel and playing track"
|
5
5
|
|
6
6
|
help "This command displays the active network and channel, as well as the currently playing track."
|
7
7
|
|
@@ -11,15 +11,14 @@ module AudioAddict
|
|
11
11
|
def run
|
12
12
|
needs :network, :channel
|
13
13
|
|
14
|
-
say
|
15
|
-
say
|
16
|
-
say
|
14
|
+
say "!txtblu! Network !txtrst!: !txtgrn!#{radio.name}!txtrst! # #{radio.network}"
|
15
|
+
say "!txtblu! Channel !txtrst!: !txtgrn!#{current_channel.name}!txtrst! # #{current_channel.key}"
|
16
|
+
say "!txtblu! Track !txtrst!: ... "
|
17
17
|
|
18
18
|
track = current_channel.current_track
|
19
19
|
resay "!txtblu! Track !txtrst!: !txtgrn!#{track.title.strip}"
|
20
|
-
say
|
20
|
+
say "!txtblu! By !txtrst!: !txtgrn!#{track.artist.strip}"
|
21
21
|
end
|
22
|
-
|
23
22
|
end
|
24
23
|
end
|
25
|
-
end
|
24
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AudioAddict
|
2
2
|
module Commands
|
3
3
|
class PlaylistCmd < Base
|
4
|
-
summary "Generate playlists"
|
4
|
+
summary "Generate playlists"
|
5
5
|
|
6
6
|
help "This command lets you generate playlists for the active network. In order to allow configuration, the process is done in two stages: 'init' and 'generate'."
|
7
7
|
|
@@ -22,7 +22,7 @@ module AudioAddict
|
|
22
22
|
|
23
23
|
require_premium_account
|
24
24
|
|
25
|
-
name = args[
|
25
|
+
name = args["NAME"]
|
26
26
|
outfile = "#{name}.yml"
|
27
27
|
|
28
28
|
say "!txtred!Warning!txtrst!: !txtgrn!#{outfile}!txtrst! already exists!" if File.exist? outfile
|
@@ -34,12 +34,12 @@ module AudioAddict
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
def generate_command(name=nil)
|
37
|
+
def generate_command(name = nil)
|
38
38
|
needs :network, :channel, :listen_key
|
39
39
|
|
40
40
|
require_premium_account
|
41
41
|
|
42
|
-
name ||= args[
|
42
|
+
name ||= args["NAME"]
|
43
43
|
|
44
44
|
infile = "#{name}.yml"
|
45
45
|
outfile = "#{name}.pls"
|
@@ -53,15 +53,12 @@ module AudioAddict
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
private
|
57
57
|
|
58
58
|
def generate_config(outfile)
|
59
|
-
data = {
|
60
|
-
template: "http://prem2.#{radio.domain}:80/%{channel_key}?%{listen_key}"
|
61
|
-
}
|
62
|
-
|
59
|
+
data = { template: radio.url_template }
|
63
60
|
channels = {}
|
64
|
-
|
61
|
+
|
65
62
|
radio.channels.each do |key, channel|
|
66
63
|
key = fix_key key.to_sym
|
67
64
|
channels[key] = channel.name
|
@@ -107,10 +104,9 @@ module AudioAddict
|
|
107
104
|
|
108
105
|
# This is a patch to circumvent some anomalies in the AudioAddict API
|
109
106
|
def fix_key(key)
|
110
|
-
key = :electrohouse if current_network ==
|
107
|
+
key = :electrohouse if current_network == "di" and key == :electro
|
111
108
|
key
|
112
109
|
end
|
113
|
-
|
114
110
|
end
|
115
111
|
end
|
116
|
-
end
|
112
|
+
end
|
@@ -19,8 +19,8 @@ module AudioAddict
|
|
19
19
|
example "radio set - rockradio"
|
20
20
|
|
21
21
|
def run
|
22
|
-
channel = args[
|
23
|
-
network = args[
|
22
|
+
channel = args["CHANNEL"]
|
23
|
+
network = args["NETWORK"]
|
24
24
|
|
25
25
|
full_set = (channel and network) || !(channel or network)
|
26
26
|
|
@@ -31,7 +31,7 @@ module AudioAddict
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
private
|
35
35
|
|
36
36
|
def set_both(channel, network)
|
37
37
|
needs :session_key
|
@@ -50,19 +50,14 @@ module AudioAddict
|
|
50
50
|
|
51
51
|
if !channel
|
52
52
|
channel_menu
|
53
|
-
|
54
|
-
elsif channel == '-'
|
53
|
+
elsif channel == "-"
|
55
54
|
save_channel radio.channels.keys.first
|
56
|
-
|
57
55
|
elsif radio.valid_channel? channel
|
58
56
|
save_channel channel
|
59
|
-
|
60
57
|
elsif radio.search(channel).any?
|
61
58
|
channel_menu channel
|
62
|
-
|
63
59
|
else
|
64
60
|
say "!txtred!Invalid channel: #{radio.name} > #{channel}"
|
65
|
-
|
66
61
|
end
|
67
62
|
end
|
68
63
|
|
@@ -91,13 +86,13 @@ module AudioAddict
|
|
91
86
|
def channel_prompt(channels)
|
92
87
|
options = channels.map { |channel| ["#{channel.name.ljust 20} # #{channel.key}", channel.key] }.to_h
|
93
88
|
options = { "Cancel" => :cancel }.merge options
|
94
|
-
prompt.select "Channel :", options, marker:
|
89
|
+
prompt.select "Channel :", options, symbols: { marker: ">" }, filter: true
|
95
90
|
end
|
96
91
|
|
97
92
|
def network_prompt(networks)
|
98
93
|
options = networks.invert
|
99
94
|
options["Skip"] = :cancel
|
100
|
-
prompt.select "Network :", options, marker:
|
95
|
+
prompt.select "Network :", options, symbols: { marker: ">" }, filter: true, per_page: 10
|
101
96
|
end
|
102
97
|
|
103
98
|
def save_channel(channel, echo: true)
|
@@ -111,7 +106,6 @@ module AudioAddict
|
|
111
106
|
Config.save
|
112
107
|
say "Network : !txtgrn!#{radio.name}!txtrst! # #{network}" if echo
|
113
108
|
end
|
114
|
-
|
115
109
|
end
|
116
110
|
end
|
117
|
-
end
|
111
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AudioAddict
|
2
2
|
module Commands
|
3
3
|
class VoteCmd < Base
|
4
|
-
summary "Vote on a recently played track"
|
4
|
+
summary "Vote on a recently played track"
|
5
5
|
|
6
6
|
help "This command starts an interactive voting prompt for the currently playing track or for previously played tracks."
|
7
7
|
|
@@ -22,7 +22,7 @@ module AudioAddict
|
|
22
22
|
vote_mode == :now ? vote_now : vote_past
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
private
|
26
26
|
|
27
27
|
def vote_past
|
28
28
|
track = get_user_track
|
@@ -54,9 +54,9 @@ module AudioAddict
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def get_user_track
|
57
|
-
options = tracks.map { |t| ["#{t.artist.ljust max_artist_len} > #{t.title}", t
|
57
|
+
options = tracks.map { |t| ["#{t.artist.ljust max_artist_len} > #{t.title}", t] }.to_h
|
58
58
|
options = { "Cancel" => :cancel }.merge options
|
59
|
-
prompt.select "Track:", options, marker:
|
59
|
+
prompt.select "Track:", options, symbols: { marker: ">" }
|
60
60
|
end
|
61
61
|
|
62
62
|
def get_user_vote
|
@@ -64,9 +64,9 @@ module AudioAddict
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def menu_prompt
|
67
|
-
options = { "Like" => :up, "Dislike" => :down,
|
68
|
-
|
69
|
-
prompt.select "Vote:", options, marker:
|
67
|
+
options = { "Like" => :up, "Dislike" => :down,
|
68
|
+
"Unvote" => :delete, "Cancel" => :cancel }
|
69
|
+
prompt.select "Vote:", options, symbols: { marker: ">" }
|
70
70
|
end
|
71
71
|
|
72
72
|
def simple_prompt
|
@@ -75,13 +75,12 @@ module AudioAddict
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def vote_style
|
78
|
-
args[
|
78
|
+
args["--all"] ? :menu : :simple
|
79
79
|
end
|
80
80
|
|
81
81
|
def vote_mode
|
82
|
-
args[
|
82
|
+
args["--past"] ? :past : :now
|
83
83
|
end
|
84
|
-
|
85
84
|
end
|
86
85
|
end
|
87
86
|
end
|
data/lib/audio_addict/config.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "yaml"
|
2
2
|
|
3
3
|
module AudioAddict
|
4
4
|
class Config
|
@@ -6,7 +6,7 @@ module AudioAddict
|
|
6
6
|
attr_writer :path
|
7
7
|
|
8
8
|
def method_missing(name, *args, &_blk)
|
9
|
-
if name.to_s.end_with?
|
9
|
+
if name.to_s.end_with? "="
|
10
10
|
name = name[0..-2].to_sym
|
11
11
|
properties[name] = args.first
|
12
12
|
else
|
@@ -39,13 +39,12 @@ module AudioAddict
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def path
|
42
|
-
@path ||= ENV.fetch(
|
42
|
+
@path ||= ENV.fetch("AUDIO_ADDICT_CONFIG_PATH", default_path)
|
43
43
|
end
|
44
44
|
|
45
45
|
def default_path
|
46
46
|
"#{Dir.home}/.audio_addict/config"
|
47
47
|
end
|
48
|
-
|
49
48
|
end
|
50
49
|
end
|
51
50
|
end
|
@@ -1,25 +1,24 @@
|
|
1
1
|
module AudioAddict
|
2
2
|
class Error < StandardError; end
|
3
|
-
|
4
3
|
class Interrupt < Error; end
|
5
|
-
|
6
4
|
class ArgumentError < Error; end
|
5
|
+
class DependencyError < Error; end
|
7
6
|
|
8
7
|
class ConfigError < Error
|
9
8
|
attr_reader :missing_keys
|
10
9
|
|
11
10
|
def initialize(missing_keys)
|
12
11
|
@missing_keys = missing_keys
|
13
|
-
super "Some parameters required by this operation are missing"
|
12
|
+
super "Some parameters required by this operation are missing"
|
14
13
|
end
|
15
14
|
end
|
16
|
-
|
15
|
+
|
17
16
|
class PremiumAccount < Error
|
18
|
-
def initialize(message="This operation requires a premium account")
|
17
|
+
def initialize(message = "This operation requires a premium account")
|
19
18
|
super
|
20
19
|
end
|
21
20
|
end
|
22
|
-
|
21
|
+
|
23
22
|
class APIError < Error
|
24
23
|
attr_reader :response
|
25
24
|
|
@@ -28,4 +27,4 @@ module AudioAddict
|
|
28
27
|
super "#{response.code} #{response.message}:\n#{response.body}"
|
29
28
|
end
|
30
29
|
end
|
31
|
-
end
|
30
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "fileutils"
|
2
2
|
|
3
3
|
class File
|
4
4
|
def self.contains?(file, content)
|
@@ -10,7 +10,7 @@ class File
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.append(file, content)
|
13
|
-
open(file,
|
13
|
+
open(file, "a") { |f| f.puts content }
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.deep_write(file, content)
|
data/lib/audio_addict/log.rb
CHANGED
@@ -31,9 +31,9 @@ module AudioAddict
|
|
31
31
|
|
32
32
|
def tree!
|
33
33
|
result = {}
|
34
|
-
|
34
|
+
|
35
35
|
data.each do |line|
|
36
|
-
network, channel, artist, song = line.split(
|
36
|
+
network, channel, artist, song = line.split(" :: ")
|
37
37
|
result[network] ||= {}
|
38
38
|
result[network][channel] ||= {}
|
39
39
|
result[network][channel][artist] ||= []
|
@@ -42,6 +42,5 @@ module AudioAddict
|
|
42
42
|
|
43
43
|
result
|
44
44
|
end
|
45
|
-
|
46
45
|
end
|
47
46
|
end
|
data/lib/audio_addict/radio.rb
CHANGED
@@ -10,7 +10,8 @@ module AudioAddict
|
|
10
10
|
rockradio: "Rock Radio",
|
11
11
|
radiotunes: "Radio Tunes",
|
12
12
|
jazzradio: "Jazz Radio",
|
13
|
-
classicalradio: "Classical Radio"
|
13
|
+
classicalradio: "Classical Radio",
|
14
|
+
zenradio: "Zen Radio",
|
14
15
|
}
|
15
16
|
|
16
17
|
DOMAINS = {
|
@@ -18,7 +19,8 @@ module AudioAddict
|
|
18
19
|
rockradio: "rockradio.com",
|
19
20
|
radiotunes: "radiotunes.com",
|
20
21
|
jazzradio: "jazzradio.com",
|
21
|
-
classicalradio: "classicalradio.com"
|
22
|
+
classicalradio: "classicalradio.com",
|
23
|
+
zenradio: "zenradio.com",
|
22
24
|
}
|
23
25
|
|
24
26
|
def self.networks(search = nil)
|
@@ -50,6 +52,11 @@ module AudioAddict
|
|
50
52
|
DOMAINS[network.to_sym]
|
51
53
|
end
|
52
54
|
|
55
|
+
def url_template
|
56
|
+
channel_path = network == "zenradio" ? "zr%{channel_key}_aac" : "%{channel_key}"
|
57
|
+
"http://prem2.#{domain}:80/#{channel_path}?%{listen_key}"
|
58
|
+
end
|
59
|
+
|
53
60
|
def channels
|
54
61
|
@channels ||= channels!
|
55
62
|
end
|
@@ -57,7 +64,7 @@ module AudioAddict
|
|
57
64
|
def search(query)
|
58
65
|
channels.select do |key, channel|
|
59
66
|
"#{key} #{channel.name.downcase}".include? query.downcase
|
60
|
-
end
|
67
|
+
end
|
61
68
|
end
|
62
69
|
|
63
70
|
def search_by_id(ids)
|
@@ -77,16 +84,16 @@ module AudioAddict
|
|
77
84
|
@api ||= API.new network
|
78
85
|
end
|
79
86
|
|
80
|
-
|
87
|
+
private
|
81
88
|
|
82
89
|
def channels!
|
83
90
|
response = cache.get "#{network}/channels" do
|
84
|
-
api.get
|
91
|
+
api.get "channels"
|
85
92
|
end
|
86
93
|
|
87
94
|
result = {}
|
88
95
|
response.map do |channel|
|
89
|
-
key = channel[
|
96
|
+
key = channel["key"]
|
90
97
|
candidate = Channel.new self, channel
|
91
98
|
result[key] = candidate if candidate.active?
|
92
99
|
end
|
data/lib/audio_addict/track.rb
CHANGED
@@ -6,7 +6,7 @@ module AudioAddict
|
|
6
6
|
attr_reader :channel
|
7
7
|
|
8
8
|
def initialize(channel, properties)
|
9
|
-
@channel, @properties
|
9
|
+
@channel, @properties = channel, properties
|
10
10
|
end
|
11
11
|
|
12
12
|
def inspectable
|
@@ -14,11 +14,15 @@ module AudioAddict
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def id
|
17
|
-
properties[
|
17
|
+
properties["track_id"]
|
18
18
|
end
|
19
19
|
|
20
20
|
def title
|
21
|
-
properties[
|
21
|
+
properties["title"].strip
|
22
|
+
end
|
23
|
+
|
24
|
+
def search_string
|
25
|
+
"#{artist}, #{title}"
|
22
26
|
end
|
23
27
|
end
|
24
28
|
end
|
data/lib/audio_addict/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module AudioAddict
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.4.0"
|
3
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
class Youtube
|
3
|
+
include Inspectable
|
4
|
+
include Colsole
|
5
|
+
|
6
|
+
attr_reader :query
|
7
|
+
|
8
|
+
def initialize(query)
|
9
|
+
@query = query
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspectable
|
13
|
+
[:query]
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(count = 1)
|
17
|
+
raise DependencyError, "This command requires youtube-dl" unless command_exist? 'youtube-dl'
|
18
|
+
success = execute command(count: count, query: query)
|
19
|
+
raise DependencyError, "youtube-dl exited with an error" unless success
|
20
|
+
end
|
21
|
+
|
22
|
+
def command(args)
|
23
|
+
command_template % args
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def execute(command)
|
29
|
+
if ENV['YOUTUBE_DL_DRY_RUN']
|
30
|
+
puts "DRY RUN: #{command}"
|
31
|
+
true
|
32
|
+
else
|
33
|
+
system command
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def command_template
|
38
|
+
@command_template ||= %Q[youtube-dl --extract-audio --audio-format mp3 ytsearch%{count}:"%{query}"]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: audio_addict
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Danny Ben Shitrit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colsole
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
89
|
+
version: '0.19'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0.
|
96
|
+
version: '0.19'
|
97
97
|
description: Command line for playlist management and voting for AudioAddict radio
|
98
98
|
networks
|
99
99
|
email: db@dannyben.com
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- lib/audio_addict/commands/base.rb
|
115
115
|
- lib/audio_addict/commands/channels.rb
|
116
116
|
- lib/audio_addict/commands/config.rb
|
117
|
+
- lib/audio_addict/commands/download.rb
|
117
118
|
- lib/audio_addict/commands/history.rb
|
118
119
|
- lib/audio_addict/commands/log.rb
|
119
120
|
- lib/audio_addict/commands/login.rb
|
@@ -129,6 +130,7 @@ files:
|
|
129
130
|
- lib/audio_addict/radio.rb
|
130
131
|
- lib/audio_addict/track.rb
|
131
132
|
- lib/audio_addict/version.rb
|
133
|
+
- lib/audio_addict/youtube.rb
|
132
134
|
homepage: https://github.com/dannyben/audio_addict
|
133
135
|
licenses:
|
134
136
|
- MIT
|
@@ -148,8 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
150
|
- !ruby/object:Gem::Version
|
149
151
|
version: '0'
|
150
152
|
requirements: []
|
151
|
-
|
152
|
-
rubygems_version: 2.7.6
|
153
|
+
rubygems_version: 3.2.3
|
153
154
|
signing_key:
|
154
155
|
specification_version: 4
|
155
156
|
summary: AudioAddict Command Line
|