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

Content deleted Content added
create script
 
move range blocks to separate page, add range links to contributions user tools
Line 1:
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function() {
if (mw.config.get('wgPageNamewgCanonicalSpecialPageName') !=== 'Special:Log/blockContributions') {
const userToolsBDI = document.querySelector('.mw-contributions-user-tools bdi');
return;
const userName = userToolsBDI ? userToolsBDI.textContent.trim() : '';
const ip = extractIP(userName);
if (ip) {
addContributionsLinks(ip);
}
} else if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage') {
const pageName = mw.config.get('wgPageName') || '';
const match = pageName.match(/^Special:BlankPage\/RangeBlocks\/(.+)$/);
if (match) {
const ip = extractIP(match[1]);
if (ip) {
addRangeBlocks(ip);
}
}
} else if (mw.config.get('wgPageName') === 'Special:Log/block') {
const pageParam = mw.util.getParamValue('page');
if (pageParam) {
const match = pageParam.match(/^User:(.+)$/);
if (match) {
const ip = extractIP(match[1]);
if (ip) {
mw.util.addPortletLink('p-tb', `/wiki/Special:BlankPage/RangeBlocks/${ip.ip}`, "Find range blocks");
}
}
}
}
return;
 
// extractadds links to user tools
function addContributionsLinks(ip) {
const pageParam = mw.util.getParamValue('page');
const userToolsContainer = document.querySelector('.mw-contributions-user-tools .mw-changeslist-links');
if (!pageParam || !pageParam.startsWith('User:')) return;
if (!userToolsContainer) return;
const IPV4REGEX = /^(?:1?\d\d?|2[0-2]\d)\b(?:\.(?:1?\d\d?|2[0-4]\d|25[0-5])){3}$/;
const spans = userToolsContainer.querySelectorAll('span');
const IPV6REGEX = /^[\dA-Fa-f]{1,4}(?:\:[\dA-Fa-f]{1,4}){7}$/;
let insertBefore = null;
const user = pageParam.replace('User:', '');
for (const span of spans) {
 
if (span.textContent.toLowerCase().includes('global')) {
// test user
insertBefore = span;
const isIPv6 = IPV6REGEX.test(user);
break;
if (!IPV4REGEX.test(user) && !isIPv6) {
return; }
}
if (!insertBefore) return;
let floor = 16, ceiling = 24, steps = 8;
if (ip.version === 6) {
floor = 32;
ceiling = 64;
if (ip.mask >= 64)
steps = 16;
}
let links = [];
const rangeBlockLink = document.createElement('a');
rangeBlockLink.href = `/wiki/Special:BlankPage/RangeBlocks/${ip.ip}`;
rangeBlockLink.textContent = 'range block log';
rangeBlockLink.className = 'mw-link-range-blocks';
links.push(rangeBlockLink);
for (let mask = floor; mask <= ceiling && mask < ip.mask; mask += steps) {
const contribsLink = document.createElement('a');
contribsLink.href = `/wiki/Special:Contributions/${ip.ip}/${mask}`;
contribsLink.textContent = `/${mask}`;
contribsLink.className = 'mw-contributions-link-range-suggestion';
links.push(contribsLink)
}
links.forEach(link => {
const span = document.createElement('span');
span.appendChild(link);
userToolsContainer.insertBefore(span, insertBefore);
});
}
 
// add button
const link = mw.util.addPortletLink('p-tb', '#', "Find range blocks", 'ca-find-range-blocks');
link.addEventListener('click', function(event) {
event.preventDefault();
findRangeBlocks();
});
 
// find range blocks
async function findRangeBlocksaddRangeBlocks(ip) {
document.title = `Range blocks for ${ip.ip}`;
let netmasks;
const heading = document.querySelector('#firstHeading');
if (isIPv6) {
if (heading) {
netmasks = Array.from({ length: 46 }, (_, i) => 64 - i);
heading.innerHTML = `Range blocks for <a href="/wiki/Special:Contributions/${ip.ip}">${ip.ip}</a>`;
}
const contentContainer = document.querySelector('#mw-content-text');
if (!contentContainer) return;
contentContainer.innerHTML = '';
let masks;
if (ip.version === 6) {
masks = Array.from({ length: 46 }, (_, i) => 64 - i);
} else {
netmasksmasks = Array.from({ length: 16 }, (_, i) => 31 - i);
}
const api = new mw.Api();
const contentContainer = document.querySelector('.mw-content-container');
if (!contentContainer) return;
const outputDiv = document.createElement('div');
outputDiv.style.marginTop = '1em';
const statusMessage = document.createElement('p');
statusMessage.textContent = 'Querying logs for relevant IP range blocks...';
outputDivcontentContainer.appendChild(statusMessage);
const resultsList = document.createElement('ul');
outputDivcontentContainer.appendChild(resultsList);
mw.hook('wikipage.content').fire($(contentContainer));
contentContainer.appendChild(outputDiv);
mw.hook('wikipage.content').fire($(outputDiv));
let foundBlocks = false;
for (const mask ofallBlocks netmasks)= {}
for (const mask of masks) {
const range = isIPv6 ? maskedIPv6(user, mask) : maskedIPv4(user, mask);
const range = ip.version === 6 ? maskedIPv6(ip.ip, mask) : maskedIPv4(ip.ip, mask);
const blocks = await getBlockLogs(api, range);
if (Object.keys(blocks).length > 0) {
foundBlocks = true;
Object.assign(allBlocks, blocks);
blocks.forEach(block => {
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);
}
mw.hook('wikipage.content').fire($(resultsList));
});
}
mw.hook('wikipage.content').fire($(resultsList));
}
statusMessage.textContent = foundBlocks ? 'Range blocks:' : 'No blocks found.';
mw.hook('wikipage.content').fire($(contentContainer));
? 'Blocks affecting this IP:'
: 'No blocks found.';
mw.hook('wikipage.content').fire($(statusMessage));
}
 
Line 72 ⟶ 124:
format: 'json'
});
const blocksObj = {};
return response.query.logevents.map(event => ({
response.query.logevents.forEach(event => {
timestamp: event.timestamp,
user: blocksObj[event.user,logid] = {
expiry timestamp: event.params.duration || 'indefinite'timestamp,
user: event.user,
url: mw.util.getUrl('Special:Log', { logid: event.logid })
expiry: event.params.duration || 'indefinite',
}));
url: mw.util.getUrl('Special:Log', { logid: event.logid }),
range: range,
};
});
return blocksObj;
}
 
// extract IP address
function extractIP(userName) {
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]))?$/;
let 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 };
}
return null;
}