From 1be85257f32b33f5abb6bd22657e0aee0008653d Mon Sep 17 00:00:00 2001 From: "trougnouf (Benoit Brummer)" Date: Sat, 16 Mar 2019 17:05:53 +0100 Subject: [PATCH 1/7] Copy dtMediaWiki v44 from trougnouf/dtMediaWiki --- contrib/dtMediaWiki/README.md | 52 ++++ contrib/dtMediaWiki/dtMediaWiki.lua | 396 +++++++++++++++++++++++++++ contrib/dtMediaWiki/mediawikiapi.lua | 314 +++++++++++++++++++++ 3 files changed, 762 insertions(+) create mode 100644 contrib/dtMediaWiki/README.md create mode 100644 contrib/dtMediaWiki/dtMediaWiki.lua create mode 100644 contrib/dtMediaWiki/mediawikiapi.lua diff --git a/contrib/dtMediaWiki/README.md b/contrib/dtMediaWiki/README.md new file mode 100644 index 00000000..fef021e6 --- /dev/null +++ b/contrib/dtMediaWiki/README.md @@ -0,0 +1,52 @@ +# dtMediaWiki + +Wikimedia Commons export plugin for [darktable](https://www.darktable.org/) + +See also: [Commons:DtMediaWiki](https://commons.wikimedia.org/wiki/Commons:DtMediaWiki) + +## Dependencies + +- [lua-sec](https://luarocks.org/modules/brunoos/luasec) + - Lua bindings for OpenSSL library to provide TLS/SSL communication +- [lua-luajson](https://luarocks.org/modules/harningt/luajson) + - JSON parser/encoder for Lua +- [lua-multipart-post](https://luarocks.org/modules/catwell/multipart-post) + - HTTP Multipart Post helper + +Note that `mediawikiapi.lua` is independent of darktable. + +## Installation + +- Download the plugin from [https://github.com/trougnouf/dtMediaWiki/archive/master.zip](https://github.com/trougnouf/dtMediaWiki/archive/master.zip) +- Create the [darktable plugin directory](https://www.darktable.org/usermanual/en/lua_chapter.html#lua_usage) if it doesn't exist + - `# mkdir /usr/share/darktable/lua/contrib` +- Copy (or link) the dtMediaWiki directory over there + - `# cp -r /path/to/dtMediaWiki /usr/share/darktable/lua/contrib` +- Activate the plugin in your darktable luarc config file by adding `require "contrib/dtMediaWiki/dtMediaWiki"` + - `$ echo 'require "contrib/dtMediaWiki/dtMediaWiki"' >> ~/.config/darktable/luarc` + +… or simply use the [Arch Linux package](https://aur.archlinux.org/packages/darktable-plugin-dtmediawiki-git/) and activate the plugin. + +## Usage + +- Login to Wikimedia Commons by setting your "Wikimedia username" and "Wikimedia password" in _[darktable preferences](https://www.darktable.org/usermanual/en/preferences_chapter.html) > lua options_ then restarting darktable. + - This will add the "Wikimedia Commons" entry into target storage. +- Ensure your image contains the following [metadata](https://www.darktable.org/usermanual/en/metadata_editor.html) and [tags](https://www.darktable.org/usermanual/en/tagging.html): + - **title** and/or **description** – The default output filename is `title (filename) description.ext` or `title (filename).ext` depending on what is available + - **rights** – Use something compatible with the [`{{self}}`](https://commons.wikimedia.org/wiki/Template:Self) template, some options are [`cc-by-sa-4.0`](https://commons.wikimedia.org/wiki/Template:Cc-by-sa-4.0), [`cc-by-4.0`](https://commons.wikimedia.org/wiki/Template:Cc-by-4.0), [`GFDL`](https://commons.wikimedia.org/wiki/Template:GFDL), see [Commons:Copyright tags](https://commons.wikimedia.org/wiki/Commons:Copyright_tags) + - **tags** – Categories and templates. Any tag that matches `Category:something` will be added as `[[Category:something]]` (no need to include the brackets), likewise any template matching `{{something}}` will be added as-is. + +The image coordinates will be added if they exist, and the creator metadata will be added as `[[User:Wikimedia username|creator]]` if it has been set. + +## Thanks + +- Iulia and Leslie for excellent coworking companionship and love +- darktable developers for an excellent open-source imaging software with a well documented [Lua API](https://www.darktable.org/lua-api/) +- [LrMediaWiki](https://github.com/Hasenlaeufer/LrMediaWiki) developers [robinkrahl](https://github.com/robinkrahl) and [Hasenlaeufer](https://github.com/Hasenlaeufer) for what inspired this and some base code +- MediaWiki [User:Platonides](https://www.mediawiki.org/wiki/User:Platonides) for helping me figure out the cookie issue +- [catwell](https://github.com/catwell): author of lua-multipart-post and a responsive fellow +- [simon04](https://github.com/simon04): second user and first contributor + +![:)](https://upload.wikimedia.org/wikipedia/commons/3/30/Binette-typo.png) + +--[Trougnouf](https://commons.wikimedia.org/wiki/User:Trougnouf) diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua new file mode 100644 index 00000000..6760666a --- /dev/null +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -0,0 +1,396 @@ +--[[dtMediaWiki is a darktable plugin which exports images to Wikimedia Commons + Author: Trougnouf (Benoit Brummer) + Contributor: Simon Legner (simon04) + +Dependencies: +* lua-sec: Lua bindings for OpenSSL library to provide TLS/SSL communication +* lua-luajson: JSON parser/encoder for Lua +* lua-multipart-post: HTTP Multipart Post helper +]] +local dt = require "darktable" +local MediaWikiApi = require "contrib/dtMediaWiki/mediawikiapi" +local version = 44 + +--[[The version number is generated by .git/hooks/pre-commit (+x) + with the following content: + #!/bin/sh + NEWVERSION="$(expr "$(git log master --pretty=oneline | wc -l)" + 1)" + sed -i -r "s/local version = [0-9]+/local version = ${NEWVERSION}/1" dtMediaWiki.lua + git add dtMediaWiki.lua + ]] +-- Preference entries +local preferences_prefix = "mediawiki" +dt.preferences.register( + preferences_prefix, + "username", + "string", + "Wikimedia username", + "Wikimedia Commons username", + "" +) +dt.preferences.register( + preferences_prefix, + "password", + "string", + "Wikimedia password", + "Wikimedia Commons password (to be stored in plain-text!)", + "" +) +dt.preferences.register( + preferences_prefix, + "overwrite", + "bool", + "Commons: Overwrite existing images?", + "Existing images will be overwritten without confirmation, otherwise the upload will fail.", + false +) +dt.preferences.register( + preferences_prefix, + "cat_cam", + "bool", + "Commons: Categorize camera?", + "A category will be added with the camera information " .. + "(eg: [[Category:Taken with Fujifilm X-E2 and XF18-55mmF2.8-4 R LM OIS]])", + false +) + +dt.preferences.register( + preferences_prefix, + "desc_templates", + "string", + "Commons: Templates to be placed in {{Information |description= ...}}", + 'These templates are placed in the {{Information |description= ...}} field. (comma-separated)', + "Description,Depicted person,en,de,fr,es,ja,ru,zh,it,pt,ar" +) + +local namepattern_default = "$TITLE ($FILE_NAME)" + +local namepattern_widget = + dt.new_widget("entry") { + tooltip = table.concat( + { + "Determines the `File:` page name", + "recognized variables:", + "$FILE_NAME - basename of the input image", + "$TITLE - title from metadata", + "$DESCRIPTION - description from metadata", + "Note that $TITLE or $DESCRIPTION is required, and if both are chosen but only one is available " .. + "then the fallback name will be `$TITLE$DESCRIPTION ($FILE_NAME)`" + }, + "\n" + ), + text = dt.preferences.read(preferences_prefix, "namepattern", "string"), + reset_callback = function(self) + self.text = namepattern_default + dt.preferences.write(preferences_prefix, "namepattern", "string", self.text) + end +} + +-- language widget shown in lighttable export +local language_widget = + dt.new_widget("entry") { + text = "en", + tooltip = "Description language code. Additional descriptions may be added with tag " + .. "{{Description|language_code|description_text}} or any of the templates listed in " + .. "the desc_template setting.", + reset_callback = function(self) + self.text = "en" + end +} + +dt.preferences.register( + preferences_prefix, + "authorpattern", + "string", + "Commons: Preferred author pattern", + "Determines the author value; variables are $USERNAME, $CREATOR", + "[[User:$USERNAME|$CREATOR]]" +) +dt.preferences.register( + preferences_prefix, + "titleindesc", + "bool", + "Commons: Use title in description", + "Use the title in description if both are available: description={{en|1=$TITLE: $DESCRIPTION}}", + false +) + +local function msgout(txt) + print(txt) + dt.print(txt) +end + +-- Generate image name +local function make_image_name(image, tmp_exp_path) + local basename = image.filename:match "[^.]+" + local outname = namepattern_widget.text or namepattern_default + dt.preferences.write(preferences_prefix, "namepattern", "string", outname) + local presdata = image.title .. image.description + if image.title ~= "" and image.description ~= "" then --2 items available + outname = outname:gsub("$TITLE", image.title) + outname = outname:gsub("$FILE_NAME", basename) + outname = outname:gsub("$DESCRIPTION", image.description) + elseif outname:find("$TITLE") and outname:find("$DESCRIPTION") then + outname = presdata .. " (" .. basename .. ")" + else + outname = outname:gsub("$TITLE", presdata) + outname = outname:gsub("$FILE_NAME", basename) + outname = outname:gsub("$DESCRIPTION", presdata) + end + local ext = tmp_exp_path:match "[^.]+$" + return outname .. "." .. ext +end + +-- Round to 1 decimal, remove useless .0's and convert number to string +local function fmt_flt(num) + num = math.floor(num * 10 + .5) / 10 + if string.sub(num, -2) == ".0" then + return string.sub(num, 1, -3) + else + return tostring(num) + end +end + +-- Get description field from the description (and optionally title) metadata +local function get_description(image) + local titleindesc = dt.preferences.read(preferences_prefix, "titleindesc", "bool") + if titleindesc and image.description ~= "" and image.title ~= "" then + return image.title .. ": " .. image.description + elseif image.description ~= "" then + return image.description + else + return image.title + end +end + +local function split(astring) -- helper from http://lua-users.org/wiki/SplitJoin, doesn't work with local (?) + local sep, fields = ",", {} + local pattern = string.format("([^%s]+)", sep) + astring:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +-- get description templates which need to be added to {{Information| description=...}} +local function get_intl_descriptions(image, discarded_tags) + local desc_templates = split(dt.preferences.read(preferences_prefix, "desc_templates", "string")) + local intl_descriptions = "" + for _, tag in pairs(dt.tags.get_tags(image)) do + tag = tag.name + for _,dtemplate in pairs(desc_templates) do + if tag:sub(1,#dtemplate+3) == '{{' .. dtemplate .. '|' then + intl_descriptions = intl_descriptions .. tag + discarded_tags[tag] = true + end + end + end + return intl_descriptions +end + + +-- Get other fields that are then added to the Information template (TODO document this feature) +local function get_other_fields(image, discarded_tags) + local other_fields = '' + for _, tag in pairs(dt.tags.get_tags(image)) do + tag = tag.name + if string.sub(tag, 0, 20) == '{{Information field|' + or string.sub(tag, 0, 7) == '{{InFi|' then + other_fields = other_fields .. tag + discarded_tags[tag] = true + end + end + return other_fields +end + +local function substitute_keywords(string, image) + local username = dt.preferences.read(preferences_prefix, "username", "string") + string = string:gsub("$USERNAME", username) + string = string:gsub("$CREATOR", image.creator or username) + string = string:gsub("$FILE_NAME", image.filename) + string = string:gsub("$DATETIME", image.exif_datetime_taken) + return string +end + +local function get_alt_images(image, tmp_img_fn) + local alts = {} + for _, tag in pairs(dt.tags.get_tags(image)) do + if string.sub(tag.name, 1, 4) == "alt:" then + table.insert(alts, string.sub(tag.name, 5)) + end + end + if next(alts) == nil then + return "" + end + local altcode = {""} + for _, img in ipairs(dt.collection) do + for _,altimg in pairs(alts) do + if string.find(img.filename, altimg) then + table.insert(altcode, make_image_name(img, tmp_img_fn)) + end + end + end + table.insert(altcode, '') + return table.concat(altcode, "\n") +end + +-- Generate an image page with all required info from tags, metadata, and such. +local function make_image_page(image, tmp_img_fn) + local discarded_tags = {} + local imgpg = {"=={{int:filedesc}}==\n{{Information"} + table.insert(imgpg, "|description={{" .. language_widget.text .. "|1=" + .. substitute_keywords(get_description(image), image) .. "}}" + .. get_intl_descriptions(image, discarded_tags)) + local date = image.exif_datetime_taken + date = date:gsub("(%d%d%d%d):(%d%d):(%d%d)", "%1-%2-%3") -- format date in ISO 8601 / RFC 3339 + table.insert(imgpg, "|date=" .. date) + table.insert(imgpg, "|source={{own}}") + local author = dt.preferences.read(preferences_prefix, "authorpattern", "string") + author = substitute_keywords(author, image) + table.insert(imgpg, "|author=" .. author) + table.insert(imgpg, '|other fields = ' .. get_other_fields(image, discarded_tags)) + table.insert(imgpg, '|other versions = ' .. get_alt_images(image, tmp_img_fn)) + table.insert(imgpg, "}}") + if image.latitude ~= nil and image.longitude ~= nil then + table.insert(imgpg, "{{Location |1=" .. image.latitude .. " |2=" .. image.longitude .. " }}") + end + table.insert(imgpg, "=={{int:license-header}}==") + table.insert(imgpg, "{{self|" .. image.rights .. "}}") + for _, tag in pairs(dt.tags.get_tags(image)) do + tag = substitute_keywords(tag.name, image) + if string.sub(tag, 1, 9) == "Category:" then + table.insert(imgpg, "[[" .. tag .. "]]") + elseif tag:sub(1, 2) == "{{" and not discarded_tags[tag] then + table.insert(imgpg, tag) + end + end + if dt.preferences.read(preferences_prefix, "cat_cam", "bool") then + print("catcam enabled") --dbg + if image.exif_model ~= "" then + local model = image.exif_maker:sub(1, 1) .. image.exif_maker:sub(2):lower() + local catcam = "[[Category:Taken with " .. model .. " " .. image.exif_model + if image.exif_lens ~= "" then + catcam = catcam .. " and " .. image.exif_lens .. "]]" + else + catcam = catcam .. "]]" + end + table.insert(imgpg, catcam) + end + if image.exif_aperture then + table.insert(imgpg, "[[Category:F-number f/" .. fmt_flt(image.exif_aperture) .. "]]") + end + if image.exif_focal_length ~= "" then + table.insert(imgpg, "[[Category:Lens focal length " .. fmt_flt(image.exif_focal_length) .. " mm]]") + end + if image.exif_iso ~= "" then + table.insert(imgpg, "[[Category:ISO speed rating " .. fmt_flt(image.exif_iso) .. "]]") + end + -- if image.exif_exposure ~= "" then + -- table.insert(imgpg, "[[Category:Exposure time "..image.exif_exposure.." sec]]") + -- end -- decimal instead of fraction (TODO) + end + table.insert(imgpg, "[[Category:Uploaded with dtMediaWiki]]") + imgpg = table.concat(imgpg, "\n") + return imgpg +end + +-- comment widget shown in lighttable export +local comment_widget = + dt.new_widget("entry") { + text = "Uploaded with dtMediaWiki " .. version, + reset_callback = function(self) + self.text = "Uploaded with dtMediaWiki " .. version + end +} + +--This function is called once for each exported image +local function register_storage_store(_, image, _, tmp_exp_path, _, _, _, _) + local imagepage = make_image_page(image, tmp_exp_path) + local imagename = make_image_name(image, tmp_exp_path) + --print(imagepage) + MediaWikiApi.uploadfile( + tmp_exp_path, + imagepage, + imagename, + dt.preferences.read(preferences_prefix, "overwrite", "bool"), + comment_widget.text + ) + msgout("exported " .. imagename) -- that is the path also +end + +--This function is called once all images are processed and all store calls are finished. +local function register_storage_finalize(_, image_table, extra_data) + local fcnt = 0 + for _ in pairs(image_table) do + fcnt = fcnt + 1 + end + msgout("exported " .. fcnt .. "/" .. extra_data["init_img_cnt"] .. " images to Wikimedia Commons") +end + +--A function called to check if a given image format is supported by the Lua storage; +--This is used to build the dropdown format list for the GUI. +local function register_storage_supported(_, format) + local ext = format.extension + return ext == "jpg" or ext == "png" or ext == "tif" or ext == "webp" +end + +--A function called before storage happens +--This function can change the list of exported functions +local function register_storage_initialize(_, _, images, _, extra_data) + local out_images = {} + for _, img in pairs(images) do + if img.rights == "" then + --TODO check allowed formats + msgout("Error: " .. img.path .. " has no rights, cannot be exported to Wikimedia Commons") + elseif img.title == "" and img.description == "" then + msgout( + "Error: " .. + img.path .. " is missing a meaningful title and/or description, " .. "won't be exported to Wikimedia Commons" + ) + else + table.insert(out_images, img) + end + end + extra_data["init_img_cnt"] = #images + return out_images +end + +-- widgets shown in lighttable +local export_widgets = + dt.new_widget("box") { + dt.new_widget("box") { + orientation = "horizontal", + dt.new_widget("label") {label = "Naming pattern:"}, + namepattern_widget + }, + dt.new_widget("box") { + orientation = "horizontal", + dt.new_widget("label") {label = "Comment:"}, + comment_widget + }, + dt.new_widget("box") { + orientation = "horizontal", + dt.new_widget("label") {label = "Language code:", + tooltip = "Description language code (eg: en, fr, ...)." + .. "More descriptions can be entered in the tags (eg: {{fr|Une description}}"}, + language_widget + } +} + +-- Darktable target storage entry +if + MediaWikiApi.login( + dt.preferences.read(preferences_prefix, "username", "string"), + dt.preferences.read(preferences_prefix, "password", "string") + ) + then + dt.register_storage( + "mediawiki", + "Wikimedia Commons", + register_storage_store, + register_storage_finalize, + register_storage_supported, + register_storage_initialize, + export_widgets + ) +else + msgout("Unable to log into Wikimedia Commons, export disabled.") +end diff --git a/contrib/dtMediaWiki/mediawikiapi.lua b/contrib/dtMediaWiki/mediawikiapi.lua new file mode 100644 index 00000000..bcc3ee60 --- /dev/null +++ b/contrib/dtMediaWiki/mediawikiapi.lua @@ -0,0 +1,314 @@ +--[[ +Author: Trougnouf (Benoit Brummer) +Contributor: Simon Legner (simon04) + +mediawikiapi.lua uses some code adapted from LrMediaWiki +LrMediaWiki authors: +Robin Krahl +Eckhard Henkel + +Dependencies: +* lua-sec: Lua bindings for OpenSSL library to provide TLS/SSL communication +* lua-luajson: JSON parser/encoder for Lua +* lua-multipart-post: HTTP Multipart Post helper + (darktable is not a dependency) +]] +package.path = package.path .. ";/dtMediaWiki/?.lua" +package.path = package.path .. ";/usr/share/darktable/lua/contrib/dtMediaWiki/?.lua" +local https = require "ssl.https" +local json = require "json" +local ltn12 = require "ltn12" +local mpost = require "multipart-post" + +local MediaWikiApi = { + userAgent = string.format("mediawikilua %d.%d", 0, 1), + apiPath = "https://commons.wikimedia.org/w/api.php", + cookie = {}, + edit_token = nil +} + +local function httpsget(url, reqheaders) + local res, code, resheaders, _ = + https.request { + url = url, + headers = reqheaders + } + resheaders.status = code + + return res, resheaders +end + +local function httpspost(url, postBody, reqheaders) + local res = {} + local _, code, resheaders, _ = + https.request { + url = url, + method = "POST", + headers = reqheaders, + source = ltn12.source.string(postBody), + sink = ltn12.sink.table(res) + } + resheaders.status = code + + return table.concat(res), resheaders +end + +local function throwUserError(text) + print(text) +end + +-- parse a received cookie and update MediaWikiApi.cookie +function MediaWikiApi.parseCookie(unparsedcookie) + while unparsedcookie and string.len(unparsedcookie) > 0 do + local i = string.find(unparsedcookie, ";") + local crumb = string.sub(unparsedcookie, 1, i - 1) + local isep = string.find(crumb, "=") + if isep then + local cvar = string.sub(crumb, 1, isep - 1) + local icvarcomma = string.find(cvar, ",") + while icvarcomma do + cvar = string.sub(cvar, icvarcomma + 2) + icvarcomma = string.find(cvar, ",") + end + MediaWikiApi.cookie[cvar] = string.sub(crumb, isep + 1) + end + local nexti = string.find(unparsedcookie, ",") + if not nexti then + return + end + unparsedcookie = string.sub(unparsedcookie, nexti + 2) + end +end + +-- generate a cookie string from MediaWikiApi.cookie to send to server +function MediaWikiApi.cookie2string() + local prestr = {} + for cvar, cval in pairs(MediaWikiApi.cookie) do + table.insert(prestr, cvar .. "=" .. cval .. ";") + end + return table.concat(prestr) +end + +-- Demand an edit token. probably can change this to request only one per session +function MediaWikiApi.getEditToken() + --if MediaWikiApi.edit_token == nil then + local arguments = { + action = "query", + meta = "tokens", + type = "csrf", + format = "json" + } + local jsonres = MediaWikiApi.performRequest(arguments) + MediaWikiApi.edit_token = jsonres.query.tokens.csrftoken + --end + return MediaWikiApi.edit_token +end + +function MediaWikiApi.uploadfile(filepath, pagetext, filename, overwrite, comment) + local file_handler = io.open(filepath) + local content = { + action = "upload", + filename = filename, + text = pagetext, + comment = comment, + token = MediaWikiApi.getEditToken(), + file = { + filename = "whatevs", + data = file_handler:read("*all") + } + } + if overwrite then + content["ignorewarnings"] = "true" + end + local res = {} + local req = mpost.gen_request(content) + req.headers["cookie"] = MediaWikiApi.cookie2string() + req.url = MediaWikiApi.apiPath + req.sink = ltn12.sink.table(res) + local _, code, resheaders = https.request(req) + --MediaWikiApi.trace(" Result headers:", resheaders) + MediaWikiApi.parseCookie(resheaders["set-cookie"]) + return code, resheaders, res +end + +-- Code adapted from LrMediaWiki: +MediaWikiApi.trace = function(...) + print(...) +end + +--- 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.performHttpRequest(path, arguments, post) -- changed signature! + local requestBody = MediaWikiApi.createRequestBody(arguments) + local requestHeaders = { + ["Content-Type"] = "application/x-www-form-urlencoded", + ["User-Agent"] = MediaWikiApi.userAgent + } + if post then + requestHeaders["Content-Length"] = #requestBody + end + requestHeaders["Cookie"] = MediaWikiApi.cookie2string() + MediaWikiApi.trace("Performing HTTP request") + MediaWikiApi.trace(" Path:", path) + MediaWikiApi.trace(" Request body:", requestBody) + + local resultBody, resultHeaders + if post then + resultBody, resultHeaders = httpspost(path, requestBody, requestHeaders) + else + resultBody, resultHeaders = httpsget(path .. "?" .. requestBody, requestHeaders) + end + + MediaWikiApi.trace(" Result status:", resultHeaders.status) + + if not resultHeaders.status then + throwUserError("No network connection") + elseif resultHeaders.status ~= 200 then + MediaWikiApi.httpError(resultHeaders.status) + end + MediaWikiApi.parseCookie(resultHeaders["set-cookie"]) + --MediaWikiApi.trace("new cookie: "..resultHeaders["set-cookie"]) + MediaWikiApi.trace(" Result body:", resultBody) + return resultBody +end + +function MediaWikiApi.performRequest(arguments) + local resultBody = MediaWikiApi.performHttpRequest(MediaWikiApi.apiPath, arguments, true) + local jsonres = json.decode(resultBody) + return jsonres +end + +function MediaWikiApi.logout() + -- See https://www.mediawiki.org/wiki/API:Logout + local arguments = { + action = "logout" + } + MediaWikiApi.performRequest(arguments) +end + +function MediaWikiApi.login(username, password) + -- See https://www.mediawiki.org/wiki/API:Login + -- Check if the credentials are a main-account or a bot-account. + -- The different credentials need different login arguments. + -- The existance of the character "@" inside of an username is an + -- identicator if the credentials are a bot-account or a main-account. + local credentials + if string.find(username, "@") then + credentials = "bot-account" + else + credentials = "main-account" + end + MediaWikiApi.trace("Credentials: " .. credentials) + + -- Check if a user is logged in: + local arguments = { + action = "query", + meta = "userinfo", + format = "json" + } + local jsonres = MediaWikiApi.performRequest(arguments) + local id = jsonres.query.userinfo.id + local name = jsonres.query.userinfo.name + if id == "0" or id == 0 then -- not logged in, name is the IP address + MediaWikiApi.trace("Not logged in, need to login") + else -- id ~= '0' – logged in + MediaWikiApi.trace('Logged in as user "' .. name .. '" (ID: ' .. id .. ")") + if name == username then -- user is already logged in + MediaWikiApi.trace("No new login needed (1)") + return true + else -- name ~= username + -- Check if name is main-account name of bot-username + if credentials == "bot-account" then + local pattern = "(.*)@" -- all characters up to "@" + if name == string.match(username, pattern) then + MediaWikiApi.trace("No new login needed (2)") + return true + end + end + MediaWikiApi.trace('Logout and new login needed with username "' .. username .. '".') + MediaWikiApi.logout() -- without this logout a new login MIGHT fail + end + end + + -- A login token needs to be retrieved prior of a login action: + arguments = { + action = "query", + meta = "tokens", + type = "login", + format = "json" + } + jsonres = MediaWikiApi.performRequest(arguments) + local logintoken = jsonres.query.tokens.logintoken + + -- Perform login: + if credentials == "main-account" then + arguments = { + format = "json", + action = "clientlogin", + loginreturnurl = "https://www.mediawiki.org", -- dummy; required parameter + username = username, + password = password, + logintoken = logintoken + } + jsonres = MediaWikiApi.performRequest(arguments) + local loginResult = jsonres.clientlogin.status + if loginResult == "PASS" then + return true + else + return jsonres.clientlogin.message + end + else -- credentials == bot-account + assert(credentials == "bot-account") + arguments = { + format = "json", + action = "login", + lgname = username, + lgpassword = password, + lgtoken = logintoken + } + jsonres = MediaWikiApi.performRequest(arguments) + local loginResult = jsonres.login.result + if loginResult == "Success" then + return true + else + return jsonres.login.reason + end + end +end +-- end of LrMediaWiki code + +return MediaWikiApi From 21bea7c01577d1714a3d8f265f1a342e1b39aeb1 Mon Sep 17 00:00:00 2001 From: "trougnouf (Benoit Brummer)" Date: Sun, 5 Jul 2020 22:49:12 +0200 Subject: [PATCH 2/7] update dtMediaWiki to v46 --- contrib/dtMediaWiki/README.md | 0 contrib/dtMediaWiki/dtMediaWiki.lua | 14 +++++++++----- contrib/dtMediaWiki/mediawikiapi.lua | 16 ++++++++++------ 3 files changed, 19 insertions(+), 11 deletions(-) mode change 100644 => 100755 contrib/dtMediaWiki/README.md mode change 100644 => 100755 contrib/dtMediaWiki/dtMediaWiki.lua mode change 100644 => 100755 contrib/dtMediaWiki/mediawikiapi.lua diff --git a/contrib/dtMediaWiki/README.md b/contrib/dtMediaWiki/README.md old mode 100644 new mode 100755 diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua old mode 100644 new mode 100755 index 6760666a..3db47521 --- a/contrib/dtMediaWiki/dtMediaWiki.lua +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -9,7 +9,7 @@ Dependencies: ]] local dt = require "darktable" local MediaWikiApi = require "contrib/dtMediaWiki/mediawikiapi" -local version = 44 +local version = 46 --[[The version number is generated by .git/hooks/pre-commit (+x) with the following content: @@ -123,7 +123,7 @@ end -- Generate image name local function make_image_name(image, tmp_exp_path) local basename = image.filename:match "[^.]+" - local outname = namepattern_widget.text or namepattern_default + local outname = namepattern_widget.text ~= '' and namepattern_widget.text or namepattern_default dt.preferences.write(preferences_prefix, "namepattern", "string", outname) local presdata = image.title .. image.description if image.title ~= "" and image.description ~= "" then --2 items available @@ -263,7 +263,6 @@ local function make_image_page(image, tmp_img_fn) end end if dt.preferences.read(preferences_prefix, "cat_cam", "bool") then - print("catcam enabled") --dbg if image.exif_model ~= "" then local model = image.exif_maker:sub(1, 1) .. image.exif_maker:sub(2):lower() local catcam = "[[Category:Taken with " .. model .. " " .. image.exif_model @@ -306,14 +305,19 @@ local function register_storage_store(_, image, _, tmp_exp_path, _, _, _, _) local imagepage = make_image_page(image, tmp_exp_path) local imagename = make_image_name(image, tmp_exp_path) --print(imagepage) - MediaWikiApi.uploadfile( + local success = MediaWikiApi.uploadfile( tmp_exp_path, imagepage, imagename, dt.preferences.read(preferences_prefix, "overwrite", "bool"), comment_widget.text ) - msgout("exported " .. imagename) -- that is the path also + if success then + msgout("exported " .. imagename) -- that is the path also + else + print('Upload failed: ' .. imagename) + msgout("Failed to export " .. imagename) + end end --This function is called once all images are processed and all store calls are finished. diff --git a/contrib/dtMediaWiki/mediawikiapi.lua b/contrib/dtMediaWiki/mediawikiapi.lua old mode 100644 new mode 100755 index bcc3ee60..d2c02781 --- a/contrib/dtMediaWiki/mediawikiapi.lua +++ b/contrib/dtMediaWiki/mediawikiapi.lua @@ -108,12 +108,13 @@ function MediaWikiApi.uploadfile(filepath, pagetext, filename, overwrite, commen local file_handler = io.open(filepath) local content = { action = "upload", + format = "json", filename = filename, text = pagetext, comment = comment, token = MediaWikiApi.getEditToken(), file = { - filename = "whatevs", + filename = filename, data = file_handler:read("*all") } } @@ -125,10 +126,11 @@ function MediaWikiApi.uploadfile(filepath, pagetext, filename, overwrite, commen req.headers["cookie"] = MediaWikiApi.cookie2string() req.url = MediaWikiApi.apiPath req.sink = ltn12.sink.table(res) - local _, code, resheaders = https.request(req) - --MediaWikiApi.trace(" Result headers:", resheaders) + local _, _, resheaders = https.request(req) + local jsonres = json.decode(table.concat(res)) + local success = jsonres.upload.result == 'Success' MediaWikiApi.parseCookie(resheaders["set-cookie"]) - return code, resheaders, res + return success end -- Code adapted from LrMediaWiki: @@ -289,7 +291,8 @@ function MediaWikiApi.login(username, password) if loginResult == "PASS" then return true else - return jsonres.clientlogin.message + MediaWikiApi.track('Login failed: ' .. jsonres.clientlogin.message) + return false end else -- credentials == bot-account assert(credentials == "bot-account") @@ -305,7 +308,8 @@ function MediaWikiApi.login(username, password) if loginResult == "Success" then return true else - return jsonres.login.reason + MediaWikiApi.track('Login failed: ' .. jsonres.login.reason) + return false end end end From 1bafc483a7e740ad424a9ceab882794ef40e028d Mon Sep 17 00:00:00 2001 From: "trougnouf (Benoit Brummer)" Date: Wed, 8 Jul 2020 19:08:59 +0200 Subject: [PATCH 3/7] [dtMediaWiki] move mediawikiapi to lib folder --- contrib/dtMediaWiki/dtMediaWiki.lua | 4 ++-- contrib/dtMediaWiki/{ => lib}/mediawikiapi.lua | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename contrib/dtMediaWiki/{ => lib}/mediawikiapi.lua (100%) diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua index 3db47521..65638174 100755 --- a/contrib/dtMediaWiki/dtMediaWiki.lua +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -8,8 +8,8 @@ Dependencies: * lua-multipart-post: HTTP Multipart Post helper ]] local dt = require "darktable" -local MediaWikiApi = require "contrib/dtMediaWiki/mediawikiapi" -local version = 46 +local MediaWikiApi = require "contrib/dtMediaWiki/lib/mediawikiapi" +local version = 47 --[[The version number is generated by .git/hooks/pre-commit (+x) with the following content: diff --git a/contrib/dtMediaWiki/mediawikiapi.lua b/contrib/dtMediaWiki/lib/mediawikiapi.lua similarity index 100% rename from contrib/dtMediaWiki/mediawikiapi.lua rename to contrib/dtMediaWiki/lib/mediawikiapi.lua From 44b28bb6dc32999644a4314f79e102938ac26405 Mon Sep 17 00:00:00 2001 From: "trougnouf (Benoit Brummer)" Date: Fri, 1 Jan 2021 16:26:45 +0100 Subject: [PATCH 4/7] Update dtMediaWiki to v51; Allow translations with xgettext -l lua dtMediaWiki.lua --- contrib/dtMediaWiki/dtMediaWiki.lua | 98 +++++++++++++++-------------- 1 file changed, 52 insertions(+), 46 deletions(-) mode change 100755 => 100644 contrib/dtMediaWiki/dtMediaWiki.lua diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua old mode 100755 new mode 100644 index 65638174..032e1914 --- a/contrib/dtMediaWiki/dtMediaWiki.lua +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -9,7 +9,14 @@ Dependencies: ]] local dt = require "darktable" local MediaWikiApi = require "contrib/dtMediaWiki/lib/mediawikiapi" -local version = 47 +local version = 51 + +-- Use this _ function for translatable strings +local gettext = dt.gettext +gettext.bindtextdomain("dtMediaWiki",dt.configuration.config_dir.."/lua/locale/") +local function _(msgid) + return gettext.dgettext("dtMediaWiki", msgid) +end --[[The version number is generated by .git/hooks/pre-commit (+x) with the following content: @@ -24,33 +31,33 @@ dt.preferences.register( preferences_prefix, "username", "string", - "Wikimedia username", - "Wikimedia Commons username", + _('Wikimedia username'), + _("Wikimedia Commons username"), "" ) dt.preferences.register( preferences_prefix, "password", - "string", - "Wikimedia password", - "Wikimedia Commons password (to be stored in plain-text!)", + "string", -- TODO Use Lua password storage once in release. See https://github.com/darktable-org/darktable/pull/7508 + _("Wikimedia password"), + _("Wikimedia Commons password (to be stored in plain-text!)"), "" ) dt.preferences.register( preferences_prefix, "overwrite", "bool", - "Commons: Overwrite existing images?", - "Existing images will be overwritten without confirmation, otherwise the upload will fail.", + _("Commons: Overwrite existing images?"), + _("Existing images will be overwritten without confirmation, otherwise the upload will fail."), false ) dt.preferences.register( preferences_prefix, "cat_cam", "bool", - "Commons: Categorize camera?", - "A category will be added with the camera information " .. - "(eg: [[Category:Taken with Fujifilm X-E2 and XF18-55mmF2.8-4 R LM OIS]])", + _("Commons: Categorize camera?"), + _("A category will be added with the camera information " .. + "(eg: [[Category:Taken with Fujifilm X-E2 and XF18-55mmF2.8-4 R LM OIS]])"), false ) @@ -58,8 +65,8 @@ dt.preferences.register( preferences_prefix, "desc_templates", "string", - "Commons: Templates to be placed in {{Information |description= ...}}", - 'These templates are placed in the {{Information |description= ...}} field. (comma-separated)', + _("Commons: Templates to be placed in {{Information |description= ...}}"), + _('These templates are placed in the {{Information |description= ...}} field. (comma-separated)'), "Description,Depicted person,en,de,fr,es,ja,ru,zh,it,pt,ar" ) @@ -69,13 +76,13 @@ local namepattern_widget = dt.new_widget("entry") { tooltip = table.concat( { - "Determines the `File:` page name", - "recognized variables:", - "$FILE_NAME - basename of the input image", - "$TITLE - title from metadata", - "$DESCRIPTION - description from metadata", - "Note that $TITLE or $DESCRIPTION is required, and if both are chosen but only one is available " .. - "then the fallback name will be `$TITLE$DESCRIPTION ($FILE_NAME)`" + _("Determines the `File:` page name"), + _("recognized variables:"), + _("$FILE_NAME - basename of the input image"), + _("$TITLE - title from metadata"), + _("$DESCRIPTION - description from metadata"), + _("Note that $TITLE or $DESCRIPTION is required, and if both are chosen but only one is available " .. + "then the fallback name will be `$TITLE$DESCRIPTION ($FILE_NAME)`") }, "\n" ), @@ -90,9 +97,9 @@ local namepattern_widget = local language_widget = dt.new_widget("entry") { text = "en", - tooltip = "Description language code. Additional descriptions may be added with tag " + tooltip = _("Description language code. Additional descriptions may be added with tag " .. "{{Description|language_code|description_text}} or any of the templates listed in " - .. "the desc_template setting.", + .. "the desc_template setting."), reset_callback = function(self) self.text = "en" end @@ -102,22 +109,22 @@ dt.preferences.register( preferences_prefix, "authorpattern", "string", - "Commons: Preferred author pattern", - "Determines the author value; variables are $USERNAME, $CREATOR", - "[[User:$USERNAME|$CREATOR]]" + _("Commons: Preferred author pattern"), + _("Determines the author value; variables are trougnouf, $CREATOR"), + "[[User:trougnouf|$CREATOR]]" ) dt.preferences.register( preferences_prefix, "titleindesc", "bool", - "Commons: Use title in description", - "Use the title in description if both are available: description={{en|1=$TITLE: $DESCRIPTION}}", + _("Commons: Use title in description"), + _("Use the title in description if both are available: description={{en|1=$TITLE: $DESCRIPTION}}"), false ) -local function msgout(txt) - print(txt) - dt.print(txt) +local function msgout(str) + print(str) + dt.print(str) end -- Generate image name @@ -203,7 +210,6 @@ end local function substitute_keywords(string, image) local username = dt.preferences.read(preferences_prefix, "username", "string") - string = string:gsub("$USERNAME", username) string = string:gsub("$CREATOR", image.creator or username) string = string:gsub("$FILE_NAME", image.filename) string = string:gsub("$DATETIME", image.exif_datetime_taken) @@ -294,9 +300,9 @@ end -- comment widget shown in lighttable export local comment_widget = dt.new_widget("entry") { - text = "Uploaded with dtMediaWiki " .. version, + text = _("Uploaded with dtMediaWiki ") .. version, reset_callback = function(self) - self.text = "Uploaded with dtMediaWiki " .. version + self.text = _("Uploaded with dtMediaWiki ") .. version end } @@ -313,10 +319,10 @@ local function register_storage_store(_, image, _, tmp_exp_path, _, _, _, _) comment_widget.text ) if success then - msgout("exported " .. imagename) -- that is the path also + msgout(_("exported ") .. imagename) -- that is the path also else - print('Upload failed: ' .. imagename) - msgout("Failed to export " .. imagename) + print(_('Upload failed: ') .. imagename) + msgout(_("Failed to export ") .. imagename) end end @@ -326,7 +332,7 @@ local function register_storage_finalize(_, image_table, extra_data) for _ in pairs(image_table) do fcnt = fcnt + 1 end - msgout("exported " .. fcnt .. "/" .. extra_data["init_img_cnt"] .. " images to Wikimedia Commons") + msgout(_("exported ") .. fcnt .. "/" .. extra_data["init_img_cnt"] .. _(" images to Wikimedia Commons")) end --A function called to check if a given image format is supported by the Lua storage; @@ -343,11 +349,11 @@ local function register_storage_initialize(_, _, images, _, extra_data) for _, img in pairs(images) do if img.rights == "" then --TODO check allowed formats - msgout("Error: " .. img.path .. " has no rights, cannot be exported to Wikimedia Commons") + msgout(_("Error: ") .. img.path .. _(" has no rights, cannot be exported to Wikimedia Commons")) elseif img.title == "" and img.description == "" then msgout( - "Error: " .. - img.path .. " is missing a meaningful title and/or description, " .. "won't be exported to Wikimedia Commons" + _("Error: ") .. + img.path .. _(" is missing a meaningful title and/or description, ") .. _("won't be exported to Wikimedia Commons") ) else table.insert(out_images, img) @@ -362,19 +368,19 @@ local export_widgets = dt.new_widget("box") { dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label") {label = "Naming pattern:"}, + dt.new_widget("label") {label = _("Naming pattern:")}, namepattern_widget }, dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label") {label = "Comment:"}, + dt.new_widget("label") {label = _("Comment:")}, comment_widget }, dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label") {label = "Language code:", - tooltip = "Description language code (eg: en, fr, ...)." - .. "More descriptions can be entered in the tags (eg: {{fr|Une description}}"}, + dt.new_widget("label") {label = _("Language code:"), + tooltip = _("Description language code (eg: en, fr, ...).") + .. _("More descriptions can be entered in the tags (eg: {{fr|Une description}}")}, language_widget } } @@ -396,5 +402,5 @@ if export_widgets ) else - msgout("Unable to log into Wikimedia Commons, export disabled.") + msgout(_("Unable to log into Wikimedia Commons, export disabled.")) end From b26ca259189aa72b72778c3fb12fe95698f45edd Mon Sep 17 00:00:00 2001 From: "trougnouf (Benoit Brummer)" Date: Fri, 1 Jan 2021 17:43:22 +0100 Subject: [PATCH 5/7] dtMediaWiki: Fix hardcoded/missing username in image info page --- contrib/dtMediaWiki/dtMediaWiki.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua index 032e1914..aa1b15af 100644 --- a/contrib/dtMediaWiki/dtMediaWiki.lua +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -9,7 +9,7 @@ Dependencies: ]] local dt = require "darktable" local MediaWikiApi = require "contrib/dtMediaWiki/lib/mediawikiapi" -local version = 51 +local version = 53 -- Use this _ function for translatable strings local gettext = dt.gettext @@ -110,8 +110,8 @@ dt.preferences.register( "authorpattern", "string", _("Commons: Preferred author pattern"), - _("Determines the author value; variables are trougnouf, $CREATOR"), - "[[User:trougnouf|$CREATOR]]" + _("Determines the author value; variables are username, $CREATOR"), + "[[User:$USERNAME|$CREATOR]]" ) dt.preferences.register( preferences_prefix, @@ -210,6 +210,7 @@ end local function substitute_keywords(string, image) local username = dt.preferences.read(preferences_prefix, "username", "string") + string = string:gsub("$USERNAME", username) string = string:gsub("$CREATOR", image.creator or username) string = string:gsub("$FILE_NAME", image.filename) string = string:gsub("$DATETIME", image.exif_datetime_taken) @@ -353,7 +354,8 @@ local function register_storage_initialize(_, _, images, _, extra_data) elseif img.title == "" and img.description == "" then msgout( _("Error: ") .. - img.path .. _(" is missing a meaningful title and/or description, ") .. _("won't be exported to Wikimedia Commons") + img.path .. _(" is missing a meaningful title and/or description, ") .. + _("won't be exported to Wikimedia Commons") ) else table.insert(out_images, img) From 5ca9dc5361746d0c3eaadb5512aac873ecca7d76 Mon Sep 17 00:00:00 2001 From: "trougnouf (Benoit Brummer)" Date: Fri, 1 Jan 2021 19:50:39 +0100 Subject: [PATCH 6/7] dtMediaWiki: Workaround for unprotected error in call to Lua API crash when using the _ (translation) function inside a register_storage function --- contrib/dtMediaWiki/dtMediaWiki.lua | 47 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua index aa1b15af..095567d8 100644 --- a/contrib/dtMediaWiki/dtMediaWiki.lua +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -9,14 +9,7 @@ Dependencies: ]] local dt = require "darktable" local MediaWikiApi = require "contrib/dtMediaWiki/lib/mediawikiapi" -local version = 53 - --- Use this _ function for translatable strings -local gettext = dt.gettext -gettext.bindtextdomain("dtMediaWiki",dt.configuration.config_dir.."/lua/locale/") -local function _(msgid) - return gettext.dgettext("dtMediaWiki", msgid) -end +local version = 54 --[[The version number is generated by .git/hooks/pre-commit (+x) with the following content: @@ -25,6 +18,20 @@ end sed -i -r "s/local version = [0-9]+/local version = ${NEWVERSION}/1" dtMediaWiki.lua git add dtMediaWiki.lua ]] + +-- Use this _ function for translatable strings +local gettext = dt.gettext +gettext.bindtextdomain("dtMediaWiki",dt.configuration.config_dir.."/lua/locale/") +local function _(msgid) + return gettext.dgettext("dtMediaWiki", msgid) +end + +local function msgout(str) + -- Use msgout(_("AMESSAGE")) to output AMESSAGE in both the terminal and GUI + print(str) + dt.print(str) +end + -- Preference entries local preferences_prefix = "mediawiki" dt.preferences.register( @@ -122,11 +129,6 @@ dt.preferences.register( false ) -local function msgout(str) - print(str) - dt.print(str) -end - -- Generate image name local function make_image_name(image, tmp_exp_path) local basename = image.filename:match "[^.]+" @@ -320,10 +322,10 @@ local function register_storage_store(_, image, _, tmp_exp_path, _, _, _, _) comment_widget.text ) if success then - msgout(_("exported ") .. imagename) -- that is the path also + msgout(gettext.dgettext("dtMediaWiki", ("exported ")) .. imagename) -- that is the path also else - print(_('Upload failed: ') .. imagename) - msgout(_("Failed to export ") .. imagename) + print(gettext.dgettext("dtMediaWiki", ('Upload failed: ')) .. imagename) + msgout(gettext.dgettext("dtMediaWiki", ("Failed to export ")) .. imagename) end end @@ -333,7 +335,8 @@ local function register_storage_finalize(_, image_table, extra_data) for _ in pairs(image_table) do fcnt = fcnt + 1 end - msgout(_("exported ") .. fcnt .. "/" .. extra_data["init_img_cnt"] .. _(" images to Wikimedia Commons")) + msgout(gettext.dgettext("dtMediaWiki", ("exported ")) .. fcnt .. "/" .. extra_data["init_img_cnt"] .. + gettext.dgettext("dtMediaWiki", (" images to Wikimedia Commons"))) end --A function called to check if a given image format is supported by the Lua storage; @@ -349,13 +352,13 @@ local function register_storage_initialize(_, _, images, _, extra_data) local out_images = {} for _, img in pairs(images) do if img.rights == "" then - --TODO check allowed formats - msgout(_("Error: ") .. img.path .. _(" has no rights, cannot be exported to Wikimedia Commons")) + msgout(gettext.dgettext("dtMediaWiki", ("Error: ")) .. img.path .. + gettext.dgettext("dtMediaWiki", (" has no rights, cannot be exported to Wikimedia Commons"))) elseif img.title == "" and img.description == "" then msgout( - _("Error: ") .. - img.path .. _(" is missing a meaningful title and/or description, ") .. - _("won't be exported to Wikimedia Commons") + gettext.dgettext("dtMediaWiki", ("Error: ")) .. + img.path .. gettext.dgettext("dtMediaWiki", (" is missing a meaningful title and/or description, ")) .. + gettext.dgettext("dtMediaWiki", ("won't be exported to Wikimedia Commons")) ) else table.insert(out_images, img) From 8d22c9b614d167641e25369daa537f012a00f6e2 Mon Sep 17 00:00:00 2001 From: Benoit Brummer Date: Sat, 30 Mar 2024 00:21:22 +0100 Subject: [PATCH 7/7] dtMediaWiki bump 54 -> 61 commit 8a317078f6ca18767d90cab1786839d4d855b314 (HEAD -> master, origin/master, origin/HEAD) Author: Trougnouf (Benoit Brummer) Date: Sat Dec 2 02:20:45 2023 +0100 fix fmt_flt for non-decimal trailing zeroes commit ffcc5d9ec79e0a34ee836326e78d6f520fae15e5 Author: Trougnouf (Benoit Brummer) Date: Sat Dec 2 00:10:35 2023 +0100 fix camera categorization on non-US locale, add \comma workaround in categories commit 40451425f786a5ffbc5ef7883f169e5b0cc445c5 Author: trougnouf (Benoit Brummer) Date: Tue Aug 3 14:27:27 2021 +0200 dtMediaWiki.lua: rm lint commit 1949e76639a331f59d673a8570e701ae48ddd5c9 Author: trougnouf (Benoit Brummer) Date: Tue Aug 3 14:18:55 2021 +0200 dtMediaWiki.lua: workarounds for dt bug #9715 commit 25a9f8367ce94b129a3b6ebea0e106faadc155bc Author: trougnouf (Benoit Brummer) Date: Mon Mar 22 19:08:19 2021 +0100 substitute_keywords: parse commit 88373c8cb27b35da53c60238f9842df93fbaf997 Author: trougnouf (Benoit Brummer) Date: Wed Feb 10 18:41:49 2021 +0100 dtMediaWiki.get_alt_images: add debug info commit 7c2a2c79e96c4ef3838eb6127ff4576996d9e337 Author: trougnouf (Benoit Brummer) Date: Mon Jan 25 16:38:54 2021 +0100 fix Localisation template with locales using comma decimal separator --- contrib/dtMediaWiki/dtMediaWiki.lua | 114 ++++++++++++++++++---------- 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/contrib/dtMediaWiki/dtMediaWiki.lua b/contrib/dtMediaWiki/dtMediaWiki.lua index 095567d8..80d0789f 100644 --- a/contrib/dtMediaWiki/dtMediaWiki.lua +++ b/contrib/dtMediaWiki/dtMediaWiki.lua @@ -9,7 +9,7 @@ Dependencies: ]] local dt = require "darktable" local MediaWikiApi = require "contrib/dtMediaWiki/lib/mediawikiapi" -local version = 54 +local version = 61 --[[The version number is generated by .git/hooks/pre-commit (+x) with the following content: @@ -27,11 +27,15 @@ local function _(msgid) end local function msgout(str) - -- Use msgout(_("AMESSAGE")) to output AMESSAGE in both the terminal and GUI + -- Use msgout(gettext.dgettext("dtMediaWiki", "STRING")) to output STRING in both the terminal and GUI print(str) dt.print(str) end +local function dbgout(str) + -- Use dbgout(gettext.dgettext("dtMediaWiki", "STRING")) to output AMESSAGE in the debug terminal (darktable -d lua) + dt.print_log(str) +end -- Preference entries local preferences_prefix = "mediawiki" dt.preferences.register( @@ -150,14 +154,9 @@ local function make_image_name(image, tmp_exp_path) return outname .. "." .. ext end --- Round to 1 decimal, remove useless .0's and convert number to string +-- Round to 1 decimal, remove useless .0's and convert number to string. Ensure that "." decimal separator is used local function fmt_flt(num) - num = math.floor(num * 10 + .5) / 10 - if string.sub(num, -2) == ".0" then - return string.sub(num, 1, -3) - else - return tostring(num) - end + return string.format("%.1f", num):gsub(",", "."):gsub("%.0", "") end -- Get description field from the description (and optionally title) metadata @@ -184,11 +183,13 @@ local function get_intl_descriptions(image, discarded_tags) local desc_templates = split(dt.preferences.read(preferences_prefix, "desc_templates", "string")) local intl_descriptions = "" for _, tag in pairs(dt.tags.get_tags(image)) do - tag = tag.name - for _,dtemplate in pairs(desc_templates) do - if tag:sub(1,#dtemplate+3) == '{{' .. dtemplate .. '|' then - intl_descriptions = intl_descriptions .. tag - discarded_tags[tag] = true + if tag ~= 0 then -- workaround for dt bug #9715 + local tagstr = tag.name + for _, dtemplate in pairs(desc_templates) do + if tagstr:sub(1,#dtemplate+3) == '{{' .. dtemplate .. '|' then + intl_descriptions = intl_descriptions .. tagstr + discarded_tags[tagstr] = true + end end end end @@ -200,40 +201,60 @@ end local function get_other_fields(image, discarded_tags) local other_fields = '' for _, tag in pairs(dt.tags.get_tags(image)) do - tag = tag.name - if string.sub(tag, 0, 20) == '{{Information field|' - or string.sub(tag, 0, 7) == '{{InFi|' then - other_fields = other_fields .. tag - discarded_tags[tag] = true + if tag ~= 0 then -- workaround for dt bug #9715 + local tagstr = tag.name + if string.sub(tagstr, 0, 20) == '{{Information field|' + or string.sub(tagstr, 0, 7) == '{{InFi|' then + other_fields = other_fields .. tagstr + discarded_tags[tagstr] = true + end end end return other_fields end local function substitute_keywords(string, image) + local username = dt.preferences.read(preferences_prefix, "username", "string") string = string:gsub("$USERNAME", username) string = string:gsub("$CREATOR", image.creator or username) string = string:gsub("$FILE_NAME", image.filename) string = string:gsub("$DATETIME", image.exif_datetime_taken) + string = string:gsub("$YEAR", image.exif_datetime_taken:sub(0, 4)) + string = string:gsub("\\comma", ",") return string end +-- get "Other versions" of this image, ie different views of the scene or duplicate +-- of this image (the latter needs to have a different title). +-- Used with the "alt:" tag; +-- matches contained in other images' filename in the current library. +-- input: +---- image: working image (dt_lua_image_t) +---- tmp_img_fn: working image temporary export filename (string) +-- output: +---- gallery following "Other versions = " on the image page, or an empty string (string) local function get_alt_images(image, tmp_img_fn) local alts = {} for _, tag in pairs(dt.tags.get_tags(image)) do - if string.sub(tag.name, 1, 4) == "alt:" then - table.insert(alts, string.sub(tag.name, 5)) + if tag ~= 0 then -- workaround for dt bug #9715 + if string.sub(tag.name, 1, 4) == "alt:" then + table.insert(alts, string.sub(tag.name, 5)) + end end end if next(alts) == nil then return "" end local altcode = {""} - for _, img in ipairs(dt.collection) do - for _,altimg in pairs(alts) do + for _, img in pairs(dt.collection) do + dbgout(gettext.dgettext("dtMediaWiki", ("dtMediaWiki.get_alt_images: looking at ")) .. img.filename) + for _, altimg in pairs(alts) do + dbgout(gettext.dgettext("dtMediaWiki", ("dtMediaWiki.get_alt_images: looking for ")) .. altimg) if string.find(img.filename, altimg) then - table.insert(altcode, make_image_name(img, tmp_img_fn)) + local alt_fn = make_image_name(img, tmp_img_fn) + table.insert(altcode, alt_fn) + dbgout(gettext.dgettext("dtMediaWiki", ("dtMediaWiki.get_alt_images: found ")) .. alt_fn) end end end @@ -241,6 +262,7 @@ local function get_alt_images(image, tmp_img_fn) return table.concat(altcode, "\n") end + -- Generate an image page with all required info from tags, metadata, and such. local function make_image_page(image, tmp_img_fn) local discarded_tags = {} @@ -259,16 +281,19 @@ local function make_image_page(image, tmp_img_fn) table.insert(imgpg, '|other versions = ' .. get_alt_images(image, tmp_img_fn)) table.insert(imgpg, "}}") if image.latitude ~= nil and image.longitude ~= nil then - table.insert(imgpg, "{{Location |1=" .. image.latitude .. " |2=" .. image.longitude .. " }}") + table.insert(imgpg, "{{Location |1=" .. string.gsub(image.latitude,",",".") + .. " |2=" .. string.gsub(image.longitude,",",".") .. " }}") end table.insert(imgpg, "=={{int:license-header}}==") table.insert(imgpg, "{{self|" .. image.rights .. "}}") for _, tag in pairs(dt.tags.get_tags(image)) do - tag = substitute_keywords(tag.name, image) - if string.sub(tag, 1, 9) == "Category:" then - table.insert(imgpg, "[[" .. tag .. "]]") - elseif tag:sub(1, 2) == "{{" and not discarded_tags[tag] then - table.insert(imgpg, tag) + if tag ~= 0 then -- workaround for dt bug #9715 + local subbed_tag = substitute_keywords(tag.name, image) + if string.sub(subbed_tag, 1, 9) == "Category:" then + table.insert(imgpg, "[[" .. subbed_tag .. "]]") + elseif subbed_tag:sub(1, 2) == "{{" and not discarded_tags[subbed_tag] then + table.insert(imgpg, subbed_tag) + end end end if dt.preferences.read(preferences_prefix, "cat_cam", "bool") then @@ -283,6 +308,7 @@ local function make_image_page(image, tmp_img_fn) table.insert(imgpg, catcam) end if image.exif_aperture then + -- convert aperture to US_en locale and remove trailing .0 if there is any table.insert(imgpg, "[[Category:F-number f/" .. fmt_flt(image.exif_aperture) .. "]]") end if image.exif_focal_length ~= "" then @@ -311,21 +337,27 @@ local comment_widget = --This function is called once for each exported image local function register_storage_store(_, image, _, tmp_exp_path, _, _, _, _) + msgout(gettext.dgettext("dtMediaWiki", ("register_storage_store: exporting the following image:"))) + msgout(tmp_exp_path .. '(' .. image.title .. ')') + for _, tag in pairs(dt.tags.get_tags(image)) do + if tag == 0 then + msgout(gettext.dgettext("dtMediaWiki", ("BUG: invalid tag 0 for image ")) .. image.title) + end + end local imagepage = make_image_page(image, tmp_exp_path) local imagename = make_image_name(image, tmp_exp_path) --print(imagepage) local success = MediaWikiApi.uploadfile( - tmp_exp_path, - imagepage, - imagename, - dt.preferences.read(preferences_prefix, "overwrite", "bool"), - comment_widget.text + tmp_exp_path, + imagepage, + imagename, + dt.preferences.read(preferences_prefix, "overwrite", "bool"), + comment_widget.text ) if success then - msgout(gettext.dgettext("dtMediaWiki", ("exported ")) .. imagename) -- that is the path also + msgout(gettext.dgettext("dtMediaWiki", ("exported ")) .. imagename) -- that is the path also else - print(gettext.dgettext("dtMediaWiki", ('Upload failed: ')) .. imagename) - msgout(gettext.dgettext("dtMediaWiki", ("Failed to export ")) .. imagename) + msgout(gettext.dgettext("dtMediaWiki", ("Failed to export ")) .. imagename) end end @@ -351,7 +383,11 @@ end local function register_storage_initialize(_, _, images, _, extra_data) local out_images = {} for _, img in pairs(images) do - if img.rights == "" then + -- BUG? images contain "0" value (with key 3 in tested instance) instead of dt_lua_image_t + if type(img) == 'number' then + msgout(gettext.dgettext("dtMediaWiki", + ("BUG: dt.register_storage.initialize sent a number instead of dt_lua_image_t in images table: ")) .. img) + elseif img.rights == "" then msgout(gettext.dgettext("dtMediaWiki", ("Error: ")) .. img.path .. gettext.dgettext("dtMediaWiki", (" has no rights, cannot be exported to Wikimedia Commons"))) elseif img.title == "" and img.description == "" then