|
| 1 | +import tle from "tle"; |
| 2 | + |
| 3 | +import kv from "./kv"; |
| 4 | +import log from "./logger"; |
| 5 | +import config from "./config"; |
| 6 | +import fetchTle from "./tleFetcher"; |
| 7 | +import { version } from "../../package.json"; |
| 8 | + |
| 9 | +async function getObjectsTle(noradId: number) { |
| 10 | + let tleData = (await kv.get(`tle_${noradId}`)) as string | null; |
| 11 | + let timestamp = await kv.get(`tle_${noradId}_timestamp`); |
| 12 | + const now = Date.now(); |
| 13 | + const isStale = timestamp ? now - timestamp > config.cacheNoradDuration : true; |
| 14 | + |
| 15 | + if (!tleData || isStale) { |
| 16 | + let allTles = (await kv.get("active")) as string | null; |
| 17 | + if (!allTles) { |
| 18 | + await fetchTle("active"); |
| 19 | + } |
| 20 | + allTles = (await kv.get("active")) as string | null; |
| 21 | + |
| 22 | + // If we still don't have the active TLEs then something went wrong with fetching, so we throw an error |
| 23 | + if (!allTles) { |
| 24 | + log.error("Failed to fetch active TLEs from Celestrak"); |
| 25 | + throw new Error("Failed to fetch active TLEs from Celestrak"); |
| 26 | + } |
| 27 | + const lines = allTles.split("\n"); |
| 28 | + const setPromises: Promise<boolean>[] = []; |
| 29 | + for (let i = 0; i < lines.length; i += 3) { |
| 30 | + const idLine = lines[i + 0]; |
| 31 | + const tleLine1 = lines[i + 1]; |
| 32 | + const tleLine2 = lines[i + 2]; |
| 33 | + |
| 34 | + if (idLine && tleLine1 && tleLine2) { |
| 35 | + const parsed = tle.parse(`${idLine}\n${tleLine1}\n${tleLine2}`); |
| 36 | + const tleString = `${idLine}\n${tleLine1}\n${tleLine2}`; |
| 37 | + if (parsed.number === noradId) { |
| 38 | + tleData = tleString; |
| 39 | + } |
| 40 | + setPromises.push(kv.set(`tle_${parsed.number}`, tleString)); |
| 41 | + } |
| 42 | + } |
| 43 | + await Promise.all(setPromises); |
| 44 | + } |
| 45 | + |
| 46 | + // If we're here then active group doesn't contain the requested NORAD ID, try fetching the TLE directly from Celestrak, but be aware of rate limits so we don't get blocked |
| 47 | + if (!tleData) { |
| 48 | + log.verbose(`NORAD ID ${noradId} not found in active group. Attempting to fetch directly from Celestrak...`); |
| 49 | + const url = `https://celestrak.org/NORAD/elements/gp.php?CATNR=${noradId}&FORMAT=tle`; |
| 50 | + try { |
| 51 | + let tries: number = (await kv.get(`celestrakTries`)) || 0; |
| 52 | + const lastTry: number | undefined = await kv.get(`celestrakLastTry`); |
| 53 | + |
| 54 | + // Reset the counters if more than an hour has passed since the last request |
| 55 | + if (lastTry && Date.now() - lastTry >= 60 * 60 * 1000) { |
| 56 | + tries = 0; |
| 57 | + await kv.set(`celestrakTries`, 0); |
| 58 | + await kv.set(`celestrakLastTry`, Date.now()); |
| 59 | + } |
| 60 | + |
| 61 | + // We don't want to spam Celestrak with more than 20 requests per hour |
| 62 | + if (tries >= 20) { |
| 63 | + throw new Error("Rate limit exceeded for Celestrak fetches"); |
| 64 | + } |
| 65 | + |
| 66 | + const response = await fetch(url, { |
| 67 | + // Celestrak doesn't support If-Modified-Since or ETag headers, so we have to implement our own rate limiting and caching mechanism to avoid getting blocked |
| 68 | + headers: { |
| 69 | + "User-Agent": `ReTLEctor/${version} (https://github.com/MrTalon63/ReTLEctor)`, |
| 70 | + }, |
| 71 | + }); |
| 72 | + |
| 73 | + if (!response.ok) { |
| 74 | + log.error(`Failed to fetch TLE from Celestrak: ${response.status} ${response.statusText}`); |
| 75 | + throw new Error(`Failed to fetch TLE from Celestrak`); |
| 76 | + } |
| 77 | + |
| 78 | + tleData = (await response.text()) as string; |
| 79 | + await kv.set(`tle_${noradId}`, tleData); |
| 80 | + await kv.set(`celestrakTries`, tries + 1); |
| 81 | + await kv.set(`celestrakLastTry`, Date.now()); |
| 82 | + log.info(`Successfully fetched TLE for NORAD ID ${noradId} from Celestrak.`); |
| 83 | + } catch (error) { |
| 84 | + log.error(`Error fetching TLE for NORAD ID ${noradId}: ${error}`); |
| 85 | + throw error; |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + return tleData; |
| 90 | +} |
| 91 | + |
| 92 | +export default getObjectsTle; |
0 commit comments