|
| 1 | +import fs from "node:fs/promises"; |
| 2 | +import path from "node:path"; |
| 3 | + |
| 4 | +const loadFile = filePath => fs.readFile(`.${filePath}`, "utf8"); |
| 5 | + |
| 6 | +async function build() { |
| 7 | + const galleryData = await getGalleryData(); |
| 8 | + await fs.writeFile("Extensions/dist/page/gallery.json", JSON.stringify(galleryData), { |
| 9 | + encoding: "utf8", |
| 10 | + flag: "w+", |
| 11 | + }); |
| 12 | + |
| 13 | + const listData = await getListData(); |
| 14 | + await fs.writeFile("Extensions/dist/page/list.json", JSON.stringify(listData), {encoding: "utf8", flag: "w+"}); |
| 15 | + |
| 16 | + const themeGalleryData = await getThemeGalleryData(); |
| 17 | + await fs.writeFile("Extensions/dist/page/themes.json", JSON.stringify(themeGalleryData), { |
| 18 | + encoding: "utf8", |
| 19 | + flag: "w+", |
| 20 | + }); |
| 21 | + |
| 22 | + const extensionList = await getExtensionList(); |
| 23 | + for (const id of extensionList) { |
| 24 | + await fs.writeFile(`Extensions/dist/${id}.json`, JSON.stringify(await getExtensionData(id)), { |
| 25 | + encoding: "utf8", |
| 26 | + flag: "w+", |
| 27 | + }); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +build(); |
| 32 | + |
| 33 | +/** Each extension has the following fields: |
| 34 | + * {string} script - Contents of the extension file |
| 35 | + * {string} id - File name without extension |
| 36 | + * {string} icon - Contents of the `id`.icon.js file |
| 37 | + * {string} css - Contents of the `id`.css file |
| 38 | + * {string} title - Value of the TITLE field in `script` |
| 39 | + * {string} version - Value of the VERSION field in `script` |
| 40 | + * {string} description - Value of the DESCRIPTION field in `script` |
| 41 | + * {string} details - Value of the DETAILS field in `script` |
| 42 | + * {string} developer - Value of the DEVELOPER field in `script` |
| 43 | + * {boolean} frame - Value of the FRAME field in `script` |
| 44 | + * {boolean} [beta] - Value of the optional BETA field in `script` |
| 45 | + * {boolean} [slow] - Value of the optional SLOW field in `script` |
| 46 | + */ |
| 47 | +const extensionAttributes = [ |
| 48 | + {name: "title", default: null, required: true}, |
| 49 | + {name: "description", default: null, required: true}, |
| 50 | + {name: "developer", default: null, required: true}, |
| 51 | + {name: "version", default: null, required: true}, |
| 52 | + {name: "details", default: null, required: false}, |
| 53 | + {name: "frame", default: "false", required: false}, |
| 54 | + {name: "beta", default: "false", required: false}, |
| 55 | + {name: "slow", default: "false", required: false}, |
| 56 | +]; |
| 57 | + |
| 58 | +async function getExtensionData(id) { |
| 59 | + const contents = await loadFile(`/Extensions/${id}.js`); |
| 60 | + |
| 61 | + const extension = { |
| 62 | + id, |
| 63 | + script: contents, |
| 64 | + file: "found", |
| 65 | + server: "up", |
| 66 | + errors: false, |
| 67 | + }; |
| 68 | + |
| 69 | + try { |
| 70 | + const icon = await loadFile(`/Extensions/${id}.icon.js`); |
| 71 | + extension.icon = icon; |
| 72 | + } catch (e) {} |
| 73 | + try { |
| 74 | + const css = await loadFile(`/Extensions/${id}.css`); |
| 75 | + extension.css = css; |
| 76 | + } catch (e) {} |
| 77 | + |
| 78 | + extensionAttributes.forEach(({name: key, default: defaultValue}) => { |
| 79 | + const match = contents.match(new RegExp("/\\*\\s*" + key.toUpperCase() + "\\s*(.+?)\\s*\\*\\*?/")); |
| 80 | + if (match) { |
| 81 | + extension[key] = match[1]; |
| 82 | + } else if (defaultValue) { |
| 83 | + extension[key] = defaultValue; |
| 84 | + } |
| 85 | + }); |
| 86 | + |
| 87 | + return extension; |
| 88 | +} |
| 89 | + |
| 90 | +async function getGalleryData() { |
| 91 | + const extensionList = await getExtensionList(); |
| 92 | + const extensionData = await Promise.all(extensionList.map(getExtensionData)); |
| 93 | + |
| 94 | + // eslint-disable-next-line id-length |
| 95 | + extensionData.sort((a, b) => a.title.localeCompare(b.title)); |
| 96 | + |
| 97 | + return { |
| 98 | + server: "up", |
| 99 | + extensions: extensionData.map(({id, title, version, description, icon, details}) => ({ |
| 100 | + name: id, |
| 101 | + title, |
| 102 | + version, |
| 103 | + description, |
| 104 | + icon, |
| 105 | + details, |
| 106 | + })), |
| 107 | + }; |
| 108 | +} |
| 109 | + |
| 110 | +async function getExtensionList() { |
| 111 | + return (await fs.readdir("Extensions")) |
| 112 | + .filter(fileName => fileName.endsWith(".js") && !fileName.endsWith(".icon.js")) |
| 113 | + .map(fileName => path.parse(fileName).name); |
| 114 | +} |
| 115 | + |
| 116 | +async function getListData() { |
| 117 | + const extensionList = await getExtensionList(); |
| 118 | + const extensionData = await Promise.all(extensionList.map(getExtensionData)); |
| 119 | + |
| 120 | + return { |
| 121 | + server: "up", |
| 122 | + extensions: extensionData.map(({id, version}) => ({name: id, version})), |
| 123 | + }; |
| 124 | +} |
| 125 | + |
| 126 | +/** Each theme has the following fields: |
| 127 | + * {string} file - Contents of the theme file |
| 128 | + * {string} name - Value of the NAME field in `file` |
| 129 | + * {string} version - Value of the VERSION field in `file` |
| 130 | + * {string} description - Value of the DESCRIPTION field in `file` |
| 131 | + * {string} developer - Value of the DEVELOPER field in `file` |
| 132 | + */ |
| 133 | +const themeAttributes = [ |
| 134 | + {name: "name", default: null, required: true}, |
| 135 | + {name: "description", default: null, required: true}, |
| 136 | + {name: "developer", default: null, required: true}, |
| 137 | + {name: "version", default: null, required: true}, |
| 138 | +]; |
| 139 | +async function getThemeData(id) { |
| 140 | + const contents = await loadFile(`/Themes/${id}/${id}.css`); |
| 141 | + |
| 142 | + const theme = { |
| 143 | + file: `${id}.css`, |
| 144 | + contents, |
| 145 | + }; |
| 146 | + |
| 147 | + themeAttributes.forEach(({name: key, default: defaultValue}) => { |
| 148 | + const match = contents.match(new RegExp("/\\*\\s*" + key.toUpperCase() + "\\s*(.+?)\\s*\\*\\*?/")); |
| 149 | + if (match) { |
| 150 | + theme[key] = match[1]; |
| 151 | + } else if (defaultValue) { |
| 152 | + theme[key] = defaultValue; |
| 153 | + } |
| 154 | + }); |
| 155 | + |
| 156 | + return theme; |
| 157 | +} |
| 158 | + |
| 159 | +async function getThemeGalleryData() { |
| 160 | + const themeList = await fs.readdir("Themes"); |
| 161 | + const themeData = await Promise.all(themeList.map(getThemeData)); |
| 162 | + |
| 163 | + return { |
| 164 | + server: "up", |
| 165 | + themes: themeData.map(({file, name, version, description, developer, contents}) => ({ |
| 166 | + file, |
| 167 | + name, |
| 168 | + version, |
| 169 | + description, |
| 170 | + developer, |
| 171 | + contents, |
| 172 | + })), |
| 173 | + }; |
| 174 | +} |
0 commit comments