wikiranger 0.1.0 → 0.2.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: 52617871050938bbf1b5db6723a178e82ebffb0b
4
- data.tar.gz: d1a3c7941a801cb21214b29d0c565fbfc86decfc
3
+ metadata.gz: ee56f0d0a12d1f38a39c3b63b17408da3d0870b9
4
+ data.tar.gz: 9e2ae0ebae414dd022dfe9f14ddf786494e4a256
5
5
  SHA512:
6
- metadata.gz: 27942efad962fae9cef47deb31f3ee55833eaabf6ede6ef6c841add21f585179b499349734b11758b57997bd741289e69f1371456cfa2e0e11ab03e9c5e9fbc8
7
- data.tar.gz: 0e51333984e97e4155671614e59da774a8fe5013faef746ece51ebdb911a8c1030633ef59e3297ff0869acbe24100c38ff437a06d081a53d8729d2d877cb743d
6
+ metadata.gz: 3cff48a388b7bcc366394c62969d2223d1593f05edd6088b8e6011579ebf1fa583f75c12e98369fd692b293364ab6fc27c592af42be72996107d72b515713ef0
7
+ data.tar.gz: ee3a860d27cd930481df8786432cd3292d1f79a7799e6bf9f0597b7ba1e7d94c1cc3512250fe302eb522742170f27a126f21962ed11724e55db92a637e57b2e6
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- wikiranger (0.1.0)
4
+ wikiranger (0.2.0)
5
5
  colorize (~> 0.8.1)
6
6
  ruby-progressbar (~> 1.9)
7
7
 
@@ -4,6 +4,7 @@ require "wikiranger"
4
4
 
