Add option to ignore languages/skip repositories in languages plugin (#22)

* Add option to ignore languages/skip repositories in languages plugin

* Fix logs on error and first attempt to fix update of file larger than >1mb

* Use variable branch name

* Update version number
This commit is contained in:
Simon Lecoq
2020-12-15 20:46:00 +01:00
committed by GitHub
parent d057f20fa3
commit f9460d6159
10 changed files with 140 additions and 69 deletions

View File

@@ -731,6 +731,8 @@ Add the following to your workflow :
with: with:
# ... other options # ... other options
plugin_languages: yes plugin_languages: yes
plugin_languages_ignored: "" # List of comma separated languages to ignore
plugin_languages_skipped: "" # List of comma separated repositories to skip
``` ```
</details> </details>

View File

@@ -115,6 +115,18 @@ inputs:
description: Enable most used languages metrics description: Enable most used languages metrics
default: no default: no
# List of ignored languages, comma separated
# Ignored languages won't count towards your languages metrics
plugin_languages_ignored:
description: List of ignored languages
default: ""
# List of skipped repositories, comma separated
# Skipped repositories won't count towards your languages metrics
plugin_languages_skipped:
description: List of skipped repositories
default: ""
# Follow-up plugin # Follow-up plugin
# Display the number and the ratio of opened/closed issues and opened/merged pull requests on your repositories # Display the number and the ratio of opened/closed issues and opened/merged pull requests on your repositories
plugin_followup: plugin_followup:

12
action/dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -15,9 +15,9 @@
//Runner //Runner
try { try {
//Initialization //Initialization
console.log(`GitHub metrics as SVG image`) console.log(`GitHub metrics`)
console.log(`========================================================`) console.log(`========================================================`)
console.log(`Version | <#version>`) console.log(`Version | <#version>`)
process.on("unhandledRejection", error => { throw error }) process.on("unhandledRejection", error => { throw error })
//Skip process if needed //Skip process if needed
@@ -30,30 +30,30 @@
//Load configuration //Load configuration
const conf = await setup({log:false}) const conf = await setup({log:false})
console.log(`Configuration | loaded`) console.log(`Configuration | loaded`)
//Load svg template, style, fonts and query //Load svg template, style, fonts and query
const template = core.getInput("template") || "classic" const template = core.getInput("template") || "classic"
console.log(`Template to use | ${template}`) console.log(`Template to use | ${template}`)
//Token for data gathering //Token for data gathering
const token = core.getInput("token") const token = core.getInput("token")
console.log(`Github token | ${token ? "provided" : "missing"}`) console.log(`Github token | ${token ? "provided" : "missing"}`)
if (!token) if (!token)
throw new Error("You must provide a valid GitHub token to gather your metrics") throw new Error("You must provide a valid GitHub token to gather your metrics")
const graphql = octokit.graphql.defaults({headers:{authorization: `token ${token}`}}) const graphql = octokit.graphql.defaults({headers:{authorization: `token ${token}`}})
console.log(`Github GraphQL API | ok`) console.log(`Github GraphQL API | ok`)
const rest = github.getOctokit(token) const rest = github.getOctokit(token)
console.log(`Github REST API | ok`) console.log(`Github REST API | ok`)
//SVG output //SVG output
const filename = core.getInput("filename") || "github-metrics.svg" const filename = core.getInput("filename") || "github-metrics.svg"
console.log(`SVG output file | ${filename}`) console.log(`SVG output file | ${filename}`)
//SVG optimization //SVG optimization
const optimize = bool(core.getInput("optimize"), true) const optimize = bool(core.getInput("optimize"), true)
conf.optimize = optimize conf.optimize = optimize
console.log(`SVG optimization | ${optimize}`) console.log(`SVG optimization | ${optimize}`)
//GitHub user //GitHub user
let authenticated let authenticated
@@ -64,20 +64,20 @@
authenticated = github.context.repo.owner authenticated = github.context.repo.owner
} }
const user = core.getInput("user") || authenticated const user = core.getInput("user") || authenticated
console.log(`GitHub user | ${user}`) console.log(`GitHub user | ${user}`)
//Debug mode //Debug mode
const debug = bool(core.getInput("debug")) const debug = bool(core.getInput("debug"))
if (!debug) if (!debug)
console.debug = message => debugged.push(message) console.debug = message => debugged.push(message)
console.log(`Debug mode | ${debug}`) console.log(`Debug mode | ${debug}`)
//Base elements //Base elements
const base = {} const base = {}
let parts = (core.getInput("base")||"").split(",").map(part => part.trim()) let parts = (core.getInput("base")||"").split(",").map(part => part.trim())
for (const part of conf.settings.plugins.base.parts) for (const part of conf.settings.plugins.base.parts)
base[`base.${part}`] = parts.includes(part) base[`base.${part}`] = parts.includes(part)
console.log(`Base parts | ${parts.join(", ") || "(none)"}`) console.log(`Base parts | ${parts.join(", ") || "(none)"}`)
//Additional plugins //Additional plugins
const plugins = { const plugins = {
@@ -95,112 +95,129 @@
projects:{enabled:bool(core.getInput("plugin_projects"))}, projects:{enabled:bool(core.getInput("plugin_projects"))},
} }
let q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true])) let q = Object.fromEntries(Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => [key, true]))
console.log(`Plugins enabled | ${Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key).join(", ")}`) console.log(`Plugins enabled | ${Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key).join(", ")}`)
//Additional plugins options //Additional plugins options
//Pagespeed //Pagespeed
if (plugins.pagespeed.enabled) { if (plugins.pagespeed.enabled) {
plugins.pagespeed.token = core.getInput("plugin_pagespeed_token") plugins.pagespeed.token = core.getInput("plugin_pagespeed_token")
console.log(`Pagespeed token | ${plugins.pagespeed.token ? "provided" : "missing"}`)
q[`pagespeed.detailed`] = bool(core.getInput(`plugin_pagespeed_detailed`)) q[`pagespeed.detailed`] = bool(core.getInput(`plugin_pagespeed_detailed`))
console.log(`Pagespeed detailed | ${q["pagespeed.detailed"]}`) console.log(`Pagespeed token | ${plugins.pagespeed.token ? "provided" : "missing"}`)
console.log(`Pagespeed detailed | ${q["pagespeed.detailed"]}`)
}
//Languages
if (plugins.languages.enabled) {
for (const option of ["ignored", "skipped"])
q[`languages.${option}`] = core.getInput(`plugin_languages_${option}`) || null
console.log(`Languages ignored | ${q["languages.ignored"]}`)
console.log(`Languages skipped repos | ${q["languages.skipped"]}`)
} }
//Music //Music
if (plugins.music.enabled) { if (plugins.music.enabled) {
plugins.music.token = core.getInput("plugin_music_token") || ""
for (const option of ["provider", "mode", "playlist", "limit"]) for (const option of ["provider", "mode", "playlist", "limit"])
q[`music.${option}`] = core.getInput(`plugin_music_${option}`) || null q[`music.${option}`] = core.getInput(`plugin_music_${option}`) || null
console.log(`Music provider | ${q["music.provider"]}`) console.log(`Music provider | ${q["music.provider"]}`)
console.log(`Music plugin mode | ${q["music.mode"]}`) console.log(`Music plugin mode | ${q["music.mode"]}`)
console.log(`Music playlist | ${q["music.playlist"]}`) console.log(`Music playlist | ${q["music.playlist"]}`)
console.log(`Music tracks limit | ${q["music.limit"]}`) console.log(`Music tracks limit | ${q["music.limit"]}`)
plugins.music.token = core.getInput("plugin_music_token") || "" console.log(`Music token | ${plugins.music.token ? "provided" : "missing"}`)
console.log(`Music token | ${plugins.music.token ? "provided" : "missing"}`)
} }
//Posts //Posts
if (plugins.posts.enabled) { if (plugins.posts.enabled) {
for (const option of ["source", "limit"]) for (const option of ["source", "limit"])
q[`posts.${option}`] = core.getInput(`plugin_posts_${option}`) || null q[`posts.${option}`] = core.getInput(`plugin_posts_${option}`) || null
console.log(`Posts provider | ${q["posts.provider"]}`) console.log(`Posts provider | ${q["posts.provider"]}`)
console.log(`Posts limit | ${q["posts.limit"]}`) console.log(`Posts limit | ${q["posts.limit"]}`)
} }
//Isocalendar //Isocalendar
if (plugins.isocalendar.enabled) { if (plugins.isocalendar.enabled) {
q["isocalendar.duration"] = core.getInput("plugin_isocalendar_duration") ?? "half-year" q["isocalendar.duration"] = core.getInput("plugin_isocalendar_duration") ?? "half-year"
console.log(`Isocalendar duration| ${q["isocalendar.duration"]}`) console.log(`Isocalendar duration | ${q["isocalendar.duration"]}`)
} }
//Topics //Topics
if (plugins.topics.enabled) { if (plugins.topics.enabled) {
for (const option of ["sort", "limit"]) for (const option of ["sort", "limit"])
q[`topics.${option}`] = core.getInput(`plugin_topics_${option}`) || null q[`topics.${option}`] = core.getInput(`plugin_topics_${option}`) || null
console.log(`Topics sort mode | ${q["topics.sort"]}`) console.log(`Topics sort mode | ${q["topics.sort"]}`)
console.log(`Topics limit | ${q["topics.limit"]}`) console.log(`Topics limit | ${q["topics.limit"]}`)
} }
//Projects //Projects
if (plugins.projects.enabled) { if (plugins.projects.enabled) {
for (const option of ["limit"]) for (const option of ["limit"])
q[`projects.${option}`] = core.getInput(`plugin_projects_${option}`) || null q[`projects.${option}`] = core.getInput(`plugin_projects_${option}`) || null
console.log(`Projects limit | ${q["projects.limit"]}`) console.log(`Projects limit | ${q["projects.limit"]}`)
} }
//Repositories to use //Repositories to use
const repositories = Number(core.getInput("repositories")) || 100 const repositories = Number(core.getInput("repositories")) || 100
console.log(`Repositories to use | ${repositories}`) console.log(`Repositories to use | ${repositories}`)
//Die on plugins errors //Die on plugins errors
const die = bool(core.getInput("plugins_errors_fatal")) const die = bool(core.getInput("plugins_errors_fatal"))
console.log(`Plugin errors | ${die ? "die" : "ignore"}`) console.log(`Plugin errors | ${die ? "die" : "ignore"}`)
//Built query //Built query
q = {...q, base:false, ...base, repositories, template} q = {...q, base:false, ...base, repositories, template}
//Render metrics //Render metrics
const rendered = await metrics({login:user, q}, {graphql, rest, plugins, conf, die}) const rendered = await metrics({login:user, q}, {graphql, rest, plugins, conf, die})
console.log(`Render | complete`) console.log(`Render | complete`)
//Verify svg //Verify svg
const verify = bool(core.getInput("verify")) const verify = bool(core.getInput("verify"))
console.log(`Verify SVG | ${verify}`) console.log(`Verify SVG | ${verify}`)
if (verify) { if (verify) {
const [libxmljs] = [await import("libxmljs")].map(m => (m && m.default) ? m.default : m) const [libxmljs] = [await import("libxmljs")].map(m => (m && m.default) ? m.default : m)
const parsed = libxmljs.parseXml(rendered) const parsed = libxmljs.parseXml(rendered)
if (parsed.errors.length) if (parsed.errors.length)
throw new Error(`Malformed SVG : \n${parsed.errors.join("\n")}`) throw new Error(`Malformed SVG : \n${parsed.errors.join("\n")}`)
console.log(`SVG valid | yes`) console.log(`SVG valid | yes`)
} }
//Commit to repository //Commit to repository
const dryrun = bool(core.getInput("dryrun")) const dryrun = bool(core.getInput("dryrun"))
if (dryrun) if (dryrun)
console.log(`Dry-run | complete`) console.log(`Dry-run | complete`)
else { else {
//Repository //Repository and branch
console.log(`Repository | ${github.context.repo.owner}/${github.context.repo.repo}`) const branch = github.context.ref.replace(/^refs[/]heads[/]/, "")
console.log(`Repository | ${github.context.repo.owner}/${github.context.repo.repo}`)
console.log(`Branch | ${branch}`)
//Committer token //Committer token
const token = core.getInput("committer_token") || core.getInput("token") const token = core.getInput("committer_token") || core.getInput("token")
console.log(`Committer token | ${token ? "provided" : "missing"}`) console.log(`Committer token | ${token ? "provided" : "missing"}`)
if (!token) if (!token)
throw new Error("You must provide a valid GitHub token to commit your metrics") throw new Error("You must provide a valid GitHub token to commit your metrics")
const rest = github.getOctokit(token) const rest = github.getOctokit(token)
console.log(`Committer REST API | ok`) console.log(`Committer REST API | ok`)
try { try {
console.log(`Committer | ${(await rest.users.getAuthenticated()).data.login}`) console.log(`Committer | ${(await rest.users.getAuthenticated()).data.login}`)
} }
catch { catch {
console.log(`Committer | (unknown)`) console.log(`Committer | (unknown)`)
} }
//Retrieve previous render SHA to be able to update file content through API //Retrieve previous render SHA to be able to update file content through API
let sha = null let sha = null
console.log(github.context.repo)
try { try {
const {data} = await rest.repos.getContent({...github.context.repo, path:filename}) const {repository:{object:{oid}}} = await graphql(`
sha = data.sha query Sha {
repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") {
object(expression: "${branch}:${filename}") { ... on Blob { oid } }
}
}
`
)
sha = oid
} catch (error) { console.debug(error) } } catch (error) { console.debug(error) }
console.log(`Previous render sha | ${sha || "none"}`) console.log(`Previous render sha | ${sha || "none"}`)
//Update file content through API //Update file content through API
await rest.repos.createOrUpdateFileContents({ await rest.repos.createOrUpdateFileContents({
...github.context.repo, path:filename, message:`Update ${filename} - [Skip GitHub Action]`, ...github.context.repo, path:filename, message:`Update ${filename} - [Skip GitHub Action]`,
content:Buffer.from(rendered).toString("base64"), content:Buffer.from(rendered).toString("base64"),
...(sha ? {sha} : {}) ...(sha ? {sha} : {})
}) })
console.log(`Commit to repo | ok`) console.log(`Commit to repo | ok`)
} }
//Success //Success
@@ -211,7 +228,7 @@
} catch (error) { } catch (error) {
console.error(error) console.error(error)
if (!bool(core.getInput("debug"))) if (!bool(core.getInput("debug")))
console.debug("An error occured, logging debug message :", ...debugged) console.log("An error occured, logging debug message :", ...debugged)
core.setFailed(error.message) core.setFailed(error.message)
process.exit(1) process.exit(1)
} }

