aboutsummaryrefslogtreecommitdiff
path: root/mediawiki.lrdevplugin/MediaWikiApi.lua
blob: 96c3de0e38d6aff1c761aa75b077cfa7048c6918 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
-- This file is part of the LrMediaWiki project and distributed under the terms
-- of the MIT license (see LICENSE.txt file in the project root directory or
-- [0]).  See [1] for more information about LrMediaWiki.
--
-- Copyright (C) 2014 by the LrMediaWiki team (see CREDITS.txt file in the
-- project root directory or [2])
--
-- [0]  <https://raw.githubusercontent.com/ireas/LrMediaWiki/master/LICENSE.txt>
-- [1]  <https://commons.wikimedia.org/wiki/Commons:LrMediaWiki>
-- [2]  <https://raw.githubusercontent.com/ireas/LrMediaWiki/master/CREDITS.txt>

-- Code status:
-- doc:   partly
-- i18n:  complete

local LrErrors = import 'LrErrors'
local LrHttp = import 'LrHttp'
local LrPathUtils = import 'LrPathUtils'
local LrXml = import 'LrXml'

local JSON = require 'JSON'
local Info = require 'Info'
local MediaWikiUtils = require 'MediaWikiUtils'

local MediaWikiApi = {
	userAgent = string.format('LrMediaWiki %d.%d', Info.VERSION.major, Info.VERSION.minor),
	apiPath = nil,
	githubApiVersion = 'https://api.github.com/repos/robinkrahl/LrMediaWiki/releases',
}

--- URL-encode a string according to RFC 3986.
-- Based on http://lua-users.org/wiki/StringRecipes
-- @param str the string to encode
-- @return the URL-encoded string
function MediaWikiApi.urlEncode(str)
	if str then
		str = string.gsub(str, '\n', '\r\n')
		str = string.gsub (str, '([^%w %-%_%.%~])',
			function(c) return string.format('%%%02X', string.byte(c)) end)
		str = string.gsub(str, ' ', '+')
	end
	return str
end

--- Convert HTTP arguments to a URL-encoded request body.
-- @param arguments (table) the arguments to convert
-- @return (string) a request body created from the URL-encoded arguments
function MediaWikiApi.createRequestBody(arguments)
	local body = nil
	for key, value in pairs(arguments) do
		if body then
			body = body .. '&'
		else
			body = ''
		end
		body = body .. MediaWikiApi.urlEncode(key) .. '=' .. MediaWikiApi.urlEncode(value)
	end
	return body or ''
end

function MediaWikiApi.parseXmlDom(xmlDomInstance)
	local value = nil
	if xmlDomInstance:type() == 'element' then
		value = {}
		for key, attribute in pairs(xmlDomInstance:attributes()) do
			value[key] = attribute.value
		end
		for i = 1, xmlDomInstance:childCount() do
			local child = xmlDomInstance:childAtIndex(i)
			local childName = child:name()
			if childName then
				value[childName] = MediaWikiApi.parseXmlDom(child)
			end
		end
	elseif xmlDomInstance:type() == 'text' then
		value = xmlDomInstance:text()
	end
	return value
end

function MediaWikiApi.performHttpRequest(path, arguments, requestHeaders, post)
	local requestBody = MediaWikiApi.createRequestBody(arguments)

	MediaWikiUtils.trace('Performing HTTP request');
	MediaWikiUtils.trace('Path:')
	MediaWikiUtils.trace(path)
	MediaWikiUtils.trace('Request body:');
	MediaWikiUtils.trace(requestBody);

	local resultBody, resultHeaders
	if post then
		resultBody, resultHeaders = LrHttp.post(path, requestBody, requestHeaders)
	else
		resultBody, resultHeaders = LrHttp.get(path .. '?' .. requestBody, requestHeaders)
	end

	MediaWikiUtils.trace('Result status:');
	MediaWikiUtils.trace(resultHeaders.status);

	if not resultHeaders.status then
		LrErrors.throwUserError(LOC('$$$/LrMediaWiki/Api/NoConnection=No network connection.'))
	elseif resultHeaders.status ~= 200 then
		LrErrors.throwUserError(LOC('$$$/LrMediaWiki/Api/HttpError=Received HTTP status ^1.', resultHeaders.status))
	end

	MediaWikiUtils.trace('Result body:');
	MediaWikiUtils.trace(resultBody);

	return resultBody
end

