User:DreamRimmer/adminnewslettertools.js: Difference between revisions

Content deleted Content added
move arbcom section
batches, simple caching
Line 7:
*/
$(document).ready(function() {
// Simple cache for API results
var apiCache = {};
var CACHE_DURATION = 10 * 60 * 1000; // 10 minutes
 
function getCacheKey(api, params) {
return JSON.stringify({api: api.toString(), params: params});
}
 
function getCachedResult(key) {
var cached = apiCache[key];
if (cached && (Date.now() - cached.timestamp < CACHE_DURATION)) {
return cached.data;
}
return null;
}
 
function setCachedResult(key, data) {
apiCache[key] = {
data: data,
timestamp: Date.now()
};
}
 
function initializeAdminNewsTools() {
$('#mw-content-text > p').remove();
Line 364 ⟶ 387:
}
 
function fetchAllLogEventsfetchAllLogEventsBatched(api, params, results = [], batchSize = 50, delay = 200) {
returnvar api.get(params).then(responsecacheKey => {getCacheKey(api, params);
var cached = results.pushgetCachedResult(...(response.query.logevents || [])cacheKey);
if (response.continuecached) {
return fetchAllLogEventsPromise.resolve(api, { ...params, ...response.continue }, resultscached);
}
 
return results;
var currentParams = {...params, lelimit: Math.min(batchSize, params.lelimit || 500)};
});
function fetchBatch() {
return new Promise((resolve, reject) => {
var timeout = setTimeout(() => {
reject(new Error('API request timeout'));
}, 15000);
 
api.get(currentParams).then(response => {
clearTimeout(timeout);
var newResults = response.query.logevents || [];
results.push(...newResults);
if (response.continue && results.length < (params.lelimit || 500)) {
currentParams = {...currentParams, ...response.continue};
setTimeout(() => {
fetchBatch().then(resolve).catch(reject);
}, delay);
} else {
setCachedResult(cacheKey, results);
resolve(results);
}
}).catch(error => {
clearTimeout(timeout);
reject(error);
});
});
}
return fetchBatch();
}
 
function fetchAllRevisionsfetchAllRevisionsBatched(api, params, results = [], batchSize = 50, delay = 200) {
returnvar api.get(params).then(responsecacheKey => {getCacheKey(api, params);
var const pagescached = response.query.pages || {}getCachedResult(cacheKey);
if Object.values(pagescached).forEach(page => {
return if Promise.resolve(page.revisionscached) {;
results.push(...page.revisions);}
 
}
var currentParams = {...params, rvlimit: Math.min(batchSize, params.rvlimit || 500)};
function fetchBatch() {
return new Promise((resolve, reject) => {
var timeout = setTimeout(() => {
reject(new Error('API request timeout'));
}, 15000);
 
api.get(currentParams).then(response => {
clearTimeout(timeout);
const pages = response.query.pages || {};
Object.values(pages).forEach(page => {
if (page.revisions) {
results.push(...page.revisions);
}
});
if (response.continue && results.length < (params.rvlimit || 500)) {
currentParams = {...currentParams, ...response.continue};
setTimeout(() => {
fetchBatch().then(resolve).catch(reject);
}, delay);
} else {
setCachedResult(cacheKey, results);
resolve(results);
}
}).catch(error => {
clearTimeout(timeout);
reject(error);
});
});
if (response.continue) {}
return fetchAllRevisions(api, { ...params, ...response.continue }, results);
return }fetchBatch();
return results;
});
}
 
Line 395 ⟶ 475:
 
return Promise.all([
fetchAllLogEventsfetchAllLogEventsBatched(metaApi, {
action: 'query',
list: 'logevents',
Line 401 ⟶ 481:
lestart: endTs,
leend: startTs,
lelimit: 'max'500,
leprop: 'timestamp|title|user|userid|details|params|type|comment|ids',
format: 'json'
}),
fetchAllLogEventsfetchAllLogEventsBatched(localApi, {
action: 'query',
list: 'logevents',
Line 411 ⟶ 491:
lestart: endTs,
leend: startTs,
lelimit: 'max'500,
leprop: 'timestamp|title|user|userid|details|params|type|comment|ids',
format: 'json'
Line 435 ⟶ 515:
}
 
function fetchTechNewsfetchTechNewsBatched(year, month) {
const weeks = getWeeksInMonth(year, month);
var cacheKey = `tech_news_${year}_${month}`;
var cached = getCachedResult(cacheKey);
if (cached) {
return Promise.resolve(cached);
}
constfunction promises = weeks.mapfetchWeekBatch(week =>weekBatch) {
return metaApiPromise.getall(weekBatch.map(week => {
action:return 'parse',new Promise((resolve) => {
page: `Tech/News/${year}/$ var timeout = setTimeout(() => {week}`,
format: 'json', resolve(null);
prop: 'text' }, 10000);
 
}).then(response => {
if (response.parse && response metaApi.parse.text) get({
const html = response.parse.text[ action: '*parse'];,
const match = html.match( page: `Tech/News/(\d${4year}), week (\d+) \(([^)]+)\)/);${week}`,
if (match) { format: 'json',
constprop: weekDate = match[3];'text'
const parsedDate}).then(response => new Date(weekDate);{
if clearTimeout(parsedDate.getMonth() === month - 1timeout) {;
if (response.parse && return { week, date: weekDate, url: `https://metaresponse.wikimediaparse.org/wiki/Tech/News/$text) {year}/${week}` };
const html = response.parse.text['*'];
const match = html.match(/(\d{4}), week (\d+) \(([^)]+)\)/);
if (match) {
const weekDate = match[3];
const parsedDate = new Date(weekDate);
if (parsedDate.getMonth() === month - 1) {
resolve({ week, date: weekDate, url: `https://meta.wikimedia.org/wiki/Tech/News/${year}/${week}` });
return;
}
}
}
} resolve(null);
}).catch(() => {
return null clearTimeout(timeout);
}).catch(() => resolve(null);
});
});
}));
}
 
returnconst Promise.all(promises).then(resultsbatchSize => {2;
const batches = [];
for (let i = 0; i < weeks.length; i += batchSize) {
batches.push(weeks.slice(i, i + batchSize));
}
 
return batches.reduce((promise, batch) => {
return promise.then(results => {
return fetchWeekBatch(batch).then(batchResults => {
return results.concat(batchResults);
});
});
}, Promise.resolve([])).then(results => {
const validWeeks = results.filter(r => r !== null);
let html;
if (!validWeeks.length) {
returnhtml = '<p>No tech news found for this period.</p>';
} else {
html = '<h4>Tech News Links</h4><ul>';
validWeeks.forEach(({ week, date, url }) => {
html += `<li><a href="${url}" target="_blank">Tech/News/${year}/${week}</a> (${date})</li>`;
});
html += '</ul>';
}
setCachedResult(cacheKey, html);
 
let html = '<h4>Tech News Links</h4><ul>';
validWeeks.forEach(({ week, date, url }) => {
html += `<li><a href="${url}" target="_blank">Tech/News/${year}/${week}</a> (${date})</li>`;
});
html += '</ul>';
return html;
});
Line 477 ⟶ 589:
function fetchCentralizedDiscussion(startTs, endTs) {
return fetchAllRevisionsfetchAllRevisionsBatched(localApi, {
action: 'query',
titles: 'Template:Centralized discussion',
Line 483 ⟶ 595:
rvstart: endTs,
rvend: startTs,
rvlimit: 'max'500,
rvprop: 'timestamp|user|comment|ids',
format: 'json'
Line 493 ⟶ 605:
function fetchArbComNotices(startTs, endTs) {
return fetchAllRevisionsfetchAllRevisionsBatched(localApi, {
action: 'query',
titles: 'Wikipedia:Arbitration Committee/Noticeboard',
Line 499 ⟶ 611:
rvstart: endTs,
rvend: startTs,
rvlimit: 'max'500,
rvprop: 'timestamp|user|comment|tags|ids',
format: 'json'
Line 509 ⟶ 621:
 
function fetchArbComCases() {
var cacheKey = 'arbcom_cases';
var cached = getCachedResult(cacheKey);
if (cached) {
return Promise.resolve(cached);
}
 
return Promise.all([
localApi.getnew Promise((resolve) => {
action:var timeout = setTimeout(() => resolve({ 'parse': null }), 10000);
page: 'Template:ArbComOpenTasks/Cases',localApi.get({
format action: 'jsonparse',
prop page: 'textTemplate:ArbComOpenTasks/Cases',
}).catch(() => ({ parse: null })) format: 'json',
localApi.get({ prop: 'text'
action:}).then(response 'parse',=> {
page: 'Template:ArbComOpenTasks/ClosedCases', clearTimeout(timeout);
format: 'json', resolve(response);
prop:}).catch(() 'text'=> {
}).catch(() => ({ parse: null }) clearTimeout(timeout);
resolve({ parse: null });
});
}),
new Promise((resolve) => {
var timeout = setTimeout(() => resolve({ parse: null }), 10000);
localApi.get({
action: 'parse',
page: 'Template:ArbComOpenTasks/ClosedCases',
format: 'json',
prop: 'text'
}).then(response => {
clearTimeout(timeout);
resolve(response);
}).catch(() => {
clearTimeout(timeout);
resolve({ parse: null });
});
})
]).then(([openResponse, closedResponse]) => {
let html = '';
Line 542 ⟶ 678:
html += '<p><a href="https://en.wikipedia.org/wiki/Template:ArbComOpenTasks" target="_blank">See detailed statistics</a></p>';
setCachedResult(cacheKey, html);
return html;
});
Line 615 ⟶ 751:
return html;
}
 
function fetchRecentRfCs() {
function fetchRecentRfCsBatched() {
var cacheKey = 'recent_rfcs';
var cached = getCachedResult(cacheKey);
if (cached) {
return Promise.resolve(cached);
}
 
const rfcPages = [
{ name: 'Biographies', url: 'Wikipedia:Requests_for_comment/Biographies' },
Line 635 ⟶ 778:
];
 
constfunction promises = rfcPages.mapfetchPageBatch(page =>pageBatch) {
return localApiPromise.getall(pageBatch.map(page => {
action:return 'parse',new Promise((resolve) => {
page: page.url, var timeout = setTimeout(() => {
format: 'json', resolve([]);
prop: 'wikitext' }, 10000);
}).then(response => {
if (response.parse && response.parse.wikitext) {
const wikitext = response.parse.wikitext['*'];
const rfcs = parseRfCs(wikitext, page.name);
return rfcs;
}
return [];
}).catch(() => []);
});
 
localApi.get({
return Promise.all(promises).then(results => {
const allRfcs = results.flat(); action: 'parse',
console.log('Total RfCs found page:', allRfcspage.length);url,
console.log('RfC data format: 'json', allRfcs.slice(0, 3));
return createRfCDisplay(allRfcs); prop: 'wikitext'
}).then(response => {
clearTimeout(timeout);
if (response.parse && response.parse.wikitext) {
const wikitext = response.parse.wikitext['*'];
const rfcs = parseRfCs(wikitext, page.name);
resolve(rfcs);
} else {
resolve([]);
}
}).catch(() => {
clearTimeout(timeout);
resolve([]);
});
});
}));
}
 
const batchSize = 3;
const batches = [];
for (let i = 0; i < rfcPages.length; i += batchSize) {
batches.push(rfcPages.slice(i, i + batchSize));
}
 
return batches.reduce((promise, batch, index) => {
return promise.then(results => {
return fetchPageBatch(batch).then(batchResults => {
// Add delay between batches except for the last one
if (index < batches.length - 1) {
return new Promise(resolve => {
setTimeout(() => {
resolve(results.concat(batchResults.flat()));
}, 300);
});
} else {
return results.concat(batchResults.flat());
}
});
});
}, Promise.resolve([])).then(allRfcs => {
const result = createRfCDisplay(allRfcs);
setCachedResult(cacheKey, result);
return result;
});
}
Line 782 ⟶ 958:
 
function checkPageExists(title) {
returnvar localApi.get(cacheKey = `page_exists_${title}`;
var cached = action: 'query',getCachedResult(cacheKey);
if (cached !== null) titles: title,{
format:return 'json'Promise.resolve(cached);
}).then(response => {
 
const pages = response.query.pages || {};
return new Promise((resolve) => {
return !Object.values(pages).some(page => page.hasOwnProperty('missing'));
}).catch var timeout = setTimeout(() => false);{
setCachedResult(cacheKey, false);
resolve(false);
}, 10000);
 
localApi.get({
action: 'query',
titles: title,
format: 'json'
}).then(response => {
clearTimeout(timeout);
const pages = response.query.pages || {};
const exists = !Object.values(pages).some(page => page.hasOwnProperty('missing'));
setCachedResult(cacheKey, exists);
resolve(exists);
}).catch(() => {
clearTimeout(timeout);
setCachedResult(cacheKey, false);
resolve(false);
});
});
}
 
Line 837 ⟶ 1,033:
 
if (section === 'all' || section === 'tech') {
promises.push(fetchTechNewsfetchTechNewsBatched(year, month));
sections.push('tech');
}
Line 852 ⟶ 1,048:
 
if ((section === 'all' || section === 'rfc') && isCurrentOrLastMonth) {
promises.push(fetchRecentRfCsfetchRecentRfCsBatched());
sections.push('rfc');
}