14
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "metrics", "name": "metrics",
"version": "2.7.0-beta", "version": "2.8.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -352,9 +352,9 @@
} }
}, },
"@types/node": { "@types/node": {
"version": "14.14.12", "version": "14.14.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.12.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz",
"integrity": "sha512-ASH8OPHMNlkdjrEdmoILmzFfsJICvhBsFfAum4aKZ/9U4B6M6tTmTPh+f3ttWdD74CEGV5XvXWkbyfSdXaTd7g==" "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="
}, },
"@types/q": { "@types/q": {
"version": "1.5.4", "version": "1.5.4",
@@ -1349,9 +1349,9 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.13.0", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
}, },
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "metrics", "name": "metrics",
"version": "2.8.0-beta", "version": "2.8.0",
"description": "Generate an user's GitHub metrics as SVG image format to embed somewhere else", "description": "Generate an user's GitHub metrics as SVG image format to embed somewhere else",
"main": "index.mjs", "main": "index.mjs",
"scripts": { "scripts": {

View File

@@ -43,6 +43,8 @@
"base.metadata":"Metadata", "base.metadata":"Metadata",
}, },
options:{ options:{
"languages.ignored":"",
"languages.skipped":"",
"pagespeed.detailed":false, "pagespeed.detailed":false,
"habits.from":100, "habits.from":100,
"music.playlist":"", "music.playlist":"",
@@ -103,8 +105,9 @@
action() { action() {
return [ return [
`# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference`, `# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference`,
`name: GitHub metrics as SVG image`, `name: GitHub metrics`,
`on:`, `on:`,
` # Schedule updates`,
` schedule: [{cron: "0 * * * *"}]`, ` schedule: [{cron: "0 * * * *"}]`,
` push: {branches: "master"}`, ` push: {branches: "master"}`,
`jobs:`, `jobs:`,
@@ -113,16 +116,19 @@
` steps:`, ` steps:`,
` - uses: lowlighter/metrics@latest`, ` - uses: lowlighter/metrics@latest`,
` with:`, ` with:`,
` # Setup a personal token in your secrets.`, ` # You'll need to setup a personal token in your secrets.`,
` token: ${"$"}{{ secrets.METRICS_TOKEN }}`, ` token: ${"$"}{{ secrets.METRICS_TOKEN }}`,
` # You can also setup a bot token for commits`, ` # GITHUB_TOKEN is a special auto-generated token used for commits`,
` # committer_token: ${"$"}{{ secrets.METRICS_BOT_TOKEN }}`, ` committer_token: ${"$"}{{ secrets.GITHUB_TOKEN }}`,
``, ``,
` # Options`, ` # Options`,
` user: ${this.user }`, ` user: ${this.user }`,
` template: ${this.templates.selected}`, ` template: ${this.templates.selected}`,
` base: ${Object.keys(this.plugins.enabled.base).join(", ") }`, ` base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ")||'""'}`,
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base")&&(value)).map(([key]) => ` plugin_${key}: yes`) ...[
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base")&&(value)).map(([key]) => ` plugin_${key}: yes`),
...Object.entries(this.plugins.options).filter(([key, value]) => value).filter(([key, value]) => this.plugins.enabled[key.split(".")[0]]).map(([key, value]) => ` plugin_${key.replace(/[.]/, "_")}: ${typeof value === "boolean" ? {true:"yes", false:"no"}[value] : value}`)
].sort(),
].join("\n") ].join("\n")
} }
}, },

