/**
@Revision: 16:24, 16 October 2019 (UTC)
@Description:
Script for cleanup of file description pages. Script does NOT work with Internet Explorer 8 or lower.
To use, add this line to your common.js file: importScript('User:Magog the Ogre/cleanup.js');
mto_cleanup tagger (initially developed by Patstuart/Magog the Ogre)
There are 3 hook events
* "gadget.cleanup.run"
* "gadget.cleanup.done"
* "gadget.cleanup.loaded"
* ToDo: https://commons.wikimedia.org/wiki/File:Ringerike_komm.svg
*/
// <nowiki>
/* eslint no-var:"error"*/
/* eslint-env es6*/
/* eslint-disable camelcase, no-underscore-dangle, valid-jsdoc*/
/* jslint browser:true, bitwise:true, plusplus:true, regexp:true*/
(function ($, mw) {
'use strict';
let fileNamespace = mw.config.get('wgNamespaceNumber') === 6,
linksShown = [],
INFORMATION_FIELDS_REGEX,
sum, // summary
pn = mw.config.get('wgPageName'), // own tool section
cleanBox = $('<div id="p-cl" class="portal" role="navigation"><h3 id="p-cl-label">Cleanup</h3><div class="body"><ul></ul></div></div>');
function regexify(elements, action) {
let regex = '(?:';
elements.forEach(function (el, i) {
if (i) regex += '|';
regex += action ? action(el) : el;
});
regex += ')';
return regex;
}
function quoteNoCase(string) {
let char,
i,
upper,
lower,
newString = '';
for (i = 0; i < string.length; i++) {
char = string[i];
upper = char.toUpperCase();
lower = char.toLowerCase();
if (upper !== lower) newString += `[${upper}${lower}]`;
else newString += char;
}
return newString;
}
function regexifyTemplates(templates, insensitive) {
return regexify(templates, (element) => {
let string;
if (insensitive) {
string = mw.util.escapeRegExp(element);
string = string.replace(/./, quoteNoCase);
} else {
string = quoteNoCase(mw.util.escapeRegExp(element.substring(0, 1))) + mw.util.escapeRegExp(element.substring(1));
}
return string.replace(' ', '[ _]+');
});
}
INFORMATION_FIELDS_REGEX = regexifyTemplates([
'description',
'source',
'date',
'author',
'permission',
'other versions',
'other fields'
]);
/**
* Need to reinitialize because the upload form creates a new textbox asynchronously
*/
function getTextbox() {
sum = ((sum = document.forms.editform)) ? sum.wpSummary : '';
return $('#wpTextbox1,#wpUploadDescription,#wpDescText1').first();
}
function bot_move_checked(text) {
return text.replace(/\{\{BotMoveToCommons.*\}\}\s*\n/, '')
.replace(/\{\{CH2MoveToCommons\|[-a-z]+.w[a-z]+\|year=\d+\|month=\w+\|day=\d\}\}\s*\n/, '')
.replace('The tool and the bot are operated by [[User:Jan Luca]] and [[User:Magnus Manske]].', '')
.replace('The upload bot is [[User:CommonsHelper2 Bot]] which is called by [http://toolserver.org/~commonshelper2/index.php CommonsHelper2].', '');
}
/** ********************
* Function which removes <nowiki> and <!--…--> from the text, and replaces them with a set of
* temporary strings. This is necessary, because functions which change text will may to leave the
* nowikis/comments intact. The temporary strings function as "markers", so that
* rebuild_comments_nowikis() can later be called and the nowikis/comments will return to the correct
* position, untouched.
* Returns: An array, whose 0th element contains the altered string, and whose 1st element contains
* another array which will focus as a set "tokens", which should be kept unaltered and passed to
* rebuild_comments_nowikis() later.
********************* */
function parse_comments_nowikis(text) {
// constants
let nw_regex = /<nowiki>((?:.|\n)*?)<\/nowiki>/,
c_regex = /<!--((?:.|\n)*?)-->/,
/* Special one for CommonSense… causing grouping headaches */
cs_regex = /<!--(\s*categories\s*by\s*(?:commonsense|checkusage)\s*)-->/i,
// variables
comments = [],
nowikis = [],
comments_cs = [],
nw_com_order = [],
next_nw,
next_c,
next_cs,
wLimit = 20,
index;
while (wLimit--) {
next_nw = text.search(nw_regex);
next_c = text.search(c_regex);
next_cs = text.search(cs_regex);
if (next_nw === -1 && next_c === -1) {
/* cs_regex will be -1 if c_regex is -1 */
break;
}
if (next_nw === -1 || (next_c !== -1 && next_c < next_nw)) {
if (next_cs === next_c) {
index = comments_cs.length;
comments_cs.push(cs_regex.exec(text)[1]);
text = text.replace(c_regex, `%%%MTOCSCOMMENT${index}%%%`);
nw_com_order.push('s');
} else {
index = comments.length;
comments.push(c_regex.exec(text)[1]);
text = text.replace(c_regex, `%%%MTOCOMMENT${index}%%%`);
nw_com_order.push('c');
}
} else {
index = nowikis.length;
nowikis.push(nw_regex.exec(text)[1]);
text = text.replace(nw_regex, `%%%MTONOWIKI${index}%%%`);
nw_com_order.push('n');
}
}
return [text, [comments, nowikis, comments_cs, nw_com_order]];
}
/** ********************
* See function immediately above for explanation
* remove_whitespace: when rebuilding nowikis, remove unnecessary whitespace (not shown in final version anyway)
* Returns: Rebuilt text.
********************* */
function rebuild_comments_nowikis(text, tokens, remove_whitespace) {
let comments = tokens[0],
nowikis = tokens[1],
comments_cs = tokens[2],
nw_com_order = tokens[3],
next,
text_tmp;
while (nw_com_order.length > 0) {
next = nw_com_order.pop();
if (next === 's') {
text = text.replace(`%%%MTOCSCOMMENT${comments_cs.length - 1}%%%`, `<!--${comments_cs.pop()}-->`);
} else if (next === 'c') {
text = text.replace(`%%%MTOCOMMENT${comments.length - 1}%%%`, `<!--${comments.pop()}-->`);
} else {
/* i.e., nw_com_order.pop was 'n'; also, mediawiki registers all whitespace inside nw's as just one space */
text_tmp = nowikis.pop();
if (remove_whitespace) text_tmp = text_tmp.replace(/\s+/g, ' ');
// eslint-disable-next-line no-useless-escape
text = text.replace(`%%%MTONOWIKI${nowikis.length}%%%`, `<nowiki>${text_tmp}<\/nowiki>`);
}
}
return text;
}
/** ********************
* Function which iterates over 'text' and replaces each instance of 'code' with 'replacement' until
* running the iteration no longer results in a change of the text.
* Parameter code: Should be a string or regular expression (both are allowed by the
* replace() function in JavaScript).
* Parameter replacement: Should be a string or a functor (both are allowed by the replace() function
* in JavaScript).
* Parameter text: The text that will be altered.
* Returns: The altered text.
********************* */
function iterative_replace(code, replacement, text) {
let temptext,
wLimit = 20;
// only headers that aren't already inside a template will suffice, per what seems right to me
while (wLimit--) {
temptext = text.replace(code, replacement);
if (temptext === text) break;
text = temptext;
}
return text;
}
/** ***********
* month_name_re = case insensitive
* month_number = 1-12
************ */
function mto_parse_date(input_string, month_name_re, month_number) {
let regexp1 = new RegExp(`(^\\s*\\|\\s*[Dd]ate\\s*=\\s*)${month_name_re
}[-\\\\\\.\\s,]+(\\d{1,2})(?:st|nd|rd|th)?[-\\\\\\.\\s,]+(\\d{4})(?!\\d)`, 'mi'),
regexp2 = new RegExp(`(^\\s*\\|\\s*[Dd]ate\\s*=\\s*)(\\d{1,2})(?:st|nd|rd|th)?[-\\\\\\.\\s,]+${month_name_re
}[-\\\\\\.\\s,]+(\\d{4})(?!\\d)`, 'mi'),
regexp3 = new RegExp(`(^\\s*\\|\\s*[Dd]ate\\s*=\\s*)(\\d{4})[-\\\\\\.\\s,]+${month_name_re
}[-\\\\\\.\\s,]+(\\d{1,2})(?:st|nd|rd|th)?(?!\\d)`, 'mi'),
regexp4 = new RegExp(`(^\\s*\\|\\s*[Dd]ate\\s*=\\s*)(\\d{4})[-\\\\\\.\\s,]+(\\d{1,2})(?:st|nd|rd|th)?[-\\\\\\.\\s,]+${month_name_re}`, 'mi'),
regexp5 = new RegExp(`(^\\s*\\|\\s*[Dd]ate\\s*=\\s*)(\\d{4})[-\\\\\\.\\s,]+${month_name_re}`, 'mi'),
regexp6 = new RegExp(`(^\\s*\\|\\s*[Dd]ate\\s*=\\s*)${month_name_re}[-\\\\\\.\\s,]+(\\d{4})(?!(?:[-\\\\\\.\\s,]+\\d{1,2}|\\d))`, 'mi');
return input_string
.replace(regexp1, `$1{{Date|$3|${month_number}|$2}}`)
.replace(regexp2, `$1{{Date|$3|${month_number}|$2}}`)
.replace(regexp3, `$1{{Date|$2|${month_number}|$3}}`)
.replace(regexp4, `$1{{Date|$2|${month_number}|$3}}`)
.replace(regexp5, `$1{{Date|$2|${month_number}}}`)
.replace(regexp6, `$1{{Date|$2|${month_number}}}`);
}
/*
* returns author information, as found. Returns in vector form, as {(Entire author string), (author project code), (author username)}.
* If unable to parse project code or username, then they will be null
*/
function mto_parse_author_info(text) {
let return_array = new Array(3),
authorstart = text.search(/^\s*\|\s*[Aa]uthor\s*=\s*(.+?)\.?\s*$/m),
username_array;
return_array[0] = text.substring(authorstart, text.indexOf('\n', authorstart)).replace(/^\s*\|\s*[Aa]uthor\s*=\s*(.+?)\s*.?(?:\s*<\s*[Bb][Rr]\s*\/?>\s*)?\s*?$/, '$1');
username_array = /^.*\[\[(?:\s*:?\s*([a-z]{1,2}))\s*:[A-Za-z]?[a-z]+:(.*?)[|\]](?:.\n*)*$/.exec(return_array[0]);
if (username_array) {
// successful username extraction match
return_array[1] = username_array[1];
return_array[2] = username_array[2];
}
return return_array;
}
function mto_uploader_is_author(text) {
return text.replace(/(\|\s*[Aa]uthor\s*=\s*.*?)\s*\.{0,2}(?:\s*<\s*[Bb][Rr]\s*\/?>)?\s*\n\s*\*? ?([^\s|}].*\n\s*[|}])/, '$1 / $2');
}
function mto_uploader_isnt_author(text) {
// original upload date is useless
return text.replace(/(\|[Dd]ate\s*=.*?)\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\*?\s*\{\{\s*[Oo]riginal +upload +date\s*\|\s*[\d-]+\s*\}\}(?:; *\{\{[Dd]ate[\d|\s]+\}\} *\(last +version\)*)?/, '$1')
// original author info is useless
.replace(/(\|\s*[Aa]uthor\s*=\s*?.*?).?(?:\s*<\s*[Bb][Rr]\s*\/?>)?\s*(?:[Uu]ploaded\s+by\s+)?\[\[\s*:?\s*([a-z]{1,2})\s*:(?:[A-Za-z]?[a-z]+):(?:.+?)\s*(?:\|(?:.*?))\]\] at (?:\[http:\/\/)?\2.w[\w. ]+\]?.*\n/, '$1\n')
.replace(/(\|\s*[Aa]uthor\s*=\s*?.*?).?(?:\s*<\s*[Bb][Rr]\s*\/?>)?\s*Later +version\(s\) +were +uploaded +by\s*?\n?/, '$1\n');
}
// must process source cleanup in order to continue
function own_replace_pre(text) {
return text.replace(/Transferred from \[(http:\/\/[\w-. ]+)\]; transferr?ed to Commons/im, 'Transferred from [$1] to Commons')
.replace(/Transferred from \[(http:\/\/[\w-. ]+)\]; Transfer was stated to be made by \[\[(.+?)\]\]/im, 'Transferred from [$1] to Commons by [[$2]]')
.replace(/\|(\s*source\s*=\s*)transferred from \[(http:\/\/[\w-. ]+)\]((?: to commons by \[\[.+?\]\].?)?)\s*?(?:<\s*br\s*\/?>)?((?:\susing .+?)?)\s*.?(?:\s*<\s*br\s*\/?\s*>)?\n+\s*\(original\stext\s*:\s*('')?([\S\s]*?)\5\)(\s*(?:\|\s*(?:description|date|author|permission|other[ _]+versions|other[ _]+fields)\s*=|\}\}))/im, '|$1$6<br>\nTransferred from [$2]$3$4.$7');
}
/* internal function only */
function own_replace(text, username, projcode, force) {
let re_anything = '[^\\S\\n]*[^\\|\\s].*?',
username_test_re = (projcode && username) ? `|\\[\\[\\s*:\\s*${projcode ? projcode.replace('-', '\\-') : /* dummy */ ''}\\s*:\\s*[^\\s\\d\\+:\\|\\[\\]]+?\\s*:\\s*${username}\\s*\\|\\s*(?:[^\\s\\d\\+:\\|\\[\\]]+?\\s*:\\s*)?${username}\\s*\\]\\].*?` : '',
sp_re_const = force === 'selfphoto' ? re_anything : '(?:selb(?:st|er)[\\s-]*(?:(?:ph|f)oto(?:gra(?:f|ph)ie(?:rt)?)?|aufgenommen|geknipst)|self[-\\s+]taken|i[^a-z]took[^a-z]this[^a-z](?:photo(?:graph)?|picture))\\.?',
own_re_const = force ? re_anything : `(?:I,? .*created this (?:work|image) entirely by myself\\.?|self[-\\s+]made|own(?:\\s+work)?|(?:selb(?:st|er)|eigen(?:e|es)?)(?:\\s*(?:werk|(?:ph|f)oto(?:gra(?:f|ph)ie(?:rt)?)?|archiv|gezeichnet|erstellt|aufnahme|bild(?:er)?|arbeit))?|opera propria${username_test_re})\\.?`,
text_bak = text,
replacement;
// define functions
function own_replace_1(text, internal_re, replace_text) {
let re = new RegExp(`(\\|\\s*source\\s*=[^\\S\\n]*(?!\\|))(${internal_re}.*?)(?:\\s*?<\\s*[Bb][Rr]\\s*\\/?>)?\\s*?(\\n?(?:transferr?ed +from +\\[http|\\{\\{\\s*[Tt]ransferred +from|\\(?Original(?:ly)? +uploaded +on +[a-z-]{2,}\\.wik).+)?\\n`, 'i'),
re_parsed = re.exec(text);
if (re_parsed !== null) {
if (re_parsed[2].search(/(?:\s*transferr?ed +from +\[http|\{\{\s*[Tt]ransferred +from|\(?Original(?:ly)? +uploaded +on + [a-z-]{2,}.wik)/i) === 0) {
// just prepend under circumstances
return text.replace(re, `$1${replace_text}<br>$2\n`);
}
if (re_parsed[2].search(/\s*(?:\*\s*)?\{\{\s*(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\s*\}\}/) !== 0) {
// prepend plus original text
return text.replace(re, `$1${replace_text} ({{original text|1=$2|nobold=1}})<br>$3\n`);
}
}
// we've decided not to parse for whatever reason
return text;
}
function own_replace_2(text, internal_re, replace_text) {
let re = new RegExp(`(\\|\\s*source\\s*=[^\\S\\n]*)(?!\\|)(${internal_re}.*?)(?:\\s*?<\\s*[Bb][Rr]\\s*\\/?>)?\\n`, 'i'),
re_parsed = re.exec(text);
if (re_parsed !== null) {
if (re_parsed[2].search(/\s*(?:\*\s*)?\{\{\s*(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\s*\}\}/) !== 0 &&
re_parsed[2].search(/(?:\s*transferr?ed +from +\[http|\{\{\s*[Tt]ransferred +from|\(?Original(?:ly)? +uploaded +on + [a-z-]{2,}.wik)/i) !== 0) return text.replace(re, `$1${replace_text} ({{original text|1=$2|nobold=1}})\n`);
if (re_parsed[2].search(/\|\s*source\s*=\s*\*?\s*\{\{\s*(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\s*\}\}/) === 0) {
// just prepend under circumstances
return text.replace(re, `$1${replace_text}<br>$2\n`);
}
}
return text;
}
text = own_replace_pre(text);
if (force === 'ownoriginaluploader') {
text_bak = own_replace_1(text, re_anything, '{{Own work by original uploader}}');
if (text_bak === text) text_bak = own_replace_2(text, re_anything, '{{Own work by original uploader}}');
}
if (text_bak === text) {
text_bak = own_replace_1(text, sp_re_const, '{{Self-photographed}}');
if (text_bak === text) {
text_bak = own_replace_2(text, sp_re_const, '{{Self-photographed}}');
if (text_bak === text) {
text_bak = own_replace_1(text, own_re_const, '{{Own}}');
if (text_bak === text) {
text_bak = own_replace_2(text, own_re_const, '{{Own}}');
if (text_bak === text && force) {
switch (force) {
case 'selfphoto':
replacement = '$1{{Self-photographed}}';
break;
case 'ownoriginaluploader':
replacement = '$1{{Own work by original uploader}}';
break;
default:
replacement = '$1{{Own}}';
}
text = text.replace(/(\|\s*source\s*=(?!\s*\{\{\s*(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\s*\}\}))/i, replacement);
text_bak = text;
}
}
}
}
}
return text_bak.replace(/\|(\s*[Ss]ource\s*=\s*\{\{(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\}\}(?:\s*\(?\s*\{\{\s*[Oo]riginal +text\s*\|.+?\|\s*nobold\s*=\s*1\s*\}\}\s*\)?)?)(?:\s*<\s*[Bb][Rr]\s*\/?>){2,}\s*/, '|$1<br>\n')
.replace(/\|(\s*[Ss]ource\s*=\s*\{\{(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\}\}(?:\s*\(?\s*\{\{\s*[Oo]riginal +text\s*\|.+?\|\s*nobold\s*=\s*1\s*\}\}\s*\)?)?)\s*<\s*[Bb][Rr]\s*\/?>(\s*(?:\|\s*(?:description|date|author|permission|other[ _]+versions|other[ _]+fields)\s*=|\}\}|\*))/, '|$1$2');
}
function mto_own(text, type) {
// author cleanup
text = mto_uploader_is_author(text);
let authorinfo = mto_parse_author_info(text),
author = authorinfo[0],
project = authorinfo[1],
user = authorinfo[2];
text = own_replace(text, user, project, type);
// specific license cleanup
if (user) text = text.replace(/\{\{\s*pd-release\s*\}\}/mi, `{{PD-user-w|${project}|wikipedia|${user}}}`);
// {{Self}} cleanup
text = text.replace(/\{\{\s*([Ss]elf2?|[Pp]ropio|[Сс]ебе|[Mm]ultilicense +replacing +placeholder(?: +new)?)\s*\|(?!.*\s*[Aa]uthor\s*=[^|]+\|\s*)(.*?)\}\}/m, `{{$1|author=${author}|$2}}`);
text = iterative_replace(/\|(\s*[Ss]ource\s*=\s*\{\{(?:[Oo]wn|[Ss]elf(?:-| +)photographed|[Oo]wn[_ ]+work[_ ]+by[_ ]+original[_ ]+uploader)\}\}<br\/?>)\s*<\s*[Bb][Rr]\s*\/?>\s*/, '|$1\n', text);
return text;
}
function mto_wrapper(type) {
let textbox = getTextbox(),
cleanup = textbox.val(),
// parse comments/nowikis
cnwt = parse_comments_nowikis(cleanup),
cleanup2 = cnwt[0],
tokens = cnwt[1];
cleanup2 = mto_own(cleanup2, type);
// rebuild comments/nowikis
cleanup2 = rebuild_comments_nowikis(cleanup2, tokens, false);
textbox.val(cleanup2);
}
function remove_useless_permission_messages(cleanup) {
// rm various useless permission messages
let USELESS_TEXT_REGEX = regexify([
'PD[A-Z0-9-\\| ]*\\d*',
'CC-(?:ZERO|BY(?:[\\w-,.]*)?)',
'(?:BILD-)?GFDL[A-Z-\\|]*',
'ATTRIBUTION',
'NORIGHTSRESERVED',
'BILD-BY',
'BSD',
'This image is in the (?:\\[\\[)?public domain(?:\\]\\])?(?: due to its age| because it is ineligible for copyright)?',
'Released under the \\[\\[GNU Free Documentation License\\]\\]',
'GNU Free Documentation License 1\\.2',
'Released into the public domain \\(?by the author\\)?',
'Licensed under the \\[\\[GFDL\\]\\] \\(?by the author\\)?',
'-+'
]),
OTRS_TEMPLATES_REGEX = regexifyTemplates([
'PermissionOTRS',
'Permission OTRS',
'OTRSPermission',
'Crediti',
'Разрешение OTRS'
]);
cleanup = cleanup.replace(/(\|\s*[Pp]ermission\s*=[^|\n]*?)(?:\s*?<\s*[Bb][Rr]\s*\/?>)?\n\(Original +text *: *(?:'')?(?:[Pp]ublic\s+[Dd]omain|GNU\s*-?\s*FDL|[Yy][Ee][Ss]|[Jj][Aa]|[Ss][Ee][Ee]\s+[Bb][Ee][Ll][Oo][Ww]|[Bb]ild-frei|[Ss]ee +license +section|(?:[Cc][Cc]-(?:[Bb][Yy](?:-[Ss][Aa])?(?:-\d.\d)?|zero),?\s*)+)\.?\s*(?:'')?\)\s*?\n(\s*?\|)/, '$1\n$2');
cleanup = iterative_replace(new RegExp(`(\\|\\s*Permission\\s*=)(?:(.+?);)?\\s*${
USELESS_TEXT_REGEX
}\\s*(;.*?)?\\.?(?:<\\s*[Bb][Rr]\\s*\\/?>)?\\s*?(?:\\s*\\(Original +text\\s*:\\s*('')([\\S\\s]*?)\\4\\)|\\s*(\\{\\{\\s*${
OTRS_TEMPLATES_REGEX
}\\s*\\|[\\s\\S]*?\\}\\})\\s*)?(\\n\\s*\\|${
INFORMATION_FIELDS_REGEX
})`, 'i'), '$1$2$3; $5$6$7', cleanup); // \| included due to early buggy bot moves
return cleanup
.replace(/(\|\s*[Pp]ermission\s*=)\s*('')?(?:see +license +section|see +below)\.?\s*\2\s*\n/i, '$1\n')
.replace(/(\|\s*[Pp]ermission\s*=\s*);+\s*([^\s|])/, '$1$2')
.replace(/(\|\s*[Pp]ermission\s*=.*);+(?:\s*(.))?(\s*\|(?:description|date|author|permission|other[ _]+(?:versions|fields)))/i, '$1$2$3');
}
// mto_cleanup tagger (initially developed by Patstuart/Magog the Ogre)
function mto_cleanup(fileContent) {
let cbText,
hascat = false;
if (typeof fileContent === 'object') cbText = fileContent.text;
else fileContent = 0;
let textbox = getTextbox(),
cleanupTmp = (typeof cbText === 'string' ? cbText.trim() : 0) || textbox.val().trim(),
orgText = cleanupTmp,
// store comments, nowikis as is to avoid changing them
cnwt = parse_comments_nowikis(cleanupTmp),
cleanup = cnwt[0],
tokens = cnwt[1],
de_wp_re = /\{\{Bild-GFDL-Neu\}\}\s*\r?\n\{\{(?:\s*self\s*\|\s*author=.*\|)?Cc-by-sa-3.0\}\}\s*\r?\n\{\{Cc-by-sa-3.0-de\}\}\s*\r?\n\{\{GFDL(?:-user-de(?:\|.*)?)?\}\}/i,
// uploader statuses
uploader_status = 0,
UNKNOWN = 1,
AUTHOR = 2,
NOTAUTHOR = 4,
PD_UNKNOWN = 'release|text(?: *logo)?|ineligible|trivial|uegnet|markenrecht|shape|simple|geometry|link|chem|author|because|reason|porque|reden',
PD_AUTHOR = 'self|users?|utente|own',
GFDL_AUTHOR = 'users?|self',
project_code,
username,
authorinfo,
author,
cleanup2,
i,
self_author_text,
headercheck,
re_unc,
unc,
nextcat,
re_ds,
ds,
re_chc,
chc,
re_cs_chc,
cs_chc,
infoend,
opennext,
closenext,
template_level,
cleanup_pre,
cleanup_post,
// summary = "([[User:Magog the Ogre/cleanup.js|Script]]) cleanup",
summary = 'cleanup',
categories = [],
re_cat = /^([\s\S]*[^=])\[\[\s*[Cc]ategory\s*:\s*([^[\]]?)([^|[\]]*?)\s*(\|[^|[\]]*)?\]\](?:(\s*%%%MTOCOMMENT\d+%%%)?\s*?\n)?([\s\S]*?)$/,
catLimit = 30; // max cats;
// interwikis_regex = new RegExp('(\\{\\{\\s*' + getInterwikisRegex() + '\\s*\\|\\s*(?:1\\s*=\\s*)?)(\\S)(\\S*)', 'gm');
/* function getInterwikisRegex() {
return regexifyTemplates(Object.keys(window.wpAvailableLanguages));
} */
cleanup = bot_move_checked(cleanup);
cleanup = cleanup.replace(/×/g, '×')
.replace(/|/g, '|')
// because the new bot really is this dumb
.replace(/(\|\s*[Dd]escription\s*=\s*(.+)[\s\S]+)==\s*[Ss]ummary\s*==\n+\2/, '$1')
// Excessive wordage/markup in description
.replace(/\{\{\s*([a-z]{2,4}(?:-[a-z]{5})?)\s*\|\s*(1\s*=)?\s*(?:this\s+is\s+)?(?:an?\s+|the\s+)?(?:photo(?:graph)?|picture|image)\s+(?:taken\s+)?(?:of|depicting)\s+(\S)/ig, '{{$1|$2$3');
cleanup = iterative_replace(/\{\{\s*([a-z]{2,4}(?:-[a-z]{5})?)\s*\|\s*((?:.|\n?)+?)(?:\s*<\s*[Bb][Rr]\s*\/?>\n+)?(?:\s*\[\[\s*:\s*\1\s*:\s*(?:[Cc]ategor(?:y|ia)|[Cc]atégorie|[Kk]ategorie|[Кк]атегория)\s*:[^\]]+\]\])+\s*\}\}/, '{{$1|$2}}', cleanup);
cleanup = cleanup.replace(/\{\{(\s*[a-z]{2,4}(?:-[a-z]{5})?\s*\|\s*(.+?))\s*(?:<\s*[Bb][Rr]\s*\/?>)?(\s*\n+==[^=]+==)*\s*\}\}/m, '{{$1}}')
.replace(/\{\{\s*[a-z]{2,4}(?:-[a-z]{5})?\s*\|\s*(=+).*?\1\s*\}\}/, '');
// pipe wikilinks in descriptions
cleanup = iterative_replace(/\|(\s*[Dd]escription\s*=(?:.|\n?)*?)\{\{\s*([a-z]{2,4}(?:-[a-z]{5})?)\s*\|((?:.|\n?)*)\[\[\s*:\s*\2\s*:\s*([^|\]]*?)\]\]((?:.|\n?)*\}\}(?:.|\n?)*?\|\s*(?:[Ss]ource|[Dd]ate)\s*)=/, '|$1{{$2|$3[[:$2:$4|$4]]$5=', cleanup);
// Empty descriptions
cleanup = cleanup.replace(/(?:(=)\s*)?\{\{\s*[a-z]{2,4}(?:-[a-z]{5})?\s*\|\s*(?:1\s*=\s*)?(?:''no original description''\s*)?\s*\}\}\s*$/mg, '$1')
// internationalization
.replace(/\s*original upload date\s*(?!(?:\}\}|\|))/im, '{{original upload date}}')
.replace(/^(=+) *li[cz]en(?:s(?:e|ing)(?:\s+information)?|za(?: +d'uso)?|z) *:? *\1\s*$/mig, '$1{{int:license-header}}$1')
.replace(/^(=+) *\{\{\s*(?:[Ii][Nn][Tt]|[Mm][Ee][Dd][Ii][Aa][Ww][Ii][Kk][Ii])\s*:\s*[Ll]icense\s*\}\} *:? *\1\s*?$/mg, '$1{{int:license-header}}$1')
.replace(/^(=+) *original upload (?:log|history) *:? *\1\s*$/mig, '$1{{Original upload log}}$1')
.replace(/^(=+) *(?:summary|dettagli|beschreibung|beschreibung\W+quelle) *:? *\1\s*$/mig, '$1{{int:filedesc}}$1')
// Magnus bot bug where it's transferring over a second and incorrect license
.replace(/((=+) *\{\{int:licens[e|ing](?:-header)?\}\} *\2[\s\S]+?)((=+) *\{\{int:licens[e|ing](?:-header)?\}\} *\4)(?:\s*\{\{.+?\}\})+\s*$/, '$1');
// because the new bot by Jan Luca and Magnus Manske spams itself like crazy and thus creates two headrs
cleanup = iterative_replace(/^(=+) *\{\{int:filedesc\}\} *(?:\1(?:\s*\n)+)\1 *\{\{int:filedesc\}\} *\1\s*\n/mig, '$1{{int:filedesc}}$1\n', cleanup);
// rm various useless permission messages
cleanup = remove_useless_permission_messages(cleanup);
// wordage changes
cleanup = cleanup.replace(/\|(\s*source\s*=.*\s*(?:\(?\s*)Original uploaded on [a-z]+.wiki[a-z]+)(?:\s*\)?)((?:\s*\|\s*date\s*=\s*.*)?)\s*\|\s*(\s*author\s*=\s*.+)\s*\(transferr?ed\s*by\s*(.+?)\)/im, '|$1 (transferred to commons by $4)$2\n|$3')
.replace(/\|(\s*source\s*=.+\s*(?:\(\s*)Original(?:ly)? uploaded on [a-z]+.wiki[a-z]+(?:\s*\)))((?:.|\n)+?)\/Original(?:ly)?\s+uploaded\s+by\s+.+?(\){0,1}\s*$)/im, '|$1 - $2$3')
// author cleanup
.replace(/(\|\s*[Aa]uthor\s*=\s*\[\[(?:\s*:\s*[a-z]+){1,2}\s*:[^\]|]+\|[^\]|]+\]\]) \d{2}:\d{2}, (?:\d.? [^\]|(\d]+|\d.? [^\]|(\d]+) \d+ \((?:CES?T|UTC)\).?/g, '$1')
.replace(/^(\|\s*author\s*=\s*)(?:Original upload(?:er was|ed by)|Uploaded by)/mi, '$1')
.replace(/^(\|\s*[Aa]uthor\s*=\s*)(.+?)\.?\s*(?:<\s*br\s*\/?>\s*)?\n+\s*\[\[\s*:\s*([a-z]{2,4}(?:-[a-z]{5})?\s*:\s*.+?\s*:\s*\2(?:\s*\|.*))\s*\]\]/mi, '$1[[:$3]]')
.replace(/^(\|\s*[Aa]uthor\s*=\s*)-*\s*\[\[\s*:\s*([a-z]{2,4}(?:-[a-z]{5})?)\s*:\s*[^\]]+?\s*:\s*([^\]]+?)(?:\s*\|[^\]]*)?\s*\]\](?:\s*\(?\[\[:\2:[^|\]:]+?:\s*\3\s*\|[^|\]:]+\]\]\)?)?(.*?)\s*\)?\s*\.?\s*(?:<\s*br\s*\/?>\s*)?\n+\s*(\[\[\s*:\s*\2\s*:\s*.+?\s*:\s*\3(?:\s*\|.*)\s*\]\] .*)$/mi, '$1$5$4')
.replace(/^(\|\s*[Aa]uthor\s*=.*) 0?\d{1,2}:\d{2}, \d{1,2}.? [^\d[\].,\s()';"]{3}.? \d{4} \((?:UTC|CES?T)\)?.?/m, '$1');
// check if original uploader appears not to be the author
if (cleanup.match(new RegExp(`\\{\\{\\s*(?:pd-(?:${PD_UNKNOWN})|cc|gfdl(?!-)|gfdl-(?!${GFDL_AUTHOR})|bild-pd-(?:frei|auteur))|attribution`, 'i'))) uploader_status = UNKNOWN;
if (cleanup.match(new RegExp(`\\{\\{\\s*(?:pd-(?:${PD_AUTHOR})|Јв-ја|مالکیت عمومی-خود |گنو-خود |Pd=self|self|gfdl-(?:${GFDL_AUTHOR}))`, 'i')) || de_wp_re.test(cleanup)) uploader_status |= AUTHOR;
if (cleanup.match(new RegExp(`\\{\\{\\s*(?:pd-(?!${PD_UNKNOWN}|review|${PD_AUTHOR}).+|larsencopyright)\\s*(?:\\}\\}|\\|)`, 'i'))) uploader_status |= NOTAUTHOR;
if (uploader_status === AUTHOR) cleanup = mto_uploader_is_author(cleanup);
if (uploader_status === NOTAUTHOR) cleanup = mto_uploader_isnt_author(cleanup);
authorinfo = mto_parse_author_info(cleanup);
if (authorinfo) {
author = authorinfo[0];
project_code = authorinfo ? authorinfo[1] : null;
username = authorinfo ? authorinfo[2] : null;
// {{Multilicense}} cleanup
cleanup = cleanup.replace(/\{\{\s*([Mm]ultilicense +replacing +placeholder(?: +new)?)\s*\|(?!.*\s*[Aa]uthor\s*=[^|]+\|\s*)(.*?)\}\}/m, `{{$1|author=${author}|$2}}`);
}
// own template (internationalization/standardization). I am perfectly aware that some code is run twice, but whatever
cleanup = own_replace_pre(cleanup);
cleanup2 = own_replace(cleanup, username, project_code, false);
if (cleanup !== cleanup2) {
cleanup = mto_own(cleanup2, 'own');
// own_replace may have altered the text; worth a retry
if (!username) {
authorinfo = mto_parse_author_info(cleanup);
if (authorinfo) {
project_code = authorinfo ? authorinfo[1] : null;
username = authorinfo ? authorinfo[2] : null;
}
}
}
// LOC-image automatic detection
cleanup = cleanup.replace(/(?:\s*?<\s*[Bb][Rr]\s*\/?>)*\s*(?:http:\/\/)?[\w-]+.loc.gov\/loc.pnp\/([\w.]+\w*\d).?(?:\s*?<\s*[Bb][Rr]\s*\/?>)*\s*/, '{{LOC-image|id=$1}}')
.replace(/([\s\S]+)(\{\{\s*[Ll]OC-image\s*\|\s*(?:(?:1|id)=)?[\w.]+\w\}\})([\s\S]+)(\|\s*[Ss]ource\s*=)([\s\S]+)/, '$1$3$4$2$5')
.replace(/([\s\S]+)(\|\s*[Ss]ource\s*=)([\s\S]+)(\{\{\s*[Ll]OC-image\s*\|\s*(?:(?:1|id)=)?[\w.]+\w\}\})([\s\S]+)/, '$1$2$4$3$5')
// OTRS template from de.wp cleanup
// eslint-disable-next-line no-useless-escape
.replace(/\{\{\s*[Oo](?:TRS(?:[ _]+permission)?|trs)\s*\|\s*(?:1\s*=)?(\d+)\s*(\|.*?\}\}|\}\})/, '{\{PermissionOTRS|id=$1$2')
// date fixup (rm needless text)
.replace(/(\|\s*[Dd]ate\s*=\s*)\d\d:\d\d, (\d\d?) ([A-Z][a-z]+) (\d{4}) \(UTC\)(?:\s*<\s*[Bb][Rr]\s*\/?>)?\s*[\s\n]\s*\(?(\{\{\s*[Dd]ate\s*\|\s*\4\s*\|\s*\d\d\s*\|\s*0?\2\s*\}\})\s*\(\s*\{\{\s*[Oo]riginal +upload +date\s*\}\}\s*\)\s*\)?/, '$1$5 ({{original upload date}})')
.replace(/\|(\s*date\s*=\s*.*?\s*?)(?:\(Uploaded on Commons at )?[\d- :]+\(UTC\)\s*[(/]original(?:ly)? uploaded at (\d{4})-(\d{2})-(\d{2})[\s\d:]+\)?/i, '|$1{{Date|$2|$3|$4}} ({{original upload date}})')
// circa/or/before/early/late/between date (also remove upload date if this is provided)
.replace(/(\|\s*[Dd]ate\s*=\s*)[Cc](?:(?:[Ii][Rr][Cc])?[Aa])?\.?\s*(\d{4}(?:\s*-\s*\d{1,2}(?:\s*-\s*\d{1,2})?)?)(\W)/, '$1{{circa|$2}}$3')
.replace(/(\|\s*[Dd]ate\s*=\s*)(\d{4})(?:\s*(-)\s*(\d{1,2}))?(?:\s*(-)\s*(\d{1,2}))?\s*[Oo][Rr]\s*(\d{4})(?:\s*(-)\s*(\d{1,2}))?(?:\s*(-)\s*(\d{1,2}))?\s*\.?\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\s*(?:\(?\{\{\s*[Dd]ate\s*\|\s*\d{4}s*\|\s*\d{1,2}s*\|\s*?\d{1,2}\s*\}\}\s*\(\s*\{\{\s*[Oo]riginal +upload +date\s*\}\}\s*\)\s*\)?)?\s*/, '$1{{other date|or|$2$3$4$5$6|$7$8$9$10$11}}\n')
.replace(/(\|\s*[Dd]ate\s*=\s*\{\{circa\|.+\}\})\s*\.?\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\s*\(?\{\{\s*[Dd]ate[|\s\d]+\}\}\s*\(\s*(?:\{\{\s*[Oo]riginal +upload +date\s*\}\}|first +version)\s*?\)(?:\s*;?\s*\{\{\s*[Dd]ate[|\s\d]+\}\}\s*\(\s*last\s+version\))?\s*?\)?/, '$1')
.replace(/(\|\s*[Dd]ate\s*=\s*)(?:[Bb][Ee][Ff][Oo][Rr][Ee]\s+|[Pp][Rr][Ee]\s*-)(\d{4}(?:\s*-\s*\d{1,2}(?:\s*-\s*\d{1,2})?)?)\s*\.?\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\n\s*\(?\{\{\s*[Dd]ate\s*\|\s*\d{4}s*\|\s*\d{1,2}s*\|\s*?\d{1,2}\s*\}\}\s*\(\s*\{\{\s*[Oo]riginal +upload +date\s*\}\}\s*\)\s*\)?/, '$1{{other date|before|$2}}')
.replace(/(\|\s*[Dd]ate\s*=\s*)(?:[Ll][Aa][Tt][Ee]\s+|[Pp][Rr][Ee]\s*-)(\d{4}(?:\s*-\s*\d{1,2}(?:\s*-\s*\d{1,2})?)?)\s*\.?\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\n\s*\(?\{\{\s*[Dd]ate\s*\|\s*\d{4}s*\|\s*\d{1,2}s*\|\s*?\d{1,2}\s*\}\}\s*\(\s*\{\{\s*[Oo]riginal +upload +date\s*\}\}\s*\)\s*\)?/, '$1{{other date|late|$2}}')
.replace(/(\|\s*[Dd]ate\s*=\s*)(?:[Ee][Aa][Rr][Ll][Yy]\s+|[Pp][Rr][Ee]\s*-)(\d{4}(?:\s*-\s*\d{1,2}(?:\s*-\s*\d{1,2})?)?)\s*\.?\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\n\s*\(?\{\{\s*[Dd]ate\s*\|\s*\d{4}s*\|\s*\d{1,2}s*\|\s*?\d{1,2}\s*\}\}\s*\(\s*\{\{\s*[Oo]riginal +upload +date\s*\}\}\s*\)\s*\)?/, '$1{{other date|early|$2}}')
.replace(/(\|\s*[Dd]ate\s*=\s*)(?:[Bb][Ee][Tt][Ww][Ee]{2}[Nn])\s*(\d{4})(?:\s*(-)\s*(\d{1,2}))?(?:\s*(-)\s*(\d{1,2}))?\s*(?:[Aa][Nn][Dd]|\W)\s*(\d{4})(?:\s*(-)\s*(\d{1,2}))?(?:\s*(-)\s*(\d{1,2}))?\s*\.?\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)?\s*(?:\(?\{\{\s*[Dd]ate\s*\|\s*\d{4}s*\|\s*\d{1,2}s*\|\s*?\d{1,2}\s*\}\}\s*\(\s*\{\{\s*[Oo]riginal +upload +date\s*\}\}\s*\)\s*\)?)?\s*/, '$1{{other date|between|$2$3$4$5$6|$7$8$9$10$11}}\n');
cleanup = mto_parse_date(cleanup, '(?:jan(?:uary?|\\.)?|gennaio)', '01');
cleanup = mto_parse_date(cleanup, '(?:feb(?:ruary?|\\.)?|febbraio)', '02');
cleanup = mto_parse_date(cleanup, '(?:(?:mar(?:ch|\\.)?)|mär[z\\.]?|marzo)', '03');
cleanup = mto_parse_date(cleanup, '(?:apr(?:il|\\.)?|aprile)', '04');
cleanup = mto_parse_date(cleanup, '(?:ma[iy]|maggio)', '05');
cleanup = mto_parse_date(cleanup, '(?:jun[ie\\.]?|giugno)', '06');
cleanup = mto_parse_date(cleanup, '(?:jul[iy\\.]?|luglio)', '07');
cleanup = mto_parse_date(cleanup, '(?:aug(?:ust|\\.)?|agosto)', '08');
cleanup = mto_parse_date(cleanup, '(?:sept?(?:ember|\\.)?|settembre)', '09');
cleanup = mto_parse_date(cleanup, '(?:o[ck]t(?:ober|\\.)?|ottobre)', '10');
cleanup = mto_parse_date(cleanup, '(?:nov(?:ember|\\.)?|novembre)', '11');
cleanup = mto_parse_date(cleanup, '(?:de[cz](?:ember|\\.)?|dicembre)', '12');
// {{date}} for single year dates
cleanup = cleanup.replace(/(\|\s*[Dd]ate\s*=\s*)\{\{Date\|(\d{4})\}\}(\s*\.?\s*(?:<\s*BR\s*\/?>\s*)?)$/im, '$1$2$3')
// fix single digit dates (first run)
.replace(/(\|\s*[Dd]ate\s*=\s*\{\{\s*[Dd]ate\s*\|\d{4}\s*\|)(\d)(?!\d)/, '$10$2')
.replace(/(\|\s*[Dd]ate\s*=\s*\{\{\s*[Dd]ate\s*\|\d{4}\s*\|\s*\d{2}\s*\|\s*)(\d)(?!\d)/, '$10$2')
// rm parens around entirely original upload date
.replace(/\((\{\{[Dd]ate[^}]+\}\}\s*\(\{\{original upload date\}\}\))\)/, '$1');
// run twice to get both possible instances of date (the regex can conflict with itself); avoid changing format when undesired
for (i = 0; i < 2; i++) {
// A date written in format \d\d.\d\d.\d\d\d\d or \d\d.\d\d.\d\d is very common in Germany, apparently
if (project_code === 'de') {
cleanup = cleanup.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(0?\d|[1-2][0-9]|3[0-1]).(0?\d|1[0-2]|\d).(\d{4})(?!\d)/, '|$1{{Date|$4|$3|$2}}')
// FIXME: change this in the year 2020!!
.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(0?\d|[1-2][0-9]|3[0-1]).(0?\d|1[0-2]|\d).([01]\d)(?!\d)/, '|$1{{Date|20$4|$3|$2}}')
.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(0?\d|[1-2][0-9]|3[0-1]).(0?\d|1[0-2]|\d).([2-9]\d)(?!\d)/, '|$1{{Date|19$4|$3|$2}}');
}
cleanup = cleanup.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(1[3-9]|2\d|3[01])[-\\/.\s,]+(0?\d|1[0-2]|\d)[-\\/.\s,]+(\d{4})(?!\d)/g, '|$1{{Date|$4|$3|$2}}')
.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(0?\d|1[0-2]|\d)[-\\/.\s,]+(1[3-9]|2\d|3[01])[-\\/.\s,]+(\d{4})(?!\d)/g, '|$1{{Date|$4|$2|$3}}')
.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(0?\d|1[0-2]|\d)[-\\/.\s,]+\2[-\\/.\s,]+(\d{4})(?!\d)/g, '|$1{{Date|$3|$2|$2}}')
.replace(/\|(\s*[Dd]ate\s*=[^{|]*\s*)(\d{4})\s*([/\\.\s])\s*(\d{1,2})\s*\3\s*(\d{2})(?!\d)/g, '|$1{{Date|$2|$4|$5}}')
.replace(/\|(\s*[Dd]ate\s*=[^{|]*\s*)(\d{4})\s*-\s*(\d)\s*-\s*(\d{2})(?!\d)/g, '|$1$2-0$3-$4')
.replace(/\|(\s*[Dd]ate\s*=[^{|]*\s*)(\d{4}) ?- ?(\d{2}) ?- ?(\d{2})(?!\d)(?!\s*\n)(?![|}])/g, '|$1{{Date|$2|$3|$4}}');
}
// run twice to get both possible instances of date (the regex can conflict with itself); avoid changing format when undesired
for (i = 0; i < 2; i++) {
cleanup = cleanup.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(\d|1[0-2]|\d(?!\d))[-\\/.\s,]+(\d{4})(?!(?:[-\\/.\s,]+\d{1,2}|\d))/mg, '|$1{{Date|$3|$2}}')
.replace(/\|(\s*[Dd]ate\s*=\s*(?:[^{|]*\n)?)(\d{4})[-\\/.\s,]+(0\d|1[0-2]|\d(?!\d))(?!(?:[-\\/.\s,]+\d{1,2}|\d))/mg, '|$1{{Date|$2|$3}}');
}
// don't need to have upload date if a date is already provided
cleanup = cleanup.replace(/(\|\s*[Dd]ate\s*=\s*\{\{\s*[Dd]ate[|\s\d]+\}\})\s*.?(?:\s*;?\s*<\s*[Bb][Rr]\s*\/?>)?\s*\(?(?:\{\{\s*[Dd]ate[|\s\d]+\}\}\s*\(\s*(?:\{\{\s*[Oo]riginal +upload +date\s*\}\}|first +version)\s*?\)|\{\{\s*[Oo]riginal +upload +date\s*\|[\d-\s]+\}\})(?:\s*;?\s*\{\{\s*[Dd]ate[|\s\d]+\}\}\s*\(\s*last\s+version\))?\s*?\)?/, '$1')
// fix single digit dates, which confuses the parser below
.replace(/\{\{\s*[Dd]ate\s*\|\s*(\d+)\s*\|\s*(\d)\s*((?:\|\s*\d+\s*)?)\}\}/g, '{{Date|$1|0$2$3}}')
.replace(/\{\{\s*[Dd]ate\s*\|\s*(\d+)\s*\|\s*(\d{2})\s*\|\s*(\d)\s*\}\}/g, '{{Date|$1|$2|0$3}}')
.replace(/\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\|(\d{1,2})\s*\|(\d{1,2})\s*\}\}\s*\(first version\)\s*;?\s*\{\{\s*[Dd]ate\s*\|\s*\1\s*\|\2\s*\|\3\s*\}\}\s*\(last version\)/, '{{Date|$1|$2|$3}} ({{original upload date}})')
.replace(/\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\|(\d{1,2})\s*\|(\d{1,2})\s*\}\}\s*\(first version\)\s*/m, '{{Date|$1|$2|$3}} ({{original upload date}})')
.replace(/\|(\s*[Dd]ate\s*=\s*(\{\{[Dd]ate\|.+\}\}))\s*(?:<\s*br\s*\/?>)?\s*\n*\s*\(?\2\s*\(\s*\{\{\s*[Oo]riginal upload date\s*\}\}\s*\)?\)?/, '|$1')
.replace(/(?:\(\s*)?\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\|(\d{1,2})\s*\|(\d{1,2})\s*\}\}\s+\(?\{\{\s*[Oo]riginal upload date\s*\}\}\)?(?:\s*\))?/mg, '{{original upload date|$1-$2-$3}}')
.replace(/\d{2}:\d{2}, +\d{1,2} +[A-Z][a-z]+ +\d{4} +\([A-Z]{3,4}\)(?:<\s*br\s*\/?>)?\s*\n*\s*(?:\(\s*)?(\{\{\s*[Oo]riginal +upload +date\s*\|\s*[\d-]+\s*\}\})(?:\s*\))?/mg, '$1') // ~~~~~ for date is ignored if upload date present
// {{date}} format -> xxxx-yy-zz format where possible
.replace(/=(\s*)\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\|(\d{2})\s*\|(\d{2})\s*\}\}(\s*\|)/mg, '=$1$2-$3-$4$5')
.replace(/=(\s*)\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\|(\d{1,2})\s*\}\}(\s*\|)/mg, '=$1$2-$3$4')
.replace(/=(\s*)\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\}\}(\s*\|)/mg, '=$1$2$3')
// |date=YYYY-MM-DD HH:MM:SS is improperly parsed above; fix that ISOdate
.replace(/(\|\s*[Dd]ate\s*=\s*)\{\{\s*[Dd]ate\s*\|\s*(\d{4})\s*\|\s*(\d{2})\s*\|(\d{2})\s*\}\}\s*(\d{2}:\d{2}(?::\d{2})?)/, '$1$2-$3-$4 $5')
.replace(/(\|\s*[Dd]ate\s*=\s*)\{\{\s*[Ii]SOdate\s*\|\s*(\d{4}-\d{2}-\d{2} +\d{2}:\d{2}(?::\d{2})?)\s*\}\}(\s*(?:\|\s*(?:[Dd]escription|[Dd]ate|[Aa]uthor|[Pp]ermission|[Oo]ther[ _]+versions|[Oo]ther[ _]+fields)\s*=|\}\}))/, '$1$2$3')
// rm unnecessary comments, templates that upload bot needlessly transports over; __NOTOC__
.replace(/^\s*\{\{\s*(?:[Tt]emplate[\s_]+other|[Tt]ls?x?p?|FULLROOTPAGENAME|[Nn]s[\s_]+has[\s_]+subpages|!\)|\(!|!!?|[a-zA-Z]?[Mm]box|[Jj]ULIANDAY|[Ll]an|[Rr]ed|[Ee]n|[Mm]ax(?:\/2)?|[Cc]ite[\s_]+book|[Cc]itation\/core|[Cc]itation\/make[\s_]+link)\s*\|?\s*\}\}\s*$/gm, '')
.replace(/\{\{\s*([Pp][Dd]-ineligible|[Pp]D-[Tt]rivial|[Pp]D-uegnet|[Pp]D-Ineligible)\s*\|\s*Commons\s*=[^}|]+\}\}/, '{{$1}}')
.replace(/\{\{P[Dd]-user-w\s*\|\s*(als|ar|az|bg|ca|cs|da|de|en|es||fa|fi|fr|he|hi|hr|hu|it|ja|lt|ml|nl|nn|no|pl|pt|ro|ru|sk|sl|th|uk|vls|zh)\s*\|\s*wikipedia\s*\|\s*([^|}]+)\s*\}\}/m, '{{PD-user-$1|$2}}')
.replace(/\{\{P[Dd]-user\s*\|\s*([^|}]+)\s*\|\s*(als|ar|az|bg|ca|cs|da|de|en|es|fa|fi|fr|he|hi|hr|hu|it|ja|lt|ml|nl|nn|no|pl|pt|ro|ru|sk|sl|th|uk|vls|zh)\s*\}\}/m, '{{PD-user-$2|$1}}');
if (!cleanup.match(/\{\{\s*[Cc]heck\s+categories/)) cleanup = cleanup.replace(/^__NOTOC__\s*?\n/gm, '');
// specific silliness reserved for de.wp transfers
if (username && project_code) {
self_author_text = `[[:${project_code}:User:${username}|${username}]] at [http://${project_code}.wikipedia.org ${project_code}.wikipedia]`;
cleanup = cleanup.replace(de_wp_re, `{{Self|author=${self_author_text}|cc-by-sa-3.0|cc-by-sa-3.0-de|GFDL|migration=redundant}}`);
}
// Template loop for GFDL fix
cleanup = cleanup
.replace(/(\{\{\s*[Ss]elf2?\s*(?:\|[^|}]+)*\|)\s*GFDL-(?:user-[a-z]+-with-disclaimers|self-with-disclaimers|self-en)/,
'$1GFDL-with-disclaimers')
.replace(/(\{\{\s*[Ss]elf2?\s*(?:\|[^|}]+)*\|)\s*GFDL-(?:self|user(?:-[a-z]+)?)(?:-no-disclaimers)?/,
'$1GFDL')
.replace(/(\{\{\s*[Ss]elf2?\s*(?:\|[^|}]+)*\|GFDL)\s*\|\s*GFDL\|/,
'$1|')
// unknown template
.replace(/(\|\s*date\s*=\s*)(?:unknown(?: +date)?|not known|desconocido|desconhecido|unbekannt)\s*\.?\s*(?:(?:<\s*br\/?>)?\n?|\n)(?:(?!\|).+\n)?(\s*(?:\||\}\}))/i,
'$1{{unknown|date}}\n$2')
.replace(/(\|\s*author\s*=\s*(?:[^{}<>|]+)?)(?:unknown(?: +author)?|not known|desconocido|desconhecido|unbekannt)\s*\.?\s*(?:(?:<\s*br\/?>)?\n?|\n)((?!\|).+\n)?(\s*(?:\||\}\}))/i,
'$1{{unknown|author}}\n$2$3')
// useless other_versions messages
.replace(/(\|\s*other[_ ]versions\s*=\s*)(?:no|none(?:\s+known)?|nein|yes|keine|-+)\s*\.?\s*\n(\s*(?:\||\}\}))/i,
'$1\n$2')
// duplicate templates
.replace(/\{\{\s*(?:[Cc]c-by-sa-3.0-migrated|[Ll]icense +migration +not +eligible)\s*\}\}\s*?\n?/, '');
cleanup = iterative_replace(/\{\{\s*(?:[Tt]rademark(?:ed)?|[Tt][Mm]|[Ss]VG-Logo|®)\s*\}\}([\s\S]*)\{\{\s*(?:[Tt]rademark(?:ed)?|[Tt][Mm]|[Ss]VG-Logo|®)\s*\}\}/, '{{Trademarked}}$1', cleanup);
cleanup = iterative_replace(/\{\{\s*(?:[Mm]oney-US|[Pp]D-USGov-money)\s*\}\}([\s\S]*)\{\{\s*(?:[Mm]oney-US|[Pp]D-USGov-money)\s*\}\}/, '{{PD-USGov-money}}$1', cleanup);
cleanup = iterative_replace(/\{\{\s*(?:[Pp]D-UKGov|[Pp]D-BritishGov|[Dd]omaine[ _]+public[ _]+UK|[Dd]omainePublicGouvUK|[Pp]D-UK-Gov|نگاره[ _]+بریتانیا)\s*\}\}([\s\S]*)\{\{\s*(?:[Pp]D-UKGov|[Pp]D-BritishGov|[Dd]omaine[ _]+public[ _]+UK|[Dd]omainePublicGouvUK|[Pp]D-UK-Gov|نگاره[ _]+بریتانیا)\s*\}\}/, '{{PD-UKGov}}$1', cleanup);
cleanup = iterative_replace(/(\{\{\s*(?:[Cc]c-by-sa-3.0,2.5,2.0,1.0|[Cc]c-by-sa-all)\s*\}\}\s*?){2,}/, '{{Cc-by-sa-3.0,2.5,2.0,1.0}}', cleanup);
cleanup = iterative_replace(/\{\{\s*(?:[Pp]D-China|[Pp]D-cn|[Cc]hina-PD)\}\}([\s\S]*?)\{\{\s*(?:[Pp]D-China|[Pp]D-cn|[Cc]hina-PD)\s*\}\}/, '{{PD-China}}$1', cleanup);
cleanup = iterative_replace(/\{\{\s*(?:PD-USGov(-[A-Za-z]+)(-[A-Za-z-]+?))\s*\}\}([\s\S]*?)\{\{\s*PD-USGov\1?\2\s*\}\}/, '{{PD-USGov$2}}$3', cleanup);
cleanup = iterative_replace(/\{\{\s*bad *jpe?g\s*\}\}([\s\S]*)\{\{\s*bad *jpe?g\s*\}\}/i, '{{badjpeg}}$1', cleanup);
cleanup = iterative_replace(/\{\{\s*(?:(?:convert *to|to|should *be)? *svg|vectorize)\s*\}\}([\s\S]*)\{\{\s*(?:(?:convert *to|to|should *be)? *svg|vectorize)\s*\}\}/i, '{{Convert to SVG}}$1', cleanup);
if (cleanup.search(/\{\{\s*(?:[Pp]D-text(?: *|-)logo|[Pp]d-textlogo|[Tt]extlogo|[Pp]D-Markenrecht)\s*(?:\|[\s\S]*?)?\}\}/) !== -1 && cleanup.search(/\{\{\s*(?:[Tt]rademark(?:ed)?|[Tt][Mm]|[Ss]VG-Logo|®)\s*(?:\|[\s\S]*?)?\}\}/) !== -1) cleanup = iterative_replace(/\{\{\s*Bild-LogoSH\s*(?:\|[\s\S]*?)?\}\}\s*?\n?/, '', cleanup);
// templates which are unneeded immediately after license header FIXME: IT FREEZES
// cleanup = iterative_replace(/^((==)\s*.+\s*\2\s*(?:{{.+?}}\s*)*\n)\s*{{\s*(?:description +missing|date|-|clr)\s*}}\s*?$/im, '$1', cleanup);
// cleanup = iterative_replace(/^((==)\s*.+\s*\2\s*\n(?:\s*\{\{.+\}\}\s*\n)*)\s*\{\{\s*hidden\s*\}\}\s*?$/im, '$1', cleanup);
// redundant pd-old
cleanup = cleanup.replace(/\{\{\s*[Pp]D-old\s*\}\}\s*?\n?\s*\{\{\s*[Pp]D-old-100\s*\}\}/, '{{PD-old-100}}')
// pd-art cleanup
.replace(/\{\{\s*([Pp][Dd]-[Aa]rte?|[Bb]ild-PD-Kunst|[Pp]D-kunst)\s*\}\}\s*?\n?\s*\{\{\s*([Pp]D-[^|]+)\s*\}\}/, '{{$1|$2}}')
.replace(/\{\{\s*(?:[Pp][Dd]-[Aa]rte?|[Bb]ild-PD-Kunst|[Pp]D-kunst)\s*\|\s*[Pp][Dd]-old-(70|100)\s*\}\}/, '{{PD-art-$1}}')
.replace(/\{\{\s*(?:[Pp][Dd]-[Aa]rte?|[Bb]ild-PD-Kunst|[Pp]D-kunst)\s*\|\s*[Pp][Dd]-old\s*\}\}/, '{{PD-art-70}}')
.replace(/\{\{\s*[Pp][Dd]-US\s*\}\}\s*\{\{\s*([Pp][Dd]-[Aa]rte?|[Bb]ild-PD-Kunst|[Pp]D-kunst)\s*\}\}/, '{{$1|PD-US}}');
// insert header, but only above an {{information}} template and if there is at least one other header
// on the page already, for asthetic purposes (request by Leyo)
// TODO: what in the world is going on here?
headercheck = cleanup.replace(/([^{|]|\s)\{(?!\{)/, '$1MTOWASHERE'); // removing markings which will confuse parser
// headercheck = cleanup.replace(/((?:\{\{)+)\{(?!\{)/, "$1MTOWASHERE");
headercheck = cleanup.replace(/([^}]|\s)\}(?!\})/, '$1MTOWASHERE');
// headercheck = cleanup.replace(/((?:\}\})+)\}(?!\})/, "$1MTOWASHERE");
headercheck = iterative_replace(/\{\{(?:[^{|]|\n)+?\}\}/g, 'MTOTEMPLATE', headercheck); // only headers that aren't already inside a template will suffice, per what seems right to me
if (headercheck.match(/\n(=+).+\1\s*\n/)) cleanup = cleanup.replace(/^\s*(\{\{[Ii]nformation\s*\|)/, '== {{int:filedesc}} ==\n$1');
/* uploader_status was set wayyyy above; this is a second run (lazy coding) */
if (uploader_status === NOTAUTHOR) cleanup = mto_uploader_isnt_author(cleanup);
// further cleanup second upload type
cleanup = cleanup.replace(/(\|\s*[Ss]ource\s*=\s*.*)(\n\s*\|(?:[\s\S]*\|)?\s*[Aa]uthor\s*=\s*.*)\s*(\([Tt]ransferr?ed +by +\[\[.+\]\]\))/, '$1 $3$2')
.replace(/(\(Originally uploaded on [a-z-]{1,6}.w[a-z]+)\)?\s*-\s*\(?([Tt]ransferr?ed +by +\[\[.+\]\]\))/, '$1 - $2')
.replace(/([Aa]uthor\s*=.*?)\s*(?:\([Tt]ransferr?ed +by +\[\[.+\]\]\))/, '$1') // sometimes leftover text from {{Self}} template
// {{original file page|…}}
.replace(/The\s+original\s+description\s+page\s+(?:is\/was|is|was)\s+\[http:\/\/([a-z-]+.wik[a-z]+).org\/w\/index.php\?title=.+?(?::|%3[Aa])(.+?)\s+here(?:\].|.\])\s+All\s+following\s+user\s+names\s+refer\s+to\s+\1./g, '{{original file page|$1|$2}}')
.replace(/This file was originally uploaded at ([a-z-]+.wik[a-z]+) as \[http:\/\/\1.org\/wiki\/.+?(?::|%3[Aa])(\S+?)\s+[^\]]+\], before it was transferr?ed to Commons./, '{{original file page|$1|$2}}')
// {{transferred from|…}}
.replace(/(\|\s*[Ss]ource\s*=\s*|<\s*br\s*\/?\s*>\s*|\n\s*\*)Transferr?ed from \[(?:https?:)?\/\/([a-z-]{2,}.w[a-z]+).org\/? \2\](?:(?:; transferr?ed|; transfer was stated to be made)?(?: to Commons)? by \[\[User:([^\]]+)\]\])?(?: using (\[\[?.+?\]\]?))?.?/g, '$1{{transferred from|1=$2|2=$3|3=$4}}')
.replace(/\{\{transferred from\|1=([^|=}]*)\|2=([^|=}]*)\|3=([^|=}]*)\}\}/g, '{{transferred from|$1|$2|$3}}')
.replace(/\{\{transferred from\|(1=)?(.*)\|(2=)?(.*)\|(3=)?\}\}/g, '{{transferred from|$1$2|$3$4}}')
.replace(/\{\{transferred from\|(1=)?(.*)\|(2=)?\}\}/g, '{{transferred from|$1$2}}')
.replace(/(\{\{transferred from\|.*?\|.*?\|\s*(?:3\s*=)?\s*)\[(?:https?:)?\/\/(?:tools.wikimedia.de|toolserver.org)\/~magnus\/commonshelper.php ([Cc][Oo][Mm][Mm][Oo][Nn][Ss][Hh][Ee][Ll][Pp][Ee][Rr])\](\s*\}\})/, '$1$2$3')
// {{Self|self|self…|license}} bug
.replace(/\{\{(?:\s*[Ss]elf2?\s*\|){2,}/g, '{{Self|')
// {{original description page|…}} Fix urlencode filename QUERY
.replace(/\{\{\s?[Oo]riginal description(?: page)?\s?\|\s?[^|\n]+\|\s*([^\n]+)\s?\}\}/, function (m, p1) {
return m.replace(p1, p1.replace(/ /g, '+'));
})
// {{user at project|…}}
.replace(/\[\[:([a-z-]{2,})(?::[A-Za-z]+)?:[Uu][Ss][Ee][Rr]:([^|[\]]+)\|\2\]\]\s+at\s+\[(?:https?:)?\/\/\1.(w[a-z]+).org\/? [^|[\]]+\]/g, '{{user at project|1=$2|2=$3|3=$1}}')
.replace(/\[\[:[A-Za-z]+:([a-z-]{2,}):[Uu][Ss][Ee][Rr]:([^|[\]]+)\|\2\]\]\s+at\s+\[(?:https?:)?\/\/\1.(w[a-z]+).org\/? [^|[\]]+\]/g, '{{user at project|1=$2|2=$3|3=$1}}')
.replace(/\{\{user at project\|1=([^\]|=]+?)\|2=([^\]|]+?)\|3=([^\]|]+?)\}\}/g, '{{user at project|$1|$2|$3}}');
// move category related stuff to bottom
/* jslint unparam: true */
function replaceCat($0, $1, $2, $3, $4, $5, $6) {
$5 = $5 || '';
$6 = $6 || '';
cleanup = $1 + $6;
return `[[Category:${$2.toUpperCase()}${$3}${$4 || ''}]]${$5}`;
}
// cleanup += "\n";
while (catLimit--) {
nextcat = cleanup.replace(re_cat, replaceCat);
if (nextcat === cleanup) break;
// if (/\[\[Category:Uploaded with UploadWizard\]\]/.test(nextcat)) continue; // exclude deprecated cat
categories.push(nextcat);
hascat = true;
// cleanup = cleanup.replace(re_cat, "$1$6");
}
categories = categories.sort().join('\n');
re_unc = /^([\s\S]*)\{\{\s*([Uu]ncat(?:egorized)?(?:\s*\|(?:.|\n)*?)*?)\s*\}\}(?:(\s*%%%MTOCOMMENT\d+%%%)?\s*?\n)?([\s\S]*?)$/;
unc = cleanup.replace(re_unc, '{{$2}}$3\n');
if (unc !== cleanup) {
// positive match: uncat present
// even if categories isn't empty, they might be hidden categories, so we don't want to remove {{uncat}}
categories = unc + categories;
cleanup = cleanup.replace(re_unc, '$1$4');
}
re_ds = /^([\s\S]*)\{\{\s*DEFAULTSORT\s*:([^[\]{]*?)\}\}(?:(\s*%%%MTOCOMMENT\d+%%%)?\s*?\n)?([\s\S]*?)$/;
ds = cleanup.replace(re_ds, '{{DEFAULTSORT:$2}}$3\n');
if (ds !== cleanup) {
// positive match: DEFAULTSORT present
categories = ds + categories;
cleanup = cleanup.replace(re_ds, '$1$4');
}
re_chc = /^([\s\S]*)(\{\{\s*[Cc]heck +categories[^}]+\}\})(\s*%%%MTOCOMMENT\d+%%%)?(\s*?\n[\s\S]*?)$/;
chc = cleanup.replace(re_chc, '$2$3\n');
if (chc !== cleanup) {
// positive match: checkcategories present
categories = chc + categories;
cleanup = cleanup.replace(re_chc, '$1$4');
}
re_cs_chc = /^([\s\S]*)(%%%MTOCSCOMMENT\d+%%%)(\s*?\n[\s\S]*?)$/;
cs_chc = cleanup.replace(re_cs_chc, '$2\n');
if (cs_chc !== cleanup) {
// positive match: commonsense comment present
categories = cs_chc + categories;
cleanup = cleanup.replace(re_cs_chc, '$1$3');
}
cleanup = cleanup.replace(/\s*$/, '\n\n'); // we want exactly one trailing newline character
// remove duplicate categories
categories = iterative_replace(/\[\[Category:(.+?)(\s*\|[^\]]*)?\]\](.*?)\n((?:.+\n)*?)\[\[Category:\1(?:\s*\|[^\]]*)?\]\](.*?)\n/, '[[Category:$1$2]]$3$5\n$4', categories);
categories = iterative_replace(/^\s*%%%MTOCOMMENT\d+%%%s*$/m, '', categories);
cleanup += categories;
// add {{int:license-header}} where no license header exists, and it unambiguously would be appropriate
// first, search for end of {{Information}} template, if applicable
infoend = cleanup.search(/\{\{\s*[Ii]nformation/);
for (template_level = 1; template_level > 0 && infoend > -1; undefined) {
infoend += 2;
opennext = cleanup.indexOf('{{', infoend);
closenext = cleanup.indexOf('}}', infoend);
if (closenext === -1) {
infoend = -1;
break;
}
if (opennext < closenext && opennext !== -1) {
infoend = opennext;
template_level++;
} else {
infoend = closenext;
template_level--;
}
}
if (infoend === -1) infoend = 0;
else infoend += 2;
cleanup_pre = cleanup.substr(0, infoend);
cleanup_post = cleanup.substr(infoend);
cleanup_post = cleanup_post.replace(/^((?:\s*?\{\{\s*(?:(?:should *be|(?:convert *)?to) *(?:svg|png)|svg|vectorize|artifacts|blurry|low quality[\w-., ]*|wrong +license|disputed[\w-., ]*|bad *(?:jpe?g|gif|svg)|crop|cleanup[\w-., ]*|ifc|(?:object +|globe +)?location[\w-., ]*)\s*(?:\|\s*[^}]+)?\}\})*)\s*(\{\{\s*(?:fop[\w-., ]*|freedom +of +panorama|cc(?:-zero|-by[\w-., ]*)?|gfdl[\w-., ]*|pd[\w-., ]*|trademark|(?:image|remove +)water +mark|self2?|Propio|[Сс]ебе|jac|multilicense[\w-., ]*|attribution[\w-., ]*|fal[\w-., ]*|GPL[\w-., ]*|wik(?:i[mp]edia|isource|iquote|iversity|tionary)-screenshot|wikiportrait|wikimedia +project +screenshot|flickrr?eview|license *review|ipernityreview|openphotoreview|panoramioreview|PD-?review|Picasa(?:review|web)|GFDL[\w-., ]*)\s*(?:\|\s*[^}]+)?\}\})/i, '$1\n=={{int:license-header}}==\n$2');
cleanup = cleanup_pre + cleanup_post;
// remove {{ImageUpload|…}} (outside comments)
cleanup = cleanup.replace(/\{\{\s*[Ii]mageUpload\s*(?:\|.+?)?\}\}(?:(\n)\n*)?/, '$1')
// remove {{ImageUpload|…}} (inside comments)
.replace(/<!--\s*\{\{\s*[Ii]mageUpload\s*(?:\|.+?)?\}\}\s*-->(?:(\n)\n*)?/, '$1')
// unnecessary transfer message for User:Boteas (per request from Leyo)
.replace(/(\|\s*[Ss]ource\s*=[\s\S]+?)\s*(?:<\s*[Bb][Rr]\s*\/?>\s*|\s+)[Tt]ransferr?ed +from +\[?[a-z-.:/ ]+\]? +to +[Cc]ommons +by +\[\[\s*[Uu]ser\s*:\s*[Bb]oteas\]\] .+/i, '$1')
.replace(/(\|\s*[Ss]ource\s*=\s*[Tt]ransferr?ed +from +\[?[a-z-.:/ ]+\]? +to +[Cc]ommons) +by +\[\[\s*[Uu]ser\s*:\s*[Bb]oteas\]\](?: +using +\[[^\]]+\])?\.?\s*$/mi, '$1')
// remove transfer message if it doesn't communicate anything, and other text isn't present
.replace(/(\|\s*[Ss]ource\s*=[\s\S]{3,}?)\s*(?:<\s*[Bb][Rr]\s*\/?>\s*|\s+)(?:[Tt]ransferr?ed +from +\[?[a-z-.:/ ]+\]?(?: +to +[Cc]ommons)?(?: +using +\[[^\]]+\])?|\{\{\s*[Tt]ransferred +from\s*\|[^|]+?\}\})\.?\s*$/m, '$1')
.replace(/(\|\s*[Ss]ource\s*=.+?)\s*(?:<\s*[Bb][Rr]\s*\/?>\s*)(\|\s*(?:[Dd]ate|[Aa]uthor|[Pp]ermission|[Oo]ther[_ ]versions)\s*=)/, '$1\n$2') // rm extra <br>
// move down transfer message
.replace(/(\|\s*[Ss]ource\s*=\s*)(\{\{\s*[Tt]ransferred[ _]+from\|.*?\}\})\.?\s*(?:<\s*[Bb][Rr]\s*\/?>)?\s*(\{\{\s*[Oo]riginal[ _]+text[\s\S]+?=+\s*\{\{\s*[Oo]riginal[ _]+upload[ _]+log\s*\}\}\s*=+\s*)/, '$1$3$2 ')
.replace(/(\|\s*[Ss]ource\s*=\s*\S[\s\S]+?\s*)(?:\s*<\s*[Bb][Rr]\s*\/?>.?)?\s*\*?\s*\(\s*(\{\{\s*[Tt]ransferred[ _]+from\|.*?\}\})\s*\).?([\s\S]+?\{\{\s*[Oo]riginal[ _]+upload[ _]+log\s*\}\}\s*=+\s*)/, '$1$3$2 ')
.replace(/(\|\s*[Ss]ource\s*=\s*\S[\s\S]+?\s*?)(?:\s*<\s*[Bb][Rr]\s*\/?>.?)?\s*\*?\s*(\{\{\s*[Tt]ransferred[ _]+from\|.*?\}\}).?([\s\S]+?\{\{\s*[Oo]riginal[ _]+upload[ _]+log\s*\}\}\s*=+\s*)/, '$1$3$2 ');
// rebuild <nowiki>, <!-- --> tags
cleanup = rebuild_comments_nowikis(cleanup, tokens, true);
// spacing 0
cleanup = iterative_replace(/((=+)\s*\{\{\s*int\s*:\s*license-header\s*\}\}\s*\2*\n(?:\s*?\{\{.+\}\}\n\s*?)*(?:\s*?\{\{.+\}\}\n\s*?))\s*\n+/, '$1', cleanup)
// spacing 1
.replace(/(dimensions\s*\|\s*comment)(\s*\n){2,}\*/i, '$1\n*') // inexplicably, this bothers me beyond measure
// completely empty nowikis… no reason for these
.replace(/\s*(?:''|<small>)\s*<nowiki>\s*<\/nowiki>\s*(?:''|<\/small>)\s*?\n/gm, '\n') // completely empty… just get rid of whole clause
.replace(/\n<!-- Templates .+ do not appear to exist on commons. -->\s*\n/, '\n');
if (hascat) cleanup = cleanup.replace(/<!--\s*remove\s*this\s*line\s*once\s*you\s*have\s*added\s*categories\s*-->\s*/i, '');
// spacing 2 (need to take apart comments again)
cnwt = parse_comments_nowikis(cleanup);
cleanup = cnwt[0];
tokens = cnwt[1];
cleanup = cleanup.replace(/^\s+((=+)\s*\{\{\s*[Ii]nt\s*:\s*[Ff]iledesc\s*\}\}\s*\2\s*\n)/, '$1')
.replace(/\n+(=+)(.+)\1\s*?\n+/g, '\n\n$1$2$1\n')
.replace(/([^=])\s*(\[\[Category:.*?\]\]|\{\{\s*[Uu]ncat(?:egorized)?[\s\S]*?\s*\}\}|\{\{\s*DEFAULTSORT\s*:([^[\]{]*?)\}\}|\{\{\s*[Cc]heck +categories[\s\S]*?\s*\}\}|<!--\s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Ii][Ee][Ss]\s*[Bb][Yy]\s*(?:[Cc][Oo][Mm][Mm][Oo][Nn][Ss][Ee][Nn][Ss][Ee]|[Cc][Hh][Ee][Cc][Kk][Uu][Ss][Aa][Gg][Ee])\s*-->)/, '$1\n\n$2');
cleanup = rebuild_comments_nowikis(cleanup, tokens, false);
if (textbox.length) {
textbox.val(cleanup);
let sumTxt = sum.value.trim();
if (orgText !== cleanup.trim()) {
if (!sumTxt) sum.value = summary;
else if (sumTxt.indexOf(summary) === -1) sum.value = `${sumTxt} +${summary}`;
}
}
if (fileContent) {
fileContent.text = cleanup;
mw.hook('gadget.cleanup.done').fire(fileContent);
}
}
function mto_cleanup_ts() {
$('<form>', {
action: '//tools.wmflabs.org/magog/do_cleanup.php',
method: 'post',
style: 'display:none'
}).append($('<input>', {
name: 'image',
value: pn
})).append($('<textarea>', {
name: 'text',
text: getTextbox().val()
})).append($('<input>', {
name: 'summary',
value: sum.value
}))
.appendTo('body')
.submit();
}
function post_cleanup_ts() {
let textbox = getTextbox(),
cleanupTmp = textbox.val(),
// store comments, nowikis as is to avoid changing them
cnwt = parse_comments_nowikis(cleanupTmp),
cleanup = cnwt[0],
tokens = cnwt[1];
// mark as bot move checked
cleanup = bot_move_checked(cleanup);
// restore comments
cleanup = rebuild_comments_nowikis(cleanup, tokens, false);
// add to textbox
textbox.val(cleanup);
// make a null edit to the edit summary, to turn off the MediaWiki flag which incorrectly warns that the summary is empty
sum.value += ' ';
}
function fastCleanup() {
let textbox = getTextbox(),
cancel = 0,
modal;
function escape(text) {
let escapeChars = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
'\'': '''
};
return text.replace(/[&<>"']/g, s => escapeChars[s]);
}
function showMessage(backgroundColor, type, message) {
modal
.dialog('option', 'height', '178')
.dialog('option', 'width', '486')
.css('background-color', backgroundColor)
.html(`${type}:<br>${message}`)
// append close button
.append('<br><br><input type="button" id="closeButton" value="OK">')
.find('#closeButton')
.click(() => {
modal.dialog('close');
});
}
function error(message) {
showMessage('#faa', 'Error', message);
}
function warning(message) {
showMessage('#aaf', 'Warning', message);
}
function ajaxError() {
if (!cancel) error('Unable to communicate with Toolserver. If the error persists, please contact the bot owner.');
}
function ajaxSuccess(response) {
if (!cancel) {
if (response.error) {
error('Unknown error. If it persists, please contact the bot owner.');
} else {
modal.on('dialogclose', () => {
let updateText,
update = 1;
switch (response.changes) {
case 'major':
updateText = 'Text updated.';
break;
case 'minor':
updateText = 'Text updated (minor changes only).';
break;
default:
updateText = 'No updates.';
update = 0;
}
mw.notify(updateText);
sum.value = response.summary;
if (update) textbox.val(response.text);
});
if (response.warnings && response.warnings.length > 0) {
let warningsText = '';
response.warnings.forEach(function (el) {
warningsText += `<li>${escape(el)}</li>`;
});
warning(warningsText);
} else {
modal.dialog('close');
}
}
}
}
modal = $('<div style="text-align:center">').dialog({
autoOpen: 0,
closeOnEscapeType: 0,
dialogClass: 'noClose',
height: 140,
modal: 1
}).off('dialogclose') // remove previous handlers
.on('dialogclose', () => {
cancel = 1;
})
.html('Loading…')
.dialog('open');
modal.find('.ui-dialog-titlebar-close').hide();
$.ajax('//tools.wmflabs.org/magog/do_cleanup_json.php', {
dataType: 'json',
data: {
image: pn,
text: textbox.val(),
summary: sum.value
},
error: ajaxError,
global: true,
type: 'POST',
success: ajaxSuccess
});
}
// stolen shamelessly and modified from the add {{information}} template text
function add_toolbox_button(text, callback, title) {
cleanBox
.find('ul')
.first()
.append(
$('<li>').append($('<a>')
.append(text)
.attr({
'className': 'external',
'title': title,
'href': '#'
})
.on('click', function () {
callback();
return false;
}))
);
}
function addFunction(functionId, callback, buttonDisplayName, forceImmediateCallback, title) {
// don't add the same function twice
if (linksShown[functionId]) return;
linksShown[functionId] = true;
if (mw.util.getParamValue('functionName') === functionId) {
$(() => {
callback(functionId);
});
}
if (!/^(edit|submit)$/.test(mw.config.get('wgAction')) && !forceImmediateCallback) {
callback = Array.isArray(callback) ? callback[1] : function () {
location.href = `${mw.config.get('wgScript')}?title=${encodeURIComponent(pn)}&action=edit&functionName=${functionId}`;
};
} else if (Array.isArray(callback)) {
callback = callback[0];
}
if (buttonDisplayName) {
$(() => {
add_toolbox_button(buttonDisplayName, callback, title);
});
}
}
mw.loader.using(['mediawiki.util'], () => {
if (fileNamespace || pn === 'Special:Upload') {
addFunction('mto_cleanup', mto_cleanup, 'cleanup JS', pn === 'Special:Upload', '(fast, maybe unsafe)');
if (pn !== 'Special:Upload') {
addFunction('cleanup TS', [fastCleanup, mto_cleanup_ts], 'cleanup TS', false, 'Toolserver version (slower but safe)');
addFunction('post_cleanup_ts', post_cleanup_ts, null);
}
if (getTextbox().length) {
addFunction('own', mto_wrapper, '{{Own}}');
addFunction('ownbased', mto_wrapper, '{{Own based}}');
addFunction('selfphoto', mto_wrapper, '{{Self-photographed}}');
addFunction('ownoriginaluploader', mto_wrapper, '{{Own work by original uploader}}');
}
$('#p-tb').append(cleanBox);
}
mw.libs.fastCleanup = fastCleanup;
// window.addCleanupFunction = addFunction;
mw.hook('gadget.cleanup.loaded').fire();
mw.loader.state('gadget.cleanup', 'ready');
});
mw.hook('gadget.cleanup.run').add(mto_cleanup);
}(jQuery, mediaWiki));
// EOF </nowiki>