nicorepo 0.0.8 → 1.0.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 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
-