nicorepo 0.0.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +58 -28
- data/bin/nicorepo +3 -1
- data/lib/nicorepo.rb +5 -51
- data/lib/nicorepo/{cli/cli.rb → cli.rb} +36 -44
- data/lib/nicorepo/cli/{config.rb → configuration.rb} +3 -3
- data/lib/nicorepo/client.rb +64 -0
- data/lib/nicorepo/filter.rb +66 -0
- data/lib/nicorepo/page.rb +60 -0
- data/lib/nicorepo/report.rb +101 -10
- data/lib/nicorepo/request.rb +81 -0
- data/lib/nicorepo/version.rb +2 -2
- data/nicorepo.gemspec +6 -5
- metadata +43 -27
- data/lib/nicorepo/parser.rb +0 -87
- data/lib/nicorepo/reports.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bae618f5d97184adc26e5376e237e195903e70f0
|
4
|
+
data.tar.gz: 82a97cc15b7d6b3c2931d27108c22f9d0d9a5d08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e92bf7c4e0f7916158d5cb8fc6d21ba0dc9ea3ce1d48a0a64775563eb1d0e5391cff14148d80c3c9d8606f0f9fe0ca5419d295290e79697ed93d07f7c6e2223b
|
7
|
+
data.tar.gz: a6343d1bb79878a5fe79b025db64b897fbb00e1f79dac1f5fe9f41319ce14e4f738dfd1cbbb3fd02b531ba69a3beb70bacf91b6dafdd8bf2f3b35bd706a66e02
|
data/README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# Nicorepo
|
2
2
|
|
3
|
-
Nicorepo is
|
3
|
+
Nicorepo is an (unofficial) API client for Nicorepo on nicovideo.jp
|
4
4
|
|
5
|
-
-
|
6
|
-
-
|
7
|
-
-
|
5
|
+
- Fetch nicorepo logs
|
6
|
+
- Filter them by topics and a period
|
7
|
+
- Provides a built-in CLI and commands
|
8
8
|
|
9
|
-
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
- ruby 2.3.0
|
10
12
|
|
11
13
|
## Installation
|
12
14
|
|
@@ -24,11 +26,27 @@ Or install it yourself as:
|
|
24
26
|
|
25
27
|
## Usage
|
26
28
|
|
27
|
-
|
29
|
+
```rb
|
30
|
+
client = Nicorepo::Client.new
|
31
|
+
|
32
|
+
client.all
|
33
|
+
# => Returns raw logs wrapped in Nicorepo::Report
|
34
|
+
|
35
|
+
client.lives(10)
|
36
|
+
# => Returns 10 live logs
|
37
|
+
|
38
|
+
client.videos(10, from: Time.now - (3600 * 24), to: Time.now - (3600 * 48))
|
39
|
+
# => Returns 10 uploaded video logs (at most) within yesterday
|
28
40
|
|
29
|
-
|
41
|
+
client.all.format
|
42
|
+
# => Returns an array of logs formatted by Nicorepo::Report::DefaultFormatter
|
43
|
+
```
|
44
|
+
|
45
|
+
### Authentication for nicovideo.jp
|
30
46
|
|
31
|
-
|
47
|
+
Nicorepo requires your nicovideo account and reads them from the `~/.netrc` file.
|
48
|
+
|
49
|
+
Add following lines to your `~/.netrc`.
|
32
50
|
|
33
51
|
```
|
34
52
|
machine nicovideo.jp
|
@@ -42,48 +60,60 @@ And set the permission if not yet.
|
|
42
60
|
$ chmod 0600 ~/.netrc
|
43
61
|
```
|
44
62
|
|
45
|
-
### Start
|
63
|
+
### Start Nicorepo CLI
|
46
64
|
|
47
|
-
|
65
|
+
```console
|
66
|
+
$ nicorepo
|
67
|
+
```
|
48
68
|
|
49
|
-
You can use following commands in interactive cli
|
69
|
+
You can use following commands in interactive cli.
|
50
70
|
|
51
71
|
command | alias | description
|
52
72
|
----------------------|-------|---------------------------------------------------------
|
53
|
-
all | a | fetch all
|
54
|
-
videos | v | fetch only video
|
55
|
-
lives | li | fetch only live
|
56
|
-
show | s | show current
|
73
|
+
all | a | fetch all logs
|
74
|
+
videos | v | fetch only video logs
|
75
|
+
lives | li | fetch only live logs
|
76
|
+
show | s | show current logs
|
57
77
|
open REPORT-NUMBER | o | open the report url specified by number in your browser
|
58
78
|
help [COMMAND] | h | Describe available commands or one specific command
|
59
79
|
login | lo | re-login if your session is expired
|
60
80
|
exit | e | exit interactive prompt
|
61
81
|
|
62
|
-
|
63
|
-
For example, if you want 20 video reports by searcing over 5 pages, the command will be,
|
82
|
+
If you want only video logs, type `video` command.
|
64
83
|
|
65
|
-
> video
|
84
|
+
> video
|
66
85
|
|
67
86
|
Or you can also use aliases.
|
68
87
|
|
69
|
-
> v
|
88
|
+
> v
|
89
|
+
|
90
|
+
Some commands have options of `-n, --request-num=N` and `-p, --limit-page=N`.
|
91
|
+
For example:
|
92
|
+
|
93
|
+
> lives -n20 -p5
|
70
94
|
|
71
|
-
|
72
|
-
|
95
|
+
It fetches 20 live logs at a most, with limitation of max 5 pages.
|
96
|
+
|
97
|
+
If you omit them the default values are used for that. (the defaults are configurable)
|
73
98
|
|
74
99
|
> v
|
75
|
-
# => `video -n10 -
|
100
|
+
# => `video -n10 -p10`
|
76
101
|
> v -n20
|
77
|
-
# => `video -n20 -
|
102
|
+
# => `video -n20 -p10`
|
78
103
|
> v -p5
|
79
|
-
# => `video -n20 -
|
104
|
+
# => `video -n20 -p10`
|
80
105
|
|
81
|
-
And also, you can use `-l, --latest`, `-h, --hours
|
106
|
+
And also, you can use `-l, --latest`, `-h, --hours` and `-d, --days` options to fetch logs in the specific period.
|
82
107
|
|
83
108
|
> all -l
|
84
|
-
> lives -h 1
|
85
109
|
|
86
|
-
|
110
|
+
Collect all logs until reach the last fetched log.
|
111
|
+
|
112
|
+
> lives -h1
|
113
|
+
|
114
|
+
Collect live logs until 1 hour ago.
|
115
|
+
|
116
|
+
#### Configuration for CLI
|
87
117
|
|
88
118
|
You can configure default `request_num` and `limit_page` by adding `~/.nicorepo.yaml` if you want.
|
89
119
|
Please refer the sample `nicorepo/.nicorepo.yaml.sample` or copy it to your home directory.
|
@@ -100,7 +130,7 @@ limit_page:
|
|
100
130
|
```
|
101
131
|
|
102
132
|
- `general`: used in all commands
|
103
|
-
- `all`, `videos`, `lives`: used in each command, has priority than `general`
|
133
|
+
- `all`, `videos`, `lives`: used in each command, has higher priority than `general`
|
104
134
|
|
105
135
|
## Contributing
|
106
136
|
|
data/bin/nicorepo
CHANGED
data/lib/nicorepo.rb
CHANGED
@@ -1,52 +1,6 @@
|
|
1
|
-
require 'mechanize'
|
2
|
-
require 'nicorepo/reports'
|
3
|
-
require 'nicorepo/parser'
|
4
|
-
|
5
|
-
class Nicorepo
|
6
|
-
class LoginError < StandardError; end
|
7
|
-
|
8
|
-
PER_PAGE = 20
|
9
|
-
LOGIN_URL = 'https://secure.nicovideo.jp/secure/login?site=niconico'
|
10
|
-
|
11
|
-
attr_reader :agent
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
@agent = Mechanize.new
|
15
|
-
@agent.ssl_version = 'TLSv1'
|
16
|
-
@agent.request_headers = { 'accept-language' => 'ja-JP', 'content-language' => 'ja-JP' }
|
17
|
-
@parser = Parser.new(@agent)
|
18
|
-
@logined = false
|
19
|
-
end
|
20
|
-
|
21
|
-
def login(mail, pass)
|
22
|
-
page = @agent.post(LOGIN_URL, mail: mail, password: pass)
|
23
|
-
if page.header["x-niconico-authflag"] == '0'
|
24
|
-
raise LoginError, "Failed to login"
|
25
|
-
else
|
26
|
-
@logined = true
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def logined?
|
31
|
-
# TODO: page.header["x-niconico-auth-flag"] をチェックする?
|
32
|
-
# 現状一度ログインしたらfalseにならない
|
33
|
-
@logined
|
34
|
-
end
|
35
|
-
|
36
|
-
def all(request_num = PER_PAGE, since: nil)
|
37
|
-
limit_page = request_num / PER_PAGE + 1
|
38
|
-
Reports.new(@parser).fetch(request_num, limit_page, since: since)
|
39
|
-
end
|
40
|
-
|
41
|
-
def videos(request_num = 3, limit_page = 5, since: nil)
|
42
|
-
VideoReports.new(@parser).fetch(request_num, limit_page, since: since)
|
43
|
-
end
|
44
|
-
|
45
|
-
def lives(request_num = 3, limit_page = 5, since: nil)
|
46
|
-
LiveReports.new(@parser).fetch(request_num, limit_page, since: since)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
require 'nicorepo/cli/cli'
|
51
1
|
require "nicorepo/version"
|
52
|
-
|
2
|
+
require 'nicorepo/client'
|
3
|
+
require 'nicorepo/filter'
|
4
|
+
require 'nicorepo/page'
|
5
|
+
require 'nicorepo/report'
|
6
|
+
require 'nicorepo/request'
|
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'nicorepo'
|
2
|
+
require 'nicorepo/cli/configuration'
|
3
|
+
|
1
4
|
require 'thor'
|
2
|
-
require 'netrc'
|
3
5
|
require 'launchy'
|
4
6
|
require 'readline'
|
5
|
-
require 'nicorepo/cli/config'
|
6
7
|
|
7
|
-
|
8
|
+
module Nicorepo
|
8
9
|
module Cli
|
9
10
|
class Runner
|
10
11
|
def self.run
|
@@ -17,32 +18,14 @@ class Nicorepo
|
|
17
18
|
end
|
18
19
|
|
19
20
|
class Interactor < Thor
|
20
|
-
class LoginAccountError < StandardError; end
|
21
|
-
class ReportExistenceError < StandardError; end
|
22
|
-
|
23
21
|
class << self
|
24
|
-
def start(*)
|
25
|
-
login unless repo.logined?
|
26
|
-
super
|
27
|
-
end
|
28
|
-
|
29
22
|
# replaces by a blank for help because Interactor dosen't require the basename
|
30
23
|
def basename
|
31
24
|
''
|
32
25
|
end
|
33
26
|
|
34
|
-
def login
|
35
|
-
mail, pass = Netrc.read["nicovideo.jp"]
|
36
|
-
raise LoginAccountError, "machine nicovideo.jp is not defined in .netrc" if mail.nil? || pass.nil?
|
37
|
-
begin
|
38
|
-
repo.login(mail, pass)
|
39
|
-
rescue
|
40
|
-
raise LoginAccountError, "invalid mail or pass: mail = #{mail}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
27
|
def repo
|
45
|
-
@repo ||= Nicorepo.new
|
28
|
+
@repo ||= Nicorepo::Client.new
|
46
29
|
end
|
47
30
|
|
48
31
|
def conf
|
@@ -54,9 +37,9 @@ class Nicorepo
|
|
54
37
|
end
|
55
38
|
end
|
56
39
|
|
57
|
-
desc "
|
58
|
-
def
|
59
|
-
|
40
|
+
desc "reset_session", "reset current session and try re-login at the next access. It is useful if your session is expired"
|
41
|
+
def reset_session
|
42
|
+
@repo.reset_session
|
60
43
|
end
|
61
44
|
|
62
45
|
desc "all", "fetch all reports"
|
@@ -66,7 +49,7 @@ class Nicorepo
|
|
66
49
|
option :"hours", type: :numeric, aliases: :h
|
67
50
|
def all
|
68
51
|
request_num = options[:"request-num"] || conf.request_num("all")
|
69
|
-
request_options = {
|
52
|
+
request_options = { to: parse_time_options(options) }
|
70
53
|
cache(repo.all(request_num, request_options))
|
71
54
|
show
|
72
55
|
end
|
@@ -80,8 +63,8 @@ class Nicorepo
|
|
80
63
|
def videos
|
81
64
|
request_num = options[:"request-num"] || conf.request_num("videos")
|
82
65
|
limit_page = options[:"limit-page"] || conf.limit_page("videos")
|
83
|
-
request_options = {
|
84
|
-
cache(repo.videos(request_num,
|
66
|
+
request_options = { limit_page: limit_page, to: parse_time_options(options) }
|
67
|
+
cache(repo.videos(request_num, request_options))
|
85
68
|
show
|
86
69
|
end
|
87
70
|
|
@@ -94,24 +77,29 @@ class Nicorepo
|
|
94
77
|
def lives
|
95
78
|
request_num = options[:"request-num"] || conf.request_num("lives")
|
96
79
|
limit_page = options[:"limit-page"] || conf.limit_page("lives")
|
97
|
-
request_options = {
|
98
|
-
cache(repo.lives(request_num,
|
80
|
+
request_options = { limit_page: limit_page, to: parse_time_options(options) }
|
81
|
+
cache(repo.lives(request_num, request_options))
|
99
82
|
show
|
100
83
|
end
|
101
84
|
|
102
85
|
desc "show", "show current reports"
|
103
86
|
option :more, type: :boolean, aliases: :m
|
104
87
|
def show
|
105
|
-
|
106
|
-
|
88
|
+
if cached_reports.size == 0
|
89
|
+
say "* No reports", :red
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
reports = options[:more] ? cached_reports : cached_reports[0, recent_tail]
|
94
|
+
reports.each.with_index(1) do |report, i|
|
107
95
|
say "--- MORE ---", :blue if i == recent_tail + 1
|
108
|
-
say "[#{i}] #{report
|
109
|
-
say "
|
96
|
+
say "[#{i}] #{report[:topic]} from: #{report[:sender]} at: #{report[:created_at]}"
|
97
|
+
say " #{report[:title]} (#{report[:url]})", :green
|
110
98
|
end
|
111
99
|
say "* last fetch time: #{cached_at}", :blue
|
112
100
|
end
|
113
101
|
|
114
|
-
desc "open REPORT-NUMBER", "open the
|
102
|
+
desc "open REPORT-NUMBER", "open the url in your browser. REPORT-NUMBER is shown in left of each report"
|
115
103
|
def open(report_number)
|
116
104
|
open_numbered_link(report_number.to_i)
|
117
105
|
end
|
@@ -132,7 +120,7 @@ class Nicorepo
|
|
132
120
|
end
|
133
121
|
|
134
122
|
def cached_reports
|
135
|
-
self.class.cache[:
|
123
|
+
self.class.cache[:report] ||= []
|
136
124
|
end
|
137
125
|
|
138
126
|
def recent_tail
|
@@ -143,13 +131,13 @@ class Nicorepo
|
|
143
131
|
self.class.cache[:cached_at]
|
144
132
|
end
|
145
133
|
|
146
|
-
def cache(
|
147
|
-
cached_reports.unshift(
|
148
|
-
self.class.cache[:recent_tail] =
|
134
|
+
def cache(new_report)
|
135
|
+
cached_reports.unshift(*new_report.format)
|
136
|
+
self.class.cache[:recent_tail] = new_report.size
|
149
137
|
self.class.cache[:cached_at] = Time.now
|
150
138
|
end
|
151
139
|
|
152
|
-
def
|
140
|
+
def parse_time_options(options)
|
153
141
|
case
|
154
142
|
when options[:latest]
|
155
143
|
cached_at
|
@@ -162,12 +150,16 @@ class Nicorepo
|
|
162
150
|
end
|
163
151
|
end
|
164
152
|
|
165
|
-
def open_numbered_link(
|
166
|
-
|
167
|
-
|
153
|
+
def open_numbered_link(num)
|
154
|
+
if num > cached_reports.size || num < 1
|
155
|
+
say "Unavailable report number", :red
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
url = cached_reports[num - 1][:url]
|
168
160
|
|
169
161
|
Launchy.open(url, options) do |exception|
|
170
|
-
puts "Attempted to open #{url}
|
162
|
+
puts "Attempted to open #{url} but failed because of #{exception}"
|
171
163
|
raise exception
|
172
164
|
end
|
173
165
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
|
-
|
3
|
+
module Nicorepo
|
4
4
|
module Cli
|
5
5
|
class Configuration
|
6
6
|
|
@@ -8,7 +8,7 @@ class Nicorepo
|
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
params = defaults.merge(load_config)
|
11
|
-
@request_nums
|
11
|
+
@request_nums = params["request_num"]
|
12
12
|
@limit_pages = params["limit_page"]
|
13
13
|
end
|
14
14
|
|
@@ -35,7 +35,7 @@ class Nicorepo
|
|
35
35
|
"general" => 10
|
36
36
|
},
|
37
37
|
"limit_page" => {
|
38
|
-
"general" =>
|
38
|
+
"general" => 10
|
39
39
|
}
|
40
40
|
}
|
41
41
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'netrc'
|
2
|
+
|
3
|
+
module Nicorepo
|
4
|
+
class Client
|
5
|
+
class AccountLackError < StandardError; end
|
6
|
+
|
7
|
+
PER_PAGE = 25
|
8
|
+
MAX_PAGES_DEFAULT = 20
|
9
|
+
|
10
|
+
def session
|
11
|
+
# TODO: handle expiration
|
12
|
+
@session ||= (
|
13
|
+
mail, pass = Netrc.read["nicovideo.jp"]
|
14
|
+
raise AccountLackError, "mail and password is not defined in .netrc as a machine nicovideo.jp" if mail.nil? || pass.nil?
|
15
|
+
Request::Auth.new.login(mail, pass)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset_session
|
20
|
+
@session = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def all(request_num = PER_PAGE, options = {})
|
24
|
+
options = options.merge(limit_page: request_num / PER_PAGE + 1)
|
25
|
+
|
26
|
+
fetch(:all, request_num, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def videos(request_num, options = {})
|
30
|
+
fetch(:videos, request_num, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def lives(request_num, options = {})
|
34
|
+
fetch(:lives, request_num, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# === Params
|
38
|
+
#
|
39
|
+
# * limit_page - Integer
|
40
|
+
# * request_num - Integer
|
41
|
+
# * options - Hash
|
42
|
+
#
|
43
|
+
# === Options
|
44
|
+
#
|
45
|
+
# * from - Time or String
|
46
|
+
# * to - Time or String
|
47
|
+
#
|
48
|
+
# fetch reports `:from` newer `:to` older
|
49
|
+
#
|
50
|
+
def fetch(filter_type, request_num, options = {})
|
51
|
+
limit_page = options[:limit_page] || MAX_PAGES_DEFAULT
|
52
|
+
filter = Filter.new(filter_type, min_time: options[:to])
|
53
|
+
page = Page.new(session, filter, options[:from], options[:to])
|
54
|
+
|
55
|
+
# NOTE: filter は report に渡したほうがシンプルじゃない?
|
56
|
+
limit_page.times.each_with_object(Report.new(request_num)) do |_, report|
|
57
|
+
report.push(page)
|
58
|
+
break report if page.last_page? || report.reach_request_num?
|
59
|
+
|
60
|
+
page = page.next
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Nicorepo
|
2
|
+
class Filter
|
3
|
+
def initialize(type_or_topic, options = {})
|
4
|
+
@rules = [].tap do |r|
|
5
|
+
r << (
|
6
|
+
case type_or_topic
|
7
|
+
when :all
|
8
|
+
All.new
|
9
|
+
when :videos
|
10
|
+
Equal.new('nicovideo.user.video.upload')
|
11
|
+
when :lives
|
12
|
+
Match.new(/^live/)
|
13
|
+
when String
|
14
|
+
Equal.new(type_or_topic)
|
15
|
+
when Regex
|
16
|
+
Match.new(type_or_topic)
|
17
|
+
else
|
18
|
+
fail "Given type or topic #{type_or_topic} is not supported"
|
19
|
+
end
|
20
|
+
)
|
21
|
+
|
22
|
+
r << MinTime.new(options[:min_time]) if options[:min_time]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def accepts?(data)
|
27
|
+
@rules.all? { |r| r.accepts?(data) }
|
28
|
+
end
|
29
|
+
|
30
|
+
class MinTime
|
31
|
+
def initialize(min_time)
|
32
|
+
@min_time = min_time
|
33
|
+
end
|
34
|
+
|
35
|
+
def accepts?(data)
|
36
|
+
Time.parse(data['createdAt']) > @min_time
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class All
|
41
|
+
def accepts?(_)
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Equal
|
47
|
+
def initialize(topic)
|
48
|
+
@topic = topic
|
49
|
+
end
|
50
|
+
|
51
|
+
def accepts?(data)
|
52
|
+
data['topic'] == @topic
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Match
|
57
|
+
def initialize(topic_regexp)
|
58
|
+
@topic_regexp = topic_regexp
|
59
|
+
end
|
60
|
+
|
61
|
+
def accepts?(data)
|
62
|
+
data['topic'] =~ @topic_regexp
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Nicorepo
|
2
|
+
class Page
|
3
|
+
attr_reader :raw
|
4
|
+
|
5
|
+
def initialize(session, filter, from = nil, to = nil)
|
6
|
+
@session = session
|
7
|
+
@filter = filter
|
8
|
+
@cursor = Cursor.new(from).value
|
9
|
+
@min_cursor = Cursor.new(to).value
|
10
|
+
|
11
|
+
fetch
|
12
|
+
end
|
13
|
+
|
14
|
+
def next
|
15
|
+
fail 'Next page not found' if last_page?
|
16
|
+
|
17
|
+
self.class.new(@session, @filter, @next_cursor, @min_cursor)
|
18
|
+
end
|
19
|
+
|
20
|
+
def last_page?
|
21
|
+
return false unless @min_cursor && @next_cursor
|
22
|
+
|
23
|
+
@min_cursor > @next_cursor
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def fetch
|
29
|
+
json = Request::Timeline.fetch(@session, cursor: @cursor)
|
30
|
+
|
31
|
+
@next_cursor = json['meta']['minId']
|
32
|
+
@raw = json['data'].select { |data| @filter.accepts?(data) }
|
33
|
+
end
|
34
|
+
|
35
|
+
class Cursor
|
36
|
+
attr_reader :value
|
37
|
+
|
38
|
+
def initialize(time_or_cursor)
|
39
|
+
@value = cursor?(time_or_cursor) ? time_or_cursor : value_from_time(time_or_cursor)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@value
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def value_from_time(time)
|
49
|
+
return unless time
|
50
|
+
|
51
|
+
time = Time.parse(time) if time === String
|
52
|
+
(time.to_f * 1000).floor.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def cursor?(value)
|
56
|
+
value === String && cursor.match?(/\A\d{13}\z/)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/nicorepo/report.rb
CHANGED
@@ -1,16 +1,107 @@
|
|
1
|
-
|
1
|
+
module Nicorepo
|
2
2
|
class Report
|
3
|
-
|
3
|
+
attr_reader :pages
|
4
4
|
|
5
|
-
|
5
|
+
def initialize(request_num)
|
6
|
+
@pages = []
|
7
|
+
@request_num = request_num
|
8
|
+
end
|
9
|
+
|
10
|
+
def push(page_or_pages)
|
11
|
+
pages = (page_or_pages.is_a?(Array) ? page_or_pages : [page_or_pages]).compact
|
12
|
+
@pages.push(*pages)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
raw.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def raw
|
21
|
+
@pages.map(&:raw).flatten[0, @request_num]
|
22
|
+
end
|
23
|
+
|
24
|
+
def format(formatter = DefaultFormatter)
|
25
|
+
formatter.process_all(raw)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reach_request_num?
|
29
|
+
size >= @request_num
|
30
|
+
end
|
31
|
+
|
32
|
+
class DefaultFormatter
|
33
|
+
require 'time'
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def process_all(raw)
|
37
|
+
raw.map { |h| process(h) }.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
def process(h)
|
41
|
+
return unless h
|
42
|
+
|
43
|
+
title, url = infer_body(h)
|
44
|
+
{
|
45
|
+
sender: nickname(h),
|
46
|
+
topic: topic(h),
|
47
|
+
title: title,
|
48
|
+
url: url,
|
49
|
+
created_at: created_at(h)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def nickname(h)
|
56
|
+
h.dig('senderNiconicoUser', 'nickname')
|
57
|
+
end
|
58
|
+
|
59
|
+
def topic(h)
|
60
|
+
case h['topic']
|
61
|
+
when 'nicovideo.user.video.upload'
|
62
|
+
'動画投稿'
|
63
|
+
when 'nicovideo.user.video.kiriban.play'
|
64
|
+
"#{h.dig('actionLog', 'viewCount')}再生達成"
|
65
|
+
when 'nicovideo.user.video.advertise'
|
66
|
+
'動画広告'
|
67
|
+
when 'nicovideo.user.mylist.add.video'
|
68
|
+
'マイリスト登録'
|
69
|
+
when 'live.user.program.onairs'
|
70
|
+
'生放送開始'
|
71
|
+
else
|
72
|
+
h['topic']
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def created_at(h)
|
77
|
+
Time.parse(h['createdAt'])
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [String, String] title, url
|
81
|
+
def infer_body(h)
|
82
|
+
candidate_keys = %w(video program)
|
83
|
+
candidate_keys.each do |k|
|
84
|
+
if h[k]
|
85
|
+
id, title = h[k].fetch_values('id', 'title') if h[k]
|
86
|
+
return title, watch_url(id, k)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
['', '']
|
91
|
+
end
|
92
|
+
|
93
|
+
def watch_url(id, key)
|
94
|
+
base =
|
95
|
+
case key
|
96
|
+
when 'video'
|
97
|
+
"http://www.nicovideo.jp/watch/"
|
98
|
+
when 'program'
|
99
|
+
"http://live.nicovideo.jp/watch/"
|
100
|
+
end
|
6
101
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@url = attrs[:url]
|
11
|
-
@author = attrs[:author]
|
12
|
-
@kind = attrs[:kind]
|
13
|
-
@date = attrs[:date]
|
102
|
+
base + id
|
103
|
+
end
|
104
|
+
end
|
14
105
|
end
|
15
106
|
end
|
16
107
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'cgi'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Nicorepo
|
6
|
+
module Request
|
7
|
+
class Auth
|
8
|
+
class LoginError < StandardError; end
|
9
|
+
|
10
|
+
attr_reader :session
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@session = ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def login(mail, pass)
|
17
|
+
res = Net::HTTP.start(url.host, url.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |client|
|
18
|
+
client.post(url.path, body(mail, pass))
|
19
|
+
end
|
20
|
+
|
21
|
+
cookies = res.get_fields('set-cookie').map { |str| CGI::Cookie.parse(str) }
|
22
|
+
|
23
|
+
session_cookie = cookies.detect { |c|
|
24
|
+
c['user_session']&.size > 0 && c['user_session'] != ['deleted']
|
25
|
+
}
|
26
|
+
fail LoginError, "invalid mail or password: mail => #{mail}" unless session_cookie
|
27
|
+
|
28
|
+
@session = session_cookie.fetch('user_session').first
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def url
|
34
|
+
URI('https://secure.nicovideo.jp/secure/login?site=niconico')
|
35
|
+
end
|
36
|
+
|
37
|
+
def body(mail, pass)
|
38
|
+
"mail=#{mail}&password=#{pass}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Timeline
|
43
|
+
require 'json'
|
44
|
+
|
45
|
+
def self.fetch(session, cursor: nil)
|
46
|
+
new.fetch(session, cursor)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch(session, cursor)
|
50
|
+
url = url(cursor)
|
51
|
+
res = Net::HTTP.start(url.host, url.port) do |client|
|
52
|
+
client.get(url.request_uri, header(session))
|
53
|
+
end
|
54
|
+
|
55
|
+
fail "Request timeline failed !! #{res.inspect}: #{res.to_hash}" if res.code != '200'
|
56
|
+
|
57
|
+
JSON.parse(res.body)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def header(session)
|
63
|
+
{
|
64
|
+
'Cookie' => CGI::Cookie.new('user_session', session).to_s,
|
65
|
+
'Connection' => 'keep-alive',
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def url(cursor)
|
70
|
+
params = {
|
71
|
+
client_app: 'pc_myrepo',
|
72
|
+
cursor: cursor,
|
73
|
+
}.compact
|
74
|
+
|
75
|
+
URI('http://www.nicovideo.jp/api/nicorepo/timeline/my/all').tap do |url|
|
76
|
+
url.query = params.map { |k, v| "#{CGI.escape(k.to_s)}=#{v}" }.join('&')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/nicorepo/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.0
|
1
|
+
module Nicorepo
|
2
|
+
VERSION = "1.0.0"
|
3
3
|
end
|
data/nicorepo.gemspec
CHANGED
@@ -7,8 +7,8 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.version = Nicorepo::VERSION
|
8
8
|
spec.authors = ["upinetree"]
|
9
9
|
spec.email = ["upinetree@gmail.com"]
|
10
|
-
spec.summary = %q{Simple nicorepo
|
11
|
-
spec.description = %q{
|
10
|
+
spec.summary = %q{Simple nicorepo client}
|
11
|
+
spec.description = %q{Provides a simple API client for Nicorepo (on nicovideo.jp).}
|
12
12
|
spec.homepage = "https://github.com/upinetree/nicorepo"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
@@ -20,16 +20,17 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.required_ruby_version = ">= 2.
|
23
|
+
spec.required_ruby_version = ">= 2.3.0"
|
24
24
|
|
25
|
-
spec.add_dependency "mechanize", "~> 2.7"
|
26
25
|
spec.add_dependency "launchy", "~> 2.4"
|
27
26
|
spec.add_dependency "netrc", "~> 0.7"
|
28
27
|
spec.add_dependency "thor", "~> 0.19"
|
29
28
|
|
30
|
-
spec.add_development_dependency "bundler"
|
29
|
+
spec.add_development_dependency "bundler"
|
31
30
|
spec.add_development_dependency "pry"
|
32
31
|
spec.add_development_dependency "rake"
|
33
32
|
spec.add_development_dependency "rspec"
|
33
|
+
spec.add_development_dependency "webmock"
|
34
|
+
spec.add_development_dependency "vcr"
|
34
35
|
end
|
35
36
|
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nicorepo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- upinetree
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: mechanize
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.7'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '2.7'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: launchy
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,16 +56,16 @@ dependencies:
|
|
70
56
|
name: bundler
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
|
-
- - "
|
59
|
+
- - ">="
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
61
|
+
version: '0'
|
76
62
|
type: :development
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
|
-
- - "
|
66
|
+
- - ">="
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
68
|
+
version: '0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: pry
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,7 +108,35 @@ dependencies:
|
|
122
108
|
- - ">="
|
123
109
|
- !ruby/object:Gem::Version
|
124
110
|
version: '0'
|
125
|
-
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: vcr
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Provides a simple API client for Nicorepo (on nicovideo.jp).
|
126
140
|
email:
|
127
141
|
- upinetree@gmail.com
|
128
142
|
executables:
|
@@ -141,11 +155,13 @@ files:
|
|
141
155
|
- bin/nicorepo
|
142
156
|
- exe/nicorepo
|
143
157
|
- lib/nicorepo.rb
|
144
|
-
- lib/nicorepo/cli
|
145
|
-
- lib/nicorepo/cli/
|
146
|
-
- lib/nicorepo/
|
158
|
+
- lib/nicorepo/cli.rb
|
159
|
+
- lib/nicorepo/cli/configuration.rb
|
160
|
+
- lib/nicorepo/client.rb
|
161
|
+
- lib/nicorepo/filter.rb
|
162
|
+
- lib/nicorepo/page.rb
|
147
163
|
- lib/nicorepo/report.rb
|
148
|
-
- lib/nicorepo/
|
164
|
+
- lib/nicorepo/request.rb
|
149
165
|
- lib/nicorepo/version.rb
|
150
166
|
- nicorepo.gemspec
|
151
167
|
homepage: https://github.com/upinetree/nicorepo
|
@@ -160,7 +176,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
176
|
requirements:
|
161
177
|
- - ">="
|
162
178
|
- !ruby/object:Gem::Version
|
163
|
-
version: 2.
|
179
|
+
version: 2.3.0
|
164
180
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
181
|
requirements:
|
166
182
|
- - ">="
|
@@ -171,5 +187,5 @@ rubyforge_project:
|
|
171
187
|
rubygems_version: 2.6.11
|
172
188
|
signing_key:
|
173
189
|
specification_version: 4
|
174
|
-
summary: Simple nicorepo
|
190
|
+
summary: Simple nicorepo client
|
175
191
|
test_files: []
|
data/lib/nicorepo/parser.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
class Nicorepo
|
2
|
-
class Parser
|
3
|
-
def initialize(agent)
|
4
|
-
@agent = agent
|
5
|
-
end
|
6
|
-
|
7
|
-
def parse_page(url)
|
8
|
-
page = @agent.get(url)
|
9
|
-
{
|
10
|
-
reports_attrs: parse_reports(page),
|
11
|
-
next_url: next_url(page)
|
12
|
-
}
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def parse_reports(page)
|
18
|
-
nodes = report_nodes(page)
|
19
|
-
nodes.map { |node| report_attrs(node) }
|
20
|
-
end
|
21
|
-
|
22
|
-
def report_attrs(node)
|
23
|
-
{
|
24
|
-
body: parse_body(node),
|
25
|
-
title: parse_title(node),
|
26
|
-
url: parse_url(node),
|
27
|
-
author: parse_author(node),
|
28
|
-
kind: parse_kind(node),
|
29
|
-
date: parse_date(node)
|
30
|
-
}
|
31
|
-
rescue => e
|
32
|
-
error_report(node, e)
|
33
|
-
end
|
34
|
-
|
35
|
-
def report_nodes(page)
|
36
|
-
page.search('.timeline/.log')
|
37
|
-
end
|
38
|
-
|
39
|
-
def next_url(page)
|
40
|
-
page.search('.next-page-link').first['href']
|
41
|
-
end
|
42
|
-
|
43
|
-
def parse_body(node)
|
44
|
-
node.search('.log-body').first.inner_text.gsub(/(\t|\r|\n)/, "")
|
45
|
-
end
|
46
|
-
|
47
|
-
def parse_title(node)
|
48
|
-
node.search('.log-target-info/a').first.inner_text
|
49
|
-
end
|
50
|
-
|
51
|
-
def parse_url(node)
|
52
|
-
node.search('.log-target-info/a').first['href']
|
53
|
-
end
|
54
|
-
|
55
|
-
def parse_author(node)
|
56
|
-
node.search('.log-body/a').first.inner_text
|
57
|
-
end
|
58
|
-
|
59
|
-
# example: 'log.log-community-video-upload' -> 'community-video-upload'
|
60
|
-
def parse_kind(node)
|
61
|
-
cls = node['class']
|
62
|
-
trim = 'log-'
|
63
|
-
|
64
|
-
index = cls.index(/#{trim}/)
|
65
|
-
return cls if index.nil?
|
66
|
-
|
67
|
-
index += trim.size
|
68
|
-
cls[index..cls.size]
|
69
|
-
end
|
70
|
-
|
71
|
-
def parse_date(node)
|
72
|
-
d = node.search('.log-footer-date/time').first['datetime']
|
73
|
-
Time.xmlschema(d).localtime
|
74
|
-
end
|
75
|
-
|
76
|
-
def error_report(node, e)
|
77
|
-
{
|
78
|
-
body: node.inner_html.gsub(/\r|\n|\t/, ''),
|
79
|
-
title: "An exception occured: #{e.message}\n#{e.backtrace}",
|
80
|
-
url: '',
|
81
|
-
author: '',
|
82
|
-
kind: Report::ERROR_KIND,
|
83
|
-
date: ''
|
84
|
-
}
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
data/lib/nicorepo/reports.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
require 'nicorepo/report'
|
2
|
-
require 'forwardable'
|
3
|
-
|
4
|
-
class Nicorepo
|
5
|
-
class Reports
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
TOP_URL = 'http://www.nicovideo.jp/my/top'
|
9
|
-
|
10
|
-
attr_reader :reports
|
11
|
-
def_delegators :@reports, :size
|
12
|
-
|
13
|
-
def initialize(parser)
|
14
|
-
@parser = parser
|
15
|
-
@reports = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def fetch(request_num, limit_page, since: nil)
|
19
|
-
filter = {}
|
20
|
-
filter[:kind] = selected_kind
|
21
|
-
filter[:since] =
|
22
|
-
case since
|
23
|
-
when String
|
24
|
-
Time.parse since
|
25
|
-
when Time
|
26
|
-
since
|
27
|
-
else
|
28
|
-
nil
|
29
|
-
end
|
30
|
-
@reports = fetch_recursively(request_num, limit_page, filter)
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def selected_kind
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
|
39
|
-
def fetch_recursively(request_num, limit_page, filter = {}, url = TOP_URL)
|
40
|
-
return [] unless limit_page > 0
|
41
|
-
|
42
|
-
# fetch current page reports
|
43
|
-
page = @parser.parse_page(url)
|
44
|
-
reports = page[:reports_attrs].map { |attrs| Report.new(attrs) }
|
45
|
-
|
46
|
-
if filter[:since]
|
47
|
-
reach_oldest_page = (reports.last.date < filter[:since])
|
48
|
-
reports.reject! { |report| report.date < filter[:since] }
|
49
|
-
end
|
50
|
-
reports.select! { |report| report.kind =~ /#{filter[:kind]}|#{Report::ERROR_KIND}/ } if filter[:kind]
|
51
|
-
|
52
|
-
return reports[0, request_num] if reports.size >= request_num
|
53
|
-
return reports if filter[:since] && reach_oldest_page
|
54
|
-
|
55
|
-
# recursively fetch next reports
|
56
|
-
next_reports = fetch_recursively(request_num - reports.size, limit_page - 1, filter, page[:next_url])
|
57
|
-
reports += next_reports unless next_reports.nil?
|
58
|
-
reports
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
class VideoReports < Reports
|
63
|
-
private
|
64
|
-
|
65
|
-
def selected_kind
|
66
|
-
'video-upload'
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
class LiveReports < Reports
|
71
|
-
private
|
72
|
-
|
73
|
-
def selected_kind
|
74
|
-
'live'
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|