2023-01-17 01:00:43 +01:00
|
|
|
import * as fs from "fs"
|
2023-06-01 14:32:45 +02:00
|
|
|
import { existsSync, lstatSync, readdirSync, readFileSync } from "fs"
|
2023-07-15 18:04:30 +02:00
|
|
|
import { Utils } from "../src/Utils"
|
2024-06-16 16:06:26 +02:00
|
|
|
import { https } from "follow-redirects"
|
2024-10-17 04:06:03 +02:00
|
|
|
import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
|
2023-07-15 18:04:30 +02:00
|
|
|
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
2022-09-08 21:40:48 +02:00
|
|
|
import xml2js from "xml2js"
|
2022-05-26 13:23:25 +02:00
|
|
|
|
2021-04-10 03:18:32 +02:00
|
|
|
export default class ScriptUtils {
|
2021-06-22 14:21:32 +02:00
|
|
|
public static fixUtils() {
|
2022-05-26 13:23:25 +02:00
|
|
|
Utils.externalDownloadFunction = ScriptUtils.Download
|
2021-06-22 14:21:32 +02:00
|
|
|
}
|
|
|
|
|
2023-01-09 20:38:05 +01:00
|
|
|
/**
|
|
|
|
* Returns all files in a directory, recursively reads subdirectories.
|
|
|
|
* The returned paths include the path given and subdirectories.
|
|
|
|
*
|
|
|
|
* @param path
|
|
|
|
* @param maxDepth
|
|
|
|
*/
|
2021-05-20 12:27:33 +02:00
|
|
|
public static readDirRecSync(path, maxDepth = 999): string[] {
|
2023-01-15 23:28:02 +01:00
|
|
|
const result: string[] = []
|
2021-06-22 14:21:32 +02:00
|
|
|
if (maxDepth <= 0) {
|
2021-05-20 12:27:33 +02:00
|
|
|
return []
|
|
|
|
}
|
2023-01-17 01:00:43 +01:00
|
|
|
for (const entry of readdirSync(path)) {
|
|
|
|
const fullEntry = path + "/" + entry
|
|
|
|
const stats = lstatSync(fullEntry)
|
|
|
|
if (stats.isDirectory()) {
|
|
|
|
// Subdirectory
|
|
|
|
// @ts-ignore
|
|
|
|
result.push(...ScriptUtils.readDirRecSync(fullEntry, maxDepth - 1))
|
|
|
|
} else {
|
|
|
|
result.push(fullEntry)
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
return result
|
2021-04-10 03:18:32 +02:00
|
|
|
}
|
2021-05-14 17:37:21 +02:00
|
|
|
|
2023-05-18 13:07:14 +02:00
|
|
|
public static DownloadFileTo(url, targetFilePath: string): Promise<void> {
|
|
|
|
ScriptUtils.erasableLog("Downloading", url, "to", targetFilePath)
|
2024-01-24 23:45:20 +01:00
|
|
|
return new Promise<void>((resolve) => {
|
2023-05-18 13:07:14 +02:00
|
|
|
https.get(url, (res) => {
|
|
|
|
const filePath = fs.createWriteStream(targetFilePath)
|
|
|
|
res.pipe(filePath)
|
|
|
|
filePath.on("finish", () => {
|
|
|
|
filePath.close()
|
|
|
|
resolve()
|
|
|
|
})
|
2021-06-22 14:21:32 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2021-09-04 18:59:51 +02:00
|
|
|
|
2021-07-27 15:06:36 +02:00
|
|
|
public static erasableLog(...text) {
|
2021-09-04 18:59:51 +02:00
|
|
|
process.stdout.write("\r " + text.join(" ") + " \r")
|
2021-07-27 15:06:36 +02:00
|
|
|
}
|
2021-04-22 03:30:46 +02:00
|
|
|
|
2023-01-09 20:38:05 +01:00
|
|
|
public static sleep(ms: number, text?: string) {
|
2021-05-14 17:37:21 +02:00
|
|
|
if (ms <= 0) {
|
2021-05-14 02:25:30 +02:00
|
|
|
process.stdout.write("\r \r")
|
2022-09-08 21:40:48 +02:00
|
|
|
return
|
2021-05-14 02:25:30 +02:00
|
|
|
}
|
2021-04-22 03:30:46 +02:00
|
|
|
return new Promise((resolve) => {
|
2023-01-09 20:38:05 +01:00
|
|
|
process.stdout.write("\r" + (text ?? "") + " Sleeping for " + ms / 1000 + "s \r")
|
2022-09-08 21:40:48 +02:00
|
|
|
setTimeout(resolve, 1000)
|
|
|
|
}).then(() => ScriptUtils.sleep(ms - 1000))
|
2021-04-22 03:30:46 +02:00
|
|
|
}
|
|
|
|
|
2022-07-06 13:58:56 +02:00
|
|
|
public static getLayerPaths(): string[] {
|
|
|
|
return ScriptUtils.readDirRecSync("./assets/layers")
|
2022-09-08 21:40:48 +02:00
|
|
|
.filter((path) => path.indexOf(".json") > 0)
|
|
|
|
.filter((path) => path.indexOf(".proto.json") < 0)
|
|
|
|
.filter((path) => path.indexOf("license_info.json") < 0)
|
2022-07-06 13:58:56 +02:00
|
|
|
}
|
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
public static getLayerFiles(): { parsed: LayerConfigJson; path: string }[] {
|
2021-05-19 23:31:00 +02:00
|
|
|
return ScriptUtils.readDirRecSync("./assets/layers")
|
2022-09-08 21:40:48 +02:00
|
|
|
.filter((path) => path.indexOf(".json") > 0)
|
|
|
|
.filter((path) => path.indexOf(".proto.json") < 0)
|
|
|
|
.filter((path) => path.indexOf("license_info.json") < 0)
|
|
|
|
.map((path) => {
|
2021-05-19 23:31:00 +02:00
|
|
|
try {
|
2023-06-01 14:32:45 +02:00
|
|
|
const contents = readFileSync(path, { encoding: "utf8" })
|
2021-09-04 18:59:51 +02:00
|
|
|
if (contents === "") {
|
|
|
|
throw "The file " + path + " is empty, did you properly save?"
|
2021-07-07 15:19:05 +02:00
|
|
|
}
|
2021-09-04 18:59:51 +02:00
|
|
|
|
2022-09-08 21:40:48 +02:00
|
|
|
const parsed = JSON.parse(contents)
|
2023-06-01 14:32:45 +02:00
|
|
|
return { parsed, path }
|
2021-05-19 23:31:00 +02:00
|
|
|
} catch (e) {
|
2023-12-12 03:43:34 +01:00
|
|
|
console.error("Could not parse file ", path, "due to ", e)
|
2022-07-01 02:41:09 +02:00
|
|
|
throw e
|
2021-05-19 23:31:00 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-15 00:57:38 +02:00
|
|
|
public static getThemePaths(useTranslationPaths = false): string[] {
|
2024-09-15 00:10:01 +02:00
|
|
|
const normalFiles = ScriptUtils.readDirRecSync("./assets/themes")
|
2022-09-08 21:40:48 +02:00
|
|
|
.filter((path) => path.endsWith(".json") && !path.endsWith(".proto.json"))
|
|
|
|
.filter((path) => path.indexOf("license_info.json") < 0)
|
2024-09-15 00:57:38 +02:00
|
|
|
|
|
|
|
if (!useTranslationPaths) {
|
|
|
|
return normalFiles
|
|
|
|
}
|
2024-09-15 00:10:01 +02:00
|
|
|
const specialfiles = ["./assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json"]
|
2024-09-15 00:57:38 +02:00
|
|
|
const blacklist = ["assets/themes/mapcomplete-changes/mapcomplete-changes.json"]
|
|
|
|
|
2024-10-19 14:44:55 +02:00
|
|
|
const filtered = normalFiles.filter(
|
|
|
|
(path) => !blacklist.some((black) => path.endsWith(black))
|
|
|
|
)
|
2024-09-15 00:57:38 +02:00
|
|
|
return filtered.concat(specialfiles)
|
2022-07-06 13:58:56 +02:00
|
|
|
}
|
|
|
|
|
2024-09-15 00:57:38 +02:00
|
|
|
public static getThemeFiles(useTranslationPaths = false): {
|
2024-10-19 14:44:55 +02:00
|
|
|
parsed: ThemeConfigJson
|
|
|
|
path: string
|
2024-09-15 00:57:38 +02:00
|
|
|
raw: string
|
|
|
|
}[] {
|
|
|
|
return this.getThemePaths(useTranslationPaths).map((path) => {
|
2022-09-08 21:40:48 +02:00
|
|
|
try {
|
2023-06-01 14:32:45 +02:00
|
|
|
const contents = readFileSync(path, { encoding: "utf8" })
|
2022-09-08 21:40:48 +02:00
|
|
|
if (contents === "") {
|
|
|
|
throw "The file " + path + " is empty, did you properly save?"
|
2021-05-19 23:31:00 +02:00
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
const parsed = JSON.parse(contents)
|
2023-10-30 13:45:44 +01:00
|
|
|
return { parsed: parsed, path: path, raw: contents }
|
2022-09-08 21:40:48 +02:00
|
|
|
} catch (e) {
|
|
|
|
console.error("Could not read file ", path, "due to ", e)
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
})
|
2021-05-19 23:31:00 +02:00
|
|
|
}
|
|
|
|
|
2021-09-13 01:18:57 +02:00
|
|
|
public static TagInfoHistogram(key: string): Promise<{
|
2022-09-08 21:40:48 +02:00
|
|
|
data: { count: number; value: string; fraction: number }[]
|
2021-09-13 01:18:57 +02:00
|
|
|
}> {
|
|
|
|
const url = `https://taginfo.openstreetmap.org/api/4/key/values?key=${key}&filter=all&lang=en&sortname=count&sortorder=desc&page=1&rp=17&qtype=value`
|
|
|
|
return ScriptUtils.DownloadJSON(url)
|
|
|
|
}
|
|
|
|
|
2024-07-23 17:59:06 +02:00
|
|
|
public static async ReadSvg(path: string): Promise<SVGElement> {
|
2023-01-17 01:00:43 +01:00
|
|
|
if (!existsSync(path)) {
|
|
|
|
throw "File not found: " + path
|
|
|
|
}
|
2023-06-01 14:32:45 +02:00
|
|
|
const root = await xml2js.parseStringPromise(readFileSync(path, { encoding: "utf8" }))
|
2023-01-17 01:00:43 +01:00
|
|
|
return root.svg
|
2022-02-10 23:10:39 +01:00
|
|
|
}
|
|
|
|
|
2022-09-11 01:49:07 +02:00
|
|
|
public static ReadSvgSync(path: string, callback: (svg: any) => void): any {
|
2023-01-17 01:00:43 +01:00
|
|
|
xml2js.parseString(
|
2023-06-01 14:32:45 +02:00
|
|
|
readFileSync(path, { encoding: "utf8" }),
|
|
|
|
{ async: false },
|
2023-01-17 01:00:43 +01:00
|
|
|
(err, root) => {
|
|
|
|
if (err) {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
callback(root["svg"])
|
|
|
|
}
|
|
|
|
)
|
2022-02-10 23:10:39 +01:00
|
|
|
}
|
2022-07-06 13:58:56 +02:00
|
|
|
|
|
|
|
private static async DownloadJSON(url: string, headers?: any): Promise<any> {
|
2022-09-08 21:40:48 +02:00
|
|
|
const data = await ScriptUtils.Download(url, headers)
|
2023-03-21 20:01:11 +01:00
|
|
|
return JSON.parse(data["content"])
|
2022-07-06 13:58:56 +02:00
|
|
|
}
|
2024-09-15 00:57:38 +02:00
|
|
|
|
2024-02-26 02:24:46 +01:00
|
|
|
public static async DownloadFetch(
|
|
|
|
url: string,
|
|
|
|
headers?: any
|
|
|
|
): Promise<{ content: string } | { redirect: string }> {
|
|
|
|
console.log("Fetching", url)
|
2024-04-13 02:40:21 +02:00
|
|
|
const req = await fetch(url, { headers })
|
|
|
|
const data = await req.text()
|
|
|
|
console.log("Fetched", url, data)
|
|
|
|
return { content: data }
|
2024-02-26 02:24:46 +01:00
|
|
|
}
|
2024-09-15 00:57:38 +02:00
|
|
|
|
2023-03-21 20:01:11 +01:00
|
|
|
public static Download(
|
|
|
|
url: string,
|
|
|
|
headers?: any
|
2024-03-01 00:50:00 +01:00
|
|
|
): Promise<{ content: string } | { redirect: string }>
|
|
|
|
public static Download(
|
|
|
|
url: string,
|
|
|
|
headers?: any,
|
|
|
|
timeoutSecs?: number
|
|
|
|
): Promise<{ content: string } | { redirect: string } | "timeout">
|
|
|
|
public static Download(
|
|
|
|
url: string,
|
|
|
|
headers?: any,
|
|
|
|
timeoutSecs?: number
|
|
|
|
): Promise<{ content: string } | { redirect: string } | "timeout"> {
|
2024-08-14 13:53:56 +02:00
|
|
|
if (url.startsWith("./assets")) {
|
|
|
|
return Promise.resolve({ content: readFileSync("./public/" + url, "utf8") })
|
2024-08-14 12:42:17 +02:00
|
|
|
}
|
2024-08-14 13:53:56 +02:00
|
|
|
if (url.startsWith("./")) {
|
|
|
|
return Promise.resolve({ content: readFileSync(url, "utf8") })
|
2024-08-11 20:13:04 +02:00
|
|
|
}
|
|
|
|
|
2024-03-01 00:50:00 +01:00
|
|
|
const requestPromise = new Promise((resolve, reject) => {
|
2022-07-06 13:58:56 +02:00
|
|
|
try {
|
|
|
|
headers = headers ?? {}
|
2024-06-16 16:06:26 +02:00
|
|
|
if (!headers.Accept) {
|
2024-04-24 00:58:22 +02:00
|
|
|
headers.accept ??= "application/json"
|
|
|
|
}
|
2024-05-30 20:06:24 +02:00
|
|
|
ScriptUtils.erasableLog(" > ScriptUtils.Download(", url, ")")
|
2022-07-06 13:58:56 +02:00
|
|
|
const urlObj = new URL(url)
|
2023-03-21 20:01:11 +01:00
|
|
|
const request = https.get(
|
2022-09-08 21:40:48 +02:00
|
|
|
{
|
|
|
|
host: urlObj.host,
|
|
|
|
path: urlObj.pathname + urlObj.search,
|
|
|
|
|
|
|
|
port: urlObj.port,
|
2024-10-19 14:44:55 +02:00
|
|
|
headers: headers,
|
2022-09-08 21:40:48 +02:00
|
|
|
},
|
|
|
|
(res) => {
|
|
|
|
const parts: string[] = []
|
|
|
|
res.setEncoding("utf8")
|
2024-10-19 14:44:55 +02:00
|
|
|
res.on("data", function (chunk) {
|
2022-09-08 21:40:48 +02:00
|
|
|
// @ts-ignore
|
|
|
|
parts.push(chunk)
|
|
|
|
})
|
|
|
|
|
2024-10-19 14:44:55 +02:00
|
|
|
res.addListener("end", function () {
|
2023-03-21 20:01:11 +01:00
|
|
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
|
|
console.log("Got a redirect:", res.headers.location)
|
|
|
|
resolve({ redirect: res.headers.location })
|
|
|
|
}
|
|
|
|
if (res.statusCode >= 400) {
|
|
|
|
console.log(
|
|
|
|
"Error while fetching ",
|
|
|
|
url,
|
|
|
|
"due to",
|
|
|
|
res.statusMessage
|
|
|
|
)
|
|
|
|
reject(res.statusCode)
|
|
|
|
}
|
2023-06-01 14:32:45 +02:00
|
|
|
resolve({ content: parts.join("") })
|
2022-09-08 21:40:48 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
2024-10-19 14:44:55 +02:00
|
|
|
request.on("error", function (e) {
|
2023-03-21 20:01:11 +01:00
|
|
|
reject(e)
|
|
|
|
})
|
2022-07-06 13:58:56 +02:00
|
|
|
} catch (e) {
|
|
|
|
reject(e)
|
|
|
|
}
|
|
|
|
})
|
2024-03-01 00:50:00 +01:00
|
|
|
const timeoutPromise = new Promise<any>((resolve, reject) => {
|
2024-06-16 16:06:26 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
if (timeoutSecs === undefined) {
|
|
|
|
return // No resolve
|
|
|
|
}
|
|
|
|
resolve("timeout")
|
|
|
|
}, (timeoutSecs ?? 10) * 1000)
|
2024-03-01 00:50:00 +01:00
|
|
|
})
|
|
|
|
return Promise.race([requestPromise, timeoutPromise])
|
2022-07-06 13:58:56 +02:00
|
|
|
}
|
2021-04-10 03:18:32 +02:00
|
|
|
}
|