function MediaWikiApi.performRequest(arguments)
	arguments.format = 'xml'
	local requestHeaders = {
		{
			field = 'User-Agent',
			value = MediaWikiApi.userAgent,
		},
		{
			field = 'Content-Type',
			value = 'application/x-www-form-urlencoded',
		},
	}

	local resultBody = MediaWikiApi.performHttpRequest(MediaWikiApi.apiPath, arguments, requestHeaders, true)
	local resultXml = MediaWikiApi.parseXmlDom(LrXml.parseXml(resultBody))
	if resultXml.error then
		LrErrors.throwUserError(LOC('$$$/LrMediaWiki/Api/MediaWikiError=The MediaWiki error ^1 occured: ^2', resultXml.error.code, resultXml.error.info))
	end
	return resultXml
end

function MediaWikiApi.getCurrentPluginVersion()
	local requestHeaders = {
		{
			field = 'User-Agent',
			value = MediaWikiApi.userAgent,
		},
	}
	local resultBody = MediaWikiApi.performHttpRequest(MediaWikiApi.githubApiVersion, {}, requestHeaders, false)
	local resultJson = JSON:decode(resultBody)
	local firstKey = MediaWikiUtils.getFirstKey(resultJson)
	if firstKey ~= nil then
		return resultJson[firstKey].tag_name
	end
	return nil
end

function MediaWikiApi.login(username, password, token)
	local arguments = {
		action = 'login',
		lgname = username,
		lgpassword = password,
	}
	if token then
		arguments.lgtoken = token
	end
	local xml = MediaWikiApi.performRequest(arguments)

	local loginResult = xml.login.result
	if loginResult == 'Success' then
		return true
	elseif not token and loginResult == 'NeedToken' then
		return MediaWikiApi.login(username, password, xml.login.token)
	end

	return loginResult
end

function MediaWikiApi.getEditToken()
	local arguments = {
		action = 'tokens',
	}
	local xml = MediaWikiApi.performRequest(arguments)
	return xml.tokens.edittoken
end

function MediaWikiApi.appendToPage(page, section, text, comment)
	local arguments = {
		action = 'edit',
		title = page,
		section = 'new',
		sectiontitle = section,
		text = text,
		summary = comment,
		token = MediaWikiApi.getEditToken(),
	}
	MediaWikiApi.performRequest(arguments)
end

function MediaWikiApi.existsFile(fileName)
	local arguments = {
		action = 'query',
		titles = 'File:' .. fileName,
	}
	local xml = MediaWikiApi.performRequest(arguments)
	return xml.query and xml.query.pages and xml.query.pages.page and not xml.query.pages.page.missing
end

function MediaWikiApi.upload(fileName, sourceFilePath, text, comment, ignoreWarnings)
	local sourceFileName = LrPathUtils.leafName(sourceFilePath)

	local arguments = {
		action = 'upload',
		filename = fileName,
		text = text,
		comment = comment,
		token = MediaWikiApi.getEditToken(),
		format = 'xml',
	}
	if ignoreWarnings then
		arguments.ignorewarnings = 'true'
	end
	local requestHeaders = {
		{
			field = 'User-Agent',
			value = MediaWikiApi.userAgent,
		},
	}
	local requestBody = {}
	for key, value in pairs(arguments) do
		requestBody[#requestBody + 1] = {
			name = key,
			value = value,
		}
	end
	requestBody[#requestBody + 1] = {
		name = 'file',
		fileName = sourceFileName,
		filePath = sourceFilePath,
		contentType = 'application/octet-stream',
	}

	local resultBody, resultHeaders = LrHttp.postMultipart(MediaWikiApi.apiPath, requestBody, requestHeaders)

	if resultHeaders.status ~= 200 then
		LrErrors.throwUserError(LOC('$$$/LrMediaWiki/Api/HttpError=Received HTTP status ^1.', resultHeaders.status))
	end

	local resultXml = MediaWikiApi.parseXmlDom(LrXml.parseXml(resultBody))
	if resultXml.error then
		LrErrors.throwUserError(LOC('$$$/LrMediaWiki/Api/MediaWikiError=The MediaWiki error ^1 occured: ^2', resultXml.error.code, resultXml.error.info))
	end

	local uploadResult = resultXml.upload.result
	if uploadResult == 'Success' then
		return true
	elseif uploadResult == 'Warning' then
		local warnings = ''
		-- concatenate the keys of the warnings table (= MediaWiki name of the warning)
		for warning in pairs(resultXml.upload.warnings) do
			if warnings ~= '' then
				warnings = warnings .. ', '
			end
			warnings = warnings .. warning
		end
    return warnings
	else
		return uploadResult
	end
end

return MediaWikiApi