diff --git a/server.py b/server.py index 683aa06..de71e7b 100644 --- a/server.py +++ b/server.py @@ -31,6 +31,7 @@ import pathlib from datetime import datetime from bson import ObjectId import urllib.request +import ipaddress @@ -75,6 +76,8 @@ settings_collection = db_iptv['settings'] devices_db = db_iptv['devices'] devices = [] active_xtream_imports = {} +sio_session_ips = {} +ip_country_cache = {} # Configure Socket IO sio = socketio.AsyncServer(logger=True, engineio_logger=True, cors_allowed_origins='*') @@ -1417,6 +1420,34 @@ app.router.add_post('/gemini_search', gemini_search) app.router.add_get('/channels_epg', channels_epg) app.router.add_get('/epg', epg_programs) +def resolve_ip_country(ip_address): + if not ip_address: + return '' + if ip_address in ip_country_cache: + return ip_country_cache[ip_address] + try: + ip_obj = ipaddress.ip_address(ip_address) + if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local: + ip_country_cache[ip_address] = '' + return '' + except ValueError: + return '' + country = '' + try: + response = requests.get( + f'http://ip-api.com/json/{ip_address}', + params={'fields': 'status,country'}, + timeout=5, + ) + data = response.json() + if data.get('status') == 'success': + country = data.get('country', '') or '' + except requests.RequestException as exc: + logger.warning("IP country lookup failed for %s: %s", ip_address, exc) + ip_country_cache[ip_address] = country + return country + + # SIO FUNCTIONS def messageReceived(): @@ -1424,7 +1455,12 @@ def messageReceived(): @sio.event def connect(sid, environ): - print("connect ", sid, flush=True) + # engineio's aiohttp integration hardcodes REMOTE_ADDR to 127.0.0.1; + # the real peer address is only available via the underlying aiohttp request. + aiohttp_request = environ.get('aiohttp.request') + ip_address = aiohttp_request.remote if aiohttp_request else '' + sio_session_ips[sid] = ip_address + print("connect ", sid, ip_address, flush=True) @sio.event @@ -1432,11 +1468,12 @@ async def communication(fromSid, data): print("search request from ", fromSid, flush=True) toSidDevice = data['toSidDevice'] print("send signal to ", toSidDevice, flush=True) - await sio.emit('communication', data, room=toSidDevice ,callback=messageReceived) + await sio.emit('communication', data, room=toSidDevice ,callback=messageReceived) @sio.event def disconnect(sid): + sio_session_ips.pop(sid, None) print('disconnect ', sid, flush=True) @@ -1455,14 +1492,21 @@ async def polling(sid, data): try: vpn_status = data['vpn_status'] except: - vpn_status = 'unknown' - device_data = devices_db.find_one({ 'id_device':IdDevice}) + vpn_status = 'unknown' + + ip_address = sio_session_ips.get(sid, '') + if ip_address in ip_country_cache: + ip_country = ip_country_cache[ip_address] + else: + ip_country = await asyncio.get_event_loop().run_in_executor(None, resolve_ip_country, ip_address) + + device_data = devices_db.find_one({ 'id_device':IdDevice}) if device_data: query = { 'id_device':IdDevice} - update = { "$set": { "last_datetime": device_last_datetime ,"name": device_name, "sid":sid, "mediaRunning":mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds} } + update = { "$set": { "last_datetime": device_last_datetime ,"name": device_name, "sid":sid, "mediaRunning":mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds, 'ip_address': ip_address, 'ip_country': ip_country} } devices_db.update_one(query, update) else: - new_device = { 'id_device': IdDevice, 'name' : device_name, 'last_datetime' : device_last_datetime, 'sid':sid ,'mediaRunning' : mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds} + new_device = { 'id_device': IdDevice, 'name' : device_name, 'last_datetime' : device_last_datetime, 'sid':sid ,'mediaRunning' : mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds, 'ip_address': ip_address, 'ip_country': ip_country} devices_db.insert_one(new_device) if data_running: diff --git a/static/index.html b/static/index.html index e5f3c5b..33d48ed 100644 --- a/static/index.html +++ b/static/index.html @@ -180,6 +180,11 @@ font-weight: 500; min-width: 0; } + .device-ip { + font-size: 0.75rem; + font-weight: 400; + color: #9ca3af; + } .device-status { color: var(--success); font-size: 0.95rem; @@ -2618,10 +2623,14 @@ status2 = '#ffe6e6' } + var ipAddress = dataJSON[i].ip_address || ''; + var ipCountry = dataJSON[i].ip_country || ''; + var ipLine = ipAddress ? `
${ipAddress}${ipCountry ? ' · ' + ipCountry : ''}
` : ''; + var activeClass = (selectedDeviceId && selectedDeviceId === dataJSON[i].id_device) ? 'active' : ''; html += `
-
${dataJSON[i].name}
+
${dataJSON[i].name}${ipLine}
${status}
${vpn_status_icon}