5
5
  options = {
6
6
  :csv => nil,
7
+ :html => nil,
7
8
  :threads => 5,
8
9
  :top => 10,
9
10
  :api_base_url => Wikiranger::Wikipedia::DEFAULT_API_BASE_URI
@@ -13,6 +14,14 @@ begin
13
14
  OptionParser.new do |opts|
14
15
  opts.banner = "Usage: #{$0} [options] cidr [cidr2] ... [cidrN]"
15
16
 
17
+ opts.on("--html DESTINATION", "Write Wiki contribution HTML report file") do |v|
18
+ if File.exists?(v)
19
+ puts "ERROR: File #{v} does already exist".red
20
+ exit 1
21
+ end
22
+ options[:html] = v
23
+ end
24
+
16
25
  opts.on("--csv DESTINATION", "Write Wiki contribution data to CSV file") do |v|
17
26
  if File.exists?(v)
18
27
  puts "ERROR: File #{v} does already exist".red
@@ -97,9 +106,15 @@ if contributions.count.zero?
97
106
  exit
98
107
  end
99
108
 
109
+ if options[:html]
110
+ report = Wikiranger::Report.new(contributions)
111
+ report.generate(options[:html])
112
+ puts "[+] Wrote Wiki contribution HTML report to #{options[:html].bold}"
113
+ end
114
+
100
115
  if options[:csv]
101
116
  CSV.open(options[:csv], "wb") do |csv|
102
- csv << ["user", "page_id", "rev_id", "parent_id", "title", "timestamp", "comment", "size"]
117
+ csv << ["user", "page_id", "rev_id", "parent_id", "title", "timestamp", "comment", "size", "size_diff"]
103
118
  contributions.each do |contribution|
104
119
  csv << contribution.to_csv_array
105
120
  end
@@ -120,10 +135,10 @@ contributions.each do |contribution|
120
135
  end
121
136
 
122
137
  if !top_pages.key?(contribution.pageid)
123
- top_pages[contribution.pageid] = [contribution.size, contribution.title]
138
+ top_pages[contribution.pageid] = [contribution.sizediff.abs, contribution.title]
124
139
  else
125
140
  contribution_bytes, title = top_pages[contribution.pageid]
126
- top_pages[contribution.pageid] = [contribution_bytes + contribution.size, title]
141
+ top_pages[contribution.pageid] = [contribution_bytes + contribution.sizediff.abs, title]
127
142
  end
128
143
  end
129
144
 
@@ -6,6 +6,8 @@ require "optparse"
6
6
  require "ipaddr"
7
7
  require "date"
8
8
  require "csv"
9
+ require "erb"
10
+ require "cgi"
9
11
 
10
12
  require "colorize"
11
13
  require "ruby-progressbar"
@@ -15,8 +17,11 @@ require "wikiranger/util"
15
17
  require "wikiranger/thread_pool"
16
18
  require "wikiranger/wikipedia"
17
19
  require "wikiranger/wikipedia/user_contribution"
20
+ require "wikiranger/report"
18
21
 
19
22
  module Wikiranger
23
+ WIKIRANGER_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")).freeze
24
+
20
25
  BANNER = " _ _ _\n" +
21
26
  " _ _ _|_| |_|_|___ ___ ___ ___ ___ ___\n" +
22
27
  "| | | | | '_| | _| .'| | . | -_| _|\n" +
@@ -0,0 +1,45 @@
1
+ module Wikiranger
2
+ class Report
3
+ def initialize(contributions)
4
+ @contributions = contributions.sort_by { |c| c.timestamp_object }.reverse
5
+ @contributors = contributions.map(&:user).uniq.sort_by { |ip| ip.split(".").map(&:to_i) }
6
+ @pages = contributions.map { |c| [c.pageid, c.title] }.uniq.sort_by { |p| p.last }
7
+ @contribs_per_contributor = @contributors.map do |c|
8
+ [c, contributions.count { |contrib| contrib.user == c }]
9
+ end.to_h
10
+ @contribs_per_page = @pages.map do |p|
11
+ [p.first, contributions.count { |contrib| contrib.pageid == p.first }]
12
+ end.to_h
13
+ end
14
+
15
+ def generate(destination)
16
+ report = load_template
17
+ b = binding
18
+ File.open(destination, "w") do |f|
19
+ f.write(report.result(b))
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def load_template
26
+ ERB.new(File.read(File.join(Wikiranger::WIKIRANGER_ROOT, "templates", "default.html.erb")))
27
+ end
28
+
29
+ def h(unsafe)
30
+ CGI.escapeHTML(unsafe.to_s)
31
+ end
32
+
33
+ def contributor_url(contribution)
34
+ "https://en.m.wikipedia.org/wiki/Special:Contributions/#{h(contribution.user)}"
35
+ end
36
+
37
+ def contribution_url(contribution)
38
+ "https://en.m.wikipedia.org/wiki/Special:MobileDiff/#{h(contribution.revid)}"
39
+ end
40
+
41
+ def page_url(contribution)
42
+ "https://en.wikipedia.org/?curid=#{h(contribution.pageid)}"
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,3 @@
1
1
  module Wikiranger
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -12,7 +12,7 @@ module Wikiranger
12
12
  end
13
13
 
14
14
  def user_contributions(user)
15
- uri = "#{api_base_uri}/w/api.php?action=query&format=json&list=usercontribs&uclimit=max&ucuser=#{URI.escape(user)}&ucdir=older"
15
+ uri = "#{api_base_uri}/w/api.php?action=query&format=json&list=usercontribs&uclimit=max&ucuser=#{URI.escape(user)}&ucdir=older&ucprop=&ucprop=ids|title|timestamp|comment|size|sizediff"
16
16
  response = request(uri)
17
17
  if response.code.to_i != 200
18
18
  unexpected_response!("Unexpected response code: #{response.code} when retrieving user contributions for #{user}")
@@ -13,7 +13,7 @@ module Wikiranger
13
13
  end
14
14
 
15
15
  def to_csv_array
16
- [self.user, self.pageid, self.revid, self.parentid, self.title, self.timestamp, self.comment, self.size]
16
+ [self.user, self.pageid, self.revid, self.parentid, self.title, self.timestamp, self.comment, self.size, self.sizediff]
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,181 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>Wikiranger HTML Report</title>
8
+ <style type="text/css">
9
+ /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:0;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}
10
+
11
+ body {
12
+ font-family: Atlas Grotesk Web,helvetica neue,helvetica,arial,sans-serif;
13
+ color: #494a4b;
14
+ background-color: #f5f5f5;
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+ overflow-y: scroll;
18
+ padding-bottom: 50px;
19
+ }
20
+
21
+ h1,h2,h3,h4,h5,h6 {
22
+ color: #494a4b;
23
+ font-weight: 400;
24
+ }
25
+
26
+ p {
27
+ margin:0 0 1em 0;
28
+ line-height:1.5;
29
+ font-size:17px;
30
+ }
31
+
32
+ a {
33
+ color: inherit;
34
+ text-decoration: underline;
35
+ }
36
+
37
+ a:hover {
38
+ color: #000;
39
+ }
40
+
41
+ fieldset {
42
+ margin-bottom: 20px;
43
+ }
44
+
45
+ legend {
46
+ font-weight: bold;
47
+ padding-left: 5px;
48
+ padding-right: 5px;
49
+ }
50
+
51
+ .hidden {
52
+ display: none;
53
+ }
54
+
55
+ .content {
56
+ padding: 0px 20px 0px 20px;
57
+ }
58
+
59
+ .logo {
60
+ background-color: #2a2730;
61
+ color: #99979c;
62
+ margin: 0px 0px 50px 0px;
63
+ font-weight: bold;
64
+ }
65
+
66
+ .logo a {
67
+ color: #99979c;
68
+ }
69
+
70
+ .logo weak {
71
+ font-weight: normal;
72
+ }
73
+
74
+ table#contributions th {
75
+ text-align: left;
76
+ }
77
+
78
+ table#contributions tbody tr:hover {
79
+ background-color: #eaeaea;
80
+ }
81
+
82
+ .col-time {
83
+ width: 150px;
84
+ }
85
+
86
+ .diff-remove {
87
+ color: #dc2e16;
88
+ }
89
+
90
+ .diff-add {
91
+ color: #00af88;
92
+ }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <pre class="logo">
97
+ <div class="content">
98
+ _ _ _
99
+ _ _ _|_| |_|_|___ ___ ___ ___ ___ ___
100
+ | | | | | '_| | _| .'| | . | -_| _|
101
+ |_____|_|_,_|_|_| |__,|_|_|_ |___|_| v<%= Wikiranger::VERSION %>
102
+ <weak>by <a href="https://twitter.com/michenriksen" target="_blank">@michenriksen</a></weak> |___|
103
+
104
+ </div>
105
+ </pre>
106
+ <div class="content">
107
+ <fieldset>
108
+ <legend>Filtering options</legend>
109
+ <select id="select_contributor">
110
+ <option value="" selected="selected">From all contributors</option>
111
+ <% @contributors.each do |contributor| %>
112
+ <option value="<%=h contributor %>"><%=h contributor %> (<%=h @contribs_per_contributor[contributor] || '0' %>)</option>
113
+ <% end %>
114
+ </select>
115
+ <select id="select_page">
116
+ <option value="" selected="selected">From all pages</option>
117
+ <% @pages.each do |page| %>
118
+ <option value="<%=h page.first %>"><%=h page.last %> (<%=h @contribs_per_page[page.first] || '0' %>)</option>
119
+ <% end %>
120
+ </select>
121
+ <select id="select_changetype">
122
+ <option value="" selected="selected">Removals and additions</option>
123
+ <option value="removal">Removals</option>
124
+ <option value="addition">Additions</option>
125
+ </select>
126
+ </fieldset>
127
+ <table cellspacing="0" id="contributions" class="contribution-table">
128
+ <thead>
129
+ <tr>
130
+ <th class="col-time">Time</th>
131
+ <th>Change</th>
132
+ <th>Contributor</th>
133
+ <th>Page</th>
134
+ <th>Comment</th>
135
+ </tr>
136
+ </thead>
137
+ <tbody>
138
+ <% @contributions.each do |contribution| %>
139
+ <tr data-user="<%=h contribution.user %>" data-page-id="<%=h contribution.pageid %>" data-change-type="<%=h contribution.sizediff < 0 ? 'removal' : 'addition' %>">
140
+ <td><%=h contribution.timestamp_object.strftime("%b %e %Y, %H:%M") %></td>
141
+ <td class="<%= contribution.sizediff < 0 ? 'diff-remove' : 'diff-add' %>"><%=h contribution.sizediff%></td>
142
+ <td><a href="<%= contributor_url(contribution) %>" target="_blank"><%=h contribution.user %></a></td>
143
+ <td><a href="<%= page_url(contribution) %>" target="_blank"><%=h contribution.title %></a></td>
144
+ <td><a href="<%= contribution_url(contribution) %>" target="_blank"><%=h contribution.comment.empty? ? 'no edit summary' : contribution.comment %></a></td>
145
+ </tr>
146
+ <% end %>
147
+ </tbody>
148
+ </table>
149
+ </div>
150
+ <script type="text/javascript">
151
+ function filterContributions() {
152
+ var contributor = document.getElementById("select_contributor").value;
153
+ var page = document.getElementById("select_page").value;
154
+ var changeType = document.getElementById("select_changetype").value;
155
+ var contributions = document.querySelectorAll('#contributions tbody tr');
156
+
157
+ Array.prototype.forEach.call(contributions, function(el, i) {
158
+ el.classList.remove("hidden");
159
+ if (contributor !== "" && el.getAttribute("data-user") !== contributor ) {
160
+ el.classList.add("hidden");
161
+ return;
162
+ }
163
+
164
+ if (page !== "" && el.getAttribute("data-page-id") !== page) {
165
+ el.classList.add("hidden");
166
+ return;
167
+ }
168
+
169
+ if (changeType !== "" && el.getAttribute("data-change-type") !== changeType) {
170
+ el.classList.add("hidden");
171
+ return;
172
+ }
173
+ });
174
+ }
175
+
176
+ document.getElementById("select_contributor").addEventListener("change", filterContributions);
177
+ document.getElementById("select_page").addEventListener("change", filterContributions);
178
+ document.getElementById("select_changetype").addEventListener("change", filterContributions);
179
+ </script>
180
+ </body>
181
+ </html>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wikiranger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Henriksen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-01-14 00:00:00.000000000 Z
11
+ date: 2018-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-progressbar
@@ -99,11 +99,13 @@ files:
99
99
  - bin/setup
100
100
  - exe/wikiranger
101
101
  - lib/wikiranger.rb
102
+ - lib/wikiranger/report.rb
102
103
  - lib/wikiranger/thread_pool.rb
103
104
  - lib/wikiranger/util.rb
104
105
  - lib/wikiranger/version.rb
105
106
  - lib/wikiranger/wikipedia.rb
106
107
  - lib/wikiranger/wikipedia/user_contribution.rb
108
+ - templates/default.html.erb
107
109
  - wikiranger.gemspec
108
110
  homepage: https://github.com/michenriksen/wikiranger
109
111
  licenses: