//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 = []
const {user} = await graphql(queries.achievements({login}))
const scores = {followers:user.followers.totalCount, created:user.repositories.totalCount, stars:user.popular.nodes?.[0]?.stargazers?.totalCount ?? 0, forks:Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))}
const ranks = await graphql(queries.achievements.ranking(scores))
const requirements = {stars:5, followers:3, forks:1, created:1}
await total({imports})
//Developer
{
const value = user.repositories.totalCount
const unlock = user.repositories.nodes?.shift()
list.push({
title:"Developer",
text:`Published ${value} public repositor${imports.s(value, "y")}`,
icon:"",
...rank(value, [1, 20, 50, 100]), value, unlock:new Date(unlock?.createdAt),
leaderboard:leaderboard({user:ranks.created_rank.userCount, requirement:scores.created >= requirements.created, type:"users"}),
})
}
//Forker
{
const value = user.forks.totalCount
const unlock = user.forks.nodes?.shift()
list.push({
title:"Forker",
text:`Forked ${value} public repositor${imports.s(value, "y")}`,
icon:"",
...rank(value, [1, 5, 10, 20]), value, unlock:new Date(unlock?.createdAt),
})
}
//Contributor
{
const value = user.pullRequests.totalCount
const unlock = user.pullRequests.nodes?.shift()
list.push({
title:"Contributor",
text:`Opened ${value} pull request${imports.s(value)}`,
icon:"",
...rank(value, [1, 200, 500, 1000]), value, unlock:new Date(unlock?.createdAt),
})
}
//Manager
{
const value = user.projects.totalCount
const unlock = user.projects.nodes?.shift()
list.push({
title:"Manager",
text:`Created ${value} user project${imports.s(value)}`,
icon:"",
...rank(value, [1, 2, 3, 4]), value, unlock:new Date(unlock?.createdAt),
})
}
//Reviewer
{
const value = user.contributionsCollection.pullRequestReviewContributions.totalCount
const unlock = user.contributionsCollection.pullRequestReviewContributions.nodes?.shift()
list.push({
title:"Reviewer",
text:`Reviewed ${value} pull request${imports.s(value)}`,
icon:"",
...rank(value, [1, 200, 500, 1000]), value, unlock:new Date(unlock?.createdAt),
})
}
//Packager
{
const value = user.packages.totalCount
const unlock = user.packages.nodes?.shift()
list.push({
title:"Packager",
text:`Created ${value} package${imports.s(value)}`,
icon:"",
...rank(value, [1, 5, 10, 20]), value, unlock:new Date(unlock?.createdAt),
})
}
//Scripter
{
const value = user.gists.totalCount
const unlock = user.gists.nodes?.shift()
list.push({
title:"Scripter",
text:`Published ${value} gist${imports.s(value)}`,
icon:"",
...rank(value, [1, 20, 50, 100]), value, unlock:new Date(unlock?.createdAt),
})
}
//Worker
{
const value = user.organizations.totalCount
const unlock = user.organizations.nodes?.shift()
list.push({
title:"Worker",
text:`Joined ${value} organization${imports.s(value)}`,
icon:"",
...rank(value, [1, 2, 3, 4]), value, unlock:new Date(unlock?.createdAt),
})
}
//Stargazer
{
const value = user.starredRepositories.totalCount
const unlock = user.starredRepositories.nodes?.shift()
list.push({
title:"Stargazer",
text:`Starred ${value} repositor${imports.s(value, "y")}`,
icon:"",
...rank(value, [1, 200, 500, 1000]), value, unlock:new Date(unlock?.createdAt),
})
}
//Follower
{
const value = user.following.totalCount
const unlock = user.following.nodes?.shift()
list.push({
title:"Follower",
text:`Following ${value} user${imports.s(value)}`,
icon:"",
...rank(value, [1, 200, 500, 1000]), value, unlock:new Date(unlock?.createdAt),
})
}
//Influencer
{
const value = user.followers.totalCount
const unlock = user.followers.nodes?.shift()
list.push({
title:"Influencer",
text:`Followed by ${value} user${imports.s(value)}`,
icon:"",
...rank(value, [1, 200, 500, 1000]), value, unlock:new Date(unlock?.createdAt),
leaderboard:leaderboard({user:ranks.user_rank.userCount, requirement:scores.followers >= requirements.followers, type:"users"}),
})
}
//Maintainer
{
const value = user.popular.nodes?.shift()?.stargazers?.totalCount ?? 0
const unlock = null
list.push({
title:"Maintainer",
text:`Maintaining a repository with ${value} star${imports.s(value)}`,
icon:"",
...rank(value, [1, 1000, 5000, 10000]), value, unlock:new Date(unlock?.createdAt),
leaderboard:leaderboard({user:ranks.repo_rank.repositoryCount, requirement:scores.stars >= requirements.stars, type:"repositories"}),
})
}
//Inspirationer
{
const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))
const unlock = null
list.push({
title:"Inspirationer",
text:`Maintaining a repository which has been forked ${value} time${imports.s(value)}`,
icon:"",
...rank(value, [1, 100, 500, 1000]), value, unlock:new Date(unlock?.createdAt),
leaderboard:leaderboard({user:ranks.forks_rank.repositoryCount, requirement:scores.forks >= requirements.forks, type:"repositories"}),
})
}
//Polyglot
{
const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node:{name}}) => name))).size
const unlock = null
list.push({
title:"Polyglot",
text:`Using ${value} different programming language${imports.s(value)}`,
icon:"",
...rank(value, [1, 4, 8, 16]), value, unlock:new Date(unlock?.createdAt),
})
}
//Member
{
const value = computed.registered.diff
const unlock = null
list.push({
title:"Member",
text:`Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`,
icon:"",
...rank(value, [1, 3, 5, 10]), value, unlock:new Date(unlock?.createdAt),
})
}
//Verified
{
const value = !/This user hasn't uploaded any GPG keys/i.test((await imports.axios.get(`https://github.com/${login}.gpg`)).data)
const unlock = null
list.push({
title:"Verified",
text:"Registered a GPG key to sign commits",
icon:"",
rank:value ? "$" : "X", progress:1, value, unlock:new Date(unlock?.createdAt),
})
}
//Explorer
{
const value = !/doesn’t have any starred topics yet/i.test((await imports.axios.get(`https://github.com/stars/${login}/topics`)).data)
const unlock = null
list.push({
title:"Explorer",
text:"Starred a topic on GitHub Explore",
icon:"",
rank:value ? "$" : "X", progress:1, value, unlock:new Date(unlock?.createdAt),
})
}
//Automater
{
const value = process.env.GITHUB_ACTIONS
const unlock = null
list.push({
title:"Automater",
text:"Use GitHub Actions to automate profile updates",
icon:"",
rank:value ? "$" : "X", progress:1, value, unlock:new Date(unlock?.createdAt),
})
}
//Infographile
{
const {repository:{viewerHasStarred:value}, viewer:{login:_login}} = await graphql(queries.achievements.metrics())
const unlock = null
list.push({
title:"Infographile",
text:"Fervent supporter of metrics",
icon:"",
rank:(value)&&(login === _login) ? "$" : "X", progress:1, value, unlock:new Date(unlock?.createdAt),
})
}
//Octonaut
{
const {user:{viewerIsFollowing:value}, viewer:{login:_login}} = await graphql(queries.achievements.octocat())
const unlock = null
list.push({
title:"Octonaut",
text:"Following octocat",
icon:"",
rank:(value)&&(login === _login) ? "$" : "X", progress:1, value, unlock:new Date(unlock?.createdAt),
})
}
//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) => { //eslint-disable-line no-async-promise-executor
//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
}