| 
          export default { | 
        
        
           | 
            async fetch(request, env, ctx) { | 
        
        
           | 
              try { | 
        
        
           | 
                const srvName = "_factorio._udp.example.com"; // CHANGE ME: Requires a DNS SRV Record, See https://wiki.factorio.com/Multiplayer#DNS_SRV_Records. | 
        
        
           | 
                const dohBase = "https://1.1.1.1/dns-query"; | 
        
        
           | 
          
 | 
        
        
           | 
                // --- helpers --- | 
        
        
           | 
                const doh = async (name, type) => { | 
        
        
           | 
                  const url = `${dohBase}?name=${encodeURIComponent(name)}&type=${encodeURIComponent(type)}`; | 
        
        
           | 
                  const res = await fetch(url, { | 
        
        
           | 
                    headers: { "accept": "application/dns-json" }, | 
        
        
           | 
                    // Keep DoH results hot for a short time on the edge | 
        
        
           | 
                    cf: { cacheTtl: 20, cacheEverything: true } | 
        
        
           | 
                  }); | 
        
        
           | 
                  if (!res.ok) throw new Error(`DoH ${type} query failed (${res.status})`); | 
        
        
           | 
                  return res.json(); | 
        
        
           | 
                }; | 
        
        
           | 
          
 | 
        
        
           | 
                const pickSrv = (answers) => { | 
        
        
           | 
                  // SRV RDATA: "priority weight port target" | 
        
        
           | 
                  // Prefer lowest priority; if tie, highest weight. | 
        
        
           | 
                  const parsed = answers | 
        
        
           | 
                    .filter(a => a.type === 33 && typeof a.data === "string") | 
        
        
           | 
                    .map(a => { | 
        
        
           | 
                      const [priStr, wtStr, portStr, ...targetParts] = a.data.trim().split(/\s+/); | 
        
        
           | 
                      const target = targetParts.join(" ").replace(/\.+$/, ""); // strip trailing dot | 
        
        
           | 
                      return { | 
        
        
           | 
                        priority: parseInt(priStr, 10), | 
        
        
           | 
                        weight: parseInt(wtStr, 10), | 
        
        
           | 
                        port: parseInt(portStr, 10), | 
        
        
           | 
                        target | 
        
        
           | 
                      }; | 
        
        
           | 
                    }); | 
        
        
           | 
          
 | 
        
        
           | 
                  if (parsed.length === 0) throw new Error("No SRV answers found"); | 
        
        
           | 
                  parsed.sort((a, b) => a.priority - b.priority || b.weight - a.weight); | 
        
        
           | 
                  return parsed[0]; | 
        
        
           | 
                }; | 
        
        
           | 
          
 | 
        
        
           | 
                const firstA = (answers) => { | 
        
        
           | 
                  const a = answers.find(a => a.type === 1 && a.data); | 
        
        
           | 
                  return a?.data ?? null; | 
        
        
           | 
                }; | 
        
        
           | 
          
 | 
        
        
           | 
                const firstAAAA = (answers) => { | 
        
        
           | 
                  const aaaa = answers.find(a => a.type === 28 && aaaa?.data); // will fail; fix below | 
        
        
           | 
                  return aaaa?.data ?? null; | 
        
        
           | 
                }; | 
        
        
           | 
          
 | 
        
        
           | 
                // --- 1) SRV lookup --- | 
        
        
           | 
                const srvResp = await doh(srvName, "SRV"); | 
        
        
           | 
                if (!srvResp.Answer || !Array.isArray(srvResp.Answer)) { | 
        
        
           | 
                  throw new Error("SRV lookup returned no Answer"); | 
        
        
           | 
                } | 
        
        
           | 
                const srv = pickSrv(srvResp.Answer); | 
        
        
           | 
          
 | 
        
        
           | 
                // --- 2) Resolve target to IP (A first, then AAAA) --- | 
        
        
           | 
                const aResp = await doh(srv.target, "A"); | 
        
        
           | 
                let ip = (aResp.Answer && Array.isArray(aResp.Answer)) ? firstA(aResp.Answer) : null; | 
        
        
           | 
          
 | 
        
        
           | 
                if (!ip) { | 
        
        
           | 
                  const aaaaResp = await doh(srv.target, "AAAA"); | 
        
        
           | 
                  const answers = (aaaaResp.Answer && Array.isArray(aaaaResp.Answer)) ? aaaaResp.Answer : []; | 
        
        
           | 
                  const aaaaRec = answers.find(x => x.type === 28 && x.data); | 
        
        
           | 
                  ip = aaaaRec?.data ?? null; | 
        
        
           | 
                } | 
        
        
           | 
                if (!ip) throw new Error(`Could not resolve IP for ${srv.target}`); | 
        
        
           | 
          
 | 
        
        
           | 
                // Wrap IPv6 literal for host:port | 
        
        
           | 
                const hostForUrl = ip.includes(":") ? `[${ip}]` : ip; | 
        
        
           | 
          
 | 
        
        
           | 
                // --- 3) Query Factorio get-game-details --- | 
        
        
           | 
                const detailsUrl = `https://multiplayer.factorio.com/get-game-details/${hostForUrl}:${srv.port}`; | 
        
        
           | 
                const detailsRes = await fetch(detailsUrl, { | 
        
        
           | 
                  // Factorio endpoint is public; set a small cache | 
        
        
           | 
                  cf: { cacheTtl: 10, cacheEverything: true } | 
        
        
           | 
                }); | 
        
        
           | 
          
 | 
        
        
           | 
                // Pass through status & body; add friendly CORS | 
        
        
           | 
                const body = await detailsRes.text(); | 
        
        
           | 
                const headers = new Headers(detailsRes.headers); | 
        
        
           | 
                headers.set("access-control-allow-origin", "*"); | 
        
        
           | 
                headers.set("access-control-allow-methods", "GET, OPTIONS"); | 
        
        
           | 
                headers.set("access-control-allow-headers", "Content-Type"); | 
        
        
           | 
                headers.set("cache-control", "public, max-age=10"); | 
        
        
           | 
          
 | 
        
        
           | 
                return new Response(body, { status: detailsRes.status, headers }); | 
        
        
           | 
              } catch (err) { | 
        
        
           | 
                return new Response( | 
        
        
           | 
                  JSON.stringify({ error: String(err?.message ?? err) }, null, 2), | 
        
        
           | 
                  { | 
        
        
           | 
                    status: 502, | 
        
        
           | 
                    headers: { | 
        
        
           | 
                      "content-type": "application/json", | 
        
        
           | 
                      "access-control-allow-origin": "*" | 
        
        
           | 
                    } | 
        
        
           | 
                  } | 
        
        
           | 
                ); | 
        
        
           | 
              } | 
        
        
           | 
            } | 
        
        
           | 
          }; |