User:Daniel Quinlan/Scripts/RangeHelper.js: Difference between revisions

Content deleted Content added
fetch user talk pages without using usercontribs API
improve user talk page fetching and error handling
Line 33:
version: this.version
});
}
 
enumerate() {
if (this.version != 4) {
throw new Error('can only enumerate IPv4 addresses');
});
const count = 1n << BigInt(32 - this.mask);
let current = this.masked(this.mask).ip;
return Array.from({ length: Number(count) }, () =>
IPAddress.#bigIntToIPv4(current++)
);
}
 
Line 241 ⟶ 252:
const heading = document.querySelector('#firstHeading');
if (heading) {
heading.innerHTMLtextContent = `Range blocks for ${ip}`;
}
const contentContainer = document.querySelector('#mw-content-text');
Line 285 ⟶ 296:
async function displayRangeTalk(ip) {
async function getUserTalkPages(ip) {
const url = `/wiki/Special:Contributions/${ip}?limit=250`;
const userTalk = new Set();
const abortController = new AbortController();
Line 292 ⟶ 303:
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const talkLinks = doc.querySelectorAll('.mw-contributions-list a.mw-usertoollinks-talk:not(.new)');
for (const link of talkLinks) {
const title = link.title;
Line 300 ⟶ 311:
console.error('Error fetching usertalk pages:', error);
}
return Array.from(userTalk);
}
function timeAgo(timestamp) {
Line 356 ⟶ 367:
blockUser.href = `/wiki/Special:Block/${ip}`;
}
constlet userTalk = await getUserTalkPages(ip);
let userTalkMethod;
if (!userTalk.size) {
if (ip.version === 4 && ip.mask >= 24) {
const noContributionsMessage = document.createElement('p');
userTalk = ip.enumerate().map(ipString => `User talk:${ipString}`);
noContributionsMessage.innerHTML = '<span style="color:red;">No user talk pages found for recent contributions from this IP range.</span>';
userTalkMethod = "enumerate";
contentContainer.appendChild(noContributionsMessage);
} else {
return;
userTalk = await getUserTalkPages(ip);
userTalkMethod = "contributions";
if (!userTalk.sizelength) {
const noContributionsMessageresultMessage = document.createElement('p');
resultMessage.style.color = 'var(--color-notice, gray)';
noContributionsMessage resultMessage.innerHTMLtextContent = '<span style="color:red;">No user talk pages found for recent contributions from this IP range.</span>';
contentContainer.appendChild(noTalkPagesMessageresultMessage);
return;
}
}
const infoResponseinfoResponses = await apiPromise.getall({
batch(userTalk, 50).map(titles => api.get({
action: 'query',
titles: Array.from(userTalk).slice(0, 50).join('|'),
prop titles: titles.join('info|'),
format prop: 'jsoninfo|revisions',
format: 'json',
formatversion: 2
});
}))
const pages = infoResponse.query.pages
);
.filter(page => !page.missing)
const pages = infoResponses
.flatMap(response => response.query.pages)
.filter(page => !page.missing && page.revisions && page.revisions.length > 0)
.map(page => ({
title: page.title,
touchedtimestamp: page.touchedrevisions[0].timestamp,
redirect: !!page.redirect}))
}))
.sort((a, b) => b.touchedtimestamp.localeCompare(a.touchedtimestamp));
if (!pages.length) {
const noTalkPagesMessageresultMessage = document.createElement('p');
if (userTalkMethod === "enumerate") {
noTalkPagesMessage.innerHTML = '<span style="color:red;">An error occurred while retrieving timestamps for user talk pages in this IP range.</span>';
resultMessage.style.color = 'var(--color-notice, gray)';
contentContainer.appendChild(noTalkPagesMessage);
resultMessage.textContent = 'No user talk pages found.';
} else {
resultMessage.style.color = 'var(--color-error, red)';
noTalkPagesMessage resultMessage.innerHTMLtextContent = '<span style="color:red;">An error occurred while retrieving timestamps for user talk pages in this IP range.</span>';
}
contentContainer.appendChild(noContributionsMessageresultMessage);
return;
}
Line 386 ⟶ 416:
for (const page of pages) {
const ip = page.title.replace(/^User talk:/, '');
const relativeTime = `${timeAgo(page.touchedtimestamp)} ago`;
const headerText = `== ${relativeTime}: [[Special:Contributions/${ip}|${ip}]] ([[${page.title}|talk]]) ==`;
const inclusionText = `{{${page.title}}}`;
Line 428 ⟶ 458:
const heading = document.querySelector('#firstHeading');
if (heading) {
heading.innerHTMLtextContent = 'Range calculator';
}
const contentContainer = document.querySelector('#mw-content-text');
Line 640 ⟶ 670:
}
return wikitext;
}
 
function batch(items, maxSize) {
const minBins = Math.ceil(items.length / maxSize);
const bins = Array.from({length: minBins}, () => []);
items.forEach((item, i) => {
bins[i % minBins].push(item);
});
return bins;
}