From eaef6e80669fc0c1be3ce26c8633e68da012d89d Mon Sep 17 00:00:00 2001 From: ztimson Date: Mon, 14 Sep 2026 00:48:24 -0400 Subject: [PATCH] Image lookup fix? --- server/src/adsb.mjs | 91 +++++++++++++++++++++++++++++-------------- server/src/aurora.mjs | 9 ++--- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/server/src/adsb.mjs b/server/src/adsb.mjs index 2cbcb50..7899c7a 100644 --- a/server/src/adsb.mjs +++ b/server/src/adsb.mjs @@ -203,7 +203,6 @@ async function syncAirlines() { const text = fs.readFileSync(AIRLINES_CACHE, 'utf8'); const airlines = text.split('\n').slice(1).map(row => row.split(',')[1]?.slice(1, -1).toLowerCase()).filter(Boolean).flat(); - console.log(airlines); passengerOperators = airlines.sort((a, b) => b.length - a.length); console.log(`✈️ Airlines loaded: ${airlines.length}`); } catch (e) { @@ -363,44 +362,63 @@ export async function getHistory(icao) { } export async function getIcaoImage(icao) { - function randomUA() { - const v = 36 + Math.floor(Math.random() * 40); - const builds = [ - `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.${v} (KHTML, like Gecko) Chrome/1${10 + ~~(Math.random() * 16)}.0.0.0 Safari/537.${v}`, - `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.${v} (KHTML, like Gecko) Chrome/1${10 + ~~(Math.random() * 16)}.0.0.0 Safari/537.${v}`, - `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.${v} (KHTML, like Gecko) Chrome/1${10 + ~~(Math.random() * 16)}.0.0.0 Safari/537.${v}` - ]; - return builds[~~(Math.random() * builds.length)]; - } + const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36'; async function duckduckgo(query) { async function getVqd(query) { - const res = await fetch(`https://duckduckgo.com/?q=${encodeURIComponent(query)}`, { - headers: { 'User-Agent': randomUA() } + const url = `https://duckduckgo.com/?q=${encodeURIComponent(query)}&iax=images&ia=images`; + const res = await fetch(url, { + headers: { + 'User-Agent': UA, + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.9' + } }); - - if(!res.ok) console.warn(res.status, res.statusText); + if(!res.ok) return console.warn(`DuckDuckGo Search failed: ${res.status}`); const html = await res.text(); - const match = html.match(/vqd=['"]?([\d-]+)['"]?/); - return match?.[1]; + const match = html.match(/vqd=["']([^"']+)["']/); + return match?.[1] || null; } const vqd = await getVqd(query); if (!vqd) throw new Error('Could not get vqd token'); - const url = `https://duckduckgo.com/i.js?q=${encodeURIComponent(query)}&o=json&vqd=${vqd}&f=,,,,,&p=1`; - const data = await fetch(url, { - headers: {'User-Agent': randomUA(), 'Referer': 'https://duckduckgo.com/'} - }).then(resp => resp.json()); - if(!data.ok) console.warn(data.status, data.statusText); + await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 500)); + const searchUrl = `https://duckduckgo.com/i.js?q=${encodeURIComponent(query)}&o=json&vqd=${encodeURIComponent(vqd)}&f=,,,,,&p=1`; + const data = await fetch(searchUrl, { + headers: { + 'User-Agent': UA, + 'Accept': 'application/json, text/javascript, */*; q=0.01', + 'Accept-Language': 'en-US,en;q=0.9', + 'Referer': `https://duckduckgo.com/?q=${encodeURIComponent(query)}&iax=images&ia=images` + } + }).then(async resp => { + if (!resp.ok) return console.warn(`DuckDuckGo image failed: ${resp.status}`); + const text = await resp.text(); + try { + return JSON.parse(text); + } catch { + console.warn(`DuckDuckGo returned invalid JSON: ${text.slice(0, 200)}`); + return null; + } + }); if (data?.results?.length) { for (const result of data.results) { if (!result.image) continue; try { - const imgRes = await fetch(result.image); + const imgRes = await fetch(result.image, { + headers: { + 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', + 'User-Agent': UA, + 'Referer': result.url || 'https://duckduckgo.com/' + } + }); if (imgRes.ok) return imgRes.blob(); - } catch {} + console.warn(`Image fetch failed: ${imgRes.status} ${result.image}`); + } catch (e) { + console.warn(`Image fetch failed: ${result.image} - ${e.message}`); + } } } @@ -409,11 +427,15 @@ export async function getIcaoImage(icao) { async function genericImage(modelType) { if (!modelType) return null; + const filePath = resolve(IMAGES_DIR, 'generic', `${modelType}.jpg`); if (fs.existsSync(filePath)) return fs.readFileSync(filePath); + const blob = await duckduckgo(`${modelType} Aircraft`); if (!blob) return null; + fs.mkdirSync(dirname(filePath), { recursive: true }); + const buffer = Buffer.from(await blob.arrayBuffer()); const metadata = await sharp(buffer).metadata(); const width = metadata.width || 1200; @@ -427,6 +449,7 @@ export async function getIcaoImage(icao) { stroke="black" stroke-opacity="0.35" stroke-width="${Math.max(2, Math.round(fontSize * 0.04))}" transform="rotate(-20 ${width / 2} ${height / 2})">STOCK IMAGE `); + const watermarked = await sharp(buffer).composite([{ input: watermark }]).jpeg().toBuffer(); fs.writeFileSync(filePath, watermarked); return watermarked; @@ -434,31 +457,39 @@ export async function getIcaoImage(icao) { async function icaoImage(icao) { const filePath = resolve(IMAGES_DIR, `${icao}.jpg`); - if(fs.existsSync(filePath)) return fs.readFileSync(filePath); + if (fs.existsSync(filePath)) return fs.readFileSync(filePath); + const planeSpotters = await fetch(`https://api.planespotters.net/pub/photos/hex/${icao}`, { headers: { 'User-Agent': 'open-sight.net (zaktimson@gmail.com)' } }).then(resp => resp.ok ? resp.json() : null).catch(() => null); const src = planeSpotters?.photos?.[0]?.thumbnail_large?.src || planeSpotters?.photos?.[0]?.thumbnail?.src; - if(!src) return null; + if (!src) return null; + const resp = await fetch(src, { - headers: {'Accept': '*/*', 'User-Agent': randomUA()} + headers: { + 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', + 'User-Agent': UA, + 'Referer': 'https://www.planespotters.net/' + } }); - if(!resp.ok) return null; + if (!resp.ok) return null; + fs.mkdirSync(IMAGES_DIR, { recursive: true }); + const buffer = Buffer.from(await resp.arrayBuffer()); fs.writeFileSync(filePath, buffer); return buffer; } icao = icao.toLowerCase(); - if(!fs.existsSync(IMAGES_DIR)) fs.mkdirSync(IMAGES_DIR, { recursive: true }); + if (!fs.existsSync(IMAGES_DIR)) fs.mkdirSync(IMAGES_DIR, { recursive: true }); const aircraft = await enrich({ hex: icao }); const modelType = aircraft.aircraft || aircraft.model || aircraft.type; - const generic = genericImage(modelType); const specific = await icaoImage(icao); - return specific || await generic; + if (specific) return specific; + return genericImage(modelType); } // Purge old history diff --git a/server/src/aurora.mjs b/server/src/aurora.mjs index 09d0900..fdf55a8 100644 --- a/server/src/aurora.mjs +++ b/server/src/aurora.mjs @@ -1,4 +1,4 @@ -import {adjustedInterval, Logger} from '@ztimson/utils'; +import {adjustedInterval} from '@ztimson/utils'; const cacheOptions = { ttl: null, @@ -12,7 +12,6 @@ export class CacheService { cached = null; lastUpdate = null; - logger; name; options; pending; @@ -24,7 +23,6 @@ export class CacheService { ttl: options.reload, ...options }; - this.logger = new Logger(name); if(this.options.reload) setTimeout(() => this.startLoop(), 1000); } @@ -62,16 +60,15 @@ export class CacheService { update(catchErr = true) { if(this.pending) return this.pending; - this.logger.info('Fetching latest'); + console.log('🌌 Fetching Aurora'); this.pending = this.#fetchWithRetry().then(data => { if(data?.err) throw new Error(data.err?.stack || data.err?.message || data.err); if(data?.error) throw new Error(data.error?.stack || data.error?.message || data.error); this.lastUpdate = new Date(); this.cached = data || null; - this.logger.debug('Finished updating'); return {timestamp: this.lastUpdate, data: this.cached}; }).catch(err => { - if(catchErr) this.logger.error(`Failed: ${typeof err == 'object' ? (err.stackTrace || err.message) : err}`) + if(catchErr) console.error(`Failed: ${typeof err == 'object' ? (err.stackTrace || err.message) : err}`) else throw err; }).finally(() => this.pending = null); return this.pending;