Add linter and minor bug fixes (#107)

This commit is contained in:
Simon Lecoq
2021-02-05 23:45:48 +01:00
committed by GitHub
parent 61e2f6e1a1
commit 882a93dea5
74 changed files with 1544 additions and 712 deletions

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, rest, q, account, imports}, {enabled = false} = {}) {
export default async function({login, data, rest, q, account, imports}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met

View File

@@ -34,6 +34,7 @@ inputs:
type: array
format: comma-separated
default: all
example: issue, pr, review, wiki, star
values:
- all # Display all types of events
- comment # Display commits, issues and pull requests comments
@@ -48,4 +49,4 @@ inputs:
- fork # Display forked repositories
- star # Display starred repositories
- member # Display collaborators additions
- public # Display repositories made public
- public # Display repositories made public

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, queries, imports, q, account}, {enabled = false} = {}) {
export default async function({login, data, queries, imports, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -31,7 +31,7 @@
//Format and save results
for (const {name, entries} of lists) {
//Format results
const list = await Promise.all(entries.map(async media => await format({media, imports})))
const list = await Promise.all(entries.map(media => format({media, imports})))
result.lists[type][name.toLocaleLowerCase()] = shuffle ? imports.shuffle(list) : list
//Limit results
if (limit > 0) {
@@ -131,7 +131,7 @@
}
}
/** Media formatter */
/**Media formatter */
async function format({media, imports}) {
const {progress, score:userScore, media:{title, description, status, startDate:{year:release}, genres, averageScore, episodes, chapters, type, coverImage:{medium:artwork}}} = media
return {
@@ -140,6 +140,6 @@
description:description.replace(/<br\s*\\?>/g, " "),
scores:{user:userScore, community:averageScore},
released:type === "ANIME" ? episodes : chapters,
artwork:artwork ? await imports.imgb64(artwork) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg=="
artwork:artwork ? await imports.imgb64(artwork) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
}
}

View File

@@ -28,6 +28,7 @@ inputs:
type: array
format: comma-separated
default: favorites
example: favorites, watching, characters
values:
- favorites # Favorites animes and mangas (depending on plugin_anilist_medias values)
- watching # Animes in your watching list

View File

@@ -4,7 +4,7 @@
*/
//Setup
export default async function ({login, graphql, data, q, queries, imports}, conf) {
export default async function({login, graphql, data, q, queries, imports}, conf) {
//Load inputs
console.debug(`metrics/compute/${login}/base > started`)
let {repositories, repositories_forks:forks} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"}, {repositories:conf.settings.repositories ?? 100})
@@ -16,7 +16,7 @@
//Base parts (legacy handling for web instance)
const defaulted = ("base" in q) ? legacy.converter(q.base) ?? true : true
for (const part of conf.settings.plugins.base.parts)
data.base[part] = `base.${part}` in q ? legacy.converter(q[ `base.${part}`]) : defaulted
data.base[part] = `base.${part}` in q ? legacy.converter(q[`base.${part}`]) : defaulted
//Iterate through account types
for (const account of ["user", "organization"]) {
@@ -46,7 +46,8 @@
//Success
console.debug(`metrics/compute/${login}/base > graphql query > account ${account} > success`)
return {}
} catch (error) {
}
catch (error) {
console.debug(`metrics/compute/${login}/base > account ${account} > failed : ${error}`)
console.debug(`metrics/compute/${login}/base > checking next account`)
}
@@ -69,7 +70,7 @@
//Organization
organization({login, data}) {
console.debug(`metrics/compute/${login}/base > applying postprocessing`)
data.account = "organization",
data.account = "organization"
Object.assign(data.user, {
isHireable:false,
starredRepositories:{totalCount:0},
@@ -107,7 +108,7 @@
repositories:{totalCount:0, totalDiskUsage:0, nodes:[]},
packages:{totalCount:0},
})
}
},
}
//Legacy functions
@@ -119,5 +120,5 @@
return false
if (Number.isFinite(Number(value)))
return !!(Number(value))
}
}
},
}

View File

