Created
March 17, 2026 14:08
-
-
Save cenan/c7323fe82a2c7a5788af8822896d37ca to your computer and use it in GitHub Desktop.
server list builder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { readFile, writeFile } from "node:fs/promises"; | |
| const INPUT = "servers.json"; | |
| const OUTPUT = "params.txt"; | |
| function extractRelays(data) { | |
| for (const item of data) { | |
| if ( | |
| item && | |
| item.type === "data" && | |
| item.data && | |
| Array.isArray(item.data.relays) | |
| ) { | |
| return item.data.relays; | |
| } | |
| } | |
| throw new Error("Could not find relays array in servers.json"); | |
| } | |
| async function main() { | |
| const raw = await readFile(INPUT, "utf8"); | |
| const json = JSON.parse(raw); | |
| const relays = extractRelays(json); | |
| const params = relays | |
| .filter(r => r.active) | |
| .map(r => `-r socks5://${r.socks_name}:${r.socks_port}`) | |
| .join(" "); | |
| await writeFile(OUTPUT, params + "\n", "utf8"); | |
| console.log(`Wrote ${OUTPUT} with ${relays.length} relays (${params.split(" -r ").length - 1} active)`); | |
| } | |
| main().catch(err => { | |
| console.error(err); | |
| process.exit(1); | |
| }); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { writeFile } from "node:fs/promises"; | |
| const URL = "https://mullvad.net/en/servers?type=wireguard&status=true"; | |
| const OUTPUT_FILE = "servers.json"; | |
| function extractDataArray(source) { | |
| const marker = "data:"; | |
| const markerIndex = source.indexOf(marker); | |
| if (markerIndex === -1) { | |
| throw new Error(`Could not find "${marker}" in response`); | |
| } | |
| const arrayStart = source.indexOf("[", markerIndex); | |
| if (arrayStart === -1) { | |
| throw new Error("Could not find array start after data:"); | |
| } | |
| let depth = 0; | |
| let inString = false; | |
| let stringQuote = ""; | |
| let escaped = false; | |
| for (let i = arrayStart; i < source.length; i++) { | |
| const ch = source[i]; | |
| if (inString) { | |
| if (escaped) { | |
| escaped = false; | |
| continue; | |
| } | |
| if (ch === "\\") { | |
| escaped = true; | |
| continue; | |
| } | |
| if (ch === stringQuote) { | |
| inString = false; | |
| stringQuote = ""; | |
| } | |
| continue; | |
| } | |
| if (ch === '"' || ch === "'" || ch === "`") { | |
| inString = true; | |
| stringQuote = ch; | |
| continue; | |
| } | |
| if (ch === "[") { | |
| depth++; | |
| } else if (ch === "]") { | |
| depth--; | |
| if (depth === 0) { | |
| return source.slice(arrayStart, i + 1); | |
| } | |
| } | |
| } | |
| throw new Error("Could not find matching closing bracket for data array"); | |
| } | |
| async function main() { | |
| const res = await fetch(URL, { | |
| headers: { | |
| "user-agent": "bun fetch script", | |
| }, | |
| }); | |
| if (!res.ok) { | |
| throw new Error(`Fetch failed: ${res.status} ${res.statusText}`); | |
| } | |
| const html = await res.text(); | |
| if (!html.includes('data: [null,{type:"data"')) { | |
| console.warn('Warning: expected prefix `data: [null,{type:"data"` was not found exactly.'); | |
| } | |
| const arraySource = extractDataArray(html); | |
| const data = Function(`"use strict"; return (${arraySource});`)(); | |
| await writeFile(OUTPUT_FILE, JSON.stringify(data, null, 2), "utf8"); | |
| console.log(`Saved ${OUTPUT_FILE}`); | |
| } | |
| main().catch((err) => { | |
| console.error(err); | |
| process.exit(1); | |
| }); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment