mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-05 20:08:00 +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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user