diff --git a/src/backend/model/extension/ExtensionRepository.ts b/src/backend/model/extension/ExtensionRepository.ts new file mode 100644 index 00000000..64df2fc0 --- /dev/null +++ b/src/backend/model/extension/ExtensionRepository.ts @@ -0,0 +1,69 @@ +import {Config} from '../../../common/config/private/Config'; +import {ExtensionListItem} from '../../../common/entities/extension/ExtensionListItem'; + +export class ExtensionRepository { + + extensionsList: ExtensionListItem[]; + lastUpdate = 0; + private UPDATE_FREQUENCY_MS = 30 * 1000; + + public async getExtensionList() { + if (this.lastUpdate < Date.now() - this.UPDATE_FREQUENCY_MS) { + await this.fetchList(); + } + + return this.extensionsList; + } + + private getUrlFromMDLink(text: string) { + if (!text) { + return text; + } + text = ('' + text).trim(); + /* Match full links and relative paths */ + // source: https://davidwells.io/snippets/regex-match-markdown-links + const regex = /^\[.*]\(((?:\/|https?:\/\/)[\S./?=#]+)\)$/; + + if (text.match(regex).length > 0) { + return text.match(regex)[0].match(/https?:\/\/[\S./?=#]+/)[0].slice(0, -1); + } + return text; + } + + + public async fetchList() { + const res = await (await fetch(Config.Extensions.repositoryUrl)).text(); + const lines = res.split('\n'); + lines.forEach(line => line.trim()); + const tableStartLine = lines.findIndex(l => l.startsWith('| **Name** |')); + const tableHeaderLines = 2; + const table = lines.slice(tableStartLine + tableHeaderLines); + const extensions: ExtensionListItem[] = []; + const getUniqueID = (name: string) => { + let id = name; + let i = 2; + while (extensions.findIndex(e => e.id === id) !== -1) { + id = name + '-' + i; + ++i; + } + return id; + }; + table.forEach(l => { + const entries = l.split('|').map((l) => l.trim()).filter(e => !!e); + if (entries.length == 0) { + return; + } + + extensions.push({ + id: getUniqueID(entries[0]), + name: entries[0], + url: this.getUrlFromMDLink(entries[1]), + readme: this.getUrlFromMDLink(entries[2]), + zipUrl: this.getUrlFromMDLink(entries[3]) + }); + }); + + this.extensionsList = extensions; + this.lastUpdate = new Date().getTime(); + } +} diff --git a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts index f8aec92e..02c5e27d 100644 --- a/src/common/config/private/subconfigs/ServerExtensionsConfig.ts +++ b/src/common/config/private/subconfigs/ServerExtensionsConfig.ts @@ -49,6 +49,15 @@ export class ServerExtensionsEntryConfig { @SubConfigClass({softReadonly: true}) export class ServerExtensionsConfig extends ClientExtensionsConfig { + @ConfigProperty({ + tags: { + name: $localize`Repository url`, + priority: ConfigPriority.underTheHood + }, + description: $localize`Repository url that points to a list of extensions in .md format.`, + }) + repositoryUrl: string = 'https://raw.githubusercontent.com/bpatrik/pigallery2/master/extension/REPOSITORY.md'; + @ConfigProperty({ tags: { name: $localize`Extension folder`, diff --git a/src/common/entities/extension/ExtensionListItem.ts b/src/common/entities/extension/ExtensionListItem.ts new file mode 100644 index 00000000..c0a64b1f --- /dev/null +++ b/src/common/entities/extension/ExtensionListItem.ts @@ -0,0 +1,7 @@ +export interface ExtensionListItem { + id:string; + name: string; + url?: string; + readme?: string; + zipUrl: string; +}