audio_addict 0.0.2
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 +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: []
|