Skip to content

Instantly share code, notes, and snippets.

@trojblue
Created September 13, 2025 16:14
Show Gist options
  • Select an option

  • Save trojblue/585904c01fc651371df6402af01206dd to your computer and use it in GitHub Desktop.

Select an option

Save trojblue/585904c01fc651371df6402af01206dd to your computer and use it in GitHub Desktop.
parse link-shaped clash config to json obects:
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