mediawiki-gateway 0.6.2 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|