mdisc 0.1.1 → 0.1.2
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 +4 -4
- data/README.md +5 -3
- data/Rakefile +1 -1
- data/bin/mdisc +1 -1
- data/lib/mdisc.rb +6 -5
- data/lib/mdisc/api.rb +65 -53
- data/lib/mdisc/menu.rb +68 -66
- data/lib/mdisc/player.rb +5 -4
- data/lib/mdisc/screen.rb +46 -0
- data/lib/mdisc/ui.rb +77 -118
- data/lib/mdisc/version.rb +1 -1
- data/mdisc.gemspec +9 -8
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acbc08f6f64182d057b78a6234572c33b09d65a4
|
4
|
+
data.tar.gz: d6ba36291042a95a9623a044894632523e680d15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e2841dd8b0cea3d0bc97409972624aed93409e0923fce9193acabc7d564ba7d8b487e688977a127158d9fe2fdb3c464d8eb6dc6b247ddc371d251a0fa3a91dc
|
7
|
+
data.tar.gz: d45dcb453da9a6c249ad90dc05894074bb73e02e6cdb8e80275bacb436f157bf154760f79f52e3b7423ab13468ba655163a697d79ce9a19a1ee2ee78e0856c05
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Mdisc
|
2
2
|
|
3
|
+

|
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
|
-
##
|
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
|
-
##
|
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
|
56
|
+
Their great projects inspired me. Thanks!
|
55
57
|
|
56
58
|
## License
|
57
59
|
|
data/Rakefile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
|
data/bin/mdisc
CHANGED
data/lib/mdisc.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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'
|
data/lib/mdisc/api.rb
CHANGED
@@ -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"
|
12
|
-
"Content-Type"
|
13
|
-
"Host"
|
14
|
-
"Referer"
|
15
|
-
"User-Agent"
|
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
|
-
|
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 =
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
99
|
+
action = 'http://music.163.com/discover/toplist'
|
111
100
|
connection = http_request('GET', action)
|
112
|
-
songids = connection.scan(/\/song\?id=(\d+)/)
|
113
|
-
|
114
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
129
|
+
|
130
|
+
data['songs']
|
137
131
|
end
|
138
132
|
|
139
133
|
# song id -> song url (details)
|
140
|
-
def song_detail(
|
141
|
-
id =
|
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
|
-
|
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
|
-
|
153
|
-
|
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
|
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
|
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
|
data/lib/mdisc/menu.rb
CHANGED
@@ -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
|
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
|
-
#
|
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
|
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
|
122
|
+
sleep WAIT_TIME
|
122
123
|
|
123
124
|
# Previous song
|
124
125
|
when '['
|
125
126
|
player.prev
|
126
|
-
sleep
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
191
|
-
|
192
|
-
|
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
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
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
|
-
|
230
|
-
songs = netease.artists
|
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
|
-
|
238
|
-
songs = netease.album
|
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
|
-
|
246
|
-
songs = netease.playlist_detail
|
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
|
-
|
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
|
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
|
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
|
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
|
362
|
+
@datalist = ui.build_search @datatype
|
383
363
|
@title = '歌曲搜索列表'
|
384
364
|
|
385
365
|
when '2'
|
386
366
|
@datatype = 'artists'
|
387
|
-
@datalist = ui.build_search
|
367
|
+
@datalist = ui.build_search @datatype
|
388
368
|
@title = '艺术家搜索列表'
|
389
369
|
|
390
370
|
when '3'
|
391
371
|
@datatype = 'albums'
|
392
|
-
@datalist = ui.build_search
|
372
|
+
@datalist = ui.build_search @datatype
|
393
373
|
@title = '专辑搜索列表'
|
394
374
|
|
395
375
|
when '4'
|
396
376
|
@datatype = 'playlists'
|
397
|
-
@datalist = ui.build_search
|
377
|
+
@datalist = ui.build_search @datatype
|
398
378
|
@title = '精选歌单搜索列表'
|
399
379
|
end
|
400
380
|
end
|
401
381
|
|
402
382
|
private
|
403
383
|
|
404
|
-
def
|
405
|
-
|
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
|
-
|
410
|
-
|
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
|
-
|
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
|
data/lib/mdisc/player.rb
CHANGED
@@ -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
|
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
|
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
|
95
|
+
sleep WAIT_TIME
|
95
96
|
|
96
97
|
@idx = @carousel[0, @songs.size - 1, @idx - 1]
|
97
98
|
recall
|
data/lib/mdisc/screen.rb
ADDED
@@ -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
|
data/lib/mdisc/ui.rb
CHANGED
@@ -44,132 +44,86 @@ class Ui
|
|
44
44
|
PLAYER_POINTER_X = PLAYER_X - 3
|
45
45
|
|
46
46
|
def initialize
|
47
|
-
|
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
|
-
|
53
|
+
screen.line(PLAYER_STATUS_Y, PLAYER_NOTE_X, '■', 3)
|
65
54
|
else
|
66
|
-
|
55
|
+
screen.line(PLAYER_STATUS_Y, PLAYER_NOTE_X, '▶', 3)
|
67
56
|
end
|
68
57
|
|
69
|
-
sn =
|
70
|
-
at =
|
58
|
+
sn = pretty(song_name, 0, 32)
|
59
|
+
at = pretty(artist, 0, 28)
|
71
60
|
info = "#{sn} - #{at}"
|
72
|
-
|
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
|
-
|
78
|
-
|
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 =
|
72
|
+
title = pretty(title, 0, 52)
|
84
73
|
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
-
(
|
94
|
-
|
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
|
-
|
88
|
+
screen.line(PLAYER_INFO_Y, PLAYER_X, 'Crafted with ❤ by cosmtrek', 3)
|
104
89
|
|
105
90
|
when 'songs'
|
106
|
-
(
|
107
|
-
sn =
|
108
|
-
at =
|
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
|
-
(
|
121
|
-
an =
|
122
|
-
|
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
|
-
(
|
133
|
-
al =
|
134
|
-
an =
|
135
|
-
|
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
|
-
(
|
146
|
-
pn =
|
147
|
-
cn =
|
148
|
-
|
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
|
-
(
|
159
|
-
sn =
|
160
|
-
|
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
|
-
|
124
|
+
entries.each do |i|
|
171
125
|
info = "#{i}. #{datalist[i][0]} #{datalist[i][1]} #{datalist[i][2]}"
|
172
|
-
|
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
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
274
|
-
|
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
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
301
|
-
|
302
|
-
|
303
|
-
|
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
|
data/lib/mdisc/version.rb
CHANGED
data/mdisc.gemspec
CHANGED
@@ -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 =
|
8
|
+
spec.name = 'mdisc'
|
8
9
|
spec.version = Mdisc::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
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 =
|
14
|
-
spec.license =
|
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 = [
|
20
|
+
spec.require_paths = ['lib']
|
20
21
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
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.
|
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-
|
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
|