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:
BigBodyCobain
2026-06-08 21:04:08 -06:00
co-authored by Cursor
parent b64b9e0962
commit af9b3d08cc
76 changed files with 5769 additions and 218 deletions
@@ -2,6 +2,7 @@ import React from 'react';
import { Marker } from 'react-map-gl/maplibre';
import type { Earthquake, SelectedEntity, Ship, TrackedFlight, UAV } from '@/types/dashboard';
import type { SpreadAlertItem } from '@/utils/alertSpread';
import { TELEGRAM_MARKER_OFFSET } from '@/components/map/geoJSONBuilders';
// Shared monospace label style base
const LABEL_BASE: React.CSSProperties = {
@@ -473,3 +474,60 @@ export function ThreatMarkers({
</>
);
}
// -- Telegram OSINT pins (HTML, above threat alert boxes) --
interface TelegramOsintMarkersProps {
features: GeoJSON.Feature[];
onEntityClick?: (entity: SelectedEntity | null) => void;
}
export function TelegramOsintMarkers({ features, onEntityClick }: TelegramOsintMarkersProps) {
if (!features.length) return null;
return (
<>
{features.map((feature) => {
if (feature.geometry?.type !== 'Point') return null;
const [lng, lat] = feature.geometry.coordinates as [number, number];
const props = feature.properties || {};
const id = String(props.id || '');
if (!id) return null;
const postCount = Number(props.post_count || 1);
const size = postCount > 1 ? Math.min(30, 16 + Math.log2(postCount) * 5) : 16;
return (
<Marker
key={`telegram-osint-${id}`}
longitude={lng}
latitude={lat}
anchor="center"
offset={TELEGRAM_MARKER_OFFSET}
style={{ zIndex: 95 }}
onClick={(e) => {
e.originalEvent.stopPropagation();
onEntityClick?.({
id,
type: 'telegram_osint',
name: String(props.name || 'Telegram OSINT'),
});
}}
>
<div
title={`Telegram OSINT${postCount > 1 ? ` (${postCount} posts)` : ''}`}
style={{
width: size,
height: size,
borderRadius: '50%',
background: '#ef4444',
border: '2.5px solid #fca5a5',
boxShadow: '0 0 14px rgba(239, 68, 68, 0.75)',
cursor: 'pointer',
}}
/>
</Marker>
);
})}
</>
);
}
@@ -26,6 +26,8 @@ export type DynamicMapLayersDataPayload = DynamicMapLayersPayload;
export type DynamicMapLayersBuildPayload = {
bounds: BoundsTuple;
/** When true, /api/live-data/fast already bbox-filtered this payload — skip client cull. */
serverBboxScoped?: boolean;
dtSeconds: number;
trackedIcaos: string[];
activeLayers: {
@@ -173,6 +175,16 @@ function inView(lat: number, lng: number, bounds: BoundsTuple): boolean {
return lng >= bounds[0] && lng <= bounds[2] && lat >= bounds[1] && lat <= bounds[3];
}
function passesViewFilter(
lat: number,
lng: number,
bounds: BoundsTuple,
serverBboxScoped: boolean,
): boolean {
if (serverBboxScoped) return true;
return inView(lat, lng, bounds);
}
function cleanLabel(value: unknown): string {
if (typeof value !== 'string' && typeof value !== 'number') return '';
return String(value).trim();
@@ -239,6 +251,7 @@ function buildFlightLayerGeoJSONWorker(
bounds: BoundsTuple,
dtSeconds: number,
trackedIcaos: Set<string>,
serverBboxScoped: boolean,
): FC {
if (!flights?.length) return null;
const { colorMap, groundedMap, typeLabel, idPrefix, milSpecialMap, useTrackHeading } = config;
@@ -248,7 +261,7 @@ function buildFlightLayerGeoJSONWorker(
const f = flights[i];
if (f.lat == null || f.lng == null) continue;
const [iLng, iLat] = interpFlightPosition(f, dtSeconds);
if (!inView(iLat, iLng, bounds)) continue;
if (!passesViewFilter(iLat, iLng, bounds, serverBboxScoped)) continue;
if (f.icao24 && trackedIcaos.has(f.icao24.toLowerCase())) continue;
const acType = classifyAircraft(f.model, f.aircraft_category);
@@ -288,6 +301,7 @@ function buildTrackedFlightsGeoJSONWorker(
flights: Flight[] | undefined,
bounds: BoundsTuple,
dtSeconds: number,
serverBboxScoped: boolean,
): FC {
if (!flights?.length) return null;
const features: GeoJSON.Feature[] = [];
@@ -296,7 +310,7 @@ function buildTrackedFlightsGeoJSONWorker(
const f = flights[i];
if (f.lat == null || f.lng == null) continue;
const [lng, lat] = interpFlightPosition(f, dtSeconds);
if (!inView(lat, lng, bounds)) continue;
if (!passesViewFilter(lat, lng, bounds, serverBboxScoped)) continue;
const alertColor = ('alert_color' in f ? f.alert_color : '') || 'white';
const acType = classifyAircraft(f.model, f.aircraft_category);
@@ -334,6 +348,7 @@ function buildShipsGeoJSONWorker(
activeLayers: DynamicMapLayersBuildPayload['activeLayers'],
bounds: BoundsTuple,
dtSeconds: number,
serverBboxScoped: boolean,
): FC {
if (
!ships?.length ||
@@ -353,7 +368,7 @@ function buildShipsGeoJSONWorker(
const s = ships[i];
if (s.lat == null || s.lng == null) continue;
const [iLng, iLat] = interpShipPosition(s, dtSeconds);
if (!inView(iLat, iLng, bounds)) continue;
if (!passesViewFilter(iLat, iLng, bounds, serverBboxScoped)) continue;
if (s.type === 'carrier') continue;
const isTrackedYacht = Boolean(s.yacht_alert);
@@ -394,6 +409,7 @@ function buildSigintGeoJSONWorker(
signals: SigintSignal[] | undefined,
source: 'meshtastic' | 'aprs',
bounds: BoundsTuple,
serverBboxScoped: boolean,
): FC {
if (!signals?.length) return null;
const wanted =
@@ -405,7 +421,7 @@ function buildSigintGeoJSONWorker(
for (let i = 0; i < signals.length; i += 1) {
const sig = signals[i];
if (!wanted(sig) || sig.lat == null || sig.lng == null) continue;
if (!inView(sig.lat, sig.lng, bounds)) continue;
if (!passesViewFilter(sig.lat, sig.lng, bounds, serverBboxScoped)) continue;
features.push({
type: 'Feature',
properties: {
@@ -537,6 +553,7 @@ function applyFilters(activeFilters: Record<string, string[]> | undefined) {
function buildDynamicLayers(payload: DynamicMapLayersBuildPayload): DynamicMapLayersResult {
const trackedIcaos = new Set(payload.trackedIcaos);
const filtered = applyFilters(payload.activeFilters);
const serverBboxScoped = Boolean(payload.serverBboxScoped);
return {
commercialFlightsGeoJSON: payload.activeLayers.flights
? buildFlightLayerGeoJSONWorker(
@@ -545,6 +562,7 @@ function buildDynamicLayers(payload: DynamicMapLayersBuildPayload): DynamicMapLa
payload.bounds,
payload.dtSeconds,
trackedIcaos,
serverBboxScoped,
)
: null,
privateFlightsGeoJSON: payload.activeLayers.private
@@ -554,6 +572,7 @@ function buildDynamicLayers(payload: DynamicMapLayersBuildPayload): DynamicMapLa
payload.bounds,
payload.dtSeconds,
trackedIcaos,
serverBboxScoped,
)
: null,
privateJetsGeoJSON: payload.activeLayers.jets
@@ -563,6 +582,7 @@ function buildDynamicLayers(payload: DynamicMapLayersBuildPayload): DynamicMapLa
payload.bounds,
payload.dtSeconds,
trackedIcaos,
serverBboxScoped,
)
: null,
militaryFlightsGeoJSON: payload.activeLayers.military
@@ -572,22 +592,29 @@ function buildDynamicLayers(payload: DynamicMapLayersBuildPayload): DynamicMapLa
payload.bounds,
payload.dtSeconds,
trackedIcaos,
serverBboxScoped,
)
: null,
trackedFlightsGeoJSON: payload.activeLayers.tracked
? buildTrackedFlightsGeoJSONWorker(filtered.tracked, payload.bounds, payload.dtSeconds)
? buildTrackedFlightsGeoJSONWorker(
filtered.tracked,
payload.bounds,
payload.dtSeconds,
serverBboxScoped,
)
: null,
shipsGeoJSON: buildShipsGeoJSONWorker(
filtered.ships,
payload.activeLayers,
payload.bounds,
payload.dtSeconds,
serverBboxScoped,
),
meshtasticGeoJSON: payload.activeLayers.sigint_meshtastic
? buildSigintGeoJSONWorker(dynamicData.sigint, 'meshtastic', payload.bounds)
? buildSigintGeoJSONWorker(dynamicData.sigint, 'meshtastic', payload.bounds, serverBboxScoped)
: null,
aprsGeoJSON: payload.activeLayers.sigint_aprs
? buildSigintGeoJSONWorker(dynamicData.sigint, 'aprs', payload.bounds)
? buildSigintGeoJSONWorker(dynamicData.sigint, 'aprs', payload.bounds, serverBboxScoped)
: null,
};
}
+182 -1
View File
@@ -165,8 +165,12 @@ export function buildEarthquakesGeoJSON(earthquakes?: Earthquake[]): FC {
properties: {
id: i,
type: 'earthquake',
name: `[M${eq.mag}]\n${eq.place || 'Unknown Location'}`,
name: `[M${eq.mag}] ${eq.place || 'Unknown Location'}`,
title: eq.title,
lat: eq.lat,
lng: eq.lng,
mag: eq.mag,
place: eq.place,
},
geometry: { type: 'Point' as const, coordinates: [eq.lng, eq.lat] },
};
@@ -1566,6 +1570,183 @@ export function buildCrowdThreatGeoJSON(threats?: CrowdThreatItem[], inView?: In
};
}
// ─── Telegram OSINT ───────────────────────────────────────────────────────
/** Group geoparsed posts by city-level coordinates (~1 km grid). */
export function telegramClusterKey(lat: number, lng: number): string {
return `${lat.toFixed(2)}_${lng.toFixed(2)}`;
}
/** Small fixed shift (~5 mi NE) only when a threat alert shares the same city grid. */
export const TELEGRAM_ALERT_AVOID_METERS = 8_000;
export const TELEGRAM_ALERT_AVOID_BEARING = 45;
/** HTML marker nudge — threat alerts are DOM overlays that cover map canvas dots. */
export const TELEGRAM_MARKER_OFFSET: [number, number] = [28, -24];
export function telegramClusterNearNewsAlert(
lat: number,
lng: number,
news?: Array<{ coords?: [number, number] | null }> | null,
): boolean {
if (!news?.length) return false;
const key = telegramClusterKey(lat, lng);
return news.some((item) => {
const coords = item.coords;
if (!coords || coords.length < 2) return false;
return telegramClusterKey(coords[0], coords[1]) === key;
});
}
export function telegramMapPinCoords(
lat: number,
lng: number,
avoidAlert: boolean,
): [number, number] {
if (!avoidAlert) return [lat, lng];
return projectPoint(lat, lng, TELEGRAM_ALERT_AVOID_BEARING, TELEGRAM_ALERT_AVOID_METERS);
}
export function applyTelegramAlertAvoidance(
geo: FC,
news?: Array<{ coords?: [number, number] | null }> | null,
): FC {
if (!geo?.features?.length) return geo;
return {
...geo,
features: geo.features.map((feature) => {
const geometry = feature.geometry;
if (!geometry || geometry.type !== 'Point') return feature;
const point = geometry.coordinates;
if (!point || point.length < 2) return feature;
const lng = point[0];
const lat = point[1];
const avoid = telegramClusterNearNewsAlert(lat, lng, news);
if (!avoid) return feature;
const [pinLat, pinLng] = telegramMapPinCoords(lat, lng, true);
return {
...feature,
geometry: {
type: 'Point' as const,
coordinates: [pinLng, pinLat],
},
};
}),
};
}
export function buildTelegramOsintGeoJSON(
payload?: {
posts?: Array<{
id: string;
title?: string;
description?: string;
link?: string;
source?: string;
channel?: string;
risk_score?: number;
coords?: [number, number] | null;
}>;
},
inView?: InViewFilter,
): FC {
const posts = payload?.posts;
if (!posts?.length) return null;
const clusters = new Map<
string,
{
lat: number;
lng: number;
posts: NonNullable<typeof posts>;
maxRisk: number;
}
>();
for (const post of posts) {
const coords = post.coords;
if (!coords || coords.length < 2) continue;
const lat = coords[0];
const lng = coords[1];
if (inView && !inView(lat, lng)) continue;
const key = telegramClusterKey(lat, lng);
const bucket = clusters.get(key);
if (bucket) {
bucket.posts.push(post);
bucket.maxRisk = Math.max(bucket.maxRisk, post.risk_score ?? 1);
} else {
clusters.set(key, {
lat,
lng,
posts: [post],
maxRisk: post.risk_score ?? 1,
});
}
}
if (!clusters.size) return null;
return {
type: 'FeatureCollection' as const,
features: Array.from(clusters.entries()).map(([key, cluster]) => {
const lead = cluster.posts[0];
const count = cluster.posts.length;
return {
type: 'Feature' as const,
properties: {
id: key,
type: 'telegram_osint',
name:
count > 1
? `Telegram OSINT (${count} posts)`
: lead.title || 'Telegram OSINT',
description: lead.description || '',
link: lead.link || '',
source: lead.source || '',
channel: lead.channel || '',
risk_score: cluster.maxRisk,
post_count: count,
},
geometry: {
type: 'Point' as const,
coordinates: [cluster.lng, cluster.lat],
},
};
}),
};
}
// ─── Malware C2 / URLhaus ─────────────────────────────────────────────────
export function buildMalwareGeoJSON(
payload?: { threats?: Array<{ id: string; lat: number; lng: number; ip: string; malware: string; threat_type?: string; country?: string }> },
inView?: InViewFilter,
): FC {
const threats = payload?.threats;
if (!threats?.length) return null;
return {
type: 'FeatureCollection' as const,
features: threats
.map((t) => {
if (t.lat == null || t.lng == null) return null;
if (inView && !inView(t.lat, t.lng)) return null;
return {
type: 'Feature' as const,
properties: {
id: t.id,
type: 'malware',
name: t.malware,
ip: t.ip,
threat_type: t.threat_type || 'malware',
country: t.country || '',
},
geometry: { type: 'Point' as const, coordinates: [t.lng, t.lat] },
};
})
.filter(Boolean) as GeoJSON.Feature[],
};
}
// ─── Wastewater colors by alert level ────────────────────────────────────
const WW_COLORS = {
alert: '#ff3333', // red — elevated pathogen detected
@@ -48,6 +48,8 @@ const EMPTY_RESULT: StaticMapLayersResult = {
uapSightingsGeoJSON: null,
wastewaterGeoJSON: null,
crowdthreatGeoJSON: null,
malwareGeoJSON: null,
telegramOsintGeoJSON: null,
};
let worker: Worker | null = null;
@@ -21,6 +21,8 @@ import {
buildUapSightingsGeoJSON,
buildWastewaterGeoJSON,
buildCrowdThreatGeoJSON,
buildMalwareGeoJSON,
buildTelegramOsintGeoJSON,
} from '@/components/map/geoJSONBuilders';
import type {
AirQualityStation,
@@ -44,6 +46,7 @@ import type {
VIIRSChangeNode,
Volcano,
CrowdThreatItem,
MalwareThreat,
} from '@/types/dashboard';
type BoundsTuple = [number, number, number, number];
@@ -71,6 +74,17 @@ export type StaticMapLayersDataPayload = {
uapSightings?: UAPSighting[];
wastewater?: WastewaterPlant[];
crowdthreat?: CrowdThreatItem[];
malwareThreats?: MalwareThreat[];
telegramOsintPosts?: Array<{
id: string;
title?: string;
description?: string;
link?: string;
source?: string;
channel?: string;
risk_score?: number;
coords?: [number, number] | null;
}>;
};
export type StaticMapLayersBuildPayload = {
@@ -95,6 +109,8 @@ export type StaticMapLayersBuildPayload = {
uap_sightings: boolean;
wastewater: boolean;
crowdthreat: boolean;
malware_c2: boolean;
telegram_osint: boolean;
};
};
@@ -119,6 +135,8 @@ export type StaticMapLayersResult = {
uapSightingsGeoJSON: FC;
wastewaterGeoJSON: FC;
crowdthreatGeoJSON: FC;
malwareGeoJSON: FC;
telegramOsintGeoJSON: FC;
};
type SyncRequest = {
@@ -191,6 +209,12 @@ function buildStaticLayers(payload: StaticMapLayersBuildPayload): StaticMapLayer
uapSightingsGeoJSON: payload.activeLayers.uap_sightings ? buildUapSightingsGeoJSON(staticData.uapSightings) : null,
wastewaterGeoJSON: payload.activeLayers.wastewater ? buildWastewaterGeoJSON(staticData.wastewater) : null,
crowdthreatGeoJSON: payload.activeLayers.crowdthreat ? buildCrowdThreatGeoJSON(staticData.crowdthreat, inView) : null,
malwareGeoJSON: payload.activeLayers.malware_c2
? buildMalwareGeoJSON({ threats: staticData.malwareThreats }, inView)
: null,
telegramOsintGeoJSON: payload.activeLayers.telegram_osint
? buildTelegramOsintGeoJSON({ posts: staticData.telegramOsintPosts })
: null,
};
}