Created
September 13, 2025 16:14
-
-
Save trojblue/585904c01fc651371df6402af01206dd to your computer and use it in GitHub Desktop.
parse link-shaped clash config to json obects:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import base64 | |
| import ssl | |
| import urllib.parse | |
| import urllib.request | |
| def fetch_clash_subscriptions(link: str, timeout: int = 20) -> dict: | |
| """ | |
| Fetch a Clash/ClashX subscription and try to interpret it. | |
| Returns a dict: | |
| { | |
| "source_url": <resolved HTTP(S) URL>, | |
| "type": "clash-yaml" | "uri-list" | "text" | "bytes", | |
| "items": <list of proxies or URIs if detected>, | |
| "raw": <string or bytes of the fetched content> | |
| } | |
| """ | |
| # 1) Normalize clash:// or clashx:// installer schemes to a real URL | |
| parsed = urllib.parse.urlparse(link) | |
| if parsed.scheme in ("clash", "clashx"): | |
| qs = urllib.parse.parse_qs(parsed.query) | |
| if not qs.get("url"): | |
| raise ValueError("Scheme URL missing 'url=' parameter.") | |
| link = urllib.parse.unquote(qs["url"][0]) | |
| # 2) Fetch | |
| headers = {"User-Agent": "ClashX-Subscription-Fetcher/1.0"} | |
| req = urllib.request.Request(link, headers=headers) | |
| ctx = ssl.create_default_context() | |
| with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: | |
| data = resp.read() | |
| result = {"source_url": link, "type": "bytes", "items": [], "raw": data} | |
| # 3) Try to decode as UTF-8 text | |
| text = None | |
| try: | |
| text = data.decode("utf-8") | |
| except UnicodeDecodeError: | |
| text = None | |
| def try_b64_decode(s: str): | |
| try: | |
| # tolerate whitespace/newlines; validate=True enforces strict base64 alphabet | |
| return base64.b64decode(s.strip(), validate=True) | |
| except Exception: | |
| return None | |
| # 4) If we have text, check formats | |
| if text is not None: | |
| # 4a) Base64 subscription (V2Ray-style) -> decode, split into URIs | |
| b = try_b64_decode(text) | |
| if b: | |
| decoded = b.decode("utf-8", errors="ignore") | |
| lines = [ln.strip() for ln in decoded.splitlines() if ln.strip()] | |
| uri_prefixes = ("vmess://", "vless://", "ss://", "ssr://", "trojan://", "tuic://", "hysteria2://", "hy2://") | |
| uris = [ln for ln in lines if ln.startswith(uri_prefixes)] | |
| if uris: | |
| return {"source_url": link, "type": "uri-list", "items": uris, "raw": decoded} | |
| # 4b) Clash YAML heuristics | |
| if any(tag in text for tag in ("proxies:", "proxy-groups:", "proxy-providers:", "Proxy:")): | |
| proxies = [] | |
| # Try to parse with PyYAML if present (optional) | |
| try: | |
| import yaml # type: ignore | |
| cfg = yaml.safe_load(text) or {} | |
| maybe = cfg.get("proxies") or cfg.get("Proxy") | |
| if isinstance(maybe, list): | |
| proxies = maybe | |
| except Exception: | |
| pass | |
| return {"source_url": link, "type": "clash-yaml", "items": proxies, "raw": text} | |
| # 4c) Fallback: plain text | |
| return {"source_url": link, "type": "text", "items": [], "raw": text} | |
| # 5) Fallback: opaque bytes | |
| return result | |
| # Example: | |
| subs = fetch_clash_subscriptions("clash://install-config?url=https://YOUR_LINK") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment