// <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>