mdisc 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7f5547a564f2f00c77ae5e07ab4f343553ad39d
4
- data.tar.gz: ae58b382a6e761fbf450c7f9777f74aae550d04b
3
+ metadata.gz: acbc08f6f64182d057b78a6234572c33b09d65a4
4
+ data.tar.gz: d6ba36291042a95a9623a044894632523e680d15
5
5
  SHA512:
6
- metadata.gz: 05887319f06a3465625493599c567434b43a663085902e42fb1b6bdd2feba05e29722ebc9f72577c8ebc488dc76d66fda73633efd2889eb3b4d656c2a874b3a3
7
- data.tar.gz: b58337cc0d810e8e08bf7ce9cd8b8abc4e199346e818ba2357a0147d6908e0aae38fdbd8fe8fe85b64534024baed77a103fda2ff3c62c67b88b5d241516e6779
6
+ metadata.gz: 1e2841dd8b0cea3d0bc97409972624aed93409e0923fce9193acabc7d564ba7d8b487e688977a127158d9fe2fdb3c464d8eb6dc6b247ddc371d251a0fa3a91dc
7
+ data.tar.gz: d45dcb453da9a6c249ad90dc05894074bb73e02e6cdb8e80275bacb436f157bf154760f79f52e3b7423ab13468ba655163a697d79ce9a19a1ee2ee78e0856c05
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Mdisc
2
2
 
