import { writeFileSync } from "node:fs" import { Openmagicline } from "openmagicline" // --------------------------------------------------------------------------- // Config – set via environment variables or edit directly below // --------------------------------------------------------------------------- const GYM = process.env.MAGICLINE_GYM ?? "dieheldin" const USERNAME = process.env.MAGICLINE_USER ?? "tanja.wild" const PASSWORD = process.env.MAGICLINE_PASS ?? "Luna22111993" if (!GYM || !USERNAME || !PASSWORD) { console.error( "Missing credentials. Set MAGICLINE_GYM, MAGICLINE_USER, and MAGICLINE_PASS.", ) process.exit(1) } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Escape a value for use in a CSV cell. */ function csvCell(value: unknown): string { if (value === null || value === undefined) return "" const str = String(value) if (str.includes(",") || str.includes('"') || str.includes("\n")) { return `"${str.replace(/"/g, '""')}"` } return str } /** Escape a value for use in HTML table cell content. */ function htmlEscape(value: unknown): string { if (value === null || value === undefined) return "" return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) } // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- const mgl = new Openmagicline({ gym: GYM, username: USERNAME, password: PASSWORD }) await mgl.login() console.log("Logged in.") // Fetch the real facility/organization unit ID — searching with facility:0 returns nothing. const facilityId = await mgl.util.getDefaultUnitID() console.log(`Using facility ID: ${facilityId}`) const SEARCH_CAP = 50 const seen = new Set() const customers: Record[] = [] async function sweep(prefix: string): Promise { const results = await mgl.customer.search(prefix, { facility: facilityId, showAllFacilities: false, searchInName: false, searchInCustomerNumber: true, }) if (results.length === SEARCH_CAP) { for (const d of "0123456789") { await sweep(prefix + d) } return } for (const c of results) { if (!seen.has(c.databaseId) && c.customerStatus === 1) { seen.add(c.databaseId) customers.push(c as unknown as Record) } } } for (const d of "0123456789") { const prefix = `1-${d}` process.stdout.write(`Sweeping '${prefix}'… `) const before = customers.length await sweep(prefix) console.log(`+${customers.length - before} (total: ${customers.length})`) } if (customers.length === 0) { console.error("No customers found.") process.exit(1) } // Collect the union of all keys across all customer records const allKeys = Array.from( customers.reduce((keys, c) => { for (const k of Object.keys(c)) keys.add(k) return keys }, new Set()), ) // --------------------------------------------------------------------------- // CSV export // --------------------------------------------------------------------------- const csvLines = [ allKeys.map(csvCell).join(","), ...customers.map((c) => allKeys.map((k) => csvCell(c[k])).join(",")), ] const csvPath = "customers.csv" writeFileSync(csvPath, csvLines.join("\n"), "utf-8") console.log(`CSV written to ${csvPath} (${customers.length} rows)`) // --------------------------------------------------------------------------- // HTML export // --------------------------------------------------------------------------- const headerCells = allKeys.map((k) => `${htmlEscape(k)}`).join("") const bodyRows = customers .map( (c) => `${allKeys.map((k) => `${htmlEscape(c[k])}`).join("")}`, ) .join("\n") const html = ` Customers – ${new Date().toLocaleDateString()}

Customer Export

Generated: ${new Date().toISOString()}  |  Total: ${customers.length}

${headerCells} ${bodyRows}
` const htmlPath = "customers.html" writeFileSync(htmlPath, html, "utf-8") console.log(`HTML written to ${htmlPath}`)