User:Suffusion of Yellow/effp-helper-core.js

/* * WP:EF/FP helper (development version) * * Do not import this directly. Use User:Suffusion of Yellow/effp-helper.js instead. */

// jshint esnext: false, esversion: 8 // (function {	'use strict';

let userMessages = { "en": { 'effp-makeedit': "Make edit", 'effp-makeedit-more': "Perform edit on behalf of user", 'effp-pagedeleted': "Page deleted", 'effp-pagedeleted-more': "Page should be undeleted first, for attribution", 'effp-pagenevercreated': "Page does not exist", 'effp-pagemoved': "Page moved", 'effp-pagemoved-more': "Page moved from \"$1\" to \"$2\"", 'effp-sigsfound': "Contains signatures", 'effp-editmade': "Edit already made by $1", 'effp-editmadecurrent': "Edit already made by $1 and is current", 'effp-nolateredits': "No later edits", 'effp-somelateredits': "$1 later NaN editss", 'effp-manylateredits': "At least $1 later edits", 'effp-editsby': "Edits by $1", 'effp-diff': "diff", 'effp-noconflict': "No edit conflict", 'effp-editconflict': "Edit conflict", 'effp-whatsthis': "What's this?" }	};

let siteMessages = { "en": { 'effp-spam' : "(effp-helper)", 'effp-sig-user': "$1 (talk)", 'effp-sig-ip': "$1 (talk)", 'effp-sig-timestamp' : "$1, $2 $3 $4 (UTC)", 'effp-sum': "Edit made on behalf of $1 because it was disallowed by an edit filter $3", 'effp-sum-withorigsum': "Edit made on behalf of $1 because it was disallowed by an edit filter. Original summary was \"$3\" $4", }	};