3
+ ![Mdisc](http://cl.ly/Y6Qz/mdisc.png)
4
+
3
5
  Mdisc, built with Ruby 2.1, is a command line music player that wirelessly plugs in Netease music(http://music.163.com).
4
6
 
5
7
  ## Installation
@@ -17,7 +19,7 @@ After finishing the installation, open your terminal and input `mdisc`. Music's
17
19
 
18
20
  Sorry, I do not test Mdisc in Linux. If you try it in Linux and catch some problem, please issue me. Thanks!
19
21
 
20
- ## Shortcuts
22
+ ## Shortcut
21
23
 
22
24
  | Key | Explanation | 中文释义 |
23
25
  | :---|:---------------------|:---------------------|
@@ -45,13 +47,13 @@ Sorry, I do not test Mdisc in Linux. If you try it in Linux and catch some probl
45
47
 
46
48
  Mdisc will make a new directory `~/.mdisc` and touch a file to store user's data for the first time.
47
49
 
48
- ## Big Thanks
50
+ ## Thanks
49
51
 
50
52
  [NetEase-MusicBox](https://github.com/bluetomlee/NetEase-MusicBox)
51
53
 
52
54
  [网易云音乐API分析](https://github.com/yanunon/NeteaseCloudMusic/wiki/网易云音乐API分析)
53
55
 
54
- Their great projects inspire me. Thanks!
56
+ Their great projects inspired me. Thanks!
55
57
 
56
58
  ## License
57
59
 
data/Rakefile CHANGED
@@ -1,2 +1,2 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
 
data/bin/mdisc CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require File.expand_path('../../lib/mdisc', __FILE__)
3
+ require_relative '../lib/mdisc'
4
4
 
5
5
  main = Menu.new
6
6
  main.start
@@ -1,5 +1,6 @@
1
- require "mdisc/version"
2
- require File.expand_path('../mdisc/menu', __FILE__)
3
- require File.expand_path('../mdisc/api', __FILE__)
4
- require File.expand_path('../mdisc/player', __FILE__)
5
- require File.expand_path('../mdisc/ui', __FILE__)
1
+ require_relative 'mdisc/version'
2
+ require_relative 'mdisc/menu'
3
+ require_relative 'mdisc/api'
4
+ require_relative 'mdisc/player'
5
+ require_relative 'mdisc/ui'
6
+ require_relative 'mdisc/screen'
@@ -3,63 +3,48 @@ require 'json'
3
3
  require 'digest'
4
4
 
5
5
  class NetEase
6
+ TIMEOUT = 10
7
+
6
8
  def initialize
7
9
  @header = {
8
- "Accept" => "*/*",
10
+ "Accept" => "*/*",
9
11
  "Accept-Encoding" => "gzip,deflate,sdch",
10
12
  "Accept-Language" => "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4",
11
- "Connection" => "keep-alive",
12
- "Content-Type" => "application/x-www-form-urlencoded",
13
- "Host" => "music.163.com",
14
- "Referer" => "http://music.163.com/",
15
- "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36"
16
- }
17
-
18
- @cookies = {
19
- "appver" => "2.0.2"
13
+ "Connection" => "keep-alive",
14
+ "Content-Type" => "application/x-www-form-urlencoded",
15
+ "Host" => "music.163.com",
16
+ "Referer" => "http://music.163.com/",
17
+ "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36"
20
18
  }
21
19
 
22
- @default_timeout = 10
23
- Unirest.timeout @default_timeout
24
- end
25
-
26
- def http_request(method, action, query = nil)
27
- connection =
28
- if method == 'GET'
29
- url = (query.nil? ? action : "#{action}?#{query}")
30
- Unirest.get(url, headers: @header)
31
- elsif method == 'POST'
32
- Unirest.post(action, headers: @header, parameters: query)
33
- end
34
-
35
- connection.body
20
+ Unirest.timeout TIMEOUT
36
21
  end
37
22
 
38
23
  # Log in
39
24
  def login(username, password)
40
- action = "http://music.163.com/api/login/"
25
+ action = 'http://music.163.com/api/login/'
41
26
  query = {
42
27
  "username" => username,
43
28
  "password" => Digest::MD5.hexdigest(password),
44
29
  "rememberLogin" => "true"
45
30
  }
46
- begin
47
- return http_request('POST', action, query)
48
- rescue => e
49
- return {"code" => 501}
50
- end
31
+
32
+ http_request('POST', action, query)
33
+ rescue => e
34
+ {"code" => 501}
51
35
  end
52
36
 
53
37
  # User's playlists
54
38
  def user_playlists(uid, offset = 0, limit = 100)
55
39
  action = "http://music.163.com/api/user/playlist/?offset=#{offset}&limit=#{limit}&uid=#{uid}"
56
40
  data = http_request('GET', action)
41
+
57
42
  data['playlist']
58
43
  end
59
44
 
60
45
  # Search song(1),artist(100),album(10),playlist(1000),user(1002)
61
46
  def search(s, stype = 1, offset = 0, limit = 100)
62
- action = "http://music.163.com/api/search/get/web"
47
+ action = 'http://music.163.com/api/search/get/web'
63
48
  query = {
64
49
  "s" => s,
65
50
  "type" => stype,
@@ -67,6 +52,7 @@ class NetEase
67
52
  "total" => true,
68
53
  "limit" => limit
69
54
  }
55
+
70
56
  http_request('POST', action, query)
71
57
  end
72
58
 
@@ -75,25 +61,27 @@ class NetEase
75
61
  def new_albums(offset=0, limit=50)
76
62
  action = "http://music.163.com/api/album/new?area=ALL&offset=#{offset}&total=true&limit=#{limit}"
77
63
  data = http_request('GET', action)
64
+
78
65
  data['albums']
79
66
  end
80
67
 
81
68
  # Top playlists
82
69
  # hot||new http://music.163.com/#/discover/playlist/
83
-
84
70
  # '全部' => '%E5%85%A8%E9%83%A8'
85
71
  def top_playlists(category = '%E5%85%A8%E9%83%A8', order = 'hot', offset = 0, limit = 100)
86
- flag = (offset > 0 ? true : false)
72
+ flag = offset > 0 ? true : false
87
73
  action = "http://music.163.com/api/playlist/list?cat=#{category}&order=#{order}&offset=#{offset}&total=#{flag}&limit=#{limit}"
88
74
  data = http_request('GET', action)
89
- return data['playlists']
75
+
76
+ data['playlists']
90
77
  end
91
78
 
92
79
  # Playlist's details
93
80
  def playlist_detail(playlist_id)
94
81
  action = "http://music.163.com/api/playlist/detail?id=#{playlist_id}"
95
82
  data = http_request('GET', action)
96
- return data['result']['tracks']
83
+
84
+ data['result']['tracks']
97
85
  end
98
86
 
99
87
  # Top artists
@@ -101,61 +89,70 @@ class NetEase
101
89
  def top_artists(offset = 0, limit = 100)
102
90
  action = "http://music.163.com/api/artist/top?offset=#{offset}&total=false&limit=#{limit}"
103
91
  data = http_request('GET', action)
104
- return data['artists']
92
+
93
+ data['artists']
105
94
  end
106
95
 
107
96
  # Top songlist
108
97
  # http://music.163.com/#/discover/toplist 100
109
98
  def top_songlist
110
- action = "http://music.163.com/discover/toplist"
99
+ action = 'http://music.163.com/discover/toplist'
111
100
  connection = http_request('GET', action)
112
- songids = connection.scan(/\/song\?id=(\d+)/)
113
- return [] if songids == []
114
- return songs_detail(songids.uniq)
101
+ songids = connection.scan(/\/song\?id=(\d+)/).uniq
102
+
103
+ songs_detail songids
115
104
  end
116
105
 
117
106
  # Songs to which a artist belongs.
118
107
  def artists(artist_id)
119
108
  action = "http://music.163.com/api/artist/#{artist_id}"
120
109
  data = http_request('GET', action)
121
- return data['hotSongs']
110
+
111
+ data['hotSongs']
122
112
  end
123
113
 
124
114
  # album id -> song id set
125
115
  def album(album_id)
126
116
  action = "http://music.163.com/api/album/#{album_id}"
127
117
  data = http_request('GET', action)
128
- return data['album']['songs']
118
+
119
+ data['album']['songs']
129
120
  end
130
121
 
131
122
  # song ids -> song urls (details)
132
- def songs_detail(ids, offset=0)
123
+ def songs_detail(ids, offset = 0)
124
+ return [] if ids.empty?
125
+
133
126
  tmpids = ids[offset, 100]
134
127
  action = "http://music.163.com/api/song/detail?ids=[#{tmpids.join(',')}]"
135
128
  data = http_request('GET', action)
136
- return data['songs']
129
+
130
+ data['songs']
137
131
  end
138
132
 
139
133
  # song id -> song url (details)
140
- def song_detail(music_id)
141
- id = music_id.join(',')
134
+ def song_detail(song_id)
135
+ id = song_id.join(',')
142
136
  action = "http://music.163.com/api/song/detail/?id=#{id}&ids=[#{id}]"
143
137
  data = http_request('GET', action)
144
- return data['songs']
138
+
139
+ data['songs']
145
140
  end
146
141
 
147
142
  # DJ channels: hot today(0), week(10), history(20), new(30)
148
143
  def djchannels(stype = 0, offset = 0, limit = 50)
149
144
  action = "http://music.163.com/discover/djchannel?type=#{stype}&offset=#{offset}&limit=#{limit}"
150
145
  connection = http_request('GET', action)
151
- channelids = connection.scan(/\/dj\?id=(\d+)/) || []
152
- return [] if channelids.empty?
153
- return channel_detail(channelids.uniq)
146
+ channelids = connection.scan(/\/dj\?id=(\d+)/).uniq || []
147
+
148
+ channel_detail channelids
154
149
  end
155
150
 
156
151
  # DJchannel (id, channel_name) ids -> song urls (details)
157
152
  # channels -> songs
158
153
  def channel_detail(channelids)
154
+ return [] if channelids.empty?
155
+
159
156
  channels = []
160
157
 
161
158
  # ["xxxxxx"] -> "xxxxxx"
@@ -164,7 +161,7 @@ class NetEase
164
161
  begin
165
162
  data = http_request('GET', action)
166
163
  channel = dig_info(data['program']['mainSong'], 'channels')
167
- channels.push(channel)
164
+ channels.push channel
168
165
  rescue => e
169
166
  next
170
167
  end
@@ -175,6 +172,7 @@ class NetEase
175
172
 
176
173
  def dig_info(data, dig_type)
177
174
  tmp = []
175
+
178
176
  case dig_type
179
177
  when 'songs'
180
178
  data.each do |song|
@@ -190,9 +188,8 @@ class NetEase
190
188
  song_info['artist'] = song['artist'].join('')
191
189
  elsif song.include? 'artists'
192
190
  song['artists'].each do |artist|
193
- song_info['artist'].push(artist['name'].strip)
191
+ song_info['artist'].push artist['name'].strip
194
192
  end
195
- song_info['artist'].join(',')
196
193
  else
197
194
  song_info['artist'] = '未知艺术家'
198
195
  end
@@ -244,4 +241,19 @@ class NetEase
244
241
 
245
242
  tmp
246
243
  end
244
+
245
+ private
246
+
247
+ def http_request(method, action, query = nil)
248
+ connection =
249
+ if method == 'GET'
250
+ Unirest.get(action, headers: @header)
251
+ elsif method == 'POST'
252
+ Unirest.post(action, headers: @header, parameters: query)
253
+ end
254
+
255
+ connection.body
256
+ rescue => e
257
+ []
258
+ end
247
259
  end
@@ -24,6 +24,8 @@ SHORTCUT = [
24
24
  ]
25
25
 
26
26
  class Menu
27
+ WAIT_TIME = 0.1
28
+
27
29
  attr_accessor :player, :ui, :netease, :screen
28
30
 
29
31
  def initialize
@@ -44,8 +46,6 @@ class Menu
44
46
  @collection = []
45
47
  @playlists = []
46
48
  @account = {}
47
-
48
- @wait = 0.1
49
49
  @carousel = ->(left, right, x){x < left ? right : (x > right ? left : x)}
50
50
 
51
51
  read_data
@@ -53,7 +53,7 @@ class Menu
53
53
 
54
54
  def start
55
55
  ui.build_menu(@datatype, @title, @datalist, @offset, @index, @step)
56
- @stack.push([@datatype, @title, @datalist, @offset, @index, @step])
56
+ @stack.push [@datatype, @title, @datalist, @offset, @index, @step]
57
57
 
58
58
  loop do
59
59
  datatype = @datatype
@@ -92,7 +92,8 @@ class Menu
92
92
  @offset = @offset + step
93
93
  @index = (index + step).divmod(step)[0] * step
94
94
 
95
- # Forward
95
+ # If highlighted item is a menu or playlists, just enter it.
96
+ # If highlighted item is a song or an dj channel, just play it.
96
97
  when 'l'
97
98
  next if @datatype == 'help'
98
99
  if @datatype == 'songs' || @datatype == 'djchannels'
@@ -100,7 +101,7 @@ class Menu
100
101
  @present_songs = [datatype, title, datalist, offset, index]
101
102
  else
102
103
  ui.build_loading
103
- dispatch_enter(idx)
104
+ dispatch_enter idx
104
105
  @index = 0
105
106
  @offset = 0
106
107
  end
@@ -118,22 +119,22 @@ class Menu
118
119
  # Next song
119
120
  when ']'
120
121
  player.next
121
- sleep @wait
122
+ sleep WAIT_TIME
122
123
 
123
124
  # Previous song
124
125
  when '['
125
126
  player.prev
126
- sleep @wait
127
+ sleep WAIT_TIME
127
128
 
128
129
  # Play or pause a song.
129
130
  when ' '
130
131
  player.play(datatype, datalist, idx)
131
- sleep @wait
132
+ sleep WAIT_TIME
132
133
 
133
134
  # Load present playlist.
134
135
  when 'p'
135
136
  next if @present_songs.empty?
136
- @stack.push([datatype, title, datalist, offset, index])
137
+ @stack.push [datatype, title, datalist, offset, index]
137
138
  @datatype, @title, @datalist, @offset, @index = @present_songs[0], @present_songs[1], @present_songs[2], @present_songs[3], @present_songs[4]
138
139
 
139
140
  # Star a song, a playlist or an album.
@@ -151,7 +152,7 @@ class Menu
151
152
 
152
153
  # Load favorite playlists.
153
154
  when 't'
154
- @stack.push([datatype, title, datalist, offset, index])
155
+ @stack.push [datatype, title, datalist, offset, index]
155
156
  @datatype = 'playlists'
156
157
  @title = '网易云音乐 > 收藏精选歌单'
157
158
  @datalist = @playlists
@@ -160,25 +161,25 @@ class Menu
160
161
 
161
162
  # Load favorite songs.
162
163
  when 'c'
163
- @stack.push([datatype, title, datalist, offset, index])
164
+ @stack.push [datatype, title, datalist, offset, index]
164
165
  @datatype = 'songs'
165
166
  @title = '网易云音乐 > 收藏歌曲列表'
166
167
  @datalist = @collection
167
168
  @offset = 0
168
169
  @index = 0
169
170
 
170
- # Load favorite albums
171
+ # Load favorite albums.
171
172
  when 'a'
172
- @stack.push([datatype, title, datalist, offset, index])
173
+ @stack.push [datatype, title, datalist, offset, index]
173
174
  @datatype = 'albums'
174
175
  @title = '网易云音乐 > 收藏专辑'
175
176
  @datalist = @albums
176
177
  @offset = 0
177
178
  @index = 0
178
179
 
179
- # Load favorite dj channels
180
+ # Load favorite dj channels.
180
181
  when 'z'
181
- @stack.push([datatype, title, datalist, offset, index])
182
+ @stack.push [datatype, title, datalist, offset, index]
182
183
  @datatype = 'djchannels'
183
184
  @title = '网易云音乐 > 收藏 DJ 节目'
184
185
  @datalist = @djs
@@ -187,19 +188,17 @@ class Menu
187
188
 
188
189
  # Remove an entry from the present list.
189
190
  when 'r'
190
- if (datatype != 'main') && !datalist.empty?
191
- @datalist.delete_at(idx)
192
- @index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx]
193
- end
191
+ next if datatype == 'main' || datalist.empty?
192
+ @datalist.delete_at idx
193
+ @index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx]
194
194
 
195
- # Main menu.
195
+ # Main menu
196
196
  when 'm'
197
- if datatype != 'main'
198
- @stack.push([datatype, title, datalist, offset, index])
199
- @datatype, @title, @datalist = @stack[0][0], @stack[0][1], @stack[0][2]
200
- @offset = 0
201
- @index = 0
202
- end
197
+ next if datatype == 'main'
198
+ @stack.push [datatype, title, datalist, offset, index]
199
+ @datatype, @title, @datalist = @stack[0][0], @stack[0][1], @stack[0][2]
200
+ @offset = 0
201
+ @index = 0
203
202
 
204
203
  end
205
204
 
@@ -212,13 +211,12 @@ class Menu
212
211
  end
213
212
 
214
213
  def dispatch_enter(idx)
215
- # netease = @netease
216
214
  datatype = @datatype
217
215
  title = @title
218
216
  datalist = @datalist
219
217
  offset = @offset
220
218
  index = @index
221
- @stack.push([datatype, title, datalist, offset, index])
219
+ @stack.push [datatype, title, datalist, offset, index]
222
220
 
223
221
  case datatype
224
222
  when 'main'
@@ -226,24 +224,24 @@ class Menu
226
224
 
227
225
  # Hot songs to which a artist belongs.
228
226
  when 'artists'
229
- artist_id = datalist[idx]['artist_id']
230
- songs = netease.artists(artist_id)
227
+ id = datalist[idx]['artist_id']
228
+ songs = netease.artists id
231
229
  @datatype = 'songs'
232
230
  @datalist = netease.dig_info(songs, 'songs')
233
231
  @title += " > #{datalist[idx]['aritsts_name']}"
234
232
 
235
233
  # All songs to which an album belongs.
236
234
  when 'albums'
237
- album_id = datalist[idx]['album_id']
238
- songs = netease.album(album_id)
235
+ id = datalist[idx]['album_id']
236
+ songs = netease.album id
239
237
  @datatype = 'songs'
240
238
  @datalist = netease.dig_info(songs, 'songs')
241
239
  @title += " > #{datalist[idx]['albums_name']}"
242
240
 
243
241
  # All songs to which a playlist belongs.
244
242
  when 'playlists'
245
- playlist_id = datalist[idx]['playlist_id']
246
- songs = netease.playlist_detail(playlist_id)
243
+ id = datalist[idx]['playlist_id']
244
+ songs = netease.playlist_detail id
247
245
  @datatype = 'songs'
248
246
  @datalist = netease.dig_info(songs, 'songs')
249
247
  @title += " > #{datalist[idx]['playlists_name']}"
@@ -251,10 +249,7 @@ class Menu
251
249
  end
252
250
 
253
251
  def choice_channel(idx)
254
- # netease = @netease
255
-
256
252
  case idx
257
-
258
253
  # Top
259
254
  when 0
260
255
  songs = netease.top_songlist
@@ -286,21 +281,10 @@ class Menu
286
281
  # My playlist
287
282
  when 4
288
283
  # Require user's account before fetching his playlists.
289
- if !@userid
290
- user_info = netease.login(@account[0], @account[1]) unless @account.empty?
291
-
292
- if @account == {} || user_info['code'] != 200
293
- data = ui.build_login
294
- return if data == -1
295
- user_info, @account = data[0], data[1]
296
- end
297
-
298
- @username = user_info['profile']['nickname']
299
- @userid = user_info['account']['id']
300
- end
284
+ check_user_id
301
285
 
302
286
  # Fetch this user's all playlists while he logs in successfully.
303
- my_playlist = netease.user_playlists(@userid)
287
+ my_playlist = netease.user_playlists @userid
304
288
  @datalist = netease.dig_info(my_playlist, 'playlists')
305
289
  @datatype = 'playlists'
306
290
  @title += " > #{@username} 的歌单"
@@ -311,7 +295,7 @@ class Menu
311
295
  @title += ' > DJ 节目'
312
296
  @datalist = netease.djchannels
313
297
 
314
- # Favorite things.
298
+ # Favorite things
315
299
  when 6
316
300
  favorite
317
301
 
@@ -331,17 +315,15 @@ class Menu
331
315
  end
332
316
 
333
317
  def favorite
334
- # ui = @ui
335
318
  x = ui.build_favorite_menu
336
319
 
337
320
  if (1..4).include? x.to_i
338
- @stack.push([@datatype, @title, @datalist, @offset, @index])
321
+ @stack.push [@datatype, @title, @datalist, @offset, @index]
339
322
  @index = 0
340
323
  @offset = 0
341
324
  end
342
325
 
343
326
  case x
344
-
345
327
  when '1'
346
328
  @datatype = 'songs'
347
329
  @datalist = @collection
@@ -366,48 +348,50 @@ class Menu
366
348
  end
367
349
 
368
350
  def search
369
- # ui = @ui
370
351
  x = ui.build_search_menu
371
352
 
372
353
  if (1..4).include? x.to_i
373
- @stack.push([@datatype, @title, @datalist, @offset, @index])
354
+ @stack.push [@datatype, @title, @datalist, @offset, @index]
374
355
  @index = 0
375
356
  @offset = 0
376
357
  end
377
358
 
378
359
  case x
379
-
380
360
  when '1'
381
361
  @datatype = 'songs'
382
- @datalist = ui.build_search('songs')
362
+ @datalist = ui.build_search @datatype
383
363
  @title = '歌曲搜索列表'
384
364
 
385
365
  when '2'
386
366
  @datatype = 'artists'
387
- @datalist = ui.build_search('artists')
367
+ @datalist = ui.build_search @datatype
388
368
  @title = '艺术家搜索列表'
389
369
 
390
370
  when '3'
391
371
  @datatype = 'albums'
392
- @datalist = ui.build_search('albums')
372
+ @datalist = ui.build_search @datatype
393
373
  @title = '专辑搜索列表'
394
374
 
395
375
  when '4'
396
376
  @datatype = 'playlists'
397
- @datalist = ui.build_search('playlists')
377
+ @datalist = ui.build_search @datatype
398
378
  @title = '精选歌单搜索列表'
399
379
  end
400
380
  end
401
381
 
402
382
  private
403
383
 
404
- def check_mdisc_dir
405
- Dir.mkdir File.expand_path("~/.mdisc") unless Dir.exist? File.expand_path("~/.mdisc")
384
+ def check_dir
385
+ dir = "#{ENV['HOME']}/.mdisc"
386
+ unless Dir.exist? dir
387
+ Dir.mkdir dir
388
+ end
406
389
  end
407
390
 
408
391
  def read_data
409
- check_mdisc_dir
410
- user_file = File.expand_path("~/.mdisc/flavor.json")
392
+ check_dir
393
+
394
+ user_file = "#{ENV['HOME']}/.mdisc/flavor.json"
411
395
  return unless File.exist? user_file
412
396
 
413
397
  data = JSON.parse(File.read(user_file))
@@ -419,7 +403,10 @@ class Menu
419
403
  end
420
404
 
421
405
  def write_data
422
- user_file = File.expand_path("~/.mdisc/flavor.json")
406
+ check_dir
407
+
408
+ user_file = "#{ENV['HOME']}/.mdisc/flavor.json"
409
+
423
410
  data = {
424
411
  :account => @account,
425
412
  :collection => @collection,
@@ -432,4 +419,19 @@ class Menu
432
419
  f.write(JSON.generate(data))
433
420
  end
434
421
  end
422
+
423
+ def check_user_id
424
+ if !@userid
425
+ user_info = netease.login(@account[0], @account[1]) unless @account.empty?
426
+
427
+ if @account == {} || user_info['code'] != 200
428
+ data = ui.build_login
429
+ return if data == -1
430
+ user_info, @account = data[0], data[1]
431
+ end
432
+
433
+ @username = user_info['profile']['nickname']
434
+ @userid = user_info['account']['id']
435
+ end
436
+ end
435
437
  end
@@ -1,6 +1,8 @@
1
1
  require 'open4'
2
2
 
3
3
  class Player
4
+ WAIT_TIME = 0.5
5
+
4
6
  attr_accessor :ui
5
7
 
6
8
  def initialize
@@ -12,7 +14,6 @@ class Player
12
14
  @pause_flag = false
13
15
  @songs = []
14
16
  @idx = 0
15
- @wait = 0.5
16
17
  @carousel = ->(left, right, x){x < left ? right : (x > right ? left : x)}
17
18
  end
18
19
 
@@ -48,7 +49,7 @@ class Player
48
49
 
49
50
  def switch
50
51
  stop
51
- sleep @wait
52
+ sleep WAIT_TIME
52
53
  recall
53
54
  end
54
55
 
@@ -83,7 +84,7 @@ class Player
83
84
 
84
85
  def next
85
86
  stop
86
- sleep @wait
87
+ sleep WAIT_TIME
87
88
 
88
89
  @idx = @carousel[0, @songs.size - 1, @idx + 1]
89
90
  recall
@@ -91,7 +92,7 @@ class Player
91
92
 
92
93
  def prev
93
94
  stop
94
- sleep @wait
95
+ sleep WAIT_TIME
95
96
 
96
97
  @idx = @carousel[0, @songs.size - 1, @idx - 1]
97
98
  recall
@@ -0,0 +1,46 @@
1
+ require 'curses'
2
+
3
+ # The curses library only provides limit functions.
4
+ # Thus we need to add more wrapper functions based on curses.
5
+
6
+ class Screen
7
+ def initialize(height = 25, width = 80)
8
+ Curses.init_screen
9
+ Curses.start_color
10
+ Curses.cbreak
11
+ Curses.stdscr.keypad true
12
+ Curses.init_pair(1, Curses::COLOR_BLUE, Curses::COLOR_BLACK)
13
+ Curses.init_pair(2, Curses::COLOR_CYAN, Curses::COLOR_BLACK)
14
+ Curses.init_pair(3, Curses::COLOR_RED, Curses::COLOR_BLACK)
15
+ Curses.init_pair(4, Curses::COLOR_MAGENTA, Curses::COLOR_BLACK)
16
+ # height, width, top, left
17
+ @draw = Curses::Window.new(height, width, 0, 0)
18
+ end
19
+
20
+ def line(y, x, string, num = 0)
21
+ color = Curses.color_pair num
22
+ @draw.setpos(y, x)
23
+ @draw.clrtoeol
24
+ @draw.attrset(color)
25
+ @draw.addstr(string)
26
+ end
27
+
28
+ def clear(top, bottom)
29
+ (top..bottom).each do |i|
30
+ @draw.setpos(i, 0)
31
+ @draw.clrtoeol
32
+ end
33
+ end
34
+
35
+ def refresh
36
+ @draw.refresh
37
+ end
38
+
39
+ def getch
40
+ @draw.getch
41
+ end
42
+
43
+ def setpos(*args)
44
+ @draw.setpos *args
45
+ end
46
+ end
@@ -44,132 +44,86 @@ class Ui
44
44
  PLAYER_POINTER_X = PLAYER_X - 3
45
45
 
46
46
  def initialize
47
- Curses.init_screen
48
- Curses.start_color
49
- Curses.cbreak
50
- Curses.stdscr.keypad(true)
51
- Curses.init_pair(1, Curses::COLOR_BLUE, Curses::COLOR_BLACK)
52
- Curses.init_pair(2, Curses::COLOR_CYAN, Curses::COLOR_BLACK)
53
- Curses.init_pair(3, Curses::COLOR_RED, Curses::COLOR_BLACK)
54
- Curses.init_pair(4, Curses::COLOR_MAGENTA, Curses::COLOR_BLACK)
55
-
56
- # height, width, top, left
57
- self.screen = Curses::Window.new(SCREEN_HEIGHT, SCREEN_WIDTH, 0, 0)
58
-
47
+ self.screen = Screen.new(SCREEN_HEIGHT, SCREEN_WIDTH)
59
48
  self.netease = NetEase.new
60
49
  end
61
50
 
62
51
  def build_playinfo(song_name, artist, pause = false)
63
52
  if pause
64
- putstr(screen, PLAYER_STATUS_Y, PLAYER_NOTE_X, '■', Curses.color_pair(3))
53
+ screen.line(PLAYER_STATUS_Y, PLAYER_NOTE_X, '■', 3)
65
54
  else
66
- putstr(screen, PLAYER_STATUS_Y, PLAYER_NOTE_X, '▶', Curses.color_pair(3))
55
+ screen.line(PLAYER_STATUS_Y, PLAYER_NOTE_X, '▶', 3)
67
56
  end
68
57
 
69
- sn = pretty_format(song_name, 0, 32)
70
- at = pretty_format(artist, 0, 28)
58
+ sn = pretty(song_name, 0, 32)
59
+ at = pretty(artist, 0, 28)
71
60
  info = "#{sn} - #{at}"
72
- putstr(screen, PLAYER_STATUS_Y, PLAYER_X, info, Curses.color_pair(4))
61
+ screen.line(PLAYER_STATUS_Y, PLAYER_X, info, 4)
73
62
  screen.refresh
74
63
  end
75
64
 
76
65
  def build_loading
77
- clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
78
- putstr(screen, PLAYER_CONTENT_Y, PLAYER_X, 'loading...', Curses.color_pair(1))
66
+ screen.clear(PLAYER_CONTENT_Y, SCREEN_HEIGHT)
67
+ screen.line(PLAYER_CONTENT_Y, PLAYER_X, 'loading...', 1)
79
68
  screen.refresh
80
69
  end
81
70
 
82
71
  def build_menu(datatype, title, datalist, offset, index, step)
83
- title = pretty_format(title, 0, 52)
72
+ title = pretty(title, 0, 52)
84
73
 
85
- clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
86
- putstr(screen, PLAYER_TITLE_Y, PLAYER_X, title, Curses.color_pair(1))
74
+ screen.clear(PLAYER_CONTENT_Y, SCREEN_HEIGHT)
75
+ screen.line(PLAYER_TITLE_Y, PLAYER_X, title, 1)
87
76
 
88
77
  if datalist.size == 0
89
- putstr(screen, PLAYER_CONTENT_Y, PLAYER_X, '没有内容 Orz')
78
+ screen.line(PLAYER_CONTENT_Y, PLAYER_X, '没有内容 Orz')
90
79
  else
80
+ entries = offset...[datalist.length, offset + step].min
81
+
91
82
  case datatype
92
83
  when 'main'
93
- (offset...[datalist.length, offset + step].min).each do |i|
94
- if i == index
95
- info = "♩ #{i}. #{datalist[i]}"
96
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, info, Curses.color_pair(2))
97
- else
98
- info = "#{i}. #{datalist[i]}"
99
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
100
- end
84
+ show(entries, index, offset, datalist) do |i, datalist|
85
+ "#{i}. #{datalist[i]}"
101
86
  end
102
87
 
103
- putstr(screen, PLAYER_INFO_Y, PLAYER_X, 'Crafted with ❤ by cosmtrek', Curses.color_pair(3))
88
+ screen.line(PLAYER_INFO_Y, PLAYER_X, 'Crafted with ❤ by cosmtrek', 3)
104
89
 
105
90
  when 'songs'
106
- (offset...[datalist.length, offset + step].min).each do |i|
107
- sn = pretty_format(datalist[i]['song_name'], 0, 32)
108
- at = pretty_format(datalist[i]['artist'], 0, 28)
109
-
110
- if i == index
111
- info = "♩ #{i}. #{sn} - #{at}"
112
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, info, Curses.color_pair(2))
113
- else
114
- info = "#{i}. #{sn} - #{at}"
115
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
116
- end
91
+ show(entries, index, offset, datalist) do |i, datalist|
92
+ sn = pretty(datalist[i]['song_name'], 0, 32)
93
+ at = pretty(datalist[i]['artist'], 0, 28)
94
+ "#{i}. #{sn} - #{at}"
117
95
  end
118
96
 
119
97
  when 'artists'
120
- (offset...[datalist.length, offset + step].min).each do |i|
121
- an = pretty_format(datalist[i]['artists_name'], 0, 32)
122
- if i == index
123
- info = "♩ #{i}. #{an}"
124
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, info, Curses.color_pair(2))
125
- else
126
- info = "#{i}. #{an}"
127
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
128
- end
98
+ show(entries, index, offset, datalist) do |i, datalist|
99
+ an = pretty(datalist[i]['artists_name'], 0, 32)
100
+ "#{i}. #{an}"
129
101
  end
