User:B/quickimgdelete.js
Appearance
< User:B
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:B/quickimgdelete. |
/* <pre><nowiki> */
/* quickimgdelete.js - B fork
* Current version: 2.1.0
* =======================================
* Created by [[User:Howcheng|Howard Cheng]]
* Derivative work by [[User:B|B]
* Released under the [[GNU Public License]] (GPL)
* Original version at [[User:Howcheng/quickimgdelete.js]]
* =======================================
*/
// ==General configuration==
(function () {
return; // please fix errors in script before removing (see [[phab:T72470]])
var qid_testFilePage = null;//"User:Howcheng/Sandbox";
var qid_testUserPage = null;//"User:Howcheng/Sandbox2";
var qid_testLogPage = null;//"User:Howcheng/Sandbox3";
var qid_editToken; // needed for editing by API
var qid_action; // the action code (passed by the link click)
var qid_templateAddError = false; // flag for error adding to file description page
mw.loader.load('jquery.ui');
// == Shared functions ==
function qid_makeApiCall(method, parms, data) {
var url = "/w/api.php?format=json";
if (parms)
url += "&" + parms;
var promise = $.ajax({
type: method,
url: url,
async: false,
data: data
});
return promise;
}
function qid_getPageInfo(result) {
var pageId = wgArticleId + '';
if (result.query.pageids) {
pageId = result.query.pageids[0];
}
return result.query.pages[pageId];
}
function qid_getPageContent(page) {
return page.revisions[0]["*"];
}
function qid_setDialogMessage(message) {
mw.notify(message);
}
function qid_getFileInfo() {
// retrieve upload data, content, and edit token
var data = {
'action': 'query',
'prop': 'imageinfo|info|revisions',
'intoken': 'edit',
'iiprop': 'user|timestamp',
'iilimit': 10,
'rvprop': 'content',
'redirects': '',
'iilocalonly': '',
'titles': wgPageName
};
var promise = qid_makeApiCall("POST", null, data);
promise.done(qid_getEditToken);
promise.fail(qid_getFileInfo_error);
return promise;
}
function qid_getFileInfo_error() {
qid_setDialogMessage("An error occurred while attempting to get the file info");
}
function qid_getEditToken(result) {
var page = qid_getPageInfo(result);
qid_editToken = page.edittoken;
}
function qid_addTemplateToFilePage(page, templateWikiText, summary, templatesToRemove) {
var content = qid_getPageContent(page);
if (templatesToRemove) {
for (var i = 0; i < templatesToRemove.length; i++) {
content = qid_removeTemplate(content, templatesToRemove[i]);
}
}
content = templateWikiText + '\n' + content;
var editData = {
'action': 'edit',
'format': 'json',
'title': qid_testFilePage ? qid_testFilePage : page.title,
'summary': summary + ' (using [[User talk:Howcheng/quickimgdelete.js|quickimgdelete]])',
'basetimestamp': page.touched,
'contentmodel': 'wikitext',
'contentformat': 'text/x-wiki',
'text': content,
'token': qid_editToken
};
var promise = $.ajax({
type: "POST",
url: mw.util.wikiScript('api'),
data: editData,
dataType: 'json'
});
promise.then(function(result) {
qid_processEditSuccess(result, 'Template added to ' + wgPageName);
}, function(result) {
qid_setDialogMessage('Error: Request failed adding template to ' + wgPageName);
qid_templateAddError = true;
})
.then(function(result) {
if (qid_templateAddError)
return;
// get parsed template HTML
var parseData = {
'action': 'parse',
'contentmodel': 'wikitext',
'prop': 'text',
'pst': '',
'title': wgPageName,
'text': templateWikiText
};
var parsePromise = qid_makeApiCall("POST", null, parseData);
parsePromise.done(function(result) {
// inject into page
var parsed = result.parse.text["*"];
$('#mw-imagepage-content').prepend(parsed);
});
});
return promise;
}
function qid_addTemplateToUserTalkPage(username, warnWikiText, warnSummary) {
var warnPage = qid_testUserPage ? qid_testUserPage : 'User talk:' + username
warnWikiText = '\n' + warnWikiText + '\n';
var warnData = {
'action': 'edit',
'format': 'json',
'title': warnPage,
'summary': warnSummary + ' (using [[User talk:Howcheng/quickimgdelete.js|quickimgdelete]])',
'contentmodel': 'wikitext',
'contentformat': 'text/x-wiki',
'appendtext': warnWikiText,
'token': qid_editToken
};
var warnPromise = $.ajax({
type: "POST",
url: mw.util.wikiScript('api'),
data: warnData,
dataType: 'json'
});
warnPromise.then(function(result) {
qid_processEditSuccess(result, 'Warning template added to User talk:' + username);
}, function(result) {
qid_setDialogMessage('Error: Request failed when warning User: ' + username);
});
return warnPromise;
}
function qid_processEditSuccess(result, successMessage) {
if (result && result.edit && result.edit.result == 'Success') {
qid_setDialogMessage(successMessage);
} else if (result && result.error) {
qid_setDialogMessage('Error: API returned error code "' + result.error.code + '": ' + result.error.info);
} else {
qid_setDialogMessage('Error: Unknown result from API.')
}
}
function qid_userBeenWarned(txt, template) {
var re = eval("/\<!-- ?Template:" + template + " ?--\>/;");
return re.test(txt);
}
function qid_removeNamespace(fullpagename) {
var colonPos = fullpagename.indexOf(':');
var pagename = fullpagename.substring(colonPos + 1);
return pagename;
}
function qid_removeTemplate(editformval, template) {
var re = eval('/\{\{' + template + '/gi');
var templatepos = editformval.search(re);
if (templatepos > -1) {
var lastbracepos = editformval.indexOf('}}', templatepos) + 1;
editformval = editformval.substr(0, templatepos) + editformval.substr(lastbracepos + 2);
}
return editformval;
}
/** ================================================================================
* ==Automatic 'nominate for discussion' script==
* The actions in this section do three things:
* 1. Add a tag to the image page.
* 2. List the image on a log page.
* 3. Add a warning template to the uploader's talk page.
*/
// Configuration
nfd_text = "Nominate for discussion";
nfd_tooltip = "Nominate this image for discussion";
nfd_prompt = "Why do you want to nominate this image for discussion?";
nfd_delReq = "Wikipedia:Files for discussion/";
nfd_deleteTemplate = "ffd";
nfd_idwTemplate = "Fdw";
nfd_ifdTemplate = "Ffd2";
nfd_glossary = new Array(
new Array("AB", "Absent uploader"),
new Array("AU", "Absent uploader"),
new Array("CV", "Copyright violation"),
new Array("OB", "Obsolete"),
new Array("OR", "Orphaned"),
new Array("LQ", "Low quality"),
new Array("UE", "Unencyclopedic")
);
function nfd_onload() {
return; // [[phab:T72470]]
if (document.getElementById('ca-edit') == null) // not editable by non-admin
return;
if (wgAction == "view" && wgCanonicalNamespace == "File") {
mw.util.addPortletLink('p-tb', 'javascript:nfd_tagImage(\'nfd\')', nfd_text, 'nom-for-del', nfd_tooltip);
}
}
function nfd_tagImage(action) {
qid_action = action;
var promise = qid_getFileInfo();
promise.done(nfd_getFileInfo_success);
}
function nfd_getFileInfo_success(result) {
return; // [[phab:T72470]]
var template, warnTemplate, logTemplate;
var templateParms, warnTemplateParms, logTemplateParms;
var summary, warnSummary, logSummary;
var logPage;
var warnTemplateHasSig = false;
var promptMsg;
var page = qid_getPageInfo(result);
var promises = [];
// determine the listing page and the template to add to it, the template to add to the file and user pages, and the edit summaries
switch (qid_action) {
case 'nfd':
promptMsg = nfd_prompt;
template = nfd_deleteTemplate;
summary = 'nomination for [[WP:FFD|discussion]]';
warnTemplate = nfd_idwTemplate;
warnTemplateHasSig = true;
warnSummary = '[[:' + wgPageName + ']] nominated for discussion' ;
logTemplate = nfd_ifdTemplate;
logPage = nfd_delReq;
break;
}
// get user input for discussion rationale
var reason = window.prompt(promptMsg, '');
if (!reason) {
qid_setDialogMessage("No discussion rationale entered. Action cancelled.");
return;
}
// build the template wikitext
var now = new Date();
var utcNow = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
var logDate = $.datepicker.formatDate('yy MM d', utcNow);
var templateWikiText = '{{' + template + '|log=' + logDate + '}}';
// add template wikitext to page
var templatePromise = qid_addTemplateToFilePage(page, templateWikiText, summary, [ 'untagged' ]);
promises.push(templatePromise);
// build the log template wikitext
var logWikiText = '{{subst:' + logTemplate + '|1=' + wgTitle;
switch (qid_action) {
case 'nfd':
// expand abbreviations
for (var i = 0; i < nfd_glossary.length; i++) {
var abbrev = nfd_glossary[i][0];
var meaning = nfd_glossary[i][1];
var re = eval("/\\b" + abbrev + "\\b/");
if (re.test(reason))
reason = reason.replace(re, meaning);
}
// find original uploader
var uploader = page.imageinfo[page.imageinfo.length - 1].user;
logWikiText += '|Uploader=' + uploader + '|Reason=' + reason;
break;
}
logWikiText += '}} ~~' + '~~';
// add listing to log page
logPage += logDate;
logSummary = 'Adding [[:' + wgPageName + ']] (using [[User talk:Howcheng/quickimgdelete.js|quickimgdelete]])';
var logData = {
'action': 'edit',
'format': 'json',
'title': qid_testLogPage ? qid_testLogPage : logPage,
'summary': logSummary,
'contentmodel': 'wikitext',
'contentformat': 'text/x-wiki',
'appendtext': '\n' + logWikiText + '\n',
'token': qid_editToken
};
var logPromise = $.ajax({
type: "POST",
url: mw.util.wikiScript('api'),
data: logData,
dataType: 'json'
});
logPromise.then(function(result) {
qid_processEditSuccess(result, 'Entry added to ' + logPage);
}, function(result) {
qid_setDialogMessage('Error: Request failed when adding to ' + logPage);
});
promises.push(logPromise);
// warn the users
for (var i = 0; i < page.imageinfo.length; i++) {
var username = page.imageinfo[i].user;
var warnWikiText = '\n{{subst:' + warnTemplate;
warnWikiText += '|1=' + wgTitle + '}}';
if (!warnTemplateHasSig)
warnWikiText += ' ~~' + '~~';
warnWikiText += '\n';
var warnPromise = qid_addTemplateToUserTalkPage(username, warnWikiText, warnSummary);
promises.push(warnPromise);
}
// wait for all ajax calls to finish
$.when.apply($, promises).done(function() {
if (!qid_templateAddError)
qid_setDialogMessage('All done!')
});
}
/** ================================================================================
* ==Automatic 'mark no source' (mns) and 'mark no license' (mnl) script== // mnx = applies to all
* The actions in this section do two things:
* 1. Add a tag to the image page.
* 2. Add a warning to the uploader's talk page.
*/
// Configuration
mcv_text = "Copyright violation";
mcv_tooltip = "Mark this image as a copyright violation";
mns_text = "No source";
mns_tooltip = "Mark this image as missing required source information";
mnl_text = "No license";
mnl_tooltip = "Mark this image as missing required licensing information";
mnsl_text = "No source/license";
mnsl_tooltip = "Mark this image as missing both required source and licensing information";
mnpo_text = "No permission - OTRS received failed";
mnpo_tooltip = "Mark this image as missing evidence of permission because it been tagged as 'OTRS received' longer than the backlog and no follow-up email is received";
mnp_text = "No permission";
mnp_tooltip = "Mark this image as missing evidence of permission";
mnr_text = "No rationale";
mnr_tooltip = "Mark this image as missing non-free rationale";
mor_text = "Orphaned non-free";
mor_tooltip = "Mark this image as an orphaned non-free image";
mrfu_text = "Replaceable non-free";
mrfu_tooltip = "Mark this image as a replaceable non-free image";
mfud_text = "Non-free use disputed";
mfud_tooltip = "Mark this image as non-free use disputed";
mbsr_text = "Base URL";
mbsr_tooltip = "This image links directly to the image or is a generic base URL";
function mnx_markNo(action) {
qid_action = action;
var promise = qid_getFileInfo();
promise.done(mnx_getFileInfo_success);
}
function mnx_getFileInfo_success(result) {
var template, warnTemplate;
var templateParms, warnTemplateParms;
var warnNoNamespace, hasShortNotice;
var fileSrc;
var subst = true;
var page = qid_getPageInfo(result);
// determine the template to add to the file and user pages and the edit summaries
switch (qid_action) {
case 'mnx_mcv':
template = 'db-g12';
warnTemplate = "db-copyvio-notice";
fileSrc = window.prompt('Please enter the source URL of the image:', '');
if (fileSrc == null) {
alert("Action cancelled");
return;
}
if (fileSrc == '') {
alert("Error: no source URL entered. Aborting.")
return;
}
templateParms = 'header=1|url=' + fileSrc;
summary = "Image is a copyright violation; see " + fileSrc;
warnSummary = "Warning: copyright violation";
break;
case 'mnx_mns':
template = 'nsd';
warnTemplate = "Di-no source-notice";
summary = "Image is missing source information and will be deleted in seven days if it is not added.";
warnSummary = "Warning: image missing source information.";
warnNoNamespace = true;
break;
case 'mnx_mnl':
template = 'nld';
warnTemplate = "Di-no license-notice";
summary = "Image is missing license information and will be deleted in seven days if it is not added.";
warnSummary = "Warning: image missing license information.";
warnNoNamespace = true;
break;
case 'mnx_mnsl':
template = 'nsdnld';
warnTemplate = "Di-no source-notice";
summary = "Image is missing source and license information and will be deleted in seven days if both are not added.";
warnSummary = "Warning: image missing source and license information.";
warnNoNamespace = true;
break;
case 'mnx_mnpo':
template = 'npdo';
summary = "Image has been awaiting an [[WP:OTRS|OTRS]] follow-up and will be deleted in seven days if it is not provided.";
fileSrc = window.prompt('(Optional) Please enter the OTRS ID of the image:', '');
if (fileSrc)
templateParms = 'source=' + fileSrc;
warnTemplate = "Di-no permission OTRS unsuccessful-notice ";
warnSummary = "Warning: OTRS ticket was not successfully processed.";
warnNoNamespace = true;
break;
case 'mnx_mnp':
template = 'npd';
summary = "Image is missing evidence of permission and will be deleted in seven days if it is not provided.";
fileSrc = window.prompt('(Optional) Please enter the source of the image:', '');
if (fileSrc)
templateParms = 'source=' + fileSrc;
warnTemplate = "Di-no permission-notice";
warnSummary = "Warning: image missing evidence of permission.";
warnNoNamespace = true;
break;
case 'mnx_mnr':
template = 'nrd';
summary = "Image is missing non-free usage rationale and will be deleted in seven days if it is not added.";
warnTemplate = "Missing rationale";
warnSummary = "Warning: image missing non-free usage rationale.";
hasShortNotice = true;
break;
case 'mnx_mor':
template = 'orfud';
summary = "This non-free image is not used in any articles and will be deleted in seven days if it remains so.";
warnTemplate = "Orphaned";
warnSummary = "The non-free image you uploaded is now orphaned.";
hasShortNotice = true;
break;
case 'mnx_mrfu':
template = 'rfu';
summary = "This non-free image appears to illustrate a subject for which a free one could reasonably be found or created and will be deleted in two days.";
warnTemplate = "Replaceable";
warnSummary = "The non-free image you uploaded is replaceable by a free one.";
warnNoNamespace = true;
hasShortNotice = true;
break;
case 'mnx_mfud':
template = 'dfu';
summary = "The usage of this non-free image is disputed.";
var nonFreeDisputeReason = window.prompt('Please enter the reason why you are disputing the usage of this non-free image:', '');
if (!nonFreeDisputeReason) {
alert("No reason given. Action cancelled.");
return;
}
templateParms = '1=' + nonFreeDisputeReason;
warnTemplate = "No fair";
warnSummary = "The use of a non-free image you uploaded is disputed.";
hasShortNotice = true;
break;
case 'mnx_mbsr':
template = 'bsr';
summary = "The source URL given is inadequate.";
subst = false;
warnTemplate = "bsr-user";
warnSummary = "Please provide a better source URL for your image.";
break;
}
// build the template wikitext
var templateWikiText = '{{'
if (subst)
templateWikiText += 'subst:';
templateWikiText += template;
if (templateParms)
templateWikiText += '|' + templateParms;
templateWikiText += '}}';
var promises = [];
// add template wikitext to page
var templatePromise = qid_addTemplateToFilePage(page, templateWikiText, summary, [ 'untagged' ]);
promises.push(templatePromise);
// warn the users
for (var i = 0; i < page.imageinfo.length; i++) {
var username = page.imageinfo[i].user;
var useShortNotice = false;
if (hasShortNotice) {
// determine if the user has already been warned once
var chkNoticePromise = qid_makeApiCall("GET", "action=query&prop=revisions&rvprop=content&indexpageids=&titles=User talk:" + username);
chkNoticePromise.done(function(result) {
var utPage = qid_getPageInfo(result);
if (result.query.pageids[0] == '-1') {
// no talk page
return;
}
var content = qid_getPageContent(utPage);
if (qid_userBeenWarned(content, warnTemplate)) {
// yes, use short version of template
warnTemplate += ' short';
}
});
}
var warnWikiText = '{{subst:' + warnTemplate;
var filename = warnNoNamespace ? wgTitle : wgPageName;
warnWikiText += '|1=' + filename + '}} ~~' + '~~';
var warnPromise = qid_addTemplateToUserTalkPage(username, warnWikiText, warnSummary);
promises.push(warnPromise);
} // for loop
// wait for all ajax calls to finish
$.when.apply($, promises).done(function() {
if (!qid_templateAddError)
qid_setDialogMessage('All done!')
});
}
function mnx_onload() {
if (document.getElementById('ca-edit') == null) // not editable by non-admin
return;
if (wgAction == "view" && wgCanonicalNamespace == "File") {
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mcv\')', mcv_text, 'mark-copyvio', mcv_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mns\')', mns_text, 'mark-no-source', mns_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mnl\')', mnl_text, 'mark-no-license', mnl_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mnsl\')', mnsl_text, 'mark-no-source-license', mnsl_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mnpo\')', mnpo_text, 'mark-no-permission-otrs', mnpo_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mnp\')', mnp_text, 'mark-no-permission', mnp_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mnr\')', mnr_text, 'mark-no-rationale', mnr_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mor\')', mor_text, 'mark-orphaned', mor_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mrfu\')', mrfu_text, 'mark-replaceable', mrfu_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mfud\')', mfud_text, 'mark-disputed', mfud_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:mnx_markNo(\'mnx_mbsr\')', mbsr_text, 'mark-base-url', mbsr_tooltip);
}
}
/** ================================================================================
* ==Automatic speedy tagging==
* The actions in this section do one thing:
* 1. Add a tag to the image page.
*/
// Configuration
mred_text = "Mark as redundant image";
mred_tooltip = "Mark this image as redundant (tag for speedy deletion)";
mmi_text = "Mark as missing image";
mmi_tooltip = "Mark as missing image (tag for speedy deletion)";
mmtc_text = "Mark as Move to Commons";
mmtc_tooltip = "Mark as candidate to move to Commons";
mncd_text = "Mark as Commons duplicate";
mncd_tooltip = "Mark as Commons duplicate";
function speedyi_onload() {
if (document.getElementById('ca-edit') == null) // not editable by non-admin
return;
if (wgAction == "view" && wgCanonicalNamespace == "File") {
mw.util.addPortletLink('p-tb', 'javascript:speedyi_mark(\'speedyi_mred\')', mred_text, 'mark-redundant', mred_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:speedyi_mark(\'speedyi_mmi\')', mmi_text, 'mark-missing', mmi_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:speedyi_mark(\'speedyi_mmtc\')', mmtc_text, 'mark-movecommons', mmtc_tooltip);
mw.util.addPortletLink('p-tb', 'javascript:speedyi_mark(\'speedyi_mncd\')', mncd_text, 'mark-commons', mncd_tooltip);
}
}
function speedyi_mark(action) {
qid_action = action;
var promise = qid_getFileInfo();
promise.done(speedyi_getFileInfo_success);
}
function speedyi_getFileInfo_success(result) {
var template;
var templateParms;
var summary;
var subst = false;
var page = qid_getPageInfo(result);
var templatesToRemove = [ 'untagged' ];
switch (qid_action) {
case "speedyi_mred":
template = 'db-f1';
var dupe = window.prompt('Please enter the name of the image this one is redundant to.', '');
if (!dupe) {
qid_setDialogMessage('No image name entered. Action cancelled.');
return;
}
dupe = qid_removeNamespace(dupe);
summary = 'Tagging for [[WP:CSD#F1|speedy deletion]]: Image is redundant to [[:Image:' + dupe + ']].';
templateParms = '|1=' + dupe;
break;
case 'speedyi_mmi':
template = 'db-f2';
summary = 'Tagging for [[WP:CSD#F2|speedy deletion]]: Image is missing or corrupt.';
break;
case 'speedyi_mmtc':
template = 'Copy to Wikimedia Commons';
summary = 'Image is a candidate for moving to [[Commons:Main Page|Wikimedia Commons]].';
templateParms = '|human=' + wgUserName;
break;
case 'speedyi_mncd':
template = 'Now commons dated';
var commonsimg = window.prompt('Please enter the name of the corresponding image on Commons (leave empty if the same name).', '');
if (commonsimg == null) {
qid_setDialogMessage('Action cancelled.');
return;
}
if (commonsimg == '') {
// file not specified, use the same name
commonsimg = wgPageName;
} else {
templateParms = '|1=' + commonsimg;
}
summary = 'Image is duplicate of [[Commons:Main Page|Commons]] image [[Commons:' + commonsimg + '|]].';
subst = true;
// remove {{Move to Commons}} template (and its various redirects) and other now-moot maintenance templates
templatesToRemove = [ 'untagged', 'CTWC', 'CWC', 'Commons ok', 'Copy to Commons', 'Copy to commons', 'Copy to wikimedia commons', 'CopyToCommons', 'Do move to Commons', 'MITC', 'MTC', 'Move to Commons', 'Move to Wikimedia Commons', 'Move to commons', 'Move-to-commons', 'MoveToCommons', 'To commons', 'ToCommons', 'Upload to commons', 'Rename media', 'Ifr', 'Image move', 'Media rename', 'Nondescript', 'Rename image', 'Rename-image' ];
break;
}
var templateWikiText = '{{';
if (subst)
templateWikiText += 'subst:';
templateWikiText += template;
if (templateParms)
templateWikiText += '|' + templateParms;
templateWikiText += '}}';
// add template to file description page
var promise = qid_addTemplateToFilePage(page, templateWikiText, summary, templatesToRemove);
promise.then(function() {
if (!qid_templateAddError)
qid_setDialogMessage('All done!')
});
}
$(document).ready(function() {
nfd_onload();
mnx_onload();
speedyi_onload();
});
}());
/* </nowiki></pre> */