mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-09 15:53:56 +02:00
feat: Telegram OSINT map layer, Osiris intel ports, and maritime settings
Add Telegram OSINT with hourly incremental t.me scraping, metro geocoding separate from news centroids, threat-intercept popup UI with inline media, and HTML markers above alert boxes so pins stay clickable. Expose GFW_API_TOKEN in onboarding and Settings Maritime; harden GFW/CCTV/geo fetchers. Port Osiris- derived recon, SCM, entity graph, malware/cyber feeds, sanctions, and submarine cable layers with tests and documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import type { SelectedEntity } from '@/types/dashboard';
|
||||
|
||||
const GRAPH_TYPES = new Set(['aircraft', 'vessel', 'company', 'person', 'ip', 'country']);
|
||||
|
||||
const SELECTION_TO_GRAPH: Record<string, string> = {
|
||||
flight: 'aircraft',
|
||||
private_flight: 'aircraft',
|
||||
military_flight: 'aircraft',
|
||||
private_jet: 'aircraft',
|
||||
tracked_flight: 'aircraft',
|
||||
ship: 'vessel',
|
||||
};
|
||||
|
||||
export function mapEntityToGraphType(type: string): string | null {
|
||||
const mapped = SELECTION_TO_GRAPH[type] || type;
|
||||
return GRAPH_TYPES.has(mapped) ? mapped : null;
|
||||
}
|
||||
|
||||
export function isEntityGraphEligible(entity: SelectedEntity | null | undefined): boolean {
|
||||
if (!entity) return false;
|
||||
return mapEntityToGraphType(entity.type) !== null;
|
||||
}
|
||||
@@ -67,6 +67,19 @@ export function getLiveDataBounds(): LiveDataBounds | null {
|
||||
return _current;
|
||||
}
|
||||
|
||||
/** Stable cache key for the active bbox-scoped fetch window (1° quantization,
|
||||
* matching appendLiveDataBoundsParams / backend ETag). Returns null when
|
||||
* world-scale fetching is active. */
|
||||
export function liveDataBoundsKey(): string | null {
|
||||
const b = _current;
|
||||
if (!b) return null;
|
||||
const s = Math.floor(b.south);
|
||||
const w = Math.floor(b.west);
|
||||
const n = Math.ceil(b.north);
|
||||
const e = Math.ceil(b.east);
|
||||
return `${s},${w},${n},${e}`;
|
||||
}
|
||||
|
||||
/** Append `s/w/n/e` query params to a URL when bounds are set, otherwise
|
||||
* return the URL unchanged. Centralised so all live-data callers stay in
|
||||
* sync about quantization and the world-scale skip rule. */
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/** Synthetic TeleGeography corridor overlays — not real cable routes. */
|
||||
const SYNTHETIC_CABLE_NAMES = new Set([
|
||||
'SEA-ME-WE Corridor',
|
||||
'Trans-Atlantic North',
|
||||
'Trans-Atlantic South',
|
||||
'WACS / SAT-3 Corridor',
|
||||
'EASSy / SEACOM',
|
||||
'East Asia Corridor',
|
||||
'Asia-Australia',
|
||||
'Trans-Pacific',
|
||||
'South Atlantic',
|
||||
]);
|
||||
|
||||
type LngLat = [number, number];
|
||||
|
||||
function lonJumpDegrees(a: LngLat, b: LngLat): number {
|
||||
const d = Math.abs(b[0] - a[0]);
|
||||
return Math.min(d, 360 - d);
|
||||
}
|
||||
|
||||
function iterParts(geometry: GeoJSON.Geometry): LngLat[][] {
|
||||
if (geometry.type === 'LineString') {
|
||||
return [geometry.coordinates as LngLat[]];
|
||||
}
|
||||
if (geometry.type === 'MultiLineString') {
|
||||
return geometry.coordinates as LngLat[][];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Split a path when consecutive vertices jump across continents / dateline. */
|
||||
function splitAtJumps(coords: LngLat[], maxJumpDeg = 90): LngLat[][] {
|
||||
if (coords.length < 2) return coords.length ? [coords] : [];
|
||||
|
||||
const segments: LngLat[][] = [[coords[0]]];
|
||||
for (let i = 1; i < coords.length; i += 1) {
|
||||
const prev = segments[segments.length - 1][segments[segments.length - 1].length - 1];
|
||||
const next = coords[i];
|
||||
if (lonJumpDegrees(prev, next) > maxJumpDeg) {
|
||||
segments.push([next]);
|
||||
} else {
|
||||
segments[segments.length - 1].push(next);
|
||||
}
|
||||
}
|
||||
return segments.filter((seg) => seg.length >= 2);
|
||||
}
|
||||
|
||||
function partsToGeometry(parts: LngLat[][]): GeoJSON.LineString | GeoJSON.MultiLineString | null {
|
||||
if (!parts.length) return null;
|
||||
if (parts.length === 1) {
|
||||
return { type: 'LineString', coordinates: parts[0] };
|
||||
}
|
||||
return { type: 'MultiLineString', coordinates: parts };
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop synthetic corridor junk and split lines that cut across the dateline.
|
||||
* Land-crossing segments are stripped at build time (see scripts/sanitize_submarine_cables.py).
|
||||
*/
|
||||
export function sanitizeSubmarineCables(
|
||||
collection: GeoJSON.FeatureCollection,
|
||||
): GeoJSON.FeatureCollection {
|
||||
const byName = new Map<string, GeoJSON.Feature>();
|
||||
|
||||
for (const feature of collection.features) {
|
||||
const name = String(feature.properties?.name || '').trim();
|
||||
if (!name || SYNTHETIC_CABLE_NAMES.has(name)) continue;
|
||||
if (!feature.geometry || feature.geometry.type === 'GeometryCollection') continue;
|
||||
|
||||
const splitParts: LngLat[][] = [];
|
||||
for (const part of iterParts(feature.geometry)) {
|
||||
splitParts.push(...splitAtJumps(part));
|
||||
}
|
||||
const geometry = partsToGeometry(splitParts);
|
||||
if (!geometry) continue;
|
||||
|
||||
const cleaned: GeoJSON.Feature = {
|
||||
type: 'Feature',
|
||||
properties: feature.properties ?? {},
|
||||
geometry,
|
||||
};
|
||||
|
||||
const existing = byName.get(name);
|
||||
if (!existing) {
|
||||
byName.set(name, cleaned);
|
||||
continue;
|
||||
}
|
||||
const existingPts = iterParts(existing.geometry!).flat().length;
|
||||
const newPts = splitParts.flat().length;
|
||||
if (newPts > existingPts) byName.set(name, cleaned);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: Array.from(byName.values()),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Proxy Telegram CDN media through the backend (host allowlist + range requests). */
|
||||
export function buildTelegramMediaProxyUrl(rawUrl: string): string {
|
||||
return rawUrl.startsWith('http')
|
||||
? `/api/telegram/media?url=${encodeURIComponent(rawUrl)}`
|
||||
: rawUrl;
|
||||
}
|
||||
Reference in New Issue
Block a user