Image lookup fix?

This commit is contained in:
2026-09-14 00:48:24 -04:00
parent a0c15f1631
commit eaef6e8066
2 changed files with 64 additions and 36 deletions

View File

@@ -203,7 +203,6 @@ async function syncAirlines() {
const text = fs.readFileSync(AIRLINES_CACHE, 'utf8'); const text = fs.readFileSync(AIRLINES_CACHE, 'utf8');
const airlines = text.split('\n').slice(1).map(row => const airlines = text.split('\n').slice(1).map(row =>
row.split(',')[1]?.slice(1, -1).toLowerCase()).filter(Boolean).flat(); row.split(',')[1]?.slice(1, -1).toLowerCase()).filter(Boolean).flat();
console.log(airlines);
passengerOperators = airlines.sort((a, b) => b.length - a.length); passengerOperators = airlines.sort((a, b) => b.length - a.length);
console.log(`✈️ Airlines loaded: ${airlines.length}`); console.log(`✈️ Airlines loaded: ${airlines.length}`);
} catch (e) { } catch (e) {
@@ -363,44 +362,63 @@ export async function getHistory(icao) {
} }
export async function getIcaoImage(icao) { export async function getIcaoImage(icao) {
function randomUA() { 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';
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)];
}
async function duckduckgo(query) { async function duckduckgo(query) {
async function getVqd(query) { async function getVqd(query) {
const res = await fetch(`https://duckduckgo.com/?q=${encodeURIComponent(query)}`, { const url = `https://duckduckgo.com/?q=${encodeURIComponent(query)}&iax=images&ia=images`;
headers: { 'User-Agent': randomUA() } 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) return console.warn(`DuckDuckGo Search failed: ${res.status}`);
if(!res.ok) console.warn(res.status, res.statusText);
const html = await res.text(); const html = await res.text();
const match = html.match(/vqd=['"]?([\d-]+)['"]?/); const match = html.match(/vqd=["']([^"']+)["']/);
return match?.[1]; return match?.[1] || null;
} }
const vqd = await getVqd(query); const vqd = await getVqd(query);
if (!vqd) throw new Error('Could not get vqd token'); 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`; await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 500));
const data = await fetch(url, { const searchUrl = `https://duckduckgo.com/i.js?q=${encodeURIComponent(query)}&o=json&vqd=${encodeURIComponent(vqd)}&f=,,,,,&p=1`;
headers: {'User-Agent': randomUA(), 'Referer': 'https://duckduckgo.com/'} const data = await fetch(searchUrl, {
}).then(resp => resp.json()); headers: {
if(!data.ok) console.warn(data.status, data.statusText); '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) { if (data?.results?.length) {
for (const result of data.results) { for (const result of data.results) {
if (!result.image) continue; if (!result.image) continue;
try { 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(); 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) { async function genericImage(modelType) {
if (!modelType) return null; if (!modelType) return null;
const filePath = resolve(IMAGES_DIR, 'generic', `${modelType}.jpg`); const filePath = resolve(IMAGES_DIR, 'generic', `${modelType}.jpg`);
if (fs.existsSync(filePath)) return fs.readFileSync(filePath); if (fs.existsSync(filePath)) return fs.readFileSync(filePath);
const blob = await duckduckgo(`${modelType} Aircraft`); const blob = await duckduckgo(`${modelType} Aircraft`);
if (!blob) return null; if (!blob) return null;
fs.mkdirSync(dirname(filePath), { recursive: true }); fs.mkdirSync(dirname(filePath), { recursive: true });
const buffer = Buffer.from(await blob.arrayBuffer()); const buffer = Buffer.from(await blob.arrayBuffer());
const metadata = await sharp(buffer).metadata(); const metadata = await sharp(buffer).metadata();
const width = metadata.width || 1200; 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))}" 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</text> transform="rotate(-20 ${width / 2} ${height / 2})">STOCK IMAGE</text>
</svg>`); </svg>`);
const watermarked = await sharp(buffer).composite([{ input: watermark }]).jpeg().toBuffer(); const watermarked = await sharp(buffer).composite([{ input: watermark }]).jpeg().toBuffer();
fs.writeFileSync(filePath, watermarked); fs.writeFileSync(filePath, watermarked);
return watermarked; return watermarked;
@@ -434,31 +457,39 @@ export async function getIcaoImage(icao) {
async function icaoImage(icao) { async function icaoImage(icao) {
const filePath = resolve(IMAGES_DIR, `${icao}.jpg`); 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}`, { const planeSpotters = await fetch(`https://api.planespotters.net/pub/photos/hex/${icao}`, {
headers: { 'User-Agent': 'open-sight.net (zaktimson@gmail.com)' } headers: { 'User-Agent': 'open-sight.net (zaktimson@gmail.com)' }
}).then(resp => resp.ok ? resp.json() : null).catch(() => null); }).then(resp => resp.ok ? resp.json() : null).catch(() => null);
const src = planeSpotters?.photos?.[0]?.thumbnail_large?.src || planeSpotters?.photos?.[0]?.thumbnail?.src; 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, { 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 }); fs.mkdirSync(IMAGES_DIR, { recursive: true });
const buffer = Buffer.from(await resp.arrayBuffer()); const buffer = Buffer.from(await resp.arrayBuffer());
fs.writeFileSync(filePath, buffer); fs.writeFileSync(filePath, buffer);
return buffer; return buffer;
} }
icao = icao.toLowerCase(); 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 aircraft = await enrich({ hex: icao });
const modelType = aircraft.aircraft || aircraft.model || aircraft.type; const modelType = aircraft.aircraft || aircraft.model || aircraft.type;
const generic = genericImage(modelType);
const specific = await icaoImage(icao); const specific = await icaoImage(icao);
return specific || await generic; if (specific) return specific;
return genericImage(modelType);
} }
// Purge old history // Purge old history

View File

@@ -1,4 +1,4 @@
import {adjustedInterval, Logger} from '@ztimson/utils'; import {adjustedInterval} from '@ztimson/utils';
const cacheOptions = { const cacheOptions = {
ttl: null, ttl: null,
@@ -12,7 +12,6 @@ export class CacheService {
cached = null; cached = null;
lastUpdate = null; lastUpdate = null;
logger;
name; name;
options; options;
pending; pending;
@@ -24,7 +23,6 @@ export class CacheService {
ttl: options.reload, ttl: options.reload,
...options ...options
}; };
this.logger = new Logger(name);
if(this.options.reload) setTimeout(() => this.startLoop(), 1000); if(this.options.reload) setTimeout(() => this.startLoop(), 1000);
} }
@@ -62,16 +60,15 @@ export class CacheService {
update(catchErr = true) { update(catchErr = true) {
if(this.pending) return this.pending; if(this.pending) return this.pending;
this.logger.info('Fetching latest'); console.log('🌌 Fetching Aurora');
this.pending = this.#fetchWithRetry().then(data => { this.pending = this.#fetchWithRetry().then(data => {
if(data?.err) throw new Error(data.err?.stack || data.err?.message || data.err); 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); if(data?.error) throw new Error(data.error?.stack || data.error?.message || data.error);
this.lastUpdate = new Date(); this.lastUpdate = new Date();
this.cached = data || null; this.cached = data || null;
this.logger.debug('Finished updating');
return {timestamp: this.lastUpdate, data: this.cached}; return {timestamp: this.lastUpdate, data: this.cached};
}).catch(err => { }).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; else throw err;
}).finally(() => this.pending = null); }).finally(() => this.pending = null);
return this.pending; return this.pending;