function getLogId { switch (mw.config.get('wgCanonicalSpecialPageName')) { case "AbuseLog": let match = mw.config.get('wgTitle').match(/\/([0-9]+)$/); return match ? match[1] : null; case "AbuseFilter": // Don't run when examining a recent change let examine = mw.config.get('abuseFilterExamine'); return examine.type == "log" ? examine.id : null; default: return null; }

}

async function SHA1(str) { let hb = byte => byte.toString(16).padStart(2, "0"); let buf = await crypto.subtle.digest('SHA-1', new TextEncoder.encode(str));

return [...(new Uint8Array(buf))].map(hb).join(""); }

function formatTS(unixTime) { return (new Date(unixTime * 1000)).toISOString.slice(0, 19) + "Z"; }

function formatTSForSig(unixTime) { let d = new Date(unixTime * 1000);

return mw.msg('effp-sig-timestamp',					 d.toISOString.slice(11, 16),					  d.getUTCDate,					  mw.config.get('wgMonthNames')[d.getUTCMonth + 1],					  d.getUTCFullYear); }

function formatSig(user) { return mw.util.isIPAddress(user) ? mw.msg('effp-sig-ip', user) : mw.msg('effp-sig-user', user); }

function buildSummary(user, id, origsum) { if (!origsum.length) return mw.msg('effp-sum', formatSig(user), id, mw.msg('effp-spam'));

let space = 500 - mw.msg('effp-sum-withorigsum', formatSig(user), id, "").length;

if (origsum.length > space) origsum = origsum.slice(0, space - 3) + "...";

return mw.msg('effp-sum-withorigsum', formatSig(user), id, origsum, mw.msg('effp-spam')); }

/*	 * Attempt to sign comments with the user's name, not our own! *	 * Can't use action=parse directly, because that will substitue OUR sig. * Can't just replace the ~s because that won't catch nowiki, etc.	 * * Instead use action=parse to find all the places where ~ ISN'T	 * substed, and skip those when replacing the ~s. * Is there a less hacky way to do this? */	async function substTildes(text, user, unixTime) { let munged = text.replace(/~{3,5}/g, (m, o) => ("<<" + o + ">>" + m)); let pst = (await (new mw.Api).post({ action : "parse", text : munged, onlypst : 1 })).parse.text["*"];

let isFake = o => pst.includes("<<" + o + ">>");

let sig = formatSig(user); let ts = formatTSForSig(unixTime);

text = text.replace(//g, (m, o) => isFake(o) ? m : ts); text = text.replace(/~/g, (m, o) => isFake(o) ? m : sig + " " + ts); text = text.replace(//g, (m, o) => isFake(o) ? m : sig);

return text; }

/*	 * Post an edit to web interface. Can't use the API, because the user * might need to make fixes, or resolve an edit conflict. */	function postEdit(title, wikitext, summary,					 revid, editTS, startTS,					  newTab) { let input = (name, value) => $(' ', {type: "hidden", name, value});

let $form = $(' ', {			style: 'display: none !important;',			action: mw.config.get('wgScript') + "?title="				+ mw.util.wikiUrlencode(title) + "&action=submit",			method: "POST"		});

if (newTab) $form.attr("target", "_blank");

$(' ').val(wikitext).appendTo($form);

input("wpSummary", summary).appendTo($form);

/* Don't show preview without opt-in, might contain NSFW content */ if (window.effpPreviewOnFirstClick) input("wpPreview", "1").appendTo($form); else input("wpDiff", "1").appendTo($form);

input("wpEdittime", editTS.replace(/[^0-9]/g, "")).appendTo($form); input("wpStarttime", startTS.replace(/[^0-9]/g, "")).appendTo($form);

if (revid) { input("editRevId", revid).appendTo($form); input("baseRevId", revid).appendTo($form); input("parentRevId", revid).appendTo($form); }

if (mw.user.options.get('watchdefault')) input("wpWatchthis", "1").appendTo($form);

input("wpUltimateParam", "1").appendTo($form);

//No edit token needed for preview

$form.appendTo('body').submit; }

/*	 * Check if the edit will conflict. This depends on an undocumented internal * feature, so an error here shouldn't cause any other problems. */

function checkEditConflict(title, text, revision) { (new mw.Api).postWithToken('csrf', {			action: "stashedit",			text: text,			title: title,			baserevid: revision,			contentmodel: "wikitext",			contentformat: "text/x-wiki"		}).catch.then(r => {			if (r && r.stashedit) {				if (r.stashedit.status == "stashed")					$('.effp-ecinfo').text(" (" + mw.msg('effp-noconflict') + ")");				else if (r.stashedit.status == "editconflict")					$('.effp-ecinfo').text(" (" + mw.msg('effp-editconflict') + ")");			}		}, => null); }

async function setupLink(vars, newer, older, text) { let revisions = null, page, curTitle, curPageId; let noLink = false, info = "", moreInfo = ""; let	diff = null; let baseRev, timestamp;

try { curPageId = Object.keys(newer.query.pages)[0]; page = newer.query.pages[curPageId]; curTitle = page.title;

let oldRev = older.query.pages[curPageId].revisions || []; let newRevs = page.revisions || [];

if (oldRev[0]) { baseRev = oldRev[0].revid; timestamp = oldRev[0].timestamp; } else { baseRev = null; timestamp = formatTS(vars.timestamp); }

if (oldRev[0] && newRevs[0] && oldRev[0].revid === newRevs[0].revid) revisions = newRevs; else revisions = oldRev.concat(newRevs); } catch(e) { console.error("effp-helper: Bad response from API"); return; }

if (page.missing !== undefined) { curPageId = 0; curTitle = vars.page_prefixedtitle; }

// Let's not. if (page.contentmodel != "wikitext" || page.ns == 8) noLink = true;

if (curPageId === 0) { if (vars.page_id == 0) { info = mw.msg('effp-pagenevercreated'); } else { info = mw.msg('effp-pagedeleted'); moreInfo = mw.msg('effp-pagedeleted-more'); noLink = true; }		} else { let sha1 = await SHA1(text); let cnt = revisions.length; let haveAll = (page.lastrevid == revisions[cnt - 1].revid); let skip = (revisions[0].timestamp < formatTS(vars.timestamp)); let trueCnt = cnt - skip; let editMadeBy = null, users = new Set;

for(let i = 0; i < cnt; i++) { if (editMadeBy === null && revisions[i].sha1 == sha1) { editMadeBy = revisions[i].user; diff = revisions[i].revid; }

if (i >= skip) users.add(revisions[i].user); }

if (editMadeBy !== null) { if (haveAll && revisions[cnt - 1].sha1 == sha1) info = mw.msg('effp-editmadecurrent', editMadeBy); else info = mw.msg('effp-editmade', editMadeBy); } else if (trueCnt === 0) { info = mw.msg('effp-nolateredits'); } else { info = haveAll ? mw.msg('effp-somelateredits', trueCnt) : mw.msg('effp-manylateredits', trueCnt);

moreInfo = mw.msg('effp-editsby', [...users].join(", "));

checkEditConflict(curTitle, text, baseRev); }		}

if (!vars.new_pst && vars.new_wikitext != text) info = mw.msg('effp-sigsfound') + ") (" + info;

if (vars.page_prefixedtitle !== curTitle) { info = mw.msg('effp-pagemoved') + ") (" + info; moreInfo = mw.msg('effp-pagemoved-more', vars.page_prefixedtitle, curTitle) + ". " + moreInfo; }

let $info = $(' ', {			class: 'effp-info',			text: " (" + info + ")",			title: moreInfo		});

if (diff) { $info.append(" (", $('', {							 href: mw.config.get('wgScript') + "?diff=" + diff,							 text: mw.msg('effp-diff')						 }), ")");

noLink = true; }

$('#firstHeading').append($info);

// Temporary, people won't know which script this is coming from $('#firstHeading').append(" ",					 $("", { style: "font-size: 50%;", href: "https://en.wikipedia.org/wiki/User:Suffusion of Yellow/effp-helper", text: mw.msg("effp-whatsthis") })					);

if (noLink) return;

$info.append(' ');

let link = mw.util.addPortletLink(			'p-cactions',			'#',			mw.msg('effp-makeedit'),			'ca-makeedit' ,			mw.msg('effp-makeedit-more')		);

let summary = buildSummary(vars.user_name, getLogId, vars.summary);

$(link).on("click auxclick", (e) => {			if (e.button <= 1) {				e.preventDefault;

postEdit(curTitle,						 text,						 summary,						 baseRev,						 timestamp,						 formatTS(vars.timestamp),						 e.button == 1 || e.CtrlKey); }		});

}

function startup { mw.messages.set(Object.assign( {},			userMessages.en, siteMessages.en, userMessages[mw.config.get('wgUserLanguage')] || {}, siteMessages[mw.config.get('wgContentLanguage')] || {} ));

mw.util.addCSS(".effp-info { font-size: 75%; font-style: italic; }");

let vars = mw.config.get('wgAbuseFilterVariables');

// Private log entry and no permissions, or action != "edit" if (!getLogId || !vars ||			vars.page_id === undefined ||			vars.page_prefixedtitle === undefined ||			vars.new_wikitext === undefined ||			vars.timestamp === undefined) return;

let newer = { action: 'query', prop: 'info|revisions', rvprop: "ids|timestamp|user|sha1", rvstart: formatTS(vars.timestamp), rvdir: "newer", rvlimit: 500 };

let older = { action: 'query', prop: 'revisions', rvprop: "ids|timestamp|user|sha1", rvstart: formatTS(vars.timestamp), rvdir: "older", rvlimit: 1 };

// Always use the page id if available, in case page moved if (vars.page_id !== 0) older.pageids = newer.pageids = vars.page_id; else older.titles = newer.titles = vars.page_prefixedtitle;

let textP;

/*		 * Use new_pst if available, otherwise if the text looks like * it contains a signature, attempt to subst it properly. */		if (vars.new_pst) textP = vars.new_pst; else if (!vars.new_wikitext.includes("")) textP = vars.new_wikitext; else textP = substTildes(vars.new_wikitext, vars.user_name, vars.timestamp);

let api = new mw.Api;

Promise.all([			api.get(newer),			api.get(older),			textP,			$.ready		]).then(r => setupLink(vars, r[0], r[1], r[2])); }

if (mw.config.get('wgAbuseFilterVariables')) mw.loader.using(['mediawiki.interface.helpers.styles',						 'mediawiki.api',						 'mediawiki.util']).then(startup); }); //