feat: direct subscription fetching, URI parser, FLClashX compatibility
- Fetch subscriptions directly via httpx (mihomo UA → YAML, xray-checker → base64 URI list) - Add uri_parser.py: vless/vmess/ss/trojan/hysteria2 URI → Mihomo proxy dicts - Fix YAML quoting for Go parser (strings starting with special chars) - Remove hidden:true and proxy-providers from delivered configs - Inject all service groups into GLOBAL proxies for FLClashX group discovery - Strip placeholder proxy-providers (e.g. "subscription") not in DB - Fix Mihomo healthcheck: add Bearer auth header - Add README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import yaml
|
||||
from expander import filter_proxy, expand_config, build_mihomo_config
|
||||
from dataclasses import dataclass
|
||||
from expander import filter_proxy, expand_config, build_mihomo_config, inject_providers_for_delivery
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakeSub:
|
||||
name: str
|
||||
url: str
|
||||
|
||||
PROXY_FULL = {
|
||||
"name": "node1",
|
||||
@@ -173,3 +180,90 @@ proxy-providers:
|
||||
result = build_mihomo_config([valid_yaml, ":: invalid yaml ::: {{{"], "s")
|
||||
cfg = yaml.safe_load(result)
|
||||
assert "p1" in cfg["proxy-providers"]
|
||||
|
||||
|
||||
# --- inject_providers_for_delivery tests ---
|
||||
|
||||
def test_inject_providers_returns_unchanged_when_no_subs():
|
||||
base = "proxies: []\nrules:\n - MATCH,DIRECT\n"
|
||||
result = inject_providers_for_delivery(base, [])
|
||||
assert result == base
|
||||
|
||||
|
||||
def test_inject_providers_adds_entry_for_missing_sub():
|
||||
base = "proxies: []\nproxy-groups: []\nrules:\n - MATCH,DIRECT\n"
|
||||
sub = FakeSub(name="mysub", url="https://example.com/sub")
|
||||
result = inject_providers_for_delivery(base, [sub])
|
||||
cfg = yaml.safe_load(result)
|
||||
assert "mysub" in cfg["proxy-providers"]
|
||||
entry = cfg["proxy-providers"]["mysub"]
|
||||
assert entry["type"] == "http"
|
||||
assert entry["url"] == "https://example.com/sub"
|
||||
assert entry["interval"] == 3600
|
||||
assert entry["health-check"]["enable"] is True
|
||||
|
||||
|
||||
def test_inject_providers_adds_use_to_fallback_and_url_test_groups():
|
||||
base = (
|
||||
"proxies: []\n"
|
||||
"proxy-groups:\n"
|
||||
" - name: Auto\n"
|
||||
" type: url-test\n"
|
||||
" proxies: []\n"
|
||||
" - name: FB\n"
|
||||
" type: fallback\n"
|
||||
" proxies: []\n"
|
||||
" - name: LB\n"
|
||||
" type: load-balance\n"
|
||||
" proxies: []\n"
|
||||
" - name: Manual\n"
|
||||
" type: select\n"
|
||||
" proxies: []\n"
|
||||
"rules:\n"
|
||||
" - MATCH,DIRECT\n"
|
||||
)
|
||||
sub = FakeSub(name="newsub", url="https://example.com/sub")
|
||||
result = inject_providers_for_delivery(base, [sub])
|
||||
cfg = yaml.safe_load(result)
|
||||
|
||||
groups = {g["name"]: g for g in cfg["proxy-groups"]}
|
||||
assert groups["Auto"]["use"] == ["newsub"]
|
||||
assert groups["FB"]["use"] == ["newsub"]
|
||||
assert groups["LB"]["use"] == ["newsub"]
|
||||
assert "use" not in groups["Manual"]
|
||||
|
||||
|
||||
def test_inject_providers_does_not_add_use_to_groups_that_already_have_use():
|
||||
base = (
|
||||
"proxies: []\n"
|
||||
"proxy-groups:\n"
|
||||
" - name: Auto\n"
|
||||
" type: url-test\n"
|
||||
" use:\n"
|
||||
" - existing-provider\n"
|
||||
"rules:\n"
|
||||
" - MATCH,DIRECT\n"
|
||||
)
|
||||
sub = FakeSub(name="newsub", url="https://example.com/sub")
|
||||
result = inject_providers_for_delivery(base, [sub])
|
||||
cfg = yaml.safe_load(result)
|
||||
groups = {g["name"]: g for g in cfg["proxy-groups"]}
|
||||
assert groups["Auto"]["use"] == ["existing-provider"]
|
||||
|
||||
|
||||
def test_inject_providers_skips_sub_already_in_base_yaml():
|
||||
base = (
|
||||
"proxy-providers:\n"
|
||||
" existing-sub:\n"
|
||||
" type: http\n"
|
||||
" url: https://original.example.com/sub\n"
|
||||
" interval: 3600\n"
|
||||
"proxies: []\n"
|
||||
"proxy-groups: []\n"
|
||||
"rules:\n"
|
||||
" - MATCH,DIRECT\n"
|
||||
)
|
||||
sub = FakeSub(name="existing-sub", url="https://different.example.com/sub")
|
||||
result = inject_providers_for_delivery(base, [sub])
|
||||
# Should return unchanged since no new entries
|
||||
assert result == base
|
||||
|
||||
@@ -219,11 +219,12 @@ async def test_add_subscription(http_client, db_session):
|
||||
db_session.add(config)
|
||||
await db_session.commit()
|
||||
|
||||
resp = await http_client.post(
|
||||
f"/configs/{config.id}/subscriptions/new",
|
||||
data={"name": "mysub", "url": "https://example.com/sub"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
with patch("main.write_and_reload_mihomo", new_callable=AsyncMock):
|
||||
resp = await http_client.post(
|
||||
f"/configs/{config.id}/subscriptions/new",
|
||||
data={"name": "mysub", "url": "https://example.com/sub"},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 303
|
||||
|
||||
result = await db_session.execute(
|
||||
@@ -246,7 +247,8 @@ async def test_delete_subscription(http_client, db_session):
|
||||
await db_session.commit()
|
||||
sub_id = sub.id
|
||||
|
||||
resp = await http_client.post(f"/subscriptions/{sub_id}/delete", follow_redirects=False)
|
||||
with patch("main.write_and_reload_mihomo", new_callable=AsyncMock):
|
||||
resp = await http_client.post(f"/subscriptions/{sub_id}/delete", follow_redirects=False)
|
||||
assert resp.status_code == 303
|
||||
|
||||
result = await db_session.execute(select(Subscription).where(Subscription.id == sub_id))
|
||||
|
||||
Reference in New Issue
Block a user