User:NOUG4AT/JWB.js: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
mNo edit summary
m (Cloudflare sucks)
Tag: Replaced
Line 1: Line 1:
/** <nowiki>
{{delete}}
* Install this script by pasting the following in your personal JavaScript file:
 
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/load.js&action=raw&ctype=text/javascript');
 
* Or for users on en.wikipedia.org:
 
{{subst:lusc|User:Joeytje50/JWB.js/load.js}}
 
* Note that this script will only run on the 'Project:AutoWikiBrowser/Script' page.
* This script is based on the downloadable AutoWikiBrowser.
*
* @licence
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
* @version 4.4.2
* @author Joeytje50
* </nowiki>
*/
 
window.JWBdeadman = false; // ADMINS: in case of fire, set this variable to true to disable this entire tool for all users
 
//TODO: more advanced pagelist-generating options
//TODO: generate page list based on images on a page
//TODO: Add feature to perform general cleanup (<table> to {|, fullurl-links to wikilinks, removing underscores from wikilinks)
//TODO: Add report button to AJAX error alert box
//TODO: Re-add errored pages to the page list
//TODO: Automatically generate full list of protection levels
//TODO: Add 'Ignore quotes' alongside 'Ignore unparsed content'; for example: Ignore [] Unparsed content [] Quotes
//TODO: Read Wikipedia:AutoWikiBrowser/Config for when not to apply typo fixes
//TODO: RETF disable any \b and some other cases
//TODO: Fix deletedrevs deprecated API
 
//Cleanup / modernize:
// .indexOf('') != -1 -> .includes()
// mw for requests, instead of ajax
// Optional?.chaining?.properties
 
/***** Global object/variables *****/
window.JWB = {}; //The main global object for the script.
 
(function() {
// Easier way to change import location for local debugging etc.
JWB.imports = {
'JWB.css': '//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.css&action=raw&ctype=text/css',
'i18n.js': '//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/i18n.js&action=raw&ctype=text/javascript',
'i18n': {},
'RETF.js': '//en.wikipedia.org/w/index.php?title=User:Joeytje50/RETF.js&action=raw&ctype=text/javascript',
'worker.js':'//en.wikipedia.org/w/index.php?title=User:Joeytje50/JWB.js/worker.js&action=raw&ctype=text/javascript',
};
let objs = ['page', 'api', 'worker', 'fn', 'pl', 'messages', 'setup', 'settings', 'ns'];
for (let i=0;i<objs.length;i++) {
JWB[objs[i]] = {};
}
JWB.summarySuffix = ' (via JWB)';
if (document.location.hostname == 'en.wikipedia.org') JWB.summarySuffix = ' (via [[WP:JWB]])';
JWB.lang = mw.config.get('wgUserLanguage').replace('-', '_');
JWB.contentLang = mw.config.get('wgContentLanguage').replace('-', '_');
JWB.index_php = mw.config.get('wgScript');
JWB.isStopped = true;
JWB.tooltip = window.tooltipAccessKeyPrefix || '';
let configext = 'js';
if (document.location.hostname.split('.').slice(-2).join('.') == 'wikia.com' || document.location.hostname.split('.').slice(-2).join('.') == 'fandom.com') {
//LEGACY: fallback to settings on css for Wikia; uses JSON now.
configext = 'css';
}
JWB.settingspage = 'JWB-settings.'+configext;
if (window.hasOwnProperty('JWBSETTINGS')) {
JWB.settingspage = JWBSETTINGS+'-settings.'+configext;
delete window.JWBSETTINGS; //clean up the global variable
}
JWB.hasJSON = false; // whether or not the wiki supports JSON userpages (mw 1.31+).
JWB.hasSMW = false; // whether or not the wiki has SMW installed.
})();
 
/***** User verification *****/
 
