xiami_radio 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f93c045ffdd8ba47df5d6396621207607e4ddb6b
4
+ data.tar.gz: bdd9094e9627de4774fe49d3459bb3b510f60c5a
5
+ SHA512:
6
+ metadata.gz: 8d51e8774338f469d4396d33aabfb93b3f2ed37bb5adcc860e41affae13245785f1c9ab9f46b8d3c778fbe7db3e10bb78b0d851447a888de1a2cd0a976137957
7
+ data.tar.gz: d35701ac4c45494d0e2541b8b307b0df193edd247a15713a6c417c243392a8c1fcefd181b07a4af8cba5ce17549d4ad2c4c006a3a116cc40f4673f9e65e93f31
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 IvanChou
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
File without changes
data/bin/xiami_radio ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ #coding=utf-8
3
+
4
+ require 'xiami_radio'
5
+ require 'io/console'
6
+
7
+ user = XiamiRadio::User.new
8
+
9
+ email = unless user.login?
10
+ puts 'Plz keep empty if you don\'t want to login'
11
+ print 'Email: '
12
+ gets.chomp
13
+ end.to_s
14
+ pwd = unless user.login? || email.empty?
15
+ print 'Password: '
16
+ STDIN.noecho(&:gets).chomp
17
+ end.to_s
18
+ user.login_by_email email, pwd unless email.empty? || pwd.empty?
19
+
20
+ puts 'Which radio do you want to listen?'
21
+ puts '1/Enter - 虾米猜 2 - 个人电台 3 - 其他'
22
+
23
+ radio_option = case gets.chomp[0]
24
+ when nil
25
+ {type: 8, oid: 0}
26
+ when '1'
27
+ {type: 8, oid: 0}
28
+ when '2'
29
+ user.login? ? {type: 4, oid: user.user_id} : {type: 8, oid: 0}
30
+ else
31
+ print 'type: '
32
+ type = gets.chomp[0]
33
+ print 'oid: '
34
+ oid = gets.chomp
35
+ {type: (type || 8).to_i, oid: oid.to_i}
36
+ end
37
+
38
+ XiamiRadio::Player.play user.radio radio_option
@@ -0,0 +1,69 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'nori'
4
+ require 'http-cookie'
5
+
6
+ module XiamiRadio
7
+ # There is a client as you saw
8
+ class Client
9
+ HOST = 'http://www.xiami.com'.freeze
10
+ LOGIN_HOST = 'https://login.xiami.com'.freeze
11
+ HEADERS = {
12
+ 'Accept' => '*/*',
13
+ 'Accept-Encoding' => '*',
14
+ 'Accept-Language' => 'en-US,en;q=0.8,zh;q=0.6,zh-TW;q=0.4',
15
+ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
16
+ }.freeze
17
+
18
+ attr_accessor :user, :headers, :http
19
+
20
+ def initialize(headers: {}, user: nil, host: HOST)
21
+ @user = user || User.new
22
+ @headers = HEADERS.merge headers
23
+ @uri = URI.parse host
24
+
25
+ # @http = Net::HTTP.new(@uri.host, @uri.port, '127.0.0.1', '8888')
26
+ @http = Net::HTTP.new @uri.host, @uri.port
27
+ @http.use_ssl = @uri.scheme == 'https'
28
+ end
29
+
30
+ def uri(**args)
31
+ @uri.class.build scheme: @uri.scheme, host: @uri.host, **args
32
+ end
33
+
34
+ def get(uri, format: :json, headers: {})
35
+ request uri, format, headers do |_headers|
36
+ @http.start { |http| http.request(Net::HTTP::Get.new uri, _headers) }
37
+ end
38
+ end
39
+
40
+ def post(uri, form_data, format: :json, headers: {})
41
+ request uri, format, headers do |_headers|
42
+ req = Net::HTTP::Post.new uri, _headers
43
+ req.set_form_data form_data
44
+ @http.start { |http| http.request req }
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def request(uri, format, headers, &block)
51
+ _headers = @headers.merge headers
52
+ _headers.merge! 'Cookie' => HTTP::Cookie.cookie_value(user.cookie_jar.cookies uri)
53
+ _headers.merge! 'X-Requested-With' => 'XMLHttpRequest' if %i(json xml).include? format
54
+
55
+ res = block.call _headers
56
+
57
+ res.get_fields('set-cookie')&.each { |value| user.cookie_jar.parse value, uri }
58
+ case format
59
+ when :json then JSON.parse(res.body, symbolize_names: true)
60
+ when :xml then nori.parse res.body
61
+ else res
62
+ end
63
+ end
64
+
65
+ def nori
66
+ @nori ||= Nori.new(:convert_tags_to => -> (tag) { tag.snakecase.to_sym })
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ require 'net/http'
2
+
3
+ module XiamiRadio
4
+ class Downloader
5
+ class << self
6
+ def circulator
7
+ @circulator ||= Queue.new
8
+ %w(甲 乙).map(&@circulator.method(:push)) if @circulator.empty?
9
+ @circulator
10
+ end
11
+ end
12
+
13
+ attr_reader :track, :progress, :total, :file
14
+
15
+ def initialize(track)
16
+ @track = track
17
+ @uri = URI @track.location
18
+ end
19
+
20
+ def filename
21
+ File.join XiamiRadio::TMP_DIR, self.class.circulator.pop(true)
22
+ end
23
+
24
+ def progress
25
+ (@progress.to_f / @total.to_f).round(2) unless @progress.nil?
26
+ end
27
+
28
+ def start
29
+ @thread = Thread.start do
30
+ Net::HTTP.get_response @uri do |res|
31
+ if res.code == '302'
32
+ @uri = URI res.header['Location']
33
+ start
34
+ else
35
+ @progress, @total = 0, res.header['Content-Length'].to_i
36
+ @file = File.open(filename, 'w')
37
+ res.read_body do |chunk|
38
+ @file << chunk
39
+ @progress += chunk.size
40
+ @file.close unless @progress < @total
41
+ end
42
+ end
43
+ end
44
+ end
45
+ sleep 0.1 until @progress.to_i > 0
46
+ end
47
+
48
+ def stop
49
+ @thread&.exit
50
+ @thread = nil
51
+ File.delete(@file)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module XiamiRadio
2
+ class Notice
3
+ class << self
4
+
5
+ def push(msg, expired = 3)
6
+ queue << { content: msg, expired_at: Time.now.to_i + expired }
7
+ end
8
+
9
+ def shift
10
+ queue.shift until queue.empty? || queue.first[:expired_at] > Time.now.to_i
11
+ queue.first&.fetch(:content, nil)
12
+ end
13
+
14
+ def queue
15
+ @queue ||= []
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,72 @@
1
+ require 'audite'
2
+
3
+ module XiamiRadio
4
+ # There is a player as you saw
5
+ class Player
6
+
7
+ attr_reader :track, :next_track, :radio, :user
8
+
9
+ def initialize(radio:, _playlist: nil)
10
+ @radio, @user = radio, radio.client.user
11
+ @player = Audite.new
12
+ @view = View::Player.new self
13
+
14
+ @player.events.on :position_change, &method(:position_change)
15
+ @player.events.on :complete, &method(:complete)
16
+ @view.listen_on
17
+ end
18
+
19
+ def play
20
+ @track = @radio.next_track
21
+ @player.queue @track.file_path
22
+ @player.set_current_song
23
+ @player.start_stream
24
+ @player.thread.join
25
+ end
26
+
27
+ def next
28
+ if @next_track.nil?
29
+ @next_track = @radio.next_track
30
+ @player.queue @next_track.file_path
31
+ end
32
+ @track, @next_track = @next_track, nil
33
+ @player.request_next_song
34
+ end
35
+
36
+ def rewind
37
+ @player.rewind
38
+ end
39
+
40
+ def forward
41
+ @player.forward
42
+ end
43
+
44
+ def toggle
45
+ @player.toggle
46
+ end
47
+
48
+ private
49
+
50
+ def position_change(position)
51
+ @view.refresh position
52
+
53
+ if @next_track.nil? && position / @track.duration > 0.7
54
+ Thread.start do
55
+ @track.record
56
+ @next_track = @radio.next_track
57
+ @player.queue @next_track.file_path
58
+ end
59
+ end
60
+ end
61
+
62
+ def complete
63
+ @track, @next_track = @next_track, nil
64
+ end
65
+
66
+ def self.play(radio)
67
+ player = new radio: radio
68
+ player.play
69
+ player
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,41 @@
1
+ module XiamiRadio
2
+ # There is a radio as you saw
3
+ class Radio
4
+ TRACKS_PATH = '/radio/xml/type/%{type}/id/%{oid}'
5
+ RADIO_PATH = '/radio/play/type/%{type}/oid/%{oid}'
6
+
7
+ XIAMI_CAI = {type: 8, oid: 0}.freeze
8
+
9
+ attr_reader :type, :oid, :client, :nori, :play_queue
10
+
11
+ def initialize(type:, oid:, user: nil)
12
+ @type = type
13
+ @oid = oid
14
+ @client = user&.client || Client.new
15
+ @nori = Nori.new(:convert_tags_to => -> (tag) { tag.snakecase.to_sym })
16
+ @play_queue = []
17
+ end
18
+
19
+ def get_new_playlist
20
+ tracks = client.get(uri, format: :xml, headers: headers_referer).dig(:play_list, :track_list, :track)
21
+ tracks.map { |track| Track.new track, radio: self }
22
+ end
23
+
24
+ def next_track
25
+ @play_queue += get_new_playlist if @play_queue.size < 2
26
+ @play_queue.shift
27
+ end
28
+
29
+ def headers_referer
30
+ referer = client.uri(path: RADIO_PATH % {type: type, oid: oid}).to_s
31
+ {'Referer': referer}
32
+ end
33
+
34
+ private
35
+
36
+ def uri
37
+ client.uri path: TRACKS_PATH % {type: type, oid: oid},
38
+ query: URI.encode_www_form(v: Time.now.to_i)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ module XiamiRadio
2
+ class Track
3
+ attr_reader :info, :title, :song_id, :album_name, :artist, :radio, :client
4
+
5
+ def initialize(track, radio:)
6
+ @info = track
7
+ @title, @song_id, @album_name, @artist = track.values_at(:title, :song_id, :album_name, :artist)
8
+ @radio = radio
9
+ @client = radio.client
10
+ end
11
+
12
+ def location(hd: false)
13
+ hd ? decode_location(hd_location) : decode_location(@info[:location])
14
+ end
15
+
16
+ def duration
17
+ @info[:length].to_f > 1 ? @info[:length].to_f : (@info[:length].to_f * 1_000_000)
18
+ end
19
+
20
+ def duration=(duration)
21
+ @info[:length] = duration
22
+ end
23
+
24
+ def grade?
25
+ @info[:grade].to_i == 1
26
+ end
27
+
28
+ def reason
29
+ @info[:reason] ||= {content: '来自电台推送'}
30
+ OpenStruct.new @info[:reason]
31
+ end
32
+
33
+ def downloader
34
+ @downloader ||= Downloader.new self
35
+ end
36
+
37
+ def file_path
38
+ @info[:file_path] ||= begin
39
+ downloader.start if downloader.file.nil?
40
+ downloader.file.path
41
+ end
42
+ end
43
+
44
+ def record
45
+ uri = client.uri path: '/count/playrecord',
46
+ query: URI.encode_www_form(sid: song_id, type: 1, ishq: 1)
47
+ client.get(uri, headers: radio.headers_referer, format: :js)
48
+ end
49
+
50
+ def fav
51
+ uri = client.uri path: '/song/fav',
52
+ query: URI.encode_www_form(ids: song_id, _xiamitoken: client.user.xiami_token)
53
+ res = client.get(uri, headers: radio.headers_referer, format: :js)
54
+
55
+ return '操作失败 (╯‵□′)╯︵┻━┻' unless res.code == '200'
56
+ grade = /player_collected\('(\d)','(\d+)'\)/.match(res.body)[1]
57
+ grade == '1' ? '已添加到音乐库' : '已从音乐库中移除'
58
+ end
59
+
60
+ private
61
+
62
+ def decode_location(location)
63
+ key, tmp_url = location[0].to_i, location[1..location.length]
64
+ fr, ll, lu, true_url = tmp_url.length.divmod(key), [], [], ''
65
+
66
+ key.times do |i|
67
+ ll << (fr[1] > 0 ? fr[0] + 1 : fr[0])
68
+ lu << tmp_url[0,ll[i]]
69
+ tmp_url = tmp_url[ll[i]..tmp_url.length]
70
+ fr[1] -= 1
71
+ end
72
+
73
+ ll[0].times do |i|
74
+ lu.each do |piece|
75
+ piece[i] && true_url << piece[i]
76
+ end
77
+ end
78
+
79
+ URI.decode(true_url).gsub('^', '0')
80
+ end
81
+
82
+ def hd_location
83
+ @info[:hd_location] ||= begin
84
+ uri = client.uri path: "/song/gethqsong/sid/#{song_id}"
85
+ client.get(uri, headers: radio.headers_referer)[:location]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,80 @@
1
+ require 'http-cookie'
2
+
3
+ module XiamiRadio
4
+ # There is a user as you saw
5
+ class User
6
+ attr_reader :nick_name, :level, :user_id, :sign, :is_vip
7
+ attr_accessor :cookie_jar
8
+
9
+ def initialize
10
+ @cookie_jar = HTTP::CookieJar.new
11
+ @cookie_jar.load cookie_file if File.exist? cookie_file
12
+ get_user_info
13
+ end
14
+
15
+ def radio(**args)
16
+ XiamiRadio::Radio.new user: self, **args
17
+ end
18
+
19
+ def login?
20
+ !user_id.to_s.empty?
21
+ end
22
+
23
+ def login_by_email(email, password)
24
+ page_uri = login_client.uri(path: '/member/login')
25
+ form_uri = login_client.uri(path: '/passport/login')
26
+ login_client.get page_uri, format: :html
27
+ form_data = {
28
+ _xiamitoken: xiami_token,
29
+ done: 'http%3A%2F%2Fwww.xiami.com%2F%2F',
30
+ verifycode: '',
31
+ account: email,
32
+ pw: password,
33
+ submit: '登录'
34
+ }
35
+ login_client.post form_uri, form_data, headers: {
36
+ 'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
37
+ 'Referer' => page_uri.to_s
38
+ }
39
+ @cookie_jar.save cookie_file
40
+ get_user_info
41
+ end
42
+
43
+ def login_by_member_auth(member_auth)
44
+ uri = client.uri path: '/index/home'
45
+ @cookie_jar.parse "member_auth=#{member_auth}; path=/; domain=.xiami.com", uri
46
+ client.get uri, format: :head
47
+ @cookie_jar.save cookie_file
48
+ get_user_info
49
+ end
50
+
51
+ def xiami_token
52
+ cookie_jar.cookies.select { |c| c.name == '_xiamitoken' }.first&.value
53
+ end
54
+
55
+ def client
56
+ @client ||= Client.new user: self
57
+ end
58
+
59
+ private
60
+
61
+ def login_client
62
+ @l_client ||= Client.new user: self, host: Client::LOGIN_HOST
63
+ end
64
+
65
+ def get_user_info
66
+ update client.get(client.uri path: '/index/home').dig(:data, :userInfo)
67
+ Notice.push "欢迎归来 #{nick_name},当前已连续签到 #{sign[:persist_num]} 天", 10 if login?
68
+ end
69
+
70
+ def update(attrs)
71
+ return false unless attrs.is_a? Hash
72
+ @nick_name, @level, @user_id, @sign, @is_vip = attrs.values_at :nick_name, :level, :user_id, :sign, :isVip
73
+ true
74
+ end
75
+
76
+ def cookie_file
77
+ File.join XiamiRadio::TMP_DIR, '己'
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ module XiamiRadio
2
+
3
+ VERSION = '1.1.0'
4
+
5
+ end
@@ -0,0 +1,90 @@
1
+ require 'curses'
2
+
3
+ module XiamiRadio::View
4
+ class Player
5
+ attr_reader :player
6
+
7
+ def initialize(player)
8
+ @player = player
9
+ Curses.init_screen
10
+ Curses.noecho
11
+ Curses.stdscr.keypad(true)
12
+ Curses.start_color
13
+ Curses.use_default_colors
14
+ Curses.init_pair(Curses::COLOR_CYAN,Curses::COLOR_CYAN,Curses::COLOR_BLACK)
15
+ Curses.init_pair(Curses::COLOR_RED,Curses::COLOR_RED,Curses::COLOR_BLACK)
16
+ Curses.clear
17
+ end
18
+
19
+ def refresh(position)
20
+ Curses.clear
21
+ render_title_line player.track
22
+ render_progress_line (position / player.track.duration), player.track.downloader.progress
23
+ render_info_line player.track, position
24
+ render_msg_line
25
+ Curses.refresh
26
+ end
27
+
28
+ def listen_on
29
+ Thread.start do
30
+ while (key = Curses.getch)
31
+ case key
32
+ when Curses::KEY_LEFT
33
+ player.rewind
34
+ when Curses::KEY_RIGHT
35
+ player.forward
36
+ when Curses::KEY_DOWN
37
+ player.next
38
+ when 'l'
39
+ XiamiRadio::Notice.push player.track.fav
40
+ when ' '
41
+ player.toggle
42
+ else #
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def render_title_line(track)
51
+ Curses.setpos(0, 0)
52
+ Curses.addstr('Now is playing ')
53
+ Curses.attron(Curses.color_pair(Curses::COLOR_RED)|Curses::A_NORMAL){
54
+ Curses.addstr(" #{track.title} - #{track.artist} ")
55
+ }
56
+ if @player.user.login?
57
+ track.grade? ? Curses.addstr(' 已收藏 ') : Curses.addstr(' 按"L"加入收藏 ')
58
+ end
59
+ end
60
+
61
+ def render_progress_line(play_rate, dawnload_rate)
62
+ p = (play_rate * Curses.cols).round
63
+ d = (dawnload_rate * Curses.cols).round - p
64
+ Curses.setpos(1, 0)
65
+ Curses.addstr('_' * p + '#' * d + ' ' * (Curses.cols - p - d))
66
+ end
67
+
68
+ def render_info_line(track, position)
69
+ Curses.setpos(2, 0)
70
+ Curses.addstr("#{track.reason.content}")
71
+ Curses.addstr(": #{track.reason.title} ") unless track.reason.title.nil?
72
+ Curses.addstr("- #{track.reason.artist} ") unless track.reason.artist.nil?
73
+ Curses.addstr(" 播放进度: ")
74
+ Curses.attron(Curses.color_pair(Curses::COLOR_CYAN)|Curses::A_NORMAL){
75
+ Curses.addstr(" #{sec_2_min position} / #{sec_2_min track.duration} ")
76
+ }
77
+ end
78
+
79
+ def render_msg_line(msg = XiamiRadio::Notice.shift)
80
+ return if msg.nil?
81
+ Curses.setpos(3, 0)
82
+ Curses.addstr(msg)
83
+ end
84
+
85
+ def sec_2_min(sec)
86
+ Time.at(sec).utc.strftime('%M:%S')
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,20 @@
1
+ require 'xiami_radio/client'
2
+ require 'xiami_radio/downloader'
3
+ require 'xiami_radio/notice'
4
+ require 'xiami_radio/player'
5
+ require 'xiami_radio/radio'
6
+ require 'xiami_radio/track'
7
+ require 'xiami_radio/user'
8
+ require 'xiami_radio/view/player'
9
+
10
+ require 'tmpdir'
11
+
12
+ module XiamiRadio
13
+ TMP_DIR = File.join(Dir.tmpdir, 'xiami_radio').freeze
14
+
15
+ def self.mktmpdir
16
+ Dir.mkdir TMP_DIR, 0700 unless Dir.exist? TMP_DIR
17
+ end
18
+ end
19
+
20
+ XiamiRadio.mktmpdir
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xiami_radio
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Chou
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: audite
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: curses
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: http-cookie
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nori
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '9.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '9.0'
83
+ description: a command-line tool to help you listen Xiami radio via a geek way
84
+ email:
85
+ - me@ichou.cn
86
+ executables:
87
+ - xiami_radio
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - MIT-LICENSE
92
+ - README.md
93
+ - bin/xiami_radio
94
+ - lib/xiami_radio.rb
95
+ - lib/xiami_radio/client.rb
96
+ - lib/xiami_radio/downloader.rb
97
+ - lib/xiami_radio/notice.rb
98
+ - lib/xiami_radio/player.rb
99
+ - lib/xiami_radio/radio.rb
100
+ - lib/xiami_radio/track.rb
101
+ - lib/xiami_radio/user.rb
102
+ - lib/xiami_radio/version.rb
103
+ - lib/xiami_radio/view/player.rb
104
+ homepage: https://github.com/IvanChou/xiami_radio
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.6.11
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Xiami radio player with command-line on ruby
128
+ test_files: []