User:Polygnotus/Scripts/Pageviews.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/Pageviews.
// <nowiki>
// Wikipedia Pageviews Checker for common.js
// Checks pageview statistics for a list of articles and displays them sorted by views
async function checkPageviews() {
showInputDialog();
}
function showInputDialog() {
const fieldset = new OO.ui.FieldsetLayout({
label: 'Enter article names to check pageviews'
});
const helpText = new OO.ui.LabelWidget({
label: 'Supported formats:\n• [[Article Name]]\n• Article Name (one per line)'
});
const textInput = new OO.ui.MultilineTextInputWidget({
rows: 10,
placeholder: 'Paste your article names here...',
classes: ['pageviews-input']
});
const timeRangeDropdown = new OO.ui.DropdownWidget({
label: 'Time range',
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: 30, label: 'Last 30 days' }),
new OO.ui.MenuOptionWidget({ data: 60, label: 'Last 60 days' }),
new OO.ui.MenuOptionWidget({ data: 90, label: 'Last 90 days' })
]
}
});
timeRangeDropdown.getMenu().selectItemByData(30);
const statusArea = new OO.ui.MultilineTextInputWidget({
rows: 8,
readOnly: true,
classes: ['pageviews-status']
});
statusArea.$element.hide();
fieldset.addItems([
new OO.ui.FieldLayout(helpText, { align: 'top' }),
new OO.ui.FieldLayout(textInput, { label: 'Articles', align: 'top' }),
new OO.ui.FieldLayout(timeRangeDropdown, { label: 'Time range', align: 'left' }),
new OO.ui.FieldLayout(statusArea, { label: 'Status', align: 'top' })
]);
const windowManager = new OO.ui.WindowManager();
$('body').append(windowManager.$element);
function MessageDialog(config) {
MessageDialog.super.call(this, config);
}
OO.inheritClass(MessageDialog, OO.ui.ProcessDialog);
MessageDialog.static.name = 'pageviewsDialog';
MessageDialog.static.title = 'Article Pageviews Checker';
MessageDialog.static.actions = [
{ action: 'check', label: 'Check Pageviews', flags: ['primary', 'progressive'] },
{ action: 'clear', label: 'Clear', flags: 'safe' },
{ action: 'cancel', label: 'Close', flags: 'safe' }
];
MessageDialog.prototype.initialize = function() {
MessageDialog.super.prototype.initialize.apply(this, arguments);
this.content = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.content.$element.append(fieldset.$element);
this.$body.append(this.content.$element);
};
MessageDialog.prototype.getActionProcess = function(action) {
const dialog = this;
if (action === 'check') {
return new OO.ui.Process(function() {
const input = textInput.getValue().trim();
if (!input) {
OO.ui.alert('Please enter article names to check.');
return;
}
const days = timeRangeDropdown.getMenu().findSelectedItem().getData();
processArticles(input, days, statusArea, dialog);
});
} else if (action === 'clear') {
return new OO.ui.Process(function() {
textInput.setValue('');
statusArea.setValue('');
statusArea.$element.hide();
});
}
return MessageDialog.super.prototype.getActionProcess.call(this, action);
};
MessageDialog.prototype.getBodyHeight = function() {
return 500;
};
windowManager.addWindows([new MessageDialog({ size: 'large' })]);
windowManager.openWindow('pageviewsDialog');
}
function parseArticles(input) {
const lines = input.split('\n');
const articles = [];
const patterns = [
/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/, // [[Article Name]] or [[Article Name|Display]]
/^(.+)$/ // Plain article name
];
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine) continue;
for (const pattern of patterns) {
const match = trimmedLine.match(pattern);
if (match) {
const articleName = match[1].trim();
articles.push({
name: articleName,
original: trimmedLine
});
break;
}
}
}
return articles;
}
function deduplicateArticles(articles) {
const seen = new Set();
const uniqueArticles = [];
for (const article of articles) {
const normalizedName = article.name.toLowerCase().replace(/_/g, ' ');
if (!seen.has(normalizedName)) {
seen.add(normalizedName);
uniqueArticles.push(article);
}
}
return uniqueArticles;
}
async function processArticles(input, days, statusArea, dialog) {
const allArticles = parseArticles(input);
const articles = deduplicateArticles(allArticles);
if (articles.length === 0) {
OO.ui.alert('No valid article names found in the input.');
return;
}
statusArea.$element.show();
let statusText = '';
const duplicateCount = allArticles.length - articles.length;
if (duplicateCount > 0) {
statusText = `Found ${allArticles.length} articles, removed ${duplicateCount} duplicates.\n`;
}
statusText += `Checking pageviews for ${articles.length} articles (last ${days} days)...\n`;
statusArea.setValue(statusText);
const results = [];
const checkButton = dialog.actions.get({ actions: 'check' })[0];
checkButton.setDisabled(true);
checkButton.setLabel('Checking...');
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
for (let i = 0; i < articles.length; i++) {
const article = articles[i];
const progress = `[${i + 1}/${articles.length}]`;
statusText += `${progress} Checking ${article.name}...\n`;
statusArea.setValue(statusText);
try {
const pageviews = await getPageviews(article.name, startDate, endDate);
results.push({
name: article.name,
original: article.original,
views: pageviews
});
statusText += `${progress} ✓ ${article.name}: ${pageviews.toLocaleString()} views\n`;
statusArea.setValue(statusText);
} catch (error) {
console.error(`Failed to check ${article.name}:`, error);
results.push({
name: article.name,
original: article.original,
views: 0
});
statusText += `${progress} ? ${article.name}: Failed to fetch data\n`;
statusArea.setValue(statusText);
}
if (i < articles.length - 1) {
await sleep(500);
}
}
checkButton.setDisabled(false);
checkButton.setLabel('Check Pageviews');
statusText += `\n✓ Completed! Checked ${articles.length} articles.\n`;
statusArea.setValue(statusText);
results.sort((a, b) => b.views - a.views);
displayResults(results, days);
}
async function getPageviews(articleName, startDate, endDate) {
const project = mw.config.get('wgServerName');
const encodedArticle = encodeURIComponent(articleName.replace(/ /g, '_'));
const startStr = formatDate(startDate);
const endStr = formatDate(endDate);
const url = `https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/${project}/all-access/user/${encodedArticle}/daily/${startStr}/${endStr}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.items || data.items.length === 0) {
return 0;
}
const totalViews = data.items.reduce((sum, item) => sum + (item.views || 0), 0);
return totalViews;
}
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}${month}${day}`;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function displayResults(results, days) {
const windowManager = new OO.ui.WindowManager();
$('body').append(windowManager.$element);
function ResultsDialog(config) {
ResultsDialog.super.call(this, config);
}
OO.inheritClass(ResultsDialog, OO.ui.ProcessDialog);
ResultsDialog.static.name = 'resultsDialog';
ResultsDialog.static.title = `Pageviews Results (Last ${days} days)`;
ResultsDialog.static.actions = [
{ action: 'copyTable', label: 'Copy Table', flags: ['primary', 'progressive'] },
{ action: 'copyArticles', label: 'Copy Article Names', flags: 'progressive' },
{ action: 'cancel', label: 'Close', flags: 'safe' }
];
ResultsDialog.prototype.initialize = function() {
ResultsDialog.super.prototype.initialize.apply(this, arguments);
this.content = new OO.ui.PanelLayout({ padded: true, expanded: false, scrollable: true });
const tableHtml = '<table class="wikitable sortable" style="width: 100%;">' +
'<thead><tr><th>Article Name</th><th>Pageviews</th></tr></thead>' +
'<tbody>' +
results.map(r => `<tr><td>${mw.html.escape(r.name)}</td><td style="text-align: right;">${r.views.toLocaleString()}</td></tr>`).join('') +
'</tbody></table>';
this.content.$element.append(tableHtml);
this.$body.append(this.content.$element);
};
ResultsDialog.prototype.getActionProcess = function(action) {
if (action === 'copyTable') {
return new OO.ui.Process(function() {
const tableText = 'Article Name\tPageviews\n' +
results.map(r => `${r.name}\t${r.views}`).join('\n');
navigator.clipboard.writeText(tableText).then(() => {
mw.notify('Table copied to clipboard!', { type: 'success' });
}).catch(() => {
mw.notify('Failed to copy to clipboard', { type: 'error' });
});
});
} else if (action === 'copyArticles') {
return new OO.ui.Process(function() {
const articleNames = results.map(r => r.name).join('\n');
navigator.clipboard.writeText(articleNames).then(() => {
mw.notify('Article names copied to clipboard!', { type: 'success' });
}).catch(() => {
mw.notify('Failed to copy to clipboard', { type: 'error' });
});
});
}
return ResultsDialog.super.prototype.getActionProcess.call(this, action);
};
ResultsDialog.prototype.getBodyHeight = function() {
return 500;
};
windowManager.addWindows([new ResultsDialog({ size: 'large' })]);
windowManager.openWindow('resultsDialog');
}
function addPageviewsButton() {
if (mw.config.get('wgNamespaceNumber') === -1) return;
const portletId = mw.config.get('skin') === 'vector' ? 'p-cactions' : 'p-tb';
mw.util.addPortletLink(
portletId,
'#',
'Check Article Pageviews',
't-check-pageviews',
'Check pageview statistics for articles'
);
$('#t-check-pageviews').on('click', function(e) {
e.preventDefault();
checkPageviews();
});
}
$(document).ready(function() {
mw.loader.using(['oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets'], function() {
addPageviewsButton();
});
});
window.checkPageviews = checkPageviews;
// </nowiki>