douban.fm 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/douban.fm +120 -42
- data/douban.fm.gemspec +3 -0
- data/lib/douban.fm.rb +1 -0
- data/lib/douban.fm/douban_fm.rb +67 -9
- data/lib/douban.fm/logger.rb +11 -0
- data/lib/douban.fm/version.rb +1 -1
- metadata +27 -3
data/bin/douban.fm
CHANGED
@@ -1,53 +1,131 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'douban.fm'
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
def main
|
8
|
+
options = OpenStruct.new
|
9
|
+
options.verbose = false
|
10
|
+
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [OPTIONS]"
|
13
|
+
|
14
|
+
opts.on('-u', '--user email',
|
15
|
+
'douban.fm account name, normally an email address',
|
16
|
+
'if not provided, will play anonymous playlist') do |email|
|
17
|
+
options.email = email
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('-p', '--password [password]',
|
21
|
+
'douban.fm account password',
|
22
|
+
'if not provided, will be asked') do |password|
|
23
|
+
options.password = password
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on('-m', '--mpd', 'do not play by it own, send playlist to Music Player Daemon') do
|
27
|
+
options.mpd = true
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on('-c', '--channel channel',
|
31
|
+
'which channel to play',
|
32
|
+
'if not provided, channel 0 will be selected but who knows what it is') do |channel|
|
33
|
+
options.channel = channel
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-l', '--list', 'list all available channels') do
|
37
|
+
options.list = true
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-v', '--verbose', 'verbose mode') do
|
41
|
+
options.verbose = true
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on_tail('-h', '--help', 'show this message') do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.parse!
|
51
|
+
|
52
|
+
if options.verbose
|
53
|
+
logger = DoubanFM::ConsoleLogger.new
|
54
|
+
else
|
55
|
+
logger = DoubanFM::DummyLogger.new
|
56
|
+
end
|
57
|
+
|
58
|
+
if options.list
|
59
|
+
douban_fm = DoubanFM::DoubanFM.new(logger)
|
60
|
+
douban_fm.fetch_channels
|
61
|
+
douban_fm.channels['channels'].sort_by { |i| i['channel_id'] }.each do |channel|
|
62
|
+
channel_id = channel['channel_id']
|
63
|
+
puts "#{channel_id}.#{' ' * (4 - channel_id.to_s.length)}#{channel['name']}"
|
64
|
+
end
|
65
|
+
|
66
|
+
exit
|
67
|
+
end
|
68
|
+
|
69
|
+
if options.channel.nil?
|
70
|
+
options.channel = 0
|
71
|
+
end
|
72
|
+
|
73
|
+
if options.email.nil?
|
74
|
+
douban_fm = DoubanFM::DoubanFM.new(logger)
|
75
|
+
|
76
|
+
logger.log('play anonymous playlist')
|
77
|
+
else
|
78
|
+
if options.password.nil? or options.password.empty?
|
79
|
+
require 'highline/import'
|
80
|
+
options.password = ask("Enter password: ") { |q| q.echo = false }
|
81
|
+
end
|
82
|
+
|
83
|
+
douban_fm = DoubanFM::DoubanFM.new(logger, options.email, options.password)
|
84
|
+
douban_fm.login
|
85
|
+
|
86
|
+
logger.log("login as user [#{options.email}]")
|
87
|
+
end
|
88
|
+
|
89
|
+
douban_fm.select_channel(options.channel)
|
90
|
+
|
91
|
+
logger.log("select channel #{options.channel}")
|
92
|
+
|
93
|
+
if options.mpd.nil?
|
94
|
+
play_proc = proc do |waiting|
|
95
|
+
if waiting
|
96
|
+
begin
|
97
|
+
logger.log('fetch next playlist')
|
98
|
+
|
99
|
+
douban_fm.fetch_next_playlist
|
100
|
+
rescue
|
101
|
+
logger.log('session expired, relogin')
|
102
|
+
|
103
|
+
douban_fm.login
|
104
|
+
|
105
|
+
logger.log('fetch next playlist')
|
106
|
+
|
107
|
+
douban_fm.fetch_next_playlist
|
108
|
+
end
|
109
|
+
|
110
|
+
logger.log('play current playlist')
|
111
|
+
|
112
|
+
douban_fm.play do |waiting|
|
113
|
+
play_proc.call(waiting)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
9
117
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
douban_fm.
|
16
|
-
|
17
|
-
# stop = false
|
18
|
-
# go_on = true
|
19
|
-
# while not stop
|
20
|
-
# if go_on
|
21
|
-
# p "================="
|
22
|
-
# go_on = false
|
23
|
-
# douban_fm.get_next_playlist
|
24
|
-
# douban_fm.play_current_playlist do |waiting|
|
25
|
-
# unless waiting
|
26
|
-
# stop = true
|
27
|
-
# break
|
28
|
-
# else
|
29
|
-
# go_on = true
|
30
|
-
# end
|
31
|
-
# end
|
32
|
-
# else
|
33
|
-
# sleep 10
|
34
|
-
# end
|
35
|
-
# end
|
36
|
-
|
37
|
-
play_proc = proc do |waiting|
|
38
|
-
if waiting
|
39
|
-
begin
|
40
|
-
douban_fm.get_next_playlist
|
41
|
-
rescue
|
42
|
-
douban_fm.login
|
43
|
-
end
|
44
|
-
|
45
|
-
douban_fm.play_current_playlist do |waiting|
|
46
|
-
play_proc.call(waiting)
|
118
|
+
play_proc.call(true)
|
119
|
+
else
|
120
|
+
# there are a lot more chances to get stack overflow in this mode,
|
121
|
+
# so can't use the proc way
|
122
|
+
while true
|
123
|
+
douban_fm.add_to_mpd
|
124
|
+
sleep 10
|
47
125
|
end
|
48
126
|
end
|
49
127
|
end
|
50
128
|
|
51
|
-
|
129
|
+
main
|
52
130
|
|
53
131
|
sleep
|
data/douban.fm.gemspec
CHANGED
@@ -15,4 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
17
|
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_dependency 'ruby-mpd'
|
20
|
+
gem.add_dependency 'highline'
|
18
21
|
end
|
data/lib/douban.fm.rb
CHANGED
data/lib/douban.fm/douban_fm.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
1
|
module DoubanFM
|
2
2
|
require 'net/http'
|
3
3
|
require 'json'
|
4
|
+
require 'ruby-mpd'
|
4
5
|
|
5
6
|
class DoubanFM
|
6
|
-
|
7
|
+
DOUBAN_FM_MPD_PLAYLIST = 'douban.fm'
|
8
|
+
MIN_SONGS_IN_DOUBAN_FM_MPD_PLAYLIST = 10
|
7
9
|
|
8
|
-
|
10
|
+
attr_reader :waiting, :channels, :current_playlist
|
11
|
+
|
12
|
+
def initialize(logger = DummyLogger.new, email = '', password = '')
|
13
|
+
@logger = logger
|
9
14
|
@email = email
|
10
15
|
@password = password
|
11
16
|
@semaphore = Mutex.new
|
12
|
-
@waiting = false # read this to determin whether to
|
17
|
+
@waiting = false # read this to determin whether to fetch one more playlist
|
18
|
+
@user_info = {'user_id' => '', 'expire' => '', 'token' => ''}
|
13
19
|
end
|
14
20
|
|
15
21
|
def login
|
@@ -25,17 +31,19 @@ module DoubanFM
|
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
28
|
-
def
|
34
|
+
def fetch_channels
|
29
35
|
uri = URI('http://www.douban.com/j/app/radio/channels')
|
30
|
-
res = Net::HTTP.get(
|
36
|
+
res = Net::HTTP.get(uri)
|
31
37
|
@channels = JSON.parse(res)
|
38
|
+
|
39
|
+
@logger.log("raw channel list #{channels}")
|
32
40
|
end
|
33
41
|
|
34
42
|
def select_channel(channel_num)
|
35
43
|
@current_channel = channel_num
|
36
44
|
end
|
37
45
|
|
38
|
-
def
|
46
|
+
def fetch_next_playlist
|
39
47
|
uri = URI('http://www.douban.com/j/app/radio/people')
|
40
48
|
params = {
|
41
49
|
:app_name => 'radio_desktop_mac',
|
@@ -53,12 +61,14 @@ module DoubanFM
|
|
53
61
|
|
54
62
|
@current_playlist = JSON.parse(res.body)
|
55
63
|
|
64
|
+
@logger.log("raw playlist #{current_playlist}")
|
65
|
+
|
56
66
|
unless @current_playlist['err'].nil?
|
57
67
|
raise @current_playlist
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
61
|
-
def
|
71
|
+
def play
|
62
72
|
@continue = true
|
63
73
|
|
64
74
|
Thread.new do
|
@@ -72,10 +82,12 @@ module DoubanFM
|
|
72
82
|
break
|
73
83
|
end
|
74
84
|
|
75
|
-
@player_pid = spawn("mpg123 #{song['url']}")
|
85
|
+
@player_pid = spawn("mpg123 #{song['url']} > /dev/null 2>&1")
|
76
86
|
|
77
87
|
@semaphore.unlock
|
78
88
|
|
89
|
+
@logger.log("playing song \"#{song['title']}\"")
|
90
|
+
|
79
91
|
Process.wait
|
80
92
|
end
|
81
93
|
|
@@ -84,12 +96,58 @@ module DoubanFM
|
|
84
96
|
end
|
85
97
|
end
|
86
98
|
|
99
|
+
def add_to_mpd(host = 'localhost', port = 6600)
|
100
|
+
mpd = MPD.new(host, port)
|
101
|
+
mpd.connect
|
102
|
+
|
103
|
+
begin
|
104
|
+
songs = mpd.send_command(:listplaylistinfo, DOUBAN_FM_MPD_PLAYLIST)
|
105
|
+
if songs.is_a? String
|
106
|
+
total = 1
|
107
|
+
else
|
108
|
+
total = songs.length
|
109
|
+
end
|
110
|
+
rescue
|
111
|
+
total = 0
|
112
|
+
end
|
113
|
+
|
114
|
+
@logger.log("current total number of songs in mpd #{total}")
|
115
|
+
|
116
|
+
if total < MIN_SONGS_IN_DOUBAN_FM_MPD_PLAYLIST
|
117
|
+
douban_fm_playlist = MPD::Playlist.new(mpd, {:playlist => DOUBAN_FM_MPD_PLAYLIST})
|
118
|
+
|
119
|
+
begin
|
120
|
+
@logger.log('fetch next playlist')
|
121
|
+
|
122
|
+
fetch_next_playlist
|
123
|
+
rescue
|
124
|
+
@logger.log('session expired, relogin')
|
125
|
+
|
126
|
+
login
|
127
|
+
|
128
|
+
@logger.log('fetch next playlist')
|
129
|
+
|
130
|
+
fetch_next_playlist
|
131
|
+
end
|
132
|
+
|
133
|
+
@logger.log("add more songs to mpd")
|
134
|
+
|
135
|
+
@current_playlist['song'].each do |song|
|
136
|
+
@logger.log("send [#{song['url'].gsub('\\', '')}] to mpd")
|
137
|
+
|
138
|
+
douban_fm_playlist.add(song['url'].gsub('\\', ''))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
mpd.disconnect
|
143
|
+
end
|
144
|
+
|
87
145
|
def stop
|
88
146
|
@semaphore.synchronize do
|
89
147
|
@continue = false
|
90
148
|
|
91
149
|
begin
|
92
|
-
Process.kill(9, @player_pid)
|
150
|
+
Process.kill(9, @player_pid) unless @player_pid.nil?
|
93
151
|
rescue Errno::ESRCH
|
94
152
|
end
|
95
153
|
end
|
data/lib/douban.fm/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: douban.fm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,29 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
date: 2013-01-17 00:00:00.000000000 Z
|
13
|
-
dependencies:
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ruby-mpd
|
16
|
+
requirement: &70182454448200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70182454448200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: highline
|
27
|
+
requirement: &70182454447640 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70182454447640
|
14
36
|
description: douban.fm
|
15
37
|
email:
|
16
38
|
- hxliang1982@gmail.com
|
@@ -28,6 +50,7 @@ files:
|
|
28
50
|
- douban.fm.gemspec
|
29
51
|
- lib/douban.fm.rb
|
30
52
|
- lib/douban.fm/douban_fm.rb
|
53
|
+
- lib/douban.fm/logger.rb
|
31
54
|
- lib/douban.fm/version.rb
|
32
55
|
homepage: https://github.com/honnix/douban.fm
|
33
56
|
licenses: []
|
@@ -49,8 +72,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
72
|
version: '0'
|
50
73
|
requirements: []
|
51
74
|
rubyforge_project:
|
52
|
-
rubygems_version: 1.8.
|
75
|
+
rubygems_version: 1.8.15
|
53
76
|
signing_key:
|
54
77
|
specification_version: 3
|
55
78
|
summary: douban.fm
|
56
79
|
test_files: []
|
80
|
+
has_rdoc:
|