mdisc 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +58 -0
- data/Rakefile +2 -0
- data/bin/mdisc +6 -0
- data/lib/mdisc.rb +5 -0
- data/lib/mdisc/api.rb +247 -0
- data/lib/mdisc/menu.rb +435 -0
- data/lib/mdisc/player.rb +99 -0
- data/lib/mdisc/ui.rb +307 -0
- data/lib/mdisc/version.rb +3 -0
- data/mdisc.gemspec +23 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e7f5547a564f2f00c77ae5e07ab4f343553ad39d
|
4
|
+
data.tar.gz: ae58b382a6e761fbf450c7f9777f74aae550d04b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 05887319f06a3465625493599c567434b43a663085902e42fb1b6bdd2feba05e29722ebc9f72577c8ebc488dc76d66fda73633efd2889eb3b4d656c2a874b3a3
|
7
|
+
data.tar.gz: b58337cc0d810e8e08bf7ce9cd8b8abc4e199346e818ba2357a0147d6908e0aae38fdbd8fe8fe85b64534024baed77a103fda2ff3c62c67b88b5d241516e6779
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Rick Yu
|
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,58 @@
|
|
1
|
+
# Mdisc
|
2
|
+
|
3
|
+
Mdisc, built with Ruby 2.1, is a command line music player that wirelessly plugs in Netease music(http://music.163.com).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
My MacBook Pro is running Yosemite(OS X 10.10), Mdisc runs smoothly with Ruby 2.1.
|
8
|
+
|
9
|
+
You're lucky if you use Mac OS X. Just follow:
|
10
|
+
|
11
|
+
```
|
12
|
+
$ brew install mpg123
|
13
|
+
$ gem install mdisc
|
14
|
+
```
|
15
|
+
|
16
|
+
After finishing the installation, open your terminal and input `mdisc`. Music's coming!
|
17
|
+
|
18
|
+
Sorry, I do not test Mdisc in Linux. If you try it in Linux and catch some problem, please issue me. Thanks!
|
19
|
+
|
20
|
+
## Shortcuts
|
21
|
+
|
22
|
+
| Key | Explanation | 中文释义 |
|
23
|
+
| :---|:---------------------|:---------------------|
|
24
|
+
| j | Down | 向下 |
|
25
|
+
| k | Up | 向上 |
|
26
|
+
| h | Back | 后退 |
|
27
|
+
| l | Forward | 前进或播放高亮选中歌曲 |
|
28
|
+
| [ | Prev Song | 上一首歌曲 |
|
29
|
+
| ] | Next Song | 下一首歌曲 |
|
30
|
+
| ' ' | Play / Pause | 播放 / 暂停当前进行的歌曲|
|
31
|
+
| u | Prev Page | 上一页列表 |
|
32
|
+
| d | Next Page | 下一页列表 |
|
33
|
+
| f | Search | 搜索 |
|
34
|
+
| m | Main Menu | 主菜单 |
|
35
|
+
| p | Present Playlist | 当前播放列表 |
|
36
|
+
| s | Star | 收藏歌曲、精选歌单、专辑 |
|
37
|
+
| t | Playlist | 收藏精选歌单 |
|
38
|
+
| c | Collection | 收藏歌曲列表 |
|
39
|
+
| a | Album | 收藏专辑 |
|
40
|
+
| z | DJ Channels | 收藏 DJ 节目 |
|
41
|
+
| r | Remove Present Entry | 删除当前曲目 |
|
42
|
+
| q | Quit | 退出 |
|
43
|
+
|
44
|
+
## Attention
|
45
|
+
|
46
|
+
Mdisc will make a new directory `~/.mdisc` and touch a file to store user's data for the first time.
|
47
|
+
|
48
|
+
## Big Thanks
|
49
|
+
|
50
|
+
[NetEase-MusicBox](https://github.com/bluetomlee/NetEase-MusicBox)
|
51
|
+
|
52
|
+
[网易云音乐API分析](https://github.com/yanunon/NeteaseCloudMusic/wiki/网易云音乐API分析)
|
53
|
+
|
54
|
+
Their great projects inspire me. Thanks!
|
55
|
+
|
56
|
+
## License
|
57
|
+
|
58
|
+
MIT License.
|
data/Rakefile
ADDED
data/bin/mdisc
ADDED
data/lib/mdisc.rb
ADDED
data/lib/mdisc/api.rb
ADDED
@@ -0,0 +1,247 @@
|
|
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)
|
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
|
36
|
+
end
|
37
|
+
|
38
|
+
# Log in
|
39
|
+
def login(username, password)
|
40
|
+
action = "http://music.163.com/api/login/"
|
41
|
+
query = {
|
42
|
+
"username" => username,
|
43
|
+
"password" => Digest::MD5.hexdigest(password),
|
44
|
+
"rememberLogin" => "true"
|
45
|
+
}
|
46
|
+
begin
|
47
|
+
return http_request('POST', action, query)
|
48
|
+
rescue => e
|
49
|
+
return {"code" => 501}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# User's playlists
|
54
|
+
def user_playlists(uid, offset = 0, limit = 100)
|
55
|
+
action = "http://music.163.com/api/user/playlist/?offset=#{offset}&limit=#{limit}&uid=#{uid}"
|
56
|
+
data = http_request('GET', action)
|
57
|
+
data['playlist']
|
58
|
+
end
|
59
|
+
|
60
|
+
# Search song(1),artist(100),album(10),playlist(1000),user(1002)
|
61
|
+
def search(s, stype = 1, offset = 0, limit = 100)
|
62
|
+
action = "http://music.163.com/api/search/get/web"
|
63
|
+
query = {
|
64
|
+
"s" => s,
|
65
|
+
"type" => stype,
|
66
|
+
"offset" => offset,
|
67
|
+
"total" => true,
|
68
|
+
"limit" => limit
|
69
|
+
}
|
70
|
+
http_request('POST', action, query)
|
71
|
+
end
|
72
|
+
|
73
|
+
# New albums
|
74
|
+
# http://music.163.com/#/discover/album/
|
75
|
+
def new_albums(offset=0, limit=50)
|
76
|
+
action = "http://music.163.com/api/album/new?area=ALL&offset=#{offset}&total=true&limit=#{limit}"
|
77
|
+
data = http_request('GET', action)
|
78
|
+
data['albums']
|
79
|
+
end
|
80
|
+
|
81
|
+
# Top playlists
|
82
|
+
# hot||new http://music.163.com/#/discover/playlist/
|
83
|
+
|
84
|
+
# '全部' => '%E5%85%A8%E9%83%A8'
|
85
|
+
def top_playlists(category = '%E5%85%A8%E9%83%A8', order = 'hot', offset = 0, limit = 100)
|
86
|
+
flag = (offset > 0 ? true : false)
|
87
|
+
action = "http://music.163.com/api/playlist/list?cat=#{category}&order=#{order}&offset=#{offset}&total=#{flag}&limit=#{limit}"
|
88
|
+
data = http_request('GET', action)
|
89
|
+
return data['playlists']
|
90
|
+
end
|
91
|
+
|
92
|
+
# Playlist's details
|
93
|
+
def playlist_detail(playlist_id)
|
94
|
+
action = "http://music.163.com/api/playlist/detail?id=#{playlist_id}"
|
95
|
+
data = http_request('GET', action)
|
96
|
+
return data['result']['tracks']
|
97
|
+
end
|
98
|
+
|
99
|
+
# Top artists
|
100
|
+
# http://music.163.com/#/discover/artist/
|
101
|
+
def top_artists(offset = 0, limit = 100)
|
102
|
+
action = "http://music.163.com/api/artist/top?offset=#{offset}&total=false&limit=#{limit}"
|
103
|
+
data = http_request('GET', action)
|
104
|
+
return data['artists']
|
105
|
+
end
|
106
|
+
|
107
|
+
# Top songlist
|
108
|
+
# http://music.163.com/#/discover/toplist 100
|
109
|
+
def top_songlist
|
110
|
+
action = "http://music.163.com/discover/toplist"
|
111
|
+
connection = http_request('GET', action)
|
112
|
+
songids = connection.scan(/\/song\?id=(\d+)/)
|
113
|
+
return [] if songids == []
|
114
|
+
return songs_detail(songids.uniq)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Songs to which a artist belongs.
|
118
|
+
def artists(artist_id)
|
119
|
+
action = "http://music.163.com/api/artist/#{artist_id}"
|
120
|
+
data = http_request('GET', action)
|
121
|
+
return data['hotSongs']
|
122
|
+
end
|
123
|
+
|
124
|
+
# album id -> song id set
|
125
|
+
def album(album_id)
|
126
|
+
action = "http://music.163.com/api/album/#{album_id}"
|
127
|
+
data = http_request('GET', action)
|
128
|
+
return data['album']['songs']
|
129
|
+
end
|
130
|
+
|
131
|
+
# song ids -> song urls (details)
|
132
|
+
def songs_detail(ids, offset=0)
|
133
|
+
tmpids = ids[offset, 100]
|
134
|
+
action = "http://music.163.com/api/song/detail?ids=[#{tmpids.join(',')}]"
|
135
|
+
data = http_request('GET', action)
|
136
|
+
return data['songs']
|
137
|
+
end
|
138
|
+
|
139
|
+
# song id -> song url (details)
|
140
|
+
def song_detail(music_id)
|
141
|
+
id = music_id.join(',')
|
142
|
+
action = "http://music.163.com/api/song/detail/?id=#{id}&ids=[#{id}]"
|
143
|
+
data = http_request('GET', action)
|
144
|
+
return data['songs']
|
145
|
+
end
|
146
|
+
|
147
|
+
# DJ channels: hot today(0), week(10), history(20), new(30)
|
148
|
+
def djchannels(stype = 0, offset = 0, limit = 50)
|
149
|
+
action = "http://music.163.com/discover/djchannel?type=#{stype}&offset=#{offset}&limit=#{limit}"
|
150
|
+
connection = http_request('GET', action)
|
151
|
+
channelids = connection.scan(/\/dj\?id=(\d+)/) || []
|
152
|
+
return [] if channelids.empty?
|
153
|
+
return channel_detail(channelids.uniq)
|
154
|
+
end
|
155
|
+
|
156
|
+
# DJchannel (id, channel_name) ids -> song urls (details)
|
157
|
+
# channels -> songs
|
158
|
+
def channel_detail(channelids)
|
159
|
+
channels = []
|
160
|
+
|
161
|
+
# ["xxxxxx"] -> "xxxxxx"
|
162
|
+
channelids.each do |c|
|
163
|
+
action = "http://music.163.com/api/dj/program/detail?id=#{c.join('')}"
|
164
|
+
begin
|
165
|
+
data = http_request('GET', action)
|
166
|
+
channel = dig_info(data['program']['mainSong'], 'channels')
|
167
|
+
channels.push(channel)
|
168
|
+
rescue => e
|
169
|
+
next
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
channels
|
174
|
+
end
|
175
|
+
|
176
|
+
def dig_info(data, dig_type)
|
177
|
+
tmp = []
|
178
|
+
case dig_type
|
179
|
+
when 'songs'
|
180
|
+
data.each do |song|
|
181
|
+
song_info = {
|
182
|
+
"song_id" => song['id'],
|
183
|
+
"artist" => [],
|
184
|
+
"song_name" => song['name'],
|
185
|
+
"album_name" => song['album']['name'],
|
186
|
+
"mp3_url" => song['mp3Url']
|
187
|
+
}
|
188
|
+
|
189
|
+
if song.include? 'artist'
|
190
|
+
song_info['artist'] = song['artist'].join('')
|
191
|
+
elsif song.include? 'artists'
|
192
|
+
song['artists'].each do |artist|
|
193
|
+
song_info['artist'].push(artist['name'].strip)
|
194
|
+
end
|
195
|
+
song_info['artist'].join(',')
|
196
|
+
else
|
197
|
+
song_info['artist'] = '未知艺术家'
|
198
|
+
end
|
199
|
+
|
200
|
+
song_info['artist'] = song_info['artist'].join(',')
|
201
|
+
tmp.push song_info
|
202
|
+
end
|
203
|
+
|
204
|
+
when 'artists'
|
205
|
+
data.each do |artist|
|
206
|
+
artists_info = {
|
207
|
+
"artist_id" => artist['id'],
|
208
|
+
"artists_name" => artist['name'],
|
209
|
+
"alias" => artist['alias'].join('')
|
210
|
+
}
|
211
|
+
tmp.push artists_info
|
212
|
+
end
|
213
|
+
|
214
|
+
when 'albums'
|
215
|
+
data.each do |album|
|
216
|
+
albums_info = {
|
217
|
+
"album_id" => album['id'],
|
218
|
+
"albums_name" => album['name'],
|
219
|
+
"artists_name" => album['artist']['name']
|
220
|
+
}
|
221
|
+
tmp.push albums_info
|
222
|
+
end
|
223
|
+
|
224
|
+
when 'playlists'
|
225
|
+
data.each do |playlist|
|
226
|
+
playlists_info = {
|
227
|
+
"playlist_id" => playlist['id'],
|
228
|
+
"playlists_name" => playlist['name'],
|
229
|
+
"creator_name" => playlist['creator']['nickname']
|
230
|
+
}
|
231
|
+
tmp.push playlists_info
|
232
|
+
end
|
233
|
+
|
234
|
+
when 'channels'
|
235
|
+
channel_info = {
|
236
|
+
"song_id" => data['id'],
|
237
|
+
"song_name" => data['name'],
|
238
|
+
"artist" => data['artists'][0]['name'],
|
239
|
+
"album_name" => 'DJ节目',
|
240
|
+
"mp3_url" => data['mp3Url']
|
241
|
+
}
|
242
|
+
tmp.push channel_info
|
243
|
+
end
|
244
|
+
|
245
|
+
tmp
|
246
|
+
end
|
247
|
+
end
|
data/lib/mdisc/menu.rb
ADDED
@@ -0,0 +1,435 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
SHORTCUT = [
|
5
|
+
['j', 'Down ', '向下'],
|
6
|
+
['k', 'Up ', '向上'],
|
7
|
+
['h', 'Back ', '后退'],
|
8
|
+
['l', 'Forward ', '前进或播放高亮选中歌曲'],
|
9
|
+
['[', 'Prev song ', '上一首歌曲'],
|
10
|
+
[']', 'Next song ', '下一首歌曲'],
|
11
|
+
[' ', 'Play / Pause', '播放 / 暂停当前进行的歌曲'],
|
12
|
+
['u', 'Prev page ', '上一页列表'],
|
13
|
+
['d', 'Next page ', '下一页列表'],
|
14
|
+
['f', 'Search ', '搜索'],
|
15
|
+
['m', 'Menu ', '主菜单'],
|
16
|
+
['p', 'Present ', '当前播放列表'],
|
17
|
+
['s', 'Star ', '收藏歌曲、精选歌单、专辑'],
|
18
|
+
['t', 'Playlist ', '收藏精选歌单'],
|
19
|
+
['c', 'Collection ', '收藏歌曲列表'],
|
20
|
+
['a', 'Album ', '收藏专辑'],
|
21
|
+
['z', 'DJ Channels ', '收藏 DJ 节目'],
|
22
|
+
['r', 'Remove ', '删除当前曲目'],
|
23
|
+
['q', 'Quit ', '退出']
|
24
|
+
]
|
25
|
+
|
26
|
+
class Menu
|
27
|
+
attr_accessor :player, :ui, :netease, :screen
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
self.player = Player.new
|
31
|
+
self.ui = Ui.new
|
32
|
+
self.netease = NetEase.new
|
33
|
+
self.screen = ui.screen
|
34
|
+
@datatype = 'main'
|
35
|
+
@title = '网易云音乐'
|
36
|
+
@datalist = %w(排行榜 精选歌单 艺术家 新碟上架 我的歌单 DJ节目 本地收藏 搜索 帮助)
|
37
|
+
@offset = 0
|
38
|
+
@index = 0
|
39
|
+
@present_songs = []
|
40
|
+
@step = 20
|
41
|
+
@stack = []
|
42
|
+
@userid = nil
|
43
|
+
@username = nil
|
44
|
+
@collection = []
|
45
|
+
@playlists = []
|
46
|
+
@account = {}
|
47
|
+
|
48
|
+
@wait = 0.1
|
49
|
+
@carousel = ->(left, right, x){x < left ? right : (x > right ? left : x)}
|
50
|
+
|
51
|
+
read_data
|
52
|
+
end
|
53
|
+
|
54
|
+
def start
|
55
|
+
ui.build_menu(@datatype, @title, @datalist, @offset, @index, @step)
|
56
|
+
@stack.push([@datatype, @title, @datalist, @offset, @index, @step])
|
57
|
+
|
58
|
+
loop do
|
59
|
+
datatype = @datatype
|
60
|
+
title = @title
|
61
|
+
datalist = @datalist
|
62
|
+
offset = @offset
|
63
|
+
idx = index = @index
|
64
|
+
step = @step
|
65
|
+
stack = @stack
|
66
|
+
key = screen.getch
|
67
|
+
screen.refresh
|
68
|
+
|
69
|
+
case key
|
70
|
+
|
71
|
+
# Quit
|
72
|
+
when 'q'
|
73
|
+
break
|
74
|
+
|
75
|
+
# Up
|
76
|
+
when 'k'
|
77
|
+
@index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx - 1]
|
78
|
+
|
79
|
+
# Down
|
80
|
+
when 'j'
|
81
|
+
@index = @carousel[@offset, [datalist.size, offset+step].min - 1, idx + 1]
|
82
|
+
|
83
|
+
# Previous page
|
84
|
+
when 'u'
|
85
|
+
next if offset == 0
|
86
|
+
@offset = @offset - step
|
87
|
+
@index = (index - step).divmod(step)[0] * step
|
88
|
+
|
89
|
+
# Next page
|
90
|
+
when 'd'
|
91
|
+
next if offset + step >= datalist.size
|
92
|
+
@offset = @offset + step
|
93
|
+
@index = (index + step).divmod(step)[0] * step
|
94
|
+
|
95
|
+
# Forward
|
96
|
+
when 'l'
|
97
|
+
next if @datatype == 'help'
|
98
|
+
if @datatype == 'songs' || @datatype == 'djchannels'
|
99
|
+
player.play(@datatype, datalist, @index, true)
|
100
|
+
@present_songs = [datatype, title, datalist, offset, index]
|
101
|
+
else
|
102
|
+
ui.build_loading
|
103
|
+
dispatch_enter(idx)
|
104
|
+
@index = 0
|
105
|
+
@offset = 0
|
106
|
+
end
|
107
|
+
|
108
|
+
# Back
|
109
|
+
when 'h'
|
110
|
+
next if @stack.size == 1
|
111
|
+
up = stack.pop
|
112
|
+
@datatype, @title, @datalist, @offset, @index = up[0], up[1], up[2], up[3], up[4]
|
113
|
+
|
114
|
+
# Search
|
115
|
+
when 'f'
|
116
|
+
search
|
117
|
+
|
118
|
+
# Next song
|
119
|
+
when ']'
|
120
|
+
player.next
|
121
|
+
sleep @wait
|
122
|
+
|
123
|
+
# Previous song
|
124
|
+
when '['
|
125
|
+
player.prev
|
126
|
+
sleep @wait
|
127
|
+
|
128
|
+
# Play or pause a song.
|
129
|
+
when ' '
|
130
|
+
player.play(datatype, datalist, idx)
|
131
|
+
sleep @wait
|
132
|
+
|
133
|
+
# Load present playlist.
|
134
|
+
when 'p'
|
135
|
+
next if @present_songs.empty?
|
136
|
+
@stack.push([datatype, title, datalist, offset, index])
|
137
|
+
@datatype, @title, @datalist, @offset, @index = @present_songs[0], @present_songs[1], @present_songs[2], @present_songs[3], @present_songs[4]
|
138
|
+
|
139
|
+
# Star a song, a playlist or an album.
|
140
|
+
when 's'
|
141
|
+
next if datalist.empty?
|
142
|
+
if datatype == 'songs'
|
143
|
+
@collection.push(datalist[idx]).uniq!
|
144
|
+
elsif datatype == 'playlists'
|
145
|
+
@playlists.push(datalist[idx]).uniq!
|
146
|
+
elsif datatype == 'albums'
|
147
|
+
@albums.push(datalist[idx]).uniq!
|
148
|
+
elsif datatype == 'djchannels'
|
149
|
+
@djs.push(datalist[idx]).uniq!
|
150
|
+
end
|
151
|
+
|
152
|
+
# Load favorite playlists.
|
153
|
+
when 't'
|
154
|
+
@stack.push([datatype, title, datalist, offset, index])
|
155
|
+
@datatype = 'playlists'
|
156
|
+
@title = '网易云音乐 > 收藏精选歌单'
|
157
|
+
@datalist = @playlists
|
158
|
+
@offset = 0
|
159
|
+
@index = 0
|
160
|
+
|
161
|
+
# Load favorite songs.
|
162
|
+
when 'c'
|
163
|
+
@stack.push([datatype, title, datalist, offset, index])
|
164
|
+
@datatype = 'songs'
|
165
|
+
@title = '网易云音乐 > 收藏歌曲列表'
|
166
|
+
@datalist = @collection
|
167
|
+
@offset = 0
|
168
|
+
@index = 0
|
169
|
+
|
170
|
+
# Load favorite albums
|
171
|
+
when 'a'
|
172
|
+
@stack.push([datatype, title, datalist, offset, index])
|
173
|
+
@datatype = 'albums'
|
174
|
+
@title = '网易云音乐 > 收藏专辑'
|
175
|
+
@datalist = @albums
|
176
|
+
@offset = 0
|
177
|
+
@index = 0
|
178
|
+
|
179
|
+
# Load favorite dj channels
|
180
|
+
when 'z'
|
181
|
+
@stack.push([datatype, title, datalist, offset, index])
|
182
|
+
@datatype = 'djchannels'
|
183
|
+
@title = '网易云音乐 > 收藏 DJ 节目'
|
184
|
+
@datalist = @djs
|
185
|
+
@offset = 0
|
186
|
+
@index = 0
|
187
|
+
|
188
|
+
# Remove an entry from the present list.
|
189
|
+
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
|
194
|
+
|
195
|
+
# Main menu.
|
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
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
write_data
|
207
|
+
ui.build_menu(@datatype, @title, @datalist, @offset, @index, @step)
|
208
|
+
end
|
209
|
+
|
210
|
+
player.stop
|
211
|
+
exit
|
212
|
+
end
|
213
|
+
|
214
|
+
def dispatch_enter(idx)
|
215
|
+
# netease = @netease
|
216
|
+
datatype = @datatype
|
217
|
+
title = @title
|
218
|
+
datalist = @datalist
|
219
|
+
offset = @offset
|
220
|
+
index = @index
|
221
|
+
@stack.push([datatype, title, datalist, offset, index])
|
222
|
+
|
223
|
+
case datatype
|
224
|
+
when 'main'
|
225
|
+
choice_channel idx
|
226
|
+
|
227
|
+
# Hot songs to which a artist belongs.
|
228
|
+
when 'artists'
|
229
|
+
artist_id = datalist[idx]['artist_id']
|
230
|
+
songs = netease.artists(artist_id)
|
231
|
+
@datatype = 'songs'
|
232
|
+
@datalist = netease.dig_info(songs, 'songs')
|
233
|
+
@title += " > #{datalist[idx]['aritsts_name']}"
|
234
|
+
|
235
|
+
# All songs to which an album belongs.
|
236
|
+
when 'albums'
|
237
|
+
album_id = datalist[idx]['album_id']
|
238
|
+
songs = netease.album(album_id)
|
239
|
+
@datatype = 'songs'
|
240
|
+
@datalist = netease.dig_info(songs, 'songs')
|
241
|
+
@title += " > #{datalist[idx]['albums_name']}"
|
242
|
+
|
243
|
+
# All songs to which a playlist belongs.
|
244
|
+
when 'playlists'
|
245
|
+
playlist_id = datalist[idx]['playlist_id']
|
246
|
+
songs = netease.playlist_detail(playlist_id)
|
247
|
+
@datatype = 'songs'
|
248
|
+
@datalist = netease.dig_info(songs, 'songs')
|
249
|
+
@title += " > #{datalist[idx]['playlists_name']}"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def choice_channel(idx)
|
254
|
+
# netease = @netease
|
255
|
+
|
256
|
+
case idx
|
257
|
+
|
258
|
+
# Top
|
259
|
+
when 0
|
260
|
+
songs = netease.top_songlist
|
261
|
+
@datalist = netease.dig_info(songs, 'songs')
|
262
|
+
@title += ' > 排行榜'
|
263
|
+
@datatype = 'songs'
|
264
|
+
|
265
|
+
# Playlists
|
266
|
+
when 1
|
267
|
+
playlists = netease.top_playlists
|
268
|
+
@datalist = netease.dig_info(playlists, 'playlists')
|
269
|
+
@title += ' > 精选歌单'
|
270
|
+
@datatype = 'playlists'
|
271
|
+
|
272
|
+
# Artist
|
273
|
+
when 2
|
274
|
+
artists = netease.top_artists
|
275
|
+
@datalist = netease.dig_info(artists, 'artists')
|
276
|
+
@title += ' > 艺术家'
|
277
|
+
@datatype = 'artists'
|
278
|
+
|
279
|
+
# New album
|
280
|
+
when 3
|
281
|
+
albums = netease.new_albums
|
282
|
+
@datalist = netease.dig_info(albums, 'albums')
|
283
|
+
@title += ' > 新碟上架'
|
284
|
+
@datatype = 'albums'
|
285
|
+
|
286
|
+
# My playlist
|
287
|
+
when 4
|
288
|
+
# 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
|
301
|
+
|
302
|
+
# Fetch this user's all playlists while he logs in successfully.
|
303
|
+
my_playlist = netease.user_playlists(@userid)
|
304
|
+
@datalist = netease.dig_info(my_playlist, 'playlists')
|
305
|
+
@datatype = 'playlists'
|
306
|
+
@title += " > #{@username} 的歌单"
|
307
|
+
|
308
|
+
# DJ channels
|
309
|
+
when 5
|
310
|
+
@datatype = 'djchannels'
|
311
|
+
@title += ' > DJ 节目'
|
312
|
+
@datalist = netease.djchannels
|
313
|
+
|
314
|
+
# Favorite things.
|
315
|
+
when 6
|
316
|
+
favorite
|
317
|
+
|
318
|
+
# Search
|
319
|
+
when 7
|
320
|
+
search
|
321
|
+
|
322
|
+
# Help
|
323
|
+
when 8
|
324
|
+
@datatype = 'help'
|
325
|
+
@title += ' > 帮助'
|
326
|
+
@datalist = SHORTCUT
|
327
|
+
end
|
328
|
+
|
329
|
+
@offset = 0
|
330
|
+
@index = 0
|
331
|
+
end
|
332
|
+
|
333
|
+
def favorite
|
334
|
+
# ui = @ui
|
335
|
+
x = ui.build_favorite_menu
|
336
|
+
|
337
|
+
if (1..4).include? x.to_i
|
338
|
+
@stack.push([@datatype, @title, @datalist, @offset, @index])
|
339
|
+
@index = 0
|
340
|
+
@offset = 0
|
341
|
+
end
|
342
|
+
|
343
|
+
case x
|
344
|
+
|
345
|
+
when '1'
|
346
|
+
@datatype = 'songs'
|
347
|
+
@datalist = @collection
|
348
|
+
@title += ' > 收藏歌曲'
|
349
|
+
|
350
|
+
when '2'
|
351
|
+
@datatype = 'playlists'
|
352
|
+
@datalist = @playlists
|
353
|
+
@title += ' > 收藏歌单'
|
354
|
+
|
355
|
+
when '3'
|
356
|
+
@datatype = 'albums'
|
357
|
+
@datalist = @albums
|
358
|
+
@title += ' > 收藏专辑'
|
359
|
+
|
360
|
+
when '4'
|
361
|
+
@datatype = 'djchannels'
|
362
|
+
@datalist = @djs
|
363
|
+
@title += ' > 收藏 DJ 节目'
|
364
|
+
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def search
|
369
|
+
# ui = @ui
|
370
|
+
x = ui.build_search_menu
|
371
|
+
|
372
|
+
if (1..4).include? x.to_i
|
373
|
+
@stack.push([@datatype, @title, @datalist, @offset, @index])
|
374
|
+
@index = 0
|
375
|
+
@offset = 0
|
376
|
+
end
|
377
|
+
|
378
|
+
case x
|
379
|
+
|
380
|
+
when '1'
|
381
|
+
@datatype = 'songs'
|
382
|
+
@datalist = ui.build_search('songs')
|
383
|
+
@title = '歌曲搜索列表'
|
384
|
+
|
385
|
+
when '2'
|
386
|
+
@datatype = 'artists'
|
387
|
+
@datalist = ui.build_search('artists')
|
388
|
+
@title = '艺术家搜索列表'
|
389
|
+
|
390
|
+
when '3'
|
391
|
+
@datatype = 'albums'
|
392
|
+
@datalist = ui.build_search('albums')
|
393
|
+
@title = '专辑搜索列表'
|
394
|
+
|
395
|
+
when '4'
|
396
|
+
@datatype = 'playlists'
|
397
|
+
@datalist = ui.build_search('playlists')
|
398
|
+
@title = '精选歌单搜索列表'
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
private
|
403
|
+
|
404
|
+
def check_mdisc_dir
|
405
|
+
Dir.mkdir File.expand_path("~/.mdisc") unless Dir.exist? File.expand_path("~/.mdisc")
|
406
|
+
end
|
407
|
+
|
408
|
+
def read_data
|
409
|
+
check_mdisc_dir
|
410
|
+
user_file = File.expand_path("~/.mdisc/flavor.json")
|
411
|
+
return unless File.exist? user_file
|
412
|
+
|
413
|
+
data = JSON.parse(File.read(user_file))
|
414
|
+
@account = data['account'] || {}
|
415
|
+
@collection = data['collection'] || []
|
416
|
+
@playlists = data['playlists'] || []
|
417
|
+
@albums = data['albums'] || []
|
418
|
+
@djs = data['djs'] || []
|
419
|
+
end
|
420
|
+
|
421
|
+
def write_data
|
422
|
+
user_file = File.expand_path("~/.mdisc/flavor.json")
|
423
|
+
data = {
|
424
|
+
:account => @account,
|
425
|
+
:collection => @collection,
|
426
|
+
:playlists => @playlists,
|
427
|
+
:albums => @albums,
|
428
|
+
:djs => @djs
|
429
|
+
}
|
430
|
+
|
431
|
+
File.open(user_file, 'w') do |f|
|
432
|
+
f.write(JSON.generate(data))
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
data/lib/mdisc/player.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'open4'
|
2
|
+
|
3
|
+
class Player
|
4
|
+
attr_accessor :ui
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
self.ui = Ui.new
|
8
|
+
@datatype = 'songs'
|
9
|
+
@mpg123_thread = nil
|
10
|
+
@mpg123_pid = nil
|
11
|
+
@playing_flag = false
|
12
|
+
@pause_flag = false
|
13
|
+
@songs = []
|
14
|
+
@idx = 0
|
15
|
+
@wait = 0.5
|
16
|
+
@carousel = ->(left, right, x){x < left ? right : (x > right ? left : x)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def recall
|
20
|
+
@playing_flag = true
|
21
|
+
@pause_flag = false
|
22
|
+
|
23
|
+
item = @songs[@idx]
|
24
|
+
ui.build_playinfo(item['song_name'], item['artist'])
|
25
|
+
|
26
|
+
@thread = Thread.new do
|
27
|
+
@mp3id, stdin, stdout, stderr = Open4::popen4('mpg123', item['mp3_url'])
|
28
|
+
Process::waitpid2 @mp3id
|
29
|
+
|
30
|
+
if @playing_flag
|
31
|
+
@idx = @carousel[0, @songs.size - 1, @idx + 1]
|
32
|
+
recall
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def play(datatype, songs, idx, switch_flag = false)
|
38
|
+
@datatype = datatype
|
39
|
+
|
40
|
+
if !switch_flag
|
41
|
+
@pause_flag ? resume : pause if @playing_flag
|
42
|
+
elsif switch_flag
|
43
|
+
@songs = songs
|
44
|
+
@idx = idx
|
45
|
+
@playing_flag ? switch : recall
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def switch
|
50
|
+
stop
|
51
|
+
sleep @wait
|
52
|
+
recall
|
53
|
+
end
|
54
|
+
|
55
|
+
def stop
|
56
|
+
return unless @playing_flag
|
57
|
+
return unless @thread
|
58
|
+
return unless @mp3id
|
59
|
+
|
60
|
+
@playing_flag = false
|
61
|
+
# kill this process and thread
|
62
|
+
Process.kill(:SIGKILL, @mp3id)
|
63
|
+
Thread.kill @thread
|
64
|
+
end
|
65
|
+
|
66
|
+
def pause
|
67
|
+
@pause_flag = true
|
68
|
+
# send SIGSTOP to pipe
|
69
|
+
Process.kill(:SIGSTOP, @mp3id)
|
70
|
+
|
71
|
+
item = @songs[@idx]
|
72
|
+
ui.build_playinfo(item['song_name'], item['artist'], true)
|
73
|
+
end
|
74
|
+
|
75
|
+
def resume
|
76
|
+
@pause_flag = false
|
77
|
+
# send SIGCONT to pipe
|
78
|
+
Process.kill(:SIGCONT, @mp3id)
|
79
|
+
|
80
|
+
item = @songs[@idx]
|
81
|
+
ui.build_playinfo(item['song_name'], item['artist'])
|
82
|
+
end
|
83
|
+
|
84
|
+
def next
|
85
|
+
stop
|
86
|
+
sleep @wait
|
87
|
+
|
88
|
+
@idx = @carousel[0, @songs.size - 1, @idx + 1]
|
89
|
+
recall
|
90
|
+
end
|
91
|
+
|
92
|
+
def prev
|
93
|
+
stop
|
94
|
+
sleep @wait
|
95
|
+
|
96
|
+
@idx = @carousel[0, @songs.size - 1, @idx - 1]
|
97
|
+
recall
|
98
|
+
end
|
99
|
+
end
|
data/lib/mdisc/ui.rb
ADDED
@@ -0,0 +1,307 @@
|
|
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_accessor :netease, :screen
|
31
|
+
|
32
|
+
SCREEN_HEIGHT = 40
|
33
|
+
SCREEN_WIDTH = 80
|
34
|
+
SCREEN_TOP = 0
|
35
|
+
SCREEN_LEFT = 0
|
36
|
+
|
37
|
+
PLAYER_X = 5
|
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 - 2
|
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
|
+
self.screen = Curses::Window.new(SCREEN_HEIGHT, SCREEN_WIDTH, 0, 0)
|
58
|
+
|
59
|
+
self.netease = NetEase.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_playinfo(song_name, artist, pause = false)
|
63
|
+
if pause
|
64
|
+
putstr(screen, PLAYER_STATUS_Y, PLAYER_NOTE_X, '■', Curses.color_pair(3))
|
65
|
+
else
|
66
|
+
putstr(screen, PLAYER_STATUS_Y, PLAYER_NOTE_X, '▶', Curses.color_pair(3))
|
67
|
+
end
|
68
|
+
|
69
|
+
sn = pretty_format(song_name, 0, 32)
|
70
|
+
at = pretty_format(artist, 0, 28)
|
71
|
+
info = "#{sn} - #{at}"
|
72
|
+
putstr(screen, PLAYER_STATUS_Y, PLAYER_X, info, Curses.color_pair(4))
|
73
|
+
screen.refresh
|
74
|
+
end
|
75
|
+
|
76
|
+
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))
|
79
|
+
screen.refresh
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_menu(datatype, title, datalist, offset, index, step)
|
83
|
+
title = pretty_format(title, 0, 52)
|
84
|
+
|
85
|
+
clear_to_bottom(screen, PLAYER_CONTENT_Y,SCREEN_HEIGHT)
|
86
|
+
putstr(screen, PLAYER_TITLE_Y, PLAYER_X, title, Curses.color_pair(1))
|
87
|
+
|
88
|
+
if datalist.size == 0
|
89
|
+
putstr(screen, PLAYER_CONTENT_Y, PLAYER_X, '没有内容 Orz')
|
90
|
+
else
|
91
|
+
case datatype
|
92
|
+
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
|
101
|
+
end
|
102
|
+
|
103
|
+
putstr(screen, PLAYER_INFO_Y, PLAYER_X, 'Crafted with ❤ by cosmtrek', Curses.color_pair(3))
|
104
|
+
|
105
|
+
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
|
117
|
+
end
|
118
|
+
|
119
|
+
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
|
129
|
+
end
|
130
|
+
|
131
|
+
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
|
142
|
+
end
|
143
|
+
|
144
|
+
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
|
155
|
+
end
|
156
|
+
|
157
|
+
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
|
167
|
+
end
|
168
|
+
|
169
|
+
when 'help'
|
170
|
+
(offset...[datalist.length, offset + step].min).each do |i|
|
171
|
+
info = "#{i}. #{datalist[i][0]} #{datalist[i][1]} #{datalist[i][2]}"
|
172
|
+
putstr(screen, i-offset+PLAYER_CONTENT_Y, PLAYER_X, info)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
def build_search(stype)
|
180
|
+
case stype
|
181
|
+
when 'songs'
|
182
|
+
song_name = get_param('搜索歌曲:')
|
183
|
+
data = netease.search(song_name, stype = 1)
|
184
|
+
song_ids = []
|
185
|
+
if data['result'].include? 'songs'
|
186
|
+
if data['result']['songs'].include? 'mp3Url'
|
187
|
+
songs = data['result']['songs']
|
188
|
+
else
|
189
|
+
(0...data['result']['songs'].size).each do |i|
|
190
|
+
song_ids.push data['result']['songs'][i]['id']
|
191
|
+
end
|
192
|
+
songs = netease.songs_detail(song_ids)
|
193
|
+
end
|
194
|
+
return netease.dig_info(songs, 'songs')
|
195
|
+
end
|
196
|
+
|
197
|
+
when 'artists'
|
198
|
+
artist_name = get_param('搜索艺术家:')
|
199
|
+
data = netease.search(artist_name, stype = 100)
|
200
|
+
if data['result'].include? 'artists'
|
201
|
+
artists = data['result']['artists']
|
202
|
+
return netease.dig_info(artists, 'artists')
|
203
|
+
end
|
204
|
+
|
205
|
+
when 'albums'
|
206
|
+
artist_name = get_param('搜索专辑:')
|
207
|
+
data = netease.search(artist_name, stype = 10)
|
208
|
+
if data['result'].include? 'albums'
|
209
|
+
albums = data['result']['albums']
|
210
|
+
return netease.dig_info(albums, 'albums')
|
211
|
+
end
|
212
|
+
|
213
|
+
when 'playlists'
|
214
|
+
artist_name = get_param('搜索精选歌单:')
|
215
|
+
data = netease.search(artist_name, stype = 1000)
|
216
|
+
if data['result'].include? 'playlists'
|
217
|
+
playlists = data['result']['playlists']
|
218
|
+
return netease.dig_info(playlists, 'playlists')
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
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))
|
232
|
+
screen.refresh
|
233
|
+
screen.getch
|
234
|
+
end
|
235
|
+
|
236
|
+
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))
|
244
|
+
screen.refresh
|
245
|
+
screen.getch
|
246
|
+
end
|
247
|
+
|
248
|
+
def build_login
|
249
|
+
params = get_param('请输入登录信息:(e.g. foobar@163.com foobar)')
|
250
|
+
account = params.split(' ')
|
251
|
+
return build_login if account.size != 2
|
252
|
+
|
253
|
+
login_info = netease.login(account[0], account[1])
|
254
|
+
if login_info['code'] != 200
|
255
|
+
x = build_login_error
|
256
|
+
return x == '1' ? build_login : -1
|
257
|
+
else
|
258
|
+
return [login_info, account]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
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))
|
268
|
+
screen.refresh
|
269
|
+
screen.getch
|
270
|
+
end
|
271
|
+
|
272
|
+
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))
|
275
|
+
screen.setpos(PLAYER_CONTENT_Y + 2, PLAYER_X)
|
276
|
+
params = screen.getstr
|
277
|
+
if params.strip.nil?
|
278
|
+
return get_param(prompt_str)
|
279
|
+
else
|
280
|
+
return params
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
private
|
285
|
+
|
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)
|
291
|
+
end
|
292
|
+
|
293
|
+
def clear_to_bottom(screen, top, bottom)
|
294
|
+
(top..bottom).each do |i|
|
295
|
+
screen.setpos(i, 0)
|
296
|
+
screen.clrtoeol
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def pretty_format(info, start, length)
|
301
|
+
if info.size >= length
|
302
|
+
"#{info[start, length]}..."
|
303
|
+
else
|
304
|
+
info
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
data/mdisc.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 'mdisc/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mdisc"
|
8
|
+
spec.version = Mdisc::VERSION
|
9
|
+
spec.authors = ["Rick Yu"]
|
10
|
+
spec.email = ["cosmtrek@gmail.com"]
|
11
|
+
spec.summary = %q{A local music player based on Netease music.}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = "https://github.com/cosmtrek/mdisc"
|
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: mdisc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rick Yu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-18 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
|
+
- cosmtrek@gmail.com
|
44
|
+
executables:
|
45
|
+
- mdisc
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/mdisc
|
55
|
+
- lib/mdisc.rb
|
56
|
+
- lib/mdisc/api.rb
|
57
|
+
- lib/mdisc/menu.rb
|
58
|
+
- lib/mdisc/player.rb
|
59
|
+
- lib/mdisc/ui.rb
|
60
|
+
- lib/mdisc/version.rb
|
61
|
+
- mdisc.gemspec
|
62
|
+
homepage: https://github.com/cosmtrek/mdisc
|
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.2.2
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: A local music player based on Netease music.
|
86
|
+
test_files: []
|