Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/modules/twinkleprod.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ Twinkle.prod.callbacks = {
return def;
},

notifyAuthor: function twinkleprodNotifyAuthor() {
notifyAuthor: async function twinkleprodNotifyAuthor() {
const def = $.Deferred();

if (!params.blp && !params.usertalk) {
Expand All @@ -359,6 +359,11 @@ Twinkle.prod.callbacks = {
Morebits.Status.info('Notifying creator', 'You (' + params.initialContrib + ') created this page; skipping user notification');
return def.resolve();
}

if (await Twinkle.hasUserOptedOutOfNotice(params.initialContrib, ['prod'])) {
return def.resolve();
}

// [[Template:Proposed deletion notify]] supports File namespace
let notifyTemplate;
if (params.blp) {
Expand Down
5 changes: 3 additions & 2 deletions src/modules/twinklespeedy.js
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ Twinkle.speedy.callbacks = {
api.post();
},

noteToCreator: function(pageobj) {
noteToCreator: async function(pageobj) {
const params = pageobj.getCallbackParameters();
let initialContrib = pageobj.getCreator();

Expand All @@ -1354,7 +1354,8 @@ Twinkle.speedy.callbacks = {
initialContrib = null;
}

if (initialContrib) {
// TODO: allow opting out of specific CSD criteria
if (initialContrib && !await Twinkle.hasUserOptedOutOfNotice(initialContrib, ['csd'])) {
const usertalkpage = new Morebits.wiki.Page('User talk:' + initialContrib, 'Notifying initial contributor (' + initialContrib + ')');
let notifytext, i, editsummary;

Expand Down
6 changes: 5 additions & 1 deletion src/modules/twinklexfd.js
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ Twinkle.xfd.callbacks = {
* @param {string} [actionName] Alternative description of the action
* being undertaken. Required if not notifying a user talk page.
*/
notifyUser: function(params, notifyTarget, noLog, actionName) {
notifyUser: async function(params, notifyTarget, noLog, actionName) {
// Ensure items with User talk or no namespace prefix both end
// up at user talkspace as expected, but retain the
// prefix-less username for addToLog
Expand All @@ -914,6 +914,10 @@ Twinkle.xfd.callbacks = {
Twinkle.xfd.callbacks.addToLog(params, null);
return;
}
if (await Twinkle.hasUserOptedOutOfNotice(usernameOrTarget, ['xfd', params.venue])) {
Twinkle.xfd.callbacks.addToLog(params, null);
return;
}
// Default is notifying the initial contributor, but MfD also
// notifies userspace page owner
actionName = actionName || 'Notifying initial contributor (' + usernameOrTarget + ')';
Expand Down
48 changes: 48 additions & 0 deletions src/twinkle.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,54 @@ Twinkle.generateBatchPageLinks = function (checkbox) {
*/
Twinkle.removeMoveToCommonsTagsFromWikicode = ( wikicode ) => wikicode.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)(?!( in))[^}]*\}\}/gi, '');

/**
* Check if the user has opted out of talk pages notices of a given type.
*
* @param {string} username
* @param {string[]} types Multiple values are allowed so that multiple levels can be
* passed in (eg. ['xfd', 'afd'] to detect if user has opted out of just AFD notices,
* or all XFD notices.)
* @return {Promise<boolean>} Resolves to true if user has opted out of notification.
*/
Twinkle.hasUserOptedOutOfNotice = async function (username, types) {
const typesSet = new Set(types);
const status = new Morebits.Status('Checking for notification opt-out');
const api = new Morebits.wiki.Api('checking...', {
action: 'query',
format: 'json',
prop: 'extlinks',
titles: 'User talk:' + username,
elquery: 'optout.twinkle',
// There should be only one matching external link. If there are multiple of them,
// consider up to the first 10.
ellimit: 10
}, null, status);
return api.post().then((apiobj) => {
const page = apiobj.getResponse().query.pages[0];
if (page.missing) {
return false;
}
const extlinks = page.extlinks || [];
return extlinks.some((link) => {
try {
const url = new URL(link.url);
const typesFromLink = url.searchParams.get('types');
return typesFromLink && typesFromLink.split(',').some((e) => typesSet.has(e.trim().toLowerCase()));
} catch (e) {
// Invalid URL
return false;
}
});
}).then((isOptOut) => {
if (isOptOut) {
status.warn(`${username} has opted out of notification`);
} else {
status.info('Not opted out');
}
return isOptOut;
});
};

}());

// </nowiki>
Loading