niconico 1.3.0 → 1.4.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 +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:
|