ipinfo.py
3.2 KB| 1 | #!/usr/bin/env python3 |
| 2 | # IP 归属地查询(ip-api.com 免费 API,无需 Key) |
| 3 | import argparse, json, socket, sys, urllib.parse, urllib.request |
| 4 | |
| 5 | API = 'http://ip-api.com/json' |
| 6 | FIELDS = 'status,message,country,countryCode,regionName,city,isp,org,as,asname,timezone,lat,lon,query' |
| 7 | |
| 8 | |
| 9 | def fetch(url): |
| 10 | req = urllib.request.Request(url, headers={'User-Agent': 'ip-geolocation-skill/1.0'}) |
| 11 | with urllib.request.urlopen(req, timeout=15) as r: |
| 12 | return json.loads(r.read().decode()) |
| 13 | |
| 14 | |
| 15 | def lookup(ip, lang): |
| 16 | params = {'lang': lang, 'fields': FIELDS} |
| 17 | q = urllib.parse.urlencode(params) |
| 18 | return fetch(f'{API}/{ip}?{q}') |
| 19 | |
| 20 | |
| 21 | def resolve(host): |
| 22 | try: |
| 23 | return socket.gethostbyname(host) |
| 24 | except socket.gaierror: |
| 25 | return None |
| 26 | |
| 27 | |
| 28 | def fmt_ip(ip, lang): |
| 29 | data = lookup(ip, lang) |
| 30 | if data.get('status') != 'success': |
| 31 | return f'{ip}: 查询失败({data.get("message", "unknown")})' |
| 32 | lines = [ip] |
| 33 | country = data.get('country', '') |
| 34 | cc = data.get('countryCode', '') |
| 35 | if lang == 'zh': |
| 36 | if cc: |
| 37 | lines.append(f' 国家:{country}({cc})') |
| 38 | region = data.get('regionName', '') |
| 39 | city = data.get('city', '') |
| 40 | loc = f'{region} {city}'.strip() |
| 41 | if loc: |
| 42 | lines.append(f' 地区:{loc}') |
| 43 | isp = data.get('isp', '') |
| 44 | org = data.get('org', '') |
| 45 | if isp: |
| 46 | lines.append(f' 运营商:{isp}' + (f'({org})' if org and org != isp else '')) |
| 47 | asn = data.get('as', '') |
| 48 | if asn: |
| 49 | lines.append(f' ASN:{asn}') |
| 50 | tz = data.get('timezone', '') |
| 51 | if tz: |
| 52 | lines.append(f' 时区:{tz}') |
| 53 | lat, lon = data.get('lat'), data.get('lon') |
| 54 | if lat is not None and lon is not None: |
| 55 | lines.append(f' 坐标:{lat}, {lon}') |
| 56 | else: |
| 57 | if cc: |
| 58 | lines.append(f' Country: {country} ({cc})') |
| 59 | loc = ' '.join(x for x in (data.get('regionName', ''), data.get('city', '')) if x) |
| 60 | if loc: |
| 61 | lines.append(f' Region: {loc}') |
| 62 | if data.get('isp'): |
| 63 | lines.append(f' ISP: {data["isp"]}') |
| 64 | if data.get('as'): |
| 65 | lines.append(f' ASN: {data["as"]}') |
| 66 | if data.get('timezone'): |
| 67 | lines.append(f' Timezone: {data["timezone"]}') |
| 68 | if data.get('lat') is not None: |
| 69 | lines.append(f' Lat/Lon: {data["lat"]}, {data["lon"]}') |
| 70 | return '\n'.join(lines) |
| 71 | |
| 72 | |
| 73 | def main(): |
| 74 | p = argparse.ArgumentParser(description='IP 归属地查询') |
| 75 | p.add_argument('targets', nargs='*', help='IP 或域名(留空=查本机公网 IP)') |
| 76 | p.add_argument('--lang', choices=['zh', 'en'], default='zh') |
| 77 | a = p.parse_args() |
| 78 | |
| 79 | targets = a.targets or [''] |
| 80 | if len(targets) > 100: |
| 81 | print('最多一次查 100 个地址') |
| 82 | sys.exit(1) |
| 83 | |
| 84 | out = [] |
| 85 | for t in targets: |
| 86 | ip = t.strip() |
| 87 | if not ip: |
| 88 | ip = '' |
| 89 | elif not ip.replace('.', '').isdigit(): |
| 90 | resolved = resolve(ip) |
| 91 | if not resolved: |
| 92 | out.append(f'{ip}: 域名解析失败') |
| 93 | continue |
| 94 | ip = resolved |
| 95 | out.append(fmt_ip(ip, a.lang)) |
| 96 | print('\n\n'.join(out)) |
| 97 | |
| 98 | |
| 99 | if __name__ == '__main__': |
| 100 | main() |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

