| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  | import Script from "./Script" | 
					
						
							|  |  |  | import NameSuggestionIndex, { NSIItem } from "../src/Logic/Web/NameSuggestionIndex" | 
					
						
							|  |  |  | import * as nsiWD from "../node_modules/name-suggestion-index/dist/wikidata.min.json" | 
					
						
							|  |  |  | import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs" | 
					
						
							|  |  |  | import ScriptUtils from "./ScriptUtils" | 
					
						
							|  |  |  | import { Utils } from "../src/Utils" | 
					
						
							|  |  |  | import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" | 
					
						
							|  |  |  | import { FilterConfigOptionJson } from "../src/Models/ThemeConfig/Json/FilterConfigJson" | 
					
						
							|  |  |  | import { TagUtils } from "../src/Logic/Tags/TagUtils" | 
					
						
							|  |  |  | import { openSync, readSync } from "node:fs" | 
					
						
							| 
									
										
										
										
											2025-01-26 22:05:44 +01:00
										 |  |  | import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | class NsiLogos extends Script { | 
					
						
							|  |  |  |     constructor() { | 
					
						
							|  |  |  |         super("Contains various subcommands for NSI logo maintainance") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |     private async downloadLogo( | 
					
						
							|  |  |  |         nsiItem: NSIItem, | 
					
						
							|  |  |  |         type: string, | 
					
						
							|  |  |  |         basePath: string, | 
					
						
							|  |  |  |         alreadyDownloaded: Map<string, string> | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         if (nsiItem === undefined) { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |         if (alreadyDownloaded.has(nsiItem.id)) { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             return await this.downloadLogoUnsafe(nsiItem, type, basePath) | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.error("Could not download", nsiItem.displayName, "due to", e) | 
					
						
							|  |  |  |             return "error" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async downloadLogoUnsafe(nsiItem: NSIItem, type: string, basePath: string) { | 
					
						
							|  |  |  |         if (nsiItem === undefined) { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let path = basePath + nsiItem.id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const logos = nsiWD["wikidata"][nsiItem?.tags?.[type + ":wikidata"]]?.logos | 
					
						
							|  |  |  |         const nsi = await NameSuggestionIndex.getNsiIndex() | 
					
						
							|  |  |  |         if (nsi.isSvg(nsiItem, type)) { | 
					
						
							|  |  |  |             path = path + ".svg" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (existsSync(path)) { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!logos) { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (logos.facebook) { | 
					
						
							|  |  |  |             // Facebook's logos are generally better and square
 | 
					
						
							|  |  |  |             await ScriptUtils.DownloadFileTo(logos.facebook, path) | 
					
						
							|  |  |  |             // Validate
 | 
					
						
							|  |  |  |             const content = readFileSync(path, "utf8") | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |             if (content.startsWith('{"error"')) { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 unlinkSync(path) | 
					
						
							|  |  |  |                 console.error("Attempted to fetch", logos.facebook, " but this gave an error") | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 return true | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (logos.wikidata) { | 
					
						
							|  |  |  |             let url: string = logos.wikidata | 
					
						
							|  |  |  |             console.log("Downloading", url) | 
					
						
							|  |  |  |             let ttl = 10 | 
					
						
							|  |  |  |             do { | 
					
						
							|  |  |  |                 ttl-- | 
					
						
							|  |  |  |                 const dloaded = await Utils.downloadAdvanced(url, { | 
					
						
							|  |  |  |                     "User-Agent": | 
					
						
							| 
									
										
										
										
											2025-04-07 19:10:11 +02:00
										 |  |  |                         "MapComplete NSI scraper/0.1 (https://source.mapcomplete.org/MapComplete/MapComplete; pietervdvn@posteo.net)", | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |                 const redirect: string | undefined = dloaded["redirect"] | 
					
						
							|  |  |  |                 if (redirect) { | 
					
						
							|  |  |  |                     url = redirect | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if ((<string>logos.wikidata).toLowerCase().endsWith(".svg")) { | 
					
						
							|  |  |  |                     if (!path.endsWith(".svg")) { | 
					
						
							|  |  |  |                         throw "Undetected svg path:" + logos.wikidata | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     writeFileSync(path, dloaded["content"], "utf8") | 
					
						
							|  |  |  |                     return true | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 await ScriptUtils.DownloadFileTo(url, path) | 
					
						
							|  |  |  |                 return true | 
					
						
							|  |  |  |             } while (ttl > 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return false | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Returns | 
					
						
							|  |  |  |      * @param type | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     async downloadFor(type: string): Promise<{ downloadCount: number; errored: number }> { | 
					
						
							|  |  |  |         const nsi = await NameSuggestionIndex.getNsiIndex() | 
					
						
							|  |  |  |         const items = nsi.allPossible(type) | 
					
						
							|  |  |  |         const basePath = "./public/assets/data/nsi/logos/" | 
					
						
							|  |  |  |         let downloadCount = 0 | 
					
						
							|  |  |  |         let errored = 0 | 
					
						
							| 
									
										
										
										
											2025-01-23 16:07:56 +01:00
										 |  |  |         let skipped = 0 | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         const alreadyDownloaded = NsiLogos.downloadedFiles() | 
					
						
							|  |  |  |         const stepcount = 50 | 
					
						
							|  |  |  |         for (let i = 0; i < items.length; i += stepcount) { | 
					
						
							|  |  |  |             if (downloadCount > 0 || i % 200 === 0) { | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |                 console.log( | 
					
						
							|  |  |  |                     i + "/" + items.length, | 
					
						
							|  |  |  |                     `downloaded ${downloadCount}; failed ${errored}; skipped ${skipped} for NSI type ${type}` | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const results = await Promise.all( | 
					
						
							|  |  |  |                 Utils.TimesT(stepcount, (j) => j).map(async (j) => { | 
					
						
							|  |  |  |                     return await this.downloadLogo(items[i + j], type, basePath, alreadyDownloaded) | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |             for (let j = 0; j < results.length; j++) { | 
					
						
							|  |  |  |                 let didDownload = results[j] | 
					
						
							|  |  |  |                 if (didDownload === true) { | 
					
						
							|  |  |  |                     downloadCount++ | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-01-23 16:07:56 +01:00
										 |  |  |                 if (didDownload === false) { | 
					
						
							|  |  |  |                     skipped++ | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 if (didDownload !== "error") { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 console.log("Retrying", items[i + j].id, type) | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |                 didDownload = await this.downloadLogo( | 
					
						
							|  |  |  |                     items[i + j], | 
					
						
							|  |  |  |                     type, | 
					
						
							|  |  |  |                     basePath, | 
					
						
							|  |  |  |                     alreadyDownloaded | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 if (didDownload === "error") { | 
					
						
							|  |  |  |                     errored++ | 
					
						
							|  |  |  |                     console.log("Failed again:", items[i + j].id) | 
					
						
							|  |  |  |                 } else if (didDownload) { | 
					
						
							|  |  |  |                     downloadCount++ | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return { | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |             downloadCount, | 
					
						
							|  |  |  |             errored, | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async generateRendering(type: string) { | 
					
						
							|  |  |  |         const nsi = await NameSuggestionIndex.getNsiIndex() | 
					
						
							|  |  |  |         const items = nsi.allPossible(type) | 
					
						
							|  |  |  |         const filterOptions: FilterConfigOptionJson[] = items.map((item) => { | 
					
						
							|  |  |  |             return { | 
					
						
							|  |  |  |                 question: item.displayName, | 
					
						
							| 
									
										
										
										
											2025-01-26 22:05:44 +01:00
										 |  |  |                 icon: nsi.getIconUrl(item), | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 osmTags: NameSuggestionIndex.asFilterTags(item), | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |         const mappings = items | 
					
						
							|  |  |  |             .map((item) => ({ | 
					
						
							|  |  |  |                 if: NameSuggestionIndex.asFilterTags(item), | 
					
						
							|  |  |  |                 then: nsi.getIconUrl(item), | 
					
						
							|  |  |  |             })) | 
					
						
							|  |  |  |             .filter((mapping) => mapping.then !== undefined) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         console.log("Checking for shadow-mappings... This will take a while") | 
					
						
							| 
									
										
										
										
											2025-01-26 22:05:44 +01:00
										 |  |  |         let deleted = 0 | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         for (let i = mappings.length - 1; i >= 0; i--) { | 
					
						
							|  |  |  |             const condition = TagUtils.Tag(mappings[i].if) | 
					
						
							|  |  |  |             if (i % 100 === 0) { | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |                 console.log( | 
					
						
							|  |  |  |                     "Checking for shadow-mappings...", | 
					
						
							|  |  |  |                     i, | 
					
						
							|  |  |  |                     "/", | 
					
						
							|  |  |  |                     mappings.length, | 
					
						
							|  |  |  |                     "deleted", | 
					
						
							|  |  |  |                     deleted | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             const shadowsSomething = mappings.some((m, j) => { | 
					
						
							|  |  |  |                 if (i === j) { | 
					
						
							|  |  |  |                     return false | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return condition.shadows(TagUtils.Tag(m.if)) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             // If this one matches, the other one will match as well
 | 
					
						
							|  |  |  |             // We can thus remove this one in favour of the other one
 | 
					
						
							|  |  |  |             if (shadowsSomething) { | 
					
						
							| 
									
										
										
										
											2025-01-26 22:05:44 +01:00
										 |  |  |                 deleted++ | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 mappings.splice(i, 1) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-23 14:30:19 +01:00
										 |  |  |         const iconsTr: QuestionableTagRenderingConfigJson = <any>{ | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             strict: true, | 
					
						
							|  |  |  |             id: "icon", | 
					
						
							|  |  |  |             mappings, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const config: LayerConfigJson = { | 
					
						
							|  |  |  |             id: "nsi_" + type, | 
					
						
							|  |  |  |             description: { | 
					
						
							|  |  |  |                 en: "Exposes part of the NSI to reuse in other themes, e.g. for rendering", | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             source: "special:library", | 
					
						
							|  |  |  |             pointRendering: null, | 
					
						
							|  |  |  |             tagRenderings: [iconsTr], | 
					
						
							|  |  |  |             filter: [ | 
					
						
							|  |  |  |                 <any>{ | 
					
						
							|  |  |  |                     "#": "ignore-possible-duplicate", | 
					
						
							|  |  |  |                     id: type, | 
					
						
							|  |  |  |                     strict: true, | 
					
						
							|  |  |  |                     options: [{ question: type }, ...filterOptions], | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             allowMove: false, | 
					
						
							|  |  |  |             "#dont-translate": "*", | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const path = "./assets/layers/nsi_" + type | 
					
						
							|  |  |  |         mkdirSync(path, { recursive: true }) | 
					
						
							|  |  |  |         writeFileSync(path + "/nsi_" + type + ".json", JSON.stringify(config, null, "  ")) | 
					
						
							|  |  |  |         console.log("Written", path) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async download() { | 
					
						
							|  |  |  |         const types = ["brand", "operator"] | 
					
						
							|  |  |  |         let dload = 0 | 
					
						
							|  |  |  |         let failed = 0 | 
					
						
							|  |  |  |         for (const type of types) { | 
					
						
							|  |  |  |             const { downloadCount, errored } = await this.downloadFor(type) | 
					
						
							|  |  |  |             dload += downloadCount | 
					
						
							|  |  |  |             failed += errored | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         console.log(`Downloading completed: downloaded ${dload}; failed: ${failed}`) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async generateRenderings() { | 
					
						
							|  |  |  |         const types = ["brand", "operator"] | 
					
						
							|  |  |  |         for (const type of types) { | 
					
						
							|  |  |  |             await this.generateRendering(type) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static readonly path: string = "./public/assets/data/nsi/logos" | 
					
						
							|  |  |  |     private static headers: Readonly<Record<string, ReadonlyArray<ReadonlyArray<number>>>> = { | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |         png: [[137, 80, 78, 71, 13, 10, 26, 10]], | 
					
						
							|  |  |  |         jpg: [ | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |             [255, 216], // FF D8
 | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |             [255, 232], | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |         gif: [[71, 73]], | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static downloadedFiles(): Map<string, string> { | 
					
						
							|  |  |  |         const allFiles = ScriptUtils.readDirRecSync(NsiLogos.path, 1) | 
					
						
							|  |  |  |         const ids = new Map<string, string>() | 
					
						
							|  |  |  |         for (const f of allFiles) { | 
					
						
							| 
									
										
										
										
											2025-01-26 22:05:44 +01:00
										 |  |  |             const match = f.match("^.*/([a-zA-Z0-9-]+)(.[a-z]{3})?") | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             const id = match[1] | 
					
						
							|  |  |  |             ids.set(id, f) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return ids | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Delete all files not mentioned in the current NSI file | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static async prune() { | 
					
						
							|  |  |  |         const nsi = await NameSuggestionIndex.getNsiIndex() | 
					
						
							|  |  |  |         const types = nsi.supportedTypes() | 
					
						
							|  |  |  |         const ids = new Set<string>() | 
					
						
							|  |  |  |         for (const t of types) { | 
					
						
							|  |  |  |             const items = nsi.allPossible(t) | 
					
						
							|  |  |  |             for (const item of items) { | 
					
						
							|  |  |  |                 ids.add(item.id) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const allFiles = ScriptUtils.readDirRecSync(NsiLogos.path, 1) | 
					
						
							|  |  |  |         let pruned = 0 | 
					
						
							|  |  |  |         for (const f of allFiles) { | 
					
						
							| 
									
										
										
										
											2025-01-26 22:05:44 +01:00
										 |  |  |             const match = f.match("^.*/([a-zA-Z0-9-]+)(.[a-z]{3})?") | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             const id = match[1] | 
					
						
							|  |  |  |             if (!ids.has(id)) { | 
					
						
							|  |  |  |                 console.log("Obsolete file:", f, id) | 
					
						
							| 
									
										
										
										
											2025-01-23 16:07:56 +01:00
										 |  |  |                 unlinkSync(f) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 pruned++ | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         console.log("Removed ", pruned, "files") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private startsWith(buffer: Buffer, header: ReadonlyArray<number>): boolean { | 
					
						
							|  |  |  |         let doesMatch = true | 
					
						
							|  |  |  |         for (let i = 0; i < header.length; i++) { | 
					
						
							|  |  |  |             doesMatch &&= buffer[i] === header[i] | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return doesMatch | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |     private startsWithAnyOf( | 
					
						
							|  |  |  |         buffer: Buffer, | 
					
						
							|  |  |  |         headers: ReadonlyArray<ReadonlyArray<number>> | 
					
						
							|  |  |  |     ): boolean { | 
					
						
							|  |  |  |         return headers.some((header) => this.startsWith(buffer, header)) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async addExtensions() { | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |         console.log("Adding all extensions in ", NsiLogos.path) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         const allFiles = ScriptUtils.readDirRecSync(NsiLogos.path, 1) | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |         let changed = 0 | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         for (const f of allFiles) { | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |             if (f.match(/[a-zA-Z0-9-]\.[a-z]{3}$/)) { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const fd = openSync(f, "r") | 
					
						
							|  |  |  |             const buffer = Buffer.alloc(10) | 
					
						
							|  |  |  |             const num = readSync(fd, buffer, 0, 10, null) | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |             if (num === 0) throw "Invalid file:" + f | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             let matchFound = false | 
					
						
							|  |  |  |             for (const format in NsiLogos.headers) { | 
					
						
							|  |  |  |                 const matches = this.startsWithAnyOf(buffer, NsiLogos.headers[format]) | 
					
						
							|  |  |  |                 if (matches) { | 
					
						
							|  |  |  |                     renameSync(f, f + "." + format) | 
					
						
							|  |  |  |                     matchFound = true | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |                     changed++ | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                     break | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (matchFound) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const text = readFileSync(f, "utf8") | 
					
						
							|  |  |  |             if (text.startsWith("<!DOCTYPE html>")) { | 
					
						
							|  |  |  |                 console.error("Got invalid file - is a HTML file:", f) | 
					
						
							|  |  |  |                 unlinkSync(f) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |             throw ( | 
					
						
							|  |  |  |                 "No format found for " + | 
					
						
							|  |  |  |                 f + | 
					
						
							|  |  |  |                 buffer.slice(0, 10).join(" ") + | 
					
						
							|  |  |  |                 " ascii: " + | 
					
						
							|  |  |  |                 text.slice(0, 40) | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |         console.log("Added", changed, "extensions") | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |     private async patchNsiFile() { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         const files = NsiLogos.downloadedFiles() | 
					
						
							| 
									
										
										
										
											2025-01-28 15:16:27 +01:00
										 |  |  |         let path = "./public/assets/data/nsi/nsi.min.json" | 
					
						
							|  |  |  |         const otherPath = "./assets/data/nsi/nsi.min.json" | 
					
						
							|  |  |  |         if (existsSync(otherPath) && !existsSync(path)) { | 
					
						
							|  |  |  |             path = otherPath | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         const nsi = JSON.parse(readFileSync(path, "utf8")) | 
					
						
							|  |  |  |         const types = nsi.nsi | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const k in types) { | 
					
						
							|  |  |  |             const t: NSIItem[] = types[k].items | 
					
						
							|  |  |  |             for (const nsiItem of t) { | 
					
						
							|  |  |  |                 const file = files.get(nsiItem.id) | 
					
						
							| 
									
										
										
										
											2025-01-28 15:16:27 +01:00
										 |  |  |                 delete nsiItem.fromTemplate | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |                 if (!file) { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-02-24 17:32:40 +01:00
										 |  |  |                 const extension = file.match(/.*\.([a-z]{3})/)?.[1] | 
					
						
							|  |  |  |                 if (!extension) { | 
					
						
							|  |  |  |                     console.error("No extension found for file", file) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |                 nsiItem["ext"] = extension | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         writeFileSync(path, JSON.stringify(nsi), "utf8") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |     private commands: Record<string, { f: () => Promise<void>; doc?: string }> = { | 
					
						
							|  |  |  |         download: { f: () => this.download(), doc: "Download all icons" }, | 
					
						
							|  |  |  |         generateRenderings: { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             f: () => this.generateRenderings(), | 
					
						
							|  |  |  |             doc: "Generates the layer files 'nsi_brand' and 'nsi_operator' which allows to reuse the icons in renderings", | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |         prune: { f: () => NsiLogos.prune(), doc: "Remove no longer needed files" }, | 
					
						
							|  |  |  |         addExtensions: { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             f: () => this.addExtensions(), | 
					
						
							|  |  |  |             doc: "Inspects all files without an extension; might remove invalid files", | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |         patch: { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             f: () => this.patchNsiFile(), | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |             doc: "Reads nsi.min.json, adds the 'ext' (extension) field to every relevant entry", | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2025-01-28 15:42:34 +01:00
										 |  |  |         all: { | 
					
						
							| 
									
										
										
										
											2025-01-23 02:40:35 +01:00
										 |  |  |             doc: "Run `download`, `generateRenderings`, `prune`  and `addExtensions`", | 
					
						
							|  |  |  |             f: async () => { | 
					
						
							|  |  |  |                 await NsiLogos.prune() | 
					
						
							|  |  |  |                 await this.download() | 
					
						
							|  |  |  |                 await this.generateRenderings() | 
					
						
							|  |  |  |                 await this.addExtensions() | 
					
						
							|  |  |  |                 await this.patchNsiFile() | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     printHelp() { | 
					
						
							|  |  |  |         super.printHelp() | 
					
						
							|  |  |  |         console.log("Supported commands are: ") | 
					
						
							|  |  |  |         for (const command in this.commands) { | 
					
						
							|  |  |  |             console.log(`  ${command}\t: ${this.commands[command].doc ?? ""}`) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async main(args: string[]): Promise<void> { | 
					
						
							|  |  |  |         if (args.length == 0) { | 
					
						
							|  |  |  |             this.printHelp() | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const command of args) { | 
					
						
							|  |  |  |             const c = this.commands[command] | 
					
						
							|  |  |  |             if (!c) { | 
					
						
							|  |  |  |                 console.log("Unrecognized command:", c) | 
					
						
							|  |  |  |                 this.printHelp() | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             console.log("> " + command + " <") | 
					
						
							|  |  |  |             await c.f() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | new NsiLogos().run() |