from __future__ import annotations from datetime import datetime # ruff: ignore[typing-only-standard-library-import] from enum import StrEnum from ipaddress import IPv4Network # ruff: ignore[typing-only-standard-library-import] import wreq from loguru import logger from pydantic import BaseModel from pydantic import ConfigDict from pydantic import Field from wreq import Client from wreq import Emulation class Service(StrEnum): """What service the prefix is for.""" API = "api" MEDIA = "media" class Prefix(BaseModel): """IP and if the IP is tied to API or media.""" model_config = ConfigDict(populate_by_name=True) ipv4_prefix: IPv4Network = Field(alias="ipv4Prefix") services: list[Service] class DiscordIPs(BaseModel): """Discord IPs.""" model_config = ConfigDict(populate_by_name=True) creation_time: datetime = Field(alias="creationTime") """\"creationTime\": \"2026-08-04T18:01:19Z\"""" sync_token: str = Field(alias="syncToken") """\"syncToken\": \"4ce19406\"""" notes: str """\"notes\": \"discord egress\"""" prefixes: list[Prefix] """\"prefixes\": [ {"ipv4Prefix": "104.196.222.45/32", "services": ["media"]}, {"ipv4Prefix": "34.138.218.50/32", "services": ["api"]}, ]""" async def get_discord_ips() -> DiscordIPs: """Grab the IPs that Discord uses. Returns: DiscordIPs: The IPs that Discord uses. """ url = "https://cdn.discordapp.com/ipranges/discord.json" client = Client(emulation=Emulation.Chrome149) resp: wreq.Response = await client.get(url) data: str = await resp.text() parsed_data: DiscordIPs = DiscordIPs.model_validate_json(data) return parsed_data if __name__ == "__main__": import asyncio # Log the IPs that Discord uses. ips: DiscordIPs = asyncio.run(get_discord_ips()) for ip in ips.prefixes: for service in ip.services: logger.info(f"[{service}]\t{ip.ipv4_prefix}")