(function() {
if (mw.config.get('wgCanonicalNamespace')+':'+mw.config.get('wgTitle') !== 'Project:AutoWikiBrowser/Script' || JWB.allowed === false || mw.config.get('wgUserName') === null) {
JWB.allowed = false;
return;
}
mw.loader.load(JWB.imports['JWB.css'], 'text/css');
mw.loader.load('mediawiki.diff.styles');
new mw.Api().loadMessagesIfMissing( [ 'pagecategorieslink', 'pagecategorieslink' ] );
 
JWB.langs = [];
 
$.getScript(JWB.imports['i18n.js'], function() {
if (JWB.allowed === false) {
JWB.checkInit(); // deny access without attempting to load other languages.
return;
}
 
if (JWB.lang !== 'en' && JWB.imports.i18n.hasOwnProperty(JWB.lang)) {
JWB.langs.push(JWB.imports.i18n[JWB.lang]);
JWB.messages[JWB.lang] = JWB.messages[JWB.lang] || null;
} else if (JWB.lang !== 'en' && JWB.lang !== 'qqx') {
// this only happens if the language file does not exist.
JWB.lang = 'en';
}
if (JWB.contentLang !== 'en' && JWB.contentLang !== JWB.lang && JWB.imports.i18n.hasOwnProperty(JWB.contentLang)) {
JWB.langs.push(JWB.imports.i18n[JWB.contentLang]);
JWB.messages[JWB.contentLang] = JWB.messages[JWB.contentLang] || null;
}
 
if (JWB.langs.length) {
$.when.apply($, JWB.langs.map(url => $.getScript(url))).done(function(r) {
JWB.checkInit();
});
} else { // no more languages to load.
console.log('no languages needed loading');
JWB.checkInit();
}
});
//RegEx Typo Fixing
$.getScript(JWB.imports['RETF.js'], function() {
$('#refreshRETF').click(RETF.load);
});
 
if (window.JWBdeadman === true) {
window.JWB = false; // disable all access
alert("This tool has been temporarily been disabled by Wikipedia admins due to issues it would otherwise cause. Please check back soon to see if it is working again.");
return false;
} else if (!window.Worker) {
// https://caniuse.com/webworkers - this should not happen for any sensible human being. Either you're on IE<10, or you're just testing my patience.
alert("Web Workers are not supported in this browser. Please use a more modern browser to use JWB. Most matching and replacing features are not supported in this browser.");
}
 
(new mw.Api()).get({
action: 'query',
titles: 'Project:AutoWikiBrowser/CheckPageJSON',
prop: 'info|revisions',
meta: 'userinfo|siteinfo',
rvprop: 'content',
rvlimit: 1,
uiprop: 'groups',
siprop: 'namespaces|usergroups|extensions',
indexpageids: true,
format: 'json',
}).done(function(response) {
if (response.error) {
alert('API error: ' + response.error.info);
JWB = false; //preventing further access. No verification => no access.
return;
}
JWB.ns = response.query.namespaces; //saving for later
// This will execute before JWB.init() and therefore before JWB.setup.load() loading the user's settings.
let wikigroups = response.query.usergroups;
for (var u of wikigroups) {
if (u.rights.indexOf('edituserjson') !== -1) {
JWB.hasJSON = true;
break;
}
}
// Check if we've got SMW on this wiki
let extensions = response.query.extensions;
for (var e of extensions) {
if (e.name == "SemanticMediaWiki") {
JWB.hasSMW = true;
break;
}
}
JWB.username = response.query.userinfo.name; //preventing any "hacks" that change wgUserName or mw.config.wgUserName
var groups = response.query.userinfo.groups;
var page = response.query.pages[response.query.pageids[0]];
var users = [];
var bots = [];
JWB.sysop = groups.indexOf('sysop') !== -1;
if (response.query.pageids[0] !== '-1') {
var checkPageData = JSON.parse(page.revisions[0]['*']);
users = checkPageData.enabledusers;
if ("enabledbots" in checkPageData) {
bots = checkPageData.enabledbots;
}
} else {
users = false; //fallback when page doesn't exist
if (JWB.sysop) { // Check and inform admins if their checkpage is the unsupported format.
(new mw.Api()).get({
action: 'query',
titles: 'Project:AutoWikiBrowser/CheckPage',
prop: 'info',
indexpageids: true,
}).done(function(oldpage){
var q = oldpage.query;
if (q.pageids[0] != '-1' && !q.pages[q.pageids[0]].hasOwnProperty('redirect')) {
// CheckPageJSON does not exist, and CheckPage does exist, and is not a redirect.
// This indicates the checkpage needs to be ported to JSON. Notify admins.
prompt('Warning: The AWB checkpage found at Project:AutoWikiBrowser/CheckPage is no longer supported.\n'+
'Please convert this checkpage to a JSON checkpage. See the URL below for more information.\n'+
'After creating the JSON checkpage, you can use "Special:ChangeContentModel" to change the content model to JSON.',
'https://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/CheckPage_format');
}
});
}
}
JWB.bot = groups.indexOf('bot') !== -1 && (users === false || bots.includes(JWB.username));
// Temporary global debugging variables
JWB.debug = [groups.indexOf('bot'), users === false, bots && bots.indexOf(JWB.username)];
if (JWB.username === "Joeytje50" && response.query.userinfo.id === 13299994) {//TEMP: Dev full access to entire interface.
JWB.bot = true;
users.push("Joeytje50");
}
if (JWB.sysop || response.query.pageids[0] === '-1' || users === false || users.includes(JWB.username) || bots.includes(JWB.username)) {
JWB.allowed = true;
JWB.checkInit(); //init if everything necessary has been loaded
} else {
if (allLoaded) {
//run this after messages have loaded, so the message that shows is in the user's language
alert(JWB.msg('not-on-list'));
}
JWB = false; //prevent further access
}
}).fail(function(xhr, error) {
alert(JWB.msg('verify-error') + '\n' + error);
JWB = false; //preventing further access. No verification => no access.
});
})();
 
/***** API functions *****/
 
//Main template for API calls
JWB.api.call = function(data, callback, onerror) {
data.format = 'json';
if (data.action !== 'query' && data.action !== 'compare' && data.action !== 'ask') {
data.bot = true; // mark edits as bot
}
$.ajax({
data: data,
dataType: 'json',
url: mw.config.get('wgScriptPath') + '/api.php',
type: 'POST',
success: function(response) {
if (response.error) {
if (onerror && onerror(response, 'API') === false) return;
alert('API error: ' + response.error.info);
JWB.stop();
} else {
callback(response);
}
},
// onerror: if it exists and returns false, do not show error alert. Otherwise, do show alert.
error: function(xhr, error) {
if (onerror && onerror(error, 'AJAX') === false) return;
alert('AJAX error: ' + error);
JWB.stop();
}
});
};
 
//Get page diff, and process it for more interactivity
JWB.api.diff = function(callback) {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('diff');
var editBoxInput = $('#editBoxArea').val();
var redirect = $('input.redirects:checked').val();
var data = {
action: 'compare',
indexpageids: true,
fromtitle: JWB.page.name,
//toslots: 'main', // TODO: Once this gets supported more widely, convert to the non-deprecated toslots system.
//'totext-main': editBoxInput,
totext: editBoxInput,
topst: true,
};
if (redirect=='follow') data.redirects = true;
JWB.api.call(data, function(response) {
var diff;
diff = response.compare['*'];
if (diff === '') {
diff = '<h2>'+JWB.msg('no-changes-made')+'</h2>';
} else {
diff = '<table class="diff">'+
'<colgroup>'+
'<col class="diff-marker">'+
'<col class="diff-content">'+
'<col class="diff-marker">'+
'<col class="diff-content">'+
'</colgroup>'+
'<tbody>'+diff+'</tbody></table>';
}
$('#resultWindow').html(diff);
$('.diff-lineno').each(function() {
var lineNumMatch = $(this).html().match(/\d+/);
if (lineNumMatch) {
$(this).parent().attr('data-line',parseInt(lineNumMatch[0])-1).addClass('lineheader');
}
});
$('table.diff tr').each(function() { //add data-line attribute to every line, relative to the previous one. Used for click event.
if (!$(this).next().is('[data-line]') && !$(this).next().has('td.diff-deletedline + td.diff-empty')) {
$(this).next().attr('data-line',parseInt($(this).data('line'))+1);
} else if ($(this).next().has('td.diff-deletedline + td.diff-empty')) {
$(this).next().attr('data-line',$(this).data('line')); //copy over current data-line for deleted lines to prevent them from messing up counting.
}
});
JWB.status('done', true);
if (typeof(callback) === 'function') {
callback();
}
}, function(err, type) {
if (type == 'API' && err.error.code == 'missingtitle') {
// missingtitle is to be expected when editing a page that doesn't exist; just show a message and move on.
$('#resultWindow').html('<span style="font-weight:bold;color:red;">'+JWB.msg('page-not-exists')+'</span>');
JWB.status('done', true);
if (typeof(callback) === 'function') {
callback();
}
return false; // stop propagation of error; do not show alerts.
}
});
};
 
//Retrieve page contents/info, process them, and store information in JWB.page object.
JWB.api.get = function(pagename) {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.pageCount();
if (!JWB.list[0] || JWB.isStopped) {
return JWB.stop();
}
if (pagename === '#PRE-PARSE-STOP') {
var curval = $('#articleList').val();
$('#articleList').val(curval.substr(curval.indexOf('\n') + 1));
$('#preparse').prop('checked', false);
JWB.stop();
return;
}
let cgns = JWB.ns[14]['*'];
let skipcg = $('#skipCategories').val();
// prepend Category: before all categories and turn CSV(,) into CSV(|).
skipcg = skipcg.replace(new RegExp('(^|,|\\|)('+cgns+':)?', 'gi'), '|'+cgns+':').substr(1);
var redirect = $('input.redirects:checked').val();
var data = {
action: 'query',
prop: 'info|revisions|categories',
inprop: 'watched|protection',
type: 'csrf|watch',
titles: pagename,
rvprop: 'content|timestamp|ids',
rvlimit: '1',
cllimit: 'max',
clcategories: skipcg,
indexpageids: true,
meta: 'userinfo|tokens',
uiprop: 'hasmsg'
};
if (redirect=='follow'||redirect=='skip') data.redirects = true;
if (JWB.sysop) {
data.list = 'deletedrevs';
}
JWB.status('load-page');
JWB.api.call(data, function(response) {
if (response.query.userinfo.hasOwnProperty('messages')) {
var view = mw.config.get('wgScriptPath') + '?title=Special:MyTalk';
var viewNew = view + '&diff=cur';
JWB.status(
'<span style="color:red;font-weight:bold;">'+
JWB.msg('status-newmsg',
'<a href="'+view+'" target="_blank">'+JWB.msg('status-talklink')+'</a>',
'<a href="'+viewNew+'" target="_blank">'+JWB.msg('status-difflink')+'</a>')+
'</span>', true);
alert(JWB.msg('new-message'));
JWB.stop();
return;
}
JWB.page = response.query.pages[response.query.pageids[0]];
JWB.page.token = response.query.tokens.csrftoken;
JWB.page.watchtoken = response.query.tokens.watchtoken;
JWB.page.name = JWB.list[0].split('|')[0];
var varOffset = JWB.list[0].indexOf('|') !== -1 ? JWB.list[0].indexOf('|') + 1 : 0;
JWB.page.pagevar = JWB.list[0].substr(varOffset);
JWB.page.content = JWB.page.revisions ? JWB.page.revisions[0]['*'] : '';
JWB.page.exists = !response.query.pages["-1"];
JWB.page.deletedrevs = response.query.deletedrevs;
JWB.page.watched = JWB.page.hasOwnProperty('watched');
JWB.page.protections = JWB.page.restrictiontypes;
 
if (response.query.redirects) {
JWB.page.name = response.query.redirects[0].to;
}
// check for skips that can be determined before replacing
if (!JWB.fn.allowBots(JWB.page.content, JWB.username) || !JWB.fn.allowBots(JWB.page.content)) {
JWB.log('nobots', JWB.page.name);
return JWB.next();
} else if (JWB.page.categories !== undefined || // skip because of a matching category as passed via clcategories.
($('#exists-no').prop('checked') && !JWB.page.exists) ||
($('#exists-yes').prop('checked') && JWB.page.exists) ||
(redirect==='skip' && response.query.redirects) // variable  redirect  is defined outside this callback function.
) {
// simple skip rules
JWB.log('skip', JWB.page.name);
return JWB.next();
}
// Check skip contains rules.
var containRegex = $('#containRegex').prop('checked'),
containFlags = $('#containFlags').val();
var skipContains, skipNotContains;
if (containRegex) {
JWB.status('check-skips');
var skipping = false; // for tracking if match is found in synchronous calls.
if ($('#skipContains').val().length) {
JWB.worker.match(JWB.page.content, $('#skipContains').val(), containFlags, function(result, err) {
console.log('Contains', result, err);
if (result !== null && err === undefined) {
JWB.log('skip', JWB.page.name);
JWB.next(); // next() also cancels the skipNotContains.
skipping = true;
return;
} // else continue with the queued worker job that checks skipNotContains
});
}
if (skipping) {
console.log('skipped page before replaces');
return;
}
if ($('#skipNotContains').val().length) {
JWB.worker.match(JWB.page.content, $('#skipNotContains').val(), containFlags, function(result, err) {
console.log('Not contains', result, err);
if (result === null && err === undefined) {
JWB.log('skip', JWB.page.name);
JWB.next(); // also cancels the replace
skipping = true;
return;
} // else move on to replacing
});
}
if (skipping) {
console.log('skipped page before replaces');
return;
}
} else {
skipContains = $('#skipContains').val();
skipNotContains = $('#skipNotContains').val();
if ((skipContains && JWB.page.content.includes(skipContains)) ||
(skipNotContains && !JWB.page.content.includes(skipNotContains))) {
console.log('skipped page before replaces');
return JWB.next();
}
JWB.status('done', true);
}
JWB.replace(JWB.page.content, function(newContent) {
if (JWB.isStopped === true) return;
if ($('#skipNoChange').prop('checked') && JWB.page.content === newContent) { //skip if no changes are made
JWB.log('skip', JWB.page.name);
return JWB.next();
} else {
JWB.editPage(newContent);
}
JWB.updateButtons();
});
});
};
 
//Some functions with self-explanatory names:
JWB.api.submit = function(page) {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('submit');
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
if ((typeof page === 'string' && page !== JWB.page.name) || $('#currentpage a').html().replace(/&amp;/g, '&') !== JWB.page.name) {
console.log(page, JWB.page.name, $('#currentpage a').html());
JWB.stop();
alert(JWB.msg('autosave-error', JWB.msg('tab-log')));
$('#currentpage').html(JWB.msg('editbox-currentpage', ' ', ' '));
return;
}
var newval = $('#editBoxArea').val();
var diffsize = newval.length - JWB.page.content.length;
if ($('#sizelimit').val() != 0 && Math.abs(diffsize) > parseInt($('#sizelimit').val())){
alert(JWB.msg('size-limit-exceeded', diffsize > 0 ? '+'+diffsize : diffsize));
JWB.status('done', true);
return;
}
var data = {
title: JWB.page.name,
summary: summary,
action: 'edit',
//tags: 'JWB',
basetimestamp: JWB.page.revisions ? JWB.page.revisions[0].timestamp : '',
token: JWB.page.token,
text: newval,
watchlist: $('#watchPage').val()
};
if ($('#minorEdit').prop('checked')) data.minor = true;
JWB.api.call(data, function(response) {
JWB.log('edit', response.edit.title, response.edit.newrevid);
}, function(error, errtype) {
var cont = false;
if (errtype == 'API') {
cont = confirm("API error: " + error.error.info + "\n" + JWB.msg('confirm-continue'));
} else {
cont = confirm("AJAX error: " + error + "\n" + JWB.msg('confirm-continue'));
}
if (!cont) {
JWB.stop();
}
return false; // do not fall back on default error handling
});
// While the edit is submitting, continue to the next page to edit.
JWB.status('done', true);
JWB.next();
};
JWB.api.preview = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('preview');
JWB.api.call({
title: JWB.page.name,
action: 'parse',
pst: true,
text: $('#editBoxArea').val()
}, function(response) {
$('#resultWindow').html(response.parse.text['*']);
$('#resultWindow div.previewnote').remove();
var cglist = response.parse.categories;
if (cglist.length > 0) {
var cgtext = mw.message('pagecategories', cglist.length).text(),
cglink = mw.message('pagecategorieslink').text();
// set defaults if MediaWiki:Pagecategories(link) have not loaded correctly:
if (cgtext[0] == '\u29FC') cgtext = 'Categories';
if (cglink[0] == '\u29FC') cglink = 'Special:Categories';
var $footer = $('<footer/>').addClass('catlinks')
.append('<a href="/wiki/'+encodeURIComponent(cglink)+'" title="'+cglink+'">'+cgtext+'</a>: <ul></ul>');
var $ul = $footer.children('ul');
for (var i=0;i<cglist.length;i++) {
var redlink = cglist[i].missing === undefined ? '' : ' class="new"';
var cg = cglist[i]['*'];
$ul.append('<li><a href="/wiki/Category:' + encodeURIComponent(cg) + '" title="' + cg + '"' + redlink + '>' + cg + '</a></li>');
}
$footer.appendTo('#resultWindow');
}
JWB.status('done', true);
});
};
JWB.api.move = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('move');
var topage = $('#moveTo').val().replace(/\$x/gi, JWB.page.pagevar);
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
var data = {
action: 'move',
from: JWB.page.name,
to: topage,
token: JWB.page.token,
reason: summary,
ignorewarnings: 'yes'
};
if ($('#moveTalk').prop('checked')) data.movetalk = true;
if ($('#moveSubpage').prop('checked')) data.movesubpages = true;
if ($('#suppressRedir').prop('checked')) data.noredirect = true;
JWB.api.call(data, function(response) {
JWB.log('move', response.move.from, response.move.to);
JWB.status('done', true);
if (!$('#moveTo').val().match(/\$x/i)) $('#moveTo').val('')[0].focus(); //clear entered move-to pagename if it's not based on the pagevar
JWB.next(topage);
});
};
JWB.api.del = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
var del_action = (!JWB.page.exists ? 'un' : '') + 'delete';
JWB.status(del_action);
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
var data = {
action: del_action,
title: JWB.page.name,
token: JWB.page.token,
reason: summary
};
if ($('#deleteTalk').prop('checked')) data[del_action + 'talk'] = true;
JWB.api.call(data, function(response) {
JWB.log(del_action, (response['delete']||response.undelete).title);
JWB.status('done', true);
JWB.next(response.undelete && response.undelete.title);
});
};
JWB.api.protect = function() {
if (JWB.isStopped) return; // prevent new API calls when stopped
JWB.status('protect');
var summary = $('#summary').val();
if ($('#summary').parent('label').hasClass('viaJWB')) summary += JWB.summarySuffix;
var editprot = $('#editProt').val();
var moveprot = $('#moveProt').val() || editprot;
var uploadprot = $('#uploadProt').val() || editprot;
var protstring = 'edit='+editprot+'|move='+moveprot;
if (!JWB.page.exists)
protstring = 'create='+editprot;
if (JWB.page.protections.includes('upload'))
protstring += '|upload='+uploadprot;
JWB.api.call({
action: 'protect',
title: JWB.page.name,
token: JWB.page.token,
reason: summary,
expiry: $('#protectExpiry').val()!==''?$('#protectExpiry').val():'infinite',
protections: protstring,
}, function(response) {
var protactions = '';
var prots = response.protect.protections;
for (var i=0;i<prots.length;i++) {
if (typeof prots[i].edit == 'string') {
protactions += ' edit: '+(prots[i].edit || 'all');
} else if (typeof prots[i].move == 'string') {
protactions += ' move: '+(prots[i].move || 'all');
} else if (typeof prots[i].create == 'string') {
protactions += ' create: '+(prots[i].create || 'all');
} else if (typeof prots[i].upload == 'string') {
protactions += ' upload: '+(prots[i].upload || 'all');
}
}
protactions += ' expires: '+prots[0].expiry;
JWB.log('protect', response.protect.title, protactions);
JWB.status('done', false);
JWB.next(response.protect.title);
});
};
 
JWB.api.watch = function() {
JWB.status('watch');
var data = {
action: 'watch',
title: JWB.page.name,
token: JWB.page.watchtoken
};
if (JWB.page.watched) data.unwatch = true;
JWB.api.call(data, function(response) {
JWB.status('<span style="color:green;">'+
JWB.msg('status-watch-'+(JWB.page.watched ? 'removed' : 'added'), "'"+JWB.page.name+"'")+
'</span>', true);
JWB.page.watched = !JWB.page.watched;
$('#watchNow').html( JWB.msg('watch-' + (JWB.page.watched ? 'remove' : 'add')) );
});
};
 
/***** Pagelist functions *****/
 
JWB.pl.iterations = 0;
JWB.pl.done = true;
 
JWB.pl.stop = function() {
if (JWB.pl.done) {
JWB.pl.iterations = 0;
$('#pagelistPopup [disabled]:not(fieldset [disabled]), #pagelistPopup legend input, #pagelistPopup button').prop('disabled', false);
$('#pagelistPopup legend input').trigger('change');
$('#pagelistPopup button img').remove();
}
};
 
JWB.pl.getNSpaces = function() {
var list = $('#pagelistPopup [name="namespace"]')[0];
return $('#pagelistPopup [name="namespace"]').val().join('|'); //.val() returns an array of selected options.
};
 
