diff --git a/.github/scripts/build.mjs b/.github/scripts/build.mjs index 2883bbea..1d89cfbe 100644 --- a/.github/scripts/build.mjs +++ b/.github/scripts/build.mjs @@ -1,7 +1,7 @@ //Imports -import fs from "fs/promises" import ejs from "ejs" import fss from "fs" +import fs from "fs/promises" import yaml from "js-yaml" import paths from "path" import sgit from "simple-git" @@ -26,13 +26,13 @@ const __test_secrets = paths.join(paths.join(__metrics, "tests/secrets.json")) //Git setup const git = sgit(__metrics) const staged = new Set() -const secrets = Object.assign(JSON.parse(`${await fs.readFile(__test_secrets)}`), { $regex: /\$\{\{\s*secrets\.(?\w+)\s*\}\}/ }) -const { plugins, templates } = await metadata({ log: false, diff: true }) +const secrets = Object.assign(JSON.parse(`${await fs.readFile(__test_secrets)}`), {$regex: /\$\{\{\s*secrets\.(?\w+)\s*\}\}/}) +const {plugins, templates} = await metadata({log: false, diff: true}) const workflow = [] //Plugins for (const id of Object.keys(plugins)) { - const { examples, options, readme, tests, header, community } = await plugin(id) + const {examples, options, readme, tests, header, community} = await plugin(id) //Readme console.log(`Generating source/plugins/${community ? "community/" : ""}${id}/README.md`) @@ -40,7 +40,7 @@ for (const id of Object.keys(plugins)) { readme.path, readme.content .replace(/()[\s\S]*()/g, `$1\n${header}\n$2`) - .replace(/()[\s\S]*()/g, `$1\n${examples.map(({ test, prod, ...step }) => ["```yaml", yaml.dump(step, { quotingType: '"', noCompatMode: true }), "```"].join("\n")).join("\n")}\n$2`) + .replace(/()[\s\S]*()/g, `$1\n${examples.map(({test, prod, ...step}) => ["```yaml", yaml.dump(step, {quotingType: '"', noCompatMode: true}), "```"].join("\n")).join("\n")}\n$2`) .replace(/()[\s\S]*()/g, `$1\n${options}\n$2`), ) staged.add(readme.path) @@ -54,7 +54,7 @@ for (const id of Object.keys(plugins)) { //Templates for (const id of Object.keys(templates)) { - const { examples, readme, tests, header } = await template(id) + const {examples, readme, tests, header} = await template(id) //Readme console.log(`Generating source/templates/${id}/README.md`) @@ -62,14 +62,14 @@ for (const id of Object.keys(templates)) { readme.path, readme.content .replace(/()[\s\S]*()/g, `$1\n${header}\n$2`) - .replace(/()[\s\S]*()/g, `$1\n${examples.map(({ test, prod, ...step }) => ["```yaml", yaml.dump(step, { quotingType: '"', noCompatMode: true }), "```"].join("\n")).join("\n")}\n$2`), + .replace(/()[\s\S]*()/g, `$1\n${examples.map(({test, prod, ...step}) => ["```yaml", yaml.dump(step, {quotingType: '"', noCompatMode: true}), "```"].join("\n")).join("\n")}\n$2`), ) staged.add(readme.path) //Tests console.log(`Generating tests/templates/${id}.yml`) workflow.push(...examples.map(example => testcase(templates[id].name, "prod", example)).filter(t => t)) - await fs.writeFile(tests.path, yaml.dump(examples.map(example => testcase(templates[id].name, "test", example)).filter(t => t), { quotingType: '"', noCompatMode: true })) + await fs.writeFile(tests.path, yaml.dump(examples.map(example => testcase(templates[id].name, "test", example)).filter(t => t), {quotingType: '"', noCompatMode: true})) staged.add(tests.path) } @@ -77,21 +77,21 @@ for (const id of Object.keys(templates)) { for (const step of ["config", "documentation"]) { switch (step) { case "config": - await update({ source: paths.join(__action, "action.yml"), output: "action.yml" }) - await update({ source: paths.join(__web, "settings.example.json"), output: "settings.example.json" }) + await update({source: paths.join(__action, "action.yml"), output: "action.yml"}) + await update({source: paths.join(__web, "settings.example.json"), output: "settings.example.json"}) break case "documentation": - await update({ source: paths.join(__documentation, "README.md"), output: "README.md", options: { root: __readme } }) - await update({ source: paths.join(__documentation, "plugins.md"), output: "source/plugins/README.md" }) - await update({ source: paths.join(__documentation, "plugins.community.md"), output: "source/plugins/community/README.md" }) - await update({ source: paths.join(__documentation, "templates.md"), output: "source/templates/README.md" }) - await update({ source: paths.join(__documentation, "compatibility.md"), output: ".github/readme/partials/documentation/compatibility.md" }) + await update({source: paths.join(__documentation, "README.md"), output: "README.md", options: {root: __readme}}) + await update({source: paths.join(__documentation, "plugins.md"), output: "source/plugins/README.md"}) + await update({source: paths.join(__documentation, "plugins.community.md"), output: "source/plugins/community/README.md"}) + await update({source: paths.join(__documentation, "templates.md"), output: "source/templates/README.md"}) + await update({source: paths.join(__documentation, "compatibility.md"), output: ".github/readme/partials/documentation/compatibility.md"}) break } } //Example workflows -await update({ source: paths.join(__metrics, ".github/scripts/files/examples.yml"), output: ".github/workflows/examples.yml", context: { steps: yaml.dump(workflow, { quotingType: '"', noCompatMode: true }) } }) +await update({source: paths.join(__metrics, ".github/scripts/files/examples.yml"), output: ".github/workflows/examples.yml", context: {steps: yaml.dump(workflow, {quotingType: '"', noCompatMode: true})}}) //Commit and push if (mode === "publish") { @@ -109,10 +109,10 @@ console.log("Success!") //================================================================================== //Update generated files -async function update({ source, output, context = {}, options = {} }) { +async function update({source, output, context = {}, options = {}}) { console.log(`Generating ${output}`) - const { plugins, templates, packaged, descriptor } = await metadata({ log: false }) - const content = await ejs.renderFile(source, { plugins, templates, packaged, descriptor, ...context }, { async: true, ...options }) + const {plugins, templates, packaged, descriptor} = await metadata({log: false}) + const content = await ejs.renderFile(source, {plugins, templates, packaged, descriptor, ...context}, {async: true, ...options}) const file = paths.join(__metrics, output) await fs.writeFile(file, content) staged.add(file) @@ -160,15 +160,15 @@ async function template(id) { //Testcase generator function testcase(name, env, args) { - const { prod = {}, test = {}, ...step } = JSON.parse(JSON.stringify(args)) - const context = { prod, test }[env] ?? {} - const { with: overrides } = context + const {prod = {}, test = {}, ...step} = JSON.parse(JSON.stringify(args)) + const context = {prod, test}[env] ?? {} + const {with: overrides} = context if (context.skip) return null Object.assign(step.with, context.with ?? {}) delete context.with - const result = { ...step, ...context, name: `${name} - ${step.name ?? "(unnamed)"}` } + const result = {...step, ...context, name: `${name} - ${step.name ?? "(unnamed)"}`} for (const [k, v] of Object.entries(result.with)) { if ((env === "test") && (secrets.$regex.test(v))) result.with[k] = v.replace(secrets.$regex, secrets[v.match(secrets.$regex)?.groups?.secret]) @@ -177,21 +177,21 @@ function testcase(name, env, args) { if (env === "prod") { result.if = "${{ success() || failure() }}" result.uses = "lowlighter/metrics@master" - Object.assign(result.with, { output_action: "none", delay: 120 }) + Object.assign(result.with, {output_action: "none", delay: 120}) - for (const { property, value } of [{ property: "user", value: "lowlighter" }, { property: "plugins_errors_fatal", value: "yes" }]) { + for (const {property, value} of [{property: "user", value: "lowlighter"}, {property: "plugins_errors_fatal", value: "yes"}]) { if (!(property in result.with)) result.with[property] = value } if ((overrides?.output_action) && (overrides?.committer_branch === "examples")) - Object.assign(result.with, { output_action: overrides.output_action, committer_branch: "examples" }) + Object.assign(result.with, {output_action: overrides.output_action, committer_branch: "examples"}) } if (env === "test") { if (!result.with.base) delete result.with.base delete result.with.filename - Object.assign(result.with, { use_mocked_data: "yes", verify: "yes" }) + Object.assign(result.with, {use_mocked_data: "yes", verify: "yes"}) } return result diff --git a/.github/scripts/markdown_example.mjs b/.github/scripts/markdown_example.mjs index 6fbcde92..2d4f6444 100644 --- a/.github/scripts/markdown_example.mjs +++ b/.github/scripts/markdown_example.mjs @@ -11,11 +11,11 @@ const browser = await puppeteer.launch({ const page = await browser.newPage() //Select markdown example and take screenshoot -await page.setViewport({ width: 600, height: 600 }) +await page.setViewport({width: 600, height: 600}) await page.goto("https://github.com/lowlighter/metrics/blob/examples/metrics.markdown.md") const clip = await page.evaluate(() => { - const { x, y, width, height } = document.querySelector("#readme").getBoundingClientRect() - return { x, y, width, height } + const {x, y, width, height} = document.querySelector("#readme").getBoundingClientRect() + return {x, y, width, height} }) -await page.screenshot({ type: "png", path: "/tmp/metrics.markdown.png", clip, omitBackground: true }) +await page.screenshot({type: "png", path: "/tmp/metrics.markdown.png", clip, omitBackground: true}) await browser.close() diff --git a/.github/scripts/presets_examples.mjs b/.github/scripts/presets_examples.mjs index 22ee0229..69d1d954 100644 --- a/.github/scripts/presets_examples.mjs +++ b/.github/scripts/presets_examples.mjs @@ -1,6 +1,6 @@ //Imports -import fs from "fs/promises" import processes from "child_process" +import fs from "fs/promises" import yaml from "js-yaml" import fetch from "node-fetch" import paths from "path" @@ -16,7 +16,7 @@ const __metrics = paths.join(paths.dirname(url.fileURLToPath(import.meta.url)), const __presets = paths.join(__metrics, ".presets") if ((!await fs.access(__presets).then(_ => true).catch(_ => false)) || (!(await fs.lstat(__presets)).isDirectory())) - await sgit().clone(`https://github-actions[bot]:${process.env.GITHUB_TOKEN}@github.com/lowlighter/metrics`, __presets, { "--branch": "presets", "--single-branch": true }) + await sgit().clone(`https://github-actions[bot]:${process.env.GITHUB_TOKEN}@github.com/lowlighter/metrics`, __presets, {"--branch": "presets", "--single-branch": true}) const git = sgit(__presets) await git.pull() const staged = new Set() @@ -27,7 +27,7 @@ web.run = async vars => await fetch(`http://localhost:3000/lowlighter?${new url. web.start = async () => new Promise(solve => { let stdout = "" - web.instance = processes.spawn("node", ["source/app/web/index.mjs"], { env: { ...process.env, SANDBOX: true } }) + web.instance = processes.spawn("node", ["source/app/web/index.mjs"], {env: {...process.env, SANDBOX: true}}) web.instance.stdout.on("data", data => (stdout += data, /Server ready !/.test(stdout) ? solve() : null)) web.instance.stderr.on("data", data => console.error(`${data}`)) }) @@ -44,13 +44,13 @@ for (const path of await fs.readdir(__presets)) { //Example console.log(`generating: ${preset}/example.svg`) - const svg = await web.run({ config_presets: `@${preset}`, plugins_errors_fatal: true }) + const svg = await web.run({config_presets: `@${preset}`, plugins_errors_fatal: true}) await fs.writeFile(paths.join(__presets, path, "example.svg"), svg) staged.add(paths.join(__presets, path, "example.svg")) //Readme console.log(`generating: ${preset}/README.svg`) - const { name, description } = await yaml.load(await fs.readFile(paths.join(__presets, preset, "preset.yml"))) + const {name, description} = await yaml.load(await fs.readFile(paths.join(__presets, preset, "preset.yml"))) await fs.writeFile( paths.join(__presets, path, "README.md"), ` diff --git a/.github/scripts/preview.mjs b/.github/scripts/preview.mjs index af46ed0c..dc3036ec 100644 --- a/.github/scripts/preview.mjs +++ b/.github/scripts/preview.mjs @@ -19,22 +19,22 @@ const __preview_templates_ = paths.join(__preview, ".templates_") const __preview_about = paths.join(__preview, "about/.statics") //Extract from web server -const { conf, Templates } = await setup({ log: false }) -const templates = Object.entries(Templates).map(([name]) => ({ name, enabled: true })) +const {conf, Templates} = await setup({log: false}) +const templates = Object.entries(Templates).map(([name]) => ({name, enabled: true})) const metadata = Object.fromEntries( Object.entries(conf.metadata.plugins) .map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "category", "web", "supports", "scopes"].includes(key)))]) - .map(([key, value]) => [key, key === "core" ? { ...value, web: Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value])) } : value]), + .map(([key, value]) => [key, key === "core" ? {...value, web: Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value]))} : value]), ) -const enabled = Object.entries(metadata).filter(([_name, { category }]) => category !== "core").map(([name]) => ({ name, category: metadata[name]?.category ?? "community", enabled: true })) +const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category: metadata[name]?.category ?? "community", enabled: true})) //Directories -await fs.mkdir(__preview, { recursive: true }) -await fs.mkdir(__preview_js, { recursive: true }) -await fs.mkdir(__preview_css, { recursive: true }) -await fs.mkdir(__preview_templates, { recursive: true }) -await fs.mkdir(__preview_templates_, { recursive: true }) -await fs.mkdir(__preview_about, { recursive: true }) +await fs.mkdir(__preview, {recursive: true}) +await fs.mkdir(__preview_js, {recursive: true}) +await fs.mkdir(__preview_css, {recursive: true}) +await fs.mkdir(__preview_templates, {recursive: true}) +await fs.mkdir(__preview_templates_, {recursive: true}) +await fs.mkdir(__preview_about, {recursive: true}) //Web fs.copyFile(paths.join(__web, "index.html"), paths.join(__preview, "index.html")) @@ -49,7 +49,7 @@ for (const template in conf.templates) { fs.writeFile(paths.join(__preview_templates_, template), JSON.stringify(conf.templates[template])) const __partials = paths.join(__templates, template, "partials") const __preview_partials = paths.join(__preview_templates, template, "partials") - await fs.mkdir(__preview_partials, { recursive: true }) + await fs.mkdir(__preview_partials, {recursive: true}) for (const file of await fs.readdir(__partials)) fs.copyFile(paths.join(__partials, file), paths.join(__preview_partials, file)) } @@ -73,7 +73,7 @@ fs.copyFile(paths.join(__node_modules, "prismjs/components/prism-markdown.min.js fs.copyFile(paths.join(__node_modules, "clipboard/dist/clipboard.min.js"), paths.join(__preview_js, "clipboard.min.js")) //Meta fs.writeFile(paths.join(__preview, ".version"), JSON.stringify(`${conf.package.version}-preview`)) -fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({ by: "metrics", link: "https://github.com/lowlighter/metrics" })) +fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({by: "metrics", link: "https://github.com/lowlighter/metrics"})) //About fs.copyFile(paths.join(__web, "about", "index.html"), paths.join(__preview, "about", "index.html")) for (const file of await fs.readdir(__web_about)) { diff --git a/.github/scripts/release.mjs b/.github/scripts/release.mjs index 236212f4..90a5eddd 100644 --- a/.github/scripts/release.mjs +++ b/.github/scripts/release.mjs @@ -26,7 +26,7 @@ if (!version) console.log(`Version: ${version}`) //Load related pr -const { data: { items: prs } } = await rest.search.issuesAndPullRequests({ +const {data: {items: prs}} = await rest.search.issuesAndPullRequests({ q: `repo:${repository.owner}/${repository.name} is:pr is:merged author:${maintainer} assignee:${maintainer} Release ${version} in:title`, }) @@ -40,9 +40,9 @@ console.log(`Using pr#${patchnote.number}: ${patchnote.title}`) //Check whether release already exists try { - const { data: { id } } = await rest.repos.getReleaseByTag({ owner: repository.owner, repo: repository.name, tag: version }) + const {data: {id}} = await rest.repos.getReleaseByTag({owner: repository.owner, repo: repository.name, tag: version}) console.log(`Release ${version} already exists (#${id}), will replace it`) - await rest.repos.deleteRelease({ owner: repository.owner, repo: repository.name, release_id: id }) + await rest.repos.deleteRelease({owner: repository.owner, repo: repository.name, release_id: id}) console.log(`Deleting tag ${version}`) await git.push(["--delete", "origin", version]) await new Promise(solve => setTimeout(solve, 15 * 1000)) @@ -52,5 +52,5 @@ catch { } //Publish release -await rest.repos.createRelease({ owner: repository.owner, repo: repository.name, tag_name: version, name: `Version ${version.replace(/^v/g, "")}`, body: patchnote.body }) +await rest.repos.createRelease({owner: repository.owner, repo: repository.name, tag_name: version, name: `Version ${version.replace(/^v/g, "")}`, body: patchnote.body}) console.log(`Successfully published`) diff --git a/source/app/action/index.mjs b/source/app/action/index.mjs index cb979599..8951d7c9 100644 --- a/source/app/action/index.mjs +++ b/source/app/action/index.mjs @@ -2,8 +2,8 @@ import core from "@actions/core" import github from "@actions/github" import octokit from "@octokit/graphql" -import fs from "fs/promises" import processes from "child_process" +import fs from "fs/promises" import paths from "path" import sgit from "simple-git" import mocks from "../../../tests/mocks/index.mjs" @@ -22,7 +22,8 @@ const debugged = [] const preset = {} //Info logger -const info = (left, right, {token = false} = {}) => console.log(`${`${left}`.padEnd(63 + 9 * (/0m$/.test(left)))} │ ${ +const info = (left, right, {token = false} = {}) => + console.log(`${`${left}`.padEnd(63 + 9 * (/0m$/.test(left)))} │ ${ Array.isArray(right) ? right.join(", ") || "(none)" : right === undefined @@ -37,7 +38,7 @@ info.section = (left = "", right = " ") => info(`\x1b[36m${left}\x1b[0m`, right) info.group = ({metadata, name, inputs}) => { info.section(metadata.plugins[name]?.name?.match(/(?
[\w\s]+)/i)?.groups?.section?.trim(), " ") for (const [input, value] of Object.entries(inputs)) - info(metadata.plugins[name]?.inputs[input]?.description?.split("\n")[0] ?? metadata.plugins[name]?.inputs[input]?.description ?? input, `${input in preset ? "*" : ""}${value}`, {token:metadata.plugins[name]?.inputs[input]?.type === "token"}) + info(metadata.plugins[name]?.inputs[input]?.description?.split("\n")[0] ?? metadata.plugins[name]?.inputs[input]?.description ?? input, `${input in preset ? "*" : ""}${value}`, {token: metadata.plugins[name]?.inputs[input]?.type === "token"}) } info.break = () => console.log("─".repeat(88)) @@ -70,14 +71,12 @@ async function retry(func, {retries = 1, delay = 0} = {}) { //Process exit function quit(reason) { - const code = {success:0, skipped:0, failed:1}[reason] ?? 0 + const code = {success: 0, skipped: 0, failed: 1}[reason] ?? 0 process.exit(code) -} - -//===================================================================================================== - +} //===================================================================================================== //Runner -(async function() { + +;(async function() { try { //Initialization info.break() @@ -96,9 +95,9 @@ function quit(reason) { } //Load configuration - const {conf, Plugins, Templates} = await setup({log:false, community:{templates:core.getInput("setup_community_templates")}}) + const {conf, Plugins, Templates} = await setup({log: false, community: {templates: core.getInput("setup_community_templates")}}) const {metadata} = conf - conf.settings.extras = {default:true} + conf.settings.extras = {default: true} info("Setup", "complete") info("Version", conf.package.version) @@ -111,42 +110,42 @@ function quit(reason) { } //Core inputs - Object.assign(preset, await presets(core.getInput("config_presets"), {log:false, core})) + Object.assign(preset, await presets(core.getInput("config_presets"), {log: false, core})) const { - user:_user, - repo:_repo, + user: _user, + repo: _repo, token, template, query, - "setup.community.templates":_templates, - filename:_filename, + "setup.community.templates": _templates, + filename: _filename, optimize, verify, - "markdown.cache":_markdown_cache, + "markdown.cache": _markdown_cache, debug, - "debug.flags":dflags, - "debug.print":dprint, - "use.mocked.data":mocked, + "debug.flags": dflags, + "debug.print": dprint, + "use.mocked.data": mocked, dryrun, - "plugins.errors.fatal":die, - "committer.token":_token, - "committer.branch":_branch, - "committer.message":_message, - "committer.gist":_gist, - "use.prebuilt.image":_image, + "plugins.errors.fatal": die, + "committer.token": _token, + "committer.branch": _branch, + "committer.message": _message, + "committer.gist": _gist, + "use.prebuilt.image": _image, retries, - "retries.delay":retries_delay, - "retries.output.action":retries_output_action, - "retries.delay.output.action":retries_delay_output_action, - "output.action":_action, - "output.condition":_output_condition, + "retries.delay": retries_delay, + "retries.output.action": retries_output_action, + "retries.delay.output.action": retries_delay_output_action, + "output.action": _action, + "output.condition": _output_condition, delay, - "notice.release":_notice_releases, + "notice.release": _notice_releases, ...config } = metadata.plugins.core.inputs.action({core, preset}) - const q = {...query, ...(_repo ? {repo:_repo} : null), template} + const q = {...query, ...(_repo ? {repo: _repo} : null), template} const _output = ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(config["config.output"]) ? config["config.output"] : metadata.templates[template]?.formats?.[0] ?? null - const filename = _filename.replace(/[*]/g, {jpeg:"jpg", markdown:"md", "markdown-pdf":"pdf", insights:"html"}[_output] ?? _output ?? "*") + const filename = _filename.replace(/[*]/g, {jpeg: "jpg", markdown: "md", "markdown-pdf": "pdf", insights: "html"}[_output] ?? _output ?? "*") //Docker image if (_image) @@ -162,7 +161,7 @@ function quit(reason) { q["debug.flags"] = dflags.join(" ") //Token for data gathering - info("GitHub token", token, {token:true}) + info("GitHub token", token, {token: true}) //A GitHub token should start with "gh" along an additional letter for type //See https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats info("GitHub token format", /^gh[pousr]_/.test(token) ? "correct" : "(old or invalid)") @@ -171,7 +170,7 @@ function quit(reason) { conf.settings.token = token const api = {} const resources = {} - api.graphql = octokit.graphql.defaults({headers:{authorization:`token ${token}`}}) + api.graphql = octokit.graphql.defaults({headers: {authorization: `token ${token}`}}) info("Github GraphQL API", "ok") const octoraw = github.getOctokit(token) api.rest = octoraw.rest @@ -185,12 +184,12 @@ function quit(reason) { //Test token validity and requests count else if (!/^NOT_NEEDED$/.test(token)) { //Check rate limit - const {data} = await api.rest.rateLimit.get().catch(() => ({data:{resources:{}}})) + const {data} = await api.rest.rateLimit.get().catch(() => ({data: {resources: {}}})) Object.assign(resources, data.resources) info("API requests (REST)", resources.core ? `${resources.core.remaining}/${resources.core.limit}` : "(unknown)") info("API requests (GraphQL)", resources.graphql ? `${resources.graphql.remaining}/${resources.graphql.limit}` : "(unknown)") info("API requests (search)", resources.search ? `${resources.search.remaining}/${resources.search.limit}` : "(unknown)") - if ((!resources.core.remaining)||(!resources.graphql.remaining)) { + if ((!resources.core.remaining) || (!resources.graphql.remaining)) { console.warn("::warning::It seems you have reached your API requests limit. Please retry later.") info.break() console.log("Nothing can be done currently, thanks for using metrics!") @@ -217,7 +216,7 @@ function quit(reason) { //Check for new versions if (_notice_releases) { - const {data:[{tag_name:tag}]} = await rest.repos.listReleases({owner:"lowlighter", repo:"metrics"}) + const {data: [{tag_name: tag}]} = await rest.repos.listReleases({owner: "lowlighter", repo: "metrics"}) const current = Number(conf.package.version.match(/(\d+\.\d+)/)?.[1] ?? 0) const latest = Number(tag.match(/(\d+\.\d+)/)?.[1] ?? 0) if (latest > current) @@ -254,7 +253,7 @@ function quit(reason) { committer.merge = _action.match(/^pull-request-(?merge|squash|rebase)$/)?.groups?.method ?? null committer.branch = _branch || github.context.ref.replace(/^refs[/]heads[/]/, "") committer.head = committer.pr ? `metrics-run-${github.context.runId}` : committer.branch - info("Committer token", committer.token, {token:true}) + info("Committer token", committer.token, {token: true}) if (!committer.token) throw new Error("You must provide a valid GitHub token to commit your metrics") info("Committer branch", committer.branch) @@ -273,25 +272,25 @@ function quit(reason) { } //Create head branch if needed try { - await committer.rest.git.getRef({...github.context.repo, ref:`heads/${committer.head}`}) + await committer.rest.git.getRef({...github.context.repo, ref: `heads/${committer.head}`}) info("Committer head branch status", "ok") } catch (error) { console.debug(error) if (/not found/i.test(`${error}`)) { - const {data:{object:{sha}}} = await committer.rest.git.getRef({...github.context.repo, ref:`heads/${committer.branch}`}) + const {data: {object: {sha}}} = await committer.rest.git.getRef({...github.context.repo, ref: `heads/${committer.branch}`}) info("Committer branch current sha", sha) - await committer.rest.git.createRef({...github.context.repo, ref:`refs/heads/${committer.head}`, sha}) + await committer.rest.git.createRef({...github.context.repo, ref: `refs/heads/${committer.head}`, sha}) info("Committer head branch status", "(created)") } - else + else { throw error - + } } //Retrieve previous render SHA to be able to update file content through API committer.sha = null try { - const {repository:{object:{oid}}} = await graphql( + const {repository: {object: {oid}}} = await graphql( ` query Sha { repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") { @@ -299,7 +298,7 @@ function quit(reason) { } } `, - {headers:{authorization:`token ${committer.token}`}}, + {headers: {authorization: `token ${committer.token}`}}, ) committer.sha = oid } @@ -308,8 +307,9 @@ function quit(reason) { } info("Previous render sha", committer.sha ?? "(none)") } - else if (dryrun) + else if (dryrun) { info("Dry-run", true) + } //SVG file conf.settings.optimize = optimize @@ -326,7 +326,7 @@ function quit(reason) { //Core config info.break() - info.group({metadata, name:"core", inputs:config}) + info.group({metadata, name: "core", inputs: config}) info("Plugin errors", die ? "(exit with error)" : "(displayed in generated image)") const convert = _output || null Object.assign(q, config) @@ -337,7 +337,7 @@ function quit(reason) { await new Promise(async (solve, reject) => { let stdout = "" setTimeout(() => reject("Timeout while waiting for Insights webserver"), 5 * 60 * 1000) - const web = await processes.spawn("node", ["/metrics/source/app/web/index.mjs"], {env:{...process.env}}) + const web = await processes.spawn("node", ["/metrics/source/app/web/index.mjs"], {env: {...process.env}}) web.stdout.on("data", data => (console.debug(`web > ${data}`), stdout += data, /Server ready !/.test(stdout) ? solve() : null)) web.stderr.on("data", data => console.debug(`web > ${data}`)) }) @@ -351,9 +351,9 @@ function quit(reason) { //Base content info.break() - const {base:parts, repositories:_repositories, ...base} = metadata.plugins.base.inputs.action({core, preset}) + const {base: parts, repositories: _repositories, ...base} = metadata.plugins.base.inputs.action({core, preset}) conf.settings.repositories = _repositories - info.group({metadata, name:"base", inputs:{repositories:conf.settings.repositories, ...base}}) + info.group({metadata, name: "base", inputs: {repositories: conf.settings.repositories, ...base}}) info("Base sections", parts) base.base = false for (const part of conf.settings.plugins.base.parts) @@ -364,7 +364,7 @@ function quit(reason) { const plugins = {} for (const name of Object.keys(Plugins).filter(key => !["base", "core"].includes(key))) { //Parse inputs - const {[name]:enabled, ...inputs} = metadata.plugins[name].inputs.action({core, preset}) + const {[name]: enabled, ...inputs} = metadata.plugins[name].inputs.action({core, preset}) plugins[name] = {enabled} //Register user inputs if (enabled) { @@ -386,9 +386,9 @@ function quit(reason) { info.break() info.section("Rendering") let rendered = await retry(async () => { - const {rendered} = await metrics({login:user, q}, {graphql, rest, plugins, conf, die, verify, convert}, {Plugins, Templates}) + const {rendered} = await metrics({login: user, q}, {graphql, rest, plugins, conf, die, verify, convert}, {Plugins, Templates}) return rendered - }, {retries, delay:retries_delay}) + }, {retries, delay: retries_delay}) if (!rendered) throw new Error("Could not render metrics") info("Status", "complete") @@ -409,13 +409,13 @@ function quit(reason) { let data = "" await retry(async () => { try { - data = `${Buffer.from((await committer.rest.repos.getContent({...github.context.repo, ref:`heads/${committer.head}`, path:filename})).data.content, "base64")}` + data = `${Buffer.from((await committer.rest.repos.getContent({...github.context.repo, ref: `heads/${committer.head}`, path: filename})).data.content, "base64")}` } catch (error) { if (error.response.status !== 404) throw error } - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) const previous = await svg.hash(data) info("Previous hash", previous) const current = await svg.hash(rendered) @@ -430,7 +430,7 @@ function quit(reason) { if (dryrun) info("Actions to perform", "(none)") else { - await fs.mkdir(paths.dirname(paths.join("/renders", filename)), {recursive:true}) + await fs.mkdir(paths.dirname(paths.join("/renders", filename)), {recursive: true}) await fs.writeFile(paths.join("/renders", filename), Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`)) info(`Save to /metrics_renders/${filename}`, "ok") info("Output action", _action) @@ -454,7 +454,7 @@ function quit(reason) { console.debug(`Processing ${path}`) let sha = null try { - const {repository:{object:{oid}}} = await graphql( + const {repository: {object: {oid}}} = await graphql( ` query Sha { repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") { @@ -462,7 +462,7 @@ function quit(reason) { } } `, - {headers:{authorization:`token ${committer.token}`}}, + {headers: {authorization: `token ${committer.token}`}}, ) sha = oid } @@ -474,14 +474,14 @@ function quit(reason) { ...github.context.repo, path, content, - message:`${committer.message} (cache)`, + message: `${committer.message} (cache)`, ...(sha ? {sha} : {}), - branch:committer.pr ? committer.head : committer.branch, + branch: committer.pr ? committer.head : committer.branch, }) rendered = rendered.replace(match, ``) info(`Saving ${path}`, "ok") } - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) } } @@ -499,10 +499,10 @@ function quit(reason) { //Upload to gist (this is done as user since committer_token may not have gist rights) if (committer.gist) { await retry(async () => { - await rest.gists.update({gist_id:committer.gist, files:{[filename]:{content:rendered}}}) + await rest.gists.update({gist_id: committer.gist, files: {[filename]: {content: rendered}}}) info(`Upload to gist ${committer.gist}`, "ok") committer.commit = false - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) } //Commit metrics @@ -510,14 +510,14 @@ function quit(reason) { await retry(async () => { await committer.rest.repos.createOrUpdateFileContents({ ...github.context.repo, - path:filename, - message:committer.message, - content:Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`).toString("base64"), - branch:committer.pr ? committer.head : committer.branch, - ...(committer.sha ? {sha:committer.sha} : {}), + path: filename, + message: committer.message, + content: Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`).toString("base64"), + branch: committer.pr ? committer.head : committer.branch, + ...(committer.sha ? {sha: committer.sha} : {}), }) info(`Commit to branch ${committer.branch}`, "ok") - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) } //Pull request @@ -526,7 +526,7 @@ function quit(reason) { let number = null await retry(async () => { try { - ({data:{number}} = await committer.rest.pulls.create({...github.context.repo, head:committer.head, base:committer.branch, title:`Auto-generated metrics for run #${github.context.runId}`, body:" ", maintainer_can_modify:true})) + ;({data: {number}} = await committer.rest.pulls.create({...github.context.repo, head: committer.head, base: committer.branch, title: `Auto-generated metrics for run #${github.context.runId}`, body: " ", maintainer_can_modify: true})) info(`Pull request from ${committer.head} to ${committer.branch}`, "(created)") } catch (error) { @@ -535,7 +535,7 @@ function quit(reason) { if (/A pull request already exists/.test(error)) { info(`Pull request from ${committer.head} to ${committer.branch}`, "(already existing)") const q = `repo:${github.context.repo.owner}/${github.context.repo.repo}+type:pr+state:open+Auto-generated metrics for run #${github.context.runId}+in:title` - const prs = (await committer.rest.search.issuesAndPullRequests({q})).data.items.filter(({user:{login}}) => login === "github-actions[bot]") + const prs = (await committer.rest.search.issuesAndPullRequests({q})).data.items.filter(({user: {login}}) => login === "github-actions[bot]") if (prs.length < 1) throw new Error("0 matching prs. Cannot proceed.") if (prs.length > 1) @@ -548,12 +548,12 @@ function quit(reason) { committer.merge = false number = "(none)" } - else + else { throw error - + } } info("Pull request number", number) - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) //Merge pull request if (committer.merge) { info("Merge method", committer.merge) @@ -561,7 +561,7 @@ function quit(reason) { do { const success = await retry(async () => { //Check pull request mergeability (https://octokit.github.io/rest.js/v18#pulls-get) - const {data:{mergeable, mergeable_state:state}} = await committer.rest.pulls.get({...github.context.repo, pull_number:number}) + const {data: {mergeable, mergeable_state: state}} = await committer.rest.pulls.get({...github.context.repo, pull_number: number}) console.debug(`Pull request #${number} mergeable state is "${state}"`) if (mergeable === null) { await wait(15) @@ -570,17 +570,17 @@ function quit(reason) { if (!mergeable) throw new Error(`Pull request #${number} is not mergeable (state is "${state}")`) //Merge pull request - await committer.rest.pulls.merge({...github.context.repo, pull_number:number, merge_method:committer.merge}) + await committer.rest.pulls.merge({...github.context.repo, pull_number: number, merge_method: committer.merge}) info(`Merge #${number} to ${committer.branch}`, "ok") return true - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) if (!success) continue //Delete head branch await retry(async () => { try { await wait(15) - await committer.rest.git.deleteRef({...github.context.repo, ref:`heads/${committer.head}`}) + await committer.rest.git.deleteRef({...github.context.repo, ref: `heads/${committer.head}`}) } catch (error) { console.debug(error) @@ -588,7 +588,7 @@ function quit(reason) { throw error } info(`Branch ${committer.head}`, "(deleted)") - }, {retries:retries_output_action, delay:retries_delay_output_action}) + }, {retries: retries_output_action, delay: retries_delay_output_action}) break } while (--attempts) } @@ -596,14 +596,14 @@ function quit(reason) { } //Consumed API requests - if ((!mocked)&&(!/^NOT_NEEDED$/.test(token))) { + if ((!mocked) && (!/^NOT_NEEDED$/.test(token))) { info.break() info.section("Consumed API requests") info(" * provided that no other app used your quota during execution", "") - const {data:current} = await rest.rateLimit.get().catch(() => ({data:{resources:{}}})) + const {data: current} = await rest.rateLimit.get().catch(() => ({data: {resources: {}}})) for (const type of ["core", "graphql", "search"]) { const used = resources[type].remaining - current.resources[type].remaining - info({core:"REST API", graphql:"GraphQL API", search:"Search API"}[type], (Number.isFinite(used)&&(used >= 0)) ? used : "(unknown)") + info({core: "REST API", graphql: "GraphQL API", search: "Search API"}[type], (Number.isFinite(used) && (used >= 0)) ? used : "(unknown)") } } diff --git a/source/app/metrics/index.mjs b/source/app/metrics/index.mjs index 0debd641..278db261 100644 --- a/source/app/metrics/index.mjs +++ b/source/app/metrics/index.mjs @@ -10,7 +10,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, //Debug login = login.replace(/[\n\r]/g, "") console.debug(`metrics/compute/${login} > start`) - console.debug(util.inspect(q, {depth:Infinity, maxStringLength:256})) + console.debug(util.inspect(q, {depth: Infinity, maxStringLength: 256})) //Load template const template = q.template || conf.settings.templates.default @@ -24,14 +24,14 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, //Initialization const pending = [] const {queries} = conf - const extras = {css:(conf.settings.extras?.css ?? conf.settings.extras?.default) ? q["extras.css"] ?? "" : "", js:(conf.settings.extras?.js ?? conf.settings.extras?.default) ? q["extras.js"] ?? "" : ""} - const data = {q, animated:true, large:false, base:{}, config:{}, errors:[], plugins:{}, computed:{}, extras, postscripts:[]} + const extras = {css: (conf.settings.extras?.css ?? conf.settings.extras?.default) ? q["extras.css"] ?? "" : "", js: (conf.settings.extras?.js ?? conf.settings.extras?.default) ? q["extras.js"] ?? "" : ""} + const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []} const imports = { - plugins:Plugins, - templates:Templates, - metadata:conf.metadata, + plugins: Plugins, + templates: Templates, + metadata: conf.metadata, ...utils, - ...utils.formatters({timeZone:q["config.timezone"]}), + ...utils.formatters({timeZone: q["config.timezone"]}), ...(/markdown/.test(convert) ? { imgb64(url, options) { @@ -60,7 +60,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, //Executing base plugin and compute metrics console.debug(`metrics/compute/${login} > compute`) await Plugins.base({login, q, data, rest, graphql, plugins, queries, pending, imports}, conf) - await computer({login, q}, {conf, data, rest, graphql, plugins, queries, account:data.account, convert, template}, {pending, imports}) + await computer({login, q}, {conf, data, rest, graphql, plugins, queries, account: data.account, convert, template}, {pending, imports}) const promised = await Promise.all(pending) //Check plugins errors @@ -70,7 +70,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, if (die) throw new Error("An error occured during rendering, dying") else - console.debug(util.inspect(errors, {depth:Infinity, maxStringLength:256})) + console.debug(util.inspect(errors, {depth: Infinity, maxStringLength: 256})) } //JSON output @@ -89,7 +89,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, } return value })) - return {rendered, mime:"application/json"} + return {rendered, mime: "application/json"} } //Markdown output @@ -100,12 +100,12 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, try { let template = `${q.markdown}`.replace(/\n/g, "") if (!/^https:/.test(template)) { - const {data:{default_branch:branch, full_name:repo}} = await rest.repos.get({owner:login, repo:q.repo || login}) + const {data: {default_branch: branch, full_name: repo}} = await rest.repos.get({owner: login, repo: q.repo || login}) console.debug(`metrics/compute/${login} > on ${repo} with default branch ${branch}`) template = `https://raw.githubusercontent.com/${repo}/${branch}/${template}` } console.debug(`metrics/compute/${login} > fetching ${template}`) - ;({data:source} = await imports.axios.get(template, {headers:{Accept:"text/plain"}})) + ;({data: source} = await imports.axios.get(template, {headers: {Accept: "text/plain"}})) } catch (error) { console.debug(error) @@ -123,7 +123,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, console.debug(`metrics/compute/${login} > embed called with`) console.debug(q) let {base} = q - q = {..._q, ...Object.fromEntries(Object.keys(Plugins).map(key => [key, false])), ...Object.fromEntries(conf.settings.plugins.base.parts.map(part => [`base.${part}`, false])), template:q.repo ? "repository" : "classic", ...q} + q = {..._q, ...Object.fromEntries(Object.keys(Plugins).map(key => [key, false])), ...Object.fromEntries(conf.settings.plugins.base.parts.map(part => [`base.${part}`, false])), template: q.repo ? "repository" : "classic", ...q} //Translate action syntax to web syntax let parts = [] if (base === true) @@ -140,33 +140,33 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, } q = Object.fromEntries([...Object.entries(q).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value]), ["base", false]]) //Compute rendering - const {rendered} = await metrics({login, q}, {...arguments[1], convert:["svg", "png", "jpeg"].includes(q["config.output"]) ? q["config.output"] : null}, arguments[2]) + const {rendered} = await metrics({login, q}, {...arguments[1], convert: ["svg", "png", "jpeg"].includes(q["config.output"]) ? q["config.output"] : null}, arguments[2]) console.debug(`metrics/compute/${login}/embed > ${name} > success >>>>>>>>>>>>>>>>>>>>>>`) - return `` + return `` } //Rendering template source let rendered = source.replace(/\{\{ (?[\s\S]*?) \}\}/g, "{%= $ %}") console.debug(rendered) - for (const delimiters of [{openDelimiter:"<", closeDelimiter:">"}, {openDelimiter:"{", closeDelimiter:"}"}]) - rendered = await ejs.render(rendered, {...data, s:imports.s, f:imports.format, embed}, {views, async:true, ...delimiters}) + for (const delimiters of [{openDelimiter: "<", closeDelimiter: ">"}, {openDelimiter: "{", closeDelimiter: "}"}]) + rendered = await ejs.render(rendered, {...data, s: imports.s, f: imports.format, embed}, {views, async: true, ...delimiters}) console.debug(`metrics/compute/${login} > success`) //Output if (convert === "markdown-pdf") { return imports.svg.pdf(rendered, { - paddings:q["config.padding"] || conf.settings.padding, - style:extras.css, - twemojis:q["config.twemoji"], - gemojis:q["config.gemoji"], - octicons:q["config.octicon"], + paddings: q["config.padding"] || conf.settings.padding, + style: extras.css, + twemojis: q["config.twemoji"], + gemojis: q["config.gemoji"], + octicons: q["config.octicon"], rest, }) } - return {rendered, mime:"text/html"} + return {rendered, mime: "text/html"} } //Rendering console.debug(`metrics/compute/${login} > render`) - let rendered = await ejs.render(image, {...data, s:imports.s, f:imports.format, style, fonts}, {views, async:true}) + let rendered = await ejs.render(image, {...data, s: imports.s, f: imports.format, style, fonts}, {views, async: true}) //Additional transformations if (q["config.twemoji"]) @@ -192,7 +192,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, console.debug(`metrics/compute/${login} > verified SVG, no parsing errors found`) } //Resizing - const {resized, mime} = await imports.svg.resize(rendered, {paddings:q["config.padding"] || conf.settings.padding, convert:convert === "svg" ? null : convert, scripts:[...data.postscripts, extras.js || null].filter(x => x)}) + const {resized, mime} = await imports.svg.resize(rendered, {paddings: q["config.padding"] || conf.settings.padding, convert: convert === "svg" ? null : convert, scripts: [...data.postscripts, extras.js || null].filter(x => x)}) rendered = resized //Result @@ -212,37 +212,37 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf, //Metrics insights metrics.insights = async function({login}, {graphql, rest, conf}, {Plugins, Templates}) { const q = { - template:"classic", - achievements:true, - "achievements.threshold":"X", - isocalendar:true, - "isocalendar.duration":"full-year", - languages:true, - "languages.limit":0, - activity:true, - "activity.limit":100, - "activity.days":0, - notable:true, - followup:true, - "followup.sections":"repositories, user", - habits:true, - "habits.from":100, - "habits.days":7, - "habits.facts":false, - "habits.charts":true, - introduction:true, + template: "classic", + achievements: true, + "achievements.threshold": "X", + isocalendar: true, + "isocalendar.duration": "full-year", + languages: true, + "languages.limit": 0, + activity: true, + "activity.limit": 100, + "activity.days": 0, + notable: true, + followup: true, + "followup.sections": "repositories, user", + habits: true, + "habits.from": 100, + "habits.days": 7, + "habits.facts": false, + "habits.charts": true, + introduction: true, } const plugins = { - achievements:{enabled:true}, - isocalendar:{enabled:true}, - languages:{enabled:true, extras:false}, - activity:{enabled:true, markdown:"extended"}, - notable:{enabled:true}, - followup:{enabled:true}, - habits:{enabled:true, extras:false}, - introduction:{enabled:true}, + achievements: {enabled: true}, + isocalendar: {enabled: true}, + languages: {enabled: true, extras: false}, + activity: {enabled: true, markdown: "extended"}, + notable: {enabled: true}, + followup: {enabled: true}, + habits: {enabled: true, extras: false}, + introduction: {enabled: true}, } - return metrics({login, q}, {graphql, rest, plugins, conf, convert:"json"}, {Plugins, Templates}) + return metrics({login, q}, {graphql, rest, plugins, conf, convert: "json"}, {Plugins, Templates}) } //Metrics insights static render @@ -260,7 +260,7 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest, await page.goto(`${server}/about/${login}?embed=1&localstorage=1`) await page.evaluate(async json => localStorage.setItem("local.metrics", json), json) //eslint-disable-line no-undef await page.goto(`${server}/about/${login}?embed=1&localstorage=1`) - await page.waitForSelector(".container .user", {timeout:10 * 60 * 1000}) + await page.waitForSelector(".container .user", {timeout: 10 * 60 * 1000}) //Rendering console.debug(`metrics/compute/${login} > insights > rendering data`) @@ -273,9 +273,9 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest, ${await page.evaluate(() => document.querySelector("main").outerHTML)} - ${(await Promise.all([".css/style.vars.css", ".css/style.css", "about/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data:style}) => ``).join("\n")} + ${(await Promise.all([".css/style.vars.css", ".css/style.css", "about/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data: style}) => ``).join("\n")} ` await browser.close() - return {mime:"text/html", rendered} + return {mime: "text/html", rendered} } diff --git a/source/app/metrics/metadata.mjs b/source/app/metrics/metadata.mjs index 85ed773b..583831c1 100644 --- a/source/app/metrics/metadata.mjs +++ b/source/app/metrics/metadata.mjs @@ -1,7 +1,7 @@ //Imports import fs from "fs" import yaml from "js-yaml" -import {marked} from "marked" +import { marked } from "marked" import fetch from "node-fetch" import path from "path" import url from "url" @@ -13,7 +13,7 @@ const categories = ["core", "github", "social", "community"] let previous = null //Environment -const env = {ghactions:`${process.env.GITHUB_ACTIONS}` === "true"} +const env = {ghactions: `${process.env.GITHUB_ACTIONS}` === "true"} /**Metadata descriptor parser */ export default async function metadata({log = true, diff = false} = {}) { @@ -50,7 +50,7 @@ export default async function metadata({log = true, diff = false} = {}) { if (!(await fs.promises.lstat(path.join(___plugins, name))).isDirectory()) continue logger(`metrics/metadata > loading plugin metadata [community/${name}]`) - Plugins[name] = await metadata.plugin({__plugins:___plugins, __templates, name, logger}) + Plugins[name] = await metadata.plugin({__plugins: ___plugins, __templates, name, logger}) Plugins[name].community = true } continue @@ -84,7 +84,7 @@ export default async function metadata({log = true, diff = false} = {}) { const descriptor = yaml.load(`${await fs.promises.readFile(__descriptor, "utf-8")}`) //Metadata - return {plugins:Plugins, templates:Templates, packaged, descriptor, env} + return {plugins: Plugins, templates: Templates, packaged, descriptor, env} } /**Metadata extractor for inputs */ @@ -104,14 +104,14 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { //Inputs parser { - meta.inputs = function({data:{user = null} = {}, q, account}, defaults = {}) { + meta.inputs = function({data: {user = null} = {}, q, account}, defaults = {}) { //Support check if (!account) console.debug(`metrics/inputs > account type not set for plugin ${name}!`) if (account !== "bypass") { const context = q.repo ? "repository" : account if (!meta.supports?.includes(context)) - throw {error:{message:`Not supported for: ${context}`, instance:new Error()}} + throw {error: {message: `Not supported for: ${context}`, instance: new Error()}} } //Special values replacer const replacer = value => { @@ -128,7 +128,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { } //Inputs checks const result = Object.fromEntries( - Object.entries(inputs).map(([key, {type, format, default:defaulted, min, max, values, inherits:_inherits}]) => [ + Object.entries(inputs).map(([key, {type, format, default: defaulted, min, max, values, inherits: _inherits}]) => [ //Format key metadata.to.query(key, {name}), //Format value @@ -165,7 +165,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { logger(`metrics/inputs > failed to decode uri : ${value}`) value = defaulted } - const separators = {"comma-separated":",", "space-separated":" "} + const separators = {"comma-separated": ",", "space-separated": " "} const separator = separators[[format].flat().filter(s => s in separators)[0]] ?? "," return value.split(separator).map(v => replacer(v).toLocaleLowerCase()).filter(v => Array.isArray(values) ? values.includes(v) : true).filter(v => v) } @@ -230,13 +230,14 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { Object.entries(inputs).map(([key, value]) => [ key, { - comment:"", - descriptor:yaml.dump({ - [key]:Object.fromEntries( - Object.entries(value).filter(([key]) => ["description", "default", "required"].includes(key)).map(([k, v]) => k === "description" ? [k, v.split("\n")[0]] : k === "default" ? [k, ((/^\$\{\{[\s\S]+\}\}$/.test(v)) || (["config_presets", "config_timezone", "use_prebuilt_image"].includes(key))) ? v : ""] : [k, v] + comment: "", + descriptor: yaml.dump({ + [key]: Object.fromEntries( + Object.entries(value).filter(([key]) => ["description", "default", "required"].includes(key)).map(([k, v]) => + k === "description" ? [k, v.split("\n")[0]] : k === "default" ? [k, ((/^\$\{\{[\s\S]+\}\}$/.test(v)) || (["config_presets", "config_timezone", "use_prebuilt_image"].includes(key))) ? v : ""] : [k, v] ), ), - }, {quotingType:'"', noCompatMode:true}), + }, {quotingType: '"', noCompatMode: true}), }, ]), ) @@ -258,9 +259,9 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { value = "" } } - else + else { value = process.env[`INPUT_${key.toUpperCase()}`]?.trim() ?? "" - + } const unspecified = value === "" //From presets @@ -279,32 +280,32 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { q[key] = value } } - return meta.inputs({q, account:"bypass"}) + return meta.inputs({q, account: "bypass"}) } } //Web metadata { meta.web = Object.fromEntries( - Object.entries(inputs).map(([key, {type, description:text, example, default:defaulted, min = 0, max = 9999, values}]) => [ + Object.entries(inputs).map(([key, {type, description: text, example, default: defaulted, min = 0, max = 9999, values}]) => [ //Format key metadata.to.query(key), //Value descriptor (() => { switch (type) { case "boolean": - return {text, type:"boolean", defaulted:/^(?:[Tt]rue|[Oo]n|[Yy]es|1)$/.test(defaulted) ? true : /^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(defaulted) ? false : defaulted} + return {text, type: "boolean", defaulted: /^(?:[Tt]rue|[Oo]n|[Yy]es|1)$/.test(defaulted) ? true : /^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(defaulted) ? false : defaulted} case "number": - return {text, type:"number", min, max, defaulted} + return {text, type: "number", min, max, defaulted} case "array": - return {text, type:"text", placeholder:example ?? defaulted, defaulted} + return {text, type: "text", placeholder: example ?? defaulted, defaulted} case "string": { if (Array.isArray(values)) - return {text, type:"select", values, defaulted} - return {text, type:"text", placeholder:example ?? defaulted, defaulted} + return {text, type: "select", values, defaulted} + return {text, type: "text", placeholder: example ?? defaulted, defaulted} } case "json": - return {text, type:"text", placeholder:example ?? defaulted, defaulted} + return {text, type: "text", placeholder: example ?? defaulted, defaulted} default: return null } @@ -317,7 +318,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { { //Extract demos const raw = `${await fs.promises.readFile(path.join(__plugins, name, "README.md"), "utf-8")}` - const demo = meta.examples ? demos({examples:meta.examples}) : raw.match(/(?[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "" + const demo = meta.examples ? demos({examples: meta.examples}) : raw.match(/(?
[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "" //Compatibility const templates = {} @@ -337,7 +338,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { const header = [ "
", ` `, - ` `, + ` `, meta.authors?.length ? `` : "", " ", ' ', @@ -353,14 +354,16 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { }`, " ", " ", - ` `, + ` `, " ", " ", - demos({colspan:2, wrap:name === "base", examples:meta.examples}), + demos({colspan: 2, wrap: name === "base", examples: meta.examples}), " ", "

${meta.name}

${marked.parse(meta.description ?? "", {silent:true})}
${marked.parse(meta.description ?? "", {silent: true})}
Authors${[meta.authors].flat().map(author => `@${author}`)}
Supported features
→ Full specification
${[ - ...(meta.scopes ?? []).map(scope => `🔑 ${{public_access:"(scopeless)"}[scope] ?? scope}`), - ...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `🗝️ ${token}`), - ...(meta.scopes?.length ? ["read:org", "read:user", "read:packages", "repo"].map(scope => !meta.scopes.includes(scope) ? `${scope} (optional)` : null).filter(v => v) : []), - ].filter(v => v).join(" ") || "No tokens are required for this plugin"}${ + [ + ...(meta.scopes ?? []).map(scope => `🔑 ${{public_access: "(scopeless)"}[scope] ?? scope}`), + ...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `🗝️ ${token}`), + ...(meta.scopes?.length ? ["read:org", "read:user", "read:packages", "repo"].map(scope => !meta.scopes.includes(scope) ? `${scope} (optional)` : null).filter(v => v) : []), + ].filter(v => v).join(" ") || "No tokens are required for this plugin" + }
", ].filter(v => v).join("\n") @@ -415,7 +418,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) { cell.push(`allowed values:
    ${o.values.map(value => `
  • ${value}
  • `).join("")}
