mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-05-12 01:56:22 +02:00
The dev.events RSS feed is limited to 100 items sorted by "date added" (not event date), causing major events like STEP Dubai to be pushed out when newer events are added. Added a curated events list as fallback for important conferences that may fall off the RSS feed: - STEP Dubai 2026 (Feb 11-12) - 8,000+ attendees, AI economy focus - GITEX Global 2026 (Dec 7-11) - World's largest tech show - TOKEN2049 Dubai 2026 (Apr 29-30) - Collision 2026 (Jun 22-25) - Toronto - Web Summit 2026 (Nov 2-5) - Lisbon Curated events are deduplicated with feed data to avoid duplicates.
733 lines
34 KiB
JavaScript
733 lines
34 KiB
JavaScript
// Tech Events API - Parses Techmeme ICS feed and dev.events RSS, returns structured events
|
|
export const config = { runtime: 'edge' };
|
|
|
|
const ICS_URL = 'https://www.techmeme.com/newsy_events.ics';
|
|
const DEV_EVENTS_RSS = 'https://dev.events/rss.xml';
|
|
|
|
// Curated major tech events that may fall off limited RSS feeds
|
|
// These are manually maintained for important conferences
|
|
const CURATED_EVENTS = [
|
|
{
|
|
id: 'step-dubai-2026',
|
|
title: 'STEP Dubai 2026',
|
|
type: 'conference',
|
|
location: 'Dubai Internet City, Dubai',
|
|
coords: { lat: 25.0956, lng: 55.1548, country: 'UAE', original: 'Dubai Internet City, Dubai' },
|
|
startDate: '2026-02-11',
|
|
endDate: '2026-02-12',
|
|
url: 'https://dubai.stepconference.com',
|
|
source: 'curated',
|
|
description: 'Intelligence Everywhere: The AI Economy - 8,000+ attendees, 400+ startups',
|
|
},
|
|
{
|
|
id: 'gitex-global-2026',
|
|
title: 'GITEX Global 2026',
|
|
type: 'conference',
|
|
location: 'Dubai World Trade Centre, Dubai',
|
|
coords: { lat: 25.2285, lng: 55.2867, country: 'UAE', original: 'Dubai World Trade Centre, Dubai' },
|
|
startDate: '2026-12-07',
|
|
endDate: '2026-12-11',
|
|
url: 'https://www.gitex.com',
|
|
source: 'curated',
|
|
description: 'World\'s largest tech & startup show',
|
|
},
|
|
{
|
|
id: 'token2049-dubai-2026',
|
|
title: 'TOKEN2049 Dubai 2026',
|
|
type: 'conference',
|
|
location: 'Dubai, UAE',
|
|
coords: { lat: 25.2048, lng: 55.2708, country: 'UAE', original: 'Dubai, UAE' },
|
|
startDate: '2026-04-29',
|
|
endDate: '2026-04-30',
|
|
url: 'https://www.token2049.com',
|
|
source: 'curated',
|
|
description: 'Premier crypto event in Dubai',
|
|
},
|
|
{
|
|
id: 'collision-2026',
|
|
title: 'Collision 2026',
|
|
type: 'conference',
|
|
location: 'Toronto, Canada',
|
|
coords: { lat: 43.6532, lng: -79.3832, country: 'Canada', original: 'Toronto, Canada' },
|
|
startDate: '2026-06-22',
|
|
endDate: '2026-06-25',
|
|
url: 'https://collisionconf.com',
|
|
source: 'curated',
|
|
description: 'North America\'s fastest growing tech conference',
|
|
},
|
|
{
|
|
id: 'web-summit-2026',
|
|
title: 'Web Summit 2026',
|
|
type: 'conference',
|
|
location: 'Lisbon, Portugal',
|
|
coords: { lat: 38.7223, lng: -9.1393, country: 'Portugal', original: 'Lisbon, Portugal' },
|
|
startDate: '2026-11-02',
|
|
endDate: '2026-11-05',
|
|
url: 'https://websummit.com',
|
|
source: 'curated',
|
|
description: 'The world\'s premier tech conference',
|
|
},
|
|
];
|
|
|
|
// Comprehensive city geocoding database (500+ cities worldwide)
|
|
const CITY_COORDS = {
|
|
// North America - USA
|
|
'san francisco': { lat: 37.7749, lng: -122.4194, country: 'USA' },
|
|
'san jose': { lat: 37.3382, lng: -121.8863, country: 'USA' },
|
|
'palo alto': { lat: 37.4419, lng: -122.1430, country: 'USA' },
|
|
'mountain view': { lat: 37.3861, lng: -122.0839, country: 'USA' },
|
|
'menlo park': { lat: 37.4530, lng: -122.1817, country: 'USA' },
|
|
'cupertino': { lat: 37.3230, lng: -122.0322, country: 'USA' },
|
|
'sunnyvale': { lat: 37.3688, lng: -122.0363, country: 'USA' },
|
|
'santa clara': { lat: 37.3541, lng: -121.9552, country: 'USA' },
|
|
'redwood city': { lat: 37.4852, lng: -122.2364, country: 'USA' },
|
|
'oakland': { lat: 37.8044, lng: -122.2712, country: 'USA' },
|
|
'berkeley': { lat: 37.8716, lng: -122.2727, country: 'USA' },
|
|
'los angeles': { lat: 34.0522, lng: -118.2437, country: 'USA' },
|
|
'santa monica': { lat: 34.0195, lng: -118.4912, country: 'USA' },
|
|
'pasadena': { lat: 34.1478, lng: -118.1445, country: 'USA' },
|
|
'irvine': { lat: 33.6846, lng: -117.8265, country: 'USA' },
|
|
'san diego': { lat: 32.7157, lng: -117.1611, country: 'USA' },
|
|
'seattle': { lat: 47.6062, lng: -122.3321, country: 'USA' },
|
|
'bellevue': { lat: 47.6101, lng: -122.2015, country: 'USA' },
|
|
'redmond': { lat: 47.6740, lng: -122.1215, country: 'USA' },
|
|
'portland': { lat: 45.5155, lng: -122.6789, country: 'USA' },
|
|
'new york': { lat: 40.7128, lng: -74.0060, country: 'USA' },
|
|
'nyc': { lat: 40.7128, lng: -74.0060, country: 'USA' },
|
|
'manhattan': { lat: 40.7831, lng: -73.9712, country: 'USA' },
|
|
'brooklyn': { lat: 40.6782, lng: -73.9442, country: 'USA' },
|
|
'boston': { lat: 42.3601, lng: -71.0589, country: 'USA' },
|
|
'cambridge': { lat: 42.3736, lng: -71.1097, country: 'USA' },
|
|
'chicago': { lat: 41.8781, lng: -87.6298, country: 'USA' },
|
|
'austin': { lat: 30.2672, lng: -97.7431, country: 'USA' },
|
|
'austin, tx': { lat: 30.2672, lng: -97.7431, country: 'USA' },
|
|
'dallas': { lat: 32.7767, lng: -96.7970, country: 'USA' },
|
|
'houston': { lat: 29.7604, lng: -95.3698, country: 'USA' },
|
|
'denver': { lat: 39.7392, lng: -104.9903, country: 'USA' },
|
|
'boulder': { lat: 40.0150, lng: -105.2705, country: 'USA' },
|
|
'phoenix': { lat: 33.4484, lng: -112.0740, country: 'USA' },
|
|
'scottsdale': { lat: 33.4942, lng: -111.9261, country: 'USA' },
|
|
'miami': { lat: 25.7617, lng: -80.1918, country: 'USA' },
|
|
'orlando': { lat: 28.5383, lng: -81.3792, country: 'USA' },
|
|
'tampa': { lat: 27.9506, lng: -82.4572, country: 'USA' },
|
|
'atlanta': { lat: 33.7490, lng: -84.3880, country: 'USA' },
|
|
'washington': { lat: 38.9072, lng: -77.0369, country: 'USA' },
|
|
'washington dc': { lat: 38.9072, lng: -77.0369, country: 'USA' },
|
|
'washington, dc': { lat: 38.9072, lng: -77.0369, country: 'USA' },
|
|
'dc': { lat: 38.9072, lng: -77.0369, country: 'USA' },
|
|
'reston': { lat: 38.9586, lng: -77.3570, country: 'USA' },
|
|
'philadelphia': { lat: 39.9526, lng: -75.1652, country: 'USA' },
|
|
'pittsburgh': { lat: 40.4406, lng: -79.9959, country: 'USA' },
|
|
'detroit': { lat: 42.3314, lng: -83.0458, country: 'USA' },
|
|
'ann arbor': { lat: 42.2808, lng: -83.7430, country: 'USA' },
|
|
'minneapolis': { lat: 44.9778, lng: -93.2650, country: 'USA' },
|
|
'salt lake city': { lat: 40.7608, lng: -111.8910, country: 'USA' },
|
|
'las vegas': { lat: 36.1699, lng: -115.1398, country: 'USA' },
|
|
'raleigh': { lat: 35.7796, lng: -78.6382, country: 'USA' },
|
|
'durham': { lat: 35.9940, lng: -78.8986, country: 'USA' },
|
|
'chapel hill': { lat: 35.9132, lng: -79.0558, country: 'USA' },
|
|
'charlotte': { lat: 35.2271, lng: -80.8431, country: 'USA' },
|
|
'nashville': { lat: 36.1627, lng: -86.7816, country: 'USA' },
|
|
'indianapolis': { lat: 39.7684, lng: -86.1581, country: 'USA' },
|
|
'columbus': { lat: 39.9612, lng: -82.9988, country: 'USA' },
|
|
'cleveland': { lat: 41.4993, lng: -81.6944, country: 'USA' },
|
|
'cincinnati': { lat: 39.1031, lng: -84.5120, country: 'USA' },
|
|
'st. louis': { lat: 38.6270, lng: -90.1994, country: 'USA' },
|
|
'kansas city': { lat: 39.0997, lng: -94.5786, country: 'USA' },
|
|
'omaha': { lat: 41.2565, lng: -95.9345, country: 'USA' },
|
|
'milwaukee': { lat: 43.0389, lng: -87.9065, country: 'USA' },
|
|
'new orleans': { lat: 29.9511, lng: -90.0715, country: 'USA' },
|
|
'san antonio': { lat: 29.4241, lng: -98.4936, country: 'USA' },
|
|
'albuquerque': { lat: 35.0844, lng: -106.6504, country: 'USA' },
|
|
'tucson': { lat: 32.2226, lng: -110.9747, country: 'USA' },
|
|
'honolulu': { lat: 21.3069, lng: -157.8583, country: 'USA' },
|
|
'anchorage': { lat: 61.2181, lng: -149.9003, country: 'USA' },
|
|
|
|
// North America - Canada
|
|
'toronto': { lat: 43.6532, lng: -79.3832, country: 'Canada' },
|
|
'vancouver': { lat: 49.2827, lng: -123.1207, country: 'Canada' },
|
|
'montreal': { lat: 45.5017, lng: -73.5673, country: 'Canada' },
|
|
'ottawa': { lat: 45.4215, lng: -75.6972, country: 'Canada' },
|
|
'calgary': { lat: 51.0447, lng: -114.0719, country: 'Canada' },
|
|
'edmonton': { lat: 53.5461, lng: -113.4938, country: 'Canada' },
|
|
'winnipeg': { lat: 49.8951, lng: -97.1384, country: 'Canada' },
|
|
'quebec city': { lat: 46.8139, lng: -71.2080, country: 'Canada' },
|
|
'waterloo': { lat: 43.4643, lng: -80.5204, country: 'Canada' },
|
|
'victoria': { lat: 48.4284, lng: -123.3656, country: 'Canada' },
|
|
'halifax': { lat: 44.6488, lng: -63.5752, country: 'Canada' },
|
|
|
|
// Mexico & Central America
|
|
'mexico city': { lat: 19.4326, lng: -99.1332, country: 'Mexico' },
|
|
'guadalajara': { lat: 20.6597, lng: -103.3496, country: 'Mexico' },
|
|
'monterrey': { lat: 25.6866, lng: -100.3161, country: 'Mexico' },
|
|
'tijuana': { lat: 32.5149, lng: -117.0382, country: 'Mexico' },
|
|
'cancun': { lat: 21.1619, lng: -86.8515, country: 'Mexico' },
|
|
'panama city': { lat: 8.9824, lng: -79.5199, country: 'Panama' },
|
|
'san jose': { lat: 9.9281, lng: -84.0907, country: 'Costa Rica' },
|
|
|
|
// South America
|
|
'sao paulo': { lat: -23.5505, lng: -46.6333, country: 'Brazil' },
|
|
'são paulo': { lat: -23.5505, lng: -46.6333, country: 'Brazil' },
|
|
'rio de janeiro': { lat: -22.9068, lng: -43.1729, country: 'Brazil' },
|
|
'brasilia': { lat: -15.7975, lng: -47.8919, country: 'Brazil' },
|
|
'belo horizonte': { lat: -19.9167, lng: -43.9345, country: 'Brazil' },
|
|
'porto alegre': { lat: -30.0346, lng: -51.2177, country: 'Brazil' },
|
|
'buenos aires': { lat: -34.6037, lng: -58.3816, country: 'Argentina' },
|
|
'santiago': { lat: -33.4489, lng: -70.6693, country: 'Chile' },
|
|
'bogota': { lat: 4.7110, lng: -74.0721, country: 'Colombia' },
|
|
'bogotá': { lat: 4.7110, lng: -74.0721, country: 'Colombia' },
|
|
'medellin': { lat: 6.2476, lng: -75.5658, country: 'Colombia' },
|
|
'medellín': { lat: 6.2476, lng: -75.5658, country: 'Colombia' },
|
|
'lima': { lat: -12.0464, lng: -77.0428, country: 'Peru' },
|
|
'caracas': { lat: 10.4806, lng: -66.9036, country: 'Venezuela' },
|
|
'montevideo': { lat: -34.9011, lng: -56.1645, country: 'Uruguay' },
|
|
'quito': { lat: -0.1807, lng: -78.4678, country: 'Ecuador' },
|
|
|
|
// Europe - UK & Ireland
|
|
'london': { lat: 51.5074, lng: -0.1278, country: 'UK' },
|
|
'cambridge': { lat: 52.2053, lng: 0.1218, country: 'UK' },
|
|
'oxford': { lat: 51.7520, lng: -1.2577, country: 'UK' },
|
|
'manchester': { lat: 53.4808, lng: -2.2426, country: 'UK' },
|
|
'birmingham': { lat: 52.4862, lng: -1.8904, country: 'UK' },
|
|
'edinburgh': { lat: 55.9533, lng: -3.1883, country: 'UK' },
|
|
'glasgow': { lat: 55.8642, lng: -4.2518, country: 'UK' },
|
|
'bristol': { lat: 51.4545, lng: -2.5879, country: 'UK' },
|
|
'leeds': { lat: 53.8008, lng: -1.5491, country: 'UK' },
|
|
'liverpool': { lat: 53.4084, lng: -2.9916, country: 'UK' },
|
|
'belfast': { lat: 54.5973, lng: -5.9301, country: 'UK' },
|
|
'cardiff': { lat: 51.4816, lng: -3.1791, country: 'UK' },
|
|
'dublin': { lat: 53.3498, lng: -6.2603, country: 'Ireland' },
|
|
'cork': { lat: 51.8985, lng: -8.4756, country: 'Ireland' },
|
|
'galway': { lat: 53.2707, lng: -9.0568, country: 'Ireland' },
|
|
|
|
// Europe - Western
|
|
'paris': { lat: 48.8566, lng: 2.3522, country: 'France' },
|
|
'lyon': { lat: 45.7640, lng: 4.8357, country: 'France' },
|
|
'marseille': { lat: 43.2965, lng: 5.3698, country: 'France' },
|
|
'toulouse': { lat: 43.6047, lng: 1.4442, country: 'France' },
|
|
'nice': { lat: 43.7102, lng: 7.2620, country: 'France' },
|
|
'bordeaux': { lat: 44.8378, lng: -0.5792, country: 'France' },
|
|
'strasbourg': { lat: 48.5734, lng: 7.7521, country: 'France' },
|
|
'nantes': { lat: 47.2184, lng: -1.5536, country: 'France' },
|
|
'cannes': { lat: 43.5528, lng: 7.0174, country: 'France' },
|
|
'monaco': { lat: 43.7384, lng: 7.4246, country: 'Monaco' },
|
|
'berlin': { lat: 52.5200, lng: 13.4050, country: 'Germany' },
|
|
'munich': { lat: 48.1351, lng: 11.5820, country: 'Germany' },
|
|
'münchen': { lat: 48.1351, lng: 11.5820, country: 'Germany' },
|
|
'frankfurt': { lat: 50.1109, lng: 8.6821, country: 'Germany' },
|
|
'hamburg': { lat: 53.5511, lng: 9.9937, country: 'Germany' },
|
|
'cologne': { lat: 50.9375, lng: 6.9603, country: 'Germany' },
|
|
'köln': { lat: 50.9375, lng: 6.9603, country: 'Germany' },
|
|
'düsseldorf': { lat: 51.2277, lng: 6.7735, country: 'Germany' },
|
|
'dusseldorf': { lat: 51.2277, lng: 6.7735, country: 'Germany' },
|
|
'stuttgart': { lat: 48.7758, lng: 9.1829, country: 'Germany' },
|
|
'hanover': { lat: 52.3759, lng: 9.7320, country: 'Germany' },
|
|
'hannover': { lat: 52.3759, lng: 9.7320, country: 'Germany' },
|
|
'dresden': { lat: 51.0504, lng: 13.7373, country: 'Germany' },
|
|
'leipzig': { lat: 51.3397, lng: 12.3731, country: 'Germany' },
|
|
'nuremberg': { lat: 49.4521, lng: 11.0767, country: 'Germany' },
|
|
'amsterdam': { lat: 52.3676, lng: 4.9041, country: 'Netherlands' },
|
|
'rotterdam': { lat: 51.9225, lng: 4.4792, country: 'Netherlands' },
|
|
'the hague': { lat: 52.0705, lng: 4.3007, country: 'Netherlands' },
|
|
'eindhoven': { lat: 51.4416, lng: 5.4697, country: 'Netherlands' },
|
|
'utrecht': { lat: 52.0907, lng: 5.1214, country: 'Netherlands' },
|
|
'brussels': { lat: 50.8503, lng: 4.3517, country: 'Belgium' },
|
|
'antwerp': { lat: 51.2194, lng: 4.4025, country: 'Belgium' },
|
|
'ghent': { lat: 51.0543, lng: 3.7174, country: 'Belgium' },
|
|
'luxembourg': { lat: 49.6116, lng: 6.1319, country: 'Luxembourg' },
|
|
'zurich': { lat: 47.3769, lng: 8.5417, country: 'Switzerland' },
|
|
'zürich': { lat: 47.3769, lng: 8.5417, country: 'Switzerland' },
|
|
'geneva': { lat: 46.2044, lng: 6.1432, country: 'Switzerland' },
|
|
'genève': { lat: 46.2044, lng: 6.1432, country: 'Switzerland' },
|
|
'basel': { lat: 47.5596, lng: 7.5886, country: 'Switzerland' },
|
|
'bern': { lat: 46.9480, lng: 7.4474, country: 'Switzerland' },
|
|
'lausanne': { lat: 46.5197, lng: 6.6323, country: 'Switzerland' },
|
|
'davos': { lat: 46.8027, lng: 9.8360, country: 'Switzerland' },
|
|
'vienna': { lat: 48.2082, lng: 16.3738, country: 'Austria' },
|
|
'wien': { lat: 48.2082, lng: 16.3738, country: 'Austria' },
|
|
'salzburg': { lat: 47.8095, lng: 13.0550, country: 'Austria' },
|
|
'graz': { lat: 47.0707, lng: 15.4395, country: 'Austria' },
|
|
'innsbruck': { lat: 47.2692, lng: 11.4041, country: 'Austria' },
|
|
|
|
// Europe - Southern
|
|
'barcelona': { lat: 41.3851, lng: 2.1734, country: 'Spain' },
|
|
'madrid': { lat: 40.4168, lng: -3.7038, country: 'Spain' },
|
|
'valencia': { lat: 39.4699, lng: -0.3763, country: 'Spain' },
|
|
'seville': { lat: 37.3891, lng: -5.9845, country: 'Spain' },
|
|
'sevilla': { lat: 37.3891, lng: -5.9845, country: 'Spain' },
|
|
'malaga': { lat: 36.7213, lng: -4.4214, country: 'Spain' },
|
|
'málaga': { lat: 36.7213, lng: -4.4214, country: 'Spain' },
|
|
'bilbao': { lat: 43.2630, lng: -2.9350, country: 'Spain' },
|
|
'lisbon': { lat: 38.7223, lng: -9.1393, country: 'Portugal' },
|
|
'lisboa': { lat: 38.7223, lng: -9.1393, country: 'Portugal' },
|
|
'porto': { lat: 41.1579, lng: -8.6291, country: 'Portugal' },
|
|
'rome': { lat: 41.9028, lng: 12.4964, country: 'Italy' },
|
|
'roma': { lat: 41.9028, lng: 12.4964, country: 'Italy' },
|
|
'milan': { lat: 45.4642, lng: 9.1900, country: 'Italy' },
|
|
'milano': { lat: 45.4642, lng: 9.1900, country: 'Italy' },
|
|
'florence': { lat: 43.7696, lng: 11.2558, country: 'Italy' },
|
|
'firenze': { lat: 43.7696, lng: 11.2558, country: 'Italy' },
|
|
'venice': { lat: 45.4408, lng: 12.3155, country: 'Italy' },
|
|
'venezia': { lat: 45.4408, lng: 12.3155, country: 'Italy' },
|
|
'turin': { lat: 45.0703, lng: 7.6869, country: 'Italy' },
|
|
'torino': { lat: 45.0703, lng: 7.6869, country: 'Italy' },
|
|
'naples': { lat: 40.8518, lng: 14.2681, country: 'Italy' },
|
|
'napoli': { lat: 40.8518, lng: 14.2681, country: 'Italy' },
|
|
'bologna': { lat: 44.4949, lng: 11.3426, country: 'Italy' },
|
|
'athens': { lat: 37.9838, lng: 23.7275, country: 'Greece' },
|
|
'thessaloniki': { lat: 40.6401, lng: 22.9444, country: 'Greece' },
|
|
'malta': { lat: 35.8989, lng: 14.5146, country: 'Malta' },
|
|
'valletta': { lat: 35.8989, lng: 14.5146, country: 'Malta' },
|
|
|
|
// Europe - Northern
|
|
'stockholm': { lat: 59.3293, lng: 18.0686, country: 'Sweden' },
|
|
'gothenburg': { lat: 57.7089, lng: 11.9746, country: 'Sweden' },
|
|
'göteborg': { lat: 57.7089, lng: 11.9746, country: 'Sweden' },
|
|
'malmö': { lat: 55.6050, lng: 13.0038, country: 'Sweden' },
|
|
'malmo': { lat: 55.6050, lng: 13.0038, country: 'Sweden' },
|
|
'copenhagen': { lat: 55.6761, lng: 12.5683, country: 'Denmark' },
|
|
'københavn': { lat: 55.6761, lng: 12.5683, country: 'Denmark' },
|
|
'aarhus': { lat: 56.1629, lng: 10.2039, country: 'Denmark' },
|
|
'oslo': { lat: 59.9139, lng: 10.7522, country: 'Norway' },
|
|
'bergen': { lat: 60.3913, lng: 5.3221, country: 'Norway' },
|
|
'helsinki': { lat: 60.1699, lng: 24.9384, country: 'Finland' },
|
|
'espoo': { lat: 60.2055, lng: 24.6559, country: 'Finland' },
|
|
'tampere': { lat: 61.4978, lng: 23.7610, country: 'Finland' },
|
|
'reykjavik': { lat: 64.1466, lng: -21.9426, country: 'Iceland' },
|
|
|
|
// Europe - Eastern
|
|
'warsaw': { lat: 52.2297, lng: 21.0122, country: 'Poland' },
|
|
'warszawa': { lat: 52.2297, lng: 21.0122, country: 'Poland' },
|
|
'krakow': { lat: 50.0647, lng: 19.9450, country: 'Poland' },
|
|
'kraków': { lat: 50.0647, lng: 19.9450, country: 'Poland' },
|
|
'wroclaw': { lat: 51.1079, lng: 17.0385, country: 'Poland' },
|
|
'wrocław': { lat: 51.1079, lng: 17.0385, country: 'Poland' },
|
|
'gdansk': { lat: 54.3520, lng: 18.6466, country: 'Poland' },
|
|
'prague': { lat: 50.0755, lng: 14.4378, country: 'Czech Republic' },
|
|
'praha': { lat: 50.0755, lng: 14.4378, country: 'Czech Republic' },
|
|
'brno': { lat: 49.1951, lng: 16.6068, country: 'Czech Republic' },
|
|
'budapest': { lat: 47.4979, lng: 19.0402, country: 'Hungary' },
|
|
'bucharest': { lat: 44.4268, lng: 26.1025, country: 'Romania' },
|
|
'bucurești': { lat: 44.4268, lng: 26.1025, country: 'Romania' },
|
|
'cluj-napoca': { lat: 46.7712, lng: 23.6236, country: 'Romania' },
|
|
'sofia': { lat: 42.6977, lng: 23.3219, country: 'Bulgaria' },
|
|
'belgrade': { lat: 44.7866, lng: 20.4489, country: 'Serbia' },
|
|
'beograd': { lat: 44.7866, lng: 20.4489, country: 'Serbia' },
|
|
'zagreb': { lat: 45.8150, lng: 15.9819, country: 'Croatia' },
|
|
'ljubljana': { lat: 46.0569, lng: 14.5058, country: 'Slovenia' },
|
|
'bratislava': { lat: 48.1486, lng: 17.1077, country: 'Slovakia' },
|
|
'tallinn': { lat: 59.4370, lng: 24.7536, country: 'Estonia' },
|
|
'riga': { lat: 56.9496, lng: 24.1052, country: 'Latvia' },
|
|
'vilnius': { lat: 54.6872, lng: 25.2797, country: 'Lithuania' },
|
|
'kyiv': { lat: 50.4501, lng: 30.5234, country: 'Ukraine' },
|
|
'kiev': { lat: 50.4501, lng: 30.5234, country: 'Ukraine' },
|
|
'lviv': { lat: 49.8397, lng: 24.0297, country: 'Ukraine' },
|
|
'minsk': { lat: 53.9045, lng: 27.5615, country: 'Belarus' },
|
|
'moscow': { lat: 55.7558, lng: 37.6173, country: 'Russia' },
|
|
'st. petersburg': { lat: 59.9311, lng: 30.3609, country: 'Russia' },
|
|
'saint petersburg': { lat: 59.9311, lng: 30.3609, country: 'Russia' },
|
|
|
|
// Middle East
|
|
'dubai': { lat: 25.2048, lng: 55.2708, country: 'UAE' },
|
|
'abu dhabi': { lat: 24.4539, lng: 54.3773, country: 'UAE' },
|
|
'doha': { lat: 25.2854, lng: 51.5310, country: 'Qatar' },
|
|
'riyadh': { lat: 24.7136, lng: 46.6753, country: 'Saudi Arabia' },
|
|
'jeddah': { lat: 21.4858, lng: 39.1925, country: 'Saudi Arabia' },
|
|
'neom': { lat: 28.0000, lng: 35.0000, country: 'Saudi Arabia' },
|
|
'tel aviv': { lat: 32.0853, lng: 34.7818, country: 'Israel' },
|
|
'jerusalem': { lat: 31.7683, lng: 35.2137, country: 'Israel' },
|
|
'haifa': { lat: 32.7940, lng: 34.9896, country: 'Israel' },
|
|
'amman': { lat: 31.9454, lng: 35.9284, country: 'Jordan' },
|
|
'beirut': { lat: 33.8938, lng: 35.5018, country: 'Lebanon' },
|
|
'istanbul': { lat: 41.0082, lng: 28.9784, country: 'Turkey' },
|
|
'ankara': { lat: 39.9334, lng: 32.8597, country: 'Turkey' },
|
|
'izmir': { lat: 38.4237, lng: 27.1428, country: 'Turkey' },
|
|
'tehran': { lat: 35.6892, lng: 51.3890, country: 'Iran' },
|
|
'cairo': { lat: 30.0444, lng: 31.2357, country: 'Egypt' },
|
|
'muscat': { lat: 23.5880, lng: 58.3829, country: 'Oman' },
|
|
'manama': { lat: 26.2285, lng: 50.5860, country: 'Bahrain' },
|
|
'kuwait city': { lat: 29.3759, lng: 47.9774, country: 'Kuwait' },
|
|
|
|
// Asia - East
|
|
'tokyo': { lat: 35.6762, lng: 139.6503, country: 'Japan' },
|
|
'osaka': { lat: 34.6937, lng: 135.5023, country: 'Japan' },
|
|
'kyoto': { lat: 35.0116, lng: 135.7681, country: 'Japan' },
|
|
'yokohama': { lat: 35.4437, lng: 139.6380, country: 'Japan' },
|
|
'nagoya': { lat: 35.1815, lng: 136.9066, country: 'Japan' },
|
|
'fukuoka': { lat: 33.5904, lng: 130.4017, country: 'Japan' },
|
|
'sapporo': { lat: 43.0618, lng: 141.3545, country: 'Japan' },
|
|
'kobe': { lat: 34.6901, lng: 135.1956, country: 'Japan' },
|
|
'seoul': { lat: 37.5665, lng: 126.9780, country: 'South Korea' },
|
|
'busan': { lat: 35.1796, lng: 129.0756, country: 'South Korea' },
|
|
'incheon': { lat: 37.4563, lng: 126.7052, country: 'South Korea' },
|
|
'beijing': { lat: 39.9042, lng: 116.4074, country: 'China' },
|
|
'shanghai': { lat: 31.2304, lng: 121.4737, country: 'China' },
|
|
'shenzhen': { lat: 22.5431, lng: 114.0579, country: 'China' },
|
|
'guangzhou': { lat: 23.1291, lng: 113.2644, country: 'China' },
|
|
'hong kong': { lat: 22.3193, lng: 114.1694, country: 'Hong Kong' },
|
|
'hangzhou': { lat: 30.2741, lng: 120.1551, country: 'China' },
|
|
'chengdu': { lat: 30.5728, lng: 104.0668, country: 'China' },
|
|
'xian': { lat: 34.3416, lng: 108.9398, country: 'China' },
|
|
"xi'an": { lat: 34.3416, lng: 108.9398, country: 'China' },
|
|
'nanjing': { lat: 32.0603, lng: 118.7969, country: 'China' },
|
|
'wuhan': { lat: 30.5928, lng: 114.3055, country: 'China' },
|
|
'tianjin': { lat: 39.3434, lng: 117.3616, country: 'China' },
|
|
'suzhou': { lat: 31.2990, lng: 120.5853, country: 'China' },
|
|
'taipei': { lat: 25.0330, lng: 121.5654, country: 'Taiwan' },
|
|
'kaohsiung': { lat: 22.6273, lng: 120.3014, country: 'Taiwan' },
|
|
'macau': { lat: 22.1987, lng: 113.5439, country: 'Macau' },
|
|
'macao': { lat: 22.1987, lng: 113.5439, country: 'Macau' },
|
|
|
|
// Asia - Southeast
|
|
'singapore': { lat: 1.3521, lng: 103.8198, country: 'Singapore' },
|
|
'kuala lumpur': { lat: 3.1390, lng: 101.6869, country: 'Malaysia' },
|
|
'penang': { lat: 5.4141, lng: 100.3288, country: 'Malaysia' },
|
|
'jakarta': { lat: -6.2088, lng: 106.8456, country: 'Indonesia' },
|
|
'bali': { lat: -8.3405, lng: 115.0920, country: 'Indonesia' },
|
|
'denpasar': { lat: -8.6705, lng: 115.2126, country: 'Indonesia' },
|
|
'bandung': { lat: -6.9175, lng: 107.6191, country: 'Indonesia' },
|
|
'surabaya': { lat: -7.2575, lng: 112.7521, country: 'Indonesia' },
|
|
'bangkok': { lat: 13.7563, lng: 100.5018, country: 'Thailand' },
|
|
'chiang mai': { lat: 18.7883, lng: 98.9853, country: 'Thailand' },
|
|
'phuket': { lat: 7.8804, lng: 98.3923, country: 'Thailand' },
|
|
'ho chi minh city': { lat: 10.8231, lng: 106.6297, country: 'Vietnam' },
|
|
'saigon': { lat: 10.8231, lng: 106.6297, country: 'Vietnam' },
|
|
'hanoi': { lat: 21.0278, lng: 105.8342, country: 'Vietnam' },
|
|
'da nang': { lat: 16.0544, lng: 108.2022, country: 'Vietnam' },
|
|
'manila': { lat: 14.5995, lng: 120.9842, country: 'Philippines' },
|
|
'cebu': { lat: 10.3157, lng: 123.8854, country: 'Philippines' },
|
|
'phnom penh': { lat: 11.5564, lng: 104.9282, country: 'Cambodia' },
|
|
'yangon': { lat: 16.8661, lng: 96.1951, country: 'Myanmar' },
|
|
|
|
// Asia - South
|
|
'mumbai': { lat: 19.0760, lng: 72.8777, country: 'India' },
|
|
'bombay': { lat: 19.0760, lng: 72.8777, country: 'India' },
|
|
'delhi': { lat: 28.7041, lng: 77.1025, country: 'India' },
|
|
'new delhi': { lat: 28.6139, lng: 77.2090, country: 'India' },
|
|
'bangalore': { lat: 12.9716, lng: 77.5946, country: 'India' },
|
|
'bengaluru': { lat: 12.9716, lng: 77.5946, country: 'India' },
|
|
'hyderabad': { lat: 17.3850, lng: 78.4867, country: 'India' },
|
|
'chennai': { lat: 13.0827, lng: 80.2707, country: 'India' },
|
|
'madras': { lat: 13.0827, lng: 80.2707, country: 'India' },
|
|
'pune': { lat: 18.5204, lng: 73.8567, country: 'India' },
|
|
'kolkata': { lat: 22.5726, lng: 88.3639, country: 'India' },
|
|
'calcutta': { lat: 22.5726, lng: 88.3639, country: 'India' },
|
|
'ahmedabad': { lat: 23.0225, lng: 72.5714, country: 'India' },
|
|
'jaipur': { lat: 26.9124, lng: 75.7873, country: 'India' },
|
|
'gurgaon': { lat: 28.4595, lng: 77.0266, country: 'India' },
|
|
'gurugram': { lat: 28.4595, lng: 77.0266, country: 'India' },
|
|
'noida': { lat: 28.5355, lng: 77.3910, country: 'India' },
|
|
'kochi': { lat: 9.9312, lng: 76.2673, country: 'India' },
|
|
'goa': { lat: 15.2993, lng: 74.1240, country: 'India' },
|
|
'karachi': { lat: 24.8607, lng: 67.0011, country: 'Pakistan' },
|
|
'lahore': { lat: 31.5497, lng: 74.3436, country: 'Pakistan' },
|
|
'islamabad': { lat: 33.6844, lng: 73.0479, country: 'Pakistan' },
|
|
'dhaka': { lat: 23.8103, lng: 90.4125, country: 'Bangladesh' },
|
|
'colombo': { lat: 6.9271, lng: 79.8612, country: 'Sri Lanka' },
|
|
'kathmandu': { lat: 27.7172, lng: 85.3240, country: 'Nepal' },
|
|
|
|
// Africa
|
|
'cape town': { lat: -33.9249, lng: 18.4241, country: 'South Africa' },
|
|
'johannesburg': { lat: -26.2041, lng: 28.0473, country: 'South Africa' },
|
|
'pretoria': { lat: -25.7479, lng: 28.2293, country: 'South Africa' },
|
|
'durban': { lat: -29.8587, lng: 31.0218, country: 'South Africa' },
|
|
'lagos': { lat: 6.5244, lng: 3.3792, country: 'Nigeria' },
|
|
'abuja': { lat: 9.0765, lng: 7.3986, country: 'Nigeria' },
|
|
'nairobi': { lat: -1.2921, lng: 36.8219, country: 'Kenya' },
|
|
'accra': { lat: 5.6037, lng: -0.1870, country: 'Ghana' },
|
|
'casablanca': { lat: 33.5731, lng: -7.5898, country: 'Morocco' },
|
|
'marrakech': { lat: 31.6295, lng: -7.9811, country: 'Morocco' },
|
|
'tunis': { lat: 36.8065, lng: 10.1815, country: 'Tunisia' },
|
|
'algiers': { lat: 36.7538, lng: 3.0588, country: 'Algeria' },
|
|
'addis ababa': { lat: 8.9806, lng: 38.7578, country: 'Ethiopia' },
|
|
'dar es salaam': { lat: -6.7924, lng: 39.2083, country: 'Tanzania' },
|
|
'kampala': { lat: 0.3476, lng: 32.5825, country: 'Uganda' },
|
|
'kigali': { lat: -1.9403, lng: 29.8739, country: 'Rwanda' },
|
|
'mauritius': { lat: -20.3484, lng: 57.5522, country: 'Mauritius' },
|
|
'port louis': { lat: -20.1609, lng: 57.5012, country: 'Mauritius' },
|
|
|
|
// Oceania
|
|
'sydney': { lat: -33.8688, lng: 151.2093, country: 'Australia' },
|
|
'melbourne': { lat: -37.8136, lng: 144.9631, country: 'Australia' },
|
|
'brisbane': { lat: -27.4698, lng: 153.0251, country: 'Australia' },
|
|
'perth': { lat: -31.9505, lng: 115.8605, country: 'Australia' },
|
|
'adelaide': { lat: -34.9285, lng: 138.6007, country: 'Australia' },
|
|
'canberra': { lat: -35.2809, lng: 149.1300, country: 'Australia' },
|
|
'gold coast': { lat: -28.0167, lng: 153.4000, country: 'Australia' },
|
|
'auckland': { lat: -36.8509, lng: 174.7645, country: 'New Zealand' },
|
|
'wellington': { lat: -41.2865, lng: 174.7762, country: 'New Zealand' },
|
|
'christchurch': { lat: -43.5321, lng: 172.6362, country: 'New Zealand' },
|
|
|
|
// Online/Virtual
|
|
'online': { lat: 0, lng: 0, country: 'Virtual', virtual: true },
|
|
'virtual': { lat: 0, lng: 0, country: 'Virtual', virtual: true },
|
|
'hybrid': { lat: 0, lng: 0, country: 'Virtual', virtual: true },
|
|
};
|
|
|
|
function normalizeLocation(location) {
|
|
if (!location) return null;
|
|
|
|
// Clean up the location string
|
|
let normalized = location.toLowerCase().trim();
|
|
|
|
// Remove common suffixes/prefixes
|
|
normalized = normalized.replace(/^hybrid:\s*/i, '');
|
|
normalized = normalized.replace(/,\s*(usa|us|uk|canada)$/i, '');
|
|
|
|
// Direct lookup
|
|
if (CITY_COORDS[normalized]) {
|
|
return { ...CITY_COORDS[normalized], original: location };
|
|
}
|
|
|
|
// Try removing state/country suffix
|
|
const parts = normalized.split(',');
|
|
if (parts.length > 1) {
|
|
const city = parts[0].trim();
|
|
if (CITY_COORDS[city]) {
|
|
return { ...CITY_COORDS[city], original: location };
|
|
}
|
|
}
|
|
|
|
// Try fuzzy match (contains)
|
|
for (const [key, coords] of Object.entries(CITY_COORDS)) {
|
|
if (normalized.includes(key) || key.includes(normalized)) {
|
|
return { ...coords, original: location };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function parseICS(icsText) {
|
|
const events = [];
|
|
const eventBlocks = icsText.split('BEGIN:VEVENT').slice(1);
|
|
|
|
for (const block of eventBlocks) {
|
|
const summaryMatch = block.match(/SUMMARY:(.+)/);
|
|
const locationMatch = block.match(/LOCATION:(.+)/);
|
|
const dtstartMatch = block.match(/DTSTART;VALUE=DATE:(\d+)/);
|
|
const dtendMatch = block.match(/DTEND;VALUE=DATE:(\d+)/);
|
|
const urlMatch = block.match(/URL:(.+)/);
|
|
const uidMatch = block.match(/UID:(.+)/);
|
|
|
|
if (summaryMatch && dtstartMatch) {
|
|
const summary = summaryMatch[1].trim();
|
|
const location = locationMatch ? locationMatch[1].trim() : null;
|
|
const startDate = dtstartMatch[1];
|
|
const endDate = dtendMatch ? dtendMatch[1] : startDate;
|
|
const url = urlMatch ? urlMatch[1].trim() : null;
|
|
const uid = uidMatch ? uidMatch[1].trim() : null;
|
|
|
|
// Determine event type
|
|
let type = 'other';
|
|
if (summary.startsWith('Earnings:')) type = 'earnings';
|
|
else if (summary.startsWith('IPO')) type = 'ipo';
|
|
else if (location) type = 'conference';
|
|
|
|
// Parse coordinates if location exists
|
|
const coords = normalizeLocation(location);
|
|
|
|
events.push({
|
|
id: uid,
|
|
title: summary,
|
|
type,
|
|
location: location,
|
|
coords: coords,
|
|
startDate: `${startDate.slice(0, 4)}-${startDate.slice(4, 6)}-${startDate.slice(6, 8)}`,
|
|
endDate: `${endDate.slice(0, 4)}-${endDate.slice(4, 6)}-${endDate.slice(6, 8)}`,
|
|
url: url,
|
|
source: 'techmeme',
|
|
});
|
|
}
|
|
}
|
|
|
|
return events.sort((a, b) => a.startDate.localeCompare(b.startDate));
|
|
}
|
|
|
|
function parseDevEventsRSS(rssText) {
|
|
const events = [];
|
|
|
|
// Simple regex-based RSS parsing for edge runtime
|
|
const itemMatches = rssText.matchAll(/<item>([\s\S]*?)<\/item>/g);
|
|
|
|
for (const match of itemMatches) {
|
|
const item = match[1];
|
|
|
|
const titleMatch = item.match(/<title><!\[CDATA\[(.*?)\]\]><\/title>|<title>(.*?)<\/title>/);
|
|
const linkMatch = item.match(/<link>(.*?)<\/link>/);
|
|
const descMatch = item.match(/<description><!\[CDATA\[(.*?)\]\]><\/description>|<description>(.*?)<\/description>/s);
|
|
const guidMatch = item.match(/<guid[^>]*>(.*?)<\/guid>/);
|
|
|
|
const title = titleMatch ? (titleMatch[1] || titleMatch[2]) : null;
|
|
const link = linkMatch ? linkMatch[1] : null;
|
|
const description = descMatch ? (descMatch[1] || descMatch[2]) : '';
|
|
const guid = guidMatch ? guidMatch[1] : null;
|
|
|
|
if (!title) continue;
|
|
|
|
// Parse date from description: "EventName is happening on Month Day, Year"
|
|
const dateMatch = description.match(/on\s+(\w+\s+\d{1,2},?\s+\d{4})/i);
|
|
let startDate = null;
|
|
if (dateMatch) {
|
|
const parsed = new Date(dateMatch[1]);
|
|
if (!isNaN(parsed.getTime())) {
|
|
startDate = parsed.toISOString().split('T')[0];
|
|
}
|
|
}
|
|
|
|
// Parse location from description: various formats
|
|
let location = null;
|
|
const locationMatch = description.match(/(?:in|at)\s+([A-Za-z\s]+,\s*[A-Za-z\s]+)(?:\.|$)/i) ||
|
|
description.match(/Location:\s*([^<\n]+)/i);
|
|
if (locationMatch) {
|
|
location = locationMatch[1].trim();
|
|
}
|
|
// Check for "Online" events
|
|
if (description.toLowerCase().includes('online')) {
|
|
location = 'Online';
|
|
}
|
|
|
|
// Skip events without valid dates or in the past
|
|
if (!startDate) continue;
|
|
const eventDate = new Date(startDate);
|
|
const now = new Date();
|
|
now.setHours(0, 0, 0, 0);
|
|
if (eventDate < now) continue;
|
|
|
|
const coords = location && location !== 'Online' ? normalizeLocation(location) : null;
|
|
if (location === 'Online') {
|
|
// Mark as virtual
|
|
if (coords) coords.virtual = true;
|
|
}
|
|
|
|
events.push({
|
|
id: guid || `dev-events-${title.slice(0, 20)}`,
|
|
title: title,
|
|
type: 'conference',
|
|
location: location,
|
|
coords: coords || (location === 'Online' ? { virtual: true, original: 'Online' } : null),
|
|
startDate: startDate,
|
|
endDate: startDate, // RSS doesn't have end date
|
|
url: link,
|
|
source: 'dev.events',
|
|
});
|
|
}
|
|
|
|
return events;
|
|
}
|
|
|
|
export default async function handler(req) {
|
|
const url = new URL(req.url);
|
|
const type = url.searchParams.get('type'); // 'all', 'conferences', 'earnings', 'ipo'
|
|
const mappable = url.searchParams.get('mappable') === 'true'; // Only return events with coords
|
|
const limit = parseInt(url.searchParams.get('limit')) || 0; // Max events (0 = unlimited)
|
|
const days = parseInt(url.searchParams.get('days')) || 0; // Events within N days (0 = unlimited)
|
|
|
|
try {
|
|
// Fetch both sources in parallel
|
|
const [icsResponse, rssResponse] = await Promise.allSettled([
|
|
fetch(ICS_URL, {
|
|
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; WorldMonitor/1.0)' },
|
|
}),
|
|
fetch(DEV_EVENTS_RSS, {
|
|
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; WorldMonitor/1.0)' },
|
|
}),
|
|
]);
|
|
|
|
let events = [];
|
|
|
|
// Parse Techmeme ICS
|
|
if (icsResponse.status === 'fulfilled' && icsResponse.value.ok) {
|
|
const icsText = await icsResponse.value.text();
|
|
events.push(...parseICS(icsText));
|
|
} else {
|
|
console.warn('Failed to fetch Techmeme ICS');
|
|
}
|
|
|
|
// Parse dev.events RSS
|
|
if (rssResponse.status === 'fulfilled' && rssResponse.value.ok) {
|
|
const rssText = await rssResponse.value.text();
|
|
const devEvents = parseDevEventsRSS(rssText);
|
|
events.push(...devEvents);
|
|
} else {
|
|
console.warn('Failed to fetch dev.events RSS');
|
|
}
|
|
|
|
// Add curated events (major conferences that may fall off limited RSS feeds)
|
|
const now = new Date();
|
|
now.setHours(0, 0, 0, 0);
|
|
for (const curated of CURATED_EVENTS) {
|
|
const eventDate = new Date(curated.startDate);
|
|
if (eventDate >= now) {
|
|
events.push(curated);
|
|
}
|
|
}
|
|
|
|
// Deduplicate by title similarity (rough match)
|
|
const seen = new Set();
|
|
events = events.filter(e => {
|
|
const key = e.title.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 30);
|
|
if (seen.has(key)) return false;
|
|
seen.add(key);
|
|
return true;
|
|
});
|
|
|
|
// Sort by date
|
|
events.sort((a, b) => a.startDate.localeCompare(b.startDate));
|
|
|
|
// Filter by type if specified
|
|
if (type && type !== 'all') {
|
|
events = events.filter(e => e.type === type);
|
|
}
|
|
|
|
// Filter to only mappable events if requested
|
|
if (mappable) {
|
|
events = events.filter(e => e.coords && !e.coords.virtual);
|
|
}
|
|
|
|
// Filter by time range if specified
|
|
if (days > 0) {
|
|
const cutoff = new Date();
|
|
cutoff.setDate(cutoff.getDate() + days);
|
|
events = events.filter(e => new Date(e.startDate) <= cutoff);
|
|
}
|
|
|
|
// Apply limit if specified
|
|
if (limit > 0) {
|
|
events = events.slice(0, limit);
|
|
}
|
|
|
|
// Add metadata
|
|
const conferences = events.filter(e => e.type === 'conference');
|
|
const mappableCount = conferences.filter(e => e.coords && !e.coords.virtual).length;
|
|
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
count: events.length,
|
|
conferenceCount: conferences.length,
|
|
mappableCount,
|
|
lastUpdated: new Date().toISOString(),
|
|
events,
|
|
}), {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Cache-Control': 'public, s-maxage=1800', // Cache for 30 minutes
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('Tech events error:', error);
|
|
return new Response(JSON.stringify({
|
|
success: false,
|
|
error: error.message,
|
|
}), {
|
|
status: 500,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': '*',
|
|
},
|
|
});
|
|
}
|
|
}
|