130
102
 
131
103
  when 'albums'
132
- (offset...[datalist.length, offset + step].min).each do |i|
133
- al = pretty_format(datalist[i]['albums_name'], 0, 32)
134
- an = pretty_format(datalist[i]['artists_name'], 0, 28)
135
- if i == index
136
- info = "♩ #{i}. #{al} - #{an}"
137
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, info, Curses.color_pair(2))
138
- else
139
- info = "#{i}. #{al} - #{an}"
140
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
141
- end
104
+ show(entries, index, offset, datalist) do |i, datalist|
105
+ al = pretty(datalist[i]['albums_name'], 0, 32)
106
+ an = pretty(datalist[i]['artists_name'], 0, 28)
107
+ "#{i}. #{al} - #{an}"
142
108
  end
143
109
 
144
110
  when 'playlists'
145
- (offset...[datalist.length, offset + step].min).each do |i|
146
- pn = pretty_format(datalist[i]['playlists_name'], 0, 32);
147
- cn = pretty_format(datalist[i]['creator_name'], 0, 28);
148
- if i == index
149
- info = "♩ #{i}. #{pn} - #{cn}"
150
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, info, Curses.color_pair(2))
151
- else
152
- info = "#{i}. #{pn} - #{cn}"
153
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
154
- end
111
+ show(entries, index, offset, datalist) do |i, datalist|
112
+ pn = pretty(datalist[i]['playlists_name'], 0, 32);
113
+ cn = pretty(datalist[i]['creator_name'], 0, 28);
114
+ "#{i}. #{pn} - #{cn}"
155
115
  end
