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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5501c9e31cdda24fbcf1bb355856b4f99a293bfd
4
- data.tar.gz: e496adc8058c169ef52d5c3a808e22375d386d94
3
+ metadata.gz: bae618f5d97184adc26e5376e237e195903e70f0
4
+ data.tar.gz: 82a97cc15b7d6b3c2931d27108c22f9d0d9a5d08
5
5
  SHA512:
6
- metadata.gz: e8701ba39746179bfd8f4e0d7203acfd03e4ccd2b41c23b5f218c1ac1ac14cead86291d0606ff84af8136e407e2591e2d66f16bb3f042989b751d6c2c4c6d9a4
7
- data.tar.gz: ebd6d0210c61adaf63eaa39642040299296dd5898f94d25944736ce310b83fc201668cd192f920edc7c8333ec995e5dae30cf2f326ea94f433b1ba358652a8f3
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 scraper and filter of nicorepo on nicovideo.
3
+ Nicorepo is an (unofficial) API client for Nicorepo on nicovideo.jp
4
4
 
5
- - filter reports by kind of them
6
- - specify number and pages to fetch reports
7
- - open url in browser
5
+ - Fetch nicorepo logs
6
+ - Filter them by topics and a period
7
+ - Provides a built-in CLI and commands
8
8
 
9
- It requires ruby 2.0.0.
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
- ### Authentication
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
- Nicorepo supports reading netrc file.
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
- Add following lines to your netrc file (`~/.netrc`)
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 nicorepo cli
63
+ ### Start Nicorepo CLI
46
64
 
47
- $ nicorepo
65
+ ```console
66
+ $ nicorepo
67
+ ```
48
68
 
49
- You can use following commands in interactive cli to fetch nicorepos.
69
+ You can use following commands in interactive cli.
50
70
 
51
71
  command | alias | description
52
72
  ----------------------|-------|---------------------------------------------------------
53
- all | a | fetch all reports
54
- videos | v | fetch only video reports
55
- lives | li | fetch only live reports
56
- show | s | show current reports
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
- Some commands have specific options `-n, --request-num=N`, `-p, --limit-page=N`.
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 -n20 -p5
84
+ > video
66
85
 
67
86
  Or you can also use aliases.
68
87
 
69
- > v -n20 -p5
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
- `-n` and `-p` are specifing options.
72
- You can omit it as you like then each commad uses default value.
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 -p3`
100
+ # => `video -n10 -p10`
76
101
  > v -n20
77
- # => `video -n20 -p3`
102
+ # => `video -n20 -p10`
78
103
  > v -p5
79
- # => `video -n20 -p5`
104
+ # => `video -n20 -p10`
80
105
 
81
- And also, you can use `-l, --latest`, `-h, --hours`, `-d, --days` options to find reports by the specific period.
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
- ### Configuration
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
 
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require_relative '../lib/nicorepo'
4
+ require 'pry'
5
+
6
+ require_relative '../lib/nicorepo/cli'
5
7
 
6
8
  Nicorepo::Cli::Runner.run
@@ -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
- class Nicorepo
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 "login", "re-login if your session is expired"
58
- def login
59
- login
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 = { since: parse_since_options(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 = { since: parse_since_options(options) }
84
- cache(repo.videos(request_num, limit_page, request_options))
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 = { since: parse_since_options(options) }
98
- cache(repo.lives(request_num, limit_page, request_options))
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
- showed_reports = options[:more] ? cached_reports : cached_reports[0, recent_tail]
106
- showed_reports.each.with_index(1) do |report, i|
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.body} at #{report.date.to_s}"
109
- say " #{report.title} (#{report.url})", :green
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 report url specified by number in your browser"
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[:reports] ||= []
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(reports)
147
- cached_reports.unshift(reports).flatten!
148
- self.class.cache[:recent_tail] = reports.size
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 parse_since_options(options)
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(request_num)
166
- url = cached_reports[request_num - 1].url
167
- raise ReportExistenceError, "report existence error: please fetch reports" if url.nil?
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} and failed because #{exception}"
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
- class Nicorepo
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 = params["request_num"]
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" => 3
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
@@ -1,16 +1,107 @@
1
- class Nicorepo
1
+ module Nicorepo
2
2
  class Report
3
- attr_accessor :body, :title, :url, :author, :kind, :date
3
+ attr_reader :pages
4
4
 
5
- ERROR_KIND = 'error'
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
- def initialize(attrs)
8
- @body = attrs[:body]
9
- @title = attrs[:title]
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
@@ -1,3 +1,3 @@
1
- class Nicorepo
2
- VERSION = "0.0.8"
1
+ module Nicorepo
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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 scraper}
11
- spec.description = %q{Scrapes and filters nicorepo reports from nicovideo.}
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.0.0"
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", "~> 1.6"
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.8
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-05-30 00:00:00.000000000 Z
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: '1.6'
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: '1.6'
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
- description: Scrapes and filters nicorepo reports from nicovideo.
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/cli.rb
145
- - lib/nicorepo/cli/config.rb
146
- - lib/nicorepo/parser.rb
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/reports.rb
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.0.0
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 scraper
190
+ summary: Simple nicorepo client
175
191
  test_files: []
@@ -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
@@ -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
-