Spaces:
Paused
Paused
| import { z, ZodError } from "zod"; | |
| import { parse } from "@std/toml"; | |
| export const ConfigSchema = z.object({ | |
| server: z.object({ | |
| port: z.number().default(Number(Deno.env.get("PORT")) || 8282), | |
| host: z.string().default(Deno.env.get("HOST") || "127.0.0.1"), | |
| use_unix_socket: z.boolean().default( | |
| Deno.env.get("SERVER_USE_UNIX_SOCKET") === "true" || false, | |
| ), | |
| unix_socket_path: z.string().default( | |
| Deno.env.get("SERVER_UNIX_SOCKET_PATH") || | |
| "/tmp/invidious-companion.sock", | |
| ), | |
| base_path: z.string() | |
| .default(Deno.env.get("SERVER_BASE_PATH") || "/companion") | |
| .refine( | |
| (path) => path.startsWith("/"), | |
| { | |
| message: | |
| "SERVER_BASE_PATH must start with a forward slash (/). Example: '/companion'", | |
| }, | |
| ) | |
| .refine( | |
| (path) => !path.endsWith("/") || path === "/", | |
| { | |
| message: | |
| "SERVER_BASE_PATH must not end with a forward slash (/) unless it's the root path. Example: '/companion' not '/companion/'", | |
| }, | |
| ) | |
| .refine( | |
| (path) => !path.includes("//"), | |
| { | |
| message: | |
| "SERVER_BASE_PATH must not contain double slashes (//). Example: '/companion' not '//companion' or '/comp//anion'", | |
| }, | |
| ), | |
| secret_key: z.preprocess( | |
| (val) => | |
| val === undefined | |
| ? Deno.env.get("SERVER_SECRET_KEY") || "" | |
| : val, | |
| z.string().min(1), | |
| ).default(undefined), | |
| verify_requests: z.boolean().default( | |
| Deno.env.get("SERVER_VERIFY_REQUESTS") === "true" || false, | |
| ), | |
| encrypt_query_params: z.boolean().default( | |
| Deno.env.get("SERVER_ENCRYPT_QUERY_PARAMS") === "true" || false, | |
| ), | |
| enable_metrics: z.boolean().default( | |
| Deno.env.get("SERVER_ENABLE_METRICS") === "true" || false, | |
| ), | |
| }).strict().default({}), | |
| cache: z.object({ | |
| enabled: z.boolean().default( | |
| Deno.env.get("CACHE_ENABLED") === "false" ? false : true, | |
| ), | |
| directory: z.string().default( | |
| Deno.env.get("CACHE_DIRECTORY") || "/var/tmp", | |
| ), | |
| }).strict().default({}), | |
| networking: z.object({ | |
| proxy: z.string().nullable().default(Deno.env.get("PROXY") || null), | |
| auto_proxy: z.boolean().default( | |
| Deno.env.get("NETWORKING_AUTO_PROXY") === "true" || false, | |
| ), | |
| vpn_source: z.number().default( | |
| Number(Deno.env.get("NETWORKING_VPN_SOURCE")) || 1, | |
| ), | |
| ipv6_block: z.string().nullable().default( | |
| Deno.env.get("NETWORKING_IPV6_BLOCK") || null, | |
| ), | |
| fetch: z.object({ | |
| timeout_ms: z.number().default( | |
| Number(Deno.env.get("NETWORKING_FETCH_TIMEOUT_MS")) || 30_000, | |
| ), | |
| retry: z.object({ | |
| enabled: z.boolean().default( | |
| Deno.env.get("NETWORKING_FETCH_RETRY_ENABLED") === "true" || | |
| false, | |
| ), | |
| times: z.number().optional().default( | |
| Number(Deno.env.get("NETWORKING_FETCH_RETRY_TIMES")) || 1, | |
| ), | |
| initial_debounce: z.number().optional().default( | |
| Number( | |
| Deno.env.get("NETWORKING_FETCH_RETRY_INITIAL_DEBOUNCE"), | |
| ) || 0, | |
| ), | |
| debounce_multiplier: z.number().optional().default( | |
| Number( | |
| Deno.env.get( | |
| "NETWORKING_FETCH_RETRY_DEBOUNCE_MULTIPLIER", | |
| ), | |
| ) || 0, | |
| ), | |
| }).strict().default({}), | |
| }).strict().default({}), | |
| videoplayback: z.object({ | |
| ump: z.boolean().default( | |
| Deno.env.get("NETWORKING_VIDEOPLAYBACK_UMP") === "true" || | |
| false, | |
| ), | |
| video_fetch_chunk_size_mb: z.number().default( | |
| Number( | |
| Deno.env.get( | |
| "NETWORKING_VIDEOPLAYBACK_VIDEO_FETCH_CHUNK_SIZE_MB", | |
| ), | |
| ) || 5, | |
| ), | |
| }).strict().default({}), | |
| }).strict().default({}), | |
| jobs: z.object({ | |
| youtube_session: z.object({ | |
| po_token_enabled: z.boolean().default( | |
| Deno.env.get("JOBS_YOUTUBE_SESSION_PO_TOKEN_ENABLED") === | |
| "false" | |
| ? false | |
| : true, | |
| ), | |
| frequency: z.string().default( | |
| Deno.env.get("JOBS_YOUTUBE_SESSION_FREQUENCY") || "*/5 * * * *", | |
| ), | |
| }).strict().default({}), | |
| }).strict().default({}), | |
| youtube_session: z.object({ | |
| oauth_enabled: z.boolean().default( | |
| Deno.env.get("YOUTUBE_SESSION_OAUTH_ENABLED") === "true" || false, | |
| ), | |
| cookies: z.string().default( | |
| Deno.env.get("YOUTUBE_SESSION_COOKIES") || "", | |
| ), | |
| }).strict().default({}), | |
| }).strict(); | |
| export type Config = z.infer<typeof ConfigSchema>; | |
| export async function parseConfig() { | |
| const configFileName = Deno.env.get("CONFIG_FILE") || "config/config.toml"; | |
| const configFileContents = await Deno.readTextFile(configFileName).catch( | |
| () => null, | |
| ); | |
| if (configFileContents) { | |
| console.log("[INFO] Using custom settings local file"); | |
| } else { | |
| console.log( | |
| "[INFO] No local config file found, using default config", | |
| ); | |
| } | |
| try { | |
| const rawConfig = configFileContents ? parse(configFileContents) : {}; | |
| const validatedConfig = ConfigSchema.parse(rawConfig); | |
| console.log("Loaded Configuration", validatedConfig); | |
| return validatedConfig; | |
| } catch (err) { | |
| let errorMessage = | |
| "There is an error in your configuration, check your environment variables"; | |
| if (configFileContents) { | |
| errorMessage += | |
| ` or in your configuration file located at ${configFileName}`; | |
| } | |
| console.log(errorMessage); | |
| if (err instanceof ZodError) { | |
| console.log(err.issues); | |
| // Include detailed error information in the thrown error for testing | |
| const detailedMessage = err.issues.map((issue) => | |
| `${issue.path.join(".")}: ${issue.message}` | |
| ).join("; "); | |
| throw new Error( | |
| `Failed to parse configuration file: ${detailedMessage}`, | |
| ); | |
| } | |
| // rethrow error if not Zod | |
| throw err; | |
| } | |
| } | |