//Imports import * as compute from "./list/index.mjs" //Setup export default async function({login, q, imports, data, computed, graphql, queries, account}, {enabled = false} = {}) { //Plugin execution try { //Check if plugin is enabled and requirements are met if ((!enabled)||(!q.achievements)) return null //Load inputs let {threshold, secrets, only, ignored, limit} = imports.metadata.plugins.achievements.inputs({data, q, account}) //Initialization const list = [] await total({imports}) await compute[account]({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) //Results const order = {S:5, A:4, B:3, C:2, $:1, X:0} const colors = {S:["#FF0000", "#FF8500"], A:["#B59151", "#FFD576"], B:["#7D6CFF", "#B2A8FF"], C:["#2088FF", "#79B8FF"], $:["#FF48BD", "#FF92D8"], X:["#7A7A7A", "#B0B0B0"]} const achievements = list .filter(a => (order[a.rank] >= order[threshold])||((a.rank === "$")&&(secrets))) .filter(a => (!only.length)||((only.length)&&(only.includes(a.title.toLocaleLowerCase())))) .filter(a => !ignored.includes(a.title.toLocaleLowerCase())) .sort((a, b) => (order[b.rank]+b.progress*0.99) - (order[a.rank]+a.progress*0.99)) .map(({title, unlock, ...achievement}) => ({title:({S:`Master ${title.toLocaleLowerCase()}`, A:`Super ${title.toLocaleLowerCase()}`, B:`Great ${title.toLocaleLowerCase()}`}[achievement.rank] ?? title), unlock:!/invalid date/i.test(unlock) ? `${imports.date(unlock, {timeStyle:"short", timeZone:data.config.timezone?.name})} on ${imports.date(unlock, {dateStyle:"short", timeZone:data.config.timezone?.name})}` : null, ...achievement})) .map(({icon, ...achievement}) => ({icon:icon.replace(/#primary/g, colors[achievement.rank][0]).replace(/#secondary/g, colors[achievement.rank][1]), ...achievement})) .slice(0, limit || Infinity) return {list:achievements} } //Handle errors catch (error) { throw {error:{message:"An error occured", instance:error}} } } /**Rank */ function rank(x, [c, b, a, m]) { if (x >= a) return {rank:"A", progress:(x-a)/(m-a)} else if (x >= b) return {rank:"B", progress:(x-b)/(a-b)} else if (x >= c) return {rank:"C", progress:(x-c)/(b-c)} return {rank:"X", progress:x/c} } /**Leaderboards */ function leaderboard({user, type, requirement}) { return requirement ? { user:1+user, total:total[type], type, get top() { return Number(`1${"0".repeat(Math.ceil(Math.log10(this.user)))}`) }, get percentile() { return 100*(this.user/this.top) }, } : null } /**Total extracter */ async function total({imports}) { if (!total.promise) { total.promise = new Promise(async(solve, reject) => { //Setup browser console.debug("metrics/compute/plugins > achievements > filling total from github.com/search") const browser = await imports.puppeteer.launch() console.debug(`metrics/compute/plugins > achievements > started ${await browser.version()}`) //Extracting total from github.com/search for (let i = 0; (i < 100)&&((!total.users)||(!total.repositories)); i++) { const page = await browser.newPage() await page.goto("https://github.com/search") const result = await page.evaluate(() => [...document.querySelectorAll("h2")].filter(node => /Search more/.test(node.innerText)).shift()?.innerText.trim().match(/(?\d+)M\s+(?repositories|users|issues)$/)?.groups) ?? null console.log(`metrics/compute/plugins > achievements > setup found ${result?.type ?? "(?)"}`) if ((result?.type)&&(!total[result.type])) { const {count, type} = result total[type] = Number(count)*10e5 console.debug(`metrics/compute/plugins > achievements > set total.${type} to ${total[type]}`) } await page.close() await imports.wait(10*Math.random()) } //Check setup state if ((!total.users)||(!total.repositories)) return reject("Failed to initiate total for achievement plugin") console.debug("metrics/compute/plugins > achievements > total setup complete") return solve() }) } return total.promise }