better military icao detection, airlines and stock image indicator
This commit is contained in:
@@ -200,7 +200,7 @@ const navball = computed(() => {
|
||||
z-index: 100;
|
||||
pointer-events: auto;
|
||||
background: rgba(0,0,0,0.88);
|
||||
border: 1px solid rgba(200,200,220,0.3);
|
||||
border: 1px solid rgba(0, 255, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
|
||||
overflow: hidden;
|
||||
@@ -213,7 +213,7 @@ const navball = computed(() => {
|
||||
.ap-header {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(200,200,220,0.2);
|
||||
border-bottom: 1px solid rgba(0, 255, 0, 0.2);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
@@ -390,7 +390,7 @@ function restoreOverlayState() {
|
||||
if (activeOverlays.value.has('rain')) refreshRain();
|
||||
}
|
||||
|
||||
function getAutoLayer(category: AutoCategory) {
|
||||
function getAutoLayer(category: AutoCategory): any {
|
||||
if (category === 'aircraft') return airTraffic;
|
||||
if (category === 'marine') return aisLayer;
|
||||
return sats;
|
||||
@@ -402,7 +402,7 @@ function getAutoId(category: AutoCategory, target: any): string {
|
||||
return String(target.satellite);
|
||||
}
|
||||
|
||||
function getAutoTarget(category: AutoCategory, id: string) {
|
||||
function getAutoTarget(category: AutoCategory, id: any) {
|
||||
if (category === 'aircraft') return airTraffic.getById(id);
|
||||
if (category === 'marine') return aisLayer.getById(id);
|
||||
return sats.getById(id);
|
||||
@@ -444,9 +444,7 @@ function focusAutoTarget(category: AutoCategory, target: any, animate = true) {
|
||||
function closeAutoPopup() {
|
||||
if (!autoCategory.value || !autoTarget.value) return;
|
||||
|
||||
getAutoLayer(autoCategory.value).closeAutoPopup(
|
||||
getAutoId(autoCategory.value, autoTarget.value),
|
||||
);
|
||||
getAutoLayer(autoCategory.value).closeAutoPopup(getAutoId(autoCategory.value, autoTarget.value));
|
||||
}
|
||||
|
||||
function clearAutoTarget() {
|
||||
@@ -520,15 +518,11 @@ function getAutoDistance(object: AutoObject): number {
|
||||
}
|
||||
|
||||
function getNewest(objects: AutoObject[]): AutoObject | null {
|
||||
return objects
|
||||
.slice()
|
||||
.sort((a, b) => b.discoveredAt - a.discoveredAt)[0] ?? null;
|
||||
return objects.slice().sort((a, b) => b.discoveredAt - a.discoveredAt)[0] ?? null;
|
||||
}
|
||||
|
||||
function getClosest(objects: AutoObject[]): AutoObject | null {
|
||||
return objects
|
||||
.slice()
|
||||
.sort((a, b) => getAutoDistance(a) - getAutoDistance(b))[0] ?? null;
|
||||
return objects.slice().sort((a, b) => getAutoDistance(a) - getAutoDistance(b))[0] ?? null;
|
||||
}
|
||||
|
||||
function setAutoTarget(object: AutoObject) {
|
||||
@@ -548,10 +542,8 @@ function setAutoTarget(object: AutoObject) {
|
||||
function shouldNewTargetSteal(currentObject: AutoObject, newObject: AutoObject): boolean {
|
||||
const currentDistance = getAutoDistance(currentObject);
|
||||
const newDistance = getAutoDistance(newObject);
|
||||
|
||||
if (!isFinite(currentDistance)) return true;
|
||||
if (!isFinite(newDistance)) return false;
|
||||
|
||||
return newDistance <= currentDistance * AUTO_DISTANCE_MARGIN;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ const compass = computed(() => {
|
||||
z-index: 100;
|
||||
pointer-events: auto;
|
||||
background: rgba(0,0,0,0.88);
|
||||
border: 1px solid rgba(200,200,220,0.3);
|
||||
border: 1px solid rgba(0,255,255,0.3);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
|
||||
overflow: hidden;
|
||||
@@ -217,7 +217,7 @@ const compass = computed(() => {
|
||||
.sp-header {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(200,200,220,0.2);
|
||||
border-bottom: 1px solid rgba(0,255,255,0.2);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
@@ -424,7 +424,7 @@ onUnmounted(() => {
|
||||
<div class="tp-meta">
|
||||
<div class="flex-c flex-fill">
|
||||
<span>Type: {{ latest?.type || 'Unknown' }}</span>
|
||||
<span>Freq: {{ latest?.frequency?.toFixed(3) || '—' }} MHz</span>
|
||||
<span>Freq: {{ latest?.frequency?.toFixed(2) || '—' }} MHz</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-c flex-fill align-x-end">
|
||||
@@ -486,7 +486,7 @@ onUnmounted(() => {
|
||||
<div class="tp-environment">
|
||||
<div class="tp-environment-label">Environment</div>
|
||||
|
||||
<div class="tp-environment-cards gap-4">
|
||||
<div class="tp-environment-cards gap-1">
|
||||
<div class="tp-environment-card">
|
||||
<span class="tp-environment-icon">🌡️</span>
|
||||
<div class="tp-environment-info">
|
||||
@@ -507,6 +507,16 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tp-environment-card">
|
||||
<span class="tp-environment-icon">⬇️</span>
|
||||
<div class="tp-environment-info">
|
||||
<span class="tp-environment-stat-label">Pressure</span>
|
||||
<span class="tp-environment-stat-value">
|
||||
{{ latest?.pressure > 0 ? latest.pressure : '—' }} hpa
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tp-environment-card">
|
||||
<span class="tp-environment-icon">🫧</span>
|
||||
<div class="tp-environment-info">
|
||||
@@ -525,7 +535,7 @@ onUnmounted(() => {
|
||||
:currentAltitude="currentAltitude"
|
||||
:unit="altitudeUnit.label"
|
||||
start-time=" "
|
||||
:end-time="formatDate('HH:MM A', latest.timestamp)"
|
||||
:end-time="formatDate('HH:mm:ss', latest.timestamp)"
|
||||
color="#ffaa00"
|
||||
/>
|
||||
|
||||
|
||||
881
server/package-lock.json
generated
881
server/package-lock.json
generated
@@ -15,9 +15,568 @@
|
||||
"cheerio": "^1.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"sharp": "^0.35.4",
|
||||
"yaml": "^2.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-freebsd-wasm32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
|
||||
"integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
|
||||
"integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
|
||||
"integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
|
||||
"integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
|
||||
"integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
|
||||
"integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
|
||||
"integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
|
||||
"integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
|
||||
"integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
|
||||
"integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
|
||||
"integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
|
||||
"integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
|
||||
"integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
|
||||
"integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
|
||||
"integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
|
||||
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.11.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-webcontainers-wasm32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
|
||||
"integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
|
||||
"integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@scalar/client-side-rendering": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@scalar/client-side-rendering/-/client-side-rendering-0.2.6.tgz",
|
||||
@@ -90,24 +649,6 @@
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz",
|
||||
"integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/readable-stream": {
|
||||
"version": "4.0.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.24.tgz",
|
||||
"integrity": "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@ztimson/utils": {
|
||||
"version": "0.29.5",
|
||||
"resolved": "https://registry.npmjs.org/@ztimson/utils/-/utils-0.29.5.tgz",
|
||||
@@ -117,18 +658,6 @@
|
||||
"var-persist": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@@ -151,66 +680,6 @@
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aedes": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aedes/-/aedes-1.1.2.tgz",
|
||||
"integrity": "sha512-prT8Zr4JkqpJMb5rMlLrW54oRyKPNgpRgqrF6PWQedL4ju+1YIwzkraxr16Rm4QyEkpqmZUdOEkfGp7zaDd5Lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aedes-packet": "^4.0.1",
|
||||
"aedes-persistence": "^10.3.1",
|
||||
"mqemitter": "^7.1.0",
|
||||
"mqtt-packet": "^9.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/aedes"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"aedes-persistence-level": "^9.1.2",
|
||||
"aedes-persistence-mongodb": "^9.3.1",
|
||||
"aedes-persistence-redis": "^11.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"aedes-persistence-level": {
|
||||
"optional": true
|
||||
},
|
||||
"aedes-persistence-mongodb": {
|
||||
"optional": true
|
||||
},
|
||||
"aedes-persistence-redis": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/aedes-packet": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/aedes-packet/-/aedes-packet-4.0.1.tgz",
|
||||
"integrity": "sha512-PLjKJElXEYB66RzHhJCw0SrRLfSmGRbbg2mtO5t/6FAbFp8vC0cG5ErhvPvfa9iKBrKZwoluWLgn8doBU8sRAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mqtt-packet": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/aedes-persistence": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/aedes-persistence/-/aedes-persistence-10.3.1.tgz",
|
||||
"integrity": "sha512-C/z0zLEXY9PkUH+7pz31H5gGzlQZxqhFONK1lt5VON0hiHXQq6tr4cSIAtFUVQ1kyUdd7SsfkeKLAECp3gjjpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aedes-packet": "^4.0.1",
|
||||
"qlobber": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
@@ -723,24 +1192,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
@@ -796,16 +1247,6 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/fastparallel": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz",
|
||||
"integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4",
|
||||
"xtend": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
@@ -1142,105 +1583,6 @@
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mqemitter": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mqemitter/-/mqemitter-7.1.0.tgz",
|
||||
"integrity": "sha512-GnBDNz3lxmllW201ne0mrmdy5tPOTnc79jjVcsfUa2LG2pUGeyGWVeiae6ZysfC/64XrYOqCKRAQYrB7pGyBVQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fastparallel": "^2.4.1",
|
||||
"qlobber": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt-packet": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz",
|
||||
"integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^6.0.8",
|
||||
"debug": "^4.3.4",
|
||||
"process-nextick-args": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt-packet/node_modules/bl": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz",
|
||||
"integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/readable-stream": "^4.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt-packet/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt-packet/node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mqtt-packet/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mqtt-packet/node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
@@ -1428,21 +1770,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@@ -1466,15 +1793,6 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qlobber": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/qlobber/-/qlobber-8.0.1.tgz",
|
||||
"integrity": "sha512-O+Wd1chXj5YE1DwmD+ae0bXiSLehmnS3czlC1R9FL/Nt/3q8uMS1bIHmg2lJfCoiimCxClWM8AAuJrF0EvNiog==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||
@@ -1544,16 +1862,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -1643,6 +1951,55 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
|
||||
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.1.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.35.4",
|
||||
"@img/sharp-darwin-x64": "0.35.4",
|
||||
"@img/sharp-freebsd-wasm32": "0.35.4",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.3",
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-arm": "1.3.3",
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.3",
|
||||
"@img/sharp-libvips-linux-x64": "1.3.3",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.3",
|
||||
"@img/sharp-linux-arm": "0.35.4",
|
||||
"@img/sharp-linux-arm64": "0.35.4",
|
||||
"@img/sharp-linux-ppc64": "0.35.4",
|
||||
"@img/sharp-linux-riscv64": "0.35.4",
|
||||
"@img/sharp-linux-s390x": "0.35.4",
|
||||
"@img/sharp-linux-x64": "0.35.4",
|
||||
"@img/sharp-linuxmusl-arm64": "0.35.4",
|
||||
"@img/sharp-linuxmusl-x64": "0.35.4",
|
||||
"@img/sharp-webcontainers-wasm32": "0.35.4",
|
||||
"@img/sharp-win32-arm64": "0.35.4",
|
||||
"@img/sharp-win32-ia32": "0.35.4",
|
||||
"@img/sharp-win32-x64": "0.35.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
@@ -1836,6 +2193,13 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@@ -1885,12 +2249,6 @@
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -1970,15 +2328,6 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"cheerio": "^1.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"sharp": "^0.35.4",
|
||||
"yaml": "^2.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,28 @@ import Database from 'better-sqlite3';
|
||||
import { fromCsv } from '@ztimson/utils';
|
||||
import { getIcon } from './adsb-shapes.mjs';
|
||||
import * as cheerio from 'cheerio';
|
||||
import sharp from 'sharp';
|
||||
|
||||
const DIR = dirname(fileURLToPath(import.meta.url));
|
||||
const DATA = resolve(DIR, '../data');
|
||||
const IMAGES_DIR = resolve(DATA, 'aircraft');
|
||||
const DB_PATH = resolve(DATA, 'aircraft.db');
|
||||
const CSV_CACHE = resolve(DATA, 'aircraft_db.csv');
|
||||
const AIRLINES_CACHE = resolve(DATA, 'airlines.dat');
|
||||
const MAX_HISTORY = 500;
|
||||
const HISTORY_TTL = 1000 * 60 * 60; // 1 hour
|
||||
|
||||
const ADSB_TTL = 1000;
|
||||
const CSV_URL = 'https://s3.opensky-network.org/data-samples/metadata/aircraft-database-complete-2024-06.csv';
|
||||
const MIL_RANGES_URL = ':8080/db-3.14.1708/ranges.js';
|
||||
const AIRLINES_URL = 'https://raw.githubusercontent.com/jpatokal/openflights/master/data/airlines.dat'
|
||||
|
||||
const MILITARY_OPERATORS = ['air force', 'army', 'navy', 'marine', 'coast guard', 'military', 'defence', 'defense', 'luftwaffe', 'RAF', 'USAF', 'USN', 'USMC'];
|
||||
const MILITARY_OPERATORS = ['air force', 'airforce', 'army', 'navy', 'marine', 'coast guard', 'military', 'defence', 'defense', 'law', 'luftwaffe', 'RAF', 'USAF', 'USN', 'USMC'];
|
||||
const CARGO_OPERATORS = ['fedex', 'ups', 'dhl', 'cargo', 'freight', 'logistic', 'atlas air', 'kalitta', 'air freight'];
|
||||
const PASSENGER_OPERATORS = ['airlines', 'airways', 'air ', ' air', 'jetblue', 'easyjet', 'ryanair', 'delta', 'united', 'american', 'avianca', 'southwest', 'lufthansa', 'emirates', 'british'];
|
||||
const PASSENGER_OPERATOR_FALLBACKS = ['airlines', 'airways', 'jetblue', 'easyjet', 'ryanair', 'delta', 'united', 'american', 'avianca', 'southwest', 'lufthansa', 'emirates', 'british'];
|
||||
const MILITARY_CLASSES = ['H1M', 'H2M', 'L1M', 'L2M', 'L4M', 'A0'];
|
||||
|
||||
let passengerOperators = PASSENGER_OPERATOR_FALLBACKS;
|
||||
|
||||
let adsbCache = null;
|
||||
let adsbCacheTs = 0;
|
||||
const noRecord = [];
|
||||
@@ -31,10 +35,51 @@ const history = new Map();
|
||||
const milRanges = [];
|
||||
let db;
|
||||
|
||||
function normalizeAirlineName(value) {
|
||||
return String(value || '')
|
||||
.normalize('NFKD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/&/g, ' and ')
|
||||
.replace(/[^a-z0-9]+/g, ' ')
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
async function syncAirlines() {
|
||||
try {
|
||||
if (!fs.existsSync(AIRLINES_CACHE)) {
|
||||
console.log('✈️ Downloading airline database');
|
||||
const res = await fetchWithTimeout(AIRLINES_URL, 10000);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
fs.writeFileSync(AIRLINES_CACHE, await res.text());
|
||||
}
|
||||
|
||||
const text = fs.readFileSync(AIRLINES_CACHE, 'utf8');
|
||||
const airlines = fromCsv(text, true);
|
||||
const names = new Set();
|
||||
for (const row of airlines) {
|
||||
if (row.active !== 'Y') continue;
|
||||
for (const value of [row.name, row.alias, row.callsign]) {
|
||||
const normalized = normalizeAirlineName(value);
|
||||
if (normalized.length >= 3) names.add(normalized);
|
||||
}
|
||||
}
|
||||
for (const value of PASSENGER_OPERATOR_FALLBACKS) names.add(normalizeAirlineName(value));
|
||||
passengerOperators = [...names].sort((a, b) => b.length - a.length);
|
||||
console.log(`✈️ Airline operators loaded: ${passengerOperators.length}`);
|
||||
} catch (e) {
|
||||
console.warn(`⚠️ Could not load airline database: ${e.message}`);
|
||||
passengerOperators = PASSENGER_OPERATOR_FALLBACKS.map(normalizeAirlineName);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
const cutoff = Date.now() - HISTORY_TTL;
|
||||
|
||||
for (const [key, trail] of history) {
|
||||
const last = trail.at(-1);
|
||||
|
||||
if (!last || last.ts < cutoff) history.delete(key);
|
||||
}
|
||||
}, 1000 * 60 * 5);
|
||||
@@ -48,16 +93,16 @@ function fetchWithTimeout(url, ms = 5000) {
|
||||
async function syncMilitaryRanges() {
|
||||
const { ADSB_URL } = cfg();
|
||||
try {
|
||||
const res = await fetch(ADSB_URL + MIL_RANGES_URL);
|
||||
const page = await fetch(ADSB_URL + ':8080').then(resp => resp.text());
|
||||
const db = /databaseFolder = "(db-.+?)"/.exec(page);
|
||||
const res = await fetch(ADSB_URL + `:8080/${db[1]}/ranges.js`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const { military } = await res.json();
|
||||
|
||||
db.exec('DELETE FROM military_ranges');
|
||||
const insert = db.prepare('INSERT INTO military_ranges (start, end) VALUES (?, ?)');
|
||||
const insertMany = db.transaction(ranges => {
|
||||
for (const [s, e] of ranges) insert.run(s.toUpperCase(), e.toUpperCase());
|
||||
});
|
||||
|
||||
insertMany(military);
|
||||
milRanges.length = 0;
|
||||
milRanges.push(...military.map(([s, e]) => [s.toUpperCase(), e.toUpperCase()]));
|
||||
@@ -70,10 +115,9 @@ async function syncMilitaryRanges() {
|
||||
|
||||
export async function initAircraftDb() {
|
||||
if (!fs.existsSync(DATA)) fs.mkdirSync(DATA, { recursive: true });
|
||||
|
||||
if (!fs.existsSync(IMAGES_DIR)) fs.mkdirSync(IMAGES_DIR, { recursive: true });
|
||||
const missing = !fs.existsSync(DB_PATH);
|
||||
if (missing) console.log(`✈️ Building database`);
|
||||
|
||||
db = new Database(DB_PATH);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS aircraft (
|
||||
@@ -93,12 +137,11 @@ export async function initAircraftDb() {
|
||||
serialNumber TEXT,
|
||||
aircraft TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS military_ranges (
|
||||
start TEXT NOT NULL,
|
||||
end TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS military_ranges (start TEXT NOT NULL, end TEXT NOT NULL);
|
||||
`);
|
||||
|
||||
await syncAirlines();
|
||||
await syncMilitaryRanges();
|
||||
|
||||
if(missing) {
|
||||
@@ -106,13 +149,24 @@ export async function initAircraftDb() {
|
||||
if(!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const text = await res.text();
|
||||
fs.writeFileSync(CSV_CACHE, text);
|
||||
|
||||
const csv = fromCsv(text, true);
|
||||
const insert = db.prepare(`
|
||||
INSERT OR REPLACE INTO aircraft VALUES (
|
||||
@icao, @built, @categoryDescription, @country, @engines,
|
||||
@class, @manufacturer, @model, @modes, @operator,
|
||||
@operatorCallsign, @owner, @registration, @serialNumber, @aircraft
|
||||
@icao,
|
||||
@built,
|
||||
@categoryDescription,
|
||||
@country,
|
||||
@engines,
|
||||
@class,
|
||||
@manufacturer,
|
||||
@model,
|
||||
@modes,
|
||||
@operator,
|
||||
@operatorCallsign,
|
||||
@owner,
|
||||
@registration,
|
||||
@serialNumber,
|
||||
@aircraft
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -135,7 +189,7 @@ export async function initAircraftDb() {
|
||||
owner: row.owner || null,
|
||||
registration: row.registration || null,
|
||||
serialNumber: row.serialNumber || null,
|
||||
aircraft: row.typecode || null,
|
||||
aircraft: row.typecode || null
|
||||
})));
|
||||
|
||||
fs.unlinkSync(CSV_CACHE);
|
||||
@@ -161,17 +215,26 @@ function wordMatch(text, keyword) {
|
||||
export function classifyAircraft(row) {
|
||||
if (!row) return 'unknown';
|
||||
|
||||
const matchesAny = (fields, list) => fields.some(f => list.some(k => f.toLowerCase().includes(k)));
|
||||
const matchesAny = (fields, list) => fields.some(f => {
|
||||
const value = normalizeAirlineName(f);
|
||||
return value && list.some(k => value.includes(normalizeAirlineName(k)));
|
||||
});
|
||||
|
||||
const operatorFields = [row.operator, row.operatorCallsign];
|
||||
const ownerFields = [row.owner];
|
||||
const allFields = [...operatorFields, ...ownerFields, row.categoryDescription];
|
||||
|
||||
if (isIcaoInMilitaryRange(row.icao) || MILITARY_CLASSES.includes(row.class) || matchesAny(allFields, MILITARY_OPERATORS)) return 'military';
|
||||
if (row.categoryDescription?.toLowerCase().includes('cargo') || matchesAny(operatorFields, CARGO_OPERATORS)) return 'cargo';
|
||||
if (matchesAny(operatorFields, PASSENGER_OPERATORS)) return 'passenger';
|
||||
if (row.owner && !row.operator) return 'private';
|
||||
if (isIcaoInMilitaryRange(row.icao) || MILITARY_CLASSES.includes(row.class) ||
|
||||
matchesAny(allFields, MILITARY_OPERATORS)) {
|
||||
return 'military';
|
||||
}
|
||||
|
||||
if (row.categoryDescription?.toLowerCase().includes('cargo') || matchesAny(operatorFields, CARGO_OPERATORS)) {
|
||||
return 'cargo';
|
||||
}
|
||||
|
||||
if (matchesAny(operatorFields, passengerOperators)) return 'passenger';
|
||||
if (row.owner && !row.operator) return 'private';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
@@ -184,7 +247,7 @@ async function fetchHexDb(icao) {
|
||||
manufacturer: found.Manufacturer || null,
|
||||
aircraft: found.ICAOTypeCode || null,
|
||||
model: found.Type || null,
|
||||
operator: found.RegisteredOwners || null,
|
||||
operator: found.RegisteredOwners || null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -192,31 +255,37 @@ async function scrapeHexDatabase(icao) {
|
||||
const url = `https://hexdatabase.com/h/${icao}`;
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) return null;
|
||||
|
||||
const html = await resp.text();
|
||||
const tableMatch = html.match(/<table[\s\S]*?<\/table>/i);
|
||||
if (!tableMatch) return null;
|
||||
|
||||
const $ = cheerio.load(tableMatch[0]);
|
||||
const row = $('tr').filter((_, el) => $(el).text().toLowerCase().includes(icao.toLowerCase())).first();
|
||||
if (!row.length) return null;
|
||||
|
||||
const cells = row.find('td');
|
||||
return {
|
||||
registration: $(cells[1]).text().trim() || null,
|
||||
aircraft: $(cells[2]).text().trim() || null,
|
||||
operator: $(cells[3]).text().trim() || null,
|
||||
serialNumber: $(cells[5]).text().trim() || null,
|
||||
serialNumber: $(cells[5]).text().trim() || null
|
||||
};
|
||||
}
|
||||
|
||||
function backfillFromSimilar(aircraft) {
|
||||
if (!aircraft) return {};
|
||||
const similar = db.prepare(`
|
||||
SELECT manufacturer, model, engines, categoryDescription, class
|
||||
SELECT
|
||||
manufacturer,
|
||||
model,
|
||||
engines,
|
||||
categoryDescription,
|
||||
class
|
||||
FROM aircraft
|
||||
WHERE aircraft = ?
|
||||
AND (manufacturer IS NOT NULL OR model IS NOT NULL OR engines IS NOT NULL)
|
||||
AND (
|
||||
manufacturer IS NOT NULL
|
||||
OR model IS NOT NULL
|
||||
OR engines IS NOT NULL
|
||||
)
|
||||
LIMIT 1
|
||||
`).get(aircraft);
|
||||
if (!similar) return {};
|
||||
@@ -225,19 +294,15 @@ function backfillFromSimilar(aircraft) {
|
||||
model: similar.model || null,
|
||||
engines: similar.engines || null,
|
||||
categoryDescription: similar.categoryDescription || null,
|
||||
class: similar.class || null,
|
||||
class: similar.class || null
|
||||
};
|
||||
}
|
||||
|
||||
export async function enrichAircraft(a) {
|
||||
if (!a.hex) return a;
|
||||
const icao = a.hex.toUpperCase();
|
||||
|
||||
// 1. Check own DB first
|
||||
const row = db.prepare('SELECT * FROM aircraft WHERE icao = ?').get(icao);
|
||||
if (row?.aircraft) return { ...a, ...row, type: classifyAircraft(row) };
|
||||
|
||||
// 2. Race the two external sources
|
||||
if (noRecord.includes(icao)) return { icao, ...a, type: 'unknown' };
|
||||
const hexDbPromise = fetchHexDb(icao);
|
||||
const scrapePromise = scrapeHexDatabase(icao);
|
||||
@@ -247,15 +312,11 @@ export async function enrichAircraft(a) {
|
||||
noRecord.push(icao);
|
||||
return { icao, ...a, type: 'unknown' };
|
||||
}
|
||||
|
||||
// 3. Backfill manufacturer/model/engines from similar aircraft type in DB
|
||||
const merged = { ...row, ...found };
|
||||
if (merged.aircraft && (!merged.manufacturer || !merged.model)) {
|
||||
const similar = backfillFromSimilar(merged.aircraft);
|
||||
Object.assign(merged, similar);
|
||||
}
|
||||
|
||||
// 4. Save back to DB
|
||||
if(found) {
|
||||
db.prepare(`
|
||||
INSERT INTO aircraft (icao, registration, manufacturer, aircraft, model, operator, country, serialNumber, engines, categoryDescription, class)
|
||||
@@ -282,17 +343,19 @@ export async function enrichAircraft(a) {
|
||||
serialNumber: merged.serialNumber || null,
|
||||
engines: merged.engines || null,
|
||||
categoryDescription: merged.categoryDescription || null,
|
||||
class: merged.class || null,
|
||||
class: merged.class || null
|
||||
});
|
||||
}
|
||||
|
||||
return {icao, ...a, ...merged, type: classifyAircraft(merged) }
|
||||
return { icao, ...a, ...merged, type: classifyAircraft(merged) };
|
||||
}
|
||||
|
||||
export async function getADSB() {
|
||||
if(adsbCache && Date.now() - adsbCacheTs < ADSB_TTL) return adsbCache;
|
||||
|
||||
const { ADSB_URL } = cfg();
|
||||
if(!ADSB_URL) return [];
|
||||
|
||||
const r = await fetchWithTimeout(`${ADSB_URL}:8080/data/aircraft.json`);
|
||||
const j = await r.json();
|
||||
const aircraft = j.aircraft || [];
|
||||
@@ -303,7 +366,12 @@ export async function getADSB() {
|
||||
const key = a.hex.toLowerCase();
|
||||
if (!history.has(key)) history.set(key, []);
|
||||
const trail = history.get(key);
|
||||
trail.push({ latitude: a.lat, longitude: a.lon, altitude: a.alt_baro || 0, ts: Date.now() });
|
||||
trail.push({
|
||||
latitude: a.lat,
|
||||
longitude: a.lon,
|
||||
altitude: a.alt_baro || 0,
|
||||
ts: Date.now()
|
||||
});
|
||||
if (trail.length > MAX_HISTORY) trail.shift();
|
||||
}
|
||||
|
||||
@@ -334,7 +402,7 @@ export async function getADSBImage(icao) {
|
||||
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}`,
|
||||
`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)];
|
||||
}
|
||||
@@ -344,6 +412,8 @@ export async function getADSBImage(icao) {
|
||||
const res = await fetch(`https://duckduckgo.com/?q=${encodeURIComponent(query)}`, {
|
||||
headers: { 'User-Agent': randomUA() }
|
||||
});
|
||||
|
||||
if(!res.ok) console.warn(res.status, res.statusText);
|
||||
const html = await res.text();
|
||||
const match = html.match(/vqd=['"]?([\d-]+)['"]?/);
|
||||
return match?.[1];
|
||||
@@ -356,6 +426,7 @@ export async function getADSBImage(icao) {
|
||||
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);
|
||||
|
||||
if (data?.results?.length) {
|
||||
for (const result of data.results) {
|
||||
@@ -366,6 +437,7 @@ export async function getADSBImage(icao) {
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -373,30 +445,41 @@ export async function getADSBImage(icao) {
|
||||
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());
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
return buffer;
|
||||
const metadata = await sharp(buffer).metadata();
|
||||
const width = metadata.width || 1200;
|
||||
const height = metadata.height || 800;
|
||||
const fontSize = Math.max(48, Math.round(Math.min(width, height) * 0.16));
|
||||
const watermark = Buffer.from(`
|
||||
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle"
|
||||
font-family="Arial, Helvetica, sans-serif" font-size="${fontSize}px"
|
||||
font-weight="700" fill="white" fill-opacity="0.65"
|
||||
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>
|
||||
</svg>`);
|
||||
const watermarked = await sharp(buffer).composite([{ input: watermark }]).jpeg().toBuffer();
|
||||
fs.writeFileSync(filePath, watermarked);
|
||||
return watermarked;
|
||||
}
|
||||
|
||||
async function icaoImage(icao) {
|
||||
const filePath = resolve(IMAGES_DIR, `${icao}.jpg`);
|
||||
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;
|
||||
const resp = await fetch(src, {
|
||||
headers: {'Accept': '*/*', 'User-Agent': randomUA()}
|
||||
});
|
||||
|
||||
const resp = await fetch(src, { headers: { 'Accept': '*/*', 'User-Agent': randomUA() } });
|
||||
if(!resp.ok) return null;
|
||||
|
||||
fs.mkdirSync(IMAGES_DIR, { recursive: true });
|
||||
const buffer = Buffer.from(await resp.arrayBuffer());
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
@@ -404,9 +487,9 @@ export async function getADSBImage(icao) {
|
||||
}
|
||||
|
||||
icao = icao.toLowerCase();
|
||||
if(!fs.existsSync(IMAGES_DIR)) fs.mkdirSync(IMAGES_DIR, { recursive: true });
|
||||
const aircraft = await enrichAircraft({ hex: icao });
|
||||
const modelType = aircraft.aircraft || aircraft.model || aircraft.type;
|
||||
|
||||
const generic = genericImage(modelType);
|
||||
const specific = await icaoImage(icao);
|
||||
return specific || await generic;
|
||||
|
||||
@@ -33,20 +33,17 @@ export async function getSondes() {
|
||||
altitude: t.alt,
|
||||
heading: t.heading,
|
||||
speed: t.vel_h,
|
||||
vertical_speed: t.vel_v,
|
||||
sats: t.sats,
|
||||
timestamp: t.datetime ? Date.parse(t.datetime) : sonde.timestamp * 1000,
|
||||
datetime: t.datetime,
|
||||
climb: t.vel_v,
|
||||
gps: t.sats,
|
||||
timestamp: t.datetime ? Date.parse(t.datetime) : new Date(sonde.timestamp * 1000),
|
||||
frame: t.frame,
|
||||
battery: t.batt,
|
||||
temperature: t.temp,
|
||||
humidity: t.humidity,
|
||||
pressure: t.pressure,
|
||||
frequency: t.freq_float,
|
||||
frequency_hz: t.tx_frequency,
|
||||
snr: t.snr,
|
||||
ppm: t.ppm,
|
||||
aprsid: t.aprsid?.trim(),
|
||||
freqMHz: t.tx_frequency / 1000,
|
||||
snr: t.snr,
|
||||
rs41_mainboard: t.rs41_mainboard,
|
||||
rs41_mainboard_fw: t.rs41_mainboard_fw,
|
||||
version: t.version,
|
||||
|
||||
Reference in New Issue
Block a user