Muxun_programs/python/converter.py
Galaxy 907bd5af0e mx init
the muxun is not operated by git,now init
2025-11-09 20:06:06 +08:00

133 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
将自定义节点 JSON 转换为可导入 GUI 的 ss:// 链接。
用法:
python to_ss_links.py -i nodes.json -o links.txt
JSON 输入可为:
- 单个对象
- 对象数组
字段映射:
- 仅处理 method == "aes-256-cfb" (不区分大小写)
- server -> 主机
- server_port -> 端口
- PluginOption -> 密码 (若为空/None, 回退到 password)
- mx_head_str -> 作为 ?mx= 参数
- name -> 作为 #TAG
输出:
- 文本中每行一个 ss:// 链接 (平铺样式),例如:
ss://aes-256-cfb:dwz1GtF7@112.54.161.34:22404?mx=com.win64.oppc.game.common%3A22021709%2C102024080020541279#韩国专线139
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any, Dict, Iterable, List, Tuple
from urllib.parse import quote
METHOD = "aes-256-cfb"
def _wrap_ipv6(host: str) -> str:
"""IPv6 主机在 URL 中需要方括号。"""
return f"[{host}]" if ":" in host and not host.startswith("[") else host
def build_ss_link(server: str, port: int, password: str, mx: str, name: str) -> str:
"""构造平铺样式 ss 链接,所有可变字段进行 URL 编码。
Args:
server: 服务器地址 (IPv4/IPv6/域名)
port: 服务器端口
password: 密码 (优先取 PluginOption)
mx: mx_head_str
name: 显示名称 (作为 #TAG)
Returns:
str: ss:// 链接
"""
# 密码、mx、name 都要百分号编码server 若是 IPv6 要加 []
enc_pwd = quote(password, safe="")
enc_mx = quote(mx or "", safe="")
enc_name = quote(name or "", safe="")
host = _wrap_ipv6(server)
return f"ss://{METHOD}:{enc_pwd}@{host}:{port}?mx={enc_mx}#{enc_name}"
def load_items(path: Path) -> Iterable[Dict[str, Any]]:
"""加载 JSON支持单对象或数组。"""
data = json.loads(path.read_text(encoding="utf-8"))
if isinstance(data, dict):
yield data
elif isinstance(data, list):
for item in data:
if isinstance(item, dict):
yield item
else:
raise ValueError("JSON 根节点必须是对象或数组")
def convert(items: Iterable[Dict[str, Any]]) -> List[Tuple[str, str]]:
"""将条目转换为 (name, ss_link) 列表;仅保留 aes-256-cfb。"""
out: List[Tuple[str, str]] = []
for i, it in enumerate(items):
method = str(it.get("method", "")).lower()
if method != METHOD:
continue # 只读 cfb
server = str(it.get("server", "")).strip()
port = int(it.get("server_port", 0))
# 密码优先用 PluginOption为空则回退 password
password = str(it.get("PluginOption") or it.get("password") or "").strip()
mx = str(it.get("mx_head_str", "")).strip()
name = str(it.get("name", f"node-{i}")).strip()
# 基本校验
if not server or port <= 0 or not password:
# 丢弃无效条目
continue
link = build_ss_link(server, port, password, mx, name)
out.append((name, link))
return out
def main() -> None:
parser = argparse.ArgumentParser(description="将自定义 JSON 转换为 GUI 可用的 ss:// 链接")
parser.add_argument("-i", "--input", default="nodes.json", help="输入 JSON 文件路径")
parser.add_argument("-o", "--output", help="输出 txt 文件路径(可选,默认只打印 stdout")
args = parser.parse_args()
in_path = Path(args.input)
if not in_path.exists():
print(f"[错误] 输入文件不存在: {in_path}", file=sys.stderr)
sys.exit(2)
try:
pairs = convert(load_items(in_path))
except Exception as e:
print(f"[错误] 解析失败: {e}", file=sys.stderr)
sys.exit(3)
if not pairs:
print("[提示] 没有符合条件 (aes-256-cfb) 的条目。", file=sys.stderr)
# 打印到 stdout
for _, link in pairs:
print(link)
# 可选写文件
if args.output:
out_path = Path(args.output)
out_path.write_text("\n".join(link for _, link in pairs), encoding="utf-8")
print(f"[完成] 已写入: {out_path}", file=sys.stderr)
if __name__ == "__main__":
main()