Image lookup fix?
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user