nehm 1.6.1 → 2.0.1

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.
@@ -0,0 +1,66 @@
1
+ require 'json'
2
+ require 'net/http'
3
+
4
+ module Nehm
5
+
6
+ class HTTPClient
7
+
8
+ ##
9
+ # SoundCloud API client ID
10
+
11
+ CLIENT_ID = '11a37feb6ccc034d5975f3f803928a32'
12
+
13
+ ##
14
+ # Exception classes
15
+
16
+ class Status404 < StandardError; end
17
+ class ConnectionError < StandardError; end
18
+
19
+ def get(api_version, uri_string)
20
+ uri = form_uri(api_version, uri_string)
21
+ get_hash(uri)
22
+ end
23
+
24
+ def resolve(url)
25
+ response = get(1, "/resolve?url=#{url}")
26
+
27
+ errors = response['errors']
28
+ if errors
29
+ if errors[0]['error_message'] =~ /404/
30
+ raise Status404
31
+ else
32
+ raise ConnectionError # HACK
33
+ end
34
+ end
35
+
36
+ if response['status'] =~ /302/
37
+ get_hash(response['location'])
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def form_uri(api_version, uri_string)
44
+ uri =
45
+ case api_version
46
+ when 1
47
+ 'https://api.soundcloud.com'
48
+ when 2
49
+ 'https://api-v2.soundcloud.com'
50
+ end
51
+ uri += uri_string
52
+ uri += "&client_id=#{CLIENT_ID}" if api_version == 1
53
+
54
+ uri
55
+ end
56
+
57
+ def get_hash(uri)
58
+ uri = URI.parse(URI.escape(uri))
59
+ http = Net::HTTP.new(uri.host, uri.port)
60
+ http.use_ssl = true
61
+ res = http.get(uri.request_uri)
62
+ JSON.parse(res.body)
63
+ end
64
+
65
+ end
66
+ end
data/lib/nehm/menu.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Nehm
2
+ module UI
3
+ class Menu
4
+
5
+ attr_writer :msg_bar, :header
6
+
7
+ def initialize
8
+ @choices = {}
9
+ @inc_index = 1
10
+ @items = []
11
+
12
+ yield self
13
+ select
14
+ end
15
+
16
+ def choice(index, desc, &block)
17
+ # Visual index - index that you see in menu
18
+ # Select index - index than can be selected
19
+ # For example, if you use ':added' index
20
+ # In menu you see 'A', but you can select it by number
21
+ # You receive a warning though
22
+
23
+ visual_index = select_index = index
24
+
25
+ if index == :inc
26
+ visual_index = select_index = @inc_index.to_s
27
+ @inc_index += 1
28
+ end
29
+
30
+ if index == :added
31
+ visual_index = 'A'.green
32
+ select_index = @inc_index.to_s
33
+ @inc_index += 1
34
+ end
35
+
36
+ @choices[select_index] = block
37
+ @items << "#{visual_index} #{desc}"
38
+ end
39
+
40
+ def show_header
41
+ UI.say @header
42
+ UI.newline
43
+ end
44
+
45
+ ##
46
+ # Message bar used to show messages of
47
+ # last successful or not operations
48
+ # Shown before menu
49
+
50
+ def show_msg_bar
51
+ UI.say 'Message:'
52
+ UI.say " #{@msg_bar}"
53
+ UI.newline
54
+ @msg_bar.clear
55
+ end
56
+
57
+ def newline
58
+ @items << "\n"
59
+ end
60
+
61
+ def select
62
+ show_header unless @header.to_s.empty?
63
+ show_msg_bar unless @msg_bar.to_s.empty?
64
+
65
+ # Add exit option
66
+ newline
67
+ choice('e', 'Exit'.red) { UI.term }
68
+
69
+ # Output items
70
+ @items.each do |item|
71
+ UI.say item
72
+ end
73
+
74
+ UI.newline
75
+
76
+ selected = UI.ask('Enter option'.yellow.freeze)
77
+ call_selected_block(selected)
78
+
79
+ UI.newline
80
+ end
81
+
82
+ def call_selected_block(selected)
83
+ loop do
84
+ if @choices.keys.include? selected
85
+ block = @choices[selected]
86
+ block.call
87
+ break
88
+ else
89
+ selected = UI.ask "You must choose one of [#{@choices.keys.join(', ')}]"
90
+ end
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -3,8 +3,6 @@ module Nehm
3
3
  ##
4
4
  # OptionParser parses options and add hash with options to specified command
5
5
 
6
- # TODO: make abbrebiated options
7
-
8
6
  class OptionParser
9
7
 
10
8
  def initialize(args, command)
data/lib/nehm/os.rb CHANGED
@@ -6,8 +6,8 @@ module Nehm
6
6
  # OS module returns information about OS on this computer
7
7
 
8
8
  module OS
