mplug163 0.1.0
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/.gitignore +14 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- data/Rakefile +1 -0
- data/bin/mplug163 +6 -0
- data/lib/mplug163.rb +4 -0
- data/lib/mplug163/api.rb +286 -0
- data/lib/mplug163/menu.rb +389 -0
- data/lib/mplug163/player.rb +99 -0
- data/lib/mplug163/ui.rb +299 -0
- data/lib/mplug163/version.rb +3 -0
- data/mplug163.gemspec +23 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5143da5494049d78f55d76a98a1b91b79655c641
|
4
|
+
data.tar.gz: 4694b7e9a9316f81ff7e90c834679cc8ac7c7410
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fb56f92dea87f05ac93efca90321ebd325f2d4c257904a18c0e15ca3d31dfde7455d4ec433cd80de737b3136458478a84a48bc8d147810ee39e8c6e299fdb1e9
|
7
|
+
data.tar.gz: 72c48c42edc713db334409dfbe2cc4999554bc1338da24860a08281f04296f218bed5af08d84f44e39b4330ff4d0ece19394db41f18f15754fe190a1304e54d6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Ripple Yui
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Mplug163
|
2
|
+
|
3
|
+
A local music player based on Netease music.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
```bash
|
8
|
+
brew install mpg123
|
9
|
+
```
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem install mplug163
|
15
|
+
```
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Open your terminal and input `mplug163`. Music's coming!
|
20
|
+
|
21
|
+
## Big Thanks
|
22
|
+
|
23
|
+
[NetEase-MusicBox](https://github.com/bluetomlee/NetEase-MusicBox)
|
24
|
+
|
25
|
+
[网易云音乐API分析](https://github.com/yanunon/NeteaseCloudMusic/wiki/网易云音乐API分析)
|
26
|
+
|
27
|
+
Their great projects inspire me. Thanks!
|
28
|
+
|
29
|
+
## License
|
30
|
+
|
31
|
+
MIT License.
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it ( https://github.com/[my-github-username]/mplug163/fork )
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/mplug163
ADDED
data/lib/mplug163.rb
ADDED
data/lib/mplug163/api.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'unirest'
|
2
|
+
require 'json'
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
class NetEase
|
6
|
+
def initialize
|
7
|
+
@header = {
|
8
|
+
"Accept" => "*/*",
|
9
|
+
"Accept-Encoding" => "gzip,deflate,sdch",
|
10
|
+
"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"
|
20
|
+
}
|
21
|
+
|
22
|
+
@default_timeout = 10
|
23
|
+
Unirest.timeout @default_timeout
|
24
|
+
end
|
25
|
+
|
26
|
+
def http_request(method, action, query=nil, urlencoded=nil, callback=nil, timeout=nil)
|
27
|
+
if method == 'GET'
|
28
|
+
url = (query == nil ? action : "#{action}?#{query}")
|
29
|
+
connection = Unirest.get(url, headers: @header)
|
30
|
+
elsif method == 'POST'
|
31
|
+
connection = Unirest.post(action, headers: @header, parameters: query)
|
32
|
+
end
|
33
|
+
|
34
|
+
connection.body
|
35
|
+
end
|
36
|
+
|
37
|
+
# 登录
|
38
|
+
def login(username, password)
|
39
|
+
action = "http://music.163.com/api/login/"
|
40
|
+
query = {
|
41
|
+
"username" => username,
|
42
|
+
"password" => Digest::MD5.hexdigest(password),
|
43
|
+
"rememberLogin" => "true"
|
44
|
+
}
|
45
|
+
begin
|
46
|
+
return http_request('POST', action, query)
|
47
|
+
rescue Exception => e
|
48
|
+
return {"code" => 501}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# 用户歌单
|
53
|
+
def user_playlists(uid, offset=0, limit=100)
|
54
|
+
action = "http://music.163.com/api/user/playlist/?offset=#{offset}&limit=#{limit}&uid=#{uid}"
|
55
|
+
begin
|
56
|
+
data = http_request('GET', action)
|
57
|
+
return data['playlist']
|
58
|
+
rescue Exception => e
|
59
|
+
return []
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# 搜索单曲(1),歌手(100),专辑(10),歌单(1000),用户(1002)
|
64
|
+
def search(s, stype=1, offset=0, total='true', limit=100)
|
65
|
+
action = "http://music.163.com/api/search/get/web"
|
66
|
+
query = {
|
67
|
+
"s" => s,
|
68
|
+
"type" => stype,
|
69
|
+
"offset" => offset,
|
70
|
+
"total" => true,
|
71
|
+
"limit" => limit
|
72
|
+
}
|
73
|
+
http_request('POST', action, query)
|
74
|
+
end
|
75
|
+
|
76
|
+
# 新碟上架 http://music.163.com/#/discover/album/
|
77
|
+
def new_albums(offset=0, limit=50)
|
78
|
+
action = "http://music.163.com/api/album/new?area=ALL&offset=#{offset}&total=true&limit=#{limit}"
|
79
|
+
begin
|
80
|
+
data = http_request('GET', action)
|
81
|
+
return data['albums']
|
82
|
+
rescue Exception => e
|
83
|
+
return []
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# 歌单(网友精选碟) hot||new http://music.163.com/#/discover/playlist/
|
88
|
+
|
89
|
+
# '全部' => '%E5%85%A8%E9%83%A8'
|
90
|
+
def top_playlists(category='%E5%85%A8%E9%83%A8', order='hot', offset=0, limit=50)
|
91
|
+
flag = (offset > 0 ? true : false)
|
92
|
+
action = "http://music.163.com/api/playlist/list?cat=#{category}&order=#{order}&offset=#{offset}&total=#{flag}&limit=#{limit}"
|
93
|
+
begin
|
94
|
+
data = http_request('GET', action)
|
95
|
+
return data['playlists']
|
96
|
+
rescue Exception => e
|
97
|
+
return []
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# 歌单详情
|
102
|
+
def playlist_detail(playlist_id)
|
103
|
+
action = "http://music.163.com/api/playlist/detail?id=#{playlist_id}"
|
104
|
+
begin
|
105
|
+
data = http_request('GET', action)
|
106
|
+
return data['result']['tracks']
|
107
|
+
rescue Exception => e
|
108
|
+
return []
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# 热门歌手 http://music.163.com/#/discover/artist/
|
113
|
+
def top_artists(offset=0, limit=100)
|
114
|
+
action = "http://music.163.com/api/artist/top?offset=#{offset}&total=false&limit=#{limit}"
|
115
|
+
begin
|
116
|
+
data = http_request('GET', action)
|
117
|
+
return data['artists']
|
118
|
+
rescue Exception => e
|
119
|
+
return []
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# 热门单曲 http://music.163.com/#/discover/toplist 50
|
124
|
+
def top_songlist(offset=0, limit=100)
|
125
|
+
action = "http://music.163.com/discover/toplist"
|
126
|
+
begin
|
127
|
+
connection = http_request('GET', action)
|
128
|
+
songids = connection.scan(/\/song\?id=(\d+)/)
|
129
|
+
return [] if songids == []
|
130
|
+
return songs_detail(songids.uniq)
|
131
|
+
rescue Exception => e
|
132
|
+
return []
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# 歌手单曲
|
137
|
+
def artists(artist_id)
|
138
|
+
action = "http://music.163.com/api/artist/#{artist_id}"
|
139
|
+
begin
|
140
|
+
data = http_request('GET', action)
|
141
|
+
return data['hotSongs']
|
142
|
+
rescue Exception => e
|
143
|
+
return []
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# album id -> song id set
|
148
|
+
def album(album_id)
|
149
|
+
action = "http://music.163.com/api/album/#{album_id}"
|
150
|
+
begin
|
151
|
+
data = http_request('GET', action)
|
152
|
+
return data['album']['songs']
|
153
|
+
rescue Exception => e
|
154
|
+
return []
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# song ids -> song urls (details)
|
159
|
+
def songs_detail(ids, offset=0)
|
160
|
+
tmpids = ids[offset, 100]
|
161
|
+
action = "http://music.163.com/api/song/detail?ids=[#{tmpids.join(',')}]"
|
162
|
+
begin
|
163
|
+
data = http_request('GET', action)
|
164
|
+
return data['songs']
|
165
|
+
rescue Exception => e
|
166
|
+
return []
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# song id -> song url (details)
|
171
|
+
def song_detail(music_id)
|
172
|
+
id = music_id.join(',')
|
173
|
+
action = "http://music.163.com/api/song/detail/?id=#{id}&ids=[#{id}]"
|
174
|
+
begin
|
175
|
+
data = http_request('GET', action)
|
176
|
+
return data['songs']
|
177
|
+
rescue Exception => e
|
178
|
+
return []
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# 今日最热(0), 本周最热(10),历史最热(20),最新节目(30)
|
183
|
+
def djchannels(stype=0, offset=0, limit=30)
|
184
|
+
action = "http://music.163.com/discover/djchannel?type=#{stype}&offset=#{offset}&limit=#{limit}"
|
185
|
+
begin
|
186
|
+
connection = http_request('GET', action)
|
187
|
+
channelids = connection.scan(/\/dj\?id=(\d+)/) || []
|
188
|
+
return [] if channelids.empty?
|
189
|
+
return channel_detail(channelids.uniq)
|
190
|
+
rescue Exception => e
|
191
|
+
return []
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# DJchannel (id, channel_name) ids -> song urls (details)
|
196
|
+
# channels 整理为 songs 类型
|
197
|
+
def channel_detail(channelids, offset=0)
|
198
|
+
channels = []
|
199
|
+
|
200
|
+
# ["xxxxxx"] -> "xxxxxx"
|
201
|
+
channelids.each do |c|
|
202
|
+
action = "http://music.163.com/api/dj/program/detail?id=#{c.join('')}"
|
203
|
+
begin
|
204
|
+
data = http_request('GET', action)
|
205
|
+
channel = dig_info(data['program']['mainSong'], 'channels')
|
206
|
+
channels.push(channel)
|
207
|
+
rescue Exception => e
|
208
|
+
next
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
channels
|
213
|
+
end
|
214
|
+
|
215
|
+
def dig_info(data, dig_type)
|
216
|
+
tmp = []
|
217
|
+
case dig_type
|
218
|
+
when 'songs'
|
219
|
+
data.each do |song|
|
220
|
+
song_info = {
|
221
|
+
"song_id" => song['id'],
|
222
|
+
"artist" => [],
|
223
|
+
"song_name" => song['name'],
|
224
|
+
"album_name" => song['album']['name'],
|
225
|
+
"mp3_url" => song['mp3Url']
|
226
|
+
}
|
227
|
+
|
228
|
+
if song.include? 'artist'
|
229
|
+
song_info['artist'] = song['artist'].join('')
|
230
|
+
elsif song.include? 'artists'
|
231
|
+
song['artists'].each do |artist|
|
232
|
+
song_info['artist'].push(artist['name'].strip)
|
233
|
+
end
|
234
|
+
song_info['artist'].join(',')
|
235
|
+
else
|
236
|
+
song_info['artist'] = '未知艺术家'
|
237
|
+
end
|
238
|
+
|
239
|
+
song_info['artist'] = song_info['artist'].join(',')
|
240
|
+
tmp.push song_info
|
241
|
+
end
|
242
|
+
|
243
|
+
when 'artists'
|
244
|
+
data.each do |artist|
|
245
|
+
artists_info = {
|
246
|
+
"artist_id" => artist['id'],
|
247
|
+
"artists_name" => artist['name'],
|
248
|
+
"alias" => artist['alias'].join('')
|
249
|
+
}
|
250
|
+
tmp.push artists_info
|
251
|
+
end
|
252
|
+
|
253
|
+
when 'albums'
|
254
|
+
data.each do |album|
|
255
|
+
albums_info = {
|
256
|
+
"album_id" => album['id'],
|
257
|
+
"albums_name" => album['name'],
|
258
|
+
"artists_name" => album['artist']['name']
|
259
|
+
}
|
260
|
+
tmp.push albums_info
|
261
|
+
end
|
262
|
+
|
263
|
+
when 'playlists'
|
264
|
+
data.each do |playlist|
|
265
|
+
playlists_info = {
|
266
|
+
"playlist_id" => playlist['id'],
|
267
|
+
"playlists_name" => playlist['name'],
|
268
|
+
"creator_name" => playlist['creator']['nickname']
|
269
|
+
}
|
270
|
+
tmp.push playlists_info
|
271
|
+
end
|
272
|
+
|
273
|
+
when 'channels'
|
274
|
+
channel_info = {
|
275
|
+
"song_id" => data['id'],
|
276
|
+
"song_name" => data['name'],
|
277
|
+
"artist" => data['artists'][0]['name'],
|
278
|
+
"album_name" => 'DJ节目',
|
279
|
+
"mp3_url" => data['mp3Url']
|
280
|
+
}
|
281
|
+
tmp.push channel_info
|
282
|
+
end
|
283
|
+
|
284
|
+
tmp
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
SHORTCUT = [
|
5
|
+
['j', 'Down ', '下移'],
|
6
|
+
['k', 'Up ', '上移'],
|
7
|
+
['h', 'Back ', '后退'],
|
8
|
+
['l', 'Forward ', '前进'],
|
9
|
+
['u', 'Prev page ', '上一页'],
|
10
|
+
['d', 'Next page ', '下一页'],
|
11
|
+
['f', 'Search ', '快速搜索'],
|
12
|
+
['[', 'Prev song ', '上一曲'],
|
13
|
+
[']', 'Next song ', '下一曲'],
|
14
|
+
[' ', 'Play/Pause', '播放/暂停'],
|
15
|
+
['m', 'Menu ', '主菜单'],
|
16
|
+
['p', 'Present ', '当前播放列表'],
|
17
|
+
['a', 'Add ', '添加曲目到打碟'],
|
18
|
+
['z', 'DJ list ', '打碟列表'],
|
19
|
+
['s', 'Star ', '添加到收藏'],
|
20
|
+
['c', 'Collection', '收藏列表'],
|
21
|
+
['r', 'Remove ', '删除当前条目'],
|
22
|
+
['q', 'Quit ', '退出']
|
23
|
+
]
|
24
|
+
|
25
|
+
class Menu
|
26
|
+
def initialize
|
27
|
+
@datatype = 'main'
|
28
|
+
@title = '网易云音乐'
|
29
|
+
@datalist = ['排行榜', '艺术家', '新碟上架', '精选歌单', '我的歌单', 'DJ节目', '打碟', '收藏', '搜索', '帮助']
|
30
|
+
@offset = 0
|
31
|
+
@index = 0
|
32
|
+
@present_songs = []
|
33
|
+
@player = Player.new
|
34
|
+
@ui = Ui.new
|
35
|
+
@netease = NetEase.new
|
36
|
+
@screen = @ui.screen
|
37
|
+
@step = 10
|
38
|
+
@stack = []
|
39
|
+
@djstack = []
|
40
|
+
@userid = nil
|
41
|
+
@username = nil
|
42
|
+
@collection = []
|
43
|
+
@account = {}
|
44
|
+
|
45
|
+
@wait = 0.1
|
46
|
+
@carousel = ->(left, right, x){x < left ? right : (x > right ? left : x)}
|
47
|
+
|
48
|
+
read_data
|
49
|
+
end
|
50
|
+
|
51
|
+
def start
|
52
|
+
@ui.build_menu(@datatype, @title, @datalist, @offset, @index, @step)
|
53
|
+
@stack.push([@datatype, @title, @datalist, @offset, @index, @step])
|
54
|
+
|
55
|
+
loop do
|
56
|
+
datatype = @datatype
|
57
|
+
title = @title
|
58
|
+
datalist = @datalist
|
59
|
+
offset = @offset
|
60
|
+
idx = index = @index
|
61
|
+
step = @step
|
62
|
+
stack = @stack
|
63
|
+
djstack = @djstack
|
64
|
+
key = @screen.getch
|
65
|
+
@screen.refresh
|
66
|
+
|
67
|
+
case key
|
68
|
+
|
69
|
+
# 退出
|
70
|
+
when 'q'
|
71
|
+
break
|
72
|
+
|
73
|
+
# 上移
|
74
|
+
when 'k'
|
75
|
+
@index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx - 1]
|
76
|
+
|
77
|
+
# 下移
|
78
|
+
when 'j'
|
79
|
+
@index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx + 1]
|
80
|
+
|
81
|
+
# 向上翻页
|
82
|
+
when 'u'
|
83
|
+
next if offset == 0
|
84
|
+
@offset = @offset - step
|
85
|
+
@index = (index - step).divmod(step)[0] * step
|
86
|
+
|
87
|
+
# 向下翻页
|
88
|
+
when 'd'
|
89
|
+
next if offset + step >= datalist.size
|
90
|
+
@offset = @offset + step
|
91
|
+
@index = (index + step).divmod(step)[0] * step
|
92
|
+
|
93
|
+
# 前进
|
94
|
+
when 'l'
|
95
|
+
next if @datatype == 'songs' || @datatype == 'djchannels' || @datatype == 'help'
|
96
|
+
@ui.build_loading
|
97
|
+
dispatch_enter(idx)
|
98
|
+
@index = 0
|
99
|
+
@offset = 0
|
100
|
+
|
101
|
+
# 回退
|
102
|
+
when 'h'
|
103
|
+
next if @stack.size == 1
|
104
|
+
up = stack.pop
|
105
|
+
@datatype, @title, @datalist, @offset, @index = up[0], up[1], up[2], up[3], up[4]
|
106
|
+
|
107
|
+
# 搜索
|
108
|
+
when 'f'
|
109
|
+
search
|
110
|
+
|
111
|
+
# 下一曲
|
112
|
+
when ']'
|
113
|
+
@player.next
|
114
|
+
sleep @wait
|
115
|
+
|
116
|
+
# 上一曲
|
117
|
+
when '['
|
118
|
+
@player.prev
|
119
|
+
sleep @wait
|
120
|
+
|
121
|
+
# 播放、暂停
|
122
|
+
when ' '
|
123
|
+
if datatype == 'songs'
|
124
|
+
@present_songs = ['songs', title, datalist, offset, index]
|
125
|
+
elsif datatype == 'djchannels'
|
126
|
+
@present_songs = ['djchannels', title, datalist, offset, index]
|
127
|
+
end
|
128
|
+
@player.play(datatype, datalist, idx)
|
129
|
+
sleep @wait
|
130
|
+
|
131
|
+
# 加载当前播放列表
|
132
|
+
when 'p'
|
133
|
+
next if @present_songs.empty?
|
134
|
+
@stack.push([datatype, title, datalist, offset, index])
|
135
|
+
@datatype, @title, @datalist, @offset, @index = @present_songs[0], @present_songs[1], @present_songs[2], @present_songs[3], @present_songs[4]
|
136
|
+
|
137
|
+
# 添加到打碟歌单
|
138
|
+
when 'a'
|
139
|
+
if datatype == 'songs' && !datalist.empty?
|
140
|
+
@djstack.push(datalist[idx])
|
141
|
+
end
|
142
|
+
|
143
|
+
# 加载打碟歌单
|
144
|
+
when 'z'
|
145
|
+
@stack.push([datatype, title, datalist, offset, index])
|
146
|
+
@datatype = 'songs'
|
147
|
+
@title = '网易云音乐 > 打碟'
|
148
|
+
@datalist = @djstack
|
149
|
+
@offset = 0
|
150
|
+
@index = 0
|
151
|
+
|
152
|
+
# 添加到收藏歌曲
|
153
|
+
when 's'
|
154
|
+
if (datatype == 'songs' || datatype == 'djchannels') && !datalist.empty?
|
155
|
+
@collection.push(datalist[idx]).uniq!
|
156
|
+
end
|
157
|
+
|
158
|
+
# 加载收藏歌曲
|
159
|
+
when 'c'
|
160
|
+
@stack.push([datatype, title, datalist, offset, index])
|
161
|
+
@datatype = 'songs'
|
162
|
+
@title = '网易云音乐 > 打碟'
|
163
|
+
@datalist = @collection
|
164
|
+
@offset = 0
|
165
|
+
@index = 0
|
166
|
+
|
167
|
+
# 从当前列表移除
|
168
|
+
when 'r'
|
169
|
+
if (datatype != 'main') && !datalist.empty?
|
170
|
+
@datalist.delete_at(idx)
|
171
|
+
@index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx]
|
172
|
+
end
|
173
|
+
|
174
|
+
# 主菜单
|
175
|
+
when 'm'
|
176
|
+
if datatype != 'main'
|
177
|
+
@stack.push([datatype, title, datalist, offset, index])
|
178
|
+
@datatype, @title, @datalist = @stack[0][0], @stack[0][1], @stack[0][2]
|
179
|
+
@offset = 0
|
180
|
+
@index = 0
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
write_data
|
186
|
+
@ui.build_menu(@datatype, @title, @datalist, @offset, @index, @step)
|
187
|
+
end
|
188
|
+
|
189
|
+
@player.stop
|
190
|
+
exit
|
191
|
+
end
|
192
|
+
|
193
|
+
def dispatch_enter(idx)
|
194
|
+
netease = @netease
|
195
|
+
datatype = @datatype
|
196
|
+
title = @title
|
197
|
+
datalist = @datalist
|
198
|
+
offset = @offset
|
199
|
+
index = @index
|
200
|
+
@stack.push([datatype, title, datalist, offset, index])
|
201
|
+
|
202
|
+
case datatype
|
203
|
+
when 'main'
|
204
|
+
choice_channel idx
|
205
|
+
|
206
|
+
# 该艺术家的热门歌曲
|
207
|
+
when 'artists'
|
208
|
+
artist_id = datalist[idx]['artist_id']
|
209
|
+
songs = netease.artists(artist_id)
|
210
|
+
@datatype = 'songs'
|
211
|
+
@datalist = netease.dig_info(songs, 'songs')
|
212
|
+
@title += " > #{datalist[idx]['aritsts_name']}"
|
213
|
+
|
214
|
+
# 该专辑包含的歌曲
|
215
|
+
when 'albums'
|
216
|
+
album_id = datalist[idx]['album_id']
|
217
|
+
songs = netease.album(album_id)
|
218
|
+
@datatype = 'songs'
|
219
|
+
@datalist = netease.dig_info(songs, 'songs')
|
220
|
+
@title += " > #{datalist[idx]['albums_name']}"
|
221
|
+
|
222
|
+
# 该歌单包含的歌曲
|
223
|
+
when 'playlists'
|
224
|
+
playlist_id = datalist[idx]['playlist_id']
|
225
|
+
songs = netease.playlist_detail(playlist_id)
|
226
|
+
@datatype = 'songs'
|
227
|
+
@datalist = netease.dig_info(songs, 'songs')
|
228
|
+
@title += " > #{datalist[idx]['playlists_name']}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def choice_channel(idx)
|
233
|
+
netease = @netease
|
234
|
+
|
235
|
+
case idx
|
236
|
+
|
237
|
+
# 排行榜
|
238
|
+
when 0
|
239
|
+
songs = netease.top_songlist
|
240
|
+
@datalist = netease.dig_info(songs, 'songs')
|
241
|
+
@title += ' > 排行榜'
|
242
|
+
@datatype = 'songs'
|
243
|
+
|
244
|
+
# 艺术家
|
245
|
+
when 1
|
246
|
+
artists = netease.top_artists
|
247
|
+
@datalist = netease.dig_info(artists, 'artists')
|
248
|
+
@title += ' > 艺术家'
|
249
|
+
@datatype = 'artists'
|
250
|
+
|
251
|
+
# 新碟上架
|
252
|
+
when 2
|
253
|
+
albums = netease.new_albums
|
254
|
+
@datalist = netease.dig_info(albums, 'albums')
|
255
|
+
@title += ' > 新碟上架'
|
256
|
+
@datatype = 'albums'
|
257
|
+
|
258
|
+
# 精选歌单
|
259
|
+
when 3
|
260
|
+
playlists = netease.top_playlists
|
261
|
+
@datalist = netease.dig_info(playlists, 'playlists')
|
262
|
+
@title += ' > 精选歌单'
|
263
|
+
@datatype = 'playlists'
|
264
|
+
|
265
|
+
# 我的歌单
|
266
|
+
when 4
|
267
|
+
# 未登录
|
268
|
+
if !@userid
|
269
|
+
if !@account.empty?
|
270
|
+
user_info = netease.login(@account[0], @account[1])
|
271
|
+
end
|
272
|
+
|
273
|
+
if @account == {} || user_info['code'] != 200
|
274
|
+
data = @ui.build_login
|
275
|
+
return if data == -1
|
276
|
+
user_info, @account = data[0], data[1]
|
277
|
+
end
|
278
|
+
|
279
|
+
@username = user_info['profile']['nickname']
|
280
|
+
@userid = user_info['account']['id']
|
281
|
+
end
|
282
|
+
|
283
|
+
# 读取登录之后的用户歌单
|
284
|
+
my_playlist = netease.user_playlists(@userid)
|
285
|
+
@datalist = netease.dig_info(my_playlist, 'playlists')
|
286
|
+
@datatype = 'playlists'
|
287
|
+
@title += " > #{@username} 的歌单"
|
288
|
+
|
289
|
+
# DJ节目
|
290
|
+
when 5
|
291
|
+
@datatype = 'djchannels'
|
292
|
+
@title += ' > DJ节目'
|
293
|
+
@datalist = netease.djchannels
|
294
|
+
|
295
|
+
# 打碟
|
296
|
+
when 6
|
297
|
+
@datatype = 'songs'
|
298
|
+
@title += ' > 打碟'
|
299
|
+
@datalist = @djstack
|
300
|
+
|
301
|
+
# 收藏
|
302
|
+
when 7
|
303
|
+
@datatype = 'songs'
|
304
|
+
@title += ' > 收藏'
|
305
|
+
@datalist = @collection
|
306
|
+
|
307
|
+
# 搜索
|
308
|
+
when 8
|
309
|
+
search
|
310
|
+
|
311
|
+
# 帮助
|
312
|
+
when 9
|
313
|
+
@datatype = 'help'
|
314
|
+
@title += ' > 帮助'
|
315
|
+
@datalist = SHORTCUT
|
316
|
+
end
|
317
|
+
|
318
|
+
@offset = 0
|
319
|
+
@index = 0
|
320
|
+
end
|
321
|
+
|
322
|
+
def search
|
323
|
+
ui = @ui
|
324
|
+
x = ui.build_search_menu
|
325
|
+
|
326
|
+
if (1...5).include? x.to_i
|
327
|
+
@stack.push([@datatype, @title, @datalist, @offset, @index])
|
328
|
+
@index = 0
|
329
|
+
@offset = 0
|
330
|
+
end
|
331
|
+
|
332
|
+
case x
|
333
|
+
|
334
|
+
when '1'
|
335
|
+
@datatype = 'songs'
|
336
|
+
@datalist = ui.build_search('songs')
|
337
|
+
@title = '歌曲搜索列表'
|
338
|
+
|
339
|
+
when '2'
|
340
|
+
@datatype = 'artists'
|
341
|
+
@datalist = ui.build_search('artists')
|
342
|
+
@title = '艺术家搜索列表'
|
343
|
+
|
344
|
+
when '3'
|
345
|
+
@datatype = 'albums'
|
346
|
+
@datalist = ui.build_search('albums')
|
347
|
+
@title = '专辑搜索列表'
|
348
|
+
|
349
|
+
when '4'
|
350
|
+
@datatype = 'playlists'
|
351
|
+
@datalist = ui.build_search('playlists')
|
352
|
+
@title = '精选歌单搜索列表'
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
private
|
357
|
+
|
358
|
+
def check_mplug163_dir
|
359
|
+
Dir.mkdir File.expand_path("~/.mplug163") unless Dir.exists? File.expand_path("~/.mplug163")
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
def read_data
|
364
|
+
check_mplug163_dir
|
365
|
+
user_file = File.expand_path("~/.mplug163/flavor.json")
|
366
|
+
if File.exists? user_file
|
367
|
+
begin
|
368
|
+
data = JSON.parse(File.read(user_file))
|
369
|
+
@collection = data['collection']
|
370
|
+
@account = data['account']
|
371
|
+
rescue Exception => e
|
372
|
+
@collection = []
|
373
|
+
@account = {}
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def write_data
|
379
|
+
user_file = File.expand_path("~/.mplug163/flavor.json")
|
380
|
+
data = {
|
381
|
+
:account => @account,
|
382
|
+
:collection => @collection
|
383
|
+
}
|
384
|
+
|
385
|
+
File.open(user_file, 'w') do |f|
|
386
|
+
f.write(JSON.generate(data))
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'open4'
|
2
|
+
|
3
|
+
class Player
|
4
|
+
def initialize
|
5
|
+
@ui = Ui.new
|
6
|
+
@datatype = 'songs'
|
7
|
+
@mpg123_thread = nil
|
8
|
+
@mpg123_pid = nil
|
9
|
+
@playing_flag = false
|
10
|
+
@pause_flag = false
|
11
|
+
@songs = []
|
12
|
+
@idx = 0
|
13
|
+
@wait = 0.5
|
14
|
+
@carousel = ->(left, right, x){x < left ? right : (x > right ? left : x)}
|
15
|
+
end
|
16
|
+
|
17
|
+
def recall
|
18
|
+
@playing_flag = true
|
19
|
+
@pause_flag = false
|
20
|
+
|
21
|
+
item = @songs[@idx]
|
22
|
+
@ui.build_playinfo(item['song_name'], item['artist'], item['album_name'])
|
23
|
+
|
24
|
+
@thread = Thread.new do
|
25
|
+
@mp3id, stdin, stdout, stderr = Open4::popen4('mpg123', item['mp3_url'])
|
26
|
+
Process::waitpid2 @mp3id
|
27
|
+
|
28
|
+
if @playing_flag
|
29
|
+
@idx = @carousel[0, @songs.size-1, @idx+1]
|
30
|
+
recall
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def play(datatype, songs, idx)
|
36
|
+
@datatype = datatype
|
37
|
+
|
38
|
+
if datatype == 'songs' || datatype == 'djchannels'
|
39
|
+
if @idx == idx && @songs == songs
|
40
|
+
@pause_flag ? resume : pause
|
41
|
+
else
|
42
|
+
if datatype == 'songs' || datatype == 'djchannels'
|
43
|
+
@songs = songs
|
44
|
+
@idx = idx
|
45
|
+
end
|
46
|
+
@playing_flag ? switch : recall
|
47
|
+
end
|
48
|
+
else
|
49
|
+
if @playing_flag
|
50
|
+
@pause_flag ? resume : pause
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def switch
|
56
|
+
stop
|
57
|
+
sleep @wait
|
58
|
+
recall
|
59
|
+
end
|
60
|
+
|
61
|
+
def stop
|
62
|
+
if @playing_flag && @thread && @mp3id
|
63
|
+
@playing_flag = false
|
64
|
+
# kill this process and thread
|
65
|
+
Process.kill(:SIGKILL, @mp3id)
|
66
|
+
Thread.kill @thread
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def pause
|
71
|
+
@pause_flag = true
|
72
|
+
# send SIGSTOP to pipe
|
73
|
+
Process.kill(:SIGSTOP, @mp3id)
|
74
|
+
item = @songs[@idx]
|
75
|
+
@ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], true)
|
76
|
+
end
|
77
|
+
|
78
|
+
def resume
|
79
|
+
@pause_flag = false
|
80
|
+
# send SIGCONT to pipe
|
81
|
+
Process.kill(:SIGCONT, @mp3id)
|
82
|
+
item = @songs[@idx]
|
83
|
+
@ui.build_playinfo(item['song_name'], item['artist'], item['album_name'])
|
84
|
+
end
|
85
|
+
|
86
|
+
def next
|
87
|
+
stop
|
88
|
+
sleep @wait
|
89
|
+
@idx = @carousel[0, @songs.size-1, @idx+1]
|
90
|
+
recall
|
91
|
+
end
|
92
|
+
|
93
|
+
def prev
|
94
|
+
stop
|
95
|
+
sleep @wait
|
96
|
+
@idx = @carousel[0, @songs.size-1, @idx-1]
|
97
|
+
recall
|
98
|
+
end
|
99
|
+
end
|
data/lib/mplug163/ui.rb
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
# Player UI
|
4
|
+
#
|
5
|
+
# SCREEN_TOP, SCREEN_LEFT
|
6
|
+
# |-----|-------------SCREEN_WIDTH-----------------------|
|
7
|
+
# | PLAYER_X |
|
8
|
+
# | |------------------------------------------------|
|
9
|
+
# | |PLAYER_TITLE_Y |
|
10
|
+
# | |------------------------------------------------|
|
11
|
+
# | |PLAYER_STATUS_Y |
|
12
|
+
# | | |
|
13
|
+
# | |------------------------------------------------|
|
14
|
+
# | |PLAYER_CONTENT_Y |
|
15
|
+
# | | |
|
16
|
+
# | | |
|
17
|
+
# | | |SCREEN_HEIGHT
|
18
|
+
# | | |
|
19
|
+
# | | |
|
20
|
+
# | | |
|
21
|
+
# | | |
|
22
|
+
# | | |
|
23
|
+
# | | |
|
24
|
+
# | | |
|
25
|
+
# | |------------------------------------------------|
|
26
|
+
# | |PLAYER_INFO_Y |
|
27
|
+
# |-----|------------------------------------------------|
|
28
|
+
|
29
|
+
class Ui
|
30
|
+
attr_reader :screen
|
31
|
+
|
32
|
+
SCREEN_HEIGHT = 40
|
33
|
+
SCREEN_WIDTH = 80
|
34
|
+
SCREEN_TOP = 0
|
35
|
+
SCREEN_LEFT = 0
|
36
|
+
|
37
|
+
PLAYER_X = 10
|
38
|
+
PLAYER_TITLE_Y = 4
|
39
|
+
PLAYER_STATUS_Y = 5
|
40
|
+
PLAYER_CONTENT_Y = 7
|
41
|
+
PLAYER_INFO_Y = 19
|
42
|
+
|
43
|
+
PLAYER_NOTE_X = PLAYER_X - 6
|
44
|
+
PLAYER_POINTER_X = PLAYER_X - 3
|
45
|
+
|
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
|
+
@screen = Curses::Window.new(SCREEN_HEIGHT, SCREEN_WIDTH, 0, 0)
|
58
|
+
|
59
|
+
@netease = NetEase.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_playinfo(song_name, artist, album_name, pause=false)
|
63
|
+
if pause
|
64
|
+
putstr(@screen, PLAYER_STATUS_Y, PLAYER_NOTE_X, 'z Zzz', Curses.color_pair(3))
|
65
|
+
else
|
66
|
+
putstr(@screen, PLAYER_STATUS_Y, PLAYER_NOTE_X, '♫ ♪ ♫ ', Curses.color_pair(3))
|
67
|
+
end
|
68
|
+
|
69
|
+
song_info = "#{song_name} - #{artist} <#{album_name}>"
|
70
|
+
putstr(@screen, PLAYER_STATUS_Y, PLAYER_X, song_info, Curses.color_pair(4))
|
71
|
+
@screen.refresh
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_loading
|
75
|
+
clear_to_bottom(@screen, PLAYER_CONTENT_Y, SCREEN_HEIGHT)
|
76
|
+
putstr(@screen, PLAYER_CONTENT_Y, PLAYER_X, 'loading...', Curses.color_pair(1))
|
77
|
+
@screen.refresh
|
78
|
+
end
|
79
|
+
|
80
|
+
def build_menu(datatype, title, datalist, offset, index, step)
|
81
|
+
clear_to_bottom(@screen, PLAYER_CONTENT_Y, SCREEN_HEIGHT)
|
82
|
+
putstr(@screen, PLAYER_TITLE_Y, PLAYER_X, title, Curses.color_pair(1))
|
83
|
+
|
84
|
+
if datalist.size == 0
|
85
|
+
putstr(@screen, PLAYER_CONTENT_Y, PLAYER_X, '还没有内容 - -||')
|
86
|
+
else
|
87
|
+
case datatype
|
88
|
+
when 'main'
|
89
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
90
|
+
if i == index
|
91
|
+
line_info = "-> #{i}.#{datalist[i]}"
|
92
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, line_info, Curses.color_pair(2))
|
93
|
+
else
|
94
|
+
line_info = "#{i}.#{datalist[i]}"
|
95
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
putstr(@screen, PLAYER_INFO_Y, PLAYER_X, 'Mplug163 built by Ripple Yui', Curses.color_pair(3))
|
100
|
+
|
101
|
+
when 'songs'
|
102
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
103
|
+
if i == index
|
104
|
+
info = "-> #{i}.#{datalist[i]['song_name']} - #{datalist[i]['artist']} <#{datalist[i]['album_name']}>"
|
105
|
+
line_info = pretty_format(info, 0, 63)
|
106
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, line_info, Curses.color_pair(2))
|
107
|
+
else
|
108
|
+
info = "#{i}.#{datalist[i]['song_name']} - #{datalist[i]['artist']} <#{datalist[i]['album_name']}>"
|
109
|
+
line_info = pretty_format(info, 0, 60)
|
110
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
when 'artists'
|
115
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
116
|
+
if i == index
|
117
|
+
line_info = "-> #{i}.#{datalist[i]['artists_name']} - #{datalist[i]['artist']} #{datalist[i]['alias']}"
|
118
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, line_info, Curses.color_pair(2))
|
119
|
+
else
|
120
|
+
line_info = "#{i}.#{datalist[i]['artists_name']} - #{datalist[i]['artist']} #{datalist[i]['alias']}"
|
121
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
when 'albums'
|
126
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
127
|
+
if i == index
|
128
|
+
line_info = "-> #{i}.#{datalist[i]['albums_name']} - #{datalist[i]['artists_name']}"
|
129
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, line_info, Curses.color_pair(2))
|
130
|
+
else
|
131
|
+
line_info = "#{i}.#{datalist[i]['albums_name']} - #{datalist[i]['artists_name']}"
|
132
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
when 'playlists'
|
137
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
138
|
+
if i == index
|
139
|
+
line_info = "-> #{i}.#{datalist[i]['playlists_name']} - #{datalist[i]['creator_name']}"
|
140
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, line_info, Curses.color_pair(2))
|
141
|
+
else
|
142
|
+
line_info = "#{i}.#{datalist[i]['playlists_name']} - #{datalist[i]['creator_name']}"
|
143
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
when 'djchannels'
|
148
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
149
|
+
if i == index
|
150
|
+
line_info = "-> #{i}.#{datalist[i][0]['song_name']}"
|
151
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_POINTER_X, line_info, Curses.color_pair(2))
|
152
|
+
else
|
153
|
+
line_info = "#{i}.#{datalist[i][0]['song_name']}"
|
154
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
when 'help'
|
159
|
+
(offset...[datalist.length, offset+step].min).each do |i|
|
160
|
+
line_info = "#{i}.#{datalist[i][0]} #{datalist[i][1]} #{datalist[i][2]}"
|
161
|
+
putstr(@screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, line_info)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
def build_search(stype)
|
169
|
+
case stype
|
170
|
+
when 'songs'
|
171
|
+
song_name = get_param('搜索歌曲:')
|
172
|
+
begin
|
173
|
+
data = @netease.search(song_name, stype=1)
|
174
|
+
song_ids = []
|
175
|
+
if data['result'].include? 'songs'
|
176
|
+
if data['result']['songs'].include? 'mp3Url'
|
177
|
+
songs = data['result']['songs']
|
178
|
+
else
|
179
|
+
(0...data['result']['songs'].size).each do |i|
|
180
|
+
song_ids.push data['result']['songs'][i]['id']
|
181
|
+
end
|
182
|
+
songs = @netease.songs_detail(song_ids)
|
183
|
+
end
|
184
|
+
return @netease.dig_info(songs, 'songs')
|
185
|
+
end
|
186
|
+
rescue Exception => e
|
187
|
+
return []
|
188
|
+
end
|
189
|
+
|
190
|
+
when 'artists'
|
191
|
+
artist_name = get_param('搜索艺术家:')
|
192
|
+
begin
|
193
|
+
data = @netease.search(artist_name, stype=100)
|
194
|
+
if data['result'].include? 'artists'
|
195
|
+
artists = data['result']['artists']
|
196
|
+
return @netease.dig_info(artists, 'artists')
|
197
|
+
end
|
198
|
+
rescue Exception => e
|
199
|
+
return []
|
200
|
+
end
|
201
|
+
|
202
|
+
when 'albums'
|
203
|
+
artist_name = get_param('搜索专辑:')
|
204
|
+
begin
|
205
|
+
data = @netease.search(artist_name, stype=10)
|
206
|
+
if data['result'].include? 'albums'
|
207
|
+
albums = data['result']['albums']
|
208
|
+
return @netease.dig_info(albums, 'albums')
|
209
|
+
end
|
210
|
+
rescue Exception => e
|
211
|
+
return []
|
212
|
+
end
|
213
|
+
|
214
|
+
when 'playlists'
|
215
|
+
artist_name = get_param('搜索网易精选集:')
|
216
|
+
begin
|
217
|
+
data = @netease.search(artist_name, stype=1000)
|
218
|
+
if data['result'].include? 'playlists'
|
219
|
+
playlists = data['result']['playlists']
|
220
|
+
return @netease.dig_info(playlists, 'playlists')
|
221
|
+
end
|
222
|
+
rescue Exception => e
|
223
|
+
return []
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def build_search_menu
|
229
|
+
clear_to_bottom(@screen, PLAYER_CONTENT_Y, SCREEN_HEIGHT)
|
230
|
+
putstr(@screen, PLAYER_CONTENT_Y, PLAYER_X, '选择搜索类型:', Curses.color_pair(1))
|
231
|
+
putstr(@screen, PLAYER_CONTENT_Y + 1, PLAYER_X, '[1] 歌曲')
|
232
|
+
putstr(@screen, PLAYER_CONTENT_Y + 2, PLAYER_X, '[2] 艺术家')
|
233
|
+
putstr(@screen, PLAYER_CONTENT_Y + 3, PLAYER_X, '[3] 专辑')
|
234
|
+
putstr(@screen, PLAYER_CONTENT_Y + 4, PLAYER_X, '[4] 网易精选集')
|
235
|
+
putstr(@screen, PLAYER_CONTENT_Y + 6, PLAYER_X, '请键入对应数字:', Curses.color_pair(2))
|
236
|
+
@screen.refresh
|
237
|
+
x = @screen.getch
|
238
|
+
end
|
239
|
+
|
240
|
+
def build_login
|
241
|
+
params = get_param('请输入登录信息: (e.g. foobar@163.com foobar)')
|
242
|
+
account = params.split(' ')
|
243
|
+
return build_login if account.size != 2
|
244
|
+
|
245
|
+
login_info = @netease.login(account[0], account[1])
|
246
|
+
if login_info['code'] != 200
|
247
|
+
x = build_login_error
|
248
|
+
return x == '1' ? build_login : -1
|
249
|
+
else
|
250
|
+
return [login_info, account]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def build_login_error
|
255
|
+
clear_to_bottom(@screen, PLAYER_CONTENT_Y, SCREEN_HEIGHT)
|
256
|
+
putstr(@screen, PLAYER_CONTENT_Y + 1, PLAYER_X, 'oh,登录信息好像不对 (O_O)#', Curses.color_pair(1))
|
257
|
+
putstr(@screen, PLAYER_CONTENT_Y + 2, PLAYER_X, '[1] 再试一次')
|
258
|
+
putstr(@screen, PLAYER_CONTENT_Y + 3, PLAYER_X, '[2] 稍后再试')
|
259
|
+
putstr(@screen, PLAYER_CONTENT_Y + 5, PLAYER_X, '请键入对应数字:', Curses.color_pair(2))
|
260
|
+
@screen.refresh
|
261
|
+
x = @screen.getch
|
262
|
+
end
|
263
|
+
|
264
|
+
def get_param(prompt_str)
|
265
|
+
clear_to_bottom(@screen, PLAYER_CONTENT_Y, SCREEN_HEIGHT)
|
266
|
+
putstr(@screen, PLAYER_CONTENT_Y, PLAYER_X, prompt_str, Curses.color_pair(1))
|
267
|
+
@screen.setpos(PLAYER_CONTENT_Y + 2, PLAYER_X)
|
268
|
+
params = @screen.getstr
|
269
|
+
if params.strip == nil
|
270
|
+
return get_param(prompt_str)
|
271
|
+
else
|
272
|
+
return params
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def putstr(screen, y, x, string, color=Curses.color_pair(0))
|
279
|
+
screen.setpos(y, x)
|
280
|
+
screen.clrtoeol
|
281
|
+
screen.attrset(color)
|
282
|
+
screen.addstr(string)
|
283
|
+
end
|
284
|
+
|
285
|
+
def clear_to_bottom(screen, top, bottom)
|
286
|
+
(top..bottom).each do |i|
|
287
|
+
screen.setpos(i, 0)
|
288
|
+
screen.clrtoeol
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def pretty_format(info, start, length)
|
293
|
+
if info.size >= length
|
294
|
+
"#{info[start, length]}..."
|
295
|
+
else
|
296
|
+
info
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
data/mplug163.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mplug163/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mplug163"
|
8
|
+
spec.version = Mplug163::VERSION
|
9
|
+
spec.authors = ["Ripple Yui"]
|
10
|
+
spec.email = ["rippleyui@gmail.com"]
|
11
|
+
spec.summary = %q{A local music player based on Netease music.}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = "https://github.com/rippleyui/mplug163"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mplug163
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ripple Yui
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: ''
|
42
|
+
email:
|
43
|
+
- rippleyui@gmail.com
|
44
|
+
executables:
|
45
|
+
- mplug163
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/mplug163
|
55
|
+
- lib/mplug163.rb
|
56
|
+
- lib/mplug163/api.rb
|
57
|
+
- lib/mplug163/menu.rb
|
58
|
+
- lib/mplug163/player.rb
|
59
|
+
- lib/mplug163/ui.rb
|
60
|
+
- lib/mplug163/version.rb
|
61
|
+
- mplug163.gemspec
|
62
|
+
homepage: https://github.com/rippleyui/mplug163
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.4.1
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: A local music player based on Netease music.
|
86
|
+
test_files: []
|