niconico 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/niconico.rb +53 -11
- data/lib/niconico/channel.rb +1 -1
- data/lib/niconico/deferrable.rb +40 -0
- data/lib/niconico/live.rb +204 -0
- data/lib/niconico/live/api.rb +212 -0
- data/lib/niconico/version.rb +1 -1
- data/lib/niconico/video.rb +9 -14
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df82c4e26df5f6e3fd88b09775a014a65600fb3b
|
4
|
+
data.tar.gz: dccb4490003953bcbc1bac19b533f775e506cdde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c56db982b4c804be9d889360b752396bb321bd2e88c874e709dc5d9bb2853348b330de2421d4aca67888e1fa19688953be8353945ed1ce2b890984c0a7f422d8
|
7
|
+
data.tar.gz: 5bea438a9fd9264d97328918663b1287bc5164d320ca2a0237bfc448a6c6915861cf875d4e4f6cbc81dd83d9eca5dbf970ff08f251cc8aa688f77d5542a06de4
|
data/lib/niconico.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
3
|
require 'mechanize'
|
5
4
|
require 'cgi'
|
6
5
|
require 'niconico/version'
|
7
6
|
|
8
7
|
class Niconico
|
9
8
|
URL = {
|
9
|
+
top: 'http://www.nicovideo.jp/',
|
10
10
|
login: 'https://secure.nicovideo.jp/secure/login?site=niconico',
|
11
11
|
watch: 'http://www.nicovideo.jp/watch/',
|
12
12
|
getflv: 'http://flapi.nicovideo.jp/api/getflv'
|
@@ -16,34 +16,76 @@ class Niconico
|
|
16
16
|
|
17
17
|
attr_reader :agent, :logined
|
18
18
|
|
19
|
-
def initialize(
|
20
|
-
|
21
|
-
|
19
|
+
def initialize(*args)
|
20
|
+
case args.size
|
21
|
+
when 2
|
22
|
+
@mail, @pass = args
|
23
|
+
when 1
|
24
|
+
@token = args.first
|
25
|
+
else
|
26
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)"
|
27
|
+
end
|
22
28
|
|
23
29
|
@logined = false
|
24
30
|
|
25
31
|
@agent = Mechanize.new.tap do |agent|
|
26
32
|
agent.user_agent = "Niconico.gem (#{Niconico::VERSION}, https://github.com/sorah/niconico)"
|
27
33
|
agent.ssl_version = 'SSLv3'
|
34
|
+
|
35
|
+
agent.cookie_jar.add(
|
36
|
+
HTTP::Cookie.new(
|
37
|
+
domain: '.nicovideo.jp', path: '/',
|
38
|
+
name: 'lang', value: 'ja-jp',
|
39
|
+
)
|
40
|
+
)
|
28
41
|
end
|
29
42
|
end
|
30
43
|
|
31
44
|
def login(force=false)
|
32
45
|
return false if !force && @logined
|
33
46
|
|
47
|
+
if @token
|
48
|
+
login_with_token
|
49
|
+
elsif @mail && @pass
|
50
|
+
login_with_email
|
51
|
+
else
|
52
|
+
raise 'huh? (may be bug)'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#<Niconico: #{@mail || '(token)'}, #{@logined ? "" : "not "}logined>"
|
58
|
+
end
|
59
|
+
|
60
|
+
class LoginError < StandardError; end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def login_with_email
|
34
65
|
page = @agent.post(URL[:login], 'mail' => @mail, 'password' => @pass)
|
35
66
|
|
36
67
|
raise LoginError, "Failed to login (x-niconico-authflag is 0)" if page.header["x-niconico-authflag"] == '0'
|
37
68
|
@logined = true
|
38
69
|
end
|
39
70
|
|
40
|
-
def
|
41
|
-
|
71
|
+
def login_with_token
|
72
|
+
@agent.cookie_jar.add(
|
73
|
+
HTTP::Cookie.new(
|
74
|
+
domain: '.nicovideo.jp', path: '/',
|
75
|
+
name: 'user_session', value: @token
|
76
|
+
)
|
77
|
+
)
|
78
|
+
|
79
|
+
page = @agent.get(URL[:top])
|
80
|
+
raise LoginError, "Failed to login (x-niconico-authflag is 0)" if page.header["x-niconico-authflag"] == '0'
|
81
|
+
|
82
|
+
@logined = true
|
42
83
|
end
|
43
|
-
|
84
|
+
|
44
85
|
end
|
45
86
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
87
|
+
require 'niconico/video'
|
88
|
+
require 'niconico/mylist'
|
89
|
+
require 'niconico/ranking'
|
90
|
+
require 'niconico/channel'
|
91
|
+
require 'niconico/live'
|
data/lib/niconico/channel.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Niconico
|
2
|
+
module Deferrable
|
3
|
+
module ClassMethods
|
4
|
+
def deferrable(*keys)
|
5
|
+
keys.each do |key|
|
6
|
+
binding.eval(<<-EOM, __FILE__, __LINE__.succ)
|
7
|
+
define_method(:#{key}) do
|
8
|
+
get() unless fetched?
|
9
|
+
@#{key}
|
10
|
+
end
|
11
|
+
EOM
|
12
|
+
end
|
13
|
+
self.deferred_methods.push *keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def deferred_methods
|
17
|
+
@deferred_methods ||= []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.included(klass)
|
22
|
+
klass.extend ClassMethods
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetched?; @fetched; end
|
26
|
+
|
27
|
+
def get
|
28
|
+
@fetched = true
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def preload_deffered_values(vars={})
|
34
|
+
vars.each do |k,v|
|
35
|
+
next unless self.class.deferred_methods.include?(k)
|
36
|
+
instance_variable_set "@#{k}", v
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'niconico/live/api'
|
3
|
+
|
4
|
+
class Niconico
|
5
|
+
def live(live_id)
|
6
|
+
Live.new(self, live_id)
|
7
|
+
end
|
8
|
+
|
9
|
+
class Live
|
10
|
+
class ReservationOutdated < Exception; end
|
11
|
+
class ReservationNotAccepted < Exception; end
|
12
|
+
class TicketRetrievingFailed < Exception; end
|
13
|
+
class AcceptingReservationFailed < Exception; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def public_key
|
17
|
+
@public_key ||= begin
|
18
|
+
if ENV["NICONICO_LIVE_PUBLIC_KEY"]
|
19
|
+
File.read(File.expand_path(ENV["NICONICO_LIVE_PUBLIC_KEY"]))
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def public_key=(other)
|
27
|
+
@public_key = other
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(parent, live_id)
|
32
|
+
@parent = parent
|
33
|
+
@agent = parent.agent
|
34
|
+
@id = @live_id = live_id
|
35
|
+
@client = Niconico::Live::API.new(@agent)
|
36
|
+
|
37
|
+
get()
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :id, :live, :ticket
|
41
|
+
attr_writer :public_key
|
42
|
+
|
43
|
+
def public_key
|
44
|
+
@public_key || self.class.public_key
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetched?
|
48
|
+
!!@fetched
|
49
|
+
end
|
50
|
+
|
51
|
+
def get(force=false)
|
52
|
+
return self if @fetched && !force
|
53
|
+
@live = @client.get(@live_id)
|
54
|
+
@fetched = true
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def seat(force=false)
|
59
|
+
return @seat if @seat && !force
|
60
|
+
raise ReservationNotAccepted if reserved? && !reservation_accepted?
|
61
|
+
|
62
|
+
@seat = @client.get_player_status(self.id, self.public_key)
|
63
|
+
|
64
|
+
raise TicketRetrievingFailed, @seat[:error] if @seat[:error]
|
65
|
+
|
66
|
+
@seat
|
67
|
+
end
|
68
|
+
|
69
|
+
def accept_reservation
|
70
|
+
return self if reservation_accepted?
|
71
|
+
raise ReservationOutdated if reservation_outdated?
|
72
|
+
|
73
|
+
result = @client.accept_watching_reservation(self.id)
|
74
|
+
raise AcceptingReservationFailed unless result
|
75
|
+
|
76
|
+
get(:reload)
|
77
|
+
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
"#<Niconico::Live: #{id}, #{title}>"
|
83
|
+
end
|
84
|
+
|
85
|
+
def title
|
86
|
+
get.live[:title]
|
87
|
+
end
|
88
|
+
|
89
|
+
def description
|
90
|
+
get.live[:description]
|
91
|
+
end
|
92
|
+
|
93
|
+
def opens_at
|
94
|
+
get.live[:opens_at]
|
95
|
+
end
|
96
|
+
|
97
|
+
def starts_at
|
98
|
+
get.live[:starts_at]
|
99
|
+
end
|
100
|
+
|
101
|
+
def status
|
102
|
+
get.live[:status]
|
103
|
+
end
|
104
|
+
|
105
|
+
def scheduled?
|
106
|
+
status == :scheduled
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_air?
|
110
|
+
status == :on_air
|
111
|
+
end
|
112
|
+
|
113
|
+
def closed?
|
114
|
+
status == :closed
|
115
|
+
end
|
116
|
+
|
117
|
+
def reserved?
|
118
|
+
get.live.key? :reservation
|
119
|
+
end
|
120
|
+
|
121
|
+
def reservation_unaccepted?
|
122
|
+
reserved? && live[:reservation][:status] == :reserved
|
123
|
+
end
|
124
|
+
|
125
|
+
def reservation_accepted?
|
126
|
+
reserved? && live[:reservation][:status] == :accepted
|
127
|
+
end
|
128
|
+
|
129
|
+
def reservation_outdated?
|
130
|
+
reserved? && live[:reservation][:status] == :outdated
|
131
|
+
end
|
132
|
+
|
133
|
+
def reservation_available?
|
134
|
+
reservation_unaccepted? || reservation_accepted?
|
135
|
+
end
|
136
|
+
|
137
|
+
def reservation_expires_at
|
138
|
+
reserved? ? live[:reservation][:expires_at] : nil
|
139
|
+
end
|
140
|
+
|
141
|
+
def channel
|
142
|
+
get.live[:channel]
|
143
|
+
end
|
144
|
+
|
145
|
+
def premium?
|
146
|
+
!!seat[:premium?]
|
147
|
+
end
|
148
|
+
|
149
|
+
def rtmp_url
|
150
|
+
seat[:rtmp][:url]
|
151
|
+
end
|
152
|
+
|
153
|
+
def ticket
|
154
|
+
seat[:rtmp][:ticket]
|
155
|
+
end
|
156
|
+
|
157
|
+
def quesheet
|
158
|
+
seat[:quesheet]
|
159
|
+
end
|
160
|
+
|
161
|
+
def rtmpdump_commands(file_base)
|
162
|
+
file_base = File.expand_path(file_base)
|
163
|
+
|
164
|
+
publishes = quesheet.select{ |_| /^\/publish / =~ _[:body] }.map do |publish|
|
165
|
+
publish[:body].split(/ /).tap(&:shift)
|
166
|
+
end
|
167
|
+
|
168
|
+
plays = quesheet.select{ |_| /^\/play / =~ _[:body] }
|
169
|
+
|
170
|
+
plays.map.with_index do |play, i|
|
171
|
+
cases = play[:body].sub(/^case:/,'').split(/ /)[1].split(/,/)
|
172
|
+
publish_id = nil
|
173
|
+
|
174
|
+
publish_id = cases.find { |_| _.start_with?('premium:') } if premium?
|
175
|
+
publish_id ||= cases.find { |_| _.start_with?('default:') }
|
176
|
+
publish_id ||= cases[0]
|
177
|
+
|
178
|
+
publish_id = publish_id.split(/:/).last
|
179
|
+
|
180
|
+
contents = publishes.select{ |_| _[0] == publish_id }
|
181
|
+
|
182
|
+
contents.map.with_index do |content, j|
|
183
|
+
content = content[1]
|
184
|
+
rtmp = "#{self.rtmp_url}/mp4:#{content}"
|
185
|
+
|
186
|
+
seq = 0
|
187
|
+
begin
|
188
|
+
file = "#{file_base}.#{i}.#{j}.#{seq}.flv"
|
189
|
+
seq += 1
|
190
|
+
end while File.exist?(file)
|
191
|
+
|
192
|
+
['rtmpdump',
|
193
|
+
'-V',
|
194
|
+
'-o', file,
|
195
|
+
'-r', rtmp,
|
196
|
+
'-C', "S:#{ticket}",
|
197
|
+
'--playpath', "mp4:#{content}",
|
198
|
+
'--app', URI.parse(self.rtmp_url).path.sub(/^\//,'')
|
199
|
+
]
|
200
|
+
end
|
201
|
+
end.flatten(1)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'time'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
class Niconico
|
6
|
+
class Live
|
7
|
+
class API
|
8
|
+
class NoPublicKeyProvided < Exception; end
|
9
|
+
|
10
|
+
URL_GETPLAYERSTATUS = 'http://ow.live.nicovideo.jp/api/getplayerstatus'.freeze
|
11
|
+
URL_WATCHINGRESERVATION_LIST = 'http://live.nicovideo.jp/api/watchingreservation?mode=list'
|
12
|
+
|
13
|
+
def initialize(agent)
|
14
|
+
@agent = agent
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :agent
|
18
|
+
|
19
|
+
def get(id)
|
20
|
+
id = normalize_id(id)
|
21
|
+
|
22
|
+
page = agent.get("http://live.nicovideo.jp/gate/#{id}")
|
23
|
+
|
24
|
+
comment_area = page.at("#comment_area#{id}").inner_text
|
25
|
+
|
26
|
+
result = {
|
27
|
+
title: page.at('h2 span').inner_text,
|
28
|
+
id: id,
|
29
|
+
description: page.at('.stream_description .text_area').inner_html,
|
30
|
+
}
|
31
|
+
|
32
|
+
kaijo = page.search('.kaijo strong').map(&:inner_text)
|
33
|
+
result[:opens_at] = Time.parse("#{kaijo[0]} #{kaijo[1]} +0900")
|
34
|
+
result[:starts_at] = Time.parse("#{kaijo[0]} #{kaijo[2]} +0900")
|
35
|
+
|
36
|
+
result[:status] = :scheduled if comment_area.include?('開場まで、あと')
|
37
|
+
result[:status] = :on_air if comment_area.include?('現在放送中')
|
38
|
+
close_message = comment_area.match(/この番組は(.+?)に終了いたしました。/)
|
39
|
+
if close_message
|
40
|
+
result[:status] = :closed
|
41
|
+
result[:closed_at] = Time.parse("#{close_message[1]} +0900")
|
42
|
+
end
|
43
|
+
|
44
|
+
reservation_valid_until_message = comment_area.match(/使用期限: (.+?)まで/)
|
45
|
+
if reservation_valid_until_message
|
46
|
+
result[:reservation] = {}
|
47
|
+
result[:reservation][:expires_at] = Time.parse("#{reservation_valid_until_message[1]} +0900")
|
48
|
+
|
49
|
+
if comment_area.include?('視聴権を使用し、タイムシフト視聴を行いますか?')
|
50
|
+
result[:reservation][:status] = :reserved
|
51
|
+
result[:reservation][:available] = true
|
52
|
+
elsif comment_area.include?('本番組は、タイムシフト視聴を行う事が可能です。')
|
53
|
+
result[:reservation][:status] = :accepted
|
54
|
+
result[:reservation][:available] = true
|
55
|
+
elsif comment_area.include?('タイムシフト視聴をこれ以上行う事は出来ません。') || comment_area.include?('視聴権の利用期限が過ぎています。')
|
56
|
+
result[:reservation][:status] = :outdated
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
channel = page.at('div.chan')
|
61
|
+
if channel
|
62
|
+
result[:channel] = {
|
63
|
+
name: channel.at('.shosai a').inner_text,
|
64
|
+
id: channel.at('.shosai a')['href'].split('/').last,
|
65
|
+
link: channel.at('.shosai a')['href'],
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
def heartbeat
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_player_status(id, public_key = nil)
|
77
|
+
id = normalize_id(id)
|
78
|
+
page = agent.get("http://ow.live.nicovideo.jp/api/getplayerstatus?locale=GLOBAL&lang=ja%2Djp&v=#{id}&seat%5Flocale=JP")
|
79
|
+
if page.body[0] == 'c' # encrypted
|
80
|
+
page = Nokogiri::XML(decrypt_encrypted_player_status(page.body, public_key))
|
81
|
+
end
|
82
|
+
|
83
|
+
status = page.at('getplayerstatus')
|
84
|
+
|
85
|
+
if status['status'] == 'fail'
|
86
|
+
error = page.at('error code').inner_text
|
87
|
+
|
88
|
+
case error
|
89
|
+
when 'notlogin'
|
90
|
+
return {error: :not_logged_in}
|
91
|
+
when 'comingsoon'
|
92
|
+
return {error: :not_yet_started}
|
93
|
+
when 'closed'
|
94
|
+
return {error: :closed}
|
95
|
+
when 'require_accept_print_timeshift_ticket'
|
96
|
+
return {error: :reservation_not_accepted}
|
97
|
+
when 'timeshift_ticket_expire'
|
98
|
+
return {error: :reservation_expired}
|
99
|
+
when 'noauth'
|
100
|
+
return {error: :archive_closed}
|
101
|
+
when 'notfound'
|
102
|
+
return {error: :not_found}
|
103
|
+
else
|
104
|
+
return {error: error}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
result = {}
|
109
|
+
|
110
|
+
# Strings
|
111
|
+
%w(id title description provider_type owner_name
|
112
|
+
bourbon_url full_video kickout_video).each do |key|
|
113
|
+
item = status.at(key)
|
114
|
+
result[key.to_sym] = item.inner_text if item
|
115
|
+
end
|
116
|
+
|
117
|
+
# Integers
|
118
|
+
%w(watch_count comment_count owner_id watch_count comment_count).each do |key|
|
119
|
+
item = status.at(key)
|
120
|
+
result[key.to_sym] = item.inner_text.to_i if item
|
121
|
+
end
|
122
|
+
|
123
|
+
# Flags
|
124
|
+
%w(is_premium is_reserved is_owner international is_rerun_stream is_archiveplayserver
|
125
|
+
archive allow_netduetto
|
126
|
+
is_nonarchive_timeshift_enabled is_timeshift_reserved).each do |key|
|
127
|
+
item = status.at(key)
|
128
|
+
result[key.sub(/^is_/,'').concat('?').to_sym] = item.inner_text == '1' if item
|
129
|
+
end
|
130
|
+
|
131
|
+
# Datetimes
|
132
|
+
%w(base_time open_time start_time end_time).each do |key|
|
133
|
+
item = status.at(key)
|
134
|
+
result[key.to_sym] = Time.at(item.inner_text.to_i) if item
|
135
|
+
end
|
136
|
+
|
137
|
+
rtmp = status.at('rtmp')
|
138
|
+
result[:rtmp] = {
|
139
|
+
url: rtmp.at('url').inner_text,
|
140
|
+
ticket: rtmp.at('ticket').inner_text,
|
141
|
+
}
|
142
|
+
|
143
|
+
ms = status.at('ms')
|
144
|
+
result[:ms] = {
|
145
|
+
address: ms.at('addr').inner_text,
|
146
|
+
port: ms.at('port').inner_text.to_i,
|
147
|
+
thread: ms.at('thread').inner_text,
|
148
|
+
}
|
149
|
+
|
150
|
+
quesheet = status.search('quesheet que')
|
151
|
+
result[:quesheet] = quesheet.map do |que|
|
152
|
+
{vpos: que['vpos'].to_i, mail: que['mail'], name: que['name'], body: que.inner_text}
|
153
|
+
end
|
154
|
+
|
155
|
+
result
|
156
|
+
end
|
157
|
+
|
158
|
+
def watching_reservations
|
159
|
+
page = agent.get(URL_WATCHINGRESERVATION_LIST)
|
160
|
+
page.search('vid').map(&:inner_text).map{ |_| normalize_id(_) }
|
161
|
+
end
|
162
|
+
|
163
|
+
def accept_watching_reservation(id_)
|
164
|
+
id = normalize_id(id_, with_lv: false)
|
165
|
+
page = agent.get("http://live.nicovideo.jp/api/watchingreservation?mode=confirm_watch_my&vid=#{id}&next_url&analytic")
|
166
|
+
token = page.at('#reserve img')['onclick'].scan(/'(.+?)'/)[1][0]
|
167
|
+
|
168
|
+
page = agent.post("http://live.nicovideo.jp/api/watchingreservation",
|
169
|
+
accept: 'true', mode: 'use', vid: id, token: token)
|
170
|
+
|
171
|
+
page.at('nicolive_video_response')['status'] == 'ok'
|
172
|
+
end
|
173
|
+
|
174
|
+
def decrypt_encrypted_player_status(body, public_key)
|
175
|
+
unless public_key
|
176
|
+
raise NoPublicKeyProvided,
|
177
|
+
'You should provide proper public key to decrypt ' \
|
178
|
+
'encrypted player status'
|
179
|
+
end
|
180
|
+
|
181
|
+
lines = body.lines
|
182
|
+
pubkey = OpenSSL::PKey::RSA.new(public_key)
|
183
|
+
|
184
|
+
encrypted_shared_key = lines[1].unpack('m*')[0]
|
185
|
+
shared_key_raw = pubkey.public_decrypt(encrypted_shared_key)
|
186
|
+
shared_key = shared_key_raw.unpack('L>*')[0].to_s
|
187
|
+
|
188
|
+
cipher = OpenSSL::Cipher.new('bf-ecb').decrypt
|
189
|
+
cipher.padding = 0
|
190
|
+
cipher.key_len = shared_key.size
|
191
|
+
cipher.key = shared_key
|
192
|
+
|
193
|
+
encrypted_body = lines[2].unpack('m*')[0]
|
194
|
+
|
195
|
+
body = cipher.update(encrypted_body) + cipher.final
|
196
|
+
body.force_encoding('utf-8')
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def normalize_id(id, with_lv: true)
|
202
|
+
id = id.to_s
|
203
|
+
|
204
|
+
if with_lv
|
205
|
+
id.start_with?('lv') ? id : "lv#{id}"
|
206
|
+
else
|
207
|
+
id.start_with?('lv') ? id[2..-1] : id
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
data/lib/niconico/version.rb
CHANGED
data/lib/niconico/video.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
require 'json'
|
3
|
+
require 'niconico/deferrable'
|
3
4
|
|
4
5
|
class Niconico
|
5
6
|
def video(video_id)
|
@@ -8,35 +9,29 @@ class Niconico
|
|
8
9
|
end
|
9
10
|
|
10
11
|
class Video
|
11
|
-
|
12
|
-
DEFERRABLES_VAR = DEFERRABLES.map{|k| :"@#{k}" }
|
12
|
+
include Niconico::Deferrable
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
14
|
+
deferrable :id, :title,
|
15
|
+
:description, :description_raw,
|
16
|
+
:url, :video_url, :type,
|
17
|
+
:tags, :mylist_comment
|
19
18
|
|
20
19
|
def initialize(parent, video_id, defer=nil)
|
21
20
|
@parent = parent
|
22
21
|
@agent = parent.agent
|
23
22
|
@fetched = false
|
24
23
|
@thread_id = @id = video_id
|
24
|
+
@page = nil
|
25
25
|
@url = "#{Niconico::URL[:watch]}#{@id}"
|
26
26
|
|
27
27
|
if defer
|
28
|
-
defer
|
29
|
-
next unless DEFERRABLES.include?(k)
|
30
|
-
instance_variable_set :"@#{k}", v
|
31
|
-
end
|
32
|
-
@page = nil
|
28
|
+
preload_deffered_values(defer)
|
33
29
|
else
|
34
|
-
|
30
|
+
get()
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
38
34
|
def economy?; @eco; end
|
39
|
-
def fetched?; @fetched; end
|
40
35
|
|
41
36
|
def get(options = {})
|
42
37
|
begin
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: niconico
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shota Fukumori (sora_h)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mechanize
|
@@ -52,6 +52,9 @@ files:
|
|
52
52
|
- Rakefile
|
53
53
|
- lib/niconico.rb
|
54
54
|
- lib/niconico/channel.rb
|
55
|
+
- lib/niconico/deferrable.rb
|
56
|
+
- lib/niconico/live.rb
|
57
|
+
- lib/niconico/live/api.rb
|
55
58
|
- lib/niconico/mylist.rb
|
56
59
|
- lib/niconico/ranking.rb
|
57
60
|
- lib/niconico/version.rb
|
@@ -76,9 +79,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
79
|
version: '0'
|
77
80
|
requirements: []
|
78
81
|
rubyforge_project:
|
79
|
-
rubygems_version: 2.2.
|
82
|
+
rubygems_version: 2.2.2
|
80
83
|
signing_key:
|
81
84
|
specification_version: 4
|
82
85
|
summary: wrapper of Mechanize, optimized for nicovideo.
|
83
86
|
test_files: []
|
84
|
-
has_rdoc:
|