View File

@@ -51,7 +51,7 @@
</label> </label>
</div> </div>
<i>*Additional plugins may be available when used as GitHub Action</i> <i>*Additional plugins may be available when used as GitHub Action</i>
<template v-if="(plugins.enabled.music)||(plugins.enabled.pagespeed)||(plugins.enabled.habits)||(plugins.enabled.posts)||(plugins.enabled.isocalendar)||(plugins.enabled.projects)||(plugins.enabled.topics)"> <template v-if="(plugins.enabled.music)||(plugins.enabled.pagespeed)||(plugins.enabled.languages)||(plugins.enabled.habits)||(plugins.enabled.posts)||(plugins.enabled.isocalendar)||(plugins.enabled.projects)||(plugins.enabled.topics)">
<h3>2.3 Configure additional plugins</h3> <h3>2.3 Configure additional plugins</h3>
<div class="options"> <div class="options">
<div class="options-group" v-if="plugins.enabled.music"> <div class="options-group" v-if="plugins.enabled.music">
@@ -72,6 +72,17 @@
<input type="checkbox" v-model="plugins.options['pagespeed.detailed']" @change="load"> <input type="checkbox" v-model="plugins.options['pagespeed.detailed']" @change="load">
</label> </label>
</div> </div>
<div class="options-group" v-if="plugins.enabled.languages">
<h4>{{ plugins.descriptions.languages }}</h4>
<label>
Ignored languages (comma separated)
<input type="text" v-model="plugins.options['languages.ignored']" @change="load">
</label>
<label>
Skipped repositories (comma separated)
<input type="text" v-model="plugins.options['languages.skipped']" @change="load">
</label>
</div>
<div class="options-group" v-if="plugins.enabled.habits"> <div class="options-group" v-if="plugins.enabled.habits">
<h4>{{ plugins.descriptions.habits }}</h4> <h4>{{ plugins.descriptions.habits }}</h4>
<label> <label>

