|
| 1 | +import asyncio |
1 | 2 | import base64 |
2 | 3 | import logging |
3 | 4 | import os |
|
7 | 8 | import time |
8 | 9 | import uuid |
9 | 10 | import zipfile |
| 11 | +from ipaddress import IPv4Address, IPv6Address, ip_address |
10 | 12 | from pathlib import Path |
11 | 13 |
|
12 | 14 | import aiohttp |
@@ -206,18 +208,53 @@ def file_to_base64(file_path: str) -> str: |
206 | 208 | return "base64://" + base64_str |
207 | 209 |
|
208 | 210 |
|
209 | | -def get_local_ip_addresses(): |
| 211 | +def get_local_ip_addresses() -> list[IPv4Address | IPv6Address]: |
210 | 212 | net_interfaces = psutil.net_if_addrs() |
211 | | - network_ips = [] |
| 213 | + network_ips: list[IPv4Address | IPv6Address] = [] |
212 | 214 |
|
213 | | - for interface, addrs in net_interfaces.items(): |
| 215 | + for _, addrs in net_interfaces.items(): |
214 | 216 | for addr in addrs: |
215 | | - if addr.family == socket.AF_INET: # 使用 socket.AF_INET 代替 psutil.AF_INET |
216 | | - network_ips.append(addr.address) |
| 217 | + if addr.family == socket.AF_INET: |
| 218 | + network_ips.append(ip_address(addr.address)) |
| 219 | + elif addr.family == socket.AF_INET6: |
| 220 | + # 过滤掉 IPv6 的 link-local 地址(fe80:...) |
| 221 | + ip = ip_address(addr.address.split("%")[0]) # 处理带 zone index 的情况 |
| 222 | + if not ip.is_link_local: |
| 223 | + network_ips.append(ip) |
217 | 224 |
|
218 | 225 | return network_ips |
219 | 226 |
|
220 | 227 |
|
| 228 | +async def get_public_ip_address() -> list[IPv4Address | IPv6Address]: |
| 229 | + urls = [ |
| 230 | + "https://api64.ipify.org", |
| 231 | + "https://ident.me", |
| 232 | + "https://ifconfig.me", |
| 233 | + "https://icanhazip.com", |
| 234 | + ] |
| 235 | + found_ips: dict[int, IPv4Address | IPv6Address] = {} |
| 236 | + |
| 237 | + async def fetch(session: aiohttp.ClientSession, url: str): |
| 238 | + try: |
| 239 | + async with session.get(url, timeout=3) as resp: |
| 240 | + if resp.status == 200: |
| 241 | + raw_ip = (await resp.text()).strip() |
| 242 | + ip = ip_address(raw_ip) |
| 243 | + if ip.version not in found_ips: |
| 244 | + found_ips[ip.version] = ip |
| 245 | + except Exception as e: |
| 246 | + # Ignore errors from individual services so that a single failing |
| 247 | + # endpoint does not prevent discovering the public IP from others. |
| 248 | + logger.debug("Failed to fetch public IP from %s: %s", url, e) |
| 249 | + |
| 250 | + async with aiohttp.ClientSession() as session: |
| 251 | + tasks = [fetch(session, url) for url in urls] |
| 252 | + await asyncio.gather(*tasks) |
| 253 | + |
| 254 | + # 返回找到的所有 IP 对象列表 |
| 255 | + return list(found_ips.values()) |
| 256 | + |
| 257 | + |
221 | 258 | async def get_dashboard_version(): |
222 | 259 | dist_dir = os.path.join(get_astrbot_data_path(), "dist") |
223 | 260 | if os.path.exists(dist_dir): |
|
0 commit comments