156
116
 
157
117
  when 'djchannels'
158
- (offset...[datalist.length, offset + step].min).each do |i|
159
- sn = pretty_format(datalist[i][0]['song_name'], 0, 32)
160
- if i == index
161
- info = "♩ #{i}. #{sn}"
162
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, info, Curses.color_pair(2))
163
- else
164
- info = "#{i}. #{sn}"
165
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
166
- end
118
+ show(entries, index, offset, datalist) do |i, datalist|
119
+ sn = pretty(datalist[i][0]['song_name'], 0, 32)
120
+ "#{i}. #{sn}"
167
121
  end
168
122
 
169
123
  when 'help'
170
- (offset...[datalist.length, offset + step].min).each do |i|
124
+ entries.each do |i|
171
125
  info = "#{i}. #{datalist[i][0]} #{datalist[i][1]} #{datalist[i][2]}"
172
- putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
126
+ screen.line(i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
173
127
  end
174
128
  end
175
129
  end
@@ -219,28 +173,31 @@ class Ui
219
173
  end
220
174
 
221
175
  end
176
+
177
+ # If no results, then just return empty array.
178
+ []
222
179
  end
223
180
 
224
181
  def build_favorite_menu
225
- clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
226
- putstr(screen, PLAYER_CONTENT_Y, PLAYER_X, '选择收藏条目类型:', Curses.color_pair(1))
227
- putstr(screen, PLAYER_CONTENT_Y + 1, PLAYER_X, '1 - 歌曲')
228
- putstr(screen, PLAYER_CONTENT_Y + 2, PLAYER_X, '2 - 精选歌单')
229
- putstr(screen, PLAYER_CONTENT_Y + 3, PLAYER_X, '3 - 专辑')
230
- putstr(screen, PLAYER_CONTENT_Y + 4, PLAYER_X, '4 - DJ 节目')
231
- putstr(screen, PLAYER_CONTENT_Y + 6, PLAYER_X, '请键入对应数字:', Curses.color_pair(2))
182
+ screen.clear(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
183
+ screen.line(PLAYER_CONTENT_Y, PLAYER_X, '选择收藏条目类型:', 1)
184
+ screen.line(PLAYER_CONTENT_Y + 1, PLAYER_X, '1 - 歌曲')
185
+ screen.line(PLAYER_CONTENT_Y + 2, PLAYER_X, '2 - 精选歌单')
186
+ screen.line(PLAYER_CONTENT_Y + 3, PLAYER_X, '3 - 专辑')
187
+ screen.line(PLAYER_CONTENT_Y + 4, PLAYER_X, '4 - DJ 节目')
188
+ screen.line(PLAYER_CONTENT_Y + 6, PLAYER_X, '请键入对应数字:', 2)
232
189
  screen.refresh
233
190
  screen.getch
234
191
  end
235
192
 
236
193
  def build_search_menu
237
- clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
238
- putstr(screen, PLAYER_CONTENT_Y, PLAYER_X, '选择搜索类型:', Curses.color_pair(1))
239
- putstr(screen, PLAYER_CONTENT_Y + 1, PLAYER_X, '1 - 歌曲')
240
- putstr(screen, PLAYER_CONTENT_Y + 2, PLAYER_X, '2 - 艺术家')
241
- putstr(screen, PLAYER_CONTENT_Y + 3, PLAYER_X, '3 - 专辑')
242
- putstr(screen, PLAYER_CONTENT_Y + 4, PLAYER_X, '4 - 精选歌单')
243
- putstr(screen, PLAYER_CONTENT_Y + 6, PLAYER_X, '请键入对应数字:', Curses.color_pair(2))
194
+ screen.clear(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
195
+ screen.line(PLAYER_CONTENT_Y, PLAYER_X, '选择搜索类型:', 1)
196
+ screen.line(PLAYER_CONTENT_Y + 1, PLAYER_X, '1 - 歌曲')
197
+ screen.line(PLAYER_CONTENT_Y + 2, PLAYER_X, '2 - 艺术家')
198
+ screen.line(PLAYER_CONTENT_Y + 3, PLAYER_X, '3 - 专辑')
199
+ screen.line(PLAYER_CONTENT_Y + 4, PLAYER_X, '4 - 精选歌单')
200
+ screen.line(PLAYER_CONTENT_Y + 6, PLAYER_X, '请键入对应数字:', 2)
244
201
  screen.refresh
245
202
  screen.getch
246
203
  end
@@ -260,18 +217,18 @@ class Ui
260
217
  end
261
218
 
262
219
  def build_login_error
263
- clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
264
- putstr(screen, PLAYER_CONTENT_Y + 1, PLAYER_X, 'oh,出现错误 Orz', Curses.color_pair(2))
265
- putstr(screen, PLAYER_CONTENT_Y + 2, PLAYER_X, '1 - 再试一次')
266
- putstr(screen, PLAYER_CONTENT_Y + 3, PLAYER_X, '2 - 稍后再试')
267
- putstr(screen, PLAYER_CONTENT_Y + 5, PLAYER_X, '请键入对应数字:', Curses.color_pair(2))
220
+ screen.clear(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
221
+ screen.line(PLAYER_CONTENT_Y + 1, PLAYER_X, 'oh,出现错误 Orz', 2)
222
+ screen.line(PLAYER_CONTENT_Y + 2, PLAYER_X, '1 - 再试一次')
223
+ screen.line(PLAYER_CONTENT_Y + 3, PLAYER_X, '2 - 稍后再试')
224
+ screen.line(PLAYER_CONTENT_Y + 5, PLAYER_X, '请键入对应数字:', 2)
268
225
  screen.refresh
269
226
  screen.getch
270
227
  end
271
228
 
272
229
  def get_param(prompt_str)
273
- clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
274
- putstr(screen, PLAYER_CONTENT_Y, PLAYER_X, prompt_str, Curses.color_pair(1))
230
+ screen.clear(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
231
+ screen.line(PLAYER_CONTENT_Y, PLAYER_X, prompt_str, 1)
275
232
  screen.setpos(PLAYER_CONTENT_Y + 2, PLAYER_X)
276
233
  params = screen.getstr
277
234
  if params.strip.nil?
@@ -283,25 +240,27 @@ class Ui
283
240
 
284
241
  private
285
242
 
286
- def putstr(screen, y, x, string, color = Curses.color_pair(0))
287
- screen.setpos(y, x)
288
- screen.clrtoeol
289
- screen.attrset(color)
290
- screen.addstr(string)
243
+ def pretty(info, start, length)
244
+ if info.size >= length
245
+ "#{info[start, length]}..."
246
+ else
247
+ info
248
+ end
291
249
  end
292
250
 
293
- def clear_to_bottom(screen, top, bottom)
294
- (top..bottom).each do |i|
295
- screen.setpos(i, 0)
296
- screen.clrtoeol
251
+ def highlight_or_not(i, index, offset, info)
252
+ if i == index
253
+ highlight = "♩ #{info}"
254
+ screen.line(i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, highlight, 2)
255
+ else
256
+ screen.line(i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
297
257
  end
298
258
  end
299
259
 
300
- def pretty_format(info, start, length)
301
- if info.size >= length
302
- "#{info[start, length]}..."
303
- else
304
- info
260
+ def show(entries, index, offset, datalist)
261
+ entries.each do |i|
262
+ info = yield(i, datalist) # Get custom info.
263
+ highlight_or_not(i, index, offset, info)
305
264
  end
306
265
  end
307
266
  end
@@ -1,3 +1,3 @@
1
1
  module Mdisc
2
- VERSION = "0.1.1"
2
+ VERSION = '0.1.2'
3
3
  end
@@ -1,23 +1,24 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'mdisc/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "mdisc"
8
+ spec.name = 'mdisc'
8
9
  spec.version = Mdisc::VERSION
9
- spec.authors = ["Rick Yu"]
10
- spec.email = ["cosmtrek@gmail.com"]
10
+ spec.authors = ['Rick Yu']
11
+ spec.email = ['cosmtrek@gmail.com']
11
12
  spec.summary = %q{A local music player based on Netease music.}
12
13
  spec.description = %q{}
13
- spec.homepage = "https://github.com/cosmtrek/mdisc"
14
- spec.license = "MIT"
14
+ spec.homepage = 'https://github.com/cosmtrek/mdisc'
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.7"
22
- spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency 'bundler', '~> 1.7'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
23
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdisc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Yu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-18 00:00:00.000000000 Z
11
+ date: 2014-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -56,6 +56,7 @@ files:
56
56
  - lib/mdisc/api.rb
57
57
  - lib/mdisc/menu.rb
58
58
  - lib/mdisc/player.rb
59
+ - lib/mdisc/screen.rb
59
60
  - lib/mdisc/ui.rb
60
61
  - lib/mdisc/version.rb
61
62
  - mdisc.gemspec