@@ -4,22 +4,25 @@
*/
//Setup
export default async function ({login, q, dflags}, {conf, data, rest, graphql, plugins, queries, account}, {pending, imports}) {
export default async function({login, q, dflags}, {conf, data, rest, graphql, plugins, queries, account}, {pending, imports}) {
//Load inputs
imports.metadata.plugins.core.inputs({data, account, q})
//Init
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}}
const computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}}
const avatar = imports.imgb64(data.user.avatarUrl)
data.computed = computed
console.debug(`metrics/compute/${login} > formatting common metrics`)
//Timezone config
if (q["config.timezone"]) {
const timezone = data.config.timezone = {name:q["config.timezone"], offset:0}
const timezone = {name:q["config.timezone"], offset:0}
data.config.timezone = timezone
try {
timezone.offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset*60*60*1000) || 0
console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset/(60*60*1000))} hours)`)
} catch {
}
catch {
timezone.error = `Failed to use timezone "${timezone.name}"`
console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`)
}
@@ -35,7 +38,7 @@
for (const name of Object.keys(imports.plugins)) {
if (!plugins[name]?.enabled)
continue
pending.push((async () => {
pending.push((async() => {
try {
console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, plugins[name])
@@ -71,7 +74,7 @@
computed.diskUsage = `${imports.bytes(data.user.repositories.totalDiskUsage*1000)}`
//Compute licenses stats
computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) ?? ""
computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([_an, a], [_bn, b]) => b - a).slice(0, 1).map(([name, _value]) => name) ?? ""
//Compute total commits
computed.commits += data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount
@@ -117,7 +120,7 @@
computed.calendar.map(day => day.color = halloween(day.color))
//Update isocalendar colors
const waiting = [...pending]
pending.push((async () => {
pending.push((async() => {
await Promise.all(waiting)
if (data.plugins.isocalendar?.svg)
data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg)
@@ -127,4 +130,4 @@
//Results
return null
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({data, computed, imports, q, account}, {enabled = false} = {}) {
export default async function({data, computed, imports, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -12,15 +12,27 @@
//Define getters
const followup = {
issues:{
get count() { return this.open + this.closed },
get open() { return computed.repositories.issues_open },
get closed() { return computed.repositories.issues_closed },
get count() {
return this.open + this.closed
},
get open() {
return computed.repositories.issues_open
},
get closed() {
return computed.repositories.issues_closed
},
},
pr:{
get count() { return this.open + this.merged },
get open() { return computed.repositories.pr_open },
get merged() { return computed.repositories.pr_merged }
}
get count() {
return this.open + this.merged
},
get open() {
return computed.repositories.pr_open
},
get merged() {
return computed.repositories.pr_merged
},
},
}
//Results
@@ -30,4 +42,4 @@
catch (error) {
throw {error:{message:"An error occured", instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -28,7 +28,7 @@
//Iterate through gists
console.debug(`metrics/compute/${login}/plugins > gists > processing ${gists.length} gists`)
let stargazers = 0, forks = 0, comments = 0, files = 0
let comments = 0, files = 0, forks = 0, stargazers = 0
for (const gist of gists) {
//Skip forks
if (gist.isFork)

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, rest, imports, q, account}, {enabled = false, ...defaults} = {}) {
export default async function({login, data, rest, imports, q, account}, {enabled = false, ...defaults} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -22,7 +22,10 @@
console.debug(`metrics/compute/${login}/plugins > habits > loading page ${page}`)
events.push(...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data)
}
} catch { console.debug(`metrics/compute/${login}/plugins > habits > no more page to load`) }
}
catch {
console.debug(`metrics/compute/${login}/plugins > habits > no more page to load`)
}
console.debug(`metrics/compute/${login}/plugins > habits > ${events.length} events loaded`)
//Get user recent commits
@@ -36,8 +39,7 @@
console.debug(`metrics/compute/${login}/plugins > habits > loading patches`)
const patches = [...await Promise.allSettled(commits
.flatMap(({payload}) => payload.commits).map(commit => commit.url)
.map(async commit => (await rest.request(commit)).data.files)
)]
.map(async commit => (await rest.request(commit)).data.files))]
.filter(({status}) => status === "fulfilled")
.map(({value}) => value)
.flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), patch:file.patch ?? ""})))
@@ -52,7 +54,7 @@
habits.commits.days[day] = (habits.commits.days[day] ?? 0) + 1
habits.commits.days.max = Math.max(...Object.values(habits.commits.days))
//Compute day with most commits
habits.commits.day = days.length ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][Object.entries(habits.commits.days).sort(([an, a], [bn, b]) => b - a).map(([day, occurence]) => day)[0]] ?? NaN : NaN
habits.commits.day = days.length ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][Object.entries(habits.commits.days).sort(([_an, a], [_bn, b]) => b - a).map(([day, _occurence]) => day)[0]] ?? NaN : NaN
}
//Commit hour
@@ -64,7 +66,7 @@
habits.commits.hours[hour] = (habits.commits.hours[hour] ?? 0) + 1
habits.commits.hours.max = Math.max(...Object.values(habits.commits.hours))
//Compute hour with most commits
habits.commits.hour = hours.length ? `${Object.entries(habits.commits.hours).sort(([an, a], [bn, b]) => b - a).map(([hour, occurence]) => hour)[0]}`.padStart(2, "0") : NaN
habits.commits.hour = hours.length ? `${Object.entries(habits.commits.hours).sort(([_an, a], [_bn, b]) => b - a).map(([hour, _occurence]) => hour)[0]}`.padStart(2, "0") : NaN
}
//Indent style
@@ -72,7 +74,7 @@
//Attempt to guess whether tabs or spaces are used in patches
console.debug(`metrics/compute/${login}/plugins > habits > searching indent style`)
patches
.map(({patch}) => patch.match(/((?:\t)|(?: )) /gm) ?? [])
.map(({patch}) => patch.match(/((?:\t)|(?:[ ]{2})) /gm) ?? []) //eslint-disable-line prefer-named-capture-group
.forEach(indent => habits.indents[/^\t/.test(indent) ? "tabs" : "spaces"]++)
habits.indents.style = habits.indents.spaces > habits.indents.tabs ? "spaces" : habits.indents.tabs > habits.indents.spaces ? "tabs" : ""
}
@@ -89,18 +91,18 @@
//Create temporary directory and save patches
console.debug(`metrics/compute/${login}/plugins > habits > creating temp dir ${path} with ${patches.length} files`)
await imports.fs.mkdir(path, {recursive:true})
await Promise.all(patches.map(async ({name, patch}, i) => await imports.fs.writeFile(imports.paths.join(path, `${i}${imports.paths.extname(name)}`), patch)))
await Promise.all(patches.map(({name, patch}, i) => imports.fs.writeFile(imports.paths.join(path, `${i}${imports.paths.extname(name)}`), patch)))
//Create temporary git repository
console.debug(`metrics/compute/${login}/plugins > habits > creating temp git repository`)
await imports.run(`git init && git add . && git config user.name "linguist" && git config user.email "<>" && git commit -m "linguist"`, {cwd:path}).catch(console.debug)
await imports.run(`git status`, {cwd:path})
await imports.run('git init && git add . && git config user.name "linguist" && git config user.email "<>" && git commit -m "linguist"', {cwd:path}).catch(console.debug)
await imports.run("git status", {cwd:path})
//Spawn linguist process
console.debug(`metrics/compute/${login}/plugins > habits > running linguist`)
;(await imports.run(`${prefix} github-linguist --breakdown`, {cwd:path}))
//Parse linguist result
.split("\n").map(line => line.match(/(?<value>[\d.]+)%\s+(?<language>\w+)/)?.groups).filter(line => line)
.map(({value, language}) => habits.linguist.languages[language] = (habits.linguist.languages[language] ?? 0) + value/100)
habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([an, a], [bn, b]) => b - a)
habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([_an, a], [_bn, b]) => b - a)
}
else
console.debug(`metrics/compute/${login}/plugins > habits > linguist not available`)
@@ -115,4 +117,4 @@
throw error
throw {error:{message:"An error occured", instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -39,7 +39,7 @@
//Compute the highest contributions in a day, streaks and average commits per day
console.debug(`metrics/compute/${login}/plugins > isocalendar > computing stats`)
let max = 0, streak = {max:0, current:0}, values = [], average = 0
let average = 0, max = 0, streak = {max:0, current:0}, values = []
for (const week of calendar.weeks) {
for (const day of week.contributionDays) {
values.push(day.contributionCount)
@@ -61,8 +61,8 @@
<feComponentTransfer>
${[..."RGB"].map(channel => `<feFunc${channel} type="linear" slope="${1-k*0.4}" />`).join("")}
</feComponentTransfer>
</filter>`
).join("")}
</filter>`)
.join("")}
<g transform="scale(4) translate(12, 0)">`
//Iterate through weeks
for (const week of calendar.weeks) {
@@ -79,10 +79,10 @@
</g>`
j++
}
svg += `</g>`
svg += "</g>"
i++
}
svg += `</g></svg>`
svg += "</g></svg>"
//Results
return {streak, max, average, svg, duration}
@@ -93,4 +93,4 @@
throw error
throw {error:{message:"An error occured", instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, imports, q, account}, {enabled = false} = {}) {
export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -42,7 +42,7 @@
//Compute languages stats
console.debug(`metrics/compute/${login}/plugins > languages > computing stats`)
languages.favorites = Object.entries(languages.stats).sort(([an, a], [bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, size:value, color:languages.colors[name], x:0})).filter(({value}) => value/languages.total > threshold)
languages.favorites = Object.entries(languages.stats).sort(([_an, a], [_bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, size:value, color:languages.colors[name], x:0})).filter(({value}) => value/languages.total > threshold)
const visible = {total:Object.values(languages.favorites).map(({size}) => size).reduce((a, b) => a + b, 0)}
for (let i = 0; i < languages.favorites.length; i++) {
languages.favorites[i].value /= visible.total

View File

@@ -18,6 +18,7 @@ inputs:
type: array
format: comma-separated
default: ""
example: html, css, ...
# List of repositories that will be skipped
plugin_languages_skipped:
@@ -25,6 +26,7 @@ inputs:
type: array
format: comma-separated
default: ""
example: my-repo-1, my-repo-2, ...
# Overrides default languages colors
# Use `${n}:${color}` to change the color of the n-th most used language (e.g. "0:red" to make your most used language red)
@@ -38,6 +40,7 @@ inputs:
- comma-separated
- /((?<index>[0-9])|(?<language>[-+a-z0-9#])):(?<color>#?[-a-z0-9]+)/
default: github
example: javascript:red, 0:blue, 1:#ff00aa
# Languages additional details
plugin_languages_details:
@@ -48,6 +51,7 @@ inputs:
- bytes-size # Languages total size written in bytes
- percentage # Languages proportions in %
default: ""
example: bytes-size, percentage
# Minimum threshold (in percentage) to reach for languages to be displayed
plugin_languages_threshold:

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, imports, rest, q, account}, {enabled = false} = {}) {
export default async function({login, data, imports, rest, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -22,7 +22,7 @@
//Get contributors stats from repositories
console.debug(`metrics/compute/${login}/plugins > lines > querying api`)
const lines = {added:0, deleted:0}
const response = await Promise.all(repositories.map(async ({repo, owner}) => await rest.repos.getContributorsStats({owner, repo})))
const response = await Promise.all(repositories.map(({repo, owner}) => rest.repos.getContributorsStats({owner, repo})))
//Compute changed lines
console.debug(`metrics/compute/${login}/plugins > lines > computing total diff`)

View File

@@ -180,8 +180,8 @@ Register your API key to finish setup.
plugin_music_provider: spotify # Use Spotify as provider
plugin_music_mode: recent # Set plugin mode
plugin_music_limit: 4 # Limit to 4 entries
plugin_music_played_at: yes # Show timestamp (only works with spotify, 🚧 @master feature)
plugin_music_token: "${{ secrets.SPOTIFY_CLIENT_ID }}, ${{ secrets.SPOTIFY_CLIENT_SECRET }}, ${{ secrets.SPOTIFY_REFRESH_TOKEN }}"
plugin_music_played_at: yes # Only works with spotify.
```
```yaml

View File

@@ -20,7 +20,7 @@
}
//Setup
export default async function ({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
export default async function({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -29,34 +29,40 @@
//Initialization
const raw = {
get provider() { return providers[provider]?.name ?? "" },
get mode() { return modes[mode] ?? "Unconfigured music plugin"},
get provider() {
return providers[provider]?.name ?? ""
},
get mode() {
return modes[mode] ?? "Unconfigured music plugin"
},
}
let tracks = null
//Load inputs
let {provider, mode, playlist, limit, user, played_at} = imports.metadata.plugins.music.inputs({data, account, q})
let {provider, mode, playlist, limit, user, "played.at":played_at} = imports.metadata.plugins.music.inputs({data, account, q})
//Auto-guess parameters
if ((playlist)&&(!mode))
mode = "playlist"
if ((playlist)&&(!provider))
for (const [name, {embed}] of Object.entries(providers))
if ((playlist)&&(!provider)) {
for (const [name, {embed}] of Object.entries(providers)) {
if (embed.test(playlist))
provider = name
}
}
if (!mode)
mode = "recent"
//Provider
if (!(provider in providers))
throw {error:{message:provider ? `Unsupported provider "${provider}"` : `Missing provider`}, ...raw}
throw {error:{message:provider ? `Unsupported provider "${provider}"` : "Missing provider"}, ...raw}
//Mode
if (!(mode in modes))
throw {error:{message:`Unsupported mode "${mode}"`}, ...raw}
//Playlist mode
if (mode === "playlist") {
if (!playlist)
throw {error:{message:`Missing playlist url`}, ...raw}
throw {error:{message:"Missing playlist url"}, ...raw}
if (!providers[provider].embed.test(playlist))
throw {error:{message:`Unsupported playlist url format`}, ...raw}
throw {error:{message:"Unsupported playlist url format"}, ...raw}
}
//Limit
limit = Math.max(1, Math.min(100, Number(limit)))
@@ -83,7 +89,7 @@
tracks = [...await frame.evaluate(() => [...document.querySelectorAll(".tracklist li")].map(li => ({
name:li.querySelector(".tracklist__track__name").innerText,
artist:li.querySelector(".tracklist__track__sub").innerText,
artwork:li.querySelector(".tracklist__track__artwork img").src
artwork:li.querySelector(".tracklist__track__artwork img").src,
})))]
break
}
@@ -95,7 +101,7 @@
name:tr.querySelector("td:nth-child(2) div:nth-child(1)").innerText,
artist:tr.querySelector("td:nth-child(2) div:nth-child(2)").innerText,
//Spotify doesn't provide artworks so we fallback on playlist artwork instead
artwork:window.getComputedStyle(document.querySelector("button[title=Play]").parentNode, null).backgroundImage.match(/^url\("(https:...+)"\)$/)[1]
artwork:window.getComputedStyle(document.querySelector("button[title=Play]").parentNode, null).backgroundImage.match(/^url\("(?<url>https:...+)"\)$/)?.groups?.url ?? null,
})))]
break
}
@@ -118,8 +124,6 @@
}
//Recently played
case "recent":{
//Initialisation
const timestamp = Date.now()-24*60*60*1000
//Handle provider
switch (provider) {
//Spotify
@@ -127,28 +131,40 @@
//Prepare credentials
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
if ((!client_id)||(!client_secret)||(!refresh_token))
throw {error:{message:`Spotify token must contain client id/secret and refresh token`}}
throw {error:{message:"Spotify token must contain client id/secret and refresh token"}}
//API call and parse tracklist
try {
//Request access token
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`)
const {data:{access_token:access}} = await imports.axios.post("https://accounts.spotify.com/api/token",
`${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`,
{headers:{"Content-Type":"application/x-www-form-urlencoded"}},
)
const {data:{access_token:access}} = await imports.axios.post("https://accounts.spotify.com/api/token", `${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`, {headers:{
"Content-Type":"application/x-www-form-urlencoded",
}})
console.debug(`metrics/compute/${login}/plugins > music > got access token`)
//Retrieve tracks
console.debug(`metrics/compute/${login}/plugins > music > querying spotify api`)
tracks = (await imports.axios.get(`https://api.spotify.com/v1/me/player/recently-played?limit=${limit}&after=${timestamp}`, {headers:{
"Accept":"application/json",
"Content-Type":"application/json",
"Authorization":`Bearer ${access}`}
})).data.items.map(({track, played_at}) => ({
name:track.name,
artist:track.artists[0].name,
artwork:track.album.images[0].url,
played_at: played_at ? imports.dayjs(played_at).format('[played at] HH:MM on DD/MM/YYYY') : ''
}))
tracks = []
for (let hours = .5; hours <= 24; hours++) {
//Load track half-hour by half-hour
const timestamp = Date.now()-hours*60*60*1000
const loaded = (await imports.axios.get(`https://api.spotify.com/v1/me/player/recently-played?after=${timestamp}`, {headers:{
"Content-Type":"application/json",
Accept:"application/json",
Authorization:`Bearer ${access}`,
}})).data.items.map(({track, played_at}) => ({
name:track.name,
artist:track.artists[0].name,
artwork:track.album.images[0].url,
played_at:played_at ? imports.dayjs(played_at).format("[played at] HH:MM on DD/MM/YYYY") : null,
}))
//Ensure no duplicate are added
for (const track of loaded) {
if (!tracks.map(({name}) => name).includes(track.name))
tracks.push(track)
}
//Early break
if (tracks.length >= limit)
break
}
}
//Handle errors
catch (error) {
@@ -169,9 +185,9 @@
try {
console.debug(`metrics/compute/${login}/plugins > music > querying lastfm api`)
tracks = (await imports.axios.get(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${user}&api_key=${token}&limit=${limit}&format=json`, {headers:{
"Accept":"application/json",
"User-Agent":"lowlighter/metrics"}
})).data.recenttracks.track.map((track) => ({
"User-Agent":"lowlighter/metrics",
Accept:"application/json",
}})).data.recenttracks.track.map(track => ({
name:track.name,
artist:track.artist["#text"],
artwork:track.image.reverse()[0]["#text"],
@@ -215,11 +231,11 @@
track.artwork = await imports.imgb64(track.artwork)
}
//Save results
return {...raw, tracks}
return {...raw, tracks, played_at}
}
//Unhandled error
throw {error:{message:`An error occured (could not retrieve tracks)`}}
throw {error:{message:"An error occured (could not retrieve tracks)"}}
}
//Handle errors
catch (error) {
@@ -227,4 +243,4 @@
throw error
throw {error:{message:"An error occured", instance:error}}
}
}
}

View File

@@ -47,6 +47,7 @@ inputs:
description: Embed playlist url
type: string
default: ""
example: https://embed.music.apple.com/--/playlist/--------/--------
# Number of music tracks to display
plugin_music_limit:
@@ -55,6 +56,8 @@ inputs:
default: 4
min: 1
max: 100
# Display when track was last played
plugin_music_played_at:
description: Display when the track was played
type: boolean

View File

@@ -18,6 +18,7 @@
token: NOT_NEEDED
plugin_music_token: MOCKED_CLIENT_ID, MOCKED_CLIENT_SECRET, MOCKED_REFRESH_TOKEN
plugin_music: yes
plugin_music_played_at: yes
plugin_music_provider: spotify
- name: Music plugin (recent - lastfm)

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, imports, data, q, account}, {enabled = false, token = null} = {}) {
export default async function({login, imports, data, q, account}, {enabled = false, token = null} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -53,4 +53,4 @@
}
throw {error:{message, instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, graphql, rest, q, queries, imports, account}, {enabled = false} = {}) {
export default async function({login, data, graphql, rest, q, queries, imports, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -12,7 +12,7 @@
types:account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"],
default:"followers, following",
alias:{followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor"},
sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"}
sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"},
}
if (q.repo) {
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)

View File

@@ -34,6 +34,7 @@ inputs:
type: array
format: comma-separated
default: followers, following
example: follower, following, sponsors, sponsoring
values:
- followers # For user metrics
- following # For user metrics

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, imports, q, account}, {enabled = false} = {}) {
export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -36,7 +36,7 @@
}
//Unhandled error
throw {error:{message:`An error occured (could not retrieve posts)`}}
throw {error:{message:"An error occured (could not retrieve posts)"}}
}
//Handle errors
catch (error) {
@@ -44,4 +44,4 @@
throw error
throw {error:{message:"An error occured", instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, imports, graphql, q, queries, account}, {enabled = false} = {}) {
export default async function({login, data, imports, graphql, q, queries, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -21,7 +21,7 @@
for (const identifier of repositories) {
//Querying repository project
console.debug(`metrics/compute/${login}/plugins > projects > querying api for ${identifier}`)
const {user, repository, id} = identifier.match(/(?<user>[-\w]+)[/](?<repository>[-\w]+)[/]projects[/](?<id>\d+)/)?.groups
const {user, repository, id} = identifier.match(/(?<user>[-\w]+)[/](?<repository>[-\w]+)[/]projects[/](?<id>\d+)/)?.groups ?? {}
const {[account]:{repository:{project}}} = await graphql(queries.projects.repository({user, repository, id, account}))
//Adding it to projects list
console.debug(`metrics/compute/${login}/plugins > projects > registering ${identifier}`)

View File

@@ -27,6 +27,7 @@ inputs:
plugin_projects_repositories:
description: List of repository project identifiers to disaplay
type: array
example: username/repo/projects/1, username/repo/projects/2, ...
format:
- comma-separated
- /(?<user>[-a-z0-9]+)[/](?<repo>[-a-z0-9]+)[/]projects[/](?<id>[0-9]+)/

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, graphql, data, imports, q, queries, account}, {enabled = false} = {}) {
export default async function({login, graphql, data, imports, q, queries, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -41,7 +41,7 @@
increments.max = Math.max(...Object.values(increments.dates))
//Compute total stargazers
let stargazers = data.computed.repositories.stargazers
let {stargazers} = data.computed.repositories
const total = {dates:{...increments.dates}, max:NaN, min:NaN}
{
const dates = Object.keys(total.dates)

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, graphql, q, queries, imports, account}, {enabled = false} = {}) {
export default async function({login, data, graphql, q, queries, imports, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, data, imports, q, account}, {enabled = false} = {}) {
export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -90,4 +90,4 @@
throw error
throw {error:{message:"An error occured", instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, imports, data, rest, q, account}, {enabled = false} = {}) {
export default async function({login, imports, data, rest, q, account}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -15,7 +15,7 @@
//Get views stats from repositories
console.debug(`metrics/compute/${login}/plugins > traffic > querying api`)
const views = {count:0, uniques:0}
const response = await Promise.all(repositories.map(async ({repo, owner}) => await rest.repos.getViews({owner, repo})))
const response = await Promise.all(repositories.map(({repo, owner}) => rest.repos.getViews({owner, repo})))
//Compute views
console.debug(`metrics/compute/${login}/plugins > traffic > computing stats`)
@@ -31,4 +31,4 @@
message = "Insufficient token rights"
throw {error:{message, instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
export default async function({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -35,18 +35,17 @@
tweet.mentions = tweet.entities?.mentions.map(({username}) => username) ?? []
//Format text
console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`)
tweet.text = imports.htmlescape(
tweet.text = imports.htmlescape( //eslint-disable-line function-paren-newline
//Escape tags
imports.htmlescape(tweet.text, {"<":true, ">":true})
//Mentions
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), ` <span class="mention">@$1</span> `)
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), ' <span class="mention">@$1</span> ')
//Hashtags (this regex comes from the twitter source code)
.replace(/(?<!&)[#|]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi, ` <span class="hashtag">#$1</span> `)
.replace(/(?<!&)[#|]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi, ' <span class="hashtag">#$1</span> ') //eslint-disable-line no-misleading-character-class, prefer-named-capture-group
//Line breaks
.replace(/\n/g, "<br/>")
//Links
.replace(/https?:[/][/](t.co[/]\w+)/g, ` <span class="link">$1</span> `)
, {"&":true})
.replace(/https?:[/][/](?<link>t.co[/]\w+)/g, ' <span class="link">$<link></span> '), {"&":true})
}))
//Result
@@ -63,4 +62,4 @@
}
throw {error:{message, instance:error}}
}
}
}

View File

@@ -1,5 +1,5 @@
//Setup
export default async function ({login, q, imports, data, account}, {enabled = false, token} = {}) {
export default async function({login, q, imports, data, account}, {enabled = false, token} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -12,8 +12,7 @@
limit = void(limit)
const range = {"7":"last_7_days", "30":"last_30_days", "180":"last_6_months", "365":"last_year"}[days] ?? "last_7_days"
//Querying api and format result
//https://wakatime.com/developers#stats
//Querying api and format result (https://wakatime.com/developers#stats)
console.debug(`metrics/compute/${login}/plugins > wakatime > querying api`)
const {data:{data:stats}} = await imports.axios.get(`https://wakatime.com/api/v1/users/current/stats/${range}?api_key=${token}`)
const result = {
@@ -42,4 +41,4 @@
}
throw {error:{message, instance:error}}
}
}
}