From 9441f47f5489d8eaf4884c00b45db9026102c934 Mon Sep 17 00:00:00 2001 From: Reece Dunham Date: Wed, 3 Feb 2021 22:59:33 +0000 Subject: [PATCH] Work works now Signed-off-by: Reece Dunham --- package.json | 20 ++- src/index.ts | 263 +++++++++++++++++++++------------- testsite/.gitignore | 2 + testsite/docusaurus.config.js | 5 +- yarn.lock | 151 +++++++++++++++++++ 5 files changed, 334 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index a4cc008..6f1d62e 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "docusaurus-plugin-remote-content", "version": "1.0.0", - "description": "A Docusaurus v2 plugin!", - "main": "src/index.js", - "repository": "https://github.com/rdilweb/template-docusaurus-plugin.git", + "description": "A Docusaurus v2 plugin that allows you to fetch content from remote sources!", + "main": "build/index.js", + "repository": "https://github.com/rdilweb/docusaurus-plugin-remote-content.git", "author": "Reece Dunham ", "license": "MIT", "scripts": { @@ -18,6 +18,7 @@ "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "@types/react-helmet": "^6.1.0", + "@types/rimraf": "^3.0.0", "commander": "^7.0.0", "prettier": "^2.2.1", "typescript": "^4.1.3" @@ -25,7 +26,16 @@ "peerDependencies": { "@docusaurus/core": "2.x" }, + "bugs": { + "url": "https://github.com/rdilweb/docusaurus-plugin-remote-content/issues" + }, "dependencies": { - "axios": "^0.21.1" - } + "axios": "^0.21.1", + "chalk": "^4.1.0", + "pretty-ms": "^7.0.1", + "rimraf": "^3.0.2" + }, + "files": [ + "build" + ] } diff --git a/src/index.ts b/src/index.ts index a52eccb..d0d15a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,129 +1,190 @@ -import type { LoadContext, Props } from "@docusaurus/types" +import type { LoadContext } from "@docusaurus/types" import type commander from "commander" import axios from "axios" import { writeFileSync } from "fs" import { join } from "path" +import { sync as delFile } from "rimraf" +import chalk from "chalk" +import milli from "pretty-ms" /** * The plugin's options. */ export interface RemoteContentPluginOptions { - /** - * Delete local content after everything? - */ - performCleanup?: boolean - - /** - * Is this instance for the docs plugin? - */ - docsIntegration?: boolean - /** - * Is this instance for the blog plugin? - */ - blogIntegration?: boolean - - /** - * The base url for the source of the content. - */ - sourceBaseUrl: string - /** - * Specify the document paths from the sourceBaseUrl - * in a string array or function that returns a string array. - */ - documents?: string[] | (() => string[]) + /** + * Delete local content after everything? + */ + performCleanup?: boolean + /** + * CLI only mode + */ + noRuntimeDownloads?: boolean + + /** + * Is this instance for the docs plugin? + */ + docsIntegration?: boolean + /** + * Is this instance for the blog plugin? + */ + blogIntegration?: boolean + + /** + * The base url for the source of the content. + */ + sourceBaseUrl: string + /** + * Specify the document paths from the sourceBaseUrl + * in a string array or function that returns a string array. + */ + documents?: string[] | (() => string[]) } export type LoadableContent = void interface Collectable { - url: string - identifier: string + url: string + identifier: string } -// @ts-ignore -export default class PluginRemoteContent extends Plugin { - name = "docusaurus-plugin-remote-content" - - options: RemoteContentPluginOptions - context: LoadContext - - constructor(context: LoadContext, options: RemoteContentPluginOptions) { - super(options, context) - - this.options = options - this.context = context - - this.onLoad() - } - - onLoad(): void { - let { blogIntegration, docsIntegration, sourceBaseUrl, documents } = this.options - - if (!([blogIntegration, docsIntegration].includes(true))) { - throw new Error("No integrations enabled! Please enable one of the blogIntegration, docsIntegration, or pagesIntegration fields in your remote-content plugin options.") - } - - if (documents === undefined) { - throw new Error("The documents field is undefined, so I don't know what to fetch!") - } - - if (sourceBaseUrl === undefined) { - throw new Error("The sourceBaseUrl field is undefined, so I don't know where to fetch from!") - } - - if (!sourceBaseUrl.endsWith("/")) { - sourceBaseUrl = `${sourceBaseUrl}/` +export default function pluginRemoteContent( + context: LoadContext, + options: RemoteContentPluginOptions +): any { + let { + blogIntegration, + docsIntegration, + sourceBaseUrl, + documents, + noRuntimeDownloads, + performCleanup, + } = options + + if (![blogIntegration, docsIntegration].includes(true)) { + throw new Error( + "No integrations enabled! Please enable either the blogIntegration or docsIntegration fields in your remote-content plugin options." + ) + } + + if (documents === undefined) { + throw new Error( + "The documents field is undefined, so I don't know what to fetch!" + ) + } + + if (sourceBaseUrl === undefined) { + throw new Error( + "The sourceBaseUrl field is undefined, so I don't know where to fetch from!" + ) + } + + if (!sourceBaseUrl.endsWith("/")) { + sourceBaseUrl = `${sourceBaseUrl}/` + } + + function findCollectables(): Collectable[] { + const a: Collectable[] = [] + + if (docsIntegration === true || blogIntegration === true) { + ;((typeof documents == "function" + ? documents.call(context.siteConfig) + : documents) as string[]).forEach((d) => { + if (d.endsWith("md")) { + a.push({ url: `${sourceBaseUrl}/${d}`, identifier: d }) + } else { + a.push({ url: `${sourceBaseUrl}/${d}.md`, identifier: `${d}.md` }) } + }) } - findCollectables(): Collectable[] { - const { documents, sourceBaseUrl } = this.options - const a: Collectable[] = [] - - if (this.options.docsIntegration === true) { - ( - (typeof documents == "function" ? documents.call(this.context.siteConfig) : documents) as string[] - ).forEach((d) => { - if (d.endsWith("md")) { - a.push({ url: `${sourceBaseUrl}/${d}`, identifier: d }) - } else { - a.push({ url: `${sourceBaseUrl}/${d}.md`, identifier: `${d}.md` }) - } - }) - } + return a + } - return a + function getTargetDirectory(): string { + if (docsIntegration) { + return join(context.siteDir, "docs") } - async loadContent(): Promise { - const c = this.findCollectables() - let loc = "" - - if (this.options.docsIntegration) { - loc = join(this.context.siteDir, "docs") - } - - if (this.options.blogIntegration) { - loc = join(this.context.siteDir, "blog") - } - - for (let i = 0; i < c.length; i++) { - writeFileSync(join(loc, c[i].identifier), await (await axios({ url: c[i].url })).data) - } + if (blogIntegration) { + return join(context.siteDir, "blog") } - async contentLoaded(): Promise { - // The contentLoaded hook is done after loadContent hook is done. - } + return "" + } - async postBuild(props: Props): Promise { - // After docusaurus finish. - } + // 'this' is a huge mess in JS, so we avoid that where we can, such as here. + const pluginReturn = { + name: `docusaurus-plugin-remote-content-${ + [blogIntegration && "blog", docsIntegration && "docs"].filter(Boolean)[0] + }`, + + async loadContent(): Promise { + if ([false, undefined].includes(noRuntimeDownloads)) { + return await pluginReturn.fetchContent() + } + }, + + async postBuild(): Promise { + if (performCleanup !== false) { + return await pluginReturn.cleanContent() + } + }, + + async fetchContent(): Promise { + const c = findCollectables() + + for (let i = 0; i < c.length; i++) { + writeFileSync( + join(getTargetDirectory(), c[i].identifier), + await (await axios({ url: c[i].url })).data + ) + } + }, + + async cleanContent(): Promise { + const c = findCollectables() + + for (let i = 0; i < c.length; i++) { + delFile(join(getTargetDirectory(), c[i].identifier)) + } + }, extendCli(cli: commander.CommanderStatic): void { - cli - .command("dothing") - .description("Does something") - .action(() => {}) - } + const t = [blogIntegration && "blog", docsIntegration && "docs"].filter( + Boolean + )[0] + + cli + .command(`download-remote-${t}`) + .description(`Downloads the remote ${t} data.`) + .action(() => { + ;(async () => { + const startTime = new Date() + await pluginReturn.fetchContent() + console.log( + chalk`{green Successfully fetched content in} {white ${milli( + (new Date() as any) - (startTime as any) + )}}{green !}` + ) + })() + }) + + cli + .command(`clear-remote-${t}`) + .description(`Removes the local copy of the remote ${t} data.`) + .action(() => { + ;(async () => { + const startTime = new Date() + await pluginReturn.cleanContent() + console.log( + chalk`{green Successfully deleted content in} {white ${milli( + (new Date() as any) - (startTime as any) + )}}{green !}` + ) + })() + }) + }, + } + + return pluginReturn } diff --git a/testsite/.gitignore b/testsite/.gitignore index b2d6de3..9ca00c3 100644 --- a/testsite/.gitignore +++ b/testsite/.gitignore @@ -18,3 +18,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +docs/api.md diff --git a/testsite/docusaurus.config.js b/testsite/docusaurus.config.js index 57f6053..22199eb 100644 --- a/testsite/docusaurus.config.js +++ b/testsite/docusaurus.config.js @@ -108,7 +108,10 @@ module.exports = { [ require.resolve("../build/index.js"), { - // options here, or if you have none, just leave this empty. + docsIntegration: true, + sourceBaseUrl: + "https://raw.githubusercontent.com/rdilweb/docs/master/docs", + documents: ["api"], }, ], ], diff --git a/yarn.lock b/yarn.lock index b056a72..a9bb5aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,6 +17,19 @@ resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== +"@types/glob@*": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + "@types/node@*": version "14.14.20" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" @@ -49,6 +62,14 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/rimraf@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f" + integrity sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -87,6 +108,13 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + axios@^0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" @@ -94,6 +122,39 @@ axios@^0.21.1: dependencies: follow-redirects "^1.10.0" +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + commander@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -104,6 +165,11 @@ commander@^7.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.0.0.tgz#3e2bbfd8bb6724760980988fb5b22b7ee6b71ab2" integrity sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA== +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + csstype@^3.0.2: version "3.0.6" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" @@ -114,21 +180,94 @@ follow-redirects@^1.10.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + lodash@^4.17.15: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +parse-ms@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + prettier@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== +pretty-ms@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" + integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== + dependencies: + parse-ms "^2.1.0" + querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -139,6 +278,13 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + typescript@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" @@ -150,3 +296,8 @@ webpack-merge@^4.2.2: integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== dependencies: lodash "^4.17.15" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=