Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* AntiVandal - Bharatpedia patrol helper
* Host: User:WikiDwarf/AntiVandal.js
* Version 1.1.1
*/
(function () {
'use strict';
if (window.AntiVandal && window.AntiVandal.loaded) {
return;
}
var RK = (window.AntiVandal = window.AntiVandal || {});
RK.loaded = true;
RK.version = '1.1.1';
var CONF = {
storageKey: 'antivandal-prefs-v1',
packKey: 'antivandal-packs-v1',
tag: 'AntiVandal',
aivPage: 'Bharatpedia:Administrator intervention against vandalism',
rfppPage: 'Bharatpedia:Requests for page protection',
anPage: 'Bharatpedia:Administrators noticeboard',
privacyPage: 'Bharatpedia:Requests for oversight',
docsPage: 'Bharatpedia:AntiVandal'
};
var PRESETS = [
{ id: 'vandalism', label: 'Vandalism', summary: 'Reverted unconstructive edit(s)', notice: 'uw-vandalism', level: true },
{ id: 'blanking', label: 'Blanking', summary: 'Reverted unexplained content removal', notice: 'uw-delete', level: true },
{ id: 'test', label: 'Test edit', summary: 'Reverted test edit', notice: 'uw-test', level: true },
{ id: 'unsourced', label: 'Unsourced', summary: 'Reverted unsourced or poorly sourced addition', notice: 'uw-unsourced', level: true },
{ id: 'spam', label: 'Spam / promo', summary: 'Reverted promotional or spam content', notice: 'uw-spam', level: true },
{ id: 'npov', label: 'POV / attack', summary: 'Reverted non-neutral or attack content', notice: 'uw-biog', level: true },
{ id: 'custom', label: 'Custom summary', summary: '', notice: '', level: false }
];
var NOTICES = [
{ id: 'uw-vandalism', label: 'Vandalism', leveled: true },
{ id: 'uw-test', label: 'Test edit', leveled: true },
{ id: 'uw-delete', label: 'Removing content', leveled: true },
{ id: 'uw-unsourced', label: 'Unsourced content', leveled: true },
{ id: 'uw-error', label: 'Factual error / hoax', leveled: true },
{ id: 'uw-spam', label: 'Spam / advertising', leveled: true },
{ id: 'uw-advert', label: 'Promotional tone', leveled: true },
{ id: 'uw-biog', label: 'BLP / biography issue', leveled: true },
{ id: 'uw-defamatory', label: 'Defamatory content', leveled: true },
{ id: 'uw-joke', label: 'Joke edit', leveled: true },
{ id: 'uw-notvoted', label: 'Not a vote / forum', leveled: false },
{ id: 'uw-chat', label: 'Talk page as chat', leveled: false },
{ id: 'welcome', label: 'Welcome (constructive new editor)', leveled: false },
{ id: 'welcometest', label: 'Welcome after a test edit', leveled: false }
];
function defaultPrefs() {
return { barPosition: 'top', useRollback: true, autoNotice: false };
}
function loadPrefs() {
try {
var raw = localStorage.getItem(CONF.storageKey);
return raw ? Object.assign(defaultPrefs(), JSON.parse(raw)) : defaultPrefs();
} catch (e) {
return defaultPrefs();
}
}
function savePrefs(p) {
try { localStorage.setItem(CONF.storageKey, JSON.stringify(p)); } catch (e) {}
}
var prefs = loadPrefs();
var api = null;
var hasRollback = false;
function toast(msg, kind) {
var el = document.getElementById('antivandal-toast');
if (!el) {
el = document.createElement('div');
el.id = 'antivandal-toast';
document.body.appendChild(el);
}
el.className = 'rk-toast rk-toast-' + (kind || 'info');
el.textContent = msg;
el.style.display = 'block';
clearTimeout(toast._t);
toast._t = setTimeout(function () { el.style.display = 'none'; }, 4200);
}
function pageName() {
return mw.config.get('wgPageName');
}
function relevantUser() {
return mw.config.get('wgRelevantUserName') || null;
}
function talkTitleFor(user) {
if (!user) { return null; }
return 'User talk:' + user;
}
function wikiSig() {
var name = mw.config.get('wgUserName') || '';
return '[[' + 'User:' + name + '|' + name + ']] ([[' + 'User talk:' + name + '|talk]]) 16:07, 21 September 2026 (IST)';
}
function injectCss() {
if (document.getElementById('antivandal-css')) { return; }
var css = [
'#antivandal-open.bpneo-title-btn{color:#c62828;}',
'#antivandal-tools{display:none;align-items:center;gap:inherit;}',
'.bpneo-title-actions.av-open > :not(#antivandal-tools){display:none !important;}',
'.bpneo-title-actions.av-open #antivandal-tools{display:inline-flex;}',
'#antivandal-tools .bpneo-title-btn.rk-on{color:#2a4a33;}',
'#antivandal-more{position:absolute;right:0;top:36px;background:#fff;border:1px solid #a2a9b1;',
'box-shadow:0 2px 8px rgba(0,0,0,.18);min-width:220px;z-index:50;display:none;padding:4px 0;}',
'#antivandal-more button{display:block;width:100%;text-align:left;border:0;background:none;',
'padding:8px 12px;cursor:pointer;font:13px sans-serif;}',
'#antivandal-more button:hover{background:#eaecf0;}',
'#antivandal-ctx{position:fixed;z-index:1300;background:#fff;border:1px solid #a2a9b1;',
'box-shadow:0 2px 8px rgba(0,0,0,.2);min-width:180px;padding:4px 0;display:none;}',
'#antivandal-ctx button{display:block;width:100%;text-align:left;border:0;background:none;',
'padding:8px 12px;cursor:pointer;font:13px sans-serif;}',
'#antivandal-ctx button:hover{background:#eaecf0;}',
'.rk-modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1100;display:flex;',
'align-items:flex-start;justify-content:center;padding:8vh 16px 16px;}',
'.rk-modal{background:#fff;color:#1b1b1b;max-width:560px;width:100%;border-radius:8px;',
'box-shadow:0 12px 40px rgba(0,0,0,.28);overflow:hidden;}',
'.rk-modal h2{margin:0;padding:12px 16px;background:#1f2a22;color:#f4efe4;font-size:16px;}',
'.rk-modal .rk-body{padding:14px 16px;}',
'.rk-modal label{display:block;font-weight:600;margin:10px 0 4px;}',
'.rk-modal input[type=text],.rk-modal select,.rk-modal textarea{width:100%;box-sizing:border-box;',
'padding:7px 8px;font:14px sans-serif;}',
'.rk-modal textarea{min-height:90px;}',
'.rk-modal .rk-actions{display:flex;justify-content:flex-end;gap:8px;padding:10px 16px;background:#f3f1ea;}',
'.rk-modal .rk-actions button{padding:7px 14px;border-radius:4px;border:1px solid #bbb;cursor:pointer;}',
'.rk-modal .rk-actions .rk-primary{background:#1f2a22;color:#fff;border-color:#1f2a22;}',
'.rk-toast{position:fixed;right:16px;bottom:16px;z-index:1200;background:#1f2a22;color:#fff;',
'padding:10px 14px;border-radius:6px;max-width:360px;display:none;}',
'.rk-toast-err{background:#6b2a22;}',
'.rk-toast-ok{background:#2a4a33;}',
'#av-patrol{position:fixed;top:0;right:0;width:320px;height:100vh;background:#111;color:#eee;',
'z-index:1250;overflow:auto;font:13px sans-serif;}',
'#av-patrol h3{margin:0;padding:12px 14px;background:#1f2a22;}',
'#av-patrol .av-p-item{display:block;padding:8px 12px;border-bottom:1px solid #333;color:#eee;text-decoration:none;}',
'.av-contrib-rev{margin-left:6px;font-size:12px;cursor:pointer;color:#c62828;border:0;background:none;}'
].join('');
var style = document.createElement('style');
style.id = 'antivandal-css';
style.textContent = css;
document.head.appendChild(style);
}
function closeModal() {
var bg = document.getElementById('antivandal-modal-bg');
if (bg) { bg.parentNode.removeChild(bg); }
}
function openModal(title, bodyNode, actions) {
closeModal();
var bg = document.createElement('div');
bg.id = 'antivandal-modal-bg';
bg.className = 'rk-modal-bg';
bg.addEventListener('click', function (e) { if (e.target === bg) { closeModal(); } });
var box = document.createElement('div');
box.className = 'rk-modal';
var h = document.createElement('h2');
h.textContent = title;
box.appendChild(h);
var body = document.createElement('div');
body.className = 'rk-body';
body.appendChild(bodyNode);
box.appendChild(body);
var foot = document.createElement('div');
foot.className = 'rk-actions';
(actions || []).forEach(function (a) {
var b = document.createElement('button');
b.type = 'button';
b.textContent = a.label;
if (a.primary) { b.className = 'rk-primary'; }
b.addEventListener('click', a.onClick);
foot.appendChild(b);
});
box.appendChild(foot);
bg.appendChild(box);
document.body.appendChild(bg);
}
function field(labelText, node) {
var wrap = document.createElement('div');
var lab = document.createElement('label');
lab.textContent = labelText;
wrap.appendChild(lab);
wrap.appendChild(node);
return wrap;
}
function getLastRev() {
return api.get({
action: 'query', prop: 'revisions', titles: pageName(),
rvlimit: 2, rvprop: 'ids|user|comment|timestamp', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
if (!page || page.missing || !page.revisions || !page.revisions.length) { return null; }
return { page: page, latest: page.revisions[0], previous: page.revisions[1] || null };
});
}
function doUndo(info, summary) {
if (!info.previous) {
throw new Error('Nothing to restore: that editor is the only author, or there is no earlier revision.');
}
return api.postWithEditToken({
action: 'edit', title: pageName(), undo: info.latest.revid, undoafter: info.previous.revid,
summary: summary + ' ([[Bharatpedia:AntiVandal|AntiVandal]])', formatversion: 2
}).then(function (res) {
if (res.error) { throw new Error(res.error.info || res.error.code); }
return { method: 'undo', data: res };
});
}
function revertWithSummary(summary, userToRevert) {
return getLastRev().then(function (info) {
if (!info) { throw new Error('Could not read this page history.'); }
var targetUser = userToRevert || info.latest.user;
function errCode(err) {
return (err && (err.code || err.name)) || (String(err.message || err));
}
if (prefs.useRollback && hasRollback) {
return api.postWithToken('rollback', {
action: 'rollback', title: pageName(), user: targetUser,
summary: summary + ' ([[Bharatpedia:AntiVandal|AntiVandal]])', formatversion: 2
}).then(function (res) {
if (res.error) { throw res.error; }
return { method: 'rollback', data: res };
}).catch(function (err) {
var code = errCode(err);
if (String(code).indexOf('onlyauthor') !== -1 ||
String(code).indexOf('alreadyrolled') !== -1 ||
String(err.info || '').indexOf('only author') !== -1) {
return doUndo(info, summary);
}
throw new Error(err.info || err.message || code || String(err));
});
}
return doUndo(info, summary);
});
}
function restoreRevision(revid) {
return api.get({
action: 'query', prop: 'revisions', revids: revid,
rvprop: 'ids|content|timestamp|user', rvslots: 'main', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var rev = page && page.revisions && page.revisions[0];
var text = rev && rev.slots && rev.slots.main && rev.slots.main.content;
if (!text && text !== '') { throw new Error('Could not load that revision.'); }
return api.postWithEditToken({
action: 'edit', title: page.title || pageName(), text: text,
summary: 'Restored revision ' + revid + ' ([[Bharatpedia:AntiVandal|AntiVandal]])',
formatversion: 2
});
});
}
function appendTalkNotice(user, templateName, article, extra) {
var talk = talkTitleFor(user);
var subst = '{' + '{subst:' + templateName;
if (article) { subst += '|1=' + article.replace(/_/g, ' '); }
if (extra) { subst += '|2=' + extra; }
subst += '}} ' + wikiSig();
return api.postWithEditToken({
action: 'edit', title: talk, appendtext: '\n\n' + subst,
summary: 'Notice ([[Bharatpedia:AntiVandal|AntiVandal]])', formatversion: 2
});
}
function detectTalkLevel(user) {
if (!user) { return Promise.resolve(1); }
return api.get({
action: 'query', prop: 'revisions', titles: talkTitleFor(user),
rvlimit: 1, rvprop: 'content', rvslots: 'main', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var text = page && page.revisions && page.revisions[0] &&
page.revisions[0].slots && page.revisions[0].slots.main &&
page.revisions[0].slots.main.content || '';
var max = 0;
var re = /\{\{(?:subst:)?uw-[\w-]+([1-4])\b/gi;
var m;
while ((m = re.exec(text))) { max = Math.max(max, parseInt(m[1], 10)); }
return Math.min(4, max + 1) || 1;
}).catch(function () { return 1; });
}
function openRevertDialog() {
getLastRev().then(function (info) {
var user = info && info.latest ? info.latest.user : (relevantUser() || '');
var wrap = document.createElement('div');
var userInput = document.createElement('input');
userInput.type = 'text';
userInput.value = user || '';
var sel = document.createElement('select');
PRESETS.forEach(function (p) {
var o = document.createElement('option');
o.value = p.id;
o.textContent = p.label;
sel.appendChild(o);
});
var sum = document.createElement('input');
sum.type = 'text';
sum.value = PRESETS[0].summary;
sel.addEventListener('change', function () {
var p = PRESETS.filter(function (x) { return x.id === sel.value; })[0];
if (p && p.summary) { sum.value = p.summary; }
});
var alsoWarn = document.createElement('input');
alsoWarn.type = 'checkbox';
var warnLab = document.createElement('label');
warnLab.style.fontWeight = 'normal';
warnLab.textContent = ' Open notice dialog after revert';
var row = document.createElement('div');
row.style.marginTop = '10px';
row.appendChild(alsoWarn);
row.appendChild(warnLab);
wrap.appendChild(field('Editor to revert', userInput));
wrap.appendChild(field('Reason', sel));
wrap.appendChild(field('Edit summary', sum));
wrap.appendChild(row);
openModal('Revert with AntiVandal', wrap, [
{ label: 'Cancel', onClick: closeModal },
{
label: 'Revert', primary: true,
onClick: function () {
var summary = (sum.value || 'Reverted edit').trim();
var who = userInput.value.trim();
if (!who) { toast('Name the editor.', 'err'); return; }
toast('Reverting...');
revertWithSummary(summary, who).then(function (res) {
toast('Reverted with ' + res.method + '.', 'ok');
closeModal();
if (alsoWarn.checked) { openWarnDialog(who, sel.value); }
else { setTimeout(function () { location.reload(); }, 700); }
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
}
]);
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
function recentEditors(limit) {
return api.get({
action: 'query', prop: 'revisions', titles: pageName(),
rvlimit: 40, rvprop: 'user', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var revs = (page && page.revisions) || [];
var seen = {};
var out = [];
revs.forEach(function (r) {
if (r.user && !seen[r.user]) {
seen[r.user] = true;
out.push(r.user);
}
});
return out.slice(0, limit || 5);
}).catch(function () { return []; });
}
function bindUserSuggest(input, listEl) {
var t = null;
input.addEventListener('input', function () {
clearTimeout(t);
var q = input.value.trim();
if (q.length < 2) { return; }
t = setTimeout(function () {
api.get({
action: 'query', list: 'allusers', aufrom: q, auprefix: q,
aulimit: 8, formatversion: 2
}).then(function (data) {
var names = (data.query && data.query.allusers || []).map(function (u) { return u.name; });
names.forEach(function (name) {
var exists = false;
for (var i = 0; i < listEl.options.length; i++) {
if (listEl.options[i].value === name) { exists = true; break; }
}
if (!exists) {
var o = document.createElement('option');
o.value = name;
listEl.appendChild(o);
}
});
}).catch(function () {});
}, 250);
});
}
function openWarnDialog(prefillUser, presetId) {
var wrap = document.createElement('div');
var recentSel = document.createElement('select');
var opt0 = document.createElement('option');
opt0.value = '';
opt0.textContent = 'Recent editors on this page';
recentSel.appendChild(opt0);
var userInput = document.createElement('input');
userInput.type = 'text';
userInput.setAttribute('list', 'av-user-suggest');
userInput.autocomplete = 'off';
userInput.value = prefillUser || relevantUser() || '';
var suggest = document.createElement('datalist');
suggest.id = 'av-user-suggest';
recentSel.addEventListener('change', function () {
if (recentSel.value) { userInput.value = recentSel.value; }
});
recentEditors(5).then(function (names) {
names.forEach(function (name) {
var o = document.createElement('option');
o.value = name;
o.textContent = name;
recentSel.appendChild(o);
var d = document.createElement('option');
d.value = name;
suggest.appendChild(d);
});
if (!userInput.value && names[0]) { userInput.value = names[0]; }
});
bindUserSuggest(userInput, suggest);
var articleInput = document.createElement('input');
articleInput.type = 'text';
articleInput.value = pageName().replace(/_/g, ' ');
var sel = document.createElement('select');
NOTICES.forEach(function (n) {
var o = document.createElement('option');
o.value = n.id;
o.textContent = n.label + (n.leveled ? ' (1-4)' : '');
sel.appendChild(o);
});
if (presetId) {
var mapped = { vandalism: 'uw-vandalism', blanking: 'uw-delete', test: 'uw-test', unsourced: 'uw-unsourced', spam: 'uw-spam', npov: 'uw-biog' }[presetId];
if (mapped) { sel.value = mapped; }
}
var level = document.createElement('select');
[1, 2, 3, 4].forEach(function (n) {
var o = document.createElement('option');
o.value = String(n);
o.textContent = 'Level ' + n;
level.appendChild(o);
});
var extra = document.createElement('textarea');
var hint = document.createElement('p');
hint.style.fontSize = '12px';
hint.textContent = 'Checking talk page for prior notices...';
function currentTemplate() {
var meta = NOTICES.filter(function (n) { return n.id === sel.value; })[0];
return (meta && meta.leveled) ? sel.value + level.value : sel.value;
}
wrap.appendChild(field('Recent editors', recentSel));
wrap.appendChild(field('User (type to search)', userInput));
wrap.appendChild(suggest);
wrap.appendChild(field('Related article', articleInput));
wrap.appendChild(field('Notice', sel));
wrap.appendChild(field('Level', level));
wrap.appendChild(field('Extra note', extra));
wrap.appendChild(hint);
detectTalkLevel(userInput.value.trim()).then(function (n) {
level.value = String(n);
hint.textContent = 'Suggested level ' + n + ' from the talk page.';
});
openModal('Post a talk-page notice', wrap, [
{ label: 'Cancel', onClick: closeModal },
{
label: 'Post notice', primary: true,
onClick: function () {
var user = userInput.value.trim();
if (!user) { toast('Enter a username or IP.', 'err'); return; }
var tpl = currentTemplate();
toast('Posting...');
appendTalkNotice(user, tpl, articleInput.value.trim(), extra.value.trim()).then(function (res) {
if (res.error) { throw new Error(res.error.info || res.error.code); }
toast('Notice posted.', 'ok');
closeModal();
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
}
]);
}
function openPrefs() {
var wrap = document.createElement('div');
var rb = document.createElement('input');
rb.type = 'checkbox';
rb.checked = !!prefs.useRollback;
var rbLab = document.createElement('label');
rbLab.style.fontWeight = 'normal';
rbLab.textContent = ' Prefer software rollback when available';
var row = document.createElement('div');
row.appendChild(rb);
row.appendChild(rbLab);
wrap.appendChild(row);
openModal('AntiVandal preferences', wrap, [
{ label: 'Close', onClick: closeModal },
{
label: 'Save', primary: true,
onClick: function () {
prefs.useRollback = rb.checked;
savePrefs(prefs);
closeModal();
toast('Saved.', 'ok');
}
}
]);
}
function goLatest() {
location.href = mw.util.getUrl(pageName(), { diff: 'cur', oldid: 'prev' });
}
function reportAiv() {
getLastRev().then(function (info) {
var user = (info && info.latest && info.latest.user) || relevantUser() || '';
var wrap = document.createElement('div');
var ta = document.createElement('textarea');
ta.value = '* {' + '{vandal|' + user + '}} - unconstructive editing on [[' +
pageName().replace(/_/g, ' ') + ']]. ' + wikiSig();
wrap.appendChild(ta);
openModal('Report at AIV', wrap, [
{ label: 'Cancel', onClick: closeModal },
{
label: 'Open AIV', primary: true,
onClick: function () {
window.open(mw.util.getUrl(CONF.aivPage, { action: 'edit', section: 'new' }), '_blank');
}
}
]);
});
}
function requestProtection() {
var wrap = document.createElement('div');
var art = document.createElement('input');
art.type = 'text';
art.value = pageName().replace(/_/g, ' ');
var level = document.createElement('select');
['semi-protection', 'extended confirmed', 'full protection', 'move protection', 'unprotection'].forEach(function (v) {
var o = document.createElement('option');
o.value = v;
o.textContent = v;
level.appendChild(o);
});
var reason = document.createElement('textarea');
reason.placeholder = 'Why this page needs that level';
var current = document.createElement('p');
current.textContent = 'Checking current protection...';
wrap.appendChild(field('Article', art));
wrap.appendChild(field('Requested level', level));
wrap.appendChild(field('Reason', reason));
wrap.appendChild(current);
api.get({
action: 'query', titles: pageName(), prop: 'info', inprop: 'protection', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var prot = (page && page.protection) || [];
current.textContent = prot.length
? ('Current: ' + prot.map(function (p) { return p.type + '=' + p.level; }).join(', '))
: 'Current: not protected';
}).catch(function () { current.textContent = ''; });
function draft() {
return '=== [[' + art.value.trim() + ']] ===\n' +
'* Requested level: ' + level.value + '\n' +
'* Reason: ' + reason.value.trim() + '\n' +
wikiSig() + '\n';
}
openModal('Request page protection', wrap, [
{ label: 'Cancel', onClick: closeModal },
{
label: 'Open board with draft',
onClick: function () {
try { sessionStorage.setItem('antivandal-rfpp-draft', draft()); } catch (e) {}
window.open(mw.util.getUrl(CONF.rfppPage, { action: 'edit', section: 'new' }), '_blank');
}
},
{
label: 'Post request', primary: true,
onClick: function () {
if (!art.value.trim() || !reason.value.trim()) {
toast('Article and reason are required.', 'err');
return;
}
toast('Posting request...');
api.postWithEditToken({
action: 'edit', title: CONF.rfppPage, appendtext: '\n' + draft(),
summary: 'Protection request for [[' + art.value.trim() + ']] ([[Bharatpedia:AntiVandal|AntiVandal]])',
formatversion: 2
}).then(function (res) {
if (res.error) { throw new Error(res.error.info || res.error.code); }
toast('Request posted.', 'ok');
closeModal();
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
}
]);
}
function openAdminReport(kind) {
var title = kind === 'privacy' ? CONF.privacyPage : CONF.anPage;
window.open(mw.util.getUrl(title, { action: 'edit', section: 'new' }), '_blank');
}
function openNewMessage(prefillUser) {
var wrap = document.createElement('div');
var userInput = document.createElement('input');
userInput.type = 'text';
userInput.value = prefillUser || relevantUser() || '';
var subj = document.createElement('input');
subj.type = 'text';
var body = document.createElement('textarea');
wrap.appendChild(field('User', userInput));
wrap.appendChild(field('Subject', subj));
wrap.appendChild(field('Message', body));
openModal('New talk-page message', wrap, [
{ label: 'Cancel', onClick: closeModal },
{
label: 'Send', primary: true,
onClick: function () {
var user = userInput.value.trim();
if (!user || !subj.value.trim()) { toast('Need a user and a subject.', 'err'); return; }
var text = '\n\n== ' + subj.value.trim() + ' ==\n' + body.value + '\n' + wikiSig();
toast('Sending...');
api.postWithEditToken({
action: 'edit', title: talkTitleFor(user), appendtext: text,
summary: 'New message ([[Bharatpedia:AntiVandal|AntiVandal]])', formatversion: 2
}).then(function (res) {
if (res.error) { throw new Error(res.error.info || res.error.code); }
toast('Message posted.', 'ok');
closeModal();
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
}
]);
}
var alertTimer = null;
var alertLastRev = null;
function toggleAlert(btn) {
if (alertTimer) {
clearInterval(alertTimer);
alertTimer = null;
btn.classList.remove('rk-on');
toast('Alert off.');
return;
}
btn.classList.add('rk-on');
toast('Watching this page.');
getLastRev().then(function (info) {
alertLastRev = info && info.latest ? info.latest.revid : null;
});
alertTimer = setInterval(function () {
getLastRev().then(function (info) {
var id = info && info.latest ? info.latest.revid : null;
if (id && alertLastRev && id !== alertLastRev) {
alertLastRev = id;
toast('New edit on this page.', 'ok');
goLatest();
} else if (id) { alertLastRev = id; }
});
}, 12000);
}
function loadPacks() {
try {
var raw = localStorage.getItem(CONF.packKey);
if (raw) { return JSON.parse(raw); }
} catch (e) {}
return [{
name: 'Starter',
items: [
{ label: 'Welcome', wikitext: '{' + '{subst:welcome}}' },
{ label: 'Please discuss', wikitext: 'Please discuss changes on the talk page. ' + wikiSig() }
]
}];
}
function openQuickTemplates(prefillUser) {
var packs = loadPacks();
var wrap = document.createElement('div');
var userInput = document.createElement('input');
userInput.type = 'text';
userInput.value = prefillUser || relevantUser() || '';
wrap.appendChild(field('User talk target', userInput));
packs.forEach(function (pack) {
(pack.items || []).forEach(function (item) {
var b = document.createElement('button');
b.type = 'button';
b.textContent = item.label;
b.style.margin = '0 6px 6px 0';
b.addEventListener('click', function () {
var user = userInput.value.trim();
if (!user) { toast('Enter a user.', 'err'); return; }
api.postWithEditToken({
action: 'edit', title: talkTitleFor(user),
appendtext: '\n\n' + item.wikitext,
summary: 'Template ([[Bharatpedia:AntiVandal|AntiVandal]])', formatversion: 2
}).then(function (res) {
if (res.error) { throw new Error(res.error.info || res.error.code); }
toast('Posted.', 'ok');
closeModal();
}).catch(function (err) { toast(err.message || String(err), 'err'); });
});
wrap.appendChild(b);
});
});
openModal('Quick templates', wrap, [{ label: 'Close', onClick: closeModal }]);
}
function togglePatrol() {
var box = document.getElementById('av-patrol');
if (box) {
box.parentNode.removeChild(box);
if (togglePatrol._t) { clearInterval(togglePatrol._t); togglePatrol._t = null; }
return;
}
box = document.createElement('div');
box.id = 'av-patrol';
box.innerHTML = '<h3>Patrol <button type="button" id="av-patrol-x">x</button></h3><div id="av-patrol-list"></div>';
document.body.appendChild(box);
document.getElementById('av-patrol-x').addEventListener('click', togglePatrol);
function refresh() {
api.get({
action: 'query', list: 'recentchanges',
rcprop: 'title|ids|sizes|user|comment|timestamp',
rclimit: 25, rctype: 'edit|new', formatversion: 2
}).then(function (data) {
var list = document.getElementById('av-patrol-list');
if (!list) { return; }
list.innerHTML = '';
(data.query && data.query.recentchanges || []).forEach(function (rc) {
var a = document.createElement('a');
a.className = 'av-p-item';
a.href = mw.util.getUrl(rc.title, { diff: rc.revid, oldid: rc.old_revid || 'prev' });
a.textContent = rc.title + ' - ' + rc.user;
list.appendChild(a);
});
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
refresh();
togglePatrol._t = setInterval(refresh, 8000);
}
function openMat() {
toast('Loading history...');
api.get({
action: 'query', prop: 'revisions', titles: pageName(),
rvlimit: 30, rvprop: 'ids|user|comment|timestamp', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var revs = (page && page.revisions) || [];
var seen = {};
var users = [];
revs.forEach(function (r) {
if (r.user && !seen[r.user]) { seen[r.user] = true; users.push(r.user); }
});
var wrap = document.createElement('div');
var boxes = [];
users.forEach(function (u) {
var lab = document.createElement('label');
lab.style.display = 'block';
lab.style.fontWeight = 'normal';
var c = document.createElement('input');
c.type = 'checkbox';
c.value = u;
c.checked = true;
lab.appendChild(c);
lab.appendChild(document.createTextNode(' ' + u));
wrap.appendChild(lab);
boxes.push(c);
});
var sel = document.createElement('select');
NOTICES.forEach(function (n) {
var o = document.createElement('option');
o.value = n.id + (n.leveled ? '1' : '');
o.textContent = n.label;
sel.appendChild(o);
});
wrap.appendChild(field('Notice', sel));
openModal('Multiple notices', wrap, [
{ label: 'Cancel', onClick: closeModal },
{
label: 'Post to checked', primary: true,
onClick: function () {
var chosen = boxes.filter(function (c) { return c.checked; }).map(function (c) { return c.value; });
if (!chosen.length) { toast('None selected.', 'err'); return; }
if (!window.confirm('Post to ' + chosen.length + ' talk pages?')) { return; }
var chain = Promise.resolve();
chosen.forEach(function (u) {
chain = chain.then(function () {
return appendTalkNotice(u, sel.value, pageName().replace(/_/g, ' '), '');
});
});
chain.then(function () { toast('Done.', 'ok'); closeModal(); })
.catch(function (err) { toast(err.message || String(err), 'err'); });
}
}
]);
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}
function openPendingReview() {
api.get({ action: 'query', list: 'oldreviewedpages', orlimit: 15, formatversion: 2 }).then(function (data) {
var pages = (data.query && data.query.oldreviewedpages) || [];
if (!pages.length) { toast('No pending-review queue on this wiki.', 'err'); return; }
var wrap = document.createElement('div');
pages.forEach(function (p) {
var a = document.createElement('a');
a.href = mw.util.getUrl(p.title);
a.textContent = p.title;
a.style.display = 'block';
wrap.appendChild(a);
});
openModal('Pending review', wrap, [{ label: 'Close', onClick: closeModal }]);
}).catch(function () { toast('Pending changes is not available here.', 'err'); });
}
function bindContribsRollback() {
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Contributions') { return; }
var list = document.querySelector('.mw-contributions-list');
if (!list) { return; }
list.querySelectorAll('li').forEach(function (li) {
if (li.querySelector('.av-contrib-rev')) { return; }
var diff = li.querySelector('a[href*="diff="]');
if (!diff) { return; }
var b = document.createElement('button');
b.type = 'button';
b.className = 'av-contrib-rev';
b.textContent = 'revert';
b.addEventListener('click', function (e) {
e.preventDefault();
var href = diff.getAttribute('href') || '';
var titleMatch = href.match(/title=([^&]+)/) || href.match(/\/wiki\/([^?]+)/);
var title = titleMatch ? decodeURIComponent(titleMatch[1].replace(/\+/g, ' ')) : null;
var user = mw.config.get('wgRelevantUserName');
if (!title || !user) { toast('Could not read that row.', 'err'); return; }
if (!window.confirm('Revert latest edit by ' + user + ' on ' + title + '?')) { return; }
api.get({
action: 'query', prop: 'revisions', titles: title.replace(/ /g, '_'),
rvlimit: 2, rvprop: 'ids|user', formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var latest = page && page.revisions && page.revisions[0];
if (!latest) { throw new Error('No revision'); }
if (prefs.useRollback && hasRollback) {
return api.postWithToken('rollback', {
action: 'rollback', title: title, user: user,
summary: 'Reverted unconstructive edit(s) ([[Bharatpedia:AntiVandal|AntiVandal]])',
formatversion: 2
});
}
var prev = page.revisions[1];
if (!prev) { throw new Error('No earlier revision'); }
return api.postWithEditToken({
action: 'edit', title: title, undo: latest.revid, undoafter: prev.revid,
summary: 'Reverted unconstructive edit(s) ([[Bharatpedia:AntiVandal|AntiVandal]])',
formatversion: 2
});
}).then(function () { toast('Reverted.', 'ok'); })
.catch(function (err) { toast(err.message || String(err), 'err'); });
});
li.appendChild(b);
});
}
function bindUserContextMenu() {
document.addEventListener('contextmenu', function (e) {
if (!e.shiftKey) { return; }
var a = e.target.closest ? e.target.closest('a') : null;
if (!a || !a.href) { return; }
var m = a.href.match(/[?&]title=User:([^&#]+)/i) || a.href.match(/\/wiki\/User:([^?#]+)/);
if (!m) { return; }
e.preventDefault();
var user = decodeURIComponent(m[1]).replace(/_/g, ' ').split('/')[0];
var ctx = document.getElementById('antivandal-ctx');
if (!ctx) {
ctx = document.createElement('div');
ctx.id = 'antivandal-ctx';
document.body.appendChild(ctx);
}
ctx.innerHTML = '';
function item(label, fn) {
var b = document.createElement('button');
b.type = 'button';
b.textContent = label;
b.addEventListener('click', function () { ctx.style.display = 'none'; fn(); });
ctx.appendChild(b);
}
item('User page', function () { location.href = mw.util.getUrl('User:' + user); });
item('Talk page', function () { location.href = mw.util.getUrl('User talk:' + user); });
item('Contributions', function () { location.href = mw.util.getUrl('Special:Contributions/' + user); });
item('New message', function () { openNewMessage(user); });
item('Notice', function () { openWarnDialog(user); });
item('Quick template', function () { openQuickTemplates(user); });
ctx.style.left = e.pageX + 'px';
ctx.style.top = e.pageY + 'px';
ctx.style.display = 'block';
});
document.addEventListener('click', function () {
var ctx = document.getElementById('antivandal-ctx');
if (ctx) { ctx.style.display = 'none'; }
var more = document.getElementById('antivandal-more');
if (more && more.style.display === 'block') { more.style.display = 'none'; }
});
}
function neoBtn(iconClass, tip, fn) {
var b = document.createElement('button');
b.type = 'button';
b.className = 'bpneo-title-btn';
b.title = tip;
b.setAttribute('aria-label', tip);
var i = document.createElement('i');
i.className = iconClass;
b.appendChild(i);
if (fn) {
b.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
fn(b);
});
}
return b;
}
function buildBar() {
if (document.getElementById('antivandal-open')) { return true; }
var row = document.querySelector('.bpneo-title-actions');
if (!row) { return false; }
var openBtn = neoBtn('ri-error-warning-fill', 'AntiVandal tools');
openBtn.id = 'antivandal-open';
openBtn.style.color = '#c62828';
var tools = document.createElement('span');
tools.id = 'antivandal-tools';
tools.appendChild(neoBtn('ri-close-line', 'Close AntiVandal', function () {
row.classList.remove('av-open');
}));
tools.appendChild(neoBtn('ri-arrow-go-back-line', 'Revert last editor', openRevertDialog));
tools.appendChild(neoBtn('ri-history-line', 'Restore this revision', function () {
var oldid = mw.util.getParamValue('oldid') || mw.config.get('wgRevisionId');
if (!oldid) { toast('Open a specific revision first.', 'err'); return; }
if (!window.confirm('Replace current text with revision ' + oldid + '?')) { return; }
restoreRevision(oldid).then(function () {
toast('Restored.', 'ok');
setTimeout(function () { location.href = mw.util.getUrl(pageName()); }, 600);
}).catch(function (err) { toast(err.message || String(err), 'err'); });
}));
tools.appendChild(neoBtn('ri-refresh-line', 'Latest revision', goLatest));
var alertBtn = neoBtn('ri-radar-line', 'Alert on change', function () { toggleAlert(alertBtn); });
tools.appendChild(alertBtn);
tools.appendChild(neoBtn('ri-pulse-line', 'Live patrol', togglePatrol));
tools.appendChild(neoBtn('ri-shield-keyhole-line', 'Request protection', requestProtection));
var moreWrap = document.createElement('span');
moreWrap.style.position = 'relative';
var moreBtn = neoBtn('ri-more-2-fill', 'More');
var menu = document.createElement('div');
menu.id = 'antivandal-more';
function menuItem(label, fn) {
var mb = document.createElement('button');
mb.type = 'button';
mb.textContent = label;
mb.addEventListener('click', function (e) {
e.stopPropagation();
menu.style.display = 'none';
fn();
});
menu.appendChild(mb);
}
menuItem('Post a notice', function () { openWarnDialog(); });
menuItem('New talk-page message', function () { openNewMessage(); });
menuItem('Quick templates', function () { openQuickTemplates(); });
menuItem('Report at AIV', reportAiv);
menuItem('Report to administrators', function () { openAdminReport('admin'); });
menuItem('Privacy request', function () { openAdminReport('privacy'); });
menuItem('Multiple notices', openMat);
menuItem('Pending review', openPendingReview);
menuItem('Preferences', openPrefs);
moreBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
});
moreWrap.appendChild(moreBtn);
moreWrap.appendChild(menu);
tools.appendChild(moreWrap);
openBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
row.classList.add('av-open');
});
var slot = document.getElementById('antivandal-slot');
var editLink = row.querySelector('a.bpneo-title-btn[title="Edit"]') ||
row.querySelector('a.bpneo-title-btn[href*="action=edit"]');
if (slot) { slot.replaceWith(openBtn); }
else if (editLink) { row.insertBefore(openBtn, editLink); }
else { row.insertBefore(openBtn, row.firstChild); }
row.appendChild(tools);
bindUserContextMenu();
return true;
}
function init() {
if (!mw.config.get('wgUserName')) { return; }
injectCss();
api = new mw.Api({ parameters: { formatversion: 2 } });
var rights = mw.config.get('wgUserGroups') || [];
hasRollback = rights.indexOf('rollbacker') !== -1 ||
rights.indexOf('sysop') !== -1 ||
rights.indexOf('steward') !== -1;
if (!buildBar()) {
var tries = 0;
var t = setInterval(function () {
tries += 1;
if (buildBar() || tries > 25) { clearInterval(t); }
}, 200);
}
try { bindContribsRollback(); } catch (e) {}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(init);
});
} else {
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(init);
}
}());