View File

@@ -1,18 +1,36 @@
//Setup //Setup
export default async function ({data, q}, {enabled = false} = {}) { export default async function ({login, data, q}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
if ((!enabled)||(!q.languages)) if ((!enabled)||(!q.languages))
return null return null
//Parameters override
let {"languages.ignored":ignored = "", "languages.skipped":skipped = ""} = q
//Ignored languages
ignored = decodeURIComponent(ignored).split(",").map(x => x.trim().toLocaleLowerCase()).filter(x => x)
//Skipped repositories
skipped = decodeURIComponent(skipped).split(",").map(x => x.trim().toLocaleLowerCase()).filter(x => x)
//Iterate through user's repositories and retrieve languages data //Iterate through user's repositories and retrieve languages data
const languages = {colors:{}, total:0, stats:{}} const languages = {colors:{}, total:0, stats:{}}
for (const repository of data.user.repositories.nodes) { for (const repository of data.user.repositories.nodes) {
for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) { //Skip repository if asked
languages.stats[name] = (languages.stats[name] ?? 0) + size if (skipped.includes(repository.name.toLocaleLowerCase())) {
languages.colors[name] = color ?? "#ededed" console.debug(`metrics/compute/${login}/plugins > languages > skipped repository ${repository.name}`)
languages.total += size continue
} }
//Process repository languages
for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) {
//Ignore language if asked
if (ignored.includes(name.toLocaleLowerCase())) {
console.debug(`metrics/compute/${login}/plugins > languages > ignored language ${name}`)
continue
}
//Update language stats
languages.stats[name] = (languages.stats[name] ?? 0) + size
languages.colors[name] = color ?? "#ededed"
languages.total += size
}
} }
//Compute languages stats //Compute languages stats
Object.keys(languages.stats).map(name => languages.stats[name] /= languages.total) Object.keys(languages.stats).map(name => languages.stats[name] /= languages.total)

View File

@@ -129,7 +129,6 @@
`${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`, `${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`,
{headers:{"Content-Type":"application/x-www-form-urlencoded"}}, {headers:{"Content-Type":"application/x-www-form-urlencoded"}},
) )
console.log(access)
console.debug(`metrics/compute/${login}/plugins > music > got new access token`) console.debug(`metrics/compute/${login}/plugins > music > got new access token`)
//Retriev tracks //Retriev tracks
tracks = (await imports.axios(`https://api.spotify.com/v1/me/player/recently-played?limit=${limit}&after=${timestamp}`, {headers:{ tracks = (await imports.axios(`https://api.spotify.com/v1/me/player/recently-played?limit=${limit}&after=${timestamp}`, {headers:{