JWB.pl.getList = function(abbrs, lists, data) {
$('#pagelistPopup button, #pagelistPopup input, #pagelistPopup select, #pagelistPopup button').prop('disabled', true);
JWB.pl.iterations++;
if (data.ask !== undefined) {
JWB.pl.SMW(data.ask); // execute SMW call in parallel
JWB.pl.done = false;
data.ask = undefined;
}
if (!abbrs.length) {
JWB.pl.done = true;
return; // don't execute the rest; only a SMW query was entered.
}
data.action = 'query';
var nspaces = JWB.pl.getNSpaces();
for (let i=0;i<abbrs.length;i++) {
if (nspaces) data[abbrs[i]+'namespace'] = data[abbrs[i]+'namespace'] || nspaces; // if namespaces are already set, use that instead (for apnamespace)
data[abbrs[i]+'limit'] = 'max';
}
let linksList = lists.indexOf('links');
if (linksList !== -1) {
data.prop = 'links';
lists.splice(linksList, 1);
}
data.list = lists.join('|');
console.log('generating:', data);
JWB.api.call(data, function(response) {
var maxiterate = 100; //allow up to 100 consecutive requests at a time to avoid overloading the server.
if (!response.query) response.query = {};
if (response.watchlistraw) response.query.watchlistraw = response.watchlistraw; //adding some consistency
var plist = [];
if (response.query.pages) {
var links;
for (var id in response.query.pages) {
links = response.query.pages[id].links;
for (let i=0;i<links.length;i++) {
plist.push(links[i].title);
}
}
}
for (var l in response.query) {
if (l === 'pages') continue;
for (let i=0;i<response.query[l].length;i++) {
plist.push(response.query[l][i].title);
}
}
//add the result to the pagelist immediately, as opposed to saving it all up and adding in 1 go like AWB does
$('#articleList').val($.trim($('#articleList').val()) + '\n' + plist.join('\n'));
JWB.pageCount();
var cont = response.continue;
console.log("Continue",JWB.pl.iterations, cont);
if (cont && JWB.pl.iterations <= maxiterate) {
var lists = [];
if (response.query) { //compatibility with the code I wrote for the old query-continue. TODO: make this unnecessary?
for (var list in response.query) {
lists.push(list); //add to the new array of &list= values
}
}
var abbrs = [];
for (var abbr in cont) {
data[abbr] = cont[abbr]; //add the &xxcontinue= value to the data
if (abbr != 'continue') {
abbrs.push(abbr.replace('continue','')); //find out what xx is and add it to the list of abbrs
}
}
JWB.pl.getList(abbrs, lists, data); //recursive function to get every page of a list
} else {
if (JWB.pl.iterations > maxiterate) {
JWB.status('pl-over-lim', true);
} else {
JWB.status('done', true);
}
JWB.pl.stop(); // if JWB.pl.done == true show stopped interface. Otherwise mark as done.
JWB.pl.done = true;
}
}, function() { //on error, simply reset and let the user work with what he has
JWB.status('done', true);
JWB.pl.stop();
JWB.pl.done = true;
});
};
 
JWB.pl.SMW = function(query) {
var data = {
action: 'ask',
query: query
};
JWB.api.call(data, function(response) {
console.log(response);
let list = response.query.results;
let pagevar = response.query.printrequests[1];
let pagevar_type = pagevar && pagevar.typeid;
if (pagevar) {
// either pagevar === undefined, or it's the first printrequest.
pagevar = pagevar.label;
}
let plist = [];
for (let l in list) {
let page = list[l];
let name = page.fulltext;
let suff;
if (pagevar) try {
let val = page.printouts[pagevar][0];
if (!val) continue; // this page does not contain this property.
switch (pagevar_type) {
case '_boo':
suff = val == 't'; // true if 't' else false;
break;
case '_wpg':
suff = val.fulltext;
break;
case '_dat':
// val.raw is also available but the unconventional format makes it a lot less convenient.
suff = val.timestamp;
break;
case '_qty':
suff = val.value + ' ' + val.unit;
break;
case '_mlt_rec':
// I doubt this is used anywhere, but it's not too hard to support.
suff = val.Text.item[0];
break;
case '_ref_rec':
// not supported; references contain too many properties.
break;
default:
suff = val;
}
} catch(e) {
console.error(e); // show error but ignore. Something is wrong in SMW query/api.
}
if (suff) {
plist.push(name + '|' + suff);
} else {
plist.push(name);
}
}
$('#articleList').val($.trim($('#articleList').val()) + '\n' + plist.join('\n'));
JWB.pageCount();
JWB.pl.stop(); // if JWB.pl.done == true show stopped interface. Otherwise mark as done.
JWB.pl.done = true;
});
};
 