9
- def self.linux?
10
- RbConfig::CONFIG['host_os'] =~ /linux/ ? true : false
9
+ def self.mac?
10
+ RbConfig::CONFIG['host_os'] =~ /darwin|mac os/ ? true : false
11
11
  end
12
12
  end
13
13
  end
@@ -1,13 +1,10 @@
1
1
  module Nehm
2
2
 
3
3
  ##
4
- # Path manager works with download pathes
4
+ # Path manager works with download paths
5
5
 
6
6
  module PathManager
7
7
 
8
- ##
9
- # Returns default download path (contains in ~/.nehmconfig)
10
-
11
8
  def self.default_dl_path
12
9
  Cfg[:dl_path]
13
10
  end
@@ -16,13 +13,10 @@ module Nehm
16
13
  # Checks path for validation and returns it if valid
17
14
 
18
15
  def self.get_path(path)
19
- # If path begins with ~
20
- path = tilde_to_home(path) if tilde_at_top?(path)
21
-
22
16
  # Check path for existence
23
17
  UI.term 'Invalid download path! Please enter correct path' unless Dir.exist?(path)
24
18
 
25
- path
19
+ File.expand_path(path)
26
20
  end
27
21
 
28
22
  def self.set_dl_path
@@ -31,21 +25,18 @@ module Nehm
31
25
  default_path = File.join(ENV['HOME'], '/Music')
32
26
 
33
27
  if Dir.exist?(default_path)
34
- ask_sentence << " (press enter to set it to #{default_path.magenta})"
28
+ ask_sentence << " (press Enter to set it to #{default_path.magenta})"
35
29
  else
36
30
  default_path = nil
37
31
  end
38
32
 
39
- path = HighLine.new.ask(ask_sentence + ':')
33
+ path = UI.ask(ask_sentence + ':')
40
34
 
41
35
  # If user press enter, set path to default
42
36
  path = default_path if path == '' && default_path
43
37
 
44
- # If tilde at top of the line of path
45
- path = PathManager.tilde_to_home(path) if PathManager.tilde_at_top?(path)
46
-
47
38
  if Dir.exist?(path)
48
- Cfg[:dl_path] = path
39
+ Cfg[:dl_path] = File.expand_path(path)
49
40
  UI.say "#{'Download directory set up to'.green} #{path.magenta}"
50
41
  break
51
42
  else
@@ -54,15 +45,5 @@ module Nehm
54
45
  end
55
46
  end
56
47
 
57
- module_function
58
-
59
- def tilde_at_top?(path)
60
- path[0] == '~'
61
- end
62
-
63
- def tilde_to_home(path)
64
- File.join(ENV['HOME'], path[1..-1])
65
- end
66
-
67
48
  end
68
49
  end
data/lib/nehm/playlist.rb CHANGED
@@ -11,9 +11,9 @@ module Nehm
11
11
  @name = name.chomp
12
12
  end
13
13
 
14
- def add_track(track_path)
14
+ def add_track(track)
15
15
  UI.say 'Adding to iTunes'
16
- AppleScript.add_track_to_playlist(track_path, @name)
16
+ AppleScript.add_track_to_playlist(track.file_path, @name)
17
17
  end
18
18
 
19
19
  def to_s
@@ -1,3 +1,4 @@
1
+ require 'nehm/applescript'
1
2
  require 'nehm/playlist'
2
3
 
3
4
  module Nehm
@@ -7,11 +8,8 @@ module Nehm
7
8
 
8
9
  module PlaylistManager
9
10
 
10
- ##
11
- # Returns default iTunes playlist (contains in ~/.nehmconfig)
12
-
13
11
  def self.default_playlist
14
- default_user_playlist || music_master_library unless OS.linux?
12
+ default_user_playlist || music_master_library if OS.mac?
15
13
  end
16
14
 
17
15
  ##
@@ -27,7 +25,7 @@ module Nehm
27
25
 
28
26
  def self.set_playlist
29
27
  loop do
30
- playlist = HighLine.new.ask('Enter name of default iTunes playlist to that you want add tracks (press Enter to set it to default iTunes Music library):')
28
+ playlist = UI.ask('Enter name of default iTunes playlist to that you want add tracks (press Enter to set it to default iTunes Music library):')
31
29
 
32
30
  # If entered nothing, unset iTunes playlist
33
31
  if playlist == ''
data/lib/nehm/track.rb CHANGED
@@ -42,7 +42,7 @@ module Nehm
42
42
 
43
43
  separators = [' - ', ' ~ ']
44
44
  separators.each do |sep|
45
- return title.split(sep) if title.include? sep
45
+ return title.split(sep, 2) if title.include? sep
46
46
  end
47
47
 
48
48
  [@hash['user']['username'], title]
@@ -53,7 +53,9 @@ module Nehm
53
53
  end
54
54
 
55
55
  def url
