User:Andrybak/Scripts/Not around.js
Appearance
< User:Andrybak | Scripts
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. |
This user script seems to have a documentation page at User:Andrybak/Scripts/Not around. |
// <nowiki>
/*
*
* Copyright (c) 2024 Andrei Rybak
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
(function() {
'use strict';
const config = {
wikipage: '[[w:User:Andrybak/Scripts/Not around|Not around]]',
version: '3.3'
};
const USERSCRIPT_NAME = 'Not around userscript';
const LOG_PREFIX = `[${USERSCRIPT_NAME}]:`;
function error(...toLog) {
console.error(LOG_PREFIX, ...toLog);
}
function warn(...toLog) {
console.warn(LOG_PREFIX, ...toLog);
}
function info(...toLog) {
console.info(LOG_PREFIX, ...toLog);
}
function debug(...toLog) {
console.debug(LOG_PREFIX, ...toLog);
}
function notify(notificationMessage) {
mw.notify(notificationMessage, {
title: USERSCRIPT_NAME
});
}
function errorAndNotify(errorMessage, rejection) {
error(errorMessage, rejection);
notify(errorMessage);
}
const ABSENSE_YEARS_MINIMUM = 6;
const mw = window.mw;
const DEBUG = false;
function constructAd() {
return `using ${config.wikipage} v${config.version}`;
}
function constructEditSummary(username, lastContribYear) {
return `/* top */ add [[Template:Not around]] – user ${username} hasn't edited since ${lastContribYear} (${constructAd()})`;
}
/**
* Asynchronously load specified number of contributions of specified username.
*/
function loadNLastUserContribs(username, n) {
const api = new mw.Api();
return api.get({
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: n
});
}
/**
* Asynchronously load the very last contribution of specified username.
*/
function loadLastUserContrib(username) {
return new Promise((resolve, reject) => {
loadNLastUserContribs(username, 1).then(response => {
debug(response);
const lastContrib = response.query.usercontribs[0];
resolve(lastContrib);
}, rejection => {
reject(rejection);
});
});
}
function isoStringToYear(timestamp) {
const d = new Date(timestamp);
return d.getUTCFullYear();
}
function loadCurrentWikitext(pagename) {
return new Promise((resolve, reject) => {
const api = new mw.Api();
api.get({
action: 'query',
titles: pagename,
prop: 'revisions',
rvprop: 'content',
rvslots: 'main',
/* v2 has nicer field names in responses to this request */
formatversion: 2
}).then(response => {
resolve(response.query.pages[0].revisions[0].slots.main.content);
}, rejection => {
reject(rejection);
});
});
}
function addNotAroundTemplateIfAbsent(username, lastContribYear) {
info(`${username} hasn't edited since ${lastContribYear}.`);
const userTalkPageTitle = 'User_talk:' + username;
loadCurrentWikitext(userTalkPageTitle).then(wikitext => {
/*
* TODO: The checks below are not enough: a mangled template invocation with spaces, like
* TODO: {{ not around}}
* TODO: will not be detected.
*/
if (wikitext.includes('{{Not around') || wikitext.includes('{{not around')) {
info(userTalkPageTitle + ' already has the template. Showing it to the user and aborting.');
location.assign('/wiki/' + userTalkPageTitle);
return;
}
const newWikitext = `{{Not around|date=${lastContribYear}}}\n` + wikitext;
const editSummary = constructEditSummary(username, lastContribYear);
if (DEBUG) {
debug(newWikitext.slice(0, 40));
debug(editSummary);
}
const api = new mw.Api();
api.postWithEditToken({
action: 'edit', /* TODO figure out how to do a preview instead of 'edit' */
title: userTalkPageTitle,
text: newWikitext,
summary: editSummary
}).then(response => {
// Show the edit performed by `postWithEditToken` to the user of the script.
loadLastUserContrib(mw.user.getName()).then(theEdit => {
location.assign('/wiki/Special:Diff/' + theEdit.revid);
}, rejection => {
errorAndNotify(`Cannot load last contribution by ${mw.user.getName()}.`, rejection);
});
}, rejection => {
errorAndNotify(`Cannot edit page [[${userTalkPageTitle}]]`, rejection);
});
});
}
function runPortlet () {
const username = mw.config.get('wgRelevantUserName');
if (!username) {
errorAndNotify('Cannot find a username', null);
return;
}
loadLastUserContrib(username).then(lastContrib => {
if (!lastContrib) {
notify(`User ${username} has zero contributions. Aborting.`);
return;
}
if (lastContrib.user != username) {
errorAndNotify(`Received wrong user. Actual ${lastContrib.user} ≠ expected ${username}. Aborting.`, null);
return;
}
const lastContribYear = isoStringToYear(lastContrib.timestamp);
const currentYear = new Date().getUTCFullYear();
info('Last edit timestamp =', lastContrib.timestamp);
// check how long ago was the last contribution
if (currentYear - lastContribYear >= ABSENSE_YEARS_MINIMUM) {
addNotAroundTemplateIfAbsent(username, lastContribYear);
} else {
notify(`${username} is still an active user. Last edit was in year ${lastContribYear}. Aborting.`);
}
}, rejection => {
errorAndNotify(`Cannot load contributions of ${username}. Aborting.`, rejection);
});
}
function lazyLoadNotAround() {
debug('Loading...');
const namespaceNumber = mw.config.get('wgNamespaceNumber');
/* "Special", "User", and "User talk" */
if (namespaceNumber === -1 || namespaceNumber === 2 || namespaceNumber === 3) {
if (!mw.loader.using) {
warn('Function mw.loader.using is no loaded yet. Retrying...');
setTimeout(lazyLoadNotAround, 300);
return;
}
mw.loader.using(
['mediawiki.util'],
() => {
const link = mw.util.addPortletLink('p-cactions', '#', 'Not around', 'ca-notaround', 'add template {{Not around}}');
if (!link) {
info('Cannot create portlet link (mw.util.addPortletLink). Assuming unsupported skin. Aborting.');
return;
}
link.onclick = event => {
event.preventDefault();
mw.loader.using('mediawiki.api', runPortlet);
};
},
(e) => {
error('Cannot add portlet link', e);
}
);
} else {
warn('Triggered on a bad namespace =', namespaceNumber);
}
}
if (document.readyState !== 'loading') {
lazyLoadNotAround();
} else {
warn('Cannot load yet. Setting up a listener...');
document.addEventListener('DOMContentLoaded', lazyLoadNotAround);
}
})();
// </nowiki>