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

Content deleted Content added
move range blocks to separate page, add range links to contributions user tools
query block log asynchronously, improve block entry formatting
Line 1:
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter']).then(function() {
// state variables
const wikitextCache = new Map();
let api = null;
let formatTimeAndDate = null;
 
// activate on relevant special pages
if (mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
const userToolsBDI = document.querySelector('.mw-contributions-user-tools bdi');
Line 86 ⟶ 92:
masks = Array.from({ length: 16 }, (_, i) => 31 - i);
}
const api = new mw.Api();
const statusMessage = document.createElement('p');
statusMessage.textContent = 'Querying logs for relevant IP range blocks...';
Line 92 ⟶ 98:
const resultsList = document.createElement('ul');
contentContainer.appendChild(resultsList);
formatTimeAndDate = mw.loader.require('mediawiki.DateFormatter').formatTimeAndDate;
mw.hook('wikipage.content').fire($(contentContainer));
letconst foundBlocksblocks = false[];
const allBlocksblockPromises = masks.map(mask => {}
for (const mask of masks) {
const range = ip.version === 6 ? maskedIPv6(ip.ip, mask) : maskedIPv4(ip.ip, mask);
const blocks = awaitreturn getBlockLogs(api, range);.then(async (blockLogs) => {
for (const block of blockLogs) {
if (Object.keys(blocks).length > 0) {
const formattedBlock = await formatBlockEntry(block);
foundBlocks = true;
blocks.push({ logid: block.logid, formattedBlock });
Object.assign(allBlocks, blocks);
resultsList.innerHTML = '';
const sortedBlocks = Object.values(allBlocks).sort((a, b) => b.timestamp > a.timestamp ? 1 : -1);
for (const block of sortedBlocks) {
const li = document.createElement('li');
li.innerHTML = `<a href="${block.url}">${block.timestamp}</a> <a href="/wiki/User:${encodeURIComponent(block.user)}">${block.user}</a> blocked <a href="/wiki/Special:Contributions/${encodeURIComponent(block.range)}">${block.range}</a> (${block.expiry})`;
resultsList.appendChild(li);
}
}).catch(error => {
}
console.error(`Error fetching block logs for range ${range}:`, error);
mw.hook('wikipage.content').fire($(resultsList));
});
});
statusMessage.textContent = foundBlocks ? 'Range blocks:' : 'No blocks found.';
await Promise.all(blockPromises);
blocks.sort((a, b) => b.logid - a.logid);
blocks.forEach(({ formattedBlock }) => {
const li = document.createElement('li');
li.innerHTML = formattedBlock;
resultsList.appendChild(li);
});
statusMessage.textContent = blocks.length ? 'Range blocks:' : 'No blocks found.';
mw.hook('wikipage.content').fire($(contentContainer));
}
Line 124 ⟶ 131:
format: 'json'
});
return response.query.logevents.map(event => ({
const blocksObj = {};
logid: event.logid,
response.query.logevents.forEach(event => {
blocksObj[timestamp: event.logid] = {timestamp,
timestampuser: event.timestampuser,
useraction: event.useraction,
expirycomment: event.params.durationcomment || 'indefinite',
params: event.params || {},
url: mw.util.getUrl('Special:Log', { logid: event.logid }),
range: range,
}));
});
return blocksObj;
}
 
Line 141 ⟶ 147:
const IPV4REGEX = /^((?:1?\d\d?|2[0-2]\d)\b(?:\.(?:1?\d\d?|2[0-4]\d|25[0-5])){3})(?:\/(1[6-9]|2\d|3[0-2]))?$/;
const IPV6REGEX = /^((?:[\dA-Fa-f]{1,4}:){2,7}(?:[\dA-Fa-f]{1,4}|:)?)(?:\/(19|[2-9]\d|1[01]\d|12[0-8]))?$/;
const match = IPV4REGEX.exec(userName) || IPV6REGEX.exec(userName);
let match;
if (match) {
if ((match = IPV4REGEX.exec(userName) || IPV6REGEX.exec(userName))) {
const version = match[1].includes(':') ? 6 : 4;
const ip = match[1];
const mask = parseInt(match[2] || (version === 6 ? '128' : '32'), 10);
return { version, ip, mask };
}
Line 216 ⟶ 222:
maskedIpInt & 0xff
].join('.') + `/${prefixLength}`;
}
 
// generate HTML for a block log entry
async function formatBlockEntry(block) {
function translateFlags(flags) {
const flagMap = {
'anononly': 'anon. only',
'nocreate': 'account creation blocked',
'nousertalk': 'cannot edit own talk page',
};
return flags.map(flag => flagMap[flag] || flag).join(', ');
}
const formattedTimestamp = formatTimeAndDate(new Date(block.timestamp));
const logLink = `<a href="/w/index.php?title=Special:Log&logid=${block.logid}" title="Special:Log">${formattedTimestamp}</a>`;
const userLink = `<a href="/wiki/User:${block.user}" title="User:${block.user}"><bdi>${block.user}</bdi></a>`;
const userTools = `(<a href="/wiki/User_talk:${block.user}" title="User talk:${block.user}">talk</a> | <span><a href="/wiki/Special:Contributions/${block.user}" title="Special:Contributions/${block.user}">contribs</a>)`;
const action = block.action === "reblock" ? "changed block settings for" : `${block.action}ed`;
const ipLink = `<a href="/wiki/Special:Contributions/${block.range}" title=""><bdi>${block.range}</bdi></a>`;
let expiryTime = '';
if (block.action !== "unblock") {
let expiryTimeStr = block.params?.duration;
if (!expiryTimeStr || ['infinite', 'indefinite', 'infinity'].includes(expiryTimeStr)) {
expiryTimeStr = 'indefinite';
} else if (!isNaN(Date.parse(expiryTimeStr))) {
const expiryDate = new Date(expiryTimeStr);
expiryTimeStr = formatTimeAndDate(expiryDate);
}
expiryTime = ` with an expiration time of <span class="blockExpiry" title="${block.params?.duration || 'indefinite'}">${expiryTimeStr}</span>`;
}
const translatedFlags = block.params?.flags && block.params.flags.length ? ` (${translateFlags(block.params.flags)})` : '';
const comment = block.comment ? ` <span class="comment" style="font-style: italic;">(${await wikitextToHTML(block.comment)})</span>` : '';
const actionLinks = `(<a href="/wiki/Special:Unblock/${block.range}" title="Special:Unblock/${block.range}">unblock</a> | <a href="/wiki/Special:Block/${block.range}" title="Special:Block/${block.range}">change block</a>)`;
return `${logLink} ${userLink} ${userTools} ${action} ${ipLink}${expiryTime}${translatedFlags}${comment} ${actionLinks}`;
}
 
// convert wikitext to HTML
async function wikitextToHTML(wikitext) {
if (wikitextCache.has(wikitext)) {
return wikitextCache.get(wikitext);
}
try {
wikitext = wikitext.replace(/{{/g, '\\{\\{').replace(/}}/g, '\\}\\}');
const response = await api.post({
action: 'parse',
disableeditsection: true,
prop: 'text',
format: 'json',
text: wikitext
});
if (response.parse && response.parse.text) {
const pattern = new RegExp('^.*?<p>(.*)<\/p>.*$', 's');
const html = response.parse.text['*']
.replace(pattern, '$1')
.replace(/\\{\\{/g, '{{')
.replace(/\\}\\}/g, '}}')
.trim();
wikitextCache.set(wikitext, html);
return html;
}
} catch (error) {
console.error('Error converting wikitext to HTML:', error);
}
return wikitext;
}
});