wikibot 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.
data/Manifest ADDED
@@ -0,0 +1,9 @@
1
+ README.textile
2
+ Rakefile
3
+ lib/category.rb
4
+ lib/class_ext.rb
5
+ lib/hash_ext.rb
6
+ lib/openhash.rb
7
+ lib/page.rb
8
+ lib/wikibot.rb
9
+ Manifest
data/README.textile ADDED
@@ -0,0 +1,17 @@
1
+ h1=(). WikiBot
2
+
3
+ h2. About
4
+
5
+ WikiBot was originally a PHP-based framework for bots I run on Wikipedia, however when it broke due to changes in mediawiki code, I decided to rewrite it in Ruby, using the MediaWiki API instead of screen-scraping. This is the result.
6
+
7
+ As you'll notice, the features it provides are rather sparse at the moment, as I've only been adding features that I require. When I get more time, I'll flesh it out more.
8
+
9
+ h2. Gem Requirements
10
+
11
+ * "taf2-curb":taf2 (a fork of curb; using the base curb gem might work but is untested)
12
+ * "xml-simple":xml
13
+ * "deep_merge":dm
14
+
15
+ [taf2]http://github.com/taf2/curb/tree/master
16
+ [xml]http://xml-simple.rubyforge.org/
17
+ [dm]http://rubyforge.org/projects/deepmerge/
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('wikibot', '0.2.0') do |p|
6
+ p.description = "Mediawiki Bot framework"
7
+ p.url = "http://github.com/dvandersluis/wiki_bot"
8
+ p.author = "Daniel Vandersluis"
9
+ p.email = "daniel@codexed.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.development_dependencies = []
12
+ p.runtime_dependencies = ['taf2-curb', 'xml-simple', 'deep_merge']
13
+ end
14
+
15
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
data/lib/category.rb ADDED
@@ -0,0 +1,48 @@
1
+ module WikiBot
2
+ class Category < Page
3
+ def category_info
4
+ data = {
5
+ :action => :query,
6
+ :titles => @name,
7
+ :prop => :categoryinfo
8
+ }
9
+
10
+ # The query API returns nothing for an empty cat, so we'll return a hash with all the normal
11
+ # properties set to 0 instead
12
+ empty_cat = { "pages" => 0, "size" => 0, "files" => 0, "subcats" => 0, "hidden" => "" }
13
+ @wiki_bot.query_api(:get, data).query.pages.page.categoryinfo || empty_cat
14
+ end
15
+
16
+ def members(sort = :sortkey, dir = :desc, namespace = nil)
17
+ data = {
18
+ :action => :query,
19
+ :list => :categorymembers,
20
+ :cmtitle => @name,
21
+ :cmsort => sort,
22
+ :cmdir => dir,
23
+ :cmnamespace => namespace
24
+ }
25
+
26
+ @wiki_bot.query_api(:get, data).query.categorymembers.cm
27
+ end
28
+
29
+ # Returns a hash of how many pages live in a category
30
+ def count(include_subcats = false)
31
+ out = {}
32
+ ci = category_info
33
+
34
+ out[@name] = {
35
+ :pages => ci.pages.to_i
36
+ }
37
+
38
+ if include_subcats and ci.subcats.to_i > 0
39
+ out[@name][:subcats] = {}
40
+ members.each do |m|
41
+ out[@name][:subcats].merge! WikiPage.new(@wiki_bot, m.title).count(include_subcats)
42
+ end
43
+ end
44
+
45
+ out
46
+ end
47
+ end
48
+ end
data/lib/class_ext.rb ADDED
@@ -0,0 +1,40 @@
1
+ # Borrowed from Rails
2
+ class Class
3
+ def cattr_reader(*syms)
4
+ syms.flatten.each do |sym|
5
+ next if sym.is_a?(Hash)
6
+ class_eval(<<-EOS, __FILE__, __LINE__)
7
+ unless defined? @@#{sym}
8
+ @@#{sym} = nil
9
+ end
10
+
11
+ def self.#{sym}
12
+ @@#{sym}
13
+ end
14
+
15
+ def #{sym}
16
+ @@#{sym}
17
+ end
18
+ EOS
19
+ end
20
+ end
21
+
22
+ def cattr_writer(*syms)
23
+ syms.flatten.each do |sym|
24
+ class_eval(<<-EOS, __FILE__, __LINE__)
25
+ unless defined? @@#{sym}
26
+ @@#{sym} = nil
27
+ end
28
+
29
+ def self.#{sym}=(obj)
30
+ @@#{sym} = obj
31
+ end
32
+ EOS
33
+ end
34
+ end
35
+
36
+ def cattr_accessor(*syms)
37
+ cattr_reader(*syms)
38
+ cattr_writer(*syms)
39
+ end
40
+ end
data/lib/hash_ext.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'cgi'
2
+
3
+ class Hash
4
+ def to_post_fields
5
+ inject([]) do |memo, pair|
6
+ key, val = pair
7
+ memo.push Curl::PostField.content(key.to_s, val.to_s)
8
+ end
9
+ end
10
+
11
+ def to_querystring
12
+ inject([]) do |memo, pair|
13
+ key, val = pair
14
+ memo.push "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"
15
+ end.join("&")
16
+ end
17
+ end
data/lib/openhash.rb ADDED
@@ -0,0 +1,23 @@
1
+ class OpenHash < Hash
2
+ undef_method :id
3
+
4
+ def initialize(hash = {})
5
+ super()
6
+ update(hash)
7
+ end
8
+
9
+ def [](key)
10
+ h = super(key)
11
+ h.is_a?(Hash) ? OpenHash.new(h) : h
12
+ end
13
+
14
+ # Allow hash properties to be referenced by dot notation
15
+ def method_missing(name, *args)
16
+ name = name.to_s
17
+ if self.include? name
18
+ self[name]
19
+ elsif self.include? name.to_sym
20
+ self[name.to_sym]
21
+ end
22
+ end
23
+ end
data/lib/page.rb ADDED
@@ -0,0 +1,108 @@
1
+ module WikiBot
2
+ class Page
3
+ class WriteError < StandardError; end
4
+
5
+ attr_writer :wiki_bot
6
+ attr_reader :name
7
+
8
+ def initialize(wiki_bot, name)
9
+ @wiki_bot = wiki_bot
10
+ @name = name
11
+ end
12
+
13
+ ###
14
+ # Read from page
15
+ def content
16
+ @content ||= begin
17
+ data = {
18
+ :action => :query,
19
+ :titles => @name,
20
+ :prop => :revisions,
21
+ :rvprop => :content
22
+ }
23
+
24
+ @wiki_bot.query_api(:get, data).query.pages.page.revisions.rev.content
25
+ end
26
+ end
27
+
28
+ # Parse page content
29
+ def text
30
+ @text ||= begin
31
+ data = {
32
+ :action => :parse,
33
+ :page => @name
34
+ }
35
+
36
+ @wiki_bot.query_api(:get, data).parse.text.content
37
+ end
38
+ end
39
+
40
+ ###
41
+ # Get page categories
42
+ def categories(show = :all)
43
+ # Cache hidden and non-hidden categories separately
44
+ @categories ||= begin
45
+ puts "Loading category data"
46
+ data = {
47
+ :action => :query,
48
+ :titles => @name,
49
+ :prop => :categories,
50
+ :clshow => "!hidden"
51
+ }
52
+
53
+ categories = @wiki_bot.query_api(:get, data).query.pages.page.categories.cl
54
+ categories = categories.inject([]) do |memo, category|
55
+ memo.push(WikiBot::Category.new(@wiki_bot, category.title))
56
+ end
57
+
58
+ data = {
59
+ :action => :query,
60
+ :titles => @name,
61
+ :prop => :categories,
62
+ :clshow => "hidden"
63
+ }
64
+
65
+ hidden_categories = @wiki_bot.query_api(:get, data).query.pages.page.categories.cl
66
+ hidden_categories = hidden_categories.inject([]) do |memo, category|
67
+ memo.push(WikiBot::Category.new(@wiki_bot, category.title))
68
+ end
69
+
70
+ {:nonhidden => categories, :hidden => hidden_categories}
71
+ end
72
+
73
+ show = :all unless [:all, :hidden, :nonhidden].include? show
74
+ return @categories[:nonhidden] + @categories[:hidden] if show == :all
75
+ @categories[show]
76
+ end
77
+
78
+ def category_names(show = :all)
79
+ categories(show).map{ |c| c.name }
80
+ end
81
+
82
+ ###
83
+ # Write to page
84
+ def write(text, summary, section = nil, minor = false)
85
+ return if @wiki_bot.debug or @wiki_bot.readonly
86
+
87
+ data = {
88
+ :action => :edit,
89
+ :title => @name,
90
+
91
+ :text => text,
92
+ :token => @wiki_bot.edit_token,
93
+ :summary => summary,
94
+ :recreate => 1,
95
+ :bot => 1
96
+ }
97
+
98
+ data[:section] = section if !section.nil?
99
+ data[:minor] = 1 if minor
100
+ data[:notminor] = 1 if !minor
101
+
102
+ result = @wiki_bot.query_api(:post, data)
103
+ status = result.edit.result
104
+ @wiki_bot.page_writes += 1
105
+ raise WriteError, status unless status == "Success"
106
+ end
107
+ end
108
+ end
data/lib/wikibot.rb ADDED
@@ -0,0 +1,208 @@
1
+ require 'class_ext'
2
+ require 'hash_ext'
3
+ require 'openhash'
4
+ require 'page'
5
+ require 'category'
6
+
7
+ require 'rubygems'
8
+ require 'curb'
9
+ require 'xmlsimple'
10
+ require 'deep_merge'
11
+
12
+ module WikiBot
13
+ class CurbError < StandardError
14
+ attr_accessor :curb
15
+ def initialize(curb)
16
+ @curb = curb
17
+ end
18
+ end
19
+
20
+ class APIError < StandardError
21
+ attr_accessor :code, :info
22
+ def initialize(code, info)
23
+ @code = code
24
+ @info = info
25
+ end
26
+ end
27
+
28
+ class Bot
29
+ class LoginError < StandardError; end
30
+
31
+ @@version = "0.2.0" # WikiBot version
32
+
33
+ cattr_reader :version
34
+ cattr_accessor :cookiejar # Filename where cookies will be stored
35
+
36
+ attr_reader :config
37
+ attr_reader :api_hits
38
+ attr_accessor :page_writes
39
+ attr_accessor :debug # In debug mode, no writes will be made to the wiki
40
+ attr_accessor :readonly # Writes will not be made
41
+
42
+ def initialize(username, password, options = {})
43
+ @config = Hash.new
44
+ @cookies = Hash.new
45
+ @api_hits = 0
46
+ @page_writes = 0
47
+
48
+ api = options[:api] || "http://en.wikipedia.org/w/api.php"
49
+ auto_login = options[:auto_login] || false
50
+ @readonly = options[:readonly] || false
51
+ @debug = options[:debug] || false
52
+
53
+ @config = {
54
+ :username => username,
55
+ :password => password,
56
+ :api => api,
57
+ :logged_in => false
58
+ }
59
+
60
+ # Set up cURL:
61
+ @curl = Curl::Easy.new do |c|
62
+ c.headers["User-Agent"] = "Mozilla/5.0 Curb/Taf2/0.2.8 WikiBot/#{config[:username]}/#{@@version}"
63
+ #c.enable_cookies = true
64
+ #c.cookiejar = @@cookiejar
65
+ end
66
+
67
+ login if auto_login
68
+ end
69
+
70
+ def query_api(method, raw_data = {})
71
+ url = @config[:api]
72
+
73
+ raw_data[:format] = :xml if !(raw_data.include? :format and raw_data.include? 'format')
74
+
75
+ @curl.headers["Cookie"] = @cookies.inject([]) do |memo, pair|
76
+ key, val = pair
77
+ memo.push(CGI::escape(key) + "=" + CGI::escape(val))
78
+ end.join("; ") unless @cookies.nil?
79
+
80
+ response_xml = {}
81
+
82
+ while true
83
+ if method == :post
84
+ data = raw_data.to_post_fields
85
+ elsif method == :get
86
+ url = url.chomp("?") + "?" + raw_data.to_querystring
87
+ data = nil
88
+ end
89
+
90
+ @curl.url = url
91
+ @curl.headers["Expect"] = nil # MediaWiki will give a 417 error if Expect is set
92
+
93
+ if @debug
94
+ @curl.on_debug do |type, data|
95
+ p data
96
+ end
97
+ end
98
+
99
+ # If Set-Cookie headers are given in the response, set the cookies
100
+ @curl.on_header do |data|
101
+ header, text = data.split(":").map(&:strip)
102
+ if header == "Set-Cookie"
103
+ parts = text.split(";")
104
+ cookie_name, cookie_value = parts[0].split("=")
105
+ @cookies[cookie_name] = cookie_value
106
+ end
107
+ data.length
108
+ end
109
+
110
+ if data.nil? or (data.is_a? Array and data.empty?)
111
+ @curl.send("http_#{method}".to_sym)
112
+ else
113
+ @curl.send("http_#{method}".to_sym, *data)
114
+ end
115
+ @api_hits += 1
116
+
117
+ raise CurbError.new(@curl) unless @curl.response_code == 200
118
+
119
+ xml = XmlSimple.xml_in(@curl.body_str, {'ForceArray' => false})
120
+ raise APIError.new(xml['error']['code'], xml['error']['info']) if xml['error']
121
+
122
+ response_xml.deep_merge! xml
123
+ if xml['query-continue']
124
+ raw_data.merge! xml['query-continue'][xml['query-continue'].keys.first]
125
+ else
126
+ break
127
+ end
128
+ end
129
+
130
+ OpenHash.new(response_xml)
131
+ end
132
+
133
+ def login
134
+ return if @config[:logged_in]
135
+
136
+ data = {
137
+ :action => :login,
138
+ :lgname => @config[:username],
139
+ :lgpassword => @config[:password]
140
+ }
141
+
142
+ response = query_api(:post, data).login
143
+
144
+ if response.result == "NeedToken"
145
+ data = {
146
+ :action => :login,
147
+ :lgname => @config[:username],
148
+ :lgpassword => @config[:password],
149
+ :lgtoken => response.token
150
+ }
151
+
152
+ response = query_api(:post, data).login
153
+ end
154
+
155
+ raise LoginError, response.result unless response.result == "Success"
156
+
157
+ @config[:cookieprefix] = response.cookieprefix
158
+ @config[:logged_in] = true
159
+ end
160
+
161
+ def logout
162
+ return if !@config[:logged_in]
163
+
164
+ query_api(:post, { :action => :logout })
165
+ @config[:logged_in] = false
166
+ @config[:edit_token] = nil
167
+ end
168
+
169
+ def edit_token(page = "Main Page")
170
+ @config[:edit_token] ||= begin
171
+ data = {
172
+ :action => :query,
173
+ :prop => :info,
174
+ :intoken => :edit,
175
+ :titles => page
176
+ }
177
+
178
+ query_api(:get, data).query.pages.page.edittoken
179
+ end
180
+ end
181
+
182
+ # Get wiki stats
183
+ def stats
184
+ data = {
185
+ :action => :query,
186
+ :meta => :siteinfo,
187
+ :siprop => :statistics
188
+ }
189
+
190
+ query_api(:get, data).query.statistics
191
+ end
192
+
193
+ def page(name)
194
+ WikiBot::Page.new(self, name)
195
+ end
196
+
197
+ def category(name)
198
+ WikiBot::Category.new(self, name)
199
+ end
200
+
201
+ def format_date(date)
202
+ # Formats a date into the Wikipedia format
203
+ time = date.strftime("%H:%M")
204
+ month = date.strftime("%B")
205
+ "#{time}, #{date.day} #{month} #{date.year} (UTC)"
206
+ end
207
+ end
208
+ end
data/wikibot.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{wikibot}
5
+ s.version = "0.2.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Daniel Vandersluis"]
9
+ s.date = %q{2010-04-09}
10
+ s.description = %q{Mediawiki Bot framework}
11
+ s.email = %q{daniel@codexed.com}
12
+ s.extra_rdoc_files = ["README.textile", "lib/category.rb", "lib/class_ext.rb", "lib/hash_ext.rb", "lib/openhash.rb", "lib/page.rb", "lib/wikibot.rb"]
13
+ s.files = ["README.textile", "Rakefile", "lib/category.rb", "lib/class_ext.rb", "lib/hash_ext.rb", "lib/openhash.rb", "lib/page.rb", "lib/wikibot.rb", "Manifest", "wikibot.gemspec"]
14
+ s.homepage = %q{http://github.com/dvandersluis/wiki_bot}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Wikibot", "--main", "README.textile"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{wikibot}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Mediawiki Bot framework}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<taf2-curb>, [">= 0"])
27
+ s.add_runtime_dependency(%q<xml-simple>, [">= 0"])
28
+ s.add_runtime_dependency(%q<deep_merge>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<taf2-curb>, [">= 0"])
31
+ s.add_dependency(%q<xml-simple>, [">= 0"])
32
+ s.add_dependency(%q<deep_merge>, [">= 0"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<taf2-curb>, [">= 0"])
36
+ s.add_dependency(%q<xml-simple>, [">= 0"])
37
+ s.add_dependency(%q<deep_merge>, [">= 0"])
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wikibot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Vandersluis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-09 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: taf2-curb
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: xml-simple
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: deep_merge
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Mediawiki Bot framework
46
+ email: daniel@codexed.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.textile
53
+ - lib/category.rb
54
+ - lib/class_ext.rb
55
+ - lib/hash_ext.rb
56
+ - lib/openhash.rb
57
+ - lib/page.rb
58
+ - lib/wikibot.rb
59
+ files:
60
+ - README.textile
61
+ - Rakefile
62
+ - lib/category.rb
63
+ - lib/class_ext.rb
64
+ - lib/hash_ext.rb
65
+ - lib/openhash.rb
66
+ - lib/page.rb
67
+ - lib/wikibot.rb
68
+ - Manifest
69
+ - wikibot.gemspec
70
+ has_rdoc: true
71
+ homepage: http://github.com/dvandersluis/wiki_bot
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --line-numbers
77
+ - --inline-source
78
+ - --title
79
+ - Wikibot
80
+ - --main
81
+ - README.textile
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "1.2"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project: wikibot
99
+ rubygems_version: 1.3.5
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Mediawiki Bot framework
103
+ test_files: []
104
+