//JWB.pl.getList(['wr'], ['watchlistraw'], {}) for watchlists
JWB.pl.generate = function() {
var $fields = $('#pagelistPopup fieldset').not('[disabled]');
$('#pagelistPopup').find('button[type="submit"]').append('<img src="//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif" width="15" height="15" alt="'+JWB.msg('status-alt')+'"/>');
var abbrs = [],
lists = [],
data = {'continue': ''};
$fields.each(function() {
var list = $(this).find('legend input').attr('name');
var abbr;
if (list === 'linksto') { //Special case since this fieldset features 3 merged lists in 1 fieldset
if (!$('[name="title"]').val()) return;
$('[name="backlinks"], [name="embeddedin"], [name="imageusage"]').filter(':checked').each(function() {
var val = this.value;
abbrs.push(val);
lists.push(this.name);
data[val+'title'] = $('[name="title"]').val();
data[val+'filterredir'] = $('[name="filterredir"]:checked').val();
if ($('[name="redirect"]').prop('checked')) data[val+'redirect'] = true;
});
} else if (list === 'smwask') {
data.ask = $(this).find('#smwquery').val();
} else { //default input system
if ($(this).find('#psstrict').prop('checked')) {
// different list if prefixsearch is strict
let $input = $(this).find('#psstrict');
list = $input.attr('name');
abbr = $input.val();
} else {
abbr = $(this).find('legend input').val();
}
lists.push(list);
abbrs.push(abbr);
$(this).find('input').not('legend input').each(function() {
if ((this.type === 'checkbox' || this.type === 'radio') && this.checked === false) return;
if (this.id == 'psstrict') return; // ignore psstrict; it only affects how pssearch is handled
var name, val;
if (this.id == 'cmtitle') {
// making sure the page has a Category: prefix, in case the user left it out
let cgns = JWB.ns[14]['*']; // name for Category: namespace
if (!this.value.startsWith(cgns+':')) {
this.value = cgns+':'+this.value;
}
}
if (this.id == 'pssearch' && this.name == 'apprefix') {
// apprefix needs namespace separate from pagename
name = this.name;
let split = this.value.split(':');
val = split[1] || split[0];
let nsid = 0;
if (split[1]) { // if a namespace is given
for (let ns in JWB.ns) {
if (JWB.ns[ns]['*'] == split[0]) {
nsid = JWB.ns[ns].id;
break;
}
}
}
data.apnamespace = nsid;
} else {
name = this.name;
val = this.value;
}
if (data.hasOwnProperty(name)) {
data[name] += '|'+val;
} else {
data[name] = val;
}
});
console.log(abbrs, lists, data);
}
});
if (abbrs.length || data.ask) JWB.pl.getList(abbrs, lists, data);
else JWB.pl.stop();
};
 
/***** Setup functions *****/
 
JWB.setup.save = function(name) {
name = name || prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-store')), $('#loadSettings').val());
if (name === null) return;
var self = JWB.settings[name] = {
string: {},
bool: {},
replaces: []
};
//inputs with a text value
$('textarea, input[type="text"], input[type="number"], select').not('.replaces input, #editBoxArea, #settings *').each(function() {
if (typeof $(this).val() == 'string') {
self.string[this.id] = this.value.replace(/\n{2,}/g,'\n');
} else {
self.string[this.id] = $(this).val();
}
});
self.replaces = [];
$('.replaces').each(function() {
if ($(this).find('.replaceText').val() || $(this).find('.replaceWith').val()) {
self.replaces.push({
replaceText: $(this).find('.replaceText').val(),
replaceWith: $(this).find('.replaceWith').val(),
useRegex: $(this).find('.useRegex').prop('checked'),
regexFlags: $(this).find('.regexFlags').val(),
ignoreNowiki: $(this).find('.ignoreNowiki').prop('checked')
});
}
});
$('input[type="radio"], input[type="checkbox"]').not('.replaces input').each(function() {
self.bool[this.id] = this.checked;
});
if (!$('#loadSettings option[value="'+name+'"]').length) {
$('#loadSettings').append('<option value="'+name+'">'+name+'</option>');
}
$('#loadSettings').val(name);
console.log(self);
};
 
JWB.setup.apply = function(name) {
name = name && JWB.settings[name] ? name : 'default';
var self = JWB.settings[name];
$('#loadSettings').val(name);
$('.replaces + .replaces').remove(); //reset find&replace inputs
$('.replaces input[type="text"]').val('');
$('.useRegex').each(function() {this.checked = false;});
$('#pagelistPopup legend input').trigger('change'); //fix checked state of pagelist generating inputs
for (var a in self.string) {
$('#'+a).val(self.string[a]);
}
for (var b in self.bool) {
$('#'+b).prop('checked', self.bool[b]);
}
var cur;
for (var c=0;c<self.replaces.length;c++) {
if ($('.replaces').length <= c) $('.moreReplaces[data-insert="before"')[0].click();
cur = self.replaces[c];
for (var d in cur) {
if (cur[d] === true || cur[d] === false) {
$('.replaces').eq(c).find('.'+d).prop('checked', cur[d]);
} else {
$('.replaces').eq(c).find('.'+d).val(cur[d]);
}
}
}
$('.useRegex, #containRegex,'+
  '#pagelistPopup legend input,'+
  '#viaJWB, #enableRETF').trigger('change'); //reset disabled inputs
};
 
JWB.setup.getObj = function() {
var settings = [];
for (var i in JWB.settings) {
if (i != '_blank') {
settings.push('"' + i + '": ' + JSON.stringify(JWB.settings[i]));
}
}
return '{\n\t' + settings.join(',\n\t').split('{{subst:').join('{{#JWB-SAFESUBST:#') + '\n}';
};
 
JWB.setup.submit = function() {
var name = prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-save')), $('#loadSettings').val());
if (name === null) return;
if ($.trim(name) === '') name = 'default';
JWB.setup.save(name);
JWB.status('setup-submit');
JWB.api.call({
action: 'query',
meta: 'tokens',
}, function(response) {
let edittoken = response.query.tokens.csrftoken;
JWB.api.call({
title: 'User:'+JWB.username+'/'+JWB.settingspage,
summary: JWB.msg(['setup-summary', JWB.contentLang]),
action: 'edit',
token: edittoken,
text: JWB.setup.getObj(),
minor: true
}, function(response) {
JWB.status('done', true);
JWB.log('edit', response.edit.title, response.edit.newrevid);
});
});
};
 