56
- "#{@hash['stream_url']}?client_id=#{Client::CLIENT_ID}"
56
+ # API V2 track hash has no 'stream_url' but 'uri'
57
+ dl_url = @hash['uri'] ? "#{@hash['uri']}/stream" : @hash['stream_url']
58
+ "#{dl_url}?client_id=#{HTTPClient::CLIENT_ID}"
57
59
  end
58
60
 
59
61
  def year
@@ -0,0 +1,135 @@
1
+ require 'taglib'
2
+
3
+ require 'nehm/track'
4
+
5
+ module Nehm
6
+
7
+ class TrackManager
8
+
9
+ def initialize(options)
10
+ setup_environment(options)
11
+ end
12
+
13
+ def process_tracks(tracks)
14
+ tracks.reverse_each do |track|
15
+ UI.newline
16
+ dl(track)
17
+ tag(track)
18
+ track.artwork.suicide
19
+ @playlist.add_track(track) if @playlist
20
+ UI.newline
21
+ end
22
+ UI.success 'Done!'
23
+ end
24
+
25
+ def likes(limit, offset)
26
+ likes = Client.tracks(limit, offset, :likes, @uid)
27
+ return nil if likes.empty?
28
+
29
+ filter(likes)
30
+ convert(likes)
31
+ end
32
+
33
+ def posts(limit, offset)
34
+ posts = Client.tracks(limit, offset, :posts, @uid)
35
+ return nil if posts.empty?
36
+
37
+ posts.reject! { |hash| hash['type'] == 'playlist' }
38
+ posts.map! { |hash| hash['track'] }
39
+ filter(posts)
40
+ convert(posts)
41
+ end
42
+
43
+ def track_from_url(url)
44
+ track = [Client.track(url)]
45
+ return nil if track.empty?
46
+
47
+ filter(track)
48
+ convert(track)
49
+ end
50
+
51
+ def search(query, limit, offset)
52
+ found = Client.search(query, limit, offset)
53
+ return nil if found.empty?
54
+
55
+ filter(found)
56
+ convert(found)
57
+ end
58
+
59
+ private
60
+
61
+ def dl(track)
62
+ # Downloading track
63
+ UI.say 'Downloading ' + track.full_name
64
+ `curl -# -o \"#{track.file_path}\" -L #{track.url}`
65
+
66
+ # Downloading artwork
67
+ UI.say 'Downloading artwork'
68
+ artwork = track.artwork
69
+ `curl -# -o \"#{artwork.file_path}\" -L #{artwork.url}`
70
+ end
71
+
72
+ def tag(track)
73
+ UI.say 'Setting tags'
74
+ TagLib::MPEG::File.open(track.file_path) do |file|
75
+ tag = file.id3v2_tag
76
+ tag.artist = track.artist
77
+ tag.title = track.title
78
+ tag.year = track.year
79
+
80
+ # Adding artwork
81
+ apic = TagLib::ID3v2::AttachedPictureFrame.new
82
+ apic.mime_type = 'image/jpeg'
83
+ apic.description = 'Cover'
84
+ apic.type = TagLib::ID3v2::AttachedPictureFrame::FrontCover
85
+ apic.picture = File.open(track.artwork.file_path, 'rb') { |f| f.read }
86
+ tag.add_frame(apic)
87
+
88
+ file.save
89
+ end
90
+ end
91
+
92
+ def setup_environment(options)
93
+ # Setting up user id
94
+ permalink = options[:from]
95
+ @uid = permalink ? UserManager.get_uid(permalink) : UserManager.default_uid
96
+ unless @uid
97
+ UI.error "You didn't logged in"
98
+ UI.say "Login from #{'nehm configure'.yellow} or use #{'[from PERMALINK]'.yellow} option"
99
+ UI.term
100
+ end
101
+
102
+ # Setting up download path
103
+ temp_path = options[:to]
104
+ dl_path = temp_path ? PathManager.get_path(temp_path) : PathManager.default_dl_path
105
+ if dl_path
106
+ ENV['dl_path'] = dl_path
107
+ else
108
+ UI.error "You don't set up download path!"
109
+ UI.say "Set it up from #{'nehm configure'.yellow} or use #{'[to PATH_TO_DIRECTORY]'.yellow} option"
110
+ UI.term
111
+ end
112
+
113
+ # Setting up iTunes playlist
114
+ @playlist = nil
115
+ if !options[:dl] && OS.mac?
116
+ playlist_name = options[:pl]
117
+ @playlist = playlist_name ? PlaylistManager.get_playlist(playlist_name) : PlaylistManager.default_playlist
118
+ end
119
+ end
120
+
121
+ def filter(tracks)
122
+ # Removing unstreamable tracks
123
+ first_length = tracks.length
124
+ tracks.select! { |hash| hash['streamable'] }
125
+ diff = first_length - tracks.length
126
+
127
+ UI.warning "Was skipped #{diff} undownloadable track(s)" if diff > 0
128
+ end
129
+
130
+ def convert(tracks)
131
+ tracks.map! { |hash| Track.new(hash) }
132
+ end
133
+
134
+ end
135
+ end