User:Polygnotus/Scripts/EditsPerNamespace.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/EditsPerNamespace.
// <nowiki>
$(document).ready(function() {
// Add link to tools menu
mw.util.addPortletLink(
'p-tb',
'/wiki/Special:BlankPage/UserEditsAnalysis',
'User Edits Analysis',
't-user-edits-analysis',
'Analyze user edits by namespace'
);
// Customize the page when viewing it
if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
mw.config.get('wgTitle') === 'BlankPage/UserEditsAnalysis') {
document.title = 'User Edits Analysis';
$('#firstHeading').text('User Edits Analysis');
$('#mw-content-text').html(`
<div style="max-width: 800px;">
<p>Enter a username to analyze their edit distribution across namespaces.</p>
<input type="text" id="username-input" placeholder="Username" style="width: 300px; padding: 5px;">
<button id="analyze-btn" style="padding: 5px 15px; margin-left: 10px;">Analyze</button>
<div id="status" style="margin-top: 10px; color: #666;"></div>
<div id="results" style="margin-top: 20px;"></div>
<canvas id="chart" width="400" height="400" style="display:none; margin-top: 20px;"></canvas>
</div>
`);
$('#analyze-btn').on('click', function() {
const username = $('#username-input').val().trim();
if (!username) {
$('#status').text('Please enter a username.');
return;
}
analyzeUser(username);
});
$('#username-input').on('keypress', function(e) {
if (e.which === 13) {
$('#analyze-btn').click();
}
});
}
});
async function analyzeUser(username) {
$('#status').text('Fetching edits...');
$('#results').empty();
$('#chart').hide();
const namespaces = {};
let continueToken = null;
let totalEdits = 0;
const delay = 1000; // 1 second between requests
try {
do {
const params = {
action: 'query',
list: 'usercontribs',
ucuser: username,
uclimit: 500,
ucprop: 'title',
format: 'json',
maxlag: 5
};
if (continueToken) {
params.uccontinue = continueToken;
}
const response = await $.ajax({
url: mw.config.get('wgScriptPath') + '/api.php',
data: params,
dataType: 'json',
headers: {
'Api-User-Agent': 'UserEditsAnalysis/1.0 (Wikipedia User Script)'
}
});
if (response.error) {
throw new Error(response.error.info);
}
const contribs = response.query.usercontribs;
for (const contrib of contribs) {
const title = contrib.title;
const colonIndex = title.indexOf(':');
let namespace;
if (colonIndex === -1) {
namespace = 'Main';
} else {
const potentialNs = title.substring(0, colonIndex);
// Check if it's a valid namespace by checking against known namespaces
const nsId = mw.config.get('wgNamespaceIds')[potentialNs.toLowerCase().replace(/ /g, '_')];
if (nsId !== undefined) {
namespace = potentialNs;
} else {
namespace = 'Main';
}
}
namespaces[namespace] = (namespaces[namespace] || 0) + 1;
totalEdits++;
}
$('#status').text(`Fetched ${totalEdits} edits...`);
continueToken = response.continue ? response.continue.uccontinue : null;
if (continueToken) {
await new Promise(resolve => setTimeout(resolve, delay));
}
} while (continueToken);
if (totalEdits === 0) {
$('#status').text('No edits found for this user.');
return;
}
displayResults(namespaces, totalEdits, username);
} catch (error) {
$('#status').text('Error: ' + error.message);
}
}
function displayResults(namespaces, totalEdits, username) {
$('#status').text(`Analysis complete. Total edits: ${totalEdits}`);
// Sort by edit count
const sorted = Object.entries(namespaces).sort((a, b) => b[1] - a[1]);
let html = '<table style="border-collapse: collapse; width: 100%;">';
html += '<tr style="background-color: #eaecf0;"><th style="border: 1px solid #a2a9b1; padding: 8px; text-align: left;">Namespace</th>';
html += '<th style="border: 1px solid #a2a9b1; padding: 8px; text-align: right;">Edits</th>';
html += '<th style="border: 1px solid #a2a9b1; padding: 8px; text-align: right;">Percentage</th></tr>';
for (const [ns, count] of sorted) {
const percentage = ((count / totalEdits) * 100).toFixed(2);
// Get namespace ID
let nsId = 0; // Main namespace
if (ns !== 'Main') {
const nsIdLookup = mw.config.get('wgNamespaceIds')[ns.toLowerCase().replace(/ /g, '_')];
if (nsIdLookup !== undefined) {
nsId = nsIdLookup;
}
}
const contribUrl = mw.util.getUrl('Special:Contributions', {
target: username,
namespace: nsId
});
html += `<tr><td style="border: 1px solid #a2a9b1; padding: 8px;"><a href="${contribUrl}">${ns}</a></td>`;
html += `<td style="border: 1px solid #a2a9b1; padding: 8px; text-align: right;">${count}</td>`;
html += `<td style="border: 1px solid #a2a9b1; padding: 8px; text-align: right;">${percentage}%</td></tr>`;
}
html += '</table>';
$('#results').html(html);
drawPieChart(sorted, totalEdits);
}
function drawPieChart(data, total) {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
canvas.style.display = 'block';
const centerX = 200;
const centerY = 200;
const radius = 150;
const colors = [
'#3366CC', '#DC3912', '#FF9900', '#109618', '#990099',
'#3B3EAC', '#0099C6', '#DD4477', '#66AA00', '#B82E2E',
'#316395', '#994499', '#22AA99', '#AAAA11', '#6633CC'
];
let currentAngle = -Math.PI / 2;
for (let i = 0; i < data.length; i++) {
const [ns, count] = data[i];
const sliceAngle = (count / total) * 2 * Math.PI;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle);
ctx.closePath();
ctx.fillStyle = colors[i % colors.length];
ctx.fill();
// Add label
const labelAngle = currentAngle + sliceAngle / 2;
const labelX = centerX + Math.cos(labelAngle) * (radius * 0.7);
const labelY = centerY + Math.sin(labelAngle) * (radius * 0.7);
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const percentage = ((count / total) * 100).toFixed(1);
if (parseFloat(percentage) > 3) { // Only show label if slice is large enough
ctx.fillText(`${percentage}%`, labelX, labelY);
}
currentAngle += sliceAngle;
}
// Add legend
let legendY = 420;
for (let i = 0; i < data.length; i++) {
const [ns, count] = data[i];
ctx.fillStyle = colors[i % colors.length];
ctx.fillRect(20, legendY, 15, 15);
ctx.fillStyle = '#000';
ctx.font = '12px sans-serif';
ctx.textAlign = 'left';
ctx.fillText(`${ns} (${count})`, 40, legendY + 12);
legendY += 20;
if (legendY > canvas.height - 20) break;
}
}
// </nowiki>