import { soapRequest, type DigestAuthOptions } from "./digestAuth.js"; const SERVICE_TYPE = "urn:dslforum-org:service:Hosts:1"; function actionEnvelope(action: string, params: Record = {}): string { const args = Object.entries(params) .map(([key, value]) => `<${key}>${value}`) .join(""); return ( `` + `` + `${args}` + `` ); } function extractTag(xml: string, tag: string): string | null { const match = xml.match(new RegExp(`<${tag}>([^<]*)`, "i")); return match ? match[1] : null; } export interface FritzBoxHost { ip: string; mac: string | null; hostname: string; online: boolean; } /** * Liest die vollständige Geräteliste der FritzBox über die TR-064-Aktionen * GetHostNumberOfEntries + GetGenericHostEntry (urn:dslforum-org:service:Hosts:1). */ export async function fetchFritzBoxHosts(options: DigestAuthOptions): Promise { const countXml = await soapRequest( options, `${SERVICE_TYPE}#GetHostNumberOfEntries`, actionEnvelope("GetHostNumberOfEntries") ); const countStr = extractTag(countXml, "NewHostNumberOfEntries"); const count = countStr ? parseInt(countStr, 10) : 0; const hosts: FritzBoxHost[] = []; for (let index = 0; index < count; index++) { const entryXml = await soapRequest( options, `${SERVICE_TYPE}#GetGenericHostEntry`, actionEnvelope("GetGenericHostEntry", { NewIndex: index }) ); const ip = extractTag(entryXml, "NewIPAddress"); const hostname = extractTag(entryXml, "NewHostName"); if (!ip || !hostname) continue; // inaktive/unvollständige Einträge überspringen hosts.push({ ip, mac: extractTag(entryXml, "NewMACAddress"), hostname, online: extractTag(entryXml, "NewActive") === "1", }); } return hosts; }