audio_addict 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ require 'requires'
2
+ require 'byebug' if ENV['BYEBUG']
3
+ requires \
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'
@@ -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,8 @@
1
+ module AudioAddict
2
+ module Inspectable
3
+ def inspect
4
+ keys = inspectable.map { |k| %Q[@#{k}="#{send k}"] }
5
+ "#<#{self.class} #{keys.join ', '}>"
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,3 @@
1
+ module AudioAddict
2
+ VERSION = "0.0.2"
3
+ 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: []