audio_addict 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +8 -0
- data/bin/radio +20 -0
- data/lib/audio_addict.rb +9 -0
- data/lib/audio_addict/api.rb +75 -0
- data/lib/audio_addict/auto_properties.rb +13 -0
- data/lib/audio_addict/cache.rb +15 -0
- data/lib/audio_addict/channel.rb +60 -0
- data/lib/audio_addict/cli.rb +19 -0
- data/lib/audio_addict/commands/base.rb +39 -0
- data/lib/audio_addict/commands/channels.rb +33 -0
- data/lib/audio_addict/commands/login.rb +39 -0
- data/lib/audio_addict/commands/now.rb +25 -0
- data/lib/audio_addict/commands/playlist.rb +107 -0
- data/lib/audio_addict/commands/set.rb +113 -0
- data/lib/audio_addict/commands/status.rb +49 -0
- data/lib/audio_addict/commands/vote.rb +34 -0
- data/lib/audio_addict/config.rb +58 -0
- data/lib/audio_addict/exceptions.rb +22 -0
- data/lib/audio_addict/extensions/file.rb +20 -0
- data/lib/audio_addict/inspectable.rb +8 -0
- data/lib/audio_addict/radio.rb +95 -0
- data/lib/audio_addict/track.rb +25 -0
- data/lib/audio_addict/version.rb +3 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e16f2df339290b35db4fa973f59acad53659d70d24221d922d5f393ceb9e18be
|
4
|
+
data.tar.gz: 99b1ec5b9da37677b7b8766c1c78de6ce4ea8d1344cfd8738a0aa4e17fa77ffc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b4ba61aa84cb4732f986fe190a075d6318e56d10d7314f9796be032020e1a3e1c20018f203ea6bb1eab6c9bd8bd005e3debbeb110e6f99f91642885e76325205
|
7
|
+
data.tar.gz: e78661eb554bcc03bb151ca4098e3710b3acfb85fe94137a3e35ac3058bcf674969fa5a8dbb4ed30c45541166444c7597449a06cf22c97124bbbb7b76d218d24
|
data/README.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
AudioAddict Command Line
|
2
|
+
==================================================
|
3
|
+
|
4
|
+
Work in progress, documentation soon.
|
5
|
+
|
6
|
+
[API Reference (Archived)][api_reference]
|
7
|
+
|
8
|
+
[api_reference]: https://web.archive.org/web/20140426192326/http://tobiass.eu/api-doc.html#trackinfo
|
data/bin/radio
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'audio_addict'
|
3
|
+
|
4
|
+
router = AudioAddict::CLI.router
|
5
|
+
|
6
|
+
begin
|
7
|
+
exit router.run ARGV
|
8
|
+
rescue AudioAddict::Interrupt, TTY::Reader::InputInterrupt
|
9
|
+
say "\nGoodbye"
|
10
|
+
exit 1
|
11
|
+
rescue => e
|
12
|
+
say ""
|
13
|
+
if ENV['DEBUG']
|
14
|
+
puts e.backtrace.reverse
|
15
|
+
say ""
|
16
|
+
end
|
17
|
+
say "!undred!ERROR: #{e.class}"
|
18
|
+
say e.message
|
19
|
+
exit 1
|
20
|
+
end
|
data/lib/audio_addict.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module AudioAddict
|
4
|
+
class API
|
5
|
+
include HTTParty
|
6
|
+
include Cache
|
7
|
+
|
8
|
+
base_uri 'https://api.audioaddict.com/v1'
|
9
|
+
|
10
|
+
attr_accessor :network
|
11
|
+
|
12
|
+
def initialize(network)
|
13
|
+
@network = network
|
14
|
+
end
|
15
|
+
|
16
|
+
def login(username, password)
|
17
|
+
session = session(username, password)
|
18
|
+
Config.session_key = session['key']
|
19
|
+
Config.listen_key = session['member']['listen_key']
|
20
|
+
Config.save
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(path, args={})
|
24
|
+
response http.get "/#{network}/#{path}", headers: headers, body: args
|
25
|
+
end
|
26
|
+
|
27
|
+
def post(path, args={})
|
28
|
+
response http.post "/#{network}/#{path}", headers: headers, body: args
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(path, args={})
|
32
|
+
response http.delete "/#{network}/#{path}", headers: headers, body: args
|
33
|
+
end
|
34
|
+
|
35
|
+
def logged_in?
|
36
|
+
session_key and listen_key
|
37
|
+
end
|
38
|
+
|
39
|
+
def basic_auth
|
40
|
+
http.basic_auth 'streams', 'diradio'
|
41
|
+
end
|
42
|
+
|
43
|
+
def session(username, password)
|
44
|
+
params = { member_session: { username: username, password: password } }
|
45
|
+
cache.get "/#{network}/member_sessions" do
|
46
|
+
basic_auth
|
47
|
+
response http.post "/#{network}/member_sessions", body: params
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def session_key
|
52
|
+
Config.session_key
|
53
|
+
end
|
54
|
+
|
55
|
+
def listen_key
|
56
|
+
Config.listen_key
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def response(httparty_response)
|
62
|
+
raise APIError.new httparty_response unless httparty_response.success?
|
63
|
+
JSON.parse httparty_response.body
|
64
|
+
end
|
65
|
+
|
66
|
+
def http
|
67
|
+
@http ||= self.class
|
68
|
+
end
|
69
|
+
|
70
|
+
def headers
|
71
|
+
{ "X-Session-Key" => session_key }
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module AutoProperties
|
3
|
+
attr_reader :properties
|
4
|
+
|
5
|
+
def method_missing(method_sym, *args, &block)
|
6
|
+
respond_to?(method_sym) ? properties[method_sym.to_s] : super
|
7
|
+
end
|
8
|
+
|
9
|
+
def respond_to_missing?(method_sym, _include_private = false)
|
10
|
+
properties.has_key?(method_sym.to_s) || super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'lightly'
|
2
|
+
|
3
|
+
module AudioAddict
|
4
|
+
module Cache
|
5
|
+
|
6
|
+
def cache
|
7
|
+
@cache ||= Lightly.new life: '6h', dir: cache_dir
|
8
|
+
end
|
9
|
+
|
10
|
+
def cache_dir
|
11
|
+
@cache_dir ||= ENV.fetch('AUDIO_ADDICT_CACHE_PATH', "#{Dir.home}/.audio_addict/cache")
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
class Channel
|
3
|
+
include Cache
|
4
|
+
include AutoProperties
|
5
|
+
include Inspectable
|
6
|
+
|
7
|
+
attr_reader :radio
|
8
|
+
|
9
|
+
def initialize(radio, properties)
|
10
|
+
@radio, @properties = radio, properties
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspectable
|
14
|
+
[:key, :name, :id]
|
15
|
+
end
|
16
|
+
|
17
|
+
def active?
|
18
|
+
# Seems like each network has a different way of marking inactive channels.
|
19
|
+
# This is where we normalize it
|
20
|
+
return false if !properties['asset_id']
|
21
|
+
return false if name[0] == 'X' and key[0] != 'x'
|
22
|
+
return true
|
23
|
+
end
|
24
|
+
|
25
|
+
def track_history
|
26
|
+
@track_history ||= track_history!
|
27
|
+
end
|
28
|
+
|
29
|
+
def track_history!
|
30
|
+
response = radio.api.get "track_history/channel/#{id}"
|
31
|
+
response.map { |track| Track.new self, track }
|
32
|
+
end
|
33
|
+
|
34
|
+
def current_track
|
35
|
+
track_history.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def vote(direction = :up)
|
39
|
+
track_id = current_track.id
|
40
|
+
endpoint = "tracks/#{track_id}/vote/#{id}"
|
41
|
+
|
42
|
+
if direction == :delete
|
43
|
+
radio.api.delete endpoint
|
44
|
+
else
|
45
|
+
radio.api.post "#{endpoint}/#{direction}"
|
46
|
+
end
|
47
|
+
|
48
|
+
log_like if direction == :up and Config.like_log
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def log_like
|
54
|
+
message = "#{radio.name} :: #{name} :: #{current_track.artist} :: #{current_track.title}"
|
55
|
+
file = Config.like_log
|
56
|
+
File.append file, message unless File.contains? file, message
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
class CLI
|
3
|
+
def self.router
|
4
|
+
router = MisterBin::Runner.new version: VERSION,
|
5
|
+
header: "AudioAddict Radio Utilities"
|
6
|
+
|
7
|
+
router.route 'login', to: Commands::LoginCmd
|
8
|
+
router.route 'status', to: Commands::StatusCmd
|
9
|
+
router.route 'set', to: Commands::SetCmd
|
10
|
+
router.route 'channels', to: Commands::ChannelsCmd
|
11
|
+
router.route 'now', to: Commands::NowCmd
|
12
|
+
router.route 'vote', to: Commands::VoteCmd
|
13
|
+
router.route 'playlist', to: Commands::PlaylistCmd
|
14
|
+
|
15
|
+
router
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'mister_bin'
|
2
|
+
require 'colsole'
|
3
|
+
require 'tty/prompt'
|
4
|
+
|
5
|
+
module AudioAddict
|
6
|
+
module Commands
|
7
|
+
class Base < MisterBin::Command
|
8
|
+
|
9
|
+
def needs(*config_keys)
|
10
|
+
missing = []
|
11
|
+
config_keys.each do |key|
|
12
|
+
missing.push key unless Config.has_key? key
|
13
|
+
end
|
14
|
+
|
15
|
+
if missing.any?
|
16
|
+
missing_keys = missing.map { |k| "- !txtblu!#{k}" }.join "\n"
|
17
|
+
raise ConfigError, "This operation requires some config parameters that are missing:\n#{missing_keys}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def radio
|
22
|
+
@radio ||= Radio.new current_network
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_network
|
26
|
+
Config.network
|
27
|
+
end
|
28
|
+
|
29
|
+
def current_channel
|
30
|
+
@current_channel ||= radio[Config.channel]
|
31
|
+
end
|
32
|
+
|
33
|
+
def prompt
|
34
|
+
@prompt ||= TTY::Prompt.new
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class ChannelsCmd < Base
|
4
|
+
summary "Show list of channels"
|
5
|
+
|
6
|
+
help "List and search channels in the currently set radio network"
|
7
|
+
|
8
|
+
usage "radio channels [SEARCH]"
|
9
|
+
usage "radio channels --help"
|
10
|
+
|
11
|
+
param "SEARCH", "Channel name or a partial name to search for."
|
12
|
+
|
13
|
+
example "radio channels"
|
14
|
+
example "radio channels metal"
|
15
|
+
|
16
|
+
def run(args)
|
17
|
+
needs :network
|
18
|
+
|
19
|
+
say "!undgrn!#{radio.name}\n"
|
20
|
+
|
21
|
+
search = args['SEARCH']
|
22
|
+
|
23
|
+
channels = search ? radio.search(search) : radio.channels
|
24
|
+
|
25
|
+
channels = channels.values
|
26
|
+
channels.each do |channel|
|
27
|
+
say "!txtgrn!#{channel.key.rjust 25} !txtblu!#{channel.name.strip}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class LoginCmd < Base
|
4
|
+
summary "Save login credentials"
|
5
|
+
|
6
|
+
help "Get a session key from AudioAddict API. This operation should only be done once."
|
7
|
+
|
8
|
+
usage "radio login"
|
9
|
+
usage "radio login --help"
|
10
|
+
|
11
|
+
def run(args)
|
12
|
+
proceed = true
|
13
|
+
|
14
|
+
if radio.api.logged_in?
|
15
|
+
say "!txtylw!You are already logged in"
|
16
|
+
proceed = prompt.yes? "Login again?"
|
17
|
+
end
|
18
|
+
|
19
|
+
login_prompt if proceed
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def login_prompt
|
25
|
+
user = prompt.ask "Username :"
|
26
|
+
pass = prompt.mask "Password :"
|
27
|
+
|
28
|
+
if user and pass
|
29
|
+
say "Logging in... "
|
30
|
+
radio.api.login user, pass
|
31
|
+
resay "!txtgrn!Saved"
|
32
|
+
else
|
33
|
+
say "!txtred!Aborted"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class NowCmd < Base
|
4
|
+
summary "Show network, channel and playing track"
|
5
|
+
|
6
|
+
help "Display the configured network and channel, as well as the currently playing track."
|
7
|
+
|
8
|
+
usage "radio now"
|
9
|
+
usage "radio now --help"
|
10
|
+
|
11
|
+
def run(args)
|
12
|
+
needs :network, :channel
|
13
|
+
|
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
|
+
|
18
|
+
track = current_channel.current_track
|
19
|
+
resay "!txtblu! Track !txtrst!: !txtgrn!#{track.title.strip}"
|
20
|
+
say "!txtblu! By !txtrst!: !txtgrn!#{track.artist.strip}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class PlaylistCmd < Base
|
4
|
+
summary "Generate playlists"
|
5
|
+
|
6
|
+
usage "radio playlist init NAME"
|
7
|
+
usage "radio playlist generate NAME"
|
8
|
+
usage "radio playlist --help"
|
9
|
+
|
10
|
+
command "init", "Create a playlist configuration file. This step is required prior to using the generate command."
|
11
|
+
command "generate", "Generate a playlist file based on the configuration file."
|
12
|
+
|
13
|
+
param "NAME", "The name of the playlist without any extension"
|
14
|
+
|
15
|
+
example "radio playlist init MyRockMusic"
|
16
|
+
example "radio playlist generate MyRockMusic"
|
17
|
+
|
18
|
+
def run(args)
|
19
|
+
needs :network, :channel, :listen_key
|
20
|
+
|
21
|
+
@args = args
|
22
|
+
@filename = "#{@args['NAME']}"
|
23
|
+
|
24
|
+
init_command if args['init']
|
25
|
+
generate_command if args['generate']
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def init_command
|
31
|
+
@outfile = "#{@filename}.yml"
|
32
|
+
|
33
|
+
say "!txtred!Warning!txtrst!: !txtgrn!#{@outfile}!txtrst! already exists!" if File.exist? @outfile
|
34
|
+
proceed = prompt.yes? "Create #{@outfile}?"
|
35
|
+
if proceed
|
36
|
+
generate_config
|
37
|
+
say ""
|
38
|
+
generate_command # we also generate the playlist
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_command
|
43
|
+
@infile = "#{@filename}.yml"
|
44
|
+
@outfile = "#{@filename}.pls"
|
45
|
+
|
46
|
+
if !File.exist? @infile
|
47
|
+
say "!txtred!Cannot find #{@infile}"
|
48
|
+
else
|
49
|
+
say "!txtred!Warning!txtrst!: !txtgrn!#{@outfile}!txtrst! already exists!" if File.exist? @outfile
|
50
|
+
proceed = prompt.yes? "Create #{@outfile}?"
|
51
|
+
generate_playlist if proceed
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_playlist
|
56
|
+
data = YAML.load_file @infile
|
57
|
+
template = data[:template]
|
58
|
+
channels = data[:channels].select { |c| c[:active] }
|
59
|
+
|
60
|
+
output = []
|
61
|
+
output << "[playlist]"
|
62
|
+
output << "NumberOfEntries=#{channels.count}"
|
63
|
+
|
64
|
+
index = 0
|
65
|
+
|
66
|
+
channels.each do |channel|
|
67
|
+
index += 1
|
68
|
+
output << "File#{index}=#{template}" % template_params(channel[:key])
|
69
|
+
output << "Title#{index}=#{channel[:name]}"
|
70
|
+
output << "Length#{index}=0"
|
71
|
+
end
|
72
|
+
|
73
|
+
output = output.join("\n") + "\n"
|
74
|
+
|
75
|
+
File.write @outfile, output
|
76
|
+
say "Saved !txtgrn!#{@outfile}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def template_params(channel_key)
|
80
|
+
{ listen_key: listen_key, channel_key: channel_key }
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_config
|
84
|
+
data = {
|
85
|
+
template: "http://prem2.#{radio.domain}:80/%{channel_key}?%{listen_key}"
|
86
|
+
}
|
87
|
+
|
88
|
+
channels = []
|
89
|
+
|
90
|
+
radio.channels.each do |key, channel|
|
91
|
+
channel_data = { name: channel.name, key: key, active: true }
|
92
|
+
channels << channel_data
|
93
|
+
end
|
94
|
+
|
95
|
+
data[:channels] = channels
|
96
|
+
|
97
|
+
File.write @outfile, data.to_yaml
|
98
|
+
say "Saved !txtgrn!#{@outfile}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def listen_key
|
102
|
+
Config.listen_key
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class SetCmd < Base
|
4
|
+
summary "Set the radio network and channel"
|
5
|
+
|
6
|
+
help "Save the channel and network to the config file for future use"
|
7
|
+
|
8
|
+
usage "radio set [CHANNEL NETWORK]"
|
9
|
+
usage "radio set --help"
|
10
|
+
|
11
|
+
param "CHANNEL", "AudioAddict channel key. You can use a partial key here or leave empty for an interactive prompt."
|
12
|
+
param "NETWORK", "AudioAddict network key. You can use a partial key here or leave empty for an interactive prompt."
|
13
|
+
|
14
|
+
example "radio set"
|
15
|
+
example "radio set punk"
|
16
|
+
example "radio set dance digitally"
|
17
|
+
example "radio set dance di"
|
18
|
+
example "radio set metal rockradio"
|
19
|
+
|
20
|
+
def run(args)
|
21
|
+
channel = args['CHANNEL']
|
22
|
+
network = args['NETWORK']
|
23
|
+
|
24
|
+
full_set = (channel and network) || !(channel or network)
|
25
|
+
|
26
|
+
if full_set
|
27
|
+
set_both channel, network
|
28
|
+
else
|
29
|
+
set_channel channel
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def set_both(channel, network)
|
36
|
+
needs :session_key
|
37
|
+
|
38
|
+
if !network or !Radio.valid_network? network
|
39
|
+
network_menu network
|
40
|
+
elsif Radio.valid_network? network
|
41
|
+
save_network network
|
42
|
+
end
|
43
|
+
|
44
|
+
set_channel channel
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_channel(channel)
|
48
|
+
needs :network, :session_key
|
49
|
+
|
50
|
+
if !channel
|
51
|
+
channel_menu
|
52
|
+
|
53
|
+
elsif radio.valid_channel? channel
|
54
|
+
save_channel channel
|
55
|
+
|
56
|
+
elsif radio.search(channel).any?
|
57
|
+
channel_menu channel
|
58
|
+
|
59
|
+
else
|
60
|
+
say "!txtred!Invalid channel: #{radio.name} > #{channel}"
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def channel_menu(channel = nil)
|
66
|
+
list = channel ? radio.search(channel).values : radio.channels.values
|
67
|
+
|
68
|
+
if list.count == 1
|
69
|
+
save_channel list.first.key
|
70
|
+
else
|
71
|
+
answer = channel_prompt list
|
72
|
+
save_channel(answer, echo: false) unless answer == :abort
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def network_menu(network = nil)
|
77
|
+
list = Radio.networks network
|
78
|
+
|
79
|
+
if list.count == 1
|
80
|
+
save_network list.keys.first
|
81
|
+
else
|
82
|
+
answer = network_prompt list
|
83
|
+
save_network(answer, echo: false) unless answer == :abort
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def channel_prompt(channels)
|
88
|
+
options = channels.map { |channel| ["#{channel.name.ljust 20} # #{channel.key}", channel.key] }.to_h
|
89
|
+
options = { "Abort" => :abort }.merge options
|
90
|
+
prompt.select "Channel :", options, marker: '>', filter: true
|
91
|
+
end
|
92
|
+
|
93
|
+
def network_prompt(networks)
|
94
|
+
options = networks.invert
|
95
|
+
options["Abort"] = :abort
|
96
|
+
prompt.select "Network :", options, marker: '>', filter: true
|
97
|
+
end
|
98
|
+
|
99
|
+
def save_channel(channel, echo: true)
|
100
|
+
Config.channel = channel
|
101
|
+
Config.save
|
102
|
+
say "Channel : !txtgrn!#{radio.name} > #{current_channel.name}!txtrst! # #{channel}" if echo
|
103
|
+
end
|
104
|
+
|
105
|
+
def save_network(network, echo: true)
|
106
|
+
Config.network = network
|
107
|
+
Config.save
|
108
|
+
say "Network : !txtgrn!#{radio.name}!txtrst! # #{network}" if echo
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class StatusCmd < Base
|
4
|
+
summary "Show configuration status"
|
5
|
+
|
6
|
+
usage "radio status [--unsafe]"
|
7
|
+
usage "radio status --help"
|
8
|
+
|
9
|
+
option "-u --unsafe", "Show the full session and listen keys"
|
10
|
+
|
11
|
+
def run(args)
|
12
|
+
say "!txtblu! Config Path !txtrst!: !txtgrn!#{Config.path}"
|
13
|
+
|
14
|
+
say "!txtblu! Session Key !txtrst!: "
|
15
|
+
if Config.session_key
|
16
|
+
key = Config.session_key
|
17
|
+
display_key = args['--unsafe'] ? key : "***#{key[-4, 4]}"
|
18
|
+
say "!txtgrn!#{display_key}"
|
19
|
+
else
|
20
|
+
say "!txtred!<Unset>!txtrst! - run !txtpur!radio login!txtrst! to fix"
|
21
|
+
end
|
22
|
+
|
23
|
+
say "!txtblu! Listen Key !txtrst!: "
|
24
|
+
if Config.listen_key
|
25
|
+
key = Config.listen_key
|
26
|
+
display_key = args['--unsafe'] ? key : "***#{key[-4, 4]}"
|
27
|
+
say "!txtgrn!#{display_key}"
|
28
|
+
else
|
29
|
+
say "!txtred!<Unset>!txtrst! - run !txtpur!radio login!txtrst! to fix"
|
30
|
+
end
|
31
|
+
|
32
|
+
say "!txtblu! Network !txtrst!: "
|
33
|
+
if Config.network
|
34
|
+
say "!txtgrn!#{Config.network}"
|
35
|
+
else
|
36
|
+
say "!txtred!<Unset>!txtrst! - run !txtpur!radio set!txtrst! to fix"
|
37
|
+
end
|
38
|
+
|
39
|
+
say "!txtblu! Channel !txtrst!: "
|
40
|
+
if Config.channel
|
41
|
+
say "!txtgrn!#{Config.channel}"
|
42
|
+
else
|
43
|
+
say"!txtred!<Unset>!txtrst! - run !txtpur!radio channel!txtrst! to fix"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
module Commands
|
3
|
+
class VoteCmd < Base
|
4
|
+
summary "Vote on the currently playing track"
|
5
|
+
|
6
|
+
help "Start an interactive voting prompt for the currently playing track."
|
7
|
+
|
8
|
+
usage "radio vote"
|
9
|
+
usage "radio vote --help"
|
10
|
+
|
11
|
+
def run(args)
|
12
|
+
needs :network, :channel, :session_key
|
13
|
+
|
14
|
+
NowCmd.new.run args
|
15
|
+
puts ""
|
16
|
+
answer = get_user_vote
|
17
|
+
unless answer == :abort
|
18
|
+
say "Voting... "
|
19
|
+
current_channel.vote answer
|
20
|
+
resay "!txtgrn!Voted"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def get_user_vote
|
27
|
+
options = { "Like" => :up, "Dislike" => :down,
|
28
|
+
"Unvote" => :delete, "Abort" => :abort }
|
29
|
+
prompt.select "Your Vote :", options, marker: '>'
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module AudioAddict
|
4
|
+
class Config
|
5
|
+
class << self
|
6
|
+
attr_writer :path
|
7
|
+
|
8
|
+
def method_missing(name, *args, &_blk)
|
9
|
+
if name.to_s.end_with? '='
|
10
|
+
name = name[0..-2].to_sym
|
11
|
+
properties[name] = args.first
|
12
|
+
else
|
13
|
+
properties[name]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key)
|
18
|
+
properties.delete key
|
19
|
+
end
|
20
|
+
|
21
|
+
def save
|
22
|
+
File.deep_write path, properties.to_yaml
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
required_keys.each do |key|
|
27
|
+
return false unless has_key? key
|
28
|
+
end
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def required_keys
|
33
|
+
[:network, :channel, :session_key]
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_key?(key)
|
37
|
+
properties.has_key? key
|
38
|
+
end
|
39
|
+
|
40
|
+
def properties
|
41
|
+
@properties ||= properties!
|
42
|
+
end
|
43
|
+
|
44
|
+
def properties!
|
45
|
+
File.exist?(path) ? YAML.load_file(path) : {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def path
|
49
|
+
@path ||= ENV.fetch('AUDIO_ADDICT_CONFIG_PATH', default_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_path
|
53
|
+
"#{Dir.home}/.audio_addict/config"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class Interrupt < Error; end
|
5
|
+
class ArgumentError < Error; end
|
6
|
+
class ConfigError < Error; end
|
7
|
+
|
8
|
+
class LoginError < Error
|
9
|
+
def initialize(msg="This operation requires logging in")
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class APIError < Error
|
15
|
+
attr_reader :response
|
16
|
+
|
17
|
+
def initialize(response)
|
18
|
+
@response = response
|
19
|
+
super "#{response.code} #{response.message}:\n#{response.body}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
class File
|
4
|
+
def self.contains?(file, content)
|
5
|
+
foreach file do |line|
|
6
|
+
return true if line.chomp == content
|
7
|
+
end
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.append(file, content)
|
12
|
+
open(file, 'a') { |f| f.puts content }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.deep_write(file, content)
|
16
|
+
dir = File.dirname file
|
17
|
+
FileUtils.mkdir_p dir unless Dir.exist? dir
|
18
|
+
File.write file, content
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
class Radio
|
3
|
+
include Cache
|
4
|
+
include Inspectable
|
5
|
+
|
6
|
+
attr_reader :network
|
7
|
+
|
8
|
+
NETWORKS = {
|
9
|
+
di: "Digitally Imported",
|
10
|
+
rockradio: "Rock Radio",
|
11
|
+
radiotunes: "Radio Tunes",
|
12
|
+
jazzradio: "Jazz Radio",
|
13
|
+
classicalradio: "Classical Radio"
|
14
|
+
}
|
15
|
+
|
16
|
+
DOMAINS = {
|
17
|
+
di: "di.fm",
|
18
|
+
rockradio: "rockradio.com",
|
19
|
+
radiotunes: "radiotunes.com",
|
20
|
+
jazzradio: "jazzradio.com",
|
21
|
+
classicalradio: "classicalradio.com"
|
22
|
+
}
|
23
|
+
|
24
|
+
def self.networks(search = nil)
|
25
|
+
if search
|
26
|
+
result = NETWORKS.select { |k, v| "#{k} #{v}".downcase.include? search.downcase }
|
27
|
+
result.any? ? result : NETWORKS
|
28
|
+
else
|
29
|
+
NETWORKS
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.valid_network?(network)
|
34
|
+
NETWORKS.keys.include? network.to_sym
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(network)
|
38
|
+
@network = network
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspectable
|
42
|
+
[:network]
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
NETWORKS[network.to_sym]
|
47
|
+
end
|
48
|
+
|
49
|
+
def domain
|
50
|
+
DOMAINS[network.to_sym]
|
51
|
+
end
|
52
|
+
|
53
|
+
def channels
|
54
|
+
@channels ||= channels!
|
55
|
+
end
|
56
|
+
|
57
|
+
def search(query)
|
58
|
+
channels.select do |key, channel|
|
59
|
+
"#{key} #{channel.name.downcase}".include? query.downcase
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def [](channel_key)
|
64
|
+
channels[channel_key]
|
65
|
+
end
|
66
|
+
|
67
|
+
def valid_channel?(channel)
|
68
|
+
channels.keys.include? channel
|
69
|
+
end
|
70
|
+
|
71
|
+
def favorites
|
72
|
+
api.member['network_favorite_channels']
|
73
|
+
end
|
74
|
+
|
75
|
+
def api
|
76
|
+
@api ||= API.new network
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def channels!
|
82
|
+
response = cache.get "#{network}/channels" do
|
83
|
+
api.get 'channels'
|
84
|
+
end
|
85
|
+
|
86
|
+
result = {}
|
87
|
+
response.map do |channel|
|
88
|
+
key = channel['key']
|
89
|
+
candidate = Channel.new self, channel
|
90
|
+
result[key] = candidate if candidate.active?
|
91
|
+
end
|
92
|
+
result.sort_by { |key, channel| channel.name }.to_h
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module AudioAddict
|
2
|
+
class Track
|
3
|
+
include AutoProperties
|
4
|
+
include Inspectable
|
5
|
+
|
6
|
+
attr_reader :channel
|
7
|
+
|
8
|
+
def initialize(channel, properties)
|
9
|
+
@channel, @properties = channel, properties
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspectable
|
13
|
+
[:title, :artist, :id]
|
14
|
+
end
|
15
|
+
|
16
|
+
def id
|
17
|
+
properties['track_id']
|
18
|
+
end
|
19
|
+
|
20
|
+
# This is only here due to the global Runfile method with the same name
|
21
|
+
def title
|
22
|
+
properties['title']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: audio_addict
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Danny Ben Shitrit
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colsole
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.16'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: lightly
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mister_bin
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.4'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 0.4.1
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0.4'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 0.4.1
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: requires
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0.1'
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0.1'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: tty-prompt
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0.17'
|
96
|
+
type: :runtime
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.17'
|
103
|
+
description: Command line for playlist management and voting for AudioAddict radio
|
104
|
+
networks
|
105
|
+
email: db@dannyben.com
|
106
|
+
executables:
|
107
|
+
- radio
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- README.md
|
112
|
+
- bin/radio
|
113
|
+
- lib/audio_addict.rb
|
114
|
+
- lib/audio_addict/api.rb
|
115
|
+
- lib/audio_addict/auto_properties.rb
|
116
|
+
- lib/audio_addict/cache.rb
|
117
|
+
- lib/audio_addict/channel.rb
|
118
|
+
- lib/audio_addict/cli.rb
|
119
|
+
- lib/audio_addict/commands/base.rb
|
120
|
+
- lib/audio_addict/commands/channels.rb
|
121
|
+
- lib/audio_addict/commands/login.rb
|
122
|
+
- lib/audio_addict/commands/now.rb
|
123
|
+
- lib/audio_addict/commands/playlist.rb
|
124
|
+
- lib/audio_addict/commands/set.rb
|
125
|
+
- lib/audio_addict/commands/status.rb
|
126
|
+
- lib/audio_addict/commands/vote.rb
|
127
|
+
- lib/audio_addict/config.rb
|
128
|
+
- lib/audio_addict/exceptions.rb
|
129
|
+
- lib/audio_addict/extensions/file.rb
|
130
|
+
- lib/audio_addict/inspectable.rb
|
131
|
+
- lib/audio_addict/radio.rb
|
132
|
+
- lib/audio_addict/track.rb
|
133
|
+
- lib/audio_addict/version.rb
|
134
|
+
homepage: https://github.com/dannyben/audio_addict
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 2.4.0
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.7.6
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: AudioAddict Command Line
|
158
|
+
test_files: []
|