"""
This plugin provides IP geolocation functionality using free APIs.
"""
import logging
import aiohttp
import simplematrixbotlib as botlib
import socket
import re
from plugins.common import is_public_destination, html_escape, collapsible_summary
async def is_valid_ip(ip):
try:
socket.inet_pton(socket.AF_INET, ip)
return True
except socket.error:
try:
socket.inet_pton(socket.AF_INET6, ip)
return True
except socket.error:
return False
def is_domain(domain):
domain_pattern = re.compile(
r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
)
return bool(domain_pattern.match(domain))
async def resolve_domain(domain):
try:
return socket.gethostbyname(domain)
except socket.gaierror:
return None
async def query_ip_api_com(ip):
url = f"http://ip-api.com/json/{ip}"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
return await response.json()
except Exception as e:
logging.error(f"ip-api.com error: {e}")
return None
async def query_ipapi_co(ip):
url = f"https://ipapi.co/{ip}/json/"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
if response.status == 200:
return await response.json()
except Exception as e:
logging.error(f"ipapi.co error: {e}")
return None
async def query_geolocation(ip):
data = await query_ip_api_com(ip)
if not data or data.get('status') == 'fail':
data = await query_ipapi_co(ip)
return data
async def format_geolocation_results(ip, data):
if not data or ('status' in data and data.get('status') == 'fail'):
return f"🔍 No geolocation data found for {ip}."
country = data.get('country', 'N/A')
country_code = data.get('countryCode', 'N/A')
region = data.get('regionName', data.get('region', 'N/A'))
city = data.get('city', 'N/A')
postal = data.get('zip', 'N/A')
latitude = data.get('lat', 'N/A')
longitude = data.get('lon', 'N/A')
timezone = data.get('timezone', 'N/A')
isp = data.get('isp', 'N/A')
org = data.get('org', 'N/A')
asn = data.get('as', 'N/A')
content = (f"Country: {country} ({country_code})
"
f"Region: {region}
"
f"City: {city}
"
f"Postal Code: {postal}
"
f"Coordinates: {latitude}, {longitude}
"
f"Timezone: {timezone}
"
f"ISP/Organization: {isp}
"
f"ASN: {asn}
")
return collapsible_summary(f"🔍 Geolocation: {ip}", content)
async def handle_command(room, message, bot, prefix, config):
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("geo"):
args = match.args()
if len(args) < 1:
await bot.api.send_text_message(room.room_id, "Usage: !geo ")
return
query = args[0].strip()
ip = query
if is_domain(query):
await bot.api.send_text_message(room.room_id, f"🔍 Resolving domain {html_escape(query)}...")
ip = await resolve_domain(query)
if not ip:
await bot.api.send_text_message(room.room_id, f"Failed to resolve {html_escape(query)}.")
return
if not is_public_destination(ip):
await bot.api.send_text_message(room.room_id, "❌ Domain resolves to private IP.")
return
await bot.api.send_text_message(room.room_id, f"Resolved to {ip}")
elif not await is_valid_ip(query):
await bot.api.send_text_message(room.room_id, f"Invalid IP/domain: {html_escape(query)}")
return
else:
if not is_public_destination(ip):
await bot.api.send_text_message(room.room_id, "❌ Private IP not allowed.")
return
geo_data = await query_geolocation(ip)
result = await format_geolocation_results(ip, geo_data)
await bot.api.send_markdown_message(room.room_id, result)
__version__ = "1.0.2"
__author__ = "Funguy Bot"
__description__ = "IP geolocation lookup"
__help__ = """!geo – IP / domain geolocation
!geo <ip> or !geo <domain>
"""