mediawiki-gateway 0.6.2 → 1.0.0.rc1
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 +4 -4
- data/COPYING +22 -0
- data/ChangeLog +16 -0
- data/README.md +80 -21
- data/Rakefile +28 -34
- data/bin/mediawiki-gateway +203 -0
- data/lib/media_wiki.rb +4 -9
- data/lib/media_wiki/exception.rb +11 -8
- data/lib/media_wiki/fake_wiki.rb +636 -0
- data/lib/media_wiki/gateway.rb +105 -940
- data/lib/media_wiki/gateway/files.rb +173 -0
- data/lib/media_wiki/gateway/pages.rb +400 -0
- data/lib/media_wiki/gateway/query.rb +98 -0
- data/lib/media_wiki/gateway/site.rb +101 -0
- data/lib/media_wiki/gateway/users.rb +182 -0
- data/lib/media_wiki/utils.rb +47 -13
- data/lib/media_wiki/version.rb +27 -0
- data/lib/mediawiki-gateway.rb +1 -0
- data/spec/{import-test-data.xml → data/import.xml} +0 -0
- data/spec/media_wiki/gateway/files_spec.rb +34 -0
- data/spec/media_wiki/gateway/pages_spec.rb +390 -0
- data/spec/media_wiki/gateway/query_spec.rb +84 -0
- data/spec/media_wiki/gateway/site_spec.rb +122 -0
- data/spec/media_wiki/gateway/users_spec.rb +171 -0
- data/spec/media_wiki/gateway_spec.rb +129 -0
- data/spec/{live_gateway_spec.rb → media_wiki/live_gateway_spec.rb} +31 -35
- data/spec/{utils_spec.rb → media_wiki/utils_spec.rb} +41 -39
- data/spec/spec_helper.rb +17 -16
- metadata +77 -135
- data/.ruby-version +0 -1
- data/.rvmrc +0 -34
- data/Gemfile +0 -19
- data/Gemfile.lock +0 -77
- data/LICENSE +0 -21
- data/config/hosts.yml +0 -17
- data/lib/media_wiki/config.rb +0 -69
- data/mediawiki-gateway.gemspec +0 -113
- data/samples/README +0 -18
- data/samples/create_page.rb +0 -13
- data/samples/delete_batch.rb +0 -14
- data/samples/download_batch.rb +0 -15
- data/samples/email_user.rb +0 -14
- data/samples/export_xml.rb +0 -14
- data/samples/get_page.rb +0 -11
- data/samples/import_xml.rb +0 -14
- data/samples/run_fake_media_wiki.rb +0 -8
- data/samples/search_content.rb +0 -12
- data/samples/semantic_query.rb +0 -17
- data/samples/upload_commons.rb +0 -45
- data/samples/upload_file.rb +0 -13
- data/spec/fake_media_wiki/api_pages.rb +0 -135
- data/spec/fake_media_wiki/app.rb +0 -360
- data/spec/fake_media_wiki/query_handling.rb +0 -136
- data/spec/gateway_spec.rb +0 -888
@@ -0,0 +1,173 @@
|
|
1
|
+
module MediaWiki
|
2
|
+
|
3
|
+
class Gateway
|
4
|
+
|
5
|
+
module Files
|
6
|
+
|
7
|
+
# Upload a file, or get the status of pending uploads. Several
|
8
|
+
# methods are available:
|
9
|
+
#
|
10
|
+
# * Upload file contents directly.
|
11
|
+
# * Have the MediaWiki server fetch a file from a URL, using the
|
12
|
+
# 'url' parameter
|
13
|
+
#
|
14
|
+
# Requires Mediawiki 1.16+
|
15
|
+
#
|
16
|
+
# Arguments:
|
17
|
+
# * [path] Path to file to upload. Set to nil if uploading from URL.
|
18
|
+
# * [options] Hash of additional options
|
19
|
+
#
|
20
|
+
# Note that queries using session keys must be done in the same login
|
21
|
+
# session as the query that originally returned the key (i.e. do not
|
22
|
+
# log out and then log back in).
|
23
|
+
#
|
24
|
+
# Options:
|
25
|
+
# * 'filename' - Target filename (defaults to local name if not given), options[:target] is alias for this.
|
26
|
+
# * 'comment' - Upload comment. Also used as the initial page text for new files if 'text' is not specified.
|
27
|
+
# * 'text' - Initial page text for new files
|
28
|
+
# * 'watch' - Watch the page
|
29
|
+
# * 'ignorewarnings' - Ignore any warnings
|
30
|
+
# * 'url' - Url to fetch the file from. Set path to nil if you want to use this.
|
31
|
+
#
|
32
|
+
# Deprecated but still supported options:
|
33
|
+
# * :description - Description of this file. Used as 'text'.
|
34
|
+
# * :target - Target filename, same as 'filename'.
|
35
|
+
# * :summary - Edit summary for history. Used as 'comment'. Also used as 'text' if neither it or :description is specified.
|
36
|
+
#
|
37
|
+
# Examples:
|
38
|
+
# mw.upload('/path/to/local/file.jpg', 'filename' => 'RemoteFile.jpg')
|
39
|
+
# mw.upload(nil, 'filename' => 'RemoteFile2.jpg', 'url' => 'http://remote.com/server/file.jpg')
|
40
|
+
#
|
41
|
+
def upload(path, options = {})
|
42
|
+
if options[:description]
|
43
|
+
options['text'] = options.delete(:description)
|
44
|
+
end
|
45
|
+
|
46
|
+
if options[:target]
|
47
|
+
options['filename'] = options.delete(:target)
|
48
|
+
end
|
49
|
+
|
50
|
+
if options[:summary]
|
51
|
+
options['text'] ||= options[:summary]
|
52
|
+
options['comment'] = options.delete(:summary)
|
53
|
+
end
|
54
|
+
|
55
|
+
options['comment'] ||= 'Uploaded by MediaWiki::Gateway'
|
56
|
+
|
57
|
+
options['file'] = File.new(path) if path
|
58
|
+
|
59
|
+
full_name = path || options['url']
|
60
|
+
options['filename'] ||= File.basename(full_name) if full_name
|
61
|
+
|
62
|
+
unless options['file'] || options['url'] || options['sessionkey']
|
63
|
+
raise ArgumentError,
|
64
|
+
"One of the 'file', 'url' or 'sessionkey' options must be specified!"
|
65
|
+
end
|
66
|
+
|
67
|
+
send_request(options.merge(
|
68
|
+
'action' => 'upload',
|
69
|
+
'token' => get_token('edit', options['filename'])
|
70
|
+
))
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get image list for given article[s]. Follows redirects.
|
74
|
+
#
|
75
|
+
# _article_or_pageid_ is the title or pageid of a single article
|
76
|
+
# _imlimit_ is the maximum number of images to return (defaults to 200)
|
77
|
+
# _options_ is the hash of additional options
|
78
|
+
#
|
79
|
+
# Example:
|
80
|
+
# images = mw.images('Gaborone')
|
81
|
+
# _images_ would contain ['File:Gaborone at night.jpg', 'File:Gaborone2.png', ...]
|
82
|
+
def images(article_or_pageid, imlimit = 200, options = {})
|
83
|
+
form_data = options.merge(
|
84
|
+
'action' => 'query',
|
85
|
+
'prop' => 'images',
|
86
|
+
'imlimit' => imlimit,
|
87
|
+
'redirects' => true
|
88
|
+
)
|
89
|
+
|
90
|
+
form_data[article_or_pageid.is_a?(Fixnum) ?
|
91
|
+
'pageids' : 'titles'] = article_or_pageid
|
92
|
+
|
93
|
+
xml = send_request(form_data)
|
94
|
+
|
95
|
+
if valid_page?(page = xml.elements['query/pages/page'])
|
96
|
+
if xml.elements['query/redirects/r']
|
97
|
+
# We're dealing with redirect here.
|
98
|
+
images(page.attributes['pageid'].to_i, imlimit)
|
99
|
+
else
|
100
|
+
REXML::XPath.match(page, 'images/im').map { |x| x.attributes['title'] }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Requests image info from MediaWiki. Follows redirects.
|
106
|
+
#
|
107
|
+
# _file_name_or_page_id_ should be either:
|
108
|
+
# * a file name (String) you want info about without File: prefix.
|
109
|
+
# * or a Fixnum page id you of the file.
|
110
|
+
#
|
111
|
+
# _options_ is +Hash+ passed as query arguments. See
|
112
|
+
# http://www.mediawiki.org/wiki/API:Query_-_Properties#imageinfo_.2F_ii
|
113
|
+
# for more information.
|
114
|
+
#
|
115
|
+
# options['iiprop'] should be either a string of properties joined by
|
116
|
+
# '|' or an +Array+ (or more precisely something that responds to #join).
|
117
|
+
#
|
118
|
+
# +Hash+ like object is returned where keys are image properties.
|
119
|
+
#
|
120
|
+
# Example:
|
121
|
+
# mw.image_info(
|
122
|
+
# 'Trooper.jpg', 'iiprop' => ['timestamp', 'user']
|
123
|
+
# ).each do |key, value|
|
124
|
+
# puts "#{key.inspect} => #{value.inspect}"
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# Output:
|
128
|
+
# "timestamp" => "2009-10-31T12:59:11Z"
|
129
|
+
# "user" => "Valdas"
|
130
|
+
#
|
131
|
+
def image_info(file_name_or_page_id, options = {})
|
132
|
+
if options['iiprop'].respond_to?(:join)
|
133
|
+
options['iiprop'] = options['iiprop'].join('|')
|
134
|
+
end
|
135
|
+
|
136
|
+
form_data = options.merge(
|
137
|
+
'action' => 'query',
|
138
|
+
'prop' => 'imageinfo',
|
139
|
+
'redirects' => true
|
140
|
+
)
|
141
|
+
|
142
|
+
file_name_or_page_id.is_a?(Fixnum) ?
|
143
|
+
form_data['pageids'] = file_name_or_page_id :
|
144
|
+
form_data['titles'] = "File:#{file_name_or_page_id}"
|
145
|
+
|
146
|
+
xml = send_request(form_data)
|
147
|
+
|
148
|
+
if valid_page?(page = xml.elements['query/pages/page'])
|
149
|
+
if xml.elements['query/redirects/r']
|
150
|
+
# We're dealing with redirect here.
|
151
|
+
image_info(page.attributes['pageid'].to_i, options)
|
152
|
+
else
|
153
|
+
page.elements['imageinfo/ii'].attributes
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Download _file_name_ (without "File:" or "Image:" prefix). Returns file contents. All options are passed to
|
159
|
+
# #image_info however options['iiprop'] is forced to url. You can still
|
160
|
+
# set other options to control what file you want to download.
|
161
|
+
def download(file_name, options = {})
|
162
|
+
if attributes = image_info(file_name, options.merge('iiprop' => 'url'))
|
163
|
+
RestClient.get(attributes['url'])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
include Files
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
@@ -0,0 +1,400 @@
|
|
1
|
+
module MediaWiki
|
2
|
+
|
3
|
+
class Gateway
|
4
|
+
|
5
|
+
module Pages
|
6
|
+
|
7
|
+
# Fetch MediaWiki page in MediaWiki format. Does not follow redirects.
|
8
|
+
#
|
9
|
+
# [page_title] Page title to fetch
|
10
|
+
# [options] Hash of additional options
|
11
|
+
#
|
12
|
+
# Returns content of page as string, nil if the page does not exist.
|
13
|
+
def get(page_title, options = {})
|
14
|
+
page = send_request(options.merge(
|
15
|
+
'action' => 'query',
|
16
|
+
'prop' => 'revisions',
|
17
|
+
'rvprop' => 'content',
|
18
|
+
'titles' => page_title
|
19
|
+
)).elements['query/pages/page']
|
20
|
+
|
21
|
+
page.elements['revisions/rev'].text || '' if valid_page?(page)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Fetch latest revision ID of a MediaWiki page. Does not follow redirects.
|
25
|
+
#
|
26
|
+
# [page_title] Page title to fetch
|
27
|
+
# [options] Hash of additional options
|
28
|
+
#
|
29
|
+
# Returns revision ID as a string, nil if the page does not exist.
|
30
|
+
def revision(page_title, options = {})
|
31
|
+
page = send_request(options.merge(
|
32
|
+
'action' => 'query',
|
33
|
+
'prop' => 'revisions',
|
34
|
+
'rvprop' => 'ids',
|
35
|
+
'rvlimit' => 1,
|
36
|
+
'titles' => page_title
|
37
|
+
)).elements['query/pages/page']
|
38
|
+
|
39
|
+
page.elements['revisions/rev'].attributes['revid'] if valid_page?(page)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Render a MediaWiki page as HTML
|
43
|
+
#
|
44
|
+
# [page_title] Page title to fetch
|
45
|
+
# [options] Hash of additional options
|
46
|
+
#
|
47
|
+
# Options:
|
48
|
+
# * [:linkbase] supply a String to prefix all internal (relative) links with. '/wiki/' is assumed to be the base of a relative link
|
49
|
+
# * [:noeditsections] strips all edit-links if set to +true+
|
50
|
+
# * [:noimages] strips all +img+ tags from the rendered text if set to +true+
|
51
|
+
#
|
52
|
+
# Returns rendered page as string, or nil if the page does not exist
|
53
|
+
def render(page_title, options = {})
|
54
|
+
form_data = { 'action' => 'parse', 'page' => page_title }
|
55
|
+
|
56
|
+
validate_options(options, %w[linkbase noeditsections noimages])
|
57
|
+
|
58
|
+
rendered, parsed = nil, send_request(form_data).elements['parse']
|
59
|
+
|
60
|
+
if parsed.attributes['revid'] != '0'
|
61
|
+
rendered = parsed.elements['text'].text.gsub(/<!--(.|\s)*?-->/, '')
|
62
|
+
|
63
|
+
# OPTIMIZE: unifiy the keys in +options+ like symbolize_keys! but w/o
|
64
|
+
if linkbase = options['linkbase'] || options[:linkbase]
|
65
|
+
rendered = rendered.gsub(/\shref="\/wiki\/([\w\(\)\-\.%:,]*)"/, ' href="' + linkbase + '/wiki/\1"')
|
66
|
+
end
|
67
|
+
|
68
|
+
if options['noeditsections'] || options[:noeditsections]
|
69
|
+
rendered = rendered.gsub(/<span class="editsection">\[.+\]<\/span>/, '')
|
70
|
+
end
|
71
|
+
|
72
|
+
if options['noimages'] || options[:noimages]
|
73
|
+
rendered = rendered.gsub(/<img.*\/>/, '')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
rendered
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create a new page, or overwrite an existing one
|
81
|
+
#
|
82
|
+
# [title] Page title to create or overwrite, string
|
83
|
+
# [content] Content for the page, string
|
84
|
+
# [options] Hash of additional options
|
85
|
+
#
|
86
|
+
# Options:
|
87
|
+
# * [:overwrite] Allow overwriting existing pages
|
88
|
+
# * [:summary] Edit summary for history, string
|
89
|
+
# * [:token] Use this existing edit token instead requesting a new one (useful for bulk loads)
|
90
|
+
# * [:minor] Mark this edit as "minor" if true, mark this edit as "major" if false, leave major/minor status by default if not specified
|
91
|
+
# * [:notminor] Mark this edit as "major" if true
|
92
|
+
# * [:bot] Set the bot parameter (see http://www.mediawiki.org/wiki/API:Edit#Parameters). Defaults to false.
|
93
|
+
def create(title, content, options = {})
|
94
|
+
form_data = {
|
95
|
+
'action' => 'edit',
|
96
|
+
'title' => title,
|
97
|
+
'text' => content,
|
98
|
+
'summary' => options[:summary] || '',
|
99
|
+
'token' => get_token('edit', title)
|
100
|
+
}
|
101
|
+
|
102
|
+
if @options[:bot] || options[:bot]
|
103
|
+
form_data.update('bot' => '1', 'assert' => 'bot')
|
104
|
+
end
|
105
|
+
|
106
|
+
form_data['minor'] = '1' if options[:minor]
|
107
|
+
form_data['notminor'] = '1' if options[:minor] == false || options[:notminor]
|
108
|
+
form_data['createonly'] = '' unless options[:overwrite]
|
109
|
+
form_data['section'] = options[:section].to_s if options[:section]
|
110
|
+
|
111
|
+
send_request(form_data)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Edit page
|
115
|
+
#
|
116
|
+
# Same options as create, but always overwrites existing pages (and creates them if they don't exist already).
|
117
|
+
def edit(title, content, options = {})
|
118
|
+
create(title, content, { overwrite: true }.merge(options))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Protect/unprotect a page
|
122
|
+
#
|
123
|
+
# Arguments:
|
124
|
+
# * [title] Page title to protect, string
|
125
|
+
# * [protections] Protections to apply, hash or array of hashes
|
126
|
+
#
|
127
|
+
# Protections:
|
128
|
+
# * [:action] (required) The action to protect, string
|
129
|
+
# * [:group] (required) The group allowed to perform the action, string
|
130
|
+
# * [:expiry] The protection expiry as a GNU timestamp, string
|
131
|
+
#
|
132
|
+
# * [options] Hash of additional options
|
133
|
+
#
|
134
|
+
# Options:
|
135
|
+
# * [:cascade] Protect pages included in this page, boolean
|
136
|
+
# * [:reason] Reason for protection, string
|
137
|
+
#
|
138
|
+
# Examples:
|
139
|
+
# 1. mw.protect('Main Page', {:action => 'edit', :group => 'all'}, {:cascade => true})
|
140
|
+
# 2. prt = [{:action => 'move', :group => 'sysop', :expiry => 'never'},
|
141
|
+
# {:action => 'edit', :group => 'autoconfirmed', :expiry => 'next Monday 16:04:57'}]
|
142
|
+
# mw.protect('Main Page', prt, {:reason => 'awesomeness'})
|
143
|
+
#
|
144
|
+
def protect(title, protections, options = {})
|
145
|
+
case protections
|
146
|
+
when Array
|
147
|
+
# ok
|
148
|
+
when Hash
|
149
|
+
protections = [protections]
|
150
|
+
else
|
151
|
+
raise ArgumentError, "Invalid type '#{protections.class}' for protections"
|
152
|
+
end
|
153
|
+
|
154
|
+
valid_prt_options = %w[action group expiry]
|
155
|
+
required_prt_options = %w[action group]
|
156
|
+
|
157
|
+
p, e = [], []
|
158
|
+
|
159
|
+
protections.each { |prt|
|
160
|
+
existing_prt_options = []
|
161
|
+
|
162
|
+
prt.each_key { |opt|
|
163
|
+
if valid_prt_options.include?(opt.to_s)
|
164
|
+
existing_prt_options << opt.to_s
|
165
|
+
else
|
166
|
+
raise ArgumentError, "Unknown option '#{opt}' for protections"
|
167
|
+
end
|
168
|
+
}
|
169
|
+
|
170
|
+
required_prt_options.each { |opt|
|
171
|
+
unless existing_prt_options.include?(opt)
|
172
|
+
raise ArgumentError, "Missing required option '#{opt}' for protections"
|
173
|
+
end
|
174
|
+
}
|
175
|
+
|
176
|
+
p << "#{prt[:action]}=#{prt[:group]}"
|
177
|
+
e << (prt.key?(:expiry) ? prt[:expiry].to_s : 'never')
|
178
|
+
}
|
179
|
+
|
180
|
+
validate_options(options, %w[cascade reason])
|
181
|
+
|
182
|
+
form_data = {
|
183
|
+
'action' => 'protect',
|
184
|
+
'title' => title,
|
185
|
+
'token' => get_token('protect', title),
|
186
|
+
'protections' => p.join('|'),
|
187
|
+
'expiry' => e.join('|')
|
188
|
+
}
|
189
|
+
|
190
|
+
form_data['cascade'] = '' if options[:cascade] == true
|
191
|
+
form_data['reason'] = options[:reason].to_s if options[:reason]
|
192
|
+
|
193
|
+
send_request(form_data)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Move a page to a new title
|
197
|
+
#
|
198
|
+
# [from] Old page name
|
199
|
+
# [to] New page name
|
200
|
+
# [options] Hash of additional options
|
201
|
+
#
|
202
|
+
# Options:
|
203
|
+
# * [:movesubpages] Move associated subpages
|
204
|
+
# * [:movetalk] Move associated talkpages
|
205
|
+
# * [:noredirect] Do not create a redirect page from old name. Requires the 'suppressredirect' user right, otherwise MW will silently ignore the option and create the redirect anyway.
|
206
|
+
# * [:reason] Reason for move
|
207
|
+
# * [:watch] Add page and any redirect to watchlist
|
208
|
+
# * [:unwatch] Remove page and any redirect from watchlist
|
209
|
+
def move(from, to, options = {})
|
210
|
+
validate_options(options, %w[movesubpages movetalk noredirect reason watch unwatch])
|
211
|
+
|
212
|
+
send_request(options.merge(
|
213
|
+
'action' => 'move',
|
214
|
+
'from' => from,
|
215
|
+
'to' => to,
|
216
|
+
'token' => get_token('move', from)
|
217
|
+
))
|
218
|
+
end
|
219
|
+
|
220
|
+
# Delete one page. (MediaWiki API does not support deleting multiple pages at a time.)
|
221
|
+
#
|
222
|
+
# [title] Title of page to delete
|
223
|
+
# [options] Hash of additional options
|
224
|
+
def delete(title, options = {})
|
225
|
+
send_request(options.merge(
|
226
|
+
'action' => 'delete',
|
227
|
+
'title' => title,
|
228
|
+
'token' => get_token('delete', title)
|
229
|
+
))
|
230
|
+
end
|
231
|
+
|
232
|
+
# Undelete all revisions of one page.
|
233
|
+
#
|
234
|
+
# [title] Title of page to undelete
|
235
|
+
# [options] Hash of additional options
|
236
|
+
#
|
237
|
+
# Returns number of revisions undeleted, or zero if nothing to undelete
|
238
|
+
def undelete(title, options = {})
|
239
|
+
if token = get_undelete_token(title)
|
240
|
+
send_request(options.merge(
|
241
|
+
'action' => 'undelete',
|
242
|
+
'title' => title,
|
243
|
+
'token' => token
|
244
|
+
)).elements['undelete'].attributes['revisions'].to_i
|
245
|
+
else
|
246
|
+
0 # No revisions to undelete
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Get a list of matching page titles in a namespace
|
251
|
+
#
|
252
|
+
# [key] Search key, matched as a prefix (^key.*). May contain or equal a namespace, defaults to main (namespace 0) if none given.
|
253
|
+
# [options] Optional hash of additional options, eg. { 'apfilterredir' => 'nonredirects' }. See http://www.mediawiki.org/wiki/API:Allpages
|
254
|
+
#
|
255
|
+
# Returns array of page titles (empty if no matches)
|
256
|
+
def list(key, options = {})
|
257
|
+
key, namespace = key.split(':', 2).reverse
|
258
|
+
namespace = namespaces_by_prefix[namespace] || 0
|
259
|
+
|
260
|
+
iterate_query('allpages', '//p', 'title', 'apfrom', options.merge(
|
261
|
+
'list' => 'allpages',
|
262
|
+
'apprefix' => key,
|
263
|
+
'apnamespace' => namespace,
|
264
|
+
'aplimit' => @options[:limit]
|
265
|
+
))
|
266
|
+
end
|
267
|
+
|
268
|
+
# Get a list of pages that are members of a category
|
269
|
+
#
|
270
|
+
# [category] Name of the category
|
271
|
+
# [options] Optional hash of additional options. See http://www.mediawiki.org/wiki/API:Categorymembers
|
272
|
+
#
|
273
|
+
# Returns array of page titles (empty if no matches)
|
274
|
+
def category_members(category, options = {})
|
275
|
+
iterate_query('categorymembers', '//cm', 'title', 'cmcontinue', options.merge(
|
276
|
+
'cmtitle' => category,
|
277
|
+
'cmlimit' => @options[:limit]
|
278
|
+
))
|
279
|
+
end
|
280
|
+
|
281
|
+
# Get a list of pages that link to a target page
|
282
|
+
#
|
283
|
+
# [title] Link target page
|
284
|
+
# [filter] 'all' links (default), 'redirects' only, or 'nonredirects' (plain links only)
|
285
|
+
# [options] Hash of additional options
|
286
|
+
#
|
287
|
+
# Returns array of page titles (empty if no matches)
|
288
|
+
def backlinks(title, filter = 'all', options = {})
|
289
|
+
iterate_query('backlinks', '//bl', 'title', 'blcontinue', options.merge(
|
290
|
+
'bltitle' => title,
|
291
|
+
'blfilterredir' => filter,
|
292
|
+
'bllimit' => @options[:limit]
|
293
|
+
))
|
294
|
+
end
|
295
|
+
|
296
|
+
# Checks if page is a redirect.
|
297
|
+
#
|
298
|
+
# [page_title] Page title to fetch
|
299
|
+
#
|
300
|
+
# Returns true if the page is a redirect, false if it is not or the page does not exist.
|
301
|
+
def redirect?(page_title)
|
302
|
+
page = send_request(
|
303
|
+
'action' => 'query',
|
304
|
+
'prop' => 'info',
|
305
|
+
'titles' => page_title
|
306
|
+
).elements['query/pages/page']
|
307
|
+
|
308
|
+
!!(valid_page?(page) && page.attributes['redirect'])
|
309
|
+
end
|
310
|
+
|
311
|
+
# Get list of interlanguage links for given article[s]. Follows redirects. Returns a hash like { 'id' => 'Yerusalem', 'en' => 'Jerusalem', ... }
|
312
|
+
#
|
313
|
+
# _article_or_pageid_ is the title or pageid of a single article
|
314
|
+
# _lllimit_ is the maximum number of langlinks to return (defaults to 500, the maximum)
|
315
|
+
# _options_ is the hash of additional options
|
316
|
+
#
|
317
|
+
# Example:
|
318
|
+
# langlinks = mw.langlinks('Jerusalem')
|
319
|
+
def langlinks(article_or_pageid, lllimit = 500, options = {})
|
320
|
+
form_data = options.merge(
|
321
|
+
'action' => 'query',
|
322
|
+
'prop' => 'langlinks',
|
323
|
+
'lllimit' => lllimit,
|
324
|
+
'redirects' => true
|
325
|
+
)
|
326
|
+
|
327
|
+
form_data[article_or_pageid.is_a?(Fixnum) ?
|
328
|
+
'pageids' : 'titles'] = article_or_pageid
|
329
|
+
|
330
|
+
xml = send_request(form_data)
|
331
|
+
|
332
|
+
if valid_page?(page = xml.elements['query/pages/page'])
|
333
|
+
if xml.elements['query/redirects/r']
|
334
|
+
# We're dealing with the redirect here.
|
335
|
+
langlinks(page.attributes['pageid'].to_i, lllimit)
|
336
|
+
elsif langl = REXML::XPath.match(page, 'langlinks/ll')
|
337
|
+
langl.each_with_object({}) { |ll, links|
|
338
|
+
links[ll.attributes['lang']] = ll.children[0].to_s
|
339
|
+
}
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# Convenience wrapper for _langlinks_ returning the title in language _lang_ (ISO code) for a given article of pageid, if it exists, via the interlanguage link
|
345
|
+
#
|
346
|
+
# Example:
|
347
|
+
#
|
348
|
+
# langlink = mw.langlink_for_lang('Tycho Brahe', 'de')
|
349
|
+
def langlink_for_lang(article_or_pageid, lang)
|
350
|
+
langlinks(article_or_pageid)[lang]
|
351
|
+
end
|
352
|
+
|
353
|
+
# Review current revision of an article (requires FlaggedRevisions extension, see http://www.mediawiki.org/wiki/Extension:FlaggedRevs)
|
354
|
+
#
|
355
|
+
# [title] Title of article to review
|
356
|
+
# [flags] Hash of flags and values to set, eg. { 'accuracy' => '1', 'depth' => '2' }
|
357
|
+
# [comment] Comment to add to review (optional)
|
358
|
+
# [options] Hash of additional options
|
359
|
+
def review(title, flags, comment = 'Reviewed by MediaWiki::Gateway', options = {})
|
360
|
+
raise APIError.new('missingtitle', "Article #{title} not found") unless revid = revision(title)
|
361
|
+
|
362
|
+
form_data = options.merge(
|
363
|
+
'action' => 'review',
|
364
|
+
'revid' => revid,
|
365
|
+
'token' => get_token('edit', title),
|
366
|
+
'comment' => comment
|
367
|
+
)
|
368
|
+
|
369
|
+
flags.each { |k, v| form_data["flag_#{k}"] = v }
|
370
|
+
|
371
|
+
send_request(form_data)
|
372
|
+
end
|
373
|
+
|
374
|
+
private
|
375
|
+
|
376
|
+
def get_undelete_token(page_titles)
|
377
|
+
res = send_request(
|
378
|
+
'action' => 'query',
|
379
|
+
'list' => 'deletedrevs',
|
380
|
+
'prop' => 'info',
|
381
|
+
'drprop' => 'token',
|
382
|
+
'titles' => page_titles
|
383
|
+
)
|
384
|
+
|
385
|
+
if res.elements['query/deletedrevs/page']
|
386
|
+
unless token = res.elements['query/deletedrevs/page'].attributes['token']
|
387
|
+
raise Unauthorized.new("User is not permitted to perform this operation: #{type}")
|
388
|
+
end
|
389
|
+
|
390
|
+
token
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
395
|
+
|
396
|
+
include Pages
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|