//TODO: use blob uri
JWB.setup.download = function() {
var name = prompt(JWB.msg('setup-prompt', JWB.msg('setup-prompt-save')), $('#loadSettings').val());
if (name === null) return;
if ($.trim(name) === '') name = 'default';
JWB.setup.save(name);
JWB.status('setup-dload');
var url = 'data:application/json;base64,' + btoa(unescape(encodeURIComponent(JWB.setup.getObj())));
var elem = $('#download-anchor')[0];
if (HTMLAnchorElement.prototype.hasOwnProperty('download')) { //use download attribute when possible, for its ability to specify a filename
elem.href = url;
elem.click();
setTimeout(function() {elem.removeAttribute('href');}, 2000);
} else { //fallback to iframes for browsers with no support for download="" attributes
elem = $('#download-iframe')[0];
elem.src = url.replace('application/json', 'application/octet-stream');
setTimeout(function() {elem.removeAttribute('src');}, 2000);
}
JWB.status('done', true);
};
 
JWB.setup.import = function(e) {
e.preventDefault();
file = (e.dataTransfer||this).files[0];
if ($(this).is('#import')) { //reset input
this.outerHTML = this.outerHTML;
$('#import').change(JWB.setup.import);
}
if (!window.hasOwnProperty('FileReader')) {
alert(JWB.msg('old-browser'));
JWB.status('old-browser', '<a target="_blank" href="'+JWB.index_php+'?title=Special:MyPage/'+JWB.settingspage+'">/'+JWB.settingspage+'</a>');
return;
}
if (file.name.split('.').pop().toLowerCase() !== 'json') {
alert(JWB.msg('not-json'));
return;
}
JWB.status('Processing file');
var reader = new FileReader();
reader.readAsText(file);
reader.onload = function(e) {
JWB.status('done', true);
try {
//Exclusion regex based on http://stackoverflow.com/a/23589204/1256925
//Removes all JS comments from the file, except when they're between quotes.
var c = reader.result;
var data = JSON.parse(c.replace(/("[^"]*")|(\/\*[\w\W]*\*\/|\/\/[^\n]*)/g, function(match, g1, g2) {
if (g1) return g1;
}));
JWB.setup.extend(data);
} catch(e) {
alert(JWB.msg('json-err', e.message, JWB.msg('json-err-upload')));
console.log(e); //also log the error for further info
return;
}
};
JWB.status('Processing file');
};
 
JWB.setup.load = function() {
JWB.status('setup-load');
var user = JWB.username||mw.config.get('wgUserName');
var oldtitle = "User:" + user + '/'+JWB.settingspage; // page title for what was used before version 4.0
var newtitle = "User:" + user + '/JWB-settings.json'; // new page title for all settings pages.
var titles = oldtitle;
// if the old title isn't JWB-settings.json, also query the new title.
if (oldtitle !== newtitle && JWB.hasJSON) {
titles += '|' + newtitle;
}
JWB.api.call({
action: 'query',
titles: titles,
prop: 'info|revisions',
meta: 'tokens',
rvprop: 'content',
indexpageids: true
}, function(response) {
if (JWB === false) return; //user is not allowed to use JWB
var firstrun = !JWB.setup.initialised;
JWB.setup.initialised = true;
var edittoken = response.query.tokens.csrftoken;
 
// determine correct page to get settings from
var pages = response.query.pages,
ids = response.query.pageids;
var page, exists = true;
if (ids.length == 2) {
var page0 = pages[ids[0]],
page1 = pages[ids[1]];
var oldpage, newpage;
if (page0.title == oldtitle) {
oldpage = page0;
newpage = page1;
} else {
oldpage = page1;
newpage = page0;
}
if (oldpage.missing === undefined && oldpage.redirect === undefined) {
// old page exists and is not a redirect
if (newpage.missing === undefined) {
// both old AND new page exist; throw error and load neither page.
let jsredir = "https://www.mediawiki.org/wiki/Help:Redirects#JavaScript_page_redirect";
prompt(JWB.msg('duplicate-settings', oldtitle, newtitle, jsredir), jsredir);
exists = false;
} else {
// old page exists but new page doesn't; move the page to the new location.
JWB.setup.moveNew(oldtitle, newtitle, edittoken);
JWB.settingspage = 'JWB-settings.json';
return;
}
} else {
// Old page either doesn't exist or is a redirect. Don't bother with it.
page = newpage;
exists = (page.missing === undefined);
JWB.settingspage = 'JWB-settings.json';
}
} else {
page = pages[ids[0]];
exists = (page.missing === undefined);
}
if (!exists) {
// settings page does not exist; don't load anything
if (JWB.allowed && firstrun) JWB.setup.save('default'); //this runs when this callback returns after the init has loaded.
return;
}
var data = page.revisions[0]['*'].split('{{#JWB-SAFESUBST:#').join('{{subst:');
if (!data) {
// settings page is empty; don't load anything.
if (JWB.allowed && firstrun) JWB.setup.save('default'); //this runs when this callback returns after the init has loaded.
return;
}
try {
data = JSON.parse(data);
} catch(e) {
alert(JWB.msg('json-err', e.message, JWB.msg('json-err-page', JWB.settingspage)) || 'JSON error:\n'+e.message);
JWB.setup.save('default');
return;
}
JWB.setup.extend(data);
JWB.status('done', true);
});
};

Revision as of 23:50, 25 January 2024

{{delete}}