`) return `

${option}

- ${marked.parse(description, {silent:true})} + ${marked.parse(description, {silent: true})} ${cell.join("\n")} @@ -462,7 +465,7 @@ metadata.template = async function({__templates, name, plugins}) { const header = [ "", ` `, - ` `, + ` `, " ", ' ', ` `, @@ -489,24 +492,24 @@ metadata.template = async function({__templates, name, plugins}) { }`, " ", " ", - demos({colspan:2, examples:meta.examples}), + demos({colspan: 2, examples: meta.examples}), " ", "

${meta.name ?? "(unnamed template)"}

${marked.parse(meta.description ?? "", {silent:true})}
${marked.parse(meta.description ?? "", {silent: true})}
Supported features
→ Full specification
${Object.entries(compatibility).filter(([_, value]) => value).map(([id]) => `${plugins[id].icon}`).join(" ")}${meta.formats?.includes("markdown") ? " ✓ embed()" : ""}
", ].join("\n") //Result return { - name:meta.name ?? "(unnamed template)", - description:meta.description ?? "", - index:meta.index ?? null, - formats:meta.formats ?? null, - supports:meta.supports ?? null, - readme:{ - demo:demos({examples:meta.examples}), - compatibility:{ + name: meta.name ?? "(unnamed template)", + description: meta.description ?? "", + index: meta.index ?? null, + formats: meta.formats ?? null, + supports: meta.supports ?? null, + readme: { + demo: demos({examples: meta.examples}), + compatibility: { ...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => value)), ...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => !value).map(([key, value]) => [key, meta.formats?.includes("markdown") ? "embed" : value])), - base:true, + base: true, }, header, }, diff --git a/source/app/metrics/presets.mjs b/source/app/metrics/presets.mjs index 458a6307..f4cdf66e 100644 --- a/source/app/metrics/presets.mjs +++ b/source/app/metrics/presets.mjs @@ -7,8 +7,8 @@ import metadata from "./metadata.mjs" /**Presets parser */ export default async function presets(list, {log = true, core = null} = {}) { //Init - const {plugins} = await metadata({log:false}) - const {"config.presets":files} = plugins.core.inputs({q:{"config.presets":list}, account:"bypass"}) + const {plugins} = await metadata({log: false}) + const {"config.presets": files} = plugins.core.inputs({q: {"config.presets": list}, account: "bypass"}) const logger = log ? console.debug : () => null const allowed = Object.entries(metadata.inputs).filter(([_, {type, preset}]) => (type !== "token") && (!/^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(preset))).map(([key]) => key) const env = core ? "action" : "web" @@ -36,7 +36,7 @@ export default async function presets(list, {log = true, core = null} = {}) { logger(`metrics/presets > ${file} cannot be loaded in current environment ${env}, skipping`) continue } - const {schema, with:inputs} = yaml.load(text) + const {schema, with: inputs} = yaml.load(text) logger(`metrics/presets > ${file} preset schema is ${schema}`) //Evaluate preset diff --git a/source/app/metrics/setup.mjs b/source/app/metrics/setup.mjs index 7f34a88f..e546b935 100644 --- a/source/app/metrics/setup.mjs +++ b/source/app/metrics/setup.mjs @@ -27,15 +27,15 @@ export default async function({log = true, sandbox = false, community = {}} = {} const logger = log ? console.debug : () => null logger("metrics/setup > setup") const conf = { - authenticated:null, - templates:{}, - queries:{}, - settings:{port:3000}, - metadata:{}, - paths:{ - statics:__statics, - templates:__templates, - node_modules:__modules, + authenticated: null, + templates: {}, + queries: {}, + settings: {port: 3000}, + metadata: {}, + paths: { + statics: __statics, + templates: __templates, + node_modules: __modules, }, } @@ -64,13 +64,13 @@ export default async function({log = true, sandbox = false, community = {}} = {} } if (!conf.settings.templates) - conf.settings.templates = {default:"classic", enabled:[]} + conf.settings.templates = {default: "classic", enabled: []} if (!conf.settings.plugins) conf.settings.plugins = {} conf.settings.community = {...conf.settings.community, ...community} - conf.settings.plugins.base = {parts:["header", "activity", "community", "repositories", "metadata"]} + conf.settings.plugins.base = {parts: ["header", "activity", "community", "repositories", "metadata"]} if (conf.settings.debug) - logger(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256})) + logger(util.inspect(conf.settings, {depth: Infinity, maxStringLength: 256})) //Load package settings logger("metrics/setup > load package.json") @@ -85,7 +85,7 @@ export default async function({log = true, sandbox = false, community = {}} = {} if ((Array.isArray(conf.settings.community.templates)) && (conf.settings.community.templates.length)) { //Clean remote repository logger(`metrics/setup > ${conf.settings.community.templates.length} community templates to install`) - await fs.promises.rm(path.join(__templates, ".community"), {recursive:true, force:true}) + await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true}) //Download community templates for (const template of conf.settings.community.templates) { try { @@ -95,10 +95,10 @@ export default async function({log = true, sandbox = false, community = {}} = {} const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}` logger(`metrics/setup > run ${command}`) //Clone remote repository - processes.execSync(command, {stdio:"ignore"}) + processes.execSync(command, {stdio: "ignore"}) //Extract template logger(`metrics/setup > extract ${name} from ${repo}@${branch}`) - await fs.promises.rm(path.join(__templates, `@${name}`), {recursive:true, force:true}) + await fs.promises.rm(path.join(__templates, `@${name}`), {recursive: true, force: true}) await fs.promises.rename(path.join(__templates, ".community/source/templates", name), path.join(__templates, `@${name}`)) //JavaScript file if (trust) @@ -113,18 +113,18 @@ export default async function({log = true, sandbox = false, community = {}} = {} logger(`metrics/setup > @${name} extended from ${inherit}`) await fs.promises.copyFile(path.join(__templates, inherit, "template.mjs"), path.join(__templates, `@${name}`, "template.mjs")) } - else + else { logger(`metrics/setup > @${name} could not extends ${inherit} as it does not exist`) - + } } } - else + else { logger(`metrics/setup > @${name}/template.mjs does not exist`) - + } //Clean remote repository logger(`metrics/setup > clean ${repo}@${branch}`) - await fs.promises.rm(path.join(__templates, ".community"), {recursive:true, force:true}) + await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true}) logger(`metrics/setup > loaded community template ${name}`) } catch (error) { @@ -133,9 +133,9 @@ export default async function({log = true, sandbox = false, community = {}} = {} } } } - else + else { logger("metrics/setup > no community templates to install") - + } //Load templates for (const name of await fs.promises.readdir(__templates)) { @@ -145,10 +145,10 @@ export default async function({log = true, sandbox = false, community = {}} = {} continue logger(`metrics/setup > load template [${name}]`) //Cache templates files - const files = ["image.svg", "style.css", "fonts.css"].map(file => path.join(__templates, (fs.existsSync(path.join(directory, file)) ? name : "classic"), file)) + const files = ["image.svg", "style.css", "fonts.css"].map(file => path.join(__templates, fs.existsSync(path.join(directory, file)) ? name : "classic", file)) const [image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(file)}`)) const partials = JSON.parse(`${await fs.promises.readFile(path.join(directory, "partials/_.json"))}`) - conf.templates[name] = {image, style, fonts, partials, views:[directory]} + conf.templates[name] = {image, style, fonts, partials, views: [directory]} //Cache templates scripts Templates[name] = await (async () => { @@ -165,7 +165,7 @@ export default async function({log = true, sandbox = false, community = {}} = {} const [image, style, fonts] = files.map(file => `${fs.readFileSync(file)}`) const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`) logger(`metrics/setup > reload template [${name}] > success`) - return {image, style, fonts, partials, views:[directory]} + return {image, style, fonts, partials, views: [directory]} }, }) } @@ -177,7 +177,7 @@ export default async function({log = true, sandbox = false, community = {}} = {} case "community": { const ___plugins = path.join(__plugins, "community") for (const name of await fs.promises.readdir(___plugins)) - await load.plugin(name, {__plugins:___plugins, Plugins, conf, logger}) + await load.plugin(name, {__plugins: ___plugins, Plugins, conf, logger}) continue } default: @@ -191,7 +191,7 @@ export default async function({log = true, sandbox = false, community = {}} = {} //Store authenticated user if (conf.settings.token) { try { - conf.authenticated = (await (new OctokitRest.Octokit({auth:conf.settings.token})).users.getAuthenticated()).data.login + conf.authenticated = (await (new OctokitRest.Octokit({auth: conf.settings.token})).users.getAuthenticated()).data.login logger(`metrics/setup > setup > authenticated as ${conf.authenticated}`) } catch (error) { @@ -251,7 +251,8 @@ const load = { } } //Create queries formatters - Object.keys(queries).map(query => queries[query.substring(1)] = (vars = {}) => { + Object.keys(queries).map(query => + queries[query.substring(1)] = (vars = {}) => { let queried = queries[query] for (const [key, value] of Object.entries(vars)) queried = queried.replace(new RegExp(`[$]${key}`, "g"), value) diff --git a/source/app/metrics/utils.mjs b/source/app/metrics/utils.mjs index a7612a31..dafb475b 100644 --- a/source/app/metrics/utils.mjs +++ b/source/app/metrics/utils.mjs @@ -1,17 +1,16 @@ //Imports import octicons from "@primer/octicons" -import fs from "fs/promises" -import prism_lang from "prismjs/components/index.js" import axios from "axios" import processes from "child_process" import crypto from "crypto" -import {minify as csso} from "csso" +import { minify as csso } from "csso" import emoji from "emoji-name-map" import fss from "fs" +import fs from "fs/promises" import GIFEncoder from "gifencoder" import jimp from "jimp" import linguist from "linguist-js" -import {marked} from "marked" +import { marked } from "marked" import minimatch from "minimatch" import nodechartist from "node-chartist" import fetch from "node-fetch" @@ -20,6 +19,7 @@ import os from "os" import paths from "path" import PNG from "png-js" import prism from "prismjs" +import prism_lang from "prismjs/components/index.js" import _puppeteer from "puppeteer" import purgecss from "purgecss" import readline from "readline" @@ -34,7 +34,7 @@ import xmlformat from "xml-formatter" prism_lang() //Exports -export {axios, emoji, fetch, fs, git, jimp, minimatch, opengraph, os, paths, processes, rss, url, util} +export { axios, emoji, fetch, fs, git, jimp, minimatch, opengraph, os, paths, processes, rss, url, util } /**Returns module __dirname */ export function __module(module) { @@ -45,25 +45,25 @@ export function __module(module) { export const puppeteer = { async launch() { return _puppeteer.launch({ - headless:this.headless, - executablePath:process.env.PUPPETEER_BROWSER_PATH, - args:this.headless ? ["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] : [], - ignoreDefaultArgs:["--disable-extensions"], + headless: this.headless, + executablePath: process.env.PUPPETEER_BROWSER_PATH, + args: this.headless ? ["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] : [], + ignoreDefaultArgs: ["--disable-extensions"], }) }, - headless:true, + headless: true, } /**Plural formatter */ export function s(value, end = "") { - return value !== 1 ? {y:"ies", "":"s"}[end] : end + return value !== 1 ? {y: "ies", "": "s"}[end] : end } /**Formatters */ export function formatters({timeZone} = {}) { //Check options try { - new Date().toLocaleString("fr", {timeZoneName:"short", timeZone}) + new Date().toLocaleString("fr", {timeZoneName: "short", timeZone}) } catch { timeZone = undefined @@ -72,7 +72,7 @@ export function formatters({timeZone} = {}) { /**Formatter */ const format = function(n, {sign = false, unit = true, fixed} = {}) { if (unit) { - for (const {u, v} of [{u:"b", v:10 ** 9}, {u:"m", v:10 ** 6}, {u:"k", v:10 ** 3}]) { + for (const {u, v} of [{u: "b", v: 10 ** 9}, {u: "m", v: 10 ** 6}, {u: "k", v: 10 ** 3}]) { if (n / v >= 1) return `${(sign) && (n > 0) ? "+" : ""}${(n / v).toFixed(fixed ?? 2).substr(0, 4).replace(/[.]0*$/, "")}${u}` } @@ -82,7 +82,7 @@ export function formatters({timeZone} = {}) { /**Bytes formatter */ format.bytes = function(n) { - for (const {u, v} of [{u:"E", v:10 ** 18}, {u:"P", v:10 ** 15}, {u:"T", v:10 ** 12}, {u:"G", v:10 ** 9}, {u:"M", v:10 ** 6}, {u:"k", v:10 ** 3}]) { + for (const {u, v} of [{u: "E", v: 10 ** 18}, {u: "P", v: 10 ** 15}, {u: "T", v: 10 ** 12}, {u: "G", v: 10 ** 9}, {u: "M", v: 10 ** 6}, {u: "k", v: 10 ** 3}]) { if (n / v >= 1) return `${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B` } @@ -110,11 +110,11 @@ export function formatters({timeZone} = {}) { format.date = function(string, options) { if (options.date) { delete options.date - Object.assign(options, {day:"numeric", month:"short", year:"numeric"}) + Object.assign(options, {day: "numeric", month: "short", year: "numeric"}) } if (options.time) { delete options.time - Object.assign(options, {hour:"2-digit", minute:"2-digit", second:"2-digit"}) + Object.assign(options, {hour: "2-digit", minute: "2-digit", second: "2-digit"}) } return new Intl.DateTimeFormat("en-GB", {timeZone, ...options}).format(new Date(string)) } @@ -139,7 +139,7 @@ export function shuffle(array) { } /**Escape html */ -export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) { +export function htmlescape(string, u = {"&": true, "<": true, ">": true, '"': true, "'": true}) { return string .replace(/&(?!(?:amp|lt|gt|quot|apos);)/g, u["&"] ? "&" : "&") .replace(/":true, '"':true, } /**Unescape html */ -export function htmlunescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) { +export function htmlunescape(string, u = {"&": true, "<": true, ">": true, '"': true, "'": true}) { return string .replace(/</g, u["<"] ? "<" : "<") .replace(/>/g, u[">"] ? ">" : ">") @@ -173,7 +173,7 @@ export async function chartist() { /**Language analyzer (single file) */ export async function language({filename, patch}) { console.debug(`metrics/language > ${filename}`) - const {files:{results}} = await linguist(filename, {fileContent:patch}) + const {files: {results}} = await linguist(filename, {fileContent: patch}) const result = (results[filename] ?? "unknown").toLocaleLowerCase() console.debug(`metrics/language > ${filename} > result: ${result}`) return result @@ -181,7 +181,7 @@ export async function language({filename, patch}) { /**Run command (use this to execute commands and process whole output at once, may not be suitable for large outputs) */ export async function run(command, options, {prefixed = true, log = true} = {}) { - const prefix = {win32:"wsl"}[process.platform] ?? "" + const prefix = {win32: "wsl"}[process.platform] ?? "" command = `${prefixed ? prefix : ""} ${command}`.trim() return new Promise((solve, reject) => { console.debug(`metrics/command/run > ${command}`) @@ -202,7 +202,7 @@ export async function run(command, options, {prefixed = true, log = true} = {}) /**Spawn command (use this to execute commands and process output on the fly) */ export async function spawn(command, args = [], options = {}, {prefixed = true, timeout = 300 * 1000, stdout} = {}) { //eslint-disable-line max-params - const prefix = {win32:"wsl"}[process.platform] ?? "" + const prefix = {win32: "wsl"}[process.platform] ?? "" if ((prefixed) && (prefix)) { args.unshift(command) command = prefix @@ -211,8 +211,8 @@ export async function spawn(command, args = [], options = {}, {prefixed = true, throw new Error("`stdout` argument was not provided, use run() instead of spawn() if processing output is not needed") return new Promise((solve, reject) => { console.debug(`metrics/command/spawn > ${command} with ${args.join(" ")}`) - const child = processes.spawn(command, args, {...options, shell:true, timeout}) - const reader = readline.createInterface({input:child.stdout}) + const child = processes.spawn(command, args, {...options, shell: true, timeout}) + const reader = readline.createInterface({input: child.stdout}) reader.on("line", stdout) const closed = new Promise(close => reader.on("close", close)) child.on("close", async code => { @@ -245,18 +245,18 @@ export function highlight(code, lang) { /**Markdown-html sanitizer-interpreter */ export async function markdown(text, {mode = "inline", codelines = Infinity} = {}) { //Sanitize user input once to prevent injections and parse into markdown - let rendered = await marked.parse(htmlunescape(htmlsanitize(text)), {highlight, silent:true, xhtml:true}) + let rendered = await marked.parse(htmlunescape(htmlsanitize(text)), {highlight, silent: true, xhtml: true}) //Markdown mode switch (mode) { case "inline": { rendered = htmlsanitize( htmlsanitize(rendered, { - allowedTags:["h1", "h2", "h3", "h4", "h5", "h6", "br", "blockquote", "code", "span"], - allowedAttributes:{code:["class"], span:["class"]}, + allowedTags: ["h1", "h2", "h3", "h4", "h5", "h6", "br", "blockquote", "code", "span"], + allowedAttributes: {code: ["class"], span: ["class"]}, }), { - allowedAttributes:{code:["class"], span:["class"]}, - transformTags:{h1:"b", h2:"b", h3:"b", h4:"b", h5:"b", h6:"b", blockquote:"i"}, + allowedAttributes: {code: ["class"], span: ["class"]}, + transformTags: {h1: "b", h2: "b", h3: "b", h4: "b", h5: "b", h6: "b", blockquote: "i"}, }, ) break @@ -349,7 +349,7 @@ export const svg = { } //Additional transformations if (twemojis) - rendered = await svg.twemojis(rendered, {custom:false}) + rendered = await svg.twemojis(rendered, {custom: false}) if ((gemojis) && (rest)) rendered = await svg.gemojis(rendered, {rest}) if (octicons) @@ -358,13 +358,13 @@ export const svg = { //Render through browser and print pdf console.debug("metrics/svg/pdf > loading svg") const page = await svg.resize.browser.newPage() - page.on("console", ({_text:text}) => console.debug(`metrics/svg/pdf > puppeteer > ${text}`)) - await page.setContent(`
${rendered}
`, {waitUntil:["load", "domcontentloaded", "networkidle2"]}) + page.on("console", ({_text: text}) => console.debug(`metrics/svg/pdf > puppeteer > ${text}`)) + await page.setContent(`
${rendered}
`, {waitUntil: ["load", "domcontentloaded", "networkidle2"]}) console.debug("metrics/svg/pdf > loaded svg successfully") const margins = (Array.isArray(paddings) ? paddings : paddings.split(",")).join(" ") console.debug(`metrics/svg/pdf > margins set to ${margins}`) await page.addStyleTag({ - content:` + content: ` main { margin: ${margins}; } main svg { height: 1em; width: 1em; } ${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "@primer/css/dist/markdown.css")).catch(_ => "")}${style} @@ -374,7 +374,7 @@ export const svg = { //Result await page.close() console.debug("metrics/svg/pdf > rendering complete") - return {rendered, mime:"application/pdf"} + return {rendered, mime: "application/pdf"} }, /**Render and resize svg */ async resize(rendered, {paddings, convert, scripts = []}) { @@ -384,7 +384,7 @@ export const svg = { console.debug(`metrics/svg/resize > started ${await svg.resize.browser.version()}`) } //Format padding - const padding = {width:1, height:1, absolute:{width:0, height:0}} + const padding = {width: 1, height: 1, absolute: {width: 0, height: 0}} paddings = Array.isArray(paddings) ? paddings : `${paddings}`.split(",").map(x => x.trim()) for (const [i, dimension] of [[0, "width"], [1, "height"]]) { let operands = (paddings?.[i] ?? paddings[0]) @@ -400,18 +400,18 @@ export const svg = { //Render through browser and resize height console.debug("metrics/svg/resize > loading svg") const page = await svg.resize.browser.newPage() - page.setViewport({width:980, height:980}) + page.setViewport({width: 980, height: 980}) page .on("console", message => console.debug(`metrics/svg/resize > puppeteer > ${message.text()}`)) .on("pageerror", error => console.debug(`metrics/svg/resize > puppeteer > ${error.message}`)) - await page.setContent(rendered, {waitUntil:["load", "domcontentloaded", "networkidle2"]}) + await page.setContent(rendered, {waitUntil: ["load", "domcontentloaded", "networkidle2"]}) console.debug("metrics/svg/resize > loaded svg successfully") - await page.addStyleTag({content:"body { margin: 0; padding: 0; }"}) + await page.addStyleTag({content: "body { margin: 0; padding: 0; }"}) let mime = "image/svg+xml" console.debug("metrics/svg/resize > resizing svg") let height, resized, width try { - ({resized, width, height} = await page.evaluate( + ;({resized, width, height} = await page.evaluate( async (padding, scripts) => { //Execute additional JavaScript for (const script of scripts) { @@ -431,7 +431,7 @@ export const svg = { console.debug(`animations are ${animated ? "enabled" : "disabled"}`) await new Promise(solve => setTimeout(solve, 2400)) //Get bounds and resize - let {y:height, width} = document.querySelector("svg #metrics-end").getBoundingClientRect() + let {y: height, width} = document.querySelector("svg #metrics-end").getBoundingClientRect() console.debug(`bounds width=${width}, height=${height}`) height = Math.max(1, Math.ceil(height * padding.height + padding.absolute.height)) width = Math.max(1, Math.ceil(width * padding.width + padding.absolute.width)) @@ -445,7 +445,7 @@ export const svg = { if (animated) document.querySelector("svg").classList.remove("no-animations") //Result - return {resized:new XMLSerializer().serializeToString(document.querySelector("svg")), height, width} + return {resized: new XMLSerializer().serializeToString(document.querySelector("svg")), height, width} }, padding, scripts, @@ -458,7 +458,7 @@ export const svg = { //Convert if required if (convert) { console.debug(`metrics/svg/resize > convert to ${convert}`) - resized = await page.screenshot({type:convert, clip:{x:0, y:0, width, height}, omitBackground:true}) + resized = await page.screenshot({type: convert, clip: {x: 0, y: 0, width, height}, omitBackground: true}) mime = `image/${convert}` } //Result @@ -478,7 +478,7 @@ export const svg = { } //Compute hash const page = await svg.resize.browser.newPage() - await page.setContent(rendered, {waitUntil:["load", "domcontentloaded", "networkidle2"]}) + await page.setContent(rendered, {waitUntil: ["load", "domcontentloaded", "networkidle2"]}) const data = await page.evaluate(async () => { document.querySelector("footer")?.remove() return document.querySelector("svg").outerHTML @@ -494,7 +494,7 @@ export const svg = { //Load emojis console.debug("metrics/svg/twemojis > rendering twemojis") const emojis = new Map() - for (const {text:emoji, url} of twemojis.parse(rendered)) { + for (const {text: emoji, url} of twemojis.parse(rendered)) { if (!emojis.has(emoji)) emojis.set(emoji, (await axios.get(url)).data.replace(/^ [`:${key}:`, value])) { - if (((!emojis.has(emoji))) && (new RegExp(emoji, "g").test(rendered))) + if ((!emojis.has(emoji)) && (new RegExp(emoji, "g").test(rendered))) emojis.set(emoji, ``) } } @@ -535,9 +535,9 @@ export const svg = { for (const size of Object.keys(heights)) { const octicon = `:octicon-${name}-${size}:` if (new RegExp(`:octicon-${name}(?:-[0-9]+)?:`, "g").test(rendered)) { - icons.set(octicon, toSVG({height:size, width:size})) + icons.set(octicon, toSVG({height: size, width: size})) if (Number(size) === 16) - icons.set(`:octicon-${name}:`, toSVG({height:size, width:size})) + icons.set(`:octicon-${name}:`, toSVG({height: size, width: size})) } } } @@ -547,7 +547,7 @@ export const svg = { return rendered }, /**Optimizers */ - optimize:{ + optimize: { /**CSS optimizer */ async css(rendered) { //Extract styles @@ -558,9 +558,9 @@ export const svg = { while (regex.test(rendered)) { const style = htmlunescape(rendered.match(regex)?.groups?.style ?? "") rendered = rendered.replace(regex, cleaned) - css.push({raw:style}) + css.push({raw: style}) } - const content = [{raw:rendered, extension:"html"}] + const content = [{raw: rendered, extension: "html"}] //Purge CSS const purged = await new purgecss.PurgeCSS().purge({content, css}) @@ -574,7 +574,7 @@ export const svg = { console.debug("metrics/svg/optimize/xml > skipped as raw option is enabled") return rendered } - return xmlformat(rendered, {lineSeparator:"\n", collapseContent:true}) + return xmlformat(rendered, {lineSeparator: "\n", collapseContent: true}) }, /**SVG optimizer */ async svg(rendered, {raw = false} = {}, experimental = new Set()) { @@ -587,16 +587,16 @@ export const svg = { console.debug("metrics/svg/optimize/svg > this feature require experimental feature flag --optimize-svg") return rendered } - const {error, data:optimized} = await SVGO.optimize(rendered, { - multipass:true, - plugins:SVGO.extendDefaultPlugins([ + const {error, data: optimized} = await SVGO.optimize(rendered, { + multipass: true, + plugins: SVGO.extendDefaultPlugins([ //Additional cleanup - {name:"cleanupListOfValues"}, - {name:"removeRasterImages"}, - {name:"removeScriptElement"}, + {name: "cleanupListOfValues"}, + {name: "removeRasterImages"}, + {name: "removeScriptElement"}, //Force CSS style consistency - {name:"inlineStyles", active:false}, - {name:"removeViewBox", active:false}, + {name: "inlineStyles", active: false}, + {name: "removeViewBox", active: false}, ]), }) if (error) @@ -616,7 +616,7 @@ export async function record({page, width, height, frames, scale = 1, quality = //Register images frames const images = [] for (let i = 0; i < frames; i++) { - images.push(await page.screenshot({type:"png", clip:{width, height, x, y}, omitBackground:background})) + images.push(await page.screenshot({type: "png", clip: {width, height, x, y}, omitBackground: background})) await wait(delay / 1000) if (i % 10 === 0) console.debug(`metrics/record > processed ${i}/${frames} frames`) @@ -643,7 +643,7 @@ export async function gif({page, width, height, frames, x = 0, y = 0, repeat = t encoder.setQuality(quality) //Register frames for (let i = 0; i < frames; i++) { - const buffer = new PNG(await page.screenshot({clip:{width, height, x, y}})) + const buffer = new PNG(await page.screenshot({clip: {width, height, x, y}})) encoder.addFrame(await new Promise(solve => buffer.decode(pixels => solve(pixels)))) if (frames % 10 === 0) console.debug(`metrics/puppeteergif > processed ${i}/${frames} frames`) diff --git a/source/app/web/index.mjs b/source/app/web/index.mjs index 6d18442d..c4e7990b 100644 --- a/source/app/web/index.mjs +++ b/source/app/web/index.mjs @@ -1,4 +1,4 @@ import app from "./instance.mjs" ;(async function() { - await app({sandbox:process.env.SANDBOX}) + await app({sandbox: process.env.SANDBOX}) })() diff --git a/source/app/web/instance.mjs b/source/app/web/instance.mjs index 926776e6..b5e78cbb 100644 --- a/source/app/web/instance.mjs +++ b/source/app/web/instance.mjs @@ -18,7 +18,7 @@ export default async function({sandbox = false} = {}) { //Sandbox mode if (sandbox) { console.debug("metrics/app > sandbox mode is specified, enabling advanced features") - Object.assign(conf.settings, {sandbox:true, optimize:true, cached:0, "plugins.default":true, extras:{default:true}}) + Object.assign(conf.settings, {sandbox: true, optimize: true, cached: 0, "plugins.default": true, extras: {default: true}}) } const {token, maxusers = 0, restricted = [], debug = false, cached = 30 * 60 * 1000, port = 3000, ratelimiter = null, plugins = null} = conf.settings const mock = sandbox || conf.settings.mocked @@ -48,10 +48,10 @@ export default async function({sandbox = false} = {}) { conf.settings.token = "MOCKED_TOKEN" } if (debug) - console.debug(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256})) + console.debug(util.inspect(conf.settings, {depth: Infinity, maxStringLength: 256})) //Load octokits - const api = {graphql:octokit.graphql.defaults({headers:{authorization:`token ${token}`}}), rest:new OctokitRest.Octokit({auth:token})} + const api = {graphql: octokit.graphql.defaults({headers: {authorization: `token ${token}`}}), rest: new OctokitRest.Octokit({auth: token})} //Apply mocking if needed if (mock) Object.assign(api, await mocks(api)) @@ -68,8 +68,8 @@ export default async function({sandbox = false} = {}) { skip(req, _res) { return !!cache.get(req.params.login) }, - message:"Too many requests: retry later", - headers:true, + message: "Too many requests: retry later", + headers: true, ...ratelimiter, })) } @@ -84,24 +84,24 @@ export default async function({sandbox = false} = {}) { }) //Base routes - const limiter = ratelimit({max:debug ? Number.MAX_SAFE_INTEGER : 60, windowMs:60 * 1000, headers:false}) + const limiter = ratelimit({max: debug ? Number.MAX_SAFE_INTEGER : 60, windowMs: 60 * 1000, headers: false}) const metadata = Object.fromEntries( Object.entries(conf.metadata.plugins) .map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "category", "web", "supports", "scopes"].includes(key)))]) - .map(([key, value]) => [key, key === "core" ? {...value, web:Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value]))} : value]), + .map(([key, value]) => [key, key === "core" ? {...value, web: Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value]))} : value]), ) - const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category:metadata[name]?.category ?? "community", enabled:plugins[name]?.enabled ?? false})) - const templates = Object.entries(Templates).map(([name]) => ({name, enabled:(conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false})) - const actions = {flush:new Map()} - const requests = {rest:{limit:0, used:0, remaining:0, reset:NaN}, graphql:{limit:0, used:0, remaining:0, reset:NaN}} + const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category: metadata[name]?.category ?? "community", enabled: plugins[name]?.enabled ?? false})) + const templates = Object.entries(Templates).map(([name]) => ({name, enabled: (conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false})) + const actions = {flush: new Map()} + const requests = {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}} let _requests_refresh = false if (!conf.settings.notoken) { const refresh = async () => { try { const {limit} = await graphql("{ limit:rateLimit {limit remaining reset:resetAt used} }") Object.assign(requests, { - rest:(await rest.rateLimit.get()).data.rate, - graphql:{...limit, reset:new Date(limit.reset).getTime()}, + rest: (await rest.rateLimit.get()).data.rate, + graphql: {...limit, reset: new Date(limit.reset).getTime()}, }) } catch { @@ -245,9 +245,9 @@ export default async function({sandbox = false} = {}) { console.debug(`metrics/app/${login} > awaiting pending request`) await pending.get(login) } - else + else { pending.set(login, new Promise(_solve => solve = _solve)) - + } //Read cached data if possible if ((!debug) && (cached) && (cache.get(login))) { @@ -273,7 +273,7 @@ export default async function({sandbox = false} = {}) { try { //Render const q = req.query - console.debug(`metrics/app/${login} > ${util.inspect(q, {depth:Infinity, maxStringLength:256})}`) + console.debug(`metrics/app/${login} > ${util.inspect(q, {depth: Infinity, maxStringLength: 256})}`) if ((q["config.presets"]) && (conf.settings.extras?.presets ?? conf.settings.extras?.default ?? false)) { console.debug(`metrics/app/${login} > presets have been specified, loading them`) Object.assign(q, await presets(q["config.presets"])) @@ -283,9 +283,9 @@ export default async function({sandbox = false} = {}) { rest, plugins, conf, - die:q["plugins.errors.fatal"] ?? false, - verify:q.verify ?? false, - convert:["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(q["config.output"]) ? q["config.output"] : null, + die: q["plugins.errors.fatal"] ?? false, + verify: q.verify ?? false, + convert: ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(q["config.output"]) ? q["config.output"] : null, }, {Plugins, Templates}) //Cache if ((!debug) && (cached)) { @@ -331,13 +331,14 @@ export default async function({sandbox = false} = {}) { }) //Listen - app.listen(port, () => console.log([ + app.listen(port, () => + console.log([ `Listening on port │ ${port}`, `Debug mode │ ${debug}`, `Mocked data │ ${conf.settings.mocked ?? false}`, `Restricted to users │ ${restricted.size ? [...restricted].join(", ") : "(unrestricted)"}`, `Cached time │ ${cached} seconds`, - `Rate limiter │ ${ratelimiter ? util.inspect(ratelimiter, {depth:Infinity, maxStringLength:256}) : "(enabled)"}`, + `Rate limiter │ ${ratelimiter ? util.inspect(ratelimiter, {depth: Infinity, maxStringLength: 256}) : "(enabled)"}`, `Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`, `Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`, `SVG optimization │ ${conf.settings.optimize ?? false}`, diff --git a/source/app/web/statics/about/script.js b/source/app/web/statics/about/script.js index 7653709d..f85f28cd 100644 --- a/source/app/web/statics/about/script.js +++ b/source/app/web/statics/about/script.js @@ -6,7 +6,7 @@ async mounted() { //Palette try { - this.palette = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") + this.palette = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" } catch (error) {} //Embed @@ -26,17 +26,17 @@ await Promise.all([ //GitHub limit tracker (async () => { - const { data: requests } = await axios.get("/.requests") + const {data: requests} = await axios.get("/.requests") this.requests = requests })(), //Version (async () => { - const { data: version } = await axios.get("/.version") + const {data: version} = await axios.get("/.version") this.version = `v${version}` })(), //Hosted (async () => { - const { data: hosted } = await axios.get("/.hosted") + const {data: hosted} = await axios.get("/.hosted") this.hosted = hosted })(), ]) @@ -89,12 +89,12 @@ this.metrics = (await axios.get(`/about/query/${this.user}`)).data } catch (error) { - this.error = { code: error.response.status, message: error.response.data } + this.error = {code: error.response.status, message: error.response.data} } finally { this.pending = false try { - const { data: requests } = await axios.get("/.requests") + const {data: requests} = await axios.get("/.requests") this.requests = requests } catch {} @@ -104,10 +104,10 @@ //Computed properties computed: { ranked() { - return this.metrics?.rendered.plugins.achievements.list?.filter(({ leaderboard }) => leaderboard).sort((a, b) => a.leaderboard.type.localeCompare(b.leaderboard.type)) ?? [] + return this.metrics?.rendered.plugins.achievements.list?.filter(({leaderboard}) => leaderboard).sort((a, b) => a.leaderboard.type.localeCompare(b.leaderboard.type)) ?? [] }, achievements() { - return this.metrics?.rendered.plugins.achievements.list?.filter(({ leaderboard }) => !leaderboard).filter(({ title }) => !/(?:automator|octonaut|infographile)/i.test(title)) ?? [] + return this.metrics?.rendered.plugins.achievements.list?.filter(({leaderboard}) => !leaderboard).filter(({title}) => !/(?:automator|octonaut|infographile)/i.test(title)) ?? [] }, introduction() { return this.metrics?.rendered.plugins.introduction?.text ?? "" @@ -138,8 +138,8 @@ account() { if (!this.metrics) return null - const { login, name } = this.metrics.rendered.user - return { login, name, avatar: this.metrics.rendered.computed.avatar, type: this.metrics?.rendered.account } + const {login, name} = this.metrics.rendered.user + return {login, name, avatar: this.metrics.rendered.computed.avatar, type: this.metrics?.rendered.account} }, url() { return `${window.location.protocol}//${window.location.host}/about/${this.user}` @@ -160,7 +160,7 @@ embed: false, localstorage: false, searchable: false, - requests: { rest: { limit: 0, used: 0, remaining: 0, reset: NaN }, graphql: { limit: 0, used: 0, remaining: 0, reset: NaN } }, + requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}}, palette: "light", metrics: null, pending: false, diff --git a/source/app/web/statics/app.js b/source/app/web/statics/app.js index 21ddb9de..f44ad097 100644 --- a/source/app/web/statics/app.js +++ b/source/app/web/statics/app.js @@ -1,6 +1,6 @@ ;(async function() { //Init - const { data: metadata } = await axios.get("/.plugins.metadata") + const {data: metadata} = await axios.get("/.plugins.metadata") delete metadata.core.web.output delete metadata.core.web.twemojis //App @@ -11,49 +11,49 @@ //Interpolate config from browser try { this.config.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone - this.palette = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light") + this.palette = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" } catch (error) {} //Init await Promise.all([ //GitHub limit tracker (async () => { - const { data: requests } = await axios.get("/.requests") + const {data: requests} = await axios.get("/.requests") this.requests = requests })(), //Templates (async () => { - const { data: templates } = await axios.get("/.templates") + const {data: templates} = await axios.get("/.templates") templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name)) this.templates.list = templates this.templates.selected = templates[0]?.name || "classic" })(), //Plugins (async () => { - const { data: plugins } = await axios.get("/.plugins") - this.plugins.list = plugins.filter(({ name }) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization")) - const categories = [...new Set(this.plugins.list.map(({ category }) => category))] + const {data: plugins} = await axios.get("/.plugins") + this.plugins.list = plugins.filter(({name}) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization")) + const categories = [...new Set(this.plugins.list.map(({category}) => category))] this.plugins.categories = Object.fromEntries(categories.map(category => [category, this.plugins.list.filter(value => category === value.category)])) })(), //Base (async () => { - const { data: base } = await axios.get("/.plugins.base") + const {data: base} = await axios.get("/.plugins.base") this.plugins.base = base this.plugins.enabled.base = Object.fromEntries(base.map(key => [key, true])) })(), //Version (async () => { - const { data: version } = await axios.get("/.version") + const {data: version} = await axios.get("/.version") this.version = `v${version}` })(), //Hosted (async () => { - const { data: hosted } = await axios.get("/.hosted") + const {data: hosted} = await axios.get("/.hosted") this.hosted = hosted })(), ]) //Generate placeholder - this.mock({ timeout: 200 }) + this.mock({timeout: 200}) setInterval(() => { const marker = document.querySelector("#metrics-end") if (marker) { @@ -62,7 +62,7 @@ } }, 100) }, - components: { Prism: PrismComponent }, + components: {Prism: PrismComponent}, //Watchers watch: { tab: { @@ -90,10 +90,10 @@ tab: "overview", palette: "light", clipboard: null, - requests: { rest: { limit: 0, used: 0, remaining: 0, reset: NaN }, graphql: { limit: 0, used: 0, remaining: 0, reset: NaN } }, + requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}}, cached: new Map(), - config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, { defaulted }]) => [key, defaulted])), - metadata: Object.fromEntries(Object.entries(metadata).map(([key, { web }]) => [key, web])), + config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, {defaulted}]) => [key, defaulted])), + metadata: Object.fromEntries(Object.entries(metadata).map(([key, {web}]) => [key, web])), hosted: null, docs: { overview: { @@ -121,15 +121,15 @@ "base.community": "Community stats", "base.repositories": "Repositories metrics", "base.metadata": "Metadata", - ...Object.fromEntries(Object.entries(metadata).map(([key, { name }]) => [key, name])), + ...Object.fromEntries(Object.entries(metadata).map(([key, {name}]) => [key, name])), }, options: { - descriptions: { ...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, { web }]) => web))) }, + descriptions: {...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)))}, ...(Object.fromEntries( Object.entries( - Object.assign({}, ...Object.entries(metadata).flatMap(([key, { web }]) => web)), + Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)), ) - .map(([key, { defaulted }]) => [key, defaulted]), + .map(([key, {defaulted}]) => [key, defaulted]), )), }, }, @@ -157,7 +157,7 @@ computed: { //Unusable plugins unusable() { - return this.plugins.list.filter(({ name }) => this.plugins.enabled[name]).filter(({ enabled }) => !enabled).map(({ name }) => name) + return this.plugins.list.filter(({name}) => this.plugins.enabled[name]).filter(({enabled}) => !enabled).map(({name}) => name) }, //User's avatar avatar() { @@ -239,13 +239,13 @@ ` base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ") || '""'}`, ...[ ...Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) => - ` ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}` + ` ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}` ), ...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(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}` + ` plugin_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}` ), - ...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => ` config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`), + ...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => ` config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`), ].sort(), ].join("\n") }, @@ -276,7 +276,7 @@ methods: { //Refresh computed properties async refresh() { - const keys = { action: ["scopes", "action"], markdown: ["url", "embed"] }[this.tab] + const keys = {action: ["scopes", "action"], markdown: ["url", "embed"]}[this.tab] if (keys) { for (const key of keys) this._computedWatchers[key]?.run() @@ -284,7 +284,7 @@ } }, //Load and render placeholder image - async mock({ timeout = 600 } = {}) { + async mock({timeout = 600} = {}) { this.refresh() clearTimeout(this.templates.placeholder.timeout) this.templates.placeholder.timeout = setTimeout(async () => { @@ -315,12 +315,12 @@ this.generated.error = null } catch (error) { - this.generated.error = { code: error.response.status, message: error.response.data } + this.generated.error = {code: error.response.status, message: error.response.data} } finally { this.generated.pending = false try { - const { data: requests } = await axios.get("/.requests") + const {data: requests} = await axios.get("/.requests") this.requests = requests } catch {} diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index 272355d8..b7fe65f6 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -1,4 +1,4 @@ -;(function({ axios, faker, ejs } = { axios: globalThis.axios, faker: globalThis.faker, ejs: globalThis.ejs }) { +;(function({axios, faker, ejs} = {axios: globalThis.axios, faker: globalThis.faker, ejs: globalThis.ejs}) { //Load assets const cached = new Map() async function load(url) { @@ -31,7 +31,7 @@ //Placeholder function globalThis.placeholder = async function(set) { //Load templates informations - let { image, style, fonts, partials } = await load(`/.templates/${set.templates.selected}`) + let {image, style, fonts, partials} = await load(`/.templates/${set.templates.selected}`) await Promise.all(partials.map(async partial => await load(`/.templates/${set.templates.selected}/partials/${escape(partial)}.ejs`))) //Trap includes image = image.replace(/<%-\s*await include[(](`.*?[.]ejs`)[)]\s*%>/g, (m, g) => `<%- await $include(${g}) %>`) @@ -45,11 +45,11 @@ partials: new Set([...(set.config.order || "").split(",").map(x => x.trim()).filter(x => partials.includes(x)), ...partials]), //Plural helper s(value, end = "") { - return value !== 1 ? { y: "ies", "": "s" }[end] : end + return value !== 1 ? {y: "ies", "": "s"}[end] : end }, //Formatter helper - f(n, { sign = false } = {}) { - for (const { u, v } of [{ u: "b", v: 10 ** 9 }, { u: "m", v: 10 ** 6 }, { u: "k", v: 10 ** 3 }]) { + f(n, {sign = false} = {}) { + for (const {u, v} of [{u: "b", v: 10 ** 9}, {u: "m", v: 10 ** 6}, {u: "k", v: 10 ** 3}]) { if (n / v >= 1) return `${(sign) && (n > 0) ? "+" : ""}${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` } @@ -58,10 +58,10 @@ //Trap for includes async $include(path) { const partial = await load(`/.templates/${set.templates.selected}/${escape(path)}`) - return await ejs.render(partial, data, { async: true, rmWhitespace: true }) + return await ejs.render(partial, data, {async: true, rmWhitespace: true}) }, //Meta-data - meta: { version: set.version, author: "lowlighter", generated: new Date().toGMTString().replace(/GMT$/g, "").trim() }, + meta: {version: set.version, author: "lowlighter", generated: new Date().toGMTString().replace(/GMT$/g, "").trim()}, //Animated animated: false, //Display size @@ -70,30 +70,30 @@ //Config config: set.config, //Extras - extras: { css: options["extras.css"] ?? "" }, + extras: {css: options["extras.css"] ?? ""}, //Base elements base: set.plugins.enabled.base, //Computed elements computed: { commits: faker.datatype.number(10000), sponsorships: faker.datatype.number(10), - licenses: { favorite: [""], used: { MIT: 1 }, about: {} }, - token: { scopes: [] }, + licenses: {favorite: [""], used: {MIT: 1}, about: {}}, + token: {scopes: []}, repositories: { watchers: faker.datatype.number(1000), stargazers: faker.datatype.number(10000), issues_open: faker.datatype.number(1000), issues_closed: faker.datatype.number(1000), pr_open: faker.datatype.number(1000), - pr_closed: { totalCount: faker.datatype.number(100) }, + pr_closed: {totalCount: faker.datatype.number(100)}, pr_merged: faker.datatype.number(1000), forks: faker.datatype.number(1000), releases: faker.datatype.number(1000), }, - diskUsage: `${faker.datatype.float({ min: 1, max: 999 }).toFixed(1)}MB`, - registration: `${faker.datatype.number({ min: 2, max: 10 })} years ago`, + diskUsage: `${faker.datatype.float({min: 1, max: 999}).toFixed(1)}MB`, + registration: `${faker.datatype.number({min: 2, max: 10})} years ago`, cakeday: false, - calendar: new Array(14).fill(null).map(_ => ({ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) })), + calendar: new Array(14).fill(null).map(_ => ({color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])})), avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", }, //User data @@ -107,12 +107,12 @@ websiteUrl: options["pagespeed.url"] || "(attached website)", isHireable: false, twitterUsername: options["tweets.user"] || "(attached Twitter account)", - repositories: { totalCount: faker.datatype.number(100), totalDiskUsage: faker.datatype.number(100000), nodes: [] }, - packages: { totalCount: faker.datatype.number(10) }, - starredRepositories: { totalCount: faker.datatype.number(1000) }, - watching: { totalCount: faker.datatype.number(100) }, - sponsorshipsAsSponsor: { totalCount: faker.datatype.number(10) }, - sponsorshipsAsMaintainer: { totalCount: faker.datatype.number(10) }, + repositories: {totalCount: faker.datatype.number(100), totalDiskUsage: faker.datatype.number(100000), nodes: []}, + packages: {totalCount: faker.datatype.number(10)}, + starredRepositories: {totalCount: faker.datatype.number(1000)}, + watching: {totalCount: faker.datatype.number(100)}, + sponsorshipsAsSponsor: {totalCount: faker.datatype.number(10)}, + sponsorshipsAsMaintainer: {totalCount: faker.datatype.number(10)}, contributionsCollection: { totalRepositoriesWithContributedCommits: faker.datatype.number(100), totalCommitContributions: faker.datatype.number(10000), @@ -121,12 +121,12 @@ totalPullRequestContributions: faker.datatype.number(1000), totalPullRequestReviewContributions: faker.datatype.number(1000), }, - calendar: { contributionCalendar: { weeks: [] } }, - repositoriesContributedTo: { totalCount: faker.datatype.number(100) }, - followers: { totalCount: faker.datatype.number(1000) }, - following: { totalCount: faker.datatype.number(1000) }, - issueComments: { totalCount: faker.datatype.number(1000) }, - organizations: { totalCount: faker.datatype.number(10) }, + calendar: {contributionCalendar: {weeks: []}}, + repositoriesContributedTo: {totalCount: faker.datatype.number(100)}, + followers: {totalCount: faker.datatype.number(1000)}, + following: {totalCount: faker.datatype.number(1000)}, + issueComments: {totalCount: faker.datatype.number(1000)}, + organizations: {totalCount: faker.datatype.number(10)}, }, //Plugins plugins: { @@ -148,7 +148,7 @@ id: faker.datatype.number(100000000000000).toString(), created_at: faker.date.recent(), entities: { - mentions: [{ start: 22, end: 33, username: "lowlighter" }], + mentions: [{start: 22, end: 33, username: "lowlighter"}], }, text: 'Checkout metrics from @lowlighter ! #GitHub ', mentions: ["lowlighter"], @@ -177,7 +177,7 @@ ? ({ traffic: { views: { - count: `${faker.datatype.number({ min: 10, max: 100 })}.${faker.datatype.number(9)}k`, + count: `${faker.datatype.number({min: 10, max: 100})}.${faker.datatype.number(9)}k`, uniques: `${faker.datatype.number(10)}.${faker.datatype.number(9)}k`, }, }, @@ -264,7 +264,7 @@ ? { user: { commits: faker.datatype.number(100), - percentage: faker.datatype.float({ max: 1 }), + percentage: faker.datatype.float({max: 1}), maintainer: false, stars: faker.datatype.number(100), }, @@ -365,7 +365,7 @@ unlock: null, text: faker.lorem.sentence(), get icon() { - const colors = { S: ["#FF0000", "#FF8500"], A: ["#B59151", "#FFD576"], B: ["#7D6CFF", "#B2A8FF"], C: ["#2088FF", "#79B8FF"], $: ["#FF48BD", "#FF92D8"], X: ["#7A7A7A", "#B0B0B0"] } + const colors = {S: ["#FF0000", "#FF8500"], A: ["#B59151", "#FFD576"], B: ["#7D6CFF", "#B2A8FF"], C: ["#2088FF", "#79B8FF"], $: ["#FF48BD", "#FF92D8"], X: ["#7A7A7A", "#B0B0B0"]} return `` .replace(/#primary/g, colors[this.rank][0]) .replace(/#secondary/g, colors[this.rank][1]) @@ -374,9 +374,9 @@ progress: faker.datatype.number(100) / 100, value: faker.datatype.number(1000), })) - .filter(({ rank }) => options["achievements.secrets"] ? true : rank !== "$") - .filter(({ rank }) => ({ S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[rank] >= { S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[options["achievements.threshold"]])) - .sort((a, b) => ({ S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[b.rank] + b.progress * 0.99) - ({ S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[a.rank] + a.progress * 0.99)) + .filter(({rank}) => options["achievements.secrets"] ? true : rank !== "$") + .filter(({rank}) => ({S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[rank] >= {S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[options["achievements.threshold"]])) + .sort((a, b) => ({S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[b.rank] + b.progress * 0.99) - ({S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[a.rank] + a.progress * 0.99)) .slice(0, options["achievements.limit"] || Infinity), }, }) @@ -437,26 +437,26 @@ sections: options["languages.sections"].split(", ").map(x => x.trim()).filter(x => /^(most-used|recently-used)$/.test(x)), details: options["languages.details"].split(",").map(x => x.trim()).filter(x => x), get colors() { - return Object.fromEntries(Object.entries(this.favorites).map(([key, { color }]) => [key, color])) + return Object.fromEntries(Object.entries(this.favorites).map(([key, {color}]) => [key, color])) }, total: faker.datatype.number(10000), get stats() { - return Object.fromEntries(Object.entries(this.favorites).map(([key, { value }]) => [key, value])) + return Object.fromEntries(Object.entries(this.favorites).map(([key, {value}]) => [key, value])) }, ["stats.recent"]: { total: faker.datatype.number(10000), get lines() { - return Object.fromEntries(Object.entries(this.favorites).map(([key, { value }]) => [key, value])) + return Object.fromEntries(Object.entries(this.favorites).map(([key, {value}]) => [key, value])) }, get stats() { - return Object.fromEntries(Object.entries(this.favorites).map(([key, { value }]) => [key, value])) + return Object.fromEntries(Object.entries(this.favorites).map(([key, {value}]) => [key, value])) }, commits: faker.datatype.number(500), files: faker.datatype.number(1000), days: Number(options["languages.recent.days"]), }, - favorites: distribution(7).map((value, index, array) => ({ name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0) })), - recent: distribution(7).map((value, index, array) => ({ name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0) })), + favorites: distribution(7).map((value, index, array) => ({name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0)})), + recent: distribution(7).map((value, index, array) => ({name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0)})), }, }) : null), @@ -536,7 +536,7 @@ }, }, }, - indents: { style: "spaces", spaces: 1, tabs: 0 }, + indents: {style: "spaces", spaces: 1, tabs: 0}, linguist: { available: true, get ordered() { @@ -554,7 +554,7 @@ ? ({ get people() { const types = options["people.types"].split(",").map(x => x.trim()) - .map(x => ({ followed: "following", sponsors: "sponsorshipsAsMaintainer", sponsored: "sponsorshipsAsSponsor", sponsoring: "sponsorshipsAsSponsor" })[x] ?? x) + .map(x => ({followed: "following", sponsors: "sponsorshipsAsMaintainer", sponsored: "sponsorshipsAsSponsor", sponsoring: "sponsorshipsAsSponsor"})[x] ?? x) .filter(x => ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor"].includes(x)) return { types, @@ -598,8 +598,8 @@ data: new Array(12).fill(null).map(_ => ({ timeUTCHumanReadable: `${new Date().getUTCHours()}:${new Date().getUTCMinutes()}`, color: faker.random.arrayElement(["#9be9a8", "#40c463", "#30a14e", "#216e39"]), - sgv: faker.datatype.number({ min: 40, max: 400 }), - delta: faker.datatype.number({ min: -10, max: 10 }), + sgv: faker.datatype.number({min: 40, max: 400}), + delta: faker.datatype.number({min: -10, max: 10}), direction: faker.random.arrayElement(["SingleUp", "DoubleUp", "FortyFiveUp", "Flat", "FortyFiveDown", "SingleDown", "DoubleDown"]), alert: faker.random.arrayElement(["Normal", "Urgent High", "Urgent Low", "High", "Low"]), arrowHumanReadable: faker.random.arrayElement(["↑↑", "↑", "↗", "→", "↘", "↓", "↓↓"]), @@ -611,9 +611,9 @@ ...(set.plugins.enabled.fortune ? ({ fortune: faker.random.arrayElement([ - { chance: .06, color: "#43FD3B", text: "Good news will come to you by mail" }, - { chance: .06, color: "#00CBB0", text: "キタ━━━━━━(゚∀゚)━━━━━━ !!!!" }, - { chance: 0.03, color: "#FD4D32", text: "Excellent Luck" }, + {chance: .06, color: "#43FD3B", text: "Good news will come to you by mail"}, + {chance: .06, color: "#00CBB0", text: "キタ━━━━━━(゚∀゚)━━━━━━ !!!!"}, + {chance: 0.03, color: "#FD4D32", text: "Excellent Luck"}, ]), }) : null), @@ -624,10 +624,10 @@ url: options["pagespeed.url"] || "(attached website url)", detailed: options["pagespeed.detailed"] || false, scores: [ - { score: faker.datatype.float({ max: 1 }), title: "Performance" }, - { score: faker.datatype.float({ max: 1 }), title: "Accessibility" }, - { score: faker.datatype.float({ max: 1 }), title: "Best Practices" }, - { score: faker.datatype.float({ max: 1 }), title: "SEO" }, + {score: faker.datatype.float({max: 1}), title: "Performance"}, + {score: faker.datatype.float({max: 1}), title: "Accessibility"}, + {score: faker.datatype.float({max: 1}), title: "Best Practices"}, + {score: faker.datatype.float({max: 1}), title: "SEO"}, ], metrics: { observedFirstContentfulPaint: faker.datatype.number(500), @@ -639,12 +639,12 @@ maxPotentialFID: faker.datatype.number(500), observedLoad: faker.datatype.number(500), firstMeaningfulPaint: faker.datatype.number(500), - observedCumulativeLayoutShift: faker.datatype.float({ max: 1 }), + observedCumulativeLayoutShift: faker.datatype.float({max: 1}), observedSpeedIndex: faker.datatype.number(1000), observedSpeedIndexTs: faker.time.recent(), observedTimeOriginTs: faker.time.recent(), observedLargestContentfulPaint: faker.datatype.number(1000), - cumulativeLayoutShift: faker.datatype.float({ max: 1 }), + cumulativeLayoutShift: faker.datatype.float({max: 1}), observedFirstPaintTs: faker.time.recent(), observedTraceEndTs: faker.time.recent(), largestContentfulPaint: faker.datatype.number(2000), @@ -698,14 +698,14 @@ ? ({ discussions: { categories: { - stats: { "🙏 Q&A": faker.datatype.number(100), "📣 Announcements": faker.datatype.number(100), "💡 Ideas": faker.datatype.number(100), "💬 General": faker.datatype.number(100) }, + stats: {"🙏 Q&A": faker.datatype.number(100), "📣 Announcements": faker.datatype.number(100), "💡 Ideas": faker.datatype.number(100), "💬 General": faker.datatype.number(100)}, favorite: "📣 Announcements", }, - upvotes: { discussions: faker.datatype.number(1000), comments: faker.datatype.number(1000) }, + upvotes: {discussions: faker.datatype.number(1000), comments: faker.datatype.number(1000)}, started: faker.datatype.number(1000), comments: faker.datatype.number(1000), answers: faker.datatype.number(1000), - display: { categories: options["discussions.categories"] ? { limit: options["discussions.categories.limit"] || Infinity } : null }, + display: {categories: options["discussions.categories"] ? {limit: options["discussions.categories.limit"] || Infinity} : null}, }, }) : null), @@ -730,7 +730,7 @@ ? ({ topics: { mode: options["topics.mode"], - type: { starred: "labels", labels: "labels", mastered: "icons", icons: "icons" }[options["topics.mode"]] || "labels", + type: {starred: "labels", labels: "labels", mastered: "icons", icons: "icons"}[options["topics.mode"]] || "labels", list: new Array(Number(options["topics.limit"]) || 20).fill(null).map(_ => ({ name: faker.lorem.words(2), description: faker.lorem.sentence(), @@ -759,8 +759,8 @@ totalCount: faker.datatype.number(100), }, stargazerCount: faker.datatype.number(10000), - licenseInfo: { nickname: null, name: "MIT License" }, - primaryLanguage: { color: "#f1e05a", name: "JavaScript" }, + licenseInfo: {nickname: null, name: "MIT License"}, + primaryLanguage: {color: "#f1e05a", name: "JavaScript"}, }, starred: "1 day ago", }, @@ -779,8 +779,8 @@ totalCount: faker.datatype.number(100), }, stargazerCount: faker.datatype.number(10000), - licenseInfo: { nickname: null, name: "License" }, - primaryLanguage: { color: faker.internet.color(), name: faker.lorem.word() }, + licenseInfo: {nickname: null, name: "License"}, + primaryLanguage: {color: faker.internet.color(), name: faker.lorem.word()}, }, starred: `${i + 2} days ago`, })), @@ -825,8 +825,8 @@ totalCount: faker.datatype.number(100), }, stargazerCount: faker.datatype.number(10000), - licenseInfo: { nickname: null, name: "License" }, - primaryLanguage: { color: faker.internet.color(), name: faker.lorem.word() }, + licenseInfo: {nickname: null, name: "License"}, + primaryLanguage: {color: faker.internet.color(), name: faker.lorem.word()}, })), }, }) @@ -861,7 +861,7 @@ for (let d = -14; d <= 0; d++) { const date = new Date(Date.now() - d * 24 * 60 * 60 * 1000).toISOString().substring(0, 10) dates.push(date) - result.total.dates[date] = (total += (result.increments.dates[date] = faker.datatype.number(100))) + result.total.dates[date] = total += result.increments.dates[date] = faker.datatype.number(100) } return result }, @@ -884,13 +884,13 @@ percents -= result.percent result.percent /= 100 } - results.filter(({ name }) => elements.includes(name) ? false : (elements.push(name), true)) + results.filter(({name}) => elements.includes(name) ? false : (elements.push(name), true)) return results.sort((a, b) => b.percent - a.percent) } return { sections: options["wakatime.sections"].split(",").map(x => x.trim()).filter(x => x), days: Number(options["wakatime.days"]) || 7, - time: { total: faker.datatype.number(100000), daily: faker.datatype.number(24) }, + time: {total: faker.datatype.number(100000), daily: faker.datatype.number(24)}, editors: stats(["VS Code", "Chrome", "IntelliJ", "PhpStorm", "WebStorm", "Android Studio", "Visual Studio", "Sublime Text", "PyCharm", "Vim", "Atom", "Xcode"]), languages: stats(["JavaScript", "TypeScript", "PHP", "Java", "Python", "Vue.js", "HTML", "C#", "JSON", "Dart", "SCSS", "Kotlin", "JSX", "Go", "Ruby", "YAML"]), projects: stats(), @@ -909,16 +909,16 @@ count: faker.datatype.number(1000), minutesWatched: faker.datatype.number(100000), episodesWatched: faker.datatype.number(10000), - genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })), + genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})), }, manga: { count: faker.datatype.number(1000), chaptersRead: faker.datatype.number(100000), volumesRead: faker.datatype.number(10000), - genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })), + genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})), }, }, - genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })), + genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})), }, get lists() { const media = type => ({ @@ -929,7 +929,7 @@ genres: new Array(6).fill(null).map(_ => faker.lorem.word()), progress: faker.datatype.number(100), description: faker.lorem.paragraphs(), - scores: { user: faker.datatype.number(100), community: faker.datatype.number(100) }, + scores: {user: faker.datatype.number(100), community: faker.datatype.number(100)}, released: 100 + faker.datatype.number(1000), artwork: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", }) @@ -939,16 +939,16 @@ ...(medias.includes("anime") ? { anime: { - ...(sections.includes("watching") ? { watching: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME")) } : {}), - ...(sections.includes("favorites") ? { favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME")) } : {}), + ...(sections.includes("watching") ? {watching: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME"))} : {}), + ...(sections.includes("favorites") ? {favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME"))} : {}), }, } : {}), ...(medias.includes("manga") ? { manga: { - ...(sections.includes("reading") ? { reading: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA")) } : {}), - ...(sections.includes("favorites") ? { favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA")) } : {}), + ...(sections.includes("reading") ? {reading: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA"))} : {}), + ...(sections.includes("favorites") ? {favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA"))} : {}), }, } : {}), @@ -974,7 +974,7 @@ repo: `${faker.random.word()}/${faker.random.word()}`, size: 1, branch: "master", - commits: [{ sha: faker.git.shortSha(), message: faker.lorem.sentence() }], + commits: [{sha: faker.git.shortSha(), message: faker.lorem.sentence()}], timestamp: faker.date.recent(), }, { @@ -1026,8 +1026,8 @@ user: set.user, number: faker.datatype.number(100), title: faker.lorem.sentence(), - lines: { added: faker.datatype.number(1000), deleted: faker.datatype.number(1000) }, - files: { changed: faker.datatype.number(10) }, + lines: {added: faker.datatype.number(1000), deleted: faker.datatype.number(1000)}, + files: {changed: faker.datatype.number(10)}, timestamp: faker.date.recent(), }, { @@ -1061,13 +1061,13 @@ { type: "ref/create", repo: `${faker.random.word()}/${faker.random.word()}`, - ref: { name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"]) }, + ref: {name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"])}, timestamp: faker.date.recent(), }, { type: "ref/delete", repo: `${faker.random.word()}/${faker.random.word()}`, - ref: { name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"]) }, + ref: {name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"])}, timestamp: faker.date.recent(), }, { @@ -1096,7 +1096,7 @@ ...(set.plugins.enabled.isocalendar ? ({ isocalendar: { - streak: { max: 30 + faker.datatype.number(20), current: faker.datatype.number(30) }, + streak: {max: 30 + faker.datatype.number(20), current: faker.datatype.number(30)}, max: 10 + faker.datatype.number(40), average: faker.datatype.float(10), svg: await staticPlaceholder(set.plugins.enabled.isocalendar, `isocalendar.${options["isocalendar.duration"]}.svg`), @@ -1108,8 +1108,8 @@ ...(set.plugins.enabled.support ? ({ support: { - stats: { solutions: faker.datatype.number(100), posts: faker.datatype.number(1000), topics: faker.datatype.number(1000), received: faker.datatype.number(1000), hearts: faker.datatype.number(1000) }, - badges: { uniques: [], multiples: [], count: faker.datatype.number(1000) }, + stats: {solutions: faker.datatype.number(100), posts: faker.datatype.number(1000), topics: faker.datatype.number(1000), received: faker.datatype.number(1000), hearts: faker.datatype.number(1000)}, + badges: {uniques: [], multiples: [], count: faker.datatype.number(1000)}, }, }) : null), @@ -1200,20 +1200,20 @@ } //Formatters data.f.bytes = function(n) { - for (const { u, v } of [{ u: "E", v: 10 ** 18 }, { u: "P", v: 10 ** 15 }, { u: "T", v: 10 ** 12 }, { u: "G", v: 10 ** 9 }, { u: "M", v: 10 ** 6 }, { u: "k", v: 10 ** 3 }]) { + for (const {u, v} of [{u: "E", v: 10 ** 18}, {u: "P", v: 10 ** 15}, {u: "T", v: 10 ** 12}, {u: "G", v: 10 ** 9}, {u: "M", v: 10 ** 6}, {u: "k", v: 10 ** 3}]) { if (n / v >= 1) return `${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B` } return `${n} byte${n > 1 ? "s" : ""}` } - data.f.percentage = function(n, { rescale = true } = {}) { + data.f.percentage = function(n, {rescale = true} = {}) { return `${ (n * (rescale ? 100 : 1)).toFixed(2) .replace(/[.]([1-9]*)(0+)$/, (m, a, b) => `.${a}`) .replace(/[.]$/, "") }%` } - data.f.ellipsis = function(text, { length = 20 } = {}) { + data.f.ellipsis = function(text, {length = 20} = {}) { text = `${text}` if (text.length < length) return text @@ -1222,11 +1222,11 @@ data.f.date = function(string, options) { if (options.date) { delete options.date - Object.assign(options, { day: "numeric", month: "short", year: "numeric" }) + Object.assign(options, {day: "numeric", month: "short", year: "numeric"}) } if (options.time) { delete options.time - Object.assign(options, { hour: "2-digit", minute: "2-digit", second: "2-digit" }) + Object.assign(options, {hour: "2-digit", minute: "2-digit", second: "2-digit"}) } return new Intl.DateTimeFormat("en-GB", options).format(new Date(string)) } @@ -1234,7 +1234,7 @@ return text?.name ?? text } //Render - return await ejs.render(image, data, { async: true, rmWhitespace: true }) + return await ejs.render(image, data, {async: true, rmWhitespace: true}) } //Reset globals contexts globalThis.placeholder.init = function(globals) { diff --git a/source/plugins/achievements/index.mjs b/source/plugins/achievements/index.mjs index 35a35f32..9e432dbd 100644 --- a/source/plugins/achievements/index.mjs +++ b/source/plugins/achievements/index.mjs @@ -18,48 +18,48 @@ export default async function({login, q, imports, data, computed, graphql, queri 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:["#EB355E", "#731237"], A:["#B59151", "#FFD576"], B:["#7D6CFF", "#B2A8FF"], C:["#2088FF", "#79B8FF"], $:["#FF48BD", "#FF92D8"], X:["#7A7A7A", "#B0B0B0"]} + const order = {S: 5, A: 4, B: 3, C: 2, $: 1, X: 0} + const colors = {S: ["#EB355E", "#731237"], 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}) => ({ - prefix:({S:"Master", A:"Super", B:"Great"}[achievement.rank] ?? ""), + prefix: ({S: "Master", A: "Super", B: "Great"}[achievement.rank] ?? ""), title, - unlock:!/invalid date/i.test(unlock) ? `${imports.format.date(unlock, {time:true})} on ${imports.format.date(unlock, {date:true})}` : null, + unlock: !/invalid date/i.test(unlock) ? `${imports.format.date(unlock, {time: true})} on ${imports.format.date(unlock, {date: true})}` : null, ...achievement, })) - .map(({icon, ...achievement}) => ({icon:icon.replace(/#primary/g, colors[achievement.rank][0]).replace(/#secondary/g, colors[achievement.rank][1]), ...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, display} + return {list: achievements, display} } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } /**Rank */ function rank(x, [c, b, a, s, m]) { if (x >= s) - return {rank:"S", progress:(x - s) / (m - s)} + return {rank: "S", progress: (x - s) / (m - s)} if (x >= a) - return {rank:"A", progress:(x - a) / (m - a)} + return {rank: "A", progress: (x - a) / (m - a)} else if (x >= b) - return {rank:"B", progress:(x - b) / (a - 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} + 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], + user: 1 + user, + total: total[type], type, get top() { return Number(`1${"0".repeat(Math.ceil(Math.log10(this.user)))}`) diff --git a/source/plugins/achievements/list/index.mjs b/source/plugins/achievements/list/index.mjs index 3939641a..c26aecdf 100644 --- a/source/plugins/achievements/list/index.mjs +++ b/source/plugins/achievements/list/index.mjs @@ -1,3 +1,3 @@ //Exports -export {default as organization} from "./organizations.mjs" -export {default as user} from "./users.mjs" +export { default as organization } from "./organizations.mjs" +export { default as user } from "./users.mjs" diff --git a/source/plugins/achievements/list/organizations.mjs b/source/plugins/achievements/list/organizations.mjs index a924528c..9c579d28 100644 --- a/source/plugins/achievements/list/organizations.mjs +++ b/source/plugins/achievements/list/organizations.mjs @@ -2,22 +2,23 @@ export default async function({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) { //Initialization const {organization} = await graphql(queries.achievements.organizations({login})) - const scores = {followers:0, created:organization.repositories.totalCount, stars:organization.popular.nodes?.[0]?.stargazers?.totalCount ?? 0, forks:Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))} + const scores = {followers: 0, created: organization.repositories.totalCount, stars: organization.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} + const requirements = {stars: 5, followers: 3, forks: 1, created: 1} //Developers { const value = organization.repositories.totalCount const unlock = organization.repositories.nodes?.shift() list.push({ - title:"Developers", - text:`Published ${value} public repositor${imports.s(value, "y")}`, - icon:'', + title: "Developers", + text: `Published ${value} public repositor${imports.s(value, "y")}`, + icon: + '', ...rank(value, [1, 50, 100, 200, 300]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.created_rank.userCount, requirement:scores.created >= requirements.created, type:"users"}), + unlock: new Date(unlock?.createdAt), + leaderboard: leaderboard({user: ranks.created_rank.userCount, requirement: scores.created >= requirements.created, type: "users"}), }) } @@ -26,12 +27,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const value = organization.forks.totalCount const unlock = organization.forks.nodes?.shift() list.push({ - title:"Forkers", - text:`Forked ${value} public repositor${imports.s(value, "y")}`, - icon:'', + title: "Forkers", + text: `Forked ${value} public repositor${imports.s(value, "y")}`, + icon: + '', ...rank(value, [1, 10, 30, 50, 100]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -41,12 +43,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = organization.projects.nodes?.shift() list.push({ - title:"Managers", - text:`Created ${value} user project${imports.s(value)}`, - icon:'', + title: "Managers", + text: `Created ${value} user project${imports.s(value)}`, + icon: + '', ...rank(value, [1, 2, 4, 8, 10]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -56,12 +59,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = organization.packages.nodes?.shift() list.push({ - title:"Packagers", - text:`Created ${value} package${imports.s(value)}`, - icon:'', + title: "Packagers", + text: `Created ${value} package${imports.s(value)}`, + icon: + '', ...rank(value, [1, 20, 50, 100, 250]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -71,13 +75,14 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Maintainers", - text:`Maintaining a repository with ${value} star${imports.s(value)}`, - icon:'', + title: "Maintainers", + text: `Maintaining a repository with ${value} star${imports.s(value)}`, + icon: + '', ...rank(value, [1, 5000, 10000, 30000, 50000]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.repo_rank.repositoryCount, requirement:scores.stars >= requirements.stars, type:"repositories"}), + unlock: new Date(unlock?.createdAt), + leaderboard: leaderboard({user: ranks.repo_rank.repositoryCount, requirement: scores.stars >= requirements.stars, type: "repositories"}), }) } @@ -86,28 +91,30 @@ export default async function({list, login, data, computed, imports, graphql, qu const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount)) const unlock = null list.push({ - title:"Inspirers", - text:`Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`, - icon:'', + title: "Inspirers", + text: `Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`, + icon: + '', ...rank(value, [1, 500, 1000, 3000, 5000]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.forks_rank.repositoryCount, requirement:scores.forks >= requirements.forks, type:"repositories"}), + unlock: new Date(unlock?.createdAt), + leaderboard: leaderboard({user: ranks.forks_rank.repositoryCount, requirement: scores.forks >= requirements.forks, type: "repositories"}), }) } //Polyglots { - const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node:{name}}) => name))).size + const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node: {name}}) => name))).size const unlock = null list.push({ - title:"Polyglots", - text:`Using ${value} different programming language${imports.s(value)}`, - icon:'', + title: "Polyglots", + text: `Using ${value} different programming language${imports.s(value)}`, + icon: + '', ...rank(value, [1, 8, 16, 32, 64]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -117,12 +124,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Sponsors", - text:`Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`, - icon:'', + title: "Sponsors", + text: `Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`, + icon: + '', ...rank(value, [1, 5, 10, 20, 50]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -132,27 +140,29 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Organization", - text:`Has ${value} member${imports.s(value)}`, - icon:'', + title: "Organization", + text: `Has ${value} member${imports.s(value)}`, + icon: + '', ...rank(value, [1, 100, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } //Member { - const {years:value} = computed.registered + const {years: value} = computed.registered const unlock = null list.push({ - title:"Member", - text:`Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`, - icon:'', + title: "Member", + text: `Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`, + icon: + '', ...rank(value, [1, 3, 5, 10, 15]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } } diff --git a/source/plugins/achievements/list/users.mjs b/source/plugins/achievements/list/users.mjs index a4b33192..63ca8ad1 100644 --- a/source/plugins/achievements/list/users.mjs +++ b/source/plugins/achievements/list/users.mjs @@ -2,22 +2,23 @@ export default async function({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) { //Initialization 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 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} + const requirements = {stars: 5, followers: 3, forks: 1, created: 1} //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:'', + title: "Developer", + text: `Published ${value} public repositor${imports.s(value, "y")}`, + icon: + '', ...rank(value, [1, 20, 50, 100, 250]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.created_rank.userCount, requirement:scores.created >= requirements.created, type:"users"}), + unlock: new Date(unlock?.createdAt), + leaderboard: leaderboard({user: ranks.created_rank.userCount, requirement: scores.created >= requirements.created, type: "users"}), }) } @@ -26,12 +27,13 @@ export default async function({list, login, data, computed, imports, graphql, qu 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:'', + title: "Forker", + text: `Forked ${value} public repositor${imports.s(value, "y")}`, + icon: + '', ...rank(value, [1, 5, 10, 20, 50]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -41,12 +43,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.pullRequests.nodes?.shift() list.push({ - title:"Contributor", - text:`Opened ${value} pull request${imports.s(value)}`, - icon:'', + title: "Contributor", + text: `Opened ${value} pull request${imports.s(value)}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -56,12 +59,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.projects.nodes?.shift() list.push({ - title:"Manager", - text:`Created ${value} user project${imports.s(value)}`, - icon:'', + title: "Manager", + text: `Created ${value} user project${imports.s(value)}`, + icon: + '', ...rank(value, [1, 2, 3, 4, 5]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -71,12 +75,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.contributionsCollection.pullRequestReviewContributions.nodes?.shift() list.push({ - title:"Reviewer", - text:`Reviewed ${value} pull request${imports.s(value)}`, - icon:'', + title: "Reviewer", + text: `Reviewed ${value} pull request${imports.s(value)}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -86,12 +91,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.packages.nodes?.shift() list.push({ - title:"Packager", - text:`Created ${value} package${imports.s(value)}`, - icon:'', + title: "Packager", + text: `Created ${value} package${imports.s(value)}`, + icon: + '', ...rank(value, [1, 5, 10, 20, 30]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -101,12 +107,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.gists.nodes?.shift() list.push({ - title:"Gister", - text:`Published ${value} gist${imports.s(value)}`, - icon:'', + title: "Gister", + text: `Published ${value} gist${imports.s(value)}`, + icon: + '', ...rank(value, [1, 20, 50, 100, 250]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -116,12 +123,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.organizations.nodes?.shift() list.push({ - title:"Worker", - text:`Joined ${value} organization${imports.s(value)}`, - icon:'', + title: "Worker", + text: `Joined ${value} organization${imports.s(value)}`, + icon: + '', ...rank(value, [1, 2, 4, 8, 10]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -131,12 +139,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.starredRepositories.nodes?.shift() list.push({ - title:"Stargazer", - text:`Starred ${value} repositor${imports.s(value, "y")}`, - icon:'', + title: "Stargazer", + text: `Starred ${value} repositor${imports.s(value, "y")}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -146,12 +155,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.following.nodes?.shift() list.push({ - title:"Follower", - text:`Following ${value} user${imports.s(value)}`, - icon:'', + title: "Follower", + text: `Following ${value} user${imports.s(value)}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -161,13 +171,14 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = user.followers.nodes?.shift() list.push({ - title:"Influencer", - text:`Followed by ${value} user${imports.s(value)}`, - icon:'', + title: "Influencer", + text: `Followed by ${value} user${imports.s(value)}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.user_rank.userCount, requirement:scores.followers >= requirements.followers, type:"users"}), + unlock: new Date(unlock?.createdAt), + leaderboard: leaderboard({user: ranks.user_rank.userCount, requirement: scores.followers >= requirements.followers, type: "users"}), }) } @@ -177,13 +188,14 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Maintainer", - text:`Maintaining a repository with ${value} star${imports.s(value)}`, - icon:'', + title: "Maintainer", + text: `Maintaining a repository with ${value} star${imports.s(value)}`, + icon: + '', ...rank(value, [1, 1000, 5000, 10000, 25000]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.repo_rank.repositoryCount, requirement:scores.stars >= requirements.stars, type:"repositories"}), + unlock: new Date(unlock?.createdAt), + leaderboard: leaderboard({user: ranks.repo_rank.repositoryCount, requirement: scores.stars >= requirements.stars, type: "repositories"}), }) } @@ -192,43 +204,46 @@ export default async function({list, login, data, computed, imports, graphql, qu const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount)) const unlock = null list.push({ - title:"Inspirer", - text:`Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`, - icon:'', + title: "Inspirer", + text: `Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`, + icon: + '', ...rank(value, [1, 100, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), - leaderboard:leaderboard({user:ranks.forks_rank.repositoryCount, requirement:scores.forks >= requirements.forks, type:"repositories"}), + 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 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:'', + title: "Polyglot", + text: `Using ${value} different programming language${imports.s(value)}`, + icon: + '', ...rank(value, [1, 4, 8, 16, 32]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } //Member { - const {years:value} = computed.registered + const {years: value} = computed.registered const unlock = null list.push({ - title:"Member", - text:`Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`, - icon:'', + title: "Member", + text: `Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`, + icon: + '', ...rank(value, [1, 3, 5, 10, 15]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -238,12 +253,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Sponsor", - text:`Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`, - icon:'', + title: "Sponsor", + text: `Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`, + icon: + '', ...rank(value, [1, 3, 5, 10, 25]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -253,12 +269,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Deployer", - text:`Repositories have been deployed ${value} time${imports.s(value)}`, - icon:'', + title: "Deployer", + text: `Repositories have been deployed ${value} time${imports.s(value)}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -268,12 +285,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Chatter", - text:`Participated in discussions ${value} time${imports.s(value)}`, - icon:'', + title: "Chatter", + text: `Participated in discussions ${value} time${imports.s(value)}`, + icon: + '', ...rank(value, [1, 200, 500, 1000, 2500]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -283,12 +301,13 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Helper", - text:`Answered and solved ${value} discussion${imports.s(value)}`, - icon:'', + title: "Helper", + text: `Answered and solved ${value} discussion${imports.s(value)}`, + icon: + '', ...rank(value, [1, 20, 50, 100, 250]), value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -298,13 +317,14 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Verified", - text:"Registered a GPG key to sign commits", - icon:'', - rank:value ? "$" : "X", - progress:value ? 1 : 0, + title: "Verified", + text: "Registered a GPG key to sign commits", + icon: + '', + rank: value ? "$" : "X", + progress: value ? 1 : 0, value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -314,13 +334,14 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Explorer", - text:"Starred a topic on GitHub Explore", - icon:'', - rank:value ? "$" : "X", - progress:value ? 1 : 0, + title: "Explorer", + text: "Starred a topic on GitHub Explore", + icon: + '', + rank: value ? "$" : "X", + progress: value ? 1 : 0, value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } @@ -330,45 +351,48 @@ export default async function({list, login, data, computed, imports, graphql, qu const unlock = null list.push({ - title:"Automator", - text:"Use GitHub Actions to automate profile updates", - icon:'', - rank:value ? "$" : "X", - progress:value ? 1 : 0, + title: "Automator", + text: "Use GitHub Actions to automate profile updates", + icon: + '', + rank: value ? "$" : "X", + progress: value ? 1 : 0, value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } //Infographile { - const {repository:{viewerHasStarred:value}, viewer:{login:_login}} = await graphql(queries.achievements.metrics()) + 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:(value) && (login === _login) ? 1 : 0, + title: "Infographile", + text: "Fervent supporter of metrics", + icon: + '', + rank: (value) && (login === _login) ? "$" : "X", + progress: (value) && (login === _login) ? 1 : 0, value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } //Octonaut { - const {user:{viewerIsFollowing:value}, viewer:{login:_login}} = await graphql(queries.achievements.octocat()) + 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:(value) && (login === _login) ? 1 : 0, + title: "Octonaut", + text: "Following octocat", + icon: + '', + rank: (value) && (login === _login) ? "$" : "X", + progress: (value) && (login === _login) ? 1 : 0, value, - unlock:new Date(unlock?.createdAt), + unlock: new Date(unlock?.createdAt), }) } } diff --git a/source/plugins/activity/index.mjs b/source/plugins/activity/index.mjs index e38cc2a7..a1e181b7 100644 --- a/source/plugins/activity/index.mjs +++ b/source/plugins/activity/index.mjs @@ -7,11 +7,11 @@ export default async function({login, data, rest, q, account, imports}, {enabled return null //Context - let context = {mode:"user"} + let context = {mode: "user"} if (q.repo) { console.debug(`metrics/compute/${login}/plugins > activity > switched to repository mode`) - const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift() - context = {...context, mode:"repository", owner, repo} + const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift() + context = {...context, mode: "repository", owner, repo} } //Load inputs @@ -29,7 +29,7 @@ export default async function({login, data, rest, q, account, imports}, {enabled try { for (let page = 1; page <= pages; page++) { console.debug(`metrics/compute/${login}/plugins > activity > loading page ${page}/${pages}`) - events.push(...(context.mode === "repository" ? await rest.activity.listRepoEvents({owner:context.owner, repo:context.repo}) : await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data) + events.push(...(context.mode === "repository" ? await rest.activity.listRepoEvents({owner: context.owner, repo: context.repo}) : await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data) } } catch { @@ -43,7 +43,7 @@ export default async function({login, data, rest, q, account, imports}, {enabled .filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase()) .filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true) .filter(event => visibility === "public" ? event.public : true) - .map(async ({type, payload, actor:{login:actor}, repo:{name:repo}, created_at}) => { + .map(async ({type, payload, actor: {login: actor}, repo: {name: repo}, created_at}) => { //See https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types const timestamp = new Date(created_at) if ((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo))) @@ -53,110 +53,110 @@ export default async function({login, data, rest, q, account, imports}, {enabled case "CommitCommentEvent": { if (!["created"].includes(payload.action)) return null - const {comment:{user:{login:user}, commit_id:sha, body:content}} = payload + const {comment: {user: {login: user}, commit_id: sha, body: content}} = payload if (ignored.includes(user)) return null - return {type:"comment", on:"commit", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile:null, number:sha.substring(0, 7), title:""} + return {type: "comment", on: "commit", actor, timestamp, repo, content: await imports.markdown(content, {mode: markdown, codelines}), user, mobile: null, number: sha.substring(0, 7), title: ""} } //Created a git branch or tag case "CreateEvent": { - const {ref:name, ref_type:type} = payload - return {type:"ref/create", actor, timestamp, repo, ref:{name, type}} + const {ref: name, ref_type: type} = payload + return {type: "ref/create", actor, timestamp, repo, ref: {name, type}} } //Deleted a git branch or tag case "DeleteEvent": { - const {ref:name, ref_type:type} = payload - return {type:"ref/delete", actor, timestamp, repo, ref:{name, type}} + const {ref: name, ref_type: type} = payload + return {type: "ref/delete", actor, timestamp, repo, ref: {name, type}} } //Forked repository case "ForkEvent": { - const {forkee:{full_name:forked}} = payload - return {type:"fork", actor, timestamp, repo, forked} + const {forkee: {full_name: forked}} = payload + return {type: "fork", actor, timestamp, repo, forked} } //Wiki editions case "GollumEvent": { const {pages} = payload - return {type:"wiki", actor, timestamp, repo, pages:pages.map(({title}) => title)} + return {type: "wiki", actor, timestamp, repo, pages: pages.map(({title}) => title)} } //Commented on an issue case "IssueCommentEvent": { if (!["created"].includes(payload.action)) return null - const {issue:{user:{login:user}, title, number}, comment:{body:content, performed_via_github_app:mobile}} = payload + const {issue: {user: {login: user}, title, number}, comment: {body: content, performed_via_github_app: mobile}} = payload if (ignored.includes(user)) return null - return {type:"comment", on:"issue", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile, number, title} + return {type: "comment", on: "issue", actor, timestamp, repo, content: await imports.markdown(content, {mode: markdown, codelines}), user, mobile, number, title} } //Issue event case "IssuesEvent": { if (!["opened", "closed", "reopened"].includes(payload.action)) return null - const {action, issue:{user:{login:user}, title, number, body:content}} = payload + const {action, issue: {user: {login: user}, title, number, body: content}} = payload if (ignored.includes(user)) return null - return {type:"issue", actor, timestamp, repo, action, user, number, title, content:await imports.markdown(content, {mode:markdown, codelines})} + return {type: "issue", actor, timestamp, repo, action, user, number, title, content: await imports.markdown(content, {mode: markdown, codelines})} } //Activity from repository collaborators case "MemberEvent": { if (!["added"].includes(payload.action)) return null - const {member:{login:user}} = payload + const {member: {login: user}} = payload if (ignored.includes(user)) return null - return {type:"member", actor, timestamp, repo, user} + return {type: "member", actor, timestamp, repo, user} } //Made repository public case "PublicEvent": { - return {type:"public", actor, timestamp, repo} + return {type: "public", actor, timestamp, repo} } //Pull requests events case "PullRequestEvent": { if (!["opened", "closed"].includes(payload.action)) return null - const {action, pull_request:{user:{login:user}, title, number, body:content, additions:added, deletions:deleted, changed_files:changed, merged}} = payload + const {action, pull_request: {user: {login: user}, title, number, body: content, additions: added, deletions: deleted, changed_files: changed, merged}} = payload if (ignored.includes(user)) return null - return {type:"pr", actor, timestamp, repo, action:(action === "closed") && (merged) ? "merged" : action, user, title, number, content:await imports.markdown(content, {mode:markdown, codelines}), lines:{added, deleted}, files:{changed}} + return {type: "pr", actor, timestamp, repo, action: (action === "closed") && (merged) ? "merged" : action, user, title, number, content: await imports.markdown(content, {mode: markdown, codelines}), lines: {added, deleted}, files: {changed}} } //Reviewed a pull request case "PullRequestReviewEvent": { - const {review:{state:review}, pull_request:{user:{login:user}, number, title}} = payload + const {review: {state: review}, pull_request: {user: {login: user}, number, title}} = payload if (ignored.includes(user)) return null - return {type:"review", actor, timestamp, repo, review, user, number, title} + return {type: "review", actor, timestamp, repo, review, user, number, title} } //Commented on a pull request case "PullRequestReviewCommentEvent": { if (!["created"].includes(payload.action)) return null - const {pull_request:{user:{login:user}, title, number}, comment:{body:content, performed_via_github_app:mobile}} = payload + const {pull_request: {user: {login: user}, title, number}, comment: {body: content, performed_via_github_app: mobile}} = payload if (ignored.includes(user)) return null - return {type:"comment", on:"pr", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile, number, title} + return {type: "comment", on: "pr", actor, timestamp, repo, content: await imports.markdown(content, {mode: markdown, codelines}), user, mobile, number, title} } //Pushed commits case "PushEvent": { let {size, commits, ref} = payload - commits = commits.filter(({author:{email}}) => !ignored.includes(email)) + commits = commits.filter(({author: {email}}) => !ignored.includes(email)) if (!commits.length) return null if (commits.slice(-1).pop()?.message.startsWith("Merge branch ")) commits = commits.slice(-1) - return {type:"push", actor, timestamp, repo, size, branch:ref.match(/refs.heads.(?.*)/)?.groups?.branch ?? null, commits:commits.reverse().map(({sha, message}) => ({sha:sha.substring(0, 7), message}))} + return {type: "push", actor, timestamp, repo, size, branch: ref.match(/refs.heads.(?.*)/)?.groups?.branch ?? null, commits: commits.reverse().map(({sha, message}) => ({sha: sha.substring(0, 7), message}))} } //Released case "ReleaseEvent": { if (!["published"].includes(payload.action)) return null - const {action, release:{name, tag_name, prerelease, draft, body:content}} = payload - return {type:"release", actor, timestamp, repo, action, name:name || tag_name, prerelease, draft, content:await imports.markdown(content, {mode:markdown, codelines})} + const {action, release: {name, tag_name, prerelease, draft, body: content}} = payload + return {type: "release", actor, timestamp, repo, action, name: name || tag_name, prerelease, draft, content: await imports.markdown(content, {mode: markdown, codelines})} } //Starred a repository case "WatchEvent": { if (!["started"].includes(payload.action)) return null const {action} = payload - return {type:"star", actor, timestamp, repo, action} + return {type: "star", actor, timestamp, repo, action} } //Unknown event default: { @@ -170,10 +170,10 @@ export default async function({login, data, rest, q, account, imports}, {enabled .slice(0, limit) //Results - return {timestamps, events:activity} + return {timestamps, events: activity} } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/anilist/index.mjs b/source/plugins/anilist/index.mjs index c0cdb553..f1e7bb01 100644 --- a/source/plugins/anilist/index.mjs +++ b/source/plugins/anilist/index.mjs @@ -7,17 +7,17 @@ export default async function({login, data, queries, imports, q, account}, {enab return null //Load inputs - let {limit, "limit.characters":limit_characters, medias, sections, shuffle, user} = imports.metadata.plugins.anilist.inputs({data, account, q}) + let {limit, "limit.characters": limit_characters, medias, sections, shuffle, user} = imports.metadata.plugins.anilist.inputs({data, account, q}) //Initialization - const result = {user:{name:user, stats:null, genres:[]}, lists:Object.fromEntries(medias.map(type => [type, {}])), characters:[], sections} + const result = {user: {name: user, stats: null, genres: []}, lists: Object.fromEntries(medias.map(type => [type, {}])), characters: [], sections} //User statistics for (let retried = false; !retried; retried = true) { try { //Query API console.debug(`metrics/compute/${login}/plugins > anilist > querying api (user statistics)`) - const {data:{data:{User:{statistics:stats}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user}, query:queries.anilist.statistics()}) + const {data: {data: {User: {statistics: stats}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user}, query: queries.anilist.statistics()}) //Format and save results result.user.stats = stats result.user.genres = [...new Set([...stats.anime.genres.map(({genre}) => genre), ...stats.manga.genres.map(({genre}) => genre)])] @@ -34,7 +34,7 @@ export default async function({login, data, queries, imports, q, account}, {enab try { //Query API console.debug(`metrics/compute/${login}/plugins > anilist > querying api (medias lists - ${type})`) - const {data:{data:{MediaListCollection:{lists}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, type:type.toLocaleUpperCase()}, query:queries.anilist.medias()}) + const {data: {data: {MediaListCollection: {lists}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user, type: type.toLocaleUpperCase()}, query: queries.anilist.medias()}) //Format and save results for (const {name, entries} of lists) { //Format results @@ -65,10 +65,10 @@ export default async function({login, data, queries, imports, q, account}, {enab do { try { console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites ${type}s - page ${page})`) - const {data:{data:{User:{favourites:{[type]:{nodes, pageInfo:cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, page}, query:queries.anilist.favorites({type})}) + const {data: {data: {User: {favourites: {[type]: {nodes, pageInfo: cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user, page}, query: queries.anilist.favorites({type})}) page++ next = cursor.hasNextPage - list.push(...await Promise.all(nodes.map(media => format({media:{progess:null, score:null, media}, imports})))) + list.push(...await Promise.all(nodes.map(media => format({media: {progess: null, score: null, media}, imports})))) } catch (error) { if (await retry({login, imports, error})) @@ -95,12 +95,12 @@ export default async function({login, data, queries, imports, q, account}, {enab do { try { console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites characters - page ${page})`) - const {data:{data:{User:{favourites:{characters:{nodes, pageInfo:cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, page}, query:queries.anilist.characters()}) + const {data: {data: {User: {favourites: {characters: {nodes, pageInfo: cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user, page}, query: queries.anilist.characters()}) page++ next = cursor.hasNextPage - for (const {name:{full:name}, image:{medium:artwork}} of nodes) { + for (const {name: {full: name}, image: {medium: artwork}} of nodes) { console.debug(`metrics/compute/${login}/plugins > anilist > processing ${name}`) - characters.push({name, artwork:await imports.imgb64(artwork)}) + characters.push({name, artwork: await imports.imgb64(artwork)}) } } catch (error) { @@ -129,24 +129,24 @@ export default async function({login, data, queries, imports, q, account}, {enab message = `API returned ${status}` error = error.response?.data ?? null } - throw {error:{message, instance:error}} + throw {error: {message, instance: error}} } } /**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 + const {progress, score: userScore, media: {title, description, status, startDate: {year: release}, genres, averageScore, episodes, chapters, type, coverImage: {medium: artwork}}} = media return { - name:title.romaji, + name: title.romaji, type, status, release, genres, progress, - description:description.replace(//g, " "), - scores:{user:userScore, community:averageScore}, - released:type === "ANIME" ? episodes : chapters, - artwork:await imports.imgb64(artwork), + description: description.replace(//g, " "), + scores: {user: userScore, community: averageScore}, + released: type === "ANIME" ? episodes : chapters, + artwork: await imports.imgb64(artwork), } } diff --git a/source/plugins/base/index.mjs b/source/plugins/base/index.mjs index dbe4e850..caae64b1 100644 --- a/source/plugins/base/index.mjs +++ b/source/plugins/base/index.mjs @@ -7,7 +7,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, conf) { //Load inputs console.debug(`metrics/compute/${login}/base > started`) - let {indepth, "repositories.forks":_forks, "repositories.affiliations":_affiliations, "repositories.batch":_batch} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"}) + let {indepth, "repositories.forks": _forks, "repositories.affiliations": _affiliations, "repositories.batch": _batch} = imports.metadata.plugins.base.inputs({data, q, account: "bypass"}) const extras = conf.settings.extras?.features ?? conf.settings.extras?.default const repositories = conf.settings.repositories || 100 const forks = _forks ? "" : ", isFork: false" @@ -29,18 +29,18 @@ export default async function({login, graphql, rest, data, q, queries, imports}, //Query data from GitHub API console.debug(`metrics/compute/${login}/base > account ${account}`) const queried = await graphql(queries.base[account]({login})) - Object.assign(data, {user:queried[account]}) + Object.assign(data, {user: queried[account]}) postprocess?.[account]({login, data}) try { - Object.assign(data.user, (await graphql(queries.base[`${account}.x`]({login, account, "calendar.from":new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to":(new Date()).toISOString()})))[account]) + Object.assign(data.user, (await graphql(queries.base[`${account}.x`]({login, account, "calendar.from": new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to": (new Date()).toISOString()})))[account]) console.debug(`metrics/compute/${login}/base > successfully loaded bulk query`) } catch { console.debug(`metrics/compute/${login}/base > failed to load bulk query, falling back to unit queries`) //Query basic fields const fields = { - user:["packages", "starredRepositories", "watching", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "followers", "following", "issueComments", "organizations", "repositoriesContributedTo(includeUserRepositories: true)"], - organization:["packages", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "membersWithRole"], + user: ["packages", "starredRepositories", "watching", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "followers", "following", "issueComments", "organizations", "repositoriesContributedTo(includeUserRepositories: true)"], + organization: ["packages", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "membersWithRole"], }[account] ?? [] for (const field of fields) { try { @@ -48,7 +48,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, } catch { console.debug(`metrics/compute/${login}/base > failed to retrieve ${field}`) - data.user[field] = {totalCount:NaN} + data.user[field] = {totalCount: NaN} } } //Query repositories fields @@ -68,7 +68,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"] for (const field of fields) { try { - Object.assign(data.user.contributionsCollection, (await graphql(queries.base.contributions({login, account, field, range:""})))[account].contributionsCollection) + Object.assign(data.user.contributionsCollection, (await graphql(queries.base.contributions({login, account, field, range: ""})))[account].contributionsCollection) } catch { console.debug(`metrics/compute/${login}/base > failed to retrieve contributionsCollection.${field}`) @@ -78,17 +78,17 @@ export default async function({login, graphql, rest, data, q, queries, imports}, } //Query calendar try { - Object.assign(data.user, (await graphql(queries.base.calendar({login, "calendar.from":new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to":(new Date()).toISOString()})))[account]) + Object.assign(data.user, (await graphql(queries.base.calendar({login, "calendar.from": new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to": (new Date()).toISOString()})))[account]) } catch { console.debug(`metrics/compute/${login}/base > failed to retrieve contributions calendar`) - data.user.calendar = {contributionCalendar:{weeks:[]}} + data.user.calendar = {contributionCalendar: {weeks: []}} } } } //Query contributions collection over account lifetime instead of last year if (account === "user") { - if ((indepth)&&(extras)) { + if ((indepth) && (extras)) { const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"] const start = new Date(data.user.createdAt) const end = new Date() @@ -111,7 +111,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, //Fetch data from api try { console.debug(`metrics/compute/${login}/plugins > base > loading contributions collections for ${field} from "${from.toISOString()}" to "${dto.toISOString()}"`) - const {[account]:{contributionsCollection}} = await graphql(queries.base.contributions({login, account, field, range:`(from: "${from.toISOString()}", to: "${dto.toISOString()}")`})) + const {[account]: {contributionsCollection}} = await graphql(queries.base.contributions({login, account, field, range: `(from: "${from.toISOString()}", to: "${dto.toISOString()}")`})) collection[field] += contributionsCollection[field] } catch { @@ -127,7 +127,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, else { try { console.debug(`metrics/compute/${login}/base > loading user commits history`) - const {data:{total_count:total = 0}} = await rest.search.commits({q:`author:${login}`}) + const {data: {total_count: total = 0}} = await rest.search.commits({q: `author:${login}`}) data.user.contributionsCollection.totalCommitContributions = Math.max(total, data.user.contributionsCollection.totalCommitContributions) } catch { @@ -136,18 +136,18 @@ export default async function({login, graphql, rest, data, q, queries, imports}, } } //Query repositories from GitHub API - for (const type of ({user:["repositories", "repositoriesContributedTo"], organization:["repositories"]}[account] ?? [])) { + for (const type of ({user: ["repositories", "repositoriesContributedTo"], organization: ["repositories"]}[account] ?? [])) { //Iterate through repositories let cursor = null let pushed = 0 - const options = {repositories:{forks, affiliations, constraints:""}, repositoriesContributedTo:{forks:"", affiliations:"", constraints:", includeUserRepositories: false, contributionTypes: COMMIT"}}[type] ?? null + const options = {repositories: {forks, affiliations, constraints: ""}, repositoriesContributedTo: {forks: "", affiliations: "", constraints: ", includeUserRepositories: false, contributionTypes: COMMIT"}}[type] ?? null data.user[type] = data.user[type] ?? {} data.user[type].nodes = data.user[type].nodes ?? [] do { console.debug(`metrics/compute/${login}/base > retrieving ${type} after ${cursor}`) const request = {} try { - Object.assign(request, await graphql(queries.base.repositories({login, account, type, after:cursor ? `after: "${cursor}"` : "", repositories:Math.min(repositories, {user:_batch, organization:Math.min(25, _batch)}[account]), ...options}))) + Object.assign(request, await graphql(queries.base.repositories({login, account, type, after: cursor ? `after: "${cursor}"` : "", repositories: Math.min(repositories, {user: _batch, organization: Math.min(25, _batch)}[account]), ...options}))) } catch (error) { console.debug(`metrics/compute/${login}/base > failed to retrieve ${_batch} repositories after ${cursor}, this is probably due to an API timeout, halving batch`) @@ -158,7 +158,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, } continue } - const {[account]:{[type]:{edges = [], nodes = []} = {}}} = request + const {[account]: {[type]: {edges = [], nodes = []} = {}}} = request cursor = edges?.[edges?.length - 1]?.cursor data.user[type].nodes.push(...nodes) pushed = nodes.length @@ -176,7 +176,7 @@ export default async function({login, graphql, rest, data, q, queries, imports}, //Fetch missing packages count from ghcr.io using REST API (as GraphQL API does not support it yet) try { console.debug(`metrics/compute/${login}/base > patching packages count if possible`) - const {data:packages} = await rest.packages[{user:"listPackagesForUser", organization:"listPackagesForOrganization"}[account]]({package_type:"container", org:login, username:login}) + const {data: packages} = await rest.packages[{user: "listPackagesForUser", organization: "listPackagesForOrganization"}[account]]({package_type: "container", org: login, username: login}) data.user.packages.totalCount += packages.length console.debug(`metrics/compute/${login}/base > patched packages count (added ${packages.length} from ghcr.io)`) } @@ -184,8 +184,8 @@ export default async function({login, graphql, rest, data, q, queries, imports}, console.debug(`metrics/compute/${login}/base > failed to patch packages count, maybe read:packages scope was not provided`) } //Shared options - let {"repositories.skipped":skipped, "users.ignored":ignored, "commits.authoring":authoring} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"}) - data.shared = {"repositories.skipped":skipped, "users.ignored":ignored, "commits.authoring":authoring, "repositories.batch":_batch} + let {"repositories.skipped": skipped, "users.ignored": ignored, "commits.authoring": authoring} = imports.metadata.plugins.base.inputs({data, q, account: "bypass"}) + data.shared = {"repositories.skipped": skipped, "users.ignored": ignored, "commits.authoring": authoring, "repositories.batch": _batch} console.debug(`metrics/compute/${login}/base > shared options > ${JSON.stringify(data.shared)}`) //Success console.debug(`metrics/compute/${login}/base > graphql query > account ${account} > success`) @@ -213,9 +213,9 @@ const postprocess = { console.debug(`metrics/compute/${login}/base > applying postprocessing`) data.account = "user" Object.assign(data.user, { - isVerified:false, - repositories:{}, - contributionsCollection:{}, + isVerified: false, + repositories: {}, + contributionsCollection: {}, }) }, //Organization @@ -223,44 +223,44 @@ const postprocess = { console.debug(`metrics/compute/${login}/base > applying postprocessing`) data.account = "organization" Object.assign(data.user, { - isHireable:false, - repositories:{}, - starredRepositories:{totalCount:NaN}, - watching:{totalCount:NaN}, - contributionsCollection:{ - totalRepositoriesWithContributedCommits:NaN, - totalCommitContributions:NaN, - restrictedContributionsCount:NaN, - totalIssueContributions:NaN, - totalPullRequestContributions:NaN, - totalPullRequestReviewContributions:NaN, + isHireable: false, + repositories: {}, + starredRepositories: {totalCount: NaN}, + watching: {totalCount: NaN}, + contributionsCollection: { + totalRepositoriesWithContributedCommits: NaN, + totalCommitContributions: NaN, + restrictedContributionsCount: NaN, + totalIssueContributions: NaN, + totalPullRequestContributions: NaN, + totalPullRequestReviewContributions: NaN, }, - calendar:{contributionCalendar:{weeks:[]}}, - repositoriesContributedTo:{totalCount:NaN, nodes:[]}, - followers:{totalCount:NaN}, - following:{totalCount:NaN}, - issueComments:{totalCount:NaN}, - organizations:{totalCount:NaN}, + calendar: {contributionCalendar: {weeks: []}}, + repositoriesContributedTo: {totalCount: NaN, nodes: []}, + followers: {totalCount: NaN}, + following: {totalCount: NaN}, + issueComments: {totalCount: NaN}, + organizations: {totalCount: NaN}, }) }, //Skip base content query and instantiate an empty user instance skip({login, data, imports}) { data.user = {} - data.shared = imports.metadata.plugins.base.inputs({data, q:{}, account:"bypass"}) + data.shared = imports.metadata.plugins.base.inputs({data, q: {}, account: "bypass"}) for (const account of ["user", "organization"]) postprocess?.[account]({login, data}) data.account = "bypass" Object.assign(data.user, { - databaseId:NaN, - name:login, + databaseId: NaN, + name: login, login, - createdAt:new Date(), - avatarUrl:`https://github.com/${login}.png`, - websiteUrl:null, - twitterUsername:login, - repositories:{totalCount:NaN, totalDiskUsage:NaN, nodes:[]}, - packages:{totalCount:NaN}, - repositoriesContributedTo:{totalCount:NaN, nodes:[]}, + createdAt: new Date(), + avatarUrl: `https://github.com/${login}.png`, + websiteUrl: null, + twitterUsername: login, + repositories: {totalCount: NaN, totalDiskUsage: NaN, nodes: []}, + packages: {totalCount: NaN}, + repositoriesContributedTo: {totalCount: NaN, nodes: []}, }) }, } diff --git a/source/plugins/code/index.mjs b/source/plugins/code/index.mjs index 211c2d93..4a7b13de 100644 --- a/source/plugins/code/index.mjs +++ b/source/plugins/code/index.mjs @@ -7,11 +7,11 @@ export default async function({login, q, imports, data, rest, account}, {enabled return null //Context - let context = {mode:"user"} + let context = {mode: "user"} if (q.repo) { console.debug(`metrics/compute/${login}/plugins > code > switched to repository mode`) - const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift() - context = {...context, mode:"repository", owner, repo} + const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift() + context = {...context, mode: "repository", owner, repo} } //Load inputs @@ -31,14 +31,14 @@ export default async function({login, q, imports, data, rest, account}, {enabled ...[ ...await Promise.all([ ...(context.mode === "repository" - ? await rest.activity.listRepoEvents({owner:context.owner, repo:context.repo}) - : await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data + ? await rest.activity.listRepoEvents({owner: context.owner, repo: context.repo}) + : await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data .filter(({type}) => type === "PushEvent") .filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase()) - .filter(({repo:{name:repo}}) => !((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo)))) + .filter(({repo: {name: repo}}) => !((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo)))) .filter(event => visibility === "public" ? event.public : true) .filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true) - .flatMap(({created_at:created, payload}) => Promise.all(payload.commits.map(async commit => ({created:new Date(created), ...(await rest.request(commit.url)).data})))), + .flatMap(({created_at: created, payload}) => Promise.all(payload.commits.map(async commit => ({created: new Date(created), ...(await rest.request(commit.url)).data})))), ]), ] .flat() @@ -54,11 +54,10 @@ export default async function({login, q, imports, data, rest, account}, {enabled //Search for a random snippet let files = events - .flatMap(({created, sha, commit:{message, url}, files}) => files.map(({filename, status, additions, deletions, patch}) => ({created, sha, message, filename, status, additions, deletions, patch, repo:url.match(/repos[/](?[\s\S]+)[/]git[/]commits/)?.groups?.repo})) - ) + .flatMap(({created, sha, commit: {message, url}, files}) => files.map(({filename, status, additions, deletions, patch}) => ({created, sha, message, filename, status, additions, deletions, patch, repo: url.match(/repos[/](?[\s\S]+)[/]git[/]commits/)?.groups?.repo}))) .filter(({patch}) => (patch ? (patch.match(/\n/mg)?.length ?? 1) : Infinity) < lines) for (const file of files) - file.language = await imports.language({...file, prefix:login}).catch(() => "unknown") + file.language = await imports.language({...file, prefix: login}).catch(() => "unknown") files = files.filter(({language}) => (!languages.length) || (languages.includes(language.toLocaleLowerCase()))) const snippet = files[Math.floor(Math.random() * files.length)] ?? null if (snippet) { @@ -80,6 +79,6 @@ export default async function({login, q, imports, data, rest, account}, {enabled } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/community/fortune/index.mjs b/source/plugins/community/fortune/index.mjs index 037d86c8..d4646f28 100644 --- a/source/plugins/community/fortune/index.mjs +++ b/source/plugins/community/fortune/index.mjs @@ -11,19 +11,19 @@ export default async function({q, data, imports, account}, {enabled = false} = { //Fortunes list const fortunes = [ - {chance:.06, color:"#F51C6A", text:"Reply hazy"}, - {chance:.03, color:"#FD4D32", text:"Excellent Luck"}, - {chance:.16, color:"#E7890C", text:"Good Luck"}, - {chance:.24, color:"#BAC200", text:"Average Luck"}, - {chance:.16, color:"#7FEC11", text:"Bad Luck"}, - {chance:.06, color:"#43FD3B", text:"Good news will come to you by mail"}, - {chance:.06, color:"#16F174", text:"( ´_ゝ`)フーン "}, - {chance:.06, color:"#00CBB0", text:"キタ━━━━━━(゚∀゚)━━━━━━ !!!!"}, - {chance:.06, color:"#0893E1", text:"You will meet a dark handsome stranger"}, - {chance:.06, color:"#2A56FB", text:"Better not tell you now"}, - {chance:.06, color:"#6023F8", text:"Outlook good"}, - {chance:.04, color:"#9D05DA", text:"Very Bad Luck"}, - {chance:.01, color:"#D302A7", text:"Godly Luck"}, + {chance: .06, color: "#F51C6A", text: "Reply hazy"}, + {chance: .03, color: "#FD4D32", text: "Excellent Luck"}, + {chance: .16, color: "#E7890C", text: "Good Luck"}, + {chance: .24, color: "#BAC200", text: "Average Luck"}, + {chance: .16, color: "#7FEC11", text: "Bad Luck"}, + {chance: .06, color: "#43FD3B", text: "Good news will come to you by mail"}, + {chance: .06, color: "#16F174", text: "( ´_ゝ`)フーン "}, + {chance: .06, color: "#00CBB0", text: "キタ━━━━━━(゚∀゚)━━━━━━ !!!!"}, + {chance: .06, color: "#0893E1", text: "You will meet a dark handsome stranger"}, + {chance: .06, color: "#2A56FB", text: "Better not tell you now"}, + {chance: .06, color: "#6023F8", text: "Outlook good"}, + {chance: .04, color: "#9D05DA", text: "Very Bad Luck"}, + {chance: .01, color: "#D302A7", text: "Godly Luck"}, ] //Result @@ -40,6 +40,6 @@ export default async function({q, data, imports, account}, {enabled = false} = { } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/community/nightscout/index.mjs b/source/plugins/community/nightscout/index.mjs index 60c5e5bd..1e1f870e 100644 --- a/source/plugins/community/nightscout/index.mjs +++ b/source/plugins/community/nightscout/index.mjs @@ -10,7 +10,7 @@ export default async function({q, imports, data, account}, {enabled = false} = { let {url, datapoints, lowalert, highalert, urgentlowalert, urgenthighalert} = imports.metadata.plugins.nightscout.inputs({data, account, q}) if (!url || url === "https://example.herokuapp.com") - throw {error:{message:"Nightscout site URL isn't set!"}} + throw {error: {message: "Nightscout site URL isn't set!"}} if (url.substring(url.length - 1) !== "/") url += "/" if (url.substring(0, 7) === "http://") @@ -44,13 +44,13 @@ export default async function({q, imports, data, account}, {enabled = false} = { resp.data[i].color = color resp.data[i].alert = alertName } - return {data:resp.data.reverse()} + return {data: resp.data.reverse()} } //Handle errors catch (error) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/community/poopmap/index.mjs b/source/plugins/community/poopmap/index.mjs index 900d90a6..2a168f71 100644 --- a/source/plugins/community/poopmap/index.mjs +++ b/source/plugins/community/poopmap/index.mjs @@ -7,10 +7,10 @@ export default async function({q, imports, data, account}, {enabled = false, tok return null if (!token) - return {poops:[], days:7} + return {poops: [], days: 7} const {days} = imports.metadata.plugins.poopmap.inputs({data, account, q}) - const {data:{poops}} = await imports.axios.get(`https://api.poopmap.net/api/v1/public_links/${token}`) + const {data: {poops}} = await imports.axios.get(`https://api.poopmap.net/api/v1/public_links/${token}`) const filteredPoops = poops.filter(poop => { const createdAt = new Date(poop.created_at) @@ -18,7 +18,7 @@ export default async function({q, imports, data, account}, {enabled = false, tok return createdAt > new Date().getTime() - days * 24 * 60 * 60 * 1000 }) - const hours = {max:0} + const hours = {max: 0} for (let i = 0; i < filteredPoops.length; i++) { const poop = filteredPoops[i] const hour = new Date(poop.created_at).getHours() @@ -28,10 +28,10 @@ export default async function({q, imports, data, account}, {enabled = false, tok } //Results - return {poops:hours, days} + return {poops: hours, days} } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/community/screenshot/index.mjs b/source/plugins/community/screenshot/index.mjs index 11533262..eff2b10a 100644 --- a/source/plugins/community/screenshot/index.mjs +++ b/source/plugins/community/screenshot/index.mjs @@ -9,14 +9,14 @@ export default async function({login, q, imports, data, account}, {enabled = fal //Load inputs let {url, selector, title, background} = imports.metadata.plugins.screenshot.inputs({data, account, q}) if (!url) - throw {error:{message:"An url is required"}} + throw {error: {message: "An url is required"}} //Start puppeteer and navigate to page console.debug(`metrics/compute/${login}/plugins > screenshot > starting browser`) const browser = await imports.puppeteer.launch() console.debug(`metrics/compute/${login}/plugins > screenshot > started ${await browser.version()}`) const page = await browser.newPage() - await page.setViewport({width:1280, height:1280}) + await page.setViewport({width: 1280, height: 1280}) console.debug(`metrics/compute/${login}/plugins > screenshot > loading ${url}`) await page.goto(url) @@ -27,17 +27,17 @@ export default async function({login, q, imports, data, account}, {enabled = fal return {x, y, width, height} }, selector) console.debug(`metrics/compute/${login}/plugins > screenshot > coordinates ${JSON.stringify(clip)}`) - const [buffer] = await imports.record({page, ...clip, frames:1, background}) + const [buffer] = await imports.record({page, ...clip, frames: 1, background}) const screenshot = await (await imports.jimp.read(Buffer.from(buffer.split(",").pop(), "base64"))).resize(Math.min(454 * (1 + data.large), clip.width), imports.jimp.AUTO) await browser.close() //Results - return {image:await screenshot.getBase64Async("image/png"), title, height:screenshot.bitmap.height, width:screenshot.bitmap.width, url} + return {image: await screenshot.getBase64Async("image/png"), title, height: screenshot.bitmap.height, width: screenshot.bitmap.width, url} } //Handle errors catch (error) { if (error.error?.message) throw error - throw {title:"Screenshot error", error:{message:"An error occured", instance:error}} + throw {title: "Screenshot error", error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/community/stock/index.mjs b/source/plugins/community/stock/index.mjs index b417f9a4..44cd8edd 100644 --- a/source/plugins/community/stock/index.mjs +++ b/source/plugins/community/stock/index.mjs @@ -9,42 +9,42 @@ export default async function({login, q, imports, data, account}, {enabled = fal //Load inputs let {symbol, interval, duration} = imports.metadata.plugins.stock.inputs({data, account, q}) if (!token) - throw {error:{message:"A token is required"}} + throw {error: {message: "A token is required"}} if (!symbol) - throw {error:{message:"A company stock symbol is required"}} + throw {error: {message: "A company stock symbol is required"}} symbol = symbol.toLocaleUpperCase() //Query API for company informations console.debug(`metrics/compute/${login}/plugins > stock > querying api for company`) - const {data:{quoteType:{shortName:company}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-profile", { - params:{symbol, region:"US"}, - headers:{"x-rapidapi-key":token}, + const {data: {quoteType: {shortName: company}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-profile", { + params: {symbol, region: "US"}, + headers: {"x-rapidapi-key": token}, }) //Query API for sotck charts console.debug(`metrics/compute/${login}/plugins > stock > querying api for stock`) - const {data:{chart:{result:[{meta, timestamp, indicators:{quote:[{close}]}}]}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-chart", { - params:{interval, symbol, range:duration, region:"US"}, - headers:{"x-rapidapi-key":token}, + const {data: {chart: {result: [{meta, timestamp, indicators: {quote: [{close}]}}]}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-chart", { + params: {interval, symbol, range: duration, region: "US"}, + headers: {"x-rapidapi-key": token}, }) - const {currency, regularMarketPrice:price, previousClose:previous} = meta + const {currency, regularMarketPrice: price, previousClose: previous} = meta //Generating chart console.debug(`metrics/compute/${login}/plugins > stock > generating chart`) const chart = await imports.chartist("line", { - width:480 * (1 + data.large), - height:160, - showPoint:false, - axisX:{showGrid:false, labelInterpolationFnc:(value, index) => index % Math.floor(close.length / 4) === 0 ? value : null}, - axisY:{scaleMinSpace:20}, - showArea:true, + width: 480 * (1 + data.large), + height: 160, + showPoint: false, + axisX: {showGrid: false, labelInterpolationFnc: (value, index) => index % Math.floor(close.length / 4) === 0 ? value : null}, + axisY: {scaleMinSpace: 20}, + showArea: true, }, { - labels:timestamp.map(timestamp => new Intl.DateTimeFormat("en-GB", {month:"2-digit", day:"2-digit", hour:"2-digit", minute:"2-digit"}).format(new Date(timestamp * 1000))), - series:[close], + labels: timestamp.map(timestamp => new Intl.DateTimeFormat("en-GB", {month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"}).format(new Date(timestamp * 1000))), + series: [close], }) //Results - return {chart, currency, price, previous, delta:price - previous, symbol, company, interval, duration} + return {chart, currency, price, previous, delta: price - previous, symbol, company, interval, duration} } //Handle errors catch (error) { @@ -55,6 +55,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null } - throw {error:{message, instance:error}} + throw {error: {message, instance: error}} } } diff --git a/source/plugins/contributors/index.mjs b/source/plugins/contributors/index.mjs index 04bb406f..d8507481 100644 --- a/source/plugins/contributors/index.mjs +++ b/source/plugins/contributors/index.mjs @@ -8,23 +8,23 @@ export default async function({login, q, imports, data, rest, graphql, queries, //Load inputs let {head, base, ignored, contributions, sections, categories} = imports.metadata.plugins.contributors.inputs({data, account, q}) - const repo = {owner:data.repo.owner.login, repo:data.repo.name} + const repo = {owner: data.repo.owner.login, repo: data.repo.name} ignored.push(...data.shared["users.ignored"]) //Retrieve head and base commits console.debug(`metrics/compute/${login}/plugins > contributors > querying api head and base commits`) const ref = { - head:(await graphql(queries.contributors.commit({...repo, expression:head}))).repository.object, - base:(await graphql(queries.contributors.commit({...repo, expression:base}))).repository.object, + head: (await graphql(queries.contributors.commit({...repo, expression: head}))).repository.object, + base: (await graphql(queries.contributors.commit({...repo, expression: base}))).repository.object, } //Get commit activity console.debug(`metrics/compute/${login}/plugins > contributors > querying api for commits between [${ref.base?.abbreviatedOid ?? null}] and [${ref.head?.abbreviatedOid ?? null}]`) const commits = [] - for (let page = 1; ; page++) { + for (let page = 1;; page++) { console.debug(`metrics/compute/${login}/plugins > contributors > loading page ${page}`) try { - const {data:loaded} = await rest.repos.listCommits({...repo, per_page:100, page}) + const {data: loaded} = await rest.repos.listCommits({...repo, per_page: 100, page}) if (loaded.map(({sha}) => sha).includes(ref.base?.oid)) { console.debug(`metrics/compute/${login}/plugins > contributors > reached ${ref.base?.oid}`) commits.push(...loaded.slice(0, loaded.map(({sha}) => sha).indexOf(ref.base.oid))) @@ -50,13 +50,13 @@ export default async function({login, q, imports, data, rest, graphql, queries, //Compute contributors and contributions let contributors = {} - for (const {author:{login, avatar_url:avatar}, commit:{message = "", author:{email = ""} = {}}} of commits) { + for (const {author: {login, avatar_url: avatar}, commit: {message = "", author: {email = ""} = {}}} of commits) { if ((!login) || (ignored.includes(login)) || (ignored.includes(email))) { console.debug(`metrics/compute/${login}/plugins > contributors > ignored contributor "${login}"`) continue } if (!(login in contributors)) - contributors[login] = {avatar:await imports.imgb64(avatar), contributions:1, pr:[]} + contributors[login] = {avatar: await imports.imgb64(avatar), contributions: 1, pr: []} else { contributors[login].contributions++ contributors[login].pr.push(...(message.match(/(?<=[(])#\d+(?=[)])/g) ?? [])) @@ -78,8 +78,8 @@ export default async function({login, q, imports, data, rest, graphql, queries, try { //Git clone into temporary directory - await imports.fs.rm(path, {recursive:true, force:true}) - await imports.fs.mkdir(path, {recursive:true}) + await imports.fs.rm(path, {recursive: true, force: true}) + await imports.fs.mkdir(path, {recursive: true}) const git = await imports.git(path) await git.clone(`https://github.com/${repository}`, ".").status() @@ -87,7 +87,7 @@ export default async function({login, q, imports, data, rest, graphql, queries, for (const contributor in contributors) { //Load edited files by contributor const files = [] - await imports.spawn("git", ["--no-pager", "log", `--author="${contributor}"`, "--regexp-ignore-case", "--no-merges", "--name-only", '--pretty=format:""'], {cwd:path}, { + await imports.spawn("git", ["--no-pager", "log", `--author="${contributor}"`, "--regexp-ignore-case", "--no-merges", "--name-only", '--pretty=format:""'], {cwd: path}, { stdout(line) { if (line.trim().length) files.push(line) @@ -98,7 +98,7 @@ export default async function({login, q, imports, data, rest, graphql, queries, for (const file of files) { for (const [category, globs] of Object.entries(categories)) { for (const glob of [globs].flat(Infinity)) { - if (imports.minimatch(file, glob, {nocase:true})) { + if (imports.minimatch(file, glob, {nocase: true})) { types[category].add(contributor) continue filesloop } @@ -114,15 +114,15 @@ export default async function({login, q, imports, data, rest, graphql, queries, finally { //Cleaning console.debug(`metrics/compute/${login}/plugins > contributors > cleaning temp dir ${path}`) - await imports.fs.rm(path, {recursive:true, force:true}) + await imports.fs.rm(path, {recursive: true, force: true}) } } //Results - return {head, base, ref, list:contributors, categories:types, contributions, sections} + return {head, base, ref, list: contributors, categories: types, contributions, sections} } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/core/index.mjs b/source/plugins/core/index.mjs index 9dbb045c..78ddfcc7 100644 --- a/source/plugins/core/index.mjs +++ b/source/plugins/core/index.mjs @@ -6,8 +6,8 @@ //Setup export default async function({login, q}, {conf, data, rest, graphql, plugins, queries, account, convert, template}, {pending, imports}) { //Load inputs - const {"config.animations":animations, "config.display":display, "config.timezone":_timezone, "config.base64":_base64, "debug.flags":dflags} = imports.metadata.plugins.core.inputs({data, account, q}) - imports.metadata.templates[template].check({q, account, format:convert}) + const {"config.animations": animations, "config.display": display, "config.timezone": _timezone, "config.base64": _base64, "debug.flags": dflags} = imports.metadata.plugins.core.inputs({data, account, q}) + imports.metadata.templates[template].check({q, account, format: convert}) //Base64 images if (!_base64) { @@ -17,23 +17,23 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q //Init const computed = { - commits:0, - sponsorships:0, - licenses:{favorite:"", used:{}, about:{}}, - token:{}, - repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_closed:0, pr_merged:0, forks:0, forked:0, releases:0, deployments:0, environments:0}, + commits: 0, + sponsorships: 0, + licenses: {favorite: "", used: {}, about: {}}, + token: {}, + repositories: {watchers: 0, stargazers: 0, issues_open: 0, issues_closed: 0, pr_open: 0, pr_closed: 0, pr_merged: 0, forks: 0, forked: 0, releases: 0, deployments: 0, environments: 0}, } const avatar = imports.imgb64(data.user.avatarUrl) data.computed = computed console.debug(`metrics/compute/${login} > formatting common metrics`) //Timezone config - const offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short"}).match(/UTC[+](?\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0 + const offset = Number(new Date().toLocaleString("fr", {timeZoneName: "short"}).match(/UTC[+](?\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0 if (_timezone) { - const timezone = {name:_timezone, offset:0} + const timezone = {name: _timezone, offset: 0} data.config.timezone = timezone try { - timezone.offset = offset - (Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0) + timezone.offset = offset - (Number(new Date().toLocaleString("fr", {timeZoneName: "short", timeZone: timezone.name}).match(/UTC[+](?\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 { @@ -41,9 +41,9 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`) } } - else if (process?.env?.TZ) - data.config.timezone = {name:process.env.TZ, offset} - + else if (process?.env?.TZ) { + data.config.timezone = {name: process.env.TZ, offset} + } //Display data.large = display === "large" @@ -60,7 +60,7 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q 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}, {extras:conf.settings?.extras?.features ?? conf.settings?.extras?.default ?? false, sandbox:conf.settings?.sandbox ?? false, ...plugins[name]}) + data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, {extras: conf.settings?.extras?.features ?? conf.settings?.extras?.default ?? false, sandbox: conf.settings?.sandbox ?? false, ...plugins[name]}) console.debug(`metrics/compute/${login}/plugins > ${name} > completed`) } catch (error) { @@ -68,8 +68,8 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q data.plugins[name] = error } finally { - const result = {name, result:data.plugins[name]} - console.debug(imports.util.inspect(result, {depth:Infinity, maxStringLength:256, getters:true})) + const result = {name, result: data.plugins[name]} + console.debug(imports.util.inspect(result, {depth: Infinity, maxStringLength: 256, getters: true})) return result } })()) @@ -108,7 +108,7 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q const months = diff.getUTCMonth() - new Date(0).getUTCMonth() const days = diff.getUTCDate() - new Date(0).getUTCDate() - computed.registered = {years:years + days / 365.25, months} + computed.registered = {years: years + days / 365.25, months} computed.registration = years ? `${years} year${imports.s(years)} ago` : months ? `${months} month${imports.s(months)} ago` : `${days} day${imports.s(days)} ago` computed.cakeday = (years >= 1 && months === 0 && days === 0) ? true : false @@ -129,9 +129,9 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q //Meta data.meta = { - version:conf.package.version, - author:conf.package.author, - generated:imports.format.date(new Date(), {date:true, time:true}), + version: conf.package.version, + author: conf.package.author, + generated: imports.format.date(new Date(), {date: true, time: true}), } //Debug flags @@ -146,7 +146,8 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q if (dflags.includes("--halloween")) { console.debug(`metrics/compute/${login} > applying dflag --halloween`) //Haloween color replacer - const halloween = content => content + const halloween = content => + content .replace(/--color-calendar-graph/g, "--color-calendar-halloween-graph") .replace(/#9be9a8/gi, "var(--color-calendar-halloween-graph-day-L1-bg)") .replace(/#40c463/gi, "var(--color-calendar-halloween-graph-day-L2-bg)") @@ -160,7 +161,7 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q await Promise.all(waiting) if (data.plugins.isocalendar?.svg) data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg) - return {name:"dflag.halloween", result:true} + return {name: "dflag.halloween", result: true} })()) } if (dflags.includes("--error")) { diff --git a/source/plugins/discussions/index.mjs b/source/plugins/discussions/index.mjs index dcf2d059..84b40a21 100644 --- a/source/plugins/discussions/index.mjs +++ b/source/plugins/discussions/index.mjs @@ -7,9 +7,9 @@ export default async function({login, q, imports, graphql, queries, data, accoun return null //Load inputs - const {categories:_categories, "categories.limit":_categories_limit} = imports.metadata.plugins.discussions.inputs({data, account, q}) - const discussions = {categories:{}, upvotes:{discussions:0, comments:0}} - discussions.display = {categories:_categories ? {limit:_categories_limit || Infinity} : null} + const {categories: _categories, "categories.limit": _categories_limit} = imports.metadata.plugins.discussions.inputs({data, account, q}) + const discussions = {categories: {}, upvotes: {discussions: 0, comments: 0}} + discussions.display = {categories: _categories ? {limit: _categories_limit || Infinity} : null} //Fetch general statistics const stats = Object.fromEntries(Object.entries((await graphql(queries.discussions.statistics({login}))).user).map(([key, value]) => [key, value.totalCount])) @@ -23,7 +23,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun let pushed = 0 do { console.debug(`metrics/compute/${login}/discussions > retrieving discussions after ${cursor}`) - const {user:{repositoryDiscussions:{edges = [], nodes = []} = {}}} = await graphql(queries.discussions.categories({login, after:cursor ? `after: "${cursor}"` : ""})) + const {user: {repositoryDiscussions: {edges = [], nodes = []} = {}}} = await graphql(queries.discussions.categories({login, after: cursor ? `after: "${cursor}"` : ""})) cursor = edges?.[edges?.length - 1]?.cursor fetched.push(...nodes) pushed = nodes.length @@ -34,7 +34,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun fetched.map(({upvoteCount}) => discussions.upvotes.discussions += upvoteCount) //Compute favorite category - for (const category of [...fetched.map(({category:{emoji, name}}) => `${imports.emoji.get(emoji) ?? emoji} ${name}`)]) + for (const category of [...fetched.map(({category: {emoji, name}}) => `${imports.emoji.get(emoji) ?? emoji} ${name}`)]) categories[category] = (categories[category] ?? 0) + 1 const categoryEntries = Object.entries(categories).sort((a, b) => b[1] - a[1]) discussions.categories.stats = Object.fromEntries(categoryEntries) @@ -48,7 +48,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun let pushed = 0 do { console.debug(`metrics/compute/${login}/discussions > retrieving comments after ${cursor}`) - const {user:{repositoryDiscussionComments:{edges = [], nodes = []} = {}}} = await graphql(queries.discussions.comments({login, after:cursor ? `after: "${cursor}"` : ""})) + const {user: {repositoryDiscussionComments: {edges = [], nodes = []} = {}}} = await graphql(queries.discussions.comments({login, after: cursor ? `after: "${cursor}"` : ""})) cursor = edges?.[edges?.length - 1]?.cursor fetched.push(...nodes) pushed = nodes.length @@ -64,6 +64,6 @@ export default async function({login, q, imports, graphql, queries, data, accoun } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/followup/index.mjs b/source/plugins/followup/index.mjs index 52363793..4eaf42cd 100644 --- a/source/plugins/followup/index.mjs +++ b/source/plugins/followup/index.mjs @@ -12,7 +12,7 @@ export default async function({login, data, computed, imports, q, graphql, queri //Define getters const followup = { sections, - issues:{ + issues: { get count() { return this.open + this.closed + this.drafts + this.skipped }, @@ -22,16 +22,16 @@ export default async function({login, data, computed, imports, q, graphql, queri get closed() { return computed.repositories.issues_closed }, - drafts:0, - skipped:0, - collaborators:{ - open:0, - closed:0, - drafts:0, - skipped:0, + drafts: 0, + skipped: 0, + collaborators: { + open: 0, + closed: 0, + drafts: 0, + skipped: 0, }, }, - pr:{ + pr: { get count() { return this.open + this.closed + this.merged + this.drafts }, @@ -44,12 +44,12 @@ export default async function({login, data, computed, imports, q, graphql, queri get merged() { return computed.repositories.pr_merged }, - drafts:0, - collaborators:{ - open:0, - closed:0, - merged:0, - drafts:0, + drafts: 0, + collaborators: { + open: 0, + closed: 0, + merged: 0, + drafts: 0, }, }, } @@ -59,15 +59,15 @@ export default async function({login, data, computed, imports, q, graphql, queri //Indepth mode if (indepth) { console.debug(`metrics/compute/${login}/plugins > followup > indepth`) - followup.indepth = {repositories:{}} + followup.indepth = {repositories: {}} //Process repositories - for (const {name:repo, owner:{login:owner}} of data.user.repositories.nodes) { + for (const {name: repo, owner: {login: owner}} of data.user.repositories.nodes) { try { console.debug(`metrics/compute/${login}/plugins > followup > processing ${owner}/${repo}`) - followup.indepth.repositories[`${owner}/${repo}`] = {stats:{}} + followup.indepth.repositories[`${owner}/${repo}`] = {stats: {}} //Fetch users with push access - let {repository:{collaborators:{nodes:collaborators}}} = await graphql(queries.followup["repository.collaborators"]({repo, owner})).catch(() => ({repository:{collaborators:{nodes:[{login:owner}]}}})) + let {repository: {collaborators: {nodes: collaborators}}} = await graphql(queries.followup["repository.collaborators"]({repo, owner})).catch(() => ({repository: {collaborators: {nodes: [{login: owner}]}}})) console.debug(`metrics/compute/${login}/plugins > followup > found ${collaborators.length} collaborators`) followup.indepth.repositories[`${owner}/${repo}`].collaborators = collaborators.map(({login}) => login) //Fetch issues and pull requests created by collaborators @@ -75,7 +75,7 @@ export default async function({login, data, computed, imports, q, graphql, queri const stats = await graphql(queries.followup.repository({repo, owner, collaborators})) followup.indepth.repositories[`${owner}/${repo}`] = stats //Aggregate global stats - for (const [key, {issueCount:count}] of Object.entries(stats)) { + for (const [key, {issueCount: count}] of Object.entries(stats)) { const [section, type] = key.split("_") followup[section].collaborators[type] += count } @@ -92,23 +92,23 @@ export default async function({login, data, computed, imports, q, graphql, queri if ((account === "user") && (sections.includes("user"))) { const search = await graphql(queries.followup.user({login})) followup.user = { - issues:{ + issues: { get count() { return this.open + this.closed + this.drafts + this.skipped }, - open:search.issues_open.issueCount, - closed:search.issues_closed.issueCount, - drafts:search.issues_drafts.issueCount, - skipped:search.issues_skipped.issueCount, + open: search.issues_open.issueCount, + closed: search.issues_closed.issueCount, + drafts: search.issues_drafts.issueCount, + skipped: search.issues_skipped.issueCount, }, - pr:{ + pr: { get count() { return this.open + this.closed + this.merged + this.drafts }, - open:search.pr_open.issueCount, - closed:search.pr_closed.issueCount, - merged:search.pr_merged.issueCount, - drafts:search.pr_drafts.issueCount, + open: search.pr_open.issueCount, + closed: search.pr_closed.issueCount, + merged: search.pr_merged.issueCount, + drafts: search.pr_drafts.issueCount, }, } } @@ -118,6 +118,6 @@ export default async function({login, data, computed, imports, q, graphql, queri } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/gists/index.mjs b/source/plugins/gists/index.mjs index 8e1311ff..31387a2c 100644 --- a/source/plugins/gists/index.mjs +++ b/source/plugins/gists/index.mjs @@ -17,7 +17,7 @@ export default async function({login, data, graphql, q, imports, queries, accoun let pushed = 0 do { console.debug(`metrics/compute/${login}/plugins > gists > retrieving gists after ${cursor}`) - const {user:{gists:{edges, nodes, totalCount}}} = await graphql(queries.gists({login, after:cursor ? `after: "${cursor}"` : ""})) + const {user: {gists: {edges, nodes, totalCount}}} = await graphql(queries.gists({login, after: cursor ? `after: "${cursor}"` : ""})) cursor = edges?.[edges?.length - 1]?.cursor gists.push(...nodes) gists.totalCount = totalCount @@ -41,12 +41,12 @@ export default async function({login, data, graphql, q, imports, queries, accoun } //Results - return {totalCount:gists.totalCount, stargazers, forks, files, comments} + return {totalCount: gists.totalCount, stargazers, forks, files, comments} } //Handle errors catch (error) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/habits/index.mjs b/source/plugins/habits/index.mjs index 1bc49016..d6349ec4 100644 --- a/source/plugins/habits/index.mjs +++ b/source/plugins/habits/index.mjs @@ -1,5 +1,5 @@ //Legacy import -import {recent as recent_analyzer} from "./../languages/analyzers.mjs" +import { recent as recent_analyzer } from "./../languages/analyzers.mjs" //Setup export default async function({login, data, rest, imports, q, account}, {enabled = false, extras = false, ...defaults} = {}) { @@ -10,10 +10,10 @@ export default async function({login, data, rest, imports, q, account}, {enabled return null //Load inputs - let {from, days, facts, charts, "charts.type":_charts, trim} = imports.metadata.plugins.habits.inputs({data, account, q}, defaults) + let {from, days, facts, charts, "charts.type": _charts, trim} = imports.metadata.plugins.habits.inputs({data, account, q}, defaults) //Initialization - const habits = {facts, charts, trim, lines:{average:{chars:0}}, commits:{fetched:0, hour:NaN, hours:{}, day:NaN, days:{}}, indents:{style:"", spaces:0, tabs:0}, linguist:{available:false, ordered:[], languages:{}}} + const habits = {facts, charts, trim, lines: {average: {chars: 0}}, commits: {fetched: 0, hour: NaN, hours: {}, day: NaN, days: {}}, indents: {style: "", spaces: 0, tabs: 0}, linguist: {available: false, ordered: [], languages: {}}} const pages = Math.ceil(from / 100) const offset = data.config.timezone?.offset ?? 0 @@ -23,7 +23,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled try { for (let page = 1; page <= pages; page++) { console.debug(`metrics/compute/${login}/plugins > habits > loading page ${page}`) - events.push(...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data) + events.push(...(await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data) } } catch { @@ -51,8 +51,8 @@ export default async function({login, data, rest, imports, q, account}, {enabled ] .filter(({status}) => status === "fulfilled") .map(({value}) => value) - .flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), patch:file.patch ?? ""}))) - .map(({name, patch}) => ({name, patch:patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")})) + .flatMap(files => files.map(file => ({name: imports.paths.basename(file.filename), patch: file.patch ?? ""}))) + .map(({name, patch}) => ({name, patch: patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")})) //Commit day { @@ -103,41 +103,41 @@ export default async function({login, data, rest, imports, q, account}, {enabled if (patches.length) { //Call language analyzer (note: using content from other plugin is usually disallowed, this is mostly for legacy purposes) habits.linguist.available = true - const {total, stats} = await recent_analyzer({login, data, imports, rest, account}, {days, load:from || 1000, tempdir:"habits"}) + const {total, stats} = await recent_analyzer({login, data, imports, rest, account}, {days, load: from || 1000, tempdir: "habits"}) habits.linguist.languages = Object.fromEntries(Object.entries(stats).map(([language, value]) => [language, value / total])) habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([_an, a], [_bn, b]) => b - a) } - else + else { console.debug(`metrics/compute/${login}/plugins > habits > linguist not available`) - + } } //Generating charts with chartist if (_charts === "chartist") { console.debug(`metrics/compute/${login}/plugins > habits > generating charts`) habits.charts = await Promise.all([ - {type:"line", data:{...empty(24), ...Object.fromEntries(Object.entries(habits.commits.hours).filter(([k]) => !Number.isNaN(+k)))}, low:0, high:habits.commits.hours.max}, - {type:"line", data:{...empty(7), ...Object.fromEntries(Object.entries(habits.commits.days).filter(([k]) => !Number.isNaN(+k)))}, low:0, high:habits.commits.days.max, labels:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], half:true}, - {type:"pie", data:habits.linguist.languages, half:true}, + {type: "line", data: {...empty(24), ...Object.fromEntries(Object.entries(habits.commits.hours).filter(([k]) => !Number.isNaN(+k)))}, low: 0, high: habits.commits.hours.max}, + {type: "line", data: {...empty(7), ...Object.fromEntries(Object.entries(habits.commits.days).filter(([k]) => !Number.isNaN(+k)))}, low: 0, high: habits.commits.days.max, labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], half: true}, + {type: "pie", data: habits.linguist.languages, half: true}, ].map(({type, data, high, low, ref, labels = {}, half = false}) => { const options = { - width:480 * (half ? 0.45 : 1), - height:160, - fullWidth:true, + width: 480 * (half ? 0.45 : 1), + height: 160, + fullWidth: true, } const values = { - labels:Object.keys(data).map(key => labels[key] ?? key), - series:Object.values(data), + labels: Object.keys(data).map(key => labels[key] ?? key), + series: Object.values(data), } if (type === "line") { Object.assign(options, { - showPoint:true, - axisX:{showGrid:false}, - axisY:{showLabel:false, offset:20, labelInterpolationFnc:value => imports.format(value), high, low, referenceValue:ref}, - showArea:true, + showPoint: true, + axisX: {showGrid: false}, + axisY: {showLabel: false, offset: 20, labelInterpolationFnc: value => imports.format(value), high, low, referenceValue: ref}, + showArea: true, }) Object.assign(values, { - series:[Object.values(data)], + series: [Object.values(data)], }) } return imports.chartist(type, options, values) @@ -166,7 +166,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled catch (error) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/introduction/index.mjs b/source/plugins/introduction/index.mjs index 8a2ce6df..8f07d4aa 100644 --- a/source/plugins/introduction/index.mjs +++ b/source/plugins/introduction/index.mjs @@ -10,22 +10,22 @@ export default async function({login, q, imports, data, graphql, queries, accoun let {title} = imports.metadata.plugins.introduction.inputs({data, account, q}) //Context - let context = {mode:account, login} + let context = {mode: account, login} if (q.repo) { console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`) - const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift() - context = {...context, mode:"repository", owner, repo} + const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift() + context = {...context, mode: "repository", owner, repo} } //Querying API console.debug(`metrics/compute/${login}/plugins > introduction > querying api`) - const text = (await graphql(queries.introduction[context.mode](context)))[context.mode][{user:"bio", organization:"description", repository:"description"}[context.mode]] + const text = (await graphql(queries.introduction[context.mode](context)))[context.mode][{user: "bio", organization: "description", repository: "description"}[context.mode]] //Results - return {mode:context.mode, title, text} + return {mode: context.mode, title, text} } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/isocalendar/index.mjs b/source/plugins/isocalendar/index.mjs index 2666a2a1..4dea5bbd 100644 --- a/source/plugins/isocalendar/index.mjs +++ b/source/plugins/isocalendar/index.mjs @@ -27,8 +27,8 @@ export default async function({login, data, graphql, q, imports, queries, accoun //Compute contribution calendar, highest contributions in a day, streaks and average commits per day console.debug(`metrics/compute/${login}/plugins > isocalendar > computing stats`) - const calendar = {weeks:[]} - const {streak, max, average} = await statistics({login, graphql, queries, start, end:now, calendar}) + const calendar = {weeks: []} + const {streak, max, average} = await statistics({login, graphql, queries, start, end: now, calendar}) const reference = Math.max(...calendar.weeks.flatMap(({contributionDays}) => contributionDays.map(({contributionCount}) => contributionCount))) //Compute SVG @@ -43,8 +43,7 @@ export default async function({login, data, graphql, q, imports, queries, accoun ${[..."RGB"].map(channel => ``).join("")} - ` - ) + `) .join("") } ` @@ -75,13 +74,13 @@ export default async function({login, data, graphql, q, imports, queries, accoun catch (error) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } /**Compute max and current streaks */ async function statistics({login, graphql, queries, start, end, calendar}) { - let average = 0, max = 0, streak = {max:0, current:0}, values = [] + let average = 0, max = 0, streak = {max: 0, current: 0}, values = [] //Load contribution calendar for (let from = new Date(start); from < end;) { //Set date range @@ -97,7 +96,7 @@ async function statistics({login, graphql, queries, start, end, calendar}) { dto.setUTCMilliseconds(999) //Fetch data from api console.debug(`metrics/compute/${login}/plugins > isocalendar > loading calendar from "${from.toISOString()}" to "${dto.toISOString()}"`) - const {user:{calendar:{contributionCalendar:{weeks}}}} = await graphql(queries.isocalendar.calendar({login, from:from.toISOString(), to:dto.toISOString()})) + const {user: {calendar: {contributionCalendar: {weeks}}}} = await graphql(queries.isocalendar.calendar({login, from: from.toISOString(), to: dto.toISOString()})) calendar.weeks.push(...weeks) //Set next date range start from = new Date(to) diff --git a/source/plugins/languages/analyzers.mjs b/source/plugins/languages/analyzers.mjs index 45614960..aa8c29f4 100644 --- a/source/plugins/languages/analyzers.mjs +++ b/source/plugins/languages/analyzers.mjs @@ -5,7 +5,7 @@ import linguist from "linguist-js" export async function indepth({login, data, imports, repositories, gpg}, {skipped, categories, timeout}) { return new Promise(async solve => { //Results - const results = {partial:false, total:0, lines:{}, stats:{}, colors:{}, commits:0, files:0, missed:{lines:0, bytes:0, commits:0}, verified:{signature:0}} + const results = {partial: false, total: 0, lines: {}, stats: {}, colors: {}, commits: 0, files: 0, missed: {lines: 0, bytes: 0, commits: 0}, verified: {signature: 0}} //Timeout if (Number.isFinite(timeout)) { @@ -27,9 +27,9 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe console.debug(`metrics/compute/${login}/plugins > languages > importing gpg ${id}`) await imports.run(`gpg --import ${path}`) } - else + else { console.debug(`metrics/compute/${login}/plugins > languages > skipping import of gpg ${id}`) - + } } catch (error) { console.debug(`metrics/compute/${login}/plugins > languages > indepth > an error occured while importing gpg ${id}, skipping...`) @@ -37,7 +37,7 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe finally { //Cleaning console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning ${path}`) - await imports.fs.rm(path, {recursive:true, force:true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`)) + await imports.fs.rm(path, {recursive: true, force: true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`)) } } @@ -64,8 +64,8 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe //Process try { //Git clone into temporary directory - await imports.fs.rm(path, {recursive:true, force:true}) - await imports.fs.mkdir(path, {recursive:true}) + await imports.fs.rm(path, {recursive: true, force: true}) + await imports.fs.mkdir(path, {recursive: true}) const git = await imports.git(path) await git.clone(`https://github.com/${repo}`, ".").status() @@ -78,7 +78,7 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe finally { //Cleaning console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning temp dir ${path}`) - await imports.fs.rm(path, {recursive:true, force:true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`)) + await imports.fs.rm(path, {recursive: true, force: true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`)) } } solve(results) @@ -89,7 +89,7 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe export async function recent({login, data, imports, rest, account}, {skipped = [], categories, days = 0, load = 0, tempdir = "recent", timeout}) { return new Promise(async solve => { //Results - const results = {partial:false, total:0, lines:{}, stats:{}, colors:{}, commits:0, files:0, missed:{lines:0, bytes:0, commits:0}, days} + const results = {partial: false, total: 0, lines: {}, stats: {}, colors: {}, commits: 0, files: 0, missed: {lines: 0, bytes: 0, commits: 0}, days} //Timeout if (Number.isFinite(timeout)) { @@ -109,10 +109,10 @@ export async function recent({login, data, imports, rest, account}, {skipped = [ for (let page = 1; page <= pages; page++) { console.debug(`metrics/compute/${login}/plugins > languages > loading page ${page}`) commits.push( - ...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data + ...(await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data .filter(({type}) => type === "PushEvent") .filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase()) - .filter(({repo:{name:repo}}) => (!skipped.includes(repo.toLocaleLowerCase())) && (!skipped.includes(repo.toLocaleLowerCase().split("/").pop()))) + .filter(({repo: {name: repo}}) => (!skipped.includes(repo.toLocaleLowerCase())) && (!skipped.includes(repo.toLocaleLowerCase().split("/").pop()))) .filter(({created_at}) => new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000)), ) } @@ -139,8 +139,8 @@ export async function recent({login, data, imports, rest, account}, {skipped = [ .map(({value}) => value) .filter(({parents}) => parents.length <= 1) .map(({files}) => files) - .flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), directory:imports.paths.dirname(file.filename), patch:file.patch ?? "", repo:file.raw_url?.match(/(?<=^https:..github.com\/)(?.*)(?=\/raw)/)?.groups.repo ?? "_"}))) - .map(({name, directory, patch, repo}) => ({name, directory:`${repo.replace(/[/]/g, "@")}/${directory}`, patch:patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")})) + .flatMap(files => files.map(file => ({name: imports.paths.basename(file.filename), directory: imports.paths.dirname(file.filename), patch: file.patch ?? "", repo: file.raw_url?.match(/(?<=^https:..github.com\/)(?.*)(?=\/raw)/)?.groups.repo ?? "_"}))) + .map(({name, directory, patch, repo}) => ({name, directory: `${repo.replace(/[/]/g, "@")}/${directory}`, patch: patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")})) //Temporary directory const path = imports.paths.join(imports.os.tmpdir(), `${data.user.databaseId}-${tempdir}`) @@ -149,10 +149,10 @@ export async function recent({login, data, imports, rest, account}, {skipped = [ //Process try { //Save patches in temporary directory matching respective repository and filename - await imports.fs.rm(path, {recursive:true, force:true}) - await imports.fs.mkdir(path, {recursive:true}) + await imports.fs.rm(path, {recursive: true, force: true}) + await imports.fs.mkdir(path, {recursive: true}) await Promise.all(patches.map(async ({name, directory, patch}) => { - await imports.fs.mkdir(imports.paths.join(path, directory), {recursive:true}) + await imports.fs.mkdir(imports.paths.join(path, directory), {recursive: true}) await imports.fs.writeFile(imports.paths.join(path, directory, name), patch) })) @@ -177,7 +177,7 @@ export async function recent({login, data, imports, rest, account}, {skipped = [ await git.init().add(".").addConfig("user.name", data.shared["commits.authoring"]?.[0] ?? login).addConfig("user.email", "<>").commit("linguist").status() //Analyze repository - await analyze(arguments[0], {results, path:imports.paths.join(path, directory), categories}) + await analyze(arguments[0], {results, path: imports.paths.join(path, directory), categories}) //Since we reproduce a "partial repository" with a single commit, use number of commits retrieved instead results.commits = commits.length @@ -189,7 +189,7 @@ export async function recent({login, data, imports, rest, account}, {skipped = [ finally { //Cleaning console.debug(`metrics/compute/${login}/plugins > languages > cleaning temp dir ${path}`) - await imports.fs.rm(path, {recursive:true, force:true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`)) + await imports.fs.rm(path, {recursive: true, force: true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`)) } solve(results) }) @@ -199,7 +199,7 @@ export async function recent({login, data, imports, rest, account}, {skipped = [ async function analyze({login, imports, data}, {results, path, categories = ["programming", "markup"]}) { //Gather language data console.debug(`metrics/compute/${login}/plugins > languages > indepth > running linguist`) - const {files:{results:files}, languages:{results:languageResults}} = await linguist(path) + const {files: {results: files}, languages: {results: languageResults}} = await linguist(path) Object.assign(results.colors, Object.fromEntries(Object.entries(languageResults).map(([lang, {color}]) => [lang, color]))) //Processing diff @@ -207,18 +207,18 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr const edited = new Set() console.debug(`metrics/compute/${login}/plugins > languages > indepth > checking git log`) try { - await imports.run("git log --max-count=1", {cwd:path}) + await imports.run("git log --max-count=1", {cwd: path}) } catch { console.debug(`metrics/compute/${login}/plugins > languages > indepth > repo seems empty or impossible to git log, skipping`) return } const pending = [] - for (let page = 0; ; page++) { + for (let page = 0;; page++) { try { console.debug(`metrics/compute/${login}/plugins > languages > indepth > processing commits ${page * per_page} from ${(page + 1) * per_page}`) let empty = true, file = null, lang = null - await imports.spawn("git", ["log", ...data.shared["commits.authoring"].map(authoring => `--author="${authoring}"`), "--regexp-ignore-case", "--format=short", "--no-merges", "--patch", `--max-count=${per_page}`, `--skip=${page * per_page}`], {cwd:path}, { + await imports.spawn("git", ["log", ...data.shared["commits.authoring"].map(authoring => `--author="${authoring}"`), "--regexp-ignore-case", "--format=short", "--no-merges", "--patch", `--max-count=${per_page}`, `--skip=${page * per_page}`], {cwd: path}, { stdout(line) { try { //Unflag empty output @@ -230,7 +230,7 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr const sha = line.match(/[0-9a-f]{40}/)?.[0] if (sha) { pending.push( - imports.run(`git verify-commit ${sha}`, {cwd:path, env:{LANG:"en_GB"}}, {log:false, prefixed:false}) + imports.run(`git verify-commit ${sha}`, {cwd: path, env: {LANG: "en_GB"}}, {log: false, prefixed: false}) .then(() => results.verified.signature++) .catch(() => null), ) @@ -289,26 +289,26 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr //import.meta.main if (/languages.analyzers.mjs$/.test(process.argv[1])) { - (async function() { + ;(async function() { //Parse inputs const [_authoring, path] = process.argv.slice(2) if ((!_authoring) || (!path)) { console.log("Usage is:\n npm run indepth -- \n\n") process.exit(1) } - const {default:setup} = await import("../../app/metrics/setup.mjs") - const {conf:{metadata}} = await setup({log:false}) - const {"commits.authoring":authoring} = await metadata.plugins.base.inputs({q:{"commits.authoring":_authoring}, account:"bypass"}) - const data = {shared:{"commits.authoring":authoring}} + const {default: setup} = await import("../../app/metrics/setup.mjs") + const {conf: {metadata}} = await setup({log: false}) + const {"commits.authoring": authoring} = await metadata.plugins.base.inputs({q: {"commits.authoring": _authoring}, account: "bypass"}) + const data = {shared: {"commits.authoring": authoring}} //Prepare call const imports = await import("../../app/metrics/utils.mjs") - const results = {total:0, lines:{}, colors:{}, stats:{}, missed:{lines:0, bytes:0, commits:0}} + const results = {total: 0, lines: {}, colors: {}, stats: {}, missed: {lines: 0, bytes: 0, commits: 0}} console.debug = log => /exited with code null/.test(log) ? console.error(log.replace(/^.*--max-count=(?\d+) --skip=(?\d+).*$/, (_, step, start) => `error: skipped commits ${start} from ${Number(start) + Number(step)}`)) : null //Analyze repository console.log(`commits authoring | ${authoring}\nrepository path | ${path}\n`) - await analyze({login:"cli", data, imports}, {results, path}) + await analyze({login: "cli", data, imports}, {results, path}) console.log(results) })() } diff --git a/source/plugins/languages/index.mjs b/source/plugins/languages/index.mjs index 8bf74581..60677377 100644 --- a/source/plugins/languages/index.mjs +++ b/source/plugins/languages/index.mjs @@ -1,5 +1,5 @@ //Imports -import {indepth as indepth_analyzer, recent as recent_analyzer} from "./analyzers.mjs" +import { indepth as indepth_analyzer, recent as recent_analyzer } from "./analyzers.mjs" //Setup export default async function({login, data, imports, q, rest, account}, {enabled = false, extras = false} = {}) { @@ -10,14 +10,14 @@ export default async function({login, data, imports, q, rest, account}, {enabled return null //Context - let context = {mode:"user"} + let context = {mode: "user"} if (q.repo) { console.debug(`metrics/compute/${login}/plugins > languages > switched to repository mode`) - context = {...context, mode:"repository"} + context = {...context, mode: "repository"} } //Load inputs - let {ignored, skipped, other, colors, aliases, details, threshold, limit, indepth, "analysis.timeout":timeout, sections, categories, "recent.categories":_recent_categories, "recent.load":_recent_load, "recent.days":_recent_days} = imports.metadata.plugins.languages + let {ignored, skipped, other, colors, aliases, details, threshold, limit, indepth, "analysis.timeout": timeout, sections, categories, "recent.categories": _recent_categories, "recent.load": _recent_load, "recent.days": _recent_days} = imports.metadata.plugins.languages .inputs({ data, account, @@ -40,11 +40,11 @@ export default async function({login, data, imports, q, rest, account}, {enabled //Unique languages const repositories = [...data.user.repositories.nodes, ...data.user.repositoriesContributedTo.nodes] - const unique = new Set(repositories.flatMap(repository => repository.languages.edges.map(({node:{name}}) => name))).size + const unique = new Set(repositories.flatMap(repository => repository.languages.edges.map(({node: {name}}) => name))).size //Iterate through user's repositories and retrieve languages data console.debug(`metrics/compute/${login}/plugins > languages > processing ${data.user.repositories.nodes.length} repositories`) - const languages = {unique, sections, details, indepth, colors:{}, total:0, stats:{}, "stats.recent":{}} + const languages = {unique, sections, details, indepth, colors: {}, total: 0, stats: {}, "stats.recent": {}} const customColors = {} for (const repository of data.user.repositories.nodes) { //Skip repository if asked @@ -53,7 +53,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled continue } //Process repository languages - for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) { + for (const {size, node: {color, name}} of Object.values(repository.languages.edges)) { languages.stats[name] = (languages.stats[name] ?? 0) + size if (colors[name.toLocaleLowerCase()]) customColors[name] = colors[name.toLocaleLowerCase()] @@ -69,7 +69,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled if ((sections.includes("recently-used")) && (context.mode === "user")) { try { console.debug(`metrics/compute/${login}/plugins > languages > using recent analyzer`) - languages["stats.recent"] = await recent_analyzer({login, data, imports, rest, account}, {skipped, categories:_recent_categories ?? categories, days:_recent_days, load:_recent_load, timeout}) + languages["stats.recent"] = await recent_analyzer({login, data, imports, rest, account}, {skipped, categories: _recent_categories ?? categories, days: _recent_days, load: _recent_load, timeout}) Object.assign(languages.colors, languages["stats.recent"].colors) } catch (error) { @@ -83,8 +83,8 @@ export default async function({login, data, imports, q, rest, account}, {enabled const gpg = [] try { for (const username of [login, "web-flow"]) { - const {data:keys} = await rest.users.listGpgKeysForUser({username}) - gpg.push(...keys.map(({key_id:id, raw_key:pub, emails}) => ({id, pub, emails}))) + const {data: keys} = await rest.users.listGpgKeysForUser({username}) + gpg.push(...keys.map(({key_id: id, raw_key: pub, emails}) => ({id, pub, emails}))) if (username === login) { for (const {email} of gpg.flatMap(({emails}) => emails)) { console.debug(`metrics/compute/${login}/plugins > languages > auto-adding ${email} to commits_authoring (fetched from gpg)`) @@ -126,9 +126,10 @@ export default async function({login, data, imports, q, rest, account}, {enabled } //Compute languages stats - for (const {section, stats = {}, lines = {}, missed = {bytes:0}, total = 0} of [{section:"favorites", stats:languages.stats, lines:languages.lines, total:languages.total, missed:languages.missed}, {section:"recent", ...languages["stats.recent"]}]) { + for (const {section, stats = {}, lines = {}, missed = {bytes: 0}, total = 0} of [{section: "favorites", stats: languages.stats, lines: languages.lines, total: languages.total, missed: languages.missed}, {section: "recent", ...languages["stats.recent"]}]) { console.debug(`metrics/compute/${login}/plugins > languages > computing stats ${section}`) - languages[section] = Object.entries(stats).filter(([name]) => !ignored.includes(name.toLocaleLowerCase())).sort(([_an, a], [_bn, b]) => b - a).slice(0, limit).map(([name, value]) => ({name, value, size:value, color:languages.colors[name], x:0})).filter(({value}) => value / total > threshold + languages[section] = Object.entries(stats).filter(([name]) => !ignored.includes(name.toLocaleLowerCase())).sort(([_an, a], [_bn, b]) => b - a).slice(0, limit).map(([name, value]) => ({name, value, size: value, color: languages.colors[name], x: 0})).filter(({value}) => + value / total > threshold ) if (other) { let value = indepth ? missed.bytes : Object.entries(stats).filter(([name]) => !Object.values(languages[section]).map(({name}) => name).includes(name)).reduce((a, [_, b]) => a + b, 0) @@ -141,7 +142,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled languages[section].push({name:"Other", value, size:value, get lines() { return missed.lines }, set lines(_) { }, x:0}) //eslint-disable-line brace-style, no-empty-function, max-statements-per-line } } - const visible = {total:Object.values(languages[section]).map(({size}) => size).reduce((a, b) => a + b, 0)} + const visible = {total: Object.values(languages[section]).map(({size}) => size).reduce((a, b) => a + b, 0)} for (let i = 0; i < languages[section].length; i++) { const {name} = languages[section][i] languages[section][i].value /= visible.total @@ -159,6 +160,6 @@ export default async function({login, data, imports, q, rest, account}, {enabled } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/licenses/index.mjs b/source/plugins/licenses/index.mjs index 1530b937..45005660 100644 --- a/source/plugins/licenses/index.mjs +++ b/source/plugins/licenses/index.mjs @@ -10,8 +10,8 @@ export default async function({login, q, imports, data, graphql, queries, accoun let {setup, ratio, legal} = imports.metadata.plugins.licenses.inputs({data, account, q}) //Initialization - const {user:{repository}} = await graphql(queries.licenses.repository({owner:data.repo.owner.login, name:data.repo.name, account})) - const result = {ratio, legal, default:repository.licenseInfo, licensed:{available:false}, text:{}, list:[], used:{}, dependencies:[], known:0, unknown:0} + const {user: {repository}} = await graphql(queries.licenses.repository({owner: data.repo.owner.login, name: data.repo.name, account})) + const result = {ratio, legal, default: repository.licenseInfo, licensed: {available: false}, text: {}, list: [], used: {}, dependencies: [], known: 0, unknown: 0} const {used, text} = result //Register existing licenses properties @@ -29,8 +29,8 @@ export default async function({login, q, imports, data, graphql, queries, accoun const path = imports.paths.join(imports.os.tmpdir(), `${repository.databaseId}`) //Create temporary directory console.debug(`metrics/compute/${login}/plugins > licenses > creating temp dir ${path}`) - await imports.fs.rm(path, {recursive:true, force:true}) - await imports.fs.mkdir(path, {recursive:true}) + await imports.fs.rm(path, {recursive: true, force: true}) + await imports.fs.mkdir(path, {recursive: true}) //Clone repository console.debug(`metrics/compute/${login}/plugins > licenses > cloning temp git repository ${repository.url} to ${path}`) const git = imports.git(path) @@ -38,7 +38,7 @@ export default async function({login, q, imports, data, graphql, queries, accoun //Run setup if (setup) { console.debug(`metrics/compute/${login}/plugins > licenses > running setup [${setup}]`) - await imports.run(setup, {cwd:path}, {prefixed:false}) + await imports.run(setup, {cwd: path}, {prefixed: false}) } //Create configuration file if needed if (!(await imports.fs.stat(imports.paths.join(path, ".licensed.yml")).then(() => 1).catch(() => 0))) { @@ -50,33 +50,35 @@ export default async function({login, q, imports, data, graphql, queries, accoun ].join("\n"), ) } - else + else { console.debug(`metrics/compute/${login}/plugins > licenses > a .licensed.yml configuration file already exists`) - + } //Spawn licensed process console.debug(`metrics/compute/${login}/plugins > licenses > running licensed`) - JSON.parse(await imports.run("licensed list --format=json --licenses", {cwd:path})).apps - .map(({sources}) => sources?.flatMap(source => source.dependencies?.map(({dependency, license}) => { + JSON.parse(await imports.run("licensed list --format=json --licenses", {cwd: path})).apps + .map(({sources}) => + sources?.flatMap(source => + source.dependencies?.map(({dependency, license}) => { used[license] = (used[license] ?? 0) + 1 result.dependencies.push(dependency) - result.known += (license in licenses) + result.known += license in licenses result.unknown += !(license in licenses) }) ) ) //Cleaning console.debug(`metrics/compute/${login}/plugins > licensed > cleaning temp dir ${path}`) - await imports.fs.rm(path, {recursive:true, force:true}) + await imports.fs.rm(path, {recursive: true, force: true}) } - else + else { console.debug(`metrics/compute/${login}/plugins > licenses > licensed not available`) - + } //List licenses properties console.debug(`metrics/compute/${login}/plugins > licenses > compute licenses properties`) - const base = {permissions:new Set(), limitations:new Set(), conditions:new Set()} - const combined = {permissions:new Set(), limitations:new Set(), conditions:new Set()} + const base = {permissions: new Set(), limitations: new Set(), conditions: new Set()} + const combined = {permissions: new Set(), limitations: new Set(), conditions: new Set()} const detected = Object.entries(used).map(([key, _value]) => ({key})) for (const properties of Object.keys(base)) { //Base license @@ -89,15 +91,15 @@ export default async function({login, q, imports, data, graphql, queries, accoun //Merge limitations and conditions for (const properties of ["limitations", "conditions"]) - result[properties] = [[...base[properties]].map(key => ({key, text:text[key], inherited:false})), [...combined[properties]].filter(key => !base[properties].has(key)).map(key => ({key, text:text[key], inherited:true}))].flat() + result[properties] = [[...base[properties]].map(key => ({key, text: text[key], inherited: false})), [...combined[properties]].filter(key => !base[properties].has(key)).map(key => ({key, text: text[key], inherited: true}))].flat() //Remove base permissions conflicting with inherited limitations - result.permissions = [...base.permissions].filter(key => !combined.limitations.has(key)).map(key => ({key, text:text[key]})) + result.permissions = [...base.permissions].filter(key => !combined.limitations.has(key)).map(key => ({key, text: text[key]})) //Count used licenses console.debug(`metrics/compute/${login}/plugins > licenses > computing ratio`) const total = Object.values(used).reduce((a, b) => a + b, 0) //Format used licenses and compute positions - const list = Object.entries(used).map(([key, count]) => ({name:licenses[key]?.spdxId ?? `${key.charAt(0).toLocaleUpperCase()}${key.substring(1)}`, key, count, value:count / total, x:0, color:licenses[key]?.color ?? "#6e7681", order:licenses[key]?.order ?? -1})).sort(( + const list = Object.entries(used).map(([key, count]) => ({name: licenses[key]?.spdxId ?? `${key.charAt(0).toLocaleUpperCase()}${key.substring(1)}`, key, count, value: count / total, x: 0, color: licenses[key]?.color ?? "#6e7681", order: licenses[key]?.order ?? -1})).sort(( a, b, ) => a.order === b.order ? b.count - a.count : b.order - a.order) @@ -111,7 +113,7 @@ export default async function({login, q, imports, data, graphql, queries, accoun } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/lines/index.mjs b/source/plugins/lines/index.mjs index ea26d12f..e7db0200 100644 --- a/source/plugins/lines/index.mjs +++ b/source/plugins/lines/index.mjs @@ -11,24 +11,25 @@ export default async function({login, data, imports, rest, q, account}, {enabled skipped.push(...data.shared["repositories.skipped"]) //Context - let context = {mode:"user"} + let context = {mode: "user"} if (q.repo) { console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`) - context = {...context, mode:"repository"} + context = {...context, mode: "repository"} } //Repositories - const repositories = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})) ?? [] + const repositories = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})) ?? [] //Get contributors stats from repositories console.debug(`metrics/compute/${login}/plugins > lines > querying api`) - const lines = {added:0, deleted:0, changed:0} - const response = [...await Promise.allSettled(repositories.map(({repo, owner}) => (skipped.includes(repo.toLocaleLowerCase())) || (skipped.includes(`${owner}/${repo}`.toLocaleLowerCase())) ? {} : rest.repos.getContributorsStats({owner, repo})))].filter(({status}) => status === "fulfilled" + const lines = {added: 0, deleted: 0, changed: 0} + const response = [...await Promise.allSettled(repositories.map(({repo, owner}) => (skipped.includes(repo.toLocaleLowerCase())) || (skipped.includes(`${owner}/${repo}`.toLocaleLowerCase())) ? {} : rest.repos.getContributorsStats({owner, repo})))].filter(({status}) => + status === "fulfilled" ).map(({value}) => value) //Compute changed lines console.debug(`metrics/compute/${login}/plugins > lines > computing total diff`) - response.map(({data:repository}) => { + response.map(({data: repository}) => { //Check if data are available if (!Array.isArray(repository)) return @@ -43,6 +44,6 @@ export default async function({login, data, imports, rest, q, account}, {enabled } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/music/index.mjs b/source/plugins/music/index.mjs index 663fbc2b..1fdf0d63 100644 --- a/source/plugins/music/index.mjs +++ b/source/plugins/music/index.mjs @@ -3,28 +3,28 @@ import crypto from "crypto" //Supported providers const providers = { - apple:{ - name:"Apple Music", - embed:/^https:..embed.music.apple.com.\w+.playlist/, + apple: { + name: "Apple Music", + embed: /^https:..embed.music.apple.com.\w+.playlist/, }, - spotify:{ - name:"Spotify", - embed:/^https:..open.spotify.com.embed.playlist/, + spotify: { + name: "Spotify", + embed: /^https:..open.spotify.com.embed.playlist/, }, - lastfm:{ - name:"Last.fm", - embed:/^\b$/, + lastfm: { + name: "Last.fm", + embed: /^\b$/, }, - youtube:{ - name:"YouTube Music", - embed:/^https:..music.youtube.com.playlist/, + youtube: { + name: "YouTube Music", + embed: /^https:..music.youtube.com.playlist/, }, } //Supported modes const modes = { - playlist:"Suggested tracks", - recent:"Recently played", - top:"Top played", + playlist: "Suggested tracks", + recent: "Recently played", + top: "Top played", } //Setup @@ -47,7 +47,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal let tracks = null //Load inputs - let {provider, mode, playlist, limit, user, "played.at":played_at, "time.range":time_range, "top.type":top_type, token:_token} = imports.metadata.plugins.music.inputs({data, account, q}) + let {provider, mode, playlist, limit, user, "played.at": played_at, "time.range": time_range, "top.type": top_type, token: _token} = imports.metadata.plugins.music.inputs({data, account, q}) if ((sandbox) && (_token)) { token = _token console.debug(`metrics/compute/${login}/plugins > music > overriden token value through user inputs as sandbox mode is enabled`) @@ -71,16 +71,16 @@ export default async function({login, imports, data, q, account}, {enabled = fal } //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} + 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))) @@ -111,9 +111,9 @@ export default async function({login, imports, data, q, account}, {enabled = fal ...await frame.evaluate(() => { const tracklist = document.querySelector("embed-root").shadowRoot.querySelector(".audio-tracklist") return [...tracklist.querySelectorAll("embed-audio-tracklist-item")].map(item => ({ - name:item.querySelector(".audio-tracklist-item__metadata h3").innerText, - artist:item.querySelector(".audio-tracklist-item__metadata h4").innerText, - artwork:item.querySelector("apple-music-artwork")?.shadowRoot?.querySelector("picture source")?.srcset?.split(",")?.[0]?.replace(/\s+\d+x$/, ""), + name: item.querySelector(".audio-tracklist-item__metadata h3").innerText, + artist: item.querySelector(".audio-tracklist-item__metadata h4").innerText, + artwork: item.querySelector("apple-music-artwork")?.shadowRoot?.querySelector("picture source")?.srcset?.split(",")?.[0]?.replace(/\s+\d+x$/, ""), })) }), ] @@ -124,11 +124,12 @@ export default async function({login, imports, data, q, account}, {enabled = fal //Parse tracklist await frame.waitForSelector("table") tracks = [ - ...await frame.evaluate(() => [...document.querySelectorAll("table tr")].map(tr => ({ - name:tr.querySelector("td:nth-child(2) div div:nth-child(1)").innerText, - artist:tr.querySelector("td:nth-child(2) div div:nth-child(2)").innerText, + ...await frame.evaluate(() => + [...document.querySelectorAll("table tr")].map(tr => ({ + name: tr.querySelector("td:nth-child(2) div div:nth-child(1)").innerText, + artist: tr.querySelector("td:nth-child(2) div 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 ?? document.querySelector("button").parentNode, null).backgroundImage.match(/^url\("(?https:...+)"\)$/)?.groups?.url ?? null, + artwork: window.getComputedStyle(document.querySelector("button[title=Play]")?.parentNode ?? document.querySelector("button").parentNode, null).backgroundImage.match(/^url\("(?https:...+)"\)$/)?.groups?.url ?? null, })) ), ] @@ -140,10 +141,11 @@ export default async function({login, imports, data, q, account}, {enabled = fal await frame.evaluate(() => window.scrollBy(0, window.innerHeight)) //Parse tracklist tracks = [ - ...await frame.evaluate(() => [...document.querySelectorAll("ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer")].map(item => ({ - name:item.querySelector("yt-formatted-string.title > a")?.innerText ?? "", - artist:item.querySelector(".secondary-flex-columns > yt-formatted-string > a")?.innerText ?? "", - artwork:item.querySelector("img").src, + ...await frame.evaluate(() => + [...document.querySelectorAll("ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer")].map(item => ({ + name: item.querySelector("yt-formatted-string.title > a")?.innerText ?? "", + artist: item.querySelector(".secondary-flex-columns > yt-formatted-string > a")?.innerText ?? "", + artwork: item.querySelector("img").src, })) ), ] @@ -151,7 +153,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal } //Unsupported default: - throw {error:{message:`Unsupported mode "${mode}" for provider "${provider}"`}, ...raw} + throw {error: {message: `Unsupported mode "${mode}" for provider "${provider}"`}, ...raw} } //Close browser console.debug(`metrics/compute/${login}/plugins > music > closing browser`) @@ -160,7 +162,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal if (Array.isArray(tracks)) { //Tracks console.debug(`metrics/compute/${login}/plugins > music > found ${tracks.length} tracks`) - console.debug(imports.util.inspect(tracks, {depth:Infinity, maxStringLength:256})) + console.debug(imports.util.inspect(tracks, {depth: Infinity, maxStringLength: 256})) //Shuffle tracks tracks = imports.shuffle(tracks) } @@ -175,14 +177,14 @@ export default async function({login, imports, data, q, account}, {enabled = fal //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`) @@ -193,16 +195,16 @@ export default async function({login, imports, data, q, account}, {enabled = fal //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}`, + 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.format.date(played_at, {time:true})} on ${imports.format.date(played_at, {date:true})}` : null, + name: track.name, + artist: track.artists[0].name, + artwork: track.album.images[0].url, + played_at: played_at ? `${imports.format.date(played_at, {time: true})} on ${imports.format.date(played_at, {date: true})}` : null, })) //Ensure no duplicate are added for (const track of loaded) { @@ -221,7 +223,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal const description = error.response.data?.error_description ?? null const message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null - throw {error:{message, instance:error}, ...raw} + throw {error: {message, instance: error}, ...raw} } throw error } @@ -233,14 +235,14 @@ export default async function({login, imports, data, q, account}, {enabled = fal 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:{ - "User-Agent":"lowlighter/metrics", - Accept:"application/json", + headers: { + "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"], + name: track.name, + artist: track.artist["#text"], + artwork: track.image.reverse()[0]["#text"], })) } //Handle errors @@ -250,7 +252,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal const description = error.response.data?.message ?? null const message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null - throw {error:{message, instance:error}, ...raw} + throw {error: {message, instance: error}, ...raw} } throw error } @@ -267,25 +269,25 @@ export default async function({login, imports, data, q, account}, {enabled = fal //Request access token console.debug(`metrics/compute/${login}/plugins > music > requesting access token with youtube refresh token`) const res = await imports.axios.post("https://music.youtube.com/youtubei/v1/browse?alt=json&key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30", { - browseEndpointContextSupportedConfigs:{ - browseEndpointContextMusicConfig:{ - pageType:"MUSIC_PAGE_TYPE_PLAYLIST", + browseEndpointContextSupportedConfigs: { + browseEndpointContextMusicConfig: { + pageType: "MUSIC_PAGE_TYPE_PLAYLIST", }, }, - context:{ - client:{ - clientName:"WEB_REMIX", - clientVersion:"1.20211129.00.01", - gl:"US", - hl:"en", + context: { + client: { + clientName: "WEB_REMIX", + clientVersion: "1.20211129.00.01", + gl: "US", + hl: "en", }, }, - browseId:"FEmusic_history", + browseId: "FEmusic_history", }, { - headers:{ - Authorization:SAPISIDHASH, - Cookie:token, - "x-origin":"https://music.youtube.com", + headers: { + Authorization: SAPISIDHASH, + Cookie: token, + "x-origin": "https://music.youtube.com", }, }) //Retrieve tracks @@ -296,9 +298,9 @@ export default async function({login, imports, data, q, account}, {enabled = fal for (let i = 0; i < parsedHistory.length; i++) { let track = parsedHistory[i] tracks.push({ - name:track.flexColumns[0].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text, - artist:track.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text, - artwork:track.thumbnail.musicThumbnailRenderer.thumbnail.thumbnails[0].url, + name: track.flexColumns[0].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text, + artist: track.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text, + artwork: track.thumbnail.musicThumbnailRenderer.thumbnail.thumbnails[0].url, }) //Early break if (tracks.length >= limit) @@ -312,7 +314,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal const description = error.response.data?.error_description ?? null const message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null - throw {error:{message, instance:error}, ...raw} + throw {error: {message, instance: error}, ...raw} } throw error } @@ -320,7 +322,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal } //Unsupported default: - throw {error:{message:`Unsupported mode "${mode}" for provider "${provider}"`}, ...raw} + throw {error: {message: `Unsupported mode "${mode}" for provider "${provider}"`}, ...raw} } break } @@ -337,7 +339,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal time_msg = "overall" break default: - throw {error:{message:`Unsupported time range "${time_range}"`}, ...raw} + throw {error: {message: `Unsupported time range "${time_range}"`}, ...raw} } if (top_type === "artists") { @@ -362,17 +364,17 @@ export default async function({login, imports, data, q, account}, {enabled = fal //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"}} else if (limit > 50) - throw {error:{message:"Spotify top limit cannot be greater than 50"}} + throw {error: {message: "Spotify top limit cannot be greater than 50"}} //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`) @@ -384,33 +386,33 @@ export default async function({login, imports, data, q, account}, {enabled = fal await imports.axios.get( `https://api.spotify.com/v1/me/top/artists?time_range=${time_range}_term&limit=${limit}`, { - headers:{ - "Content-Type":"application/json", - Accept:"application/json", - Authorization:`Bearer ${access}`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${access}`, }, }, ) ).data.items.map(({name, genres, images}) => ({ name, - artist:genres.join(" • "), - artwork:images[0].url, + artist: genres.join(" • "), + artwork: images[0].url, })) : ( await imports.axios.get( `https://api.spotify.com/v1/me/top/tracks?time_range=${time_range}_term&limit=${limit}`, { - headers:{ - "Content-Type":"application/json", - Accept:"application/json", - Authorization:`Bearer ${access}`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${access}`, }, }, ) ).data.items.map(({name, artists, album}) => ({ name, - artist:artists[0].name, - artwork:album.images[0].url, + artist: artists[0].name, + artwork: album.images[0].url, })) //Ensure no duplicate are added for (const track of loaded) { @@ -425,7 +427,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal const description = error.response.data?.error_description ?? null const message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null - throw {error:{message, instance:error}, ...raw} + throw {error: {message, instance: error}, ...raw} } throw error } @@ -442,31 +444,31 @@ export default async function({login, imports, data, q, account}, {enabled = fal await imports.axios.get( `https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${user}&api_key=${token}&limit=${limit}&period=${period}&format=json`, { - headers:{ - "User-Agent":"lowlighter/metrics", - Accept:"application/json", + headers: { + "User-Agent": "lowlighter/metrics", + Accept: "application/json", }, }, ) ).data.topartists.artist.map(artist => ({ - name:artist.name, - artist:`Play count: ${artist.playcount}`, - artwork:artist.image.reverse()[0]["#text"], + name: artist.name, + artist: `Play count: ${artist.playcount}`, + artwork: artist.image.reverse()[0]["#text"], })) : ( await imports.axios.get( `https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${user}&api_key=${token}&limit=${limit}&period=${period}&format=json`, { - headers:{ - "User-Agent":"lowlighter/metrics", - Accept:"application/json", + headers: { + "User-Agent": "lowlighter/metrics", + Accept: "application/json", }, }, ) ).data.toptracks.track.map(track => ({ - name:track.name, - artist:track.artist.name, - artwork:track.image.reverse()[0]["#text"], + name: track.name, + artist: track.artist.name, + artwork: track.image.reverse()[0]["#text"], })) } //Handle errors @@ -476,7 +478,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal const description = error.response.data?.message ?? null const message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null - throw {error:{message, instance:error}, ...raw} + throw {error: {message, instance: error}, ...raw} } throw error } @@ -484,13 +486,13 @@ export default async function({login, imports, data, q, account}, {enabled = fal } //Unsupported default: - throw {error:{message:`Unsupported mode "${mode}" for provider "${provider}"`}, ...raw} + throw {error: {message: `Unsupported mode "${mode}" for provider "${provider}"`}, ...raw} } break } //Unsupported default: - throw {error:{message:`Unsupported mode "${mode}"`}, ...raw} + throw {error: {message: `Unsupported mode "${mode}"`}, ...raw} } //Format tracks @@ -511,13 +513,13 @@ export default async function({login, imports, data, q, account}, {enabled = fal } //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) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/notable/index.mjs b/source/plugins/notable/index.mjs index 3c9e2d30..ba3d4db4 100644 --- a/source/plugins/notable/index.mjs +++ b/source/plugins/notable/index.mjs @@ -17,19 +17,20 @@ export default async function({login, q, imports, rest, graphql, data, account, let pushed = 0 do { console.debug(`metrics/compute/${login}/plugins > notable > retrieving contributed repositories after ${cursor}`) - const {user:{repositoriesContributedTo:{edges}}} = await graphql(queries.notable.contributions({login, types:types.map(x => x.toLocaleUpperCase()).join(", "), after:cursor ? `after: "${cursor}"` : "", repositories:data.shared["repositories.batch"] || 100})) + const {user: {repositoriesContributedTo: {edges}}} = await graphql(queries.notable.contributions({login, types: types.map(x => x.toLocaleUpperCase()).join(", "), after: cursor ? `after: "${cursor}"` : "", repositories: data.shared["repositories.batch"] || 100})) cursor = edges?.[edges?.length - 1]?.cursor edges .filter(({node}) => !((skipped.includes(node.nameWithOwner.toLocaleLowerCase())) || (skipped.includes(node.nameWithOwner.split("/")[1].toLocaleLowerCase())))) - .filter(({node}) => ({all:true, organization:node.isInOrganization, user:!node.isInOrganization}[from])) - .filter(({node}) => imports.ghfilter(filter, {name:node.nameWithOwner, user:node.owner.login, stars:node.stargazers.totalCount, watchers:node.watchers.totalCount, forks:node.forks.totalCount})) - .map(({node}) => contributions.push({handle:node.nameWithOwner, stars:node.stargazers.totalCount, issues:node.issues.totalCount, pulls:node.pullRequests.totalCount, organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl})) + .filter(({node}) => ({all: true, organization: node.isInOrganization, user: !node.isInOrganization}[from])) + .filter(({node}) => imports.ghfilter(filter, {name: node.nameWithOwner, user: node.owner.login, stars: node.stargazers.totalCount, watchers: node.watchers.totalCount, forks: node.forks.totalCount})) + .map(({node}) => contributions.push({handle: node.nameWithOwner, stars: node.stargazers.totalCount, issues: node.issues.totalCount, pulls: node.pullRequests.totalCount, organization: node.isInOrganization, avatarUrl: node.owner.avatarUrl})) pushed = edges.length } while ((pushed) && (cursor)) } //Set contributions - contributions = (await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name:handle.split("/").shift(), handle, stars, issues, pulls, avatar:await imports.imgb64(avatarUrl), organization})))).sort((a, b) => a.name.localeCompare(b.name) + contributions = (await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name: handle.split("/").shift(), handle, stars, issues, pulls, avatar: await imports.imgb64(avatarUrl), organization})))).sort((a, b) => + a.name.localeCompare(b.name) ) console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`) @@ -46,9 +47,9 @@ export default async function({login, q, imports, rest, graphql, data, account, let pushed = 0 do { console.debug(`metrics/compute/${login}/plugins > notable > retrieving user issues after ${cursor}`) - const {user:{issues:{edges}}} = await graphql(queries.notable.issues({login, type:"issues", after:cursor ? `after: "${cursor}"` : ""})) + const {user: {issues: {edges}}} = await graphql(queries.notable.issues({login, type: "issues", after: cursor ? `after: "${cursor}"` : ""})) cursor = edges?.[edges?.length - 1]?.cursor - edges.map(({node:{repository:{nameWithOwner:repository}}}) => issues[repository] = (issues[repositories] ?? 0) + 1) + edges.map(({node: {repository: {nameWithOwner: repository}}}) => issues[repository] = (issues[repositories] ?? 0) + 1) pushed = edges.length } while ((pushed) && (cursor)) } @@ -60,9 +61,9 @@ export default async function({login, q, imports, rest, graphql, data, account, let pushed = 0 do { console.debug(`metrics/compute/${login}/plugins > notable > retrieving user pull requests after ${cursor}`) - const {user:{pullRequests:{edges}}} = await graphql(queries.notable.issues({login, type:"pullRequests", after:cursor ? `after: "${cursor}"` : ""})) + const {user: {pullRequests: {edges}}} = await graphql(queries.notable.issues({login, type: "pullRequests", after: cursor ? `after: "${cursor}"` : ""})) cursor = edges?.[edges?.length - 1]?.cursor - edges.map(({node:{repository:{nameWithOwner:repository}}}) => pulls[repository] = (pulls[repositories] ?? 0) + 1) + edges.map(({node: {repository: {nameWithOwner: repository}}}) => pulls[repository] = (pulls[repositories] ?? 0) + 1) pushed = edges.length } while ((pushed) && (cursor)) } @@ -74,24 +75,24 @@ export default async function({login, q, imports, rest, graphql, data, account, const [owner, repo] = handle.split("/") try { //Count total commits on repository - const {repository:{defaultBranchRef:{target:{history}}}} = await graphql(queries.notable.commits({owner, repo})) + const {repository: {defaultBranchRef: {target: {history}}}} = await graphql(queries.notable.commits({owner, repo})) contribution.history = history.totalCount //Load maintainers (errors probably means that token is not allowed to list contributors hence not a maintainer of said repo) - const {data:collaborators} = await rest.repos.listCollaborators({owner, repo}).catch(() => ({data:[]})) - const maintainers = collaborators.filter(({role_name:role}) => ["admin", "maintain", "write"].includes(role)).map(({login}) => login) + const {data: collaborators} = await rest.repos.listCollaborators({owner, repo}).catch(() => ({data: []})) + const maintainers = collaborators.filter(({role_name: role}) => ["admin", "maintain", "write"].includes(role)).map(({login}) => login) //Count total commits of user - const {data:contributions = []} = await rest.repos.getContributorsStats({owner, repo}) - const commits = contributions.filter(({author}) => author.login.toLocaleLowerCase() === login.toLocaleLowerCase()).reduce((a, {total:b}) => a + b, 0) + const {data: contributions = []} = await rest.repos.getContributorsStats({owner, repo}) + const commits = contributions.filter(({author}) => author.login.toLocaleLowerCase() === login.toLocaleLowerCase()).reduce((a, {total: b}) => a + b, 0) //Save user data contribution.user = { commits, - percentage:commits / contribution.history, - maintainer:maintainers.includes(login), - issues:issues[handle] ?? 0, - pulls:pulls[handle] ?? 0, + percentage: commits / contribution.history, + maintainer: maintainers.includes(login), + issues: issues[handle] ?? 0, + pulls: pulls[handle] ?? 0, get stars() { return Math.round(this.maintainer ? stars : this.percentage * stars) }, @@ -115,7 +116,7 @@ export default async function({login, q, imports, rest, graphql, data, account, const aggregate = aggregated.get(key) aggregate.aggregated++ if (indepth) { - const {history = 0, user:{commits = 0, percentage = 0, maintainer = false} = {}} = _extras + const {history = 0, user: {commits = 0, percentage = 0, maintainer = false} = {}} = _extras aggregate.history = aggregate.history ?? 0 aggregate.history += history aggregate.user = aggregate.user ?? {} @@ -124,16 +125,16 @@ export default async function({login, q, imports, rest, graphql, data, account, aggregate.user.maintainer = aggregate.user.maintainer || maintainer } } - else - aggregated.set(key, {name:key, handle, avatar, organization, stars, aggregated:1, ..._extras}) - + else { + aggregated.set(key, {name: key, handle, avatar, organization, stars, aggregated: 1, ..._extras}) + } } contributions = [...aggregated.values()] if (indepth) { //Normalize contribution percentage contributions.map(aggregate => aggregate.user ? aggregate.user.percentage /= aggregate.aggregated : null) //Additional filtering (no user commits means that API wasn't able to answer back, considering it as matching by default) - contributions = contributions.filter(({handle, user}) => !user?.commits ? true : imports.ghfilter(filter, {handle, commits:contributions.history, "commits.user":user.commits, "commits.user%":user.percentage * 100, maintainer:user.maintainer})) + contributions = contributions.filter(({handle, user}) => !user?.commits ? true : imports.ghfilter(filter, {handle, commits: contributions.history, "commits.user": user.commits, "commits.user%": user.percentage * 100, maintainer: user.maintainer})) //Sort contribution by maintainer first and then by contribution percentage contributions = contributions.sort((a, b) => ((b.user?.percentage + b.user?.maintainer) || 0) - ((a.user?.percentage + a.user?.maintainer) || 0)) } @@ -143,6 +144,6 @@ export default async function({login, q, imports, rest, graphql, data, account, } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/pagespeed/index.mjs b/source/plugins/pagespeed/index.mjs index 57f1bf24..d02cf66e 100644 --- a/source/plugins/pagespeed/index.mjs +++ b/source/plugins/pagespeed/index.mjs @@ -12,7 +12,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal if (!/^https?:[/][/]/.test(url)) url = `https://${url}` const {protocol, host} = imports.url.parse(url) - const result = {url:`${protocol}//${host}`, detailed, scores:[], metrics:{}} + const result = {url: `${protocol}//${host}`, detailed, scores: [], metrics: {}} //Load scores from API console.debug(`metrics/compute/${login}/plugins > pagespeed > querying api for ${result.url}`) const scores = new Map() @@ -53,6 +53,6 @@ export default async function({login, imports, data, q, account}, {enabled = fal message = `API returned ${status}${description ? ` (${description})` : ""}` error = error.response?.data ?? null } - throw {error:{message, instance:error}} + throw {error: {message, instance: error}} } } diff --git a/source/plugins/people/index.mjs b/source/plugins/people/index.mjs index 1446dfa0..716cbd7c 100644 --- a/source/plugins/people/index.mjs +++ b/source/plugins/people/index.mjs @@ -8,20 +8,20 @@ export default async function({login, data, graphql, rest, q, queries, imports, //Context let context = { - mode:"user", - types:account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "membersWithRole", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"], - default:"followers, following", - alias:{followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor", members:"membersWithRole"}, - sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"}, + mode: "user", + types: account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "membersWithRole", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"], + default: "followers, following", + alias: {followed: "following", sponsors: "sponsorshipsAsMaintainer", sponsored: "sponsorshipsAsSponsor", sponsoring: "sponsorshipsAsSponsor", members: "membersWithRole"}, + sponsorships: {sponsorshipsAsMaintainer: "sponsorEntity", sponsorshipsAsSponsor: "sponsorable"}, } if (q.repo) { console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`) - const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift() - context = {...context, mode:"repository", types:["contributors", "stargazers", "watchers", "sponsorshipsAsMaintainer", "thanks"], default:"stargazers, watchers", owner, repo} + const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift() + context = {...context, mode: "repository", types: ["contributors", "stargazers", "watchers", "sponsorshipsAsMaintainer", "thanks"], default: "stargazers, watchers", owner, repo} } //Load inputs - let {limit, types, size, identicons, "identicons.hide":_identicons_hide, thanks, shuffle, "sponsors.custom":_sponsors} = imports.metadata.plugins.people.inputs({data, account, q}, {types:context.default}) + let {limit, types, size, identicons, "identicons.hide": _identicons_hide, thanks, shuffle, "sponsors.custom": _sponsors} = imports.metadata.plugins.people.inputs({data, account, q}, {types: context.default}) //Filter types types = [...new Set([...types].map(type => (context.alias[type] ?? type)).filter(type => context.types.includes(type)) ?? [])] if ((types.includes("sponsorshipsAsMaintainer")) && (_sponsors?.length)) { @@ -38,13 +38,13 @@ export default async function({login, data, graphql, rest, q, queries, imports, //Rest if (type === "contributors") { const {owner, repo} = context - const {data:nodes} = await rest.repos.listContributors({owner, repo}) - result[type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl:avatar_url}))) + const {data: nodes} = await rest.repos.listContributors({owner, repo}) + result[type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl: avatar_url}))) } else if ((type === "thanks") || (type === "sponsorshipsCustom")) { - const users = {thanks, sponsorshipsCustom:_sponsors}[type] ?? [] + const users = {thanks, sponsorshipsCustom: _sponsors}[type] ?? [] const nodes = await Promise.all(users.map(async username => (await rest.users.getByUsername({username})).data)) - result[{sponsorshipsCustom:"sponsorshipsAsMaintainer"}[type] ?? type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl:avatar_url}))) + result[{sponsorshipsCustom: "sponsorshipsAsMaintainer"}[type] ?? type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl: avatar_url}))) } //GraphQL else { @@ -52,12 +52,12 @@ export default async function({login, data, graphql, rest, q, queries, imports, let pushed = 0 do { console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type} after ${cursor}`) - const {[type]:{edges}} = ( + const {[type]: {edges}} = ( type in context.sponsorships - ? (await graphql(queries.people.sponsors({login:context.owner ?? login, type, size, after:cursor ? `after: "${cursor}"` : "", target:context.sponsorships[type], account})))[account] + ? (await graphql(queries.people.sponsors({login: context.owner ?? login, type, size, after: cursor ? `after: "${cursor}"` : "", target: context.sponsorships[type], account})))[account] : context.mode === "repository" - ? (await graphql(queries.people.repository({login:context.owner, repository:context.repo, type, size, after:cursor ? `after: "${cursor}"` : "", account})))[account].repository - : (await graphql(queries.people({login, type, size, after:cursor ? `after: "${cursor}"` : "", account})))[account] + ? (await graphql(queries.people.repository({login: context.owner, repository: context.repo, type, size, after: cursor ? `after: "${cursor}"` : "", account})))[account].repository + : (await graphql(queries.people({login, type, size, after: cursor ? `after: "${cursor}"` : "", account})))[account] ) cursor = edges?.[edges?.length - 1]?.cursor result[type].push(...edges.map(({node}) => node[context.sponsorships[type]] ?? node)) @@ -102,6 +102,6 @@ export default async function({login, data, graphql, rest, q, queries, imports, } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/posts/index.mjs b/source/plugins/posts/index.mjs index cafd87d6..b5399179 100644 --- a/source/plugins/posts/index.mjs +++ b/source/plugins/posts/index.mjs @@ -17,21 +17,21 @@ export default async function({login, data, imports, q, queries, account}, {enab //Dev.to case "dev.to": { console.debug(`metrics/compute/${login}/plugins > posts > querying api`) - posts = (await imports.axios.get(`https://dev.to/api/articles?username=${user}&state=fresh`)).data.map(({title, description, published_at:date, cover_image:image, url:link}) => ({title, description, date, image, link})) + posts = (await imports.axios.get(`https://dev.to/api/articles?username=${user}&state=fresh`)).data.map(({title, description, published_at: date, cover_image: image, url: link}) => ({title, description, date, image, link})) link = `https://dev.to/${user}` break } //Hashnode case "hashnode": { - posts = (await imports.axios.post("https://api.hashnode.com", {query:queries.posts.hashnode({user})}, {headers:{"Content-type":"application/json"}})).data.data.user.publication.posts.map(( - {title, brief:description, dateAdded:date, coverImage:image, slug}, - ) => ({title, description, date, image, link:`https://hashnode.com/post/${slug}`})) + posts = (await imports.axios.post("https://api.hashnode.com", {query: queries.posts.hashnode({user})}, {headers: {"Content-type": "application/json"}})).data.data.user.publication.posts.map(( + {title, brief: description, dateAdded: date, coverImage: image, slug}, + ) => ({title, description, date, image, link: `https://hashnode.com/post/${slug}`})) link = `https://hashnode.com/@${user}` break } //Unsupported default: - throw {error:{message:`Unsupported source "${source}"`}} + throw {error: {message: `Unsupported source "${source}"`}} } //Format posts @@ -44,19 +44,19 @@ export default async function({login, data, imports, q, queries, account}, {enab //Cover images if (covers) { console.debug(`metrics/compute/${login}/plugins > posts > formatting cover images`) - posts = await Promise.all(posts.map(async ({image, ...post}) => ({image:await imports.imgb64(image, {width:144, height:-1}), ...post}))) + posts = await Promise.all(posts.map(async ({image, ...post}) => ({image: await imports.imgb64(image, {width: 144, height: -1}), ...post}))) } //Results - return {user, source, link, descriptions, covers, list:posts} + return {user, source, link, descriptions, covers, list: posts} } //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) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/projects/index.mjs b/source/plugins/projects/index.mjs index ba917702..b227edfd 100644 --- a/source/plugins/projects/index.mjs +++ b/source/plugins/projects/index.mjs @@ -15,7 +15,7 @@ export default async function({login, data, imports, graphql, q, queries, accoun //Retrieve user owned projects from graphql api console.debug(`metrics/compute/${login}/plugins > projects > querying api`) - const {[account]:{projects}} = await graphql(queries.projects.user({login, limit, account})) + const {[account]: {projects}} = await graphql(queries.projects.user({login, limit, account})) //Retrieve repositories projects from graphql api for (const identifier of repositories) { @@ -25,7 +25,7 @@ export default async function({login, data, imports, graphql, q, queries, accoun let project = null for (const account of ["user", "organization"]) { try { - ({project} = (await graphql(queries.projects.repository({user, repository, id, account})))[account].repository) + ;({project} = (await graphql(queries.projects.repository({user, repository, id, account})))[account].repository) } catch (error) { console.debug(error) @@ -52,9 +52,9 @@ export default async function({login, data, imports, graphql, q, queries, accoun else if (time < 30) updated = `${Math.floor(time)} day${time >= 2 ? "s" : ""} ago` //Format progress - const {enabled, todoCount:todo, inProgressCount:doing, doneCount:done} = project.progress + const {enabled, todoCount: todo, inProgressCount: doing, doneCount: done} = project.progress //Append - list.push({name:project.name, updated, description:project.body, progress:{enabled, todo, doing, done, total:todo + doing + done}}) + list.push({name: project.name, updated, description: project.body, progress: {enabled, todo, doing, done, total: todo + doing + done}}) } //Limit @@ -62,13 +62,13 @@ export default async function({login, data, imports, graphql, q, queries, accoun list.splice(limit) //Results - return {list, totalCount:projects.totalCount, descriptions} + return {list, totalCount: projects.totalCount, descriptions} } //Handle errors catch (error) { let message = "An error occured" if (error.errors?.map(({type}) => type)?.includes("INSUFFICIENT_SCOPES")) message = "Insufficient token rights" - throw {error:{message, instance:error}} + throw {error: {message, instance: error}} } } diff --git a/source/plugins/reactions/index.mjs b/source/plugins/reactions/index.mjs index 27d2e40a..b0a6e644 100644 --- a/source/plugins/reactions/index.mjs +++ b/source/plugins/reactions/index.mjs @@ -7,23 +7,23 @@ export default async function({login, q, imports, data, graphql, queries, accoun return null //Load inputs - let {limit:_limit1, "limit.issues":_limit2, "limit.discussions":_limit3, "limit.discussions.comments":_limit4, days, details, display, ignored} = imports.metadata.plugins.reactions.inputs({data, account, q}) + let {limit: _limit1, "limit.issues": _limit2, "limit.discussions": _limit3, "limit.discussions.comments": _limit4, days, details, display, ignored} = imports.metadata.plugins.reactions.inputs({data, account, q}) ignored.push(...data.shared["users.ignored"]) //Load issue comments const comments = [] - for (const {type, limit} of [{type:"issueComments", limit:_limit1}, {type:"issues", limit:_limit2}, {type:"repositoryDiscussionComments", limit:_limit3}, {type:"repositoryDiscussions", limit:_limit4}].filter(({limit}) => limit)) { + for (const {type, limit} of [{type: "issueComments", limit: _limit1}, {type: "issues", limit: _limit2}, {type: "repositoryDiscussionComments", limit: _limit3}, {type: "repositoryDiscussions", limit: _limit4}].filter(({limit}) => limit)) { let cursor = null, pushed = 0 const fetched = [] try { do { //Load issue comments console.debug(`metrics/compute/${login}/plugins > reactions > retrieving ${type} before ${cursor}`) - const {user:{[type]:{edges}}} = await graphql(queries.reactions({login, type, before:cursor ? `before: "${cursor}"` : ""})) + const {user: {[type]: {edges}}} = await graphql(queries.reactions({login, type, before: cursor ? `before: "${cursor}"` : ""})) cursor = edges?.[0]?.cursor //Save issue comments const filtered = edges - .flatMap(({node:{createdAt:created, reactions:{nodes:reactions}}}) => ({created:new Date(created), reactions:reactions.filter(({user = {}}) => !ignored.includes(user.login)).map(({content}) => content)})) + .flatMap(({node: {createdAt: created, reactions: {nodes: reactions}}}) => ({created: new Date(created), reactions: reactions.filter(({user = {}}) => !ignored.includes(user.login)).map(({content}) => content)})) .filter(comment => Number.isFinite(days) ? comment.created < new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true) pushed = filtered.length fetched.push(...filtered) @@ -50,13 +50,13 @@ export default async function({login, q, imports, data, graphql, queries, accoun list[reaction] = (list[reaction] ?? 0) + 1 const max = Math.max(...Object.values(list)) for (const [key, value] of Object.entries(list)) - list[key] = {value, percentage:value / reactions.length, score:value / (display === "relative" ? max : reactions.length)} + list[key] = {value, percentage: value / reactions.length, score: value / (display === "relative" ? max : reactions.length)} //Results - return {list, comments:comments.length, details, days, twemoji:q["config.twemoji"]} + return {list, comments: comments.length, details, days, twemoji: q["config.twemoji"]} } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/repositories/index.mjs b/source/plugins/repositories/index.mjs index 5e767c08..5a6d43f5 100644 --- a/source/plugins/repositories/index.mjs +++ b/source/plugins/repositories/index.mjs @@ -10,7 +10,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun let {featured} = imports.metadata.plugins.repositories.inputs({data, account, q}) //Initialization - const repositories = {list:[]} + const repositories = {list: []} //Fetch repositories informations for (const repo of featured) { @@ -33,6 +33,6 @@ export default async function({login, q, imports, graphql, queries, data, accoun } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/rss/index.mjs b/source/plugins/rss/index.mjs index a92da33d..b1befaf4 100644 --- a/source/plugins/rss/index.mjs +++ b/source/plugins/rss/index.mjs @@ -9,11 +9,11 @@ export default async function({login, q, imports, data, account}, {enabled = fal //Load inputs let {source, limit} = imports.metadata.plugins.rss.inputs({data, account, q}) if (!source) - throw {error:{message:"A RSS feed is required"}} + throw {error: {message: "A RSS feed is required"}} //Load rss feed const {title, description, link, items} = await (new imports.rss()).parseURL(source) //eslint-disable-line new-cap - const feed = items.map(({title, link, isoDate:date}) => ({title, link, date:new Date(date)})) + const feed = items.map(({title, link, isoDate: date}) => ({title, link, date: new Date(date)})) //Limit feed if (limit > 0) { @@ -22,12 +22,12 @@ export default async function({login, q, imports, data, account}, {enabled = fal } //Results - return {source:title, description, link, feed} + return {source: title, description, link, feed} } //Handle errors catch (error) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/skyline/index.mjs b/source/plugins/skyline/index.mjs index 83b17d47..ccfd8082 100644 --- a/source/plugins/skyline/index.mjs +++ b/source/plugins/skyline/index.mjs @@ -24,15 +24,15 @@ export default async function({login, q, imports, data, account}, {enabled = fal //Load page console.debug(`metrics/compute/${login}/plugins > skyline > loading skyline.github.com/${login}/${year}`) - await page.goto(`https://skyline.github.com/${login}/${year}`, {timeout:90 * 1000}) + await page.goto(`https://skyline.github.com/${login}/${year}`, {timeout: 90 * 1000}) console.debug(`metrics/compute/${login}/plugins > skyline > waiting for initial render`) const frame = page.mainFrame() - await page.waitForFunction('[...document.querySelectorAll("span")].map(span => span.innerText).includes("Share on Twitter")', {timeout:90 * 1000}) + await page.waitForFunction('[...document.querySelectorAll("span")].map(span => span.innerText).includes("Share on Twitter")', {timeout: 90 * 1000}) await frame.evaluate(() => [...document.querySelectorAll("button, footer, a")].map(element => element.remove())) //Generate gif console.debug(`metrics/compute/${login}/plugins > skyline > generating frames`) - const animation = compatibility ? await imports.record({page, width, height, frames, scale:quality}) : await imports.gif({page, width, height, frames, quality:Math.max(1, quality * 20)}) + const animation = compatibility ? await imports.record({page, width, height, frames, scale: quality}) : await imports.gif({page, width, height, frames, quality: Math.max(1, quality * 20)}) //Close puppeteer await browser.close() @@ -42,6 +42,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/sponsors/index.mjs b/source/plugins/sponsors/index.mjs index 5a5e771e..79d036cd 100644 --- a/source/plugins/sponsors/index.mjs +++ b/source/plugins/sponsors/index.mjs @@ -11,10 +11,10 @@ export default async function({login, q, imports, data, graphql, queries, accoun //Query description and goal console.debug(`metrics/compute/${login}/plugins > sponsors > querying sponsors and goal`) - const {[account]:{sponsorsListing:{fullDescription, activeGoal}}} = await graphql(queries.sponsors.description({login, account})) - const about = await imports.markdown(fullDescription, {mode:"multiline"}) - const goal = activeGoal ? {progress:activeGoal.percentComplete, title:activeGoal.title, description:await imports.markdown(activeGoal.description)} : null - const count = {total:{count:0, user:0, organization:0}, active:{total:0, user:0, organization:0}, past:{total:0, user:0, organization:0}} + const {[account]: {sponsorsListing: {fullDescription, activeGoal}}} = await graphql(queries.sponsors.description({login, account})) + const about = await imports.markdown(fullDescription, {mode: "multiline"}) + const goal = activeGoal ? {progress: activeGoal.percentComplete, title: activeGoal.title, description: await imports.markdown(activeGoal.description)} : null + const count = {total: {count: 0, user: 0, organization: 0}, active: {total: 0, user: 0, organization: 0}, past: {total: 0, user: 0, organization: 0}} //Query active sponsors let list = [] @@ -24,13 +24,13 @@ export default async function({login, q, imports, data, graphql, queries, accoun let pushed = 0 do { console.debug(`metrics/compute/${login}/sponsors > retrieving sponsors after ${cursor}`) - const {[account]:{sponsorshipsAsMaintainer:{edges, nodes}}} = await graphql(queries.sponsors.active({login, account, after:cursor ? `after: "${cursor}"` : "", size:Math.round(size*1.5)})) + const {[account]: {sponsorshipsAsMaintainer: {edges, nodes}}} = await graphql(queries.sponsors.active({login, account, after: cursor ? `after: "${cursor}"` : "", size: Math.round(size * 1.5)})) cursor = edges?.[edges?.length - 1]?.cursor fetched.push(...nodes) pushed = nodes.length console.debug(`metrics/compute/${login}/sponsors > retrieved ${pushed} sponsors after ${cursor}`) } while ((pushed) && (cursor)) - list.push(...fetched.map(({sponsorEntity:{login, avatarUrl, url:organization = null}, tier}) => ({login, avatarUrl, type:organization ? "organization" : "user", amount:tier?.monthlyPriceInDollars ?? null, past:false}))) + list.push(...fetched.map(({sponsorEntity: {login, avatarUrl, url: organization = null}, tier}) => ({login, avatarUrl, type: organization ? "organization" : "user", amount: tier?.monthlyPriceInDollars ?? null, past: false}))) await Promise.all(list.map(async user => user.avatar = await imports.imgb64(user.avatarUrl))) count.active.total = list.length count.active.user = list.filter(user => user.type === "user").length @@ -48,18 +48,18 @@ export default async function({login, q, imports, data, graphql, queries, accoun let pushed = 0 do { console.debug(`metrics/compute/${login}/sponsors > retrieving sponsors events after ${cursor}`) - const {[account]:{sponsorsActivities:{edges, nodes}}} = await graphql(queries.sponsors.all({login, account, after:cursor ? `after: "${cursor}"` : "", size:Math.round(size*1.5)})) + const {[account]: {sponsorsActivities: {edges, nodes}}} = await graphql(queries.sponsors.all({login, account, after: cursor ? `after: "${cursor}"` : "", size: Math.round(size * 1.5)})) cursor = edges?.[edges?.length - 1]?.cursor fetched.push(...nodes) pushed = nodes.length console.debug(`metrics/compute/${login}/sponsors > retrieved ${pushed} sponsors events after ${cursor}`) } while ((pushed) && (cursor)) - users.push(...fetched.map(({sponsor:{login, avatarUrl, url:organization = null}, sponsorsTier}) => ({login, avatarUrl, type:organization ? "organization" : "user", amount:sponsorsTier?.monthlyPriceInDollars ?? null, past:true}))) + users.push(...fetched.map(({sponsor: {login, avatarUrl, url: organization = null}, sponsorsTier}) => ({login, avatarUrl, type: organization ? "organization" : "user", amount: sponsorsTier?.monthlyPriceInDollars ?? null, past: true}))) } for (const user of users) { if (!active.has(user.login)) { active.add(user.login) - list.push({...user, avatar:await imports.imgb64(user.avatarUrl)}) + list.push({...user, avatar: await imports.imgb64(user.avatarUrl)}) count.past.total++ count.past[user.type]++ } @@ -77,6 +77,6 @@ export default async function({login, q, imports, data, graphql, queries, accoun } //Handle errors catch (error) { - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } diff --git a/source/plugins/stackoverflow/index.mjs b/source/plugins/stackoverflow/index.mjs index 563bd9d8..555ddf35 100644 --- a/source/plugins/stackoverflow/index.mjs +++ b/source/plugins/stackoverflow/index.mjs @@ -7,54 +7,54 @@ export default async function({login, q, imports, data, account}, {enabled = fal return null //Load inputs - let {sections, user, limit, lines, "lines.snippet":codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q}) + let {sections, user, limit, lines, "lines.snippet": codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q}) if (!user) - throw {error:{message:"You must provide a stackoverflow user id"}} + throw {error: {message: "You must provide a stackoverflow user id"}} //Initialization //See https://api.stackexchange.com/docs - const api = {base:"https://api.stackexchange.com/2.2", user:`https://api.stackexchange.com/2.2/users/${user}`} - const filters = {user:"!0Z-LvgkLYnTCu1858)*D0lcx2", answer:"!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question:"!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"} + const api = {base: "https://api.stackexchange.com/2.2", user: `https://api.stackexchange.com/2.2/users/${user}`} + const filters = {user: "!0Z-LvgkLYnTCu1858)*D0lcx2", answer: "!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question: "!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"} const result = {sections, lines} //Stackoverflow user metrics { //Account metrics console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for user ${user}`) - const {data:{items:[{reputation, badge_counts:{bronze, silver, gold}, answer_count:answers, question_count:questions, view_count:views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`) - const {data:{total:comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`) + const {data: {items: [{reputation, badge_counts: {bronze, silver, gold}, answer_count: answers, question_count: questions, view_count: views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`) + const {data: {total: comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`) //Save result - result.user = {id:user, reputation, badges:bronze + silver + gold, questions, answers, comments, views} + result.user = {id: user, reputation, badges: bronze + silver + gold, questions, answers, comments, views} } //Answers - for (const {key, sort} of [{key:"answers-recent", sort:"sort=activity&order=desc"}, {key:"answers-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) { + for (const {key, sort} of [{key: "answers-recent", sort: "sort=activity&order=desc"}, {key: "answers-top", sort: "sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) { //Load and format answers console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`) - const {data:{items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`) + const {data: {items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`) result[key] = await Promise.all(items.map(item => format.answer(item, {imports, codelines}))) console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`) //Load related questions const ids = result[key].map(({question_id}) => question_id).filter(id => id) if (ids) { console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`) - const {data:{items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`) + const {data: {items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`) await Promise.all(items.map(item => format.question(item, {imports, codelines}))) } } //Questions - for (const {key, sort} of [{key:"questions-recent", sort:"sort=activity&order=desc"}, {key:"questions-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) { + for (const {key, sort} of [{key: "questions-recent", sort: "sort=activity&order=desc"}, {key: "questions-top", sort: "sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) { //Load and format questions console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`) - const {data:{items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`) + const {data: {items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`) result[key] = await Promise.all(items.map(item => format.question(item, {imports, codelines}))) console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`) //Load related answers const ids = result[key].map(({accepted_answer_id}) => accepted_answer_id).filter(id => id) if (ids) { console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`) - const {data:{items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`) + const {data: {items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`) await Promise.all(items.map(item => format.answer(item, {imports, codelines}))) } } @@ -66,30 +66,30 @@ export default async function({login, q, imports, data, account}, {enabled = fal catch (error) { if (error.error?.message) throw error - throw {error:{message:"An error occured", instance:error}} + throw {error: {message: "An error occured", instance: error}} } } //Formatters const format = { /**Cached */ - cached:new Map(), + cached: new Map(), /**Format stackoverflow code snippets */ code(text) { return text.replace(/\s*(? {4}[\s\S]+?)(?=(?:)|(?: