const { fetchHTML } = require("./services/http-helpers");
// Feature 24: bgp.he.net Integration.
// NOTE: currently dead code in practice -- /api/lookup's only call site
// (`timedFetch("bgp.he.net", ...)`) is hardcoded to `Promise.resolve(null)`
// rather than actually invoking this. Moved as-is (not removed) since that's
// a "disabled pending re-enable" state, not a provably-unreachable one --
// removing a whole scraping feature is a bigger decision than this
// behavior-preserving extraction pass should make unilaterally.
async function fetchBgpHeNet(asn) {
try {
const html = await fetchHTML("https://bgp.he.net/AS" + asn);
if (!html) return null;
const result = {};
const titleMatch = html.match(/
([^<]+)<\/title>/i);
if (titleMatch) result.title = titleMatch[1].trim();
const peerMatch = html.match(/BGP\s+Peers\s+Observed\s*\(all\)\s*:\s*(\d[\d,]*)/i) || html.match(/Observed\s+Peers[^<]*<[^>]*>\s*(\d+)/i);
if (peerMatch) result.peer_count = parseInt(peerMatch[1].replace(/,/g, ''));
const countryMatch = html.match(/Country[^<]*<[^>]*>[^<]*<[^>]*>\s*<[^>]*>([^<]+)/i);
if (countryMatch) result.country = countryMatch[1].trim();
// Extract 2-letter country code from href="/country/XX"
const ccMatch = html.match(/href="\/country\/([A-Z]{2})"/i);
if (ccMatch) result.country_code = ccMatch[1].toUpperCase();
// Extract clean AS name from title: "AS12345 Some Name - bgp.he.net" → "Some Name"
if (titleMatch) {
const rawTitle = titleMatch[1].trim();
const nameFromTitle = rawTitle.replace(/^AS\d+\s+/i, '').replace(/\s+-\s+bgp\.he\.net.*$/i, '').trim();
if (nameFromTitle && !nameFromTitle.toLowerCase().includes('bgp.he.net')) {
result.name_from_title = nameFromTitle;
}
}
const lgMatch = html.match(/Looking\s+Glass[^<]*<[^>]*href="([^"]+)"/i);
if (lgMatch) result.looking_glass = lgMatch[1];
const descMatch = html.match(/AS\s+Name[^<]*<[^>]*>[^<]*<[^>]*>([^<]+)/i);
if (descMatch) result.description = descMatch[1].trim();
const irrMatch = html.match(/IRR\s+Record[^<]*<[^>]*>[^<]*<[^>]*>([^<]+)/i);
if (irrMatch) result.irr_record = irrMatch[1].trim();
// bgp.he.net format: "Prefixes Originated (v4): 147
" or "Prefixes v4 ... 147"
const v4Match = html.match(/Prefixes\s+Originated\s*\(v4\)\s*:\s*(\d[\d,]*)/i) || html.match(/Prefixes\s+v4[^<]*<[^>]*>\s*(\d+)/i);
if (v4Match) result.prefixes_v4 = parseInt(v4Match[1].replace(/,/g, ''));
const v6Match = html.match(/Prefixes\s+Originated\s*\(v6\)\s*:\s*(\d[\d,]*)/i) || html.match(/Prefixes\s+v6[^<]*<[^>]*>\s*(\d+)/i);
if (v6Match) result.prefixes_v6 = parseInt(v6Match[1].replace(/,/g, ''));
const allMatch = html.match(/Prefixes\s+Originated\s*\(all\)\s*:\s*(\d[\d,]*)/i);
if (allMatch) result.prefixes_all = parseInt(allMatch[1].replace(/,/g, ''));
result.source_url = "https://bgp.he.net/AS" + asn;
return result;
} catch (_e) {
return null;
}
}
module.exports = { fetchBgpHeNet };
|