vmess2json/vmesseditor.py

325 lines
9.3 KiB
Python
Raw Normal View History

2019-07-09 12:21:32 +02:00
#!/usr/bin/env python3
import os
import sys
import json
import base64
import pprint
import argparse
import random
import hashlib
import binascii
import traceback
import urllib.request
import urllib.parse
import tempfile
vmscheme = "vmess://"
2021-12-15 10:11:27 +01:00
vlessscheme = "vless://"
2019-07-09 12:21:32 +02:00
ssscheme = "ss://"
2021-12-16 05:43:42 +01:00
2019-07-09 12:21:32 +02:00
def parseLink(link):
if link.startswith(ssscheme):
return parseSs(link)
elif link.startswith(vmscheme):
return parseVmess(link)
2021-12-15 10:11:27 +01:00
elif link.startswith(vlessscheme):
return parseVless(link)
2019-07-09 12:21:32 +02:00
else:
2019-07-10 03:56:01 +02:00
print("ERROR: unsupported line: "+link)
2019-07-09 12:21:32 +02:00
return None
2021-12-16 05:43:42 +01:00
2019-07-09 12:21:32 +02:00
def item2link(item):
if item["net"] == "shadowsocks":
2021-12-16 05:43:42 +01:00
auth = base64.b64encode(
"{method}:{password}".format(**item).encode()).decode()
2019-07-10 03:56:01 +02:00
addr = "{add}:{port}".format(**item)
2021-12-16 05:43:42 +01:00
sslink = "ss://{}@{}#{}".format(auth,
addr, urllib.parse.quote(item["ps"]))
2019-07-09 12:21:32 +02:00
return sslink
2021-12-15 10:49:41 +01:00
elif item["net"] == "vless":
2021-12-16 05:43:42 +01:00
linkobj = urllib.parse.urlparse(item.get("link", ""))
qs = urllib.parse.parse_qs(linkobj.query)
is_changed = False
2021-12-16 07:16:17 +01:00
if linkobj.hostname != item["add"] or linkobj.port != item["port"]:
2021-12-16 05:43:42 +01:00
is_changed = True
2021-12-16 07:16:17 +01:00
linkobj = linkobj._replace(
netloc="{}@{}:{}".format(linkobj.username, item["add"], linkobj.port))
2021-12-16 05:43:42 +01:00
if urllib.parse.unquote_plus(linkobj.fragment) != item["ps"]:
is_changed = True
linkobj = linkobj._replace(
fragment=urllib.parse.quote_plus(item["ps"]))
2021-12-16 07:16:17 +01:00
if qs["host"][0] != item["host"]:
is_changed = True
qs["host"] = [item["host"]]
linkobj = linkobj._replace(
query=urllib.parse.urlencode(qs, doseq=True))
2021-12-16 05:43:42 +01:00
if is_changed:
item["link"] = linkobj.geturl()
2021-12-15 10:49:41 +01:00
return item["link"]
2019-07-09 12:21:32 +02:00
else:
2021-12-16 05:43:42 +01:00
return "vmess://{}".format(base64.b64encode(json.dumps(item).encode()).decode())
2019-07-09 12:21:32 +02:00
def parseSs(sslink):
if sslink.startswith(ssscheme):
2019-07-10 03:56:01 +02:00
ps = ""
2019-07-09 12:21:32 +02:00
info = sslink[len(ssscheme):]
2021-12-16 05:43:42 +01:00
2019-07-09 12:21:32 +02:00
if info.rfind("#") > 0:
2019-07-10 03:56:01 +02:00
info, ps = info.split("#", 2)
2019-07-11 03:59:18 +02:00
ps = urllib.parse.unquote(ps)
2021-12-16 05:43:42 +01:00
2019-07-09 12:21:32 +02:00
if info.find("@") < 0:
# old style link
2021-12-16 05:43:42 +01:00
# paddings
2019-07-09 12:21:32 +02:00
blen = len(info)
if blen % 4 > 0:
info += "=" * (4 - blen % 4)
info = base64.b64decode(info).decode()
atidx = info.rfind("@")
method, password = info[:atidx].split(":", 2)
addr, port = info[atidx+1:].split(":", 2)
else:
atidx = info.rfind("@")
addr, port = info[atidx+1:].split(":", 2)
info = info[:atidx]
blen = len(info)
if blen % 4 > 0:
info += "=" * (4 - blen % 4)
info = base64.b64decode(info).decode()
method, password = info.split(":", 2)
2019-07-10 03:56:01 +02:00
return dict(net="shadowsocks", add=addr, port=port, method=method, password=password, ps=ps)
2019-07-09 12:21:32 +02:00
2021-12-16 05:43:42 +01:00
2021-12-15 10:11:27 +01:00
def parseVless(link):
if link.startswith(vlessscheme):
linkobj = urllib.parse.urlparse(link)
2021-12-16 05:43:42 +01:00
qs = urllib.parse.parse_qs(linkobj.query)
2022-01-13 04:16:03 +01:00
ret = dict(net="vless", link=link, port=linkobj.port,
add=linkobj.hostname, ps=urllib.parse.unquote(linkobj.fragment))
if "host" in qs:
ret["host"] = qs["host"][0]
return ret
2021-12-16 05:43:42 +01:00
2021-12-15 10:11:27 +01:00
2019-07-09 12:21:32 +02:00
def parseVmess(vmesslink):
if vmesslink.startswith(vmscheme):
bs = vmesslink[len(vmscheme):]
2021-12-16 05:43:42 +01:00
# paddings
2019-07-09 12:21:32 +02:00
blen = len(bs)
if blen % 4 > 0:
bs += "=" * (4 - blen % 4)
vms = base64.b64decode(bs).decode()
return json.loads(vms)
else:
raise Exception("vmess link invalid")
2019-07-10 03:56:01 +02:00
def menu_loop(lines):
2019-07-09 12:21:32 +02:00
vmesses = []
2021-12-16 05:43:42 +01:00
def menu_item(x):
return "[{ps}] {net}/{add}:{port}".format(**x)
2019-07-11 03:59:18 +02:00
2019-07-09 12:21:32 +02:00
for _v in lines:
_vinfo = parseLink(_v)
if _vinfo is not None:
2021-12-16 05:43:42 +01:00
vmesses.append({
2019-07-10 03:56:01 +02:00
"menu": menu_item(_vinfo),
"link": _v,
2019-07-09 12:21:32 +02:00
"info": _vinfo
})
while True:
print("==============================================================")
for i, item in enumerate(vmesses):
2019-07-10 03:56:01 +02:00
print("[{:^3}] - {}".format(i, item["menu"]))
2019-07-09 12:21:32 +02:00
2019-07-10 03:56:01 +02:00
print("""==============================================================
2019-10-26 09:08:02 +02:00
Commands need index as the arg (example: `edit 3')
del, edit, dup
Commands needs no args:
2019-10-26 09:21:07 +02:00
add, sort, sortdesc, save, quit, help
2019-07-10 03:56:01 +02:00
""")
2019-07-09 12:21:32 +02:00
2019-07-10 03:56:01 +02:00
try:
2019-10-26 09:08:02 +02:00
command = input("Choose >>>").split(" ", maxsplit=1)
if len(command) == 2:
act, _idx = command
act = act.lower()
2019-11-04 02:41:26 +01:00
try:
idx = int(_idx)
except ValueError:
idx = -1
if idx >= len(vmesses):
raise ValueError("Index Error", idx)
2019-10-26 09:08:02 +02:00
elif len(command) == 1:
act = command[0]
2022-01-13 03:50:51 +01:00
_idx = ""
2019-10-26 09:08:02 +02:00
2019-10-26 09:21:07 +02:00
if act == "help":
print_help()
elif act == "edit":
2019-11-04 02:41:26 +01:00
if idx < 0:
raise ValueError("Index Error")
2019-07-11 03:59:18 +02:00
try:
_edited = edit_item(vmesses[idx]["info"])
except json.decoder.JSONDecodeError:
print("Error: json syntax error")
else:
vmesses[idx] = {
"menu": menu_item(_edited),
"link": item2link(_edited),
"info": _edited
}
2019-10-26 09:08:02 +02:00
elif act == "add":
2019-11-04 02:41:26 +01:00
if _idx == "":
_v = input("input >>>")
else:
_v = _idx
2019-07-11 03:22:33 +02:00
_vinfo = parseLink(_v)
if _vinfo is not None:
2021-12-16 05:43:42 +01:00
vmesses.append({
2019-07-11 03:22:33 +02:00
"menu": menu_item(_vinfo),
"link": _v,
"info": _vinfo
})
2019-10-26 09:08:02 +02:00
elif act == "sort":
2021-12-16 05:43:42 +01:00
vmesses = sorted(vmesses, key=lambda i: i["info"]["ps"])
2019-10-26 09:08:02 +02:00
elif act == "sortdesc":
2021-12-16 05:43:42 +01:00
vmesses = sorted(
vmesses, key=lambda i: i["info"]["ps"], reverse=True)
2019-10-26 09:08:02 +02:00
elif act == "save":
2019-07-09 12:21:32 +02:00
output_item(vmesses)
2019-07-10 03:56:01 +02:00
return
2019-10-26 09:08:02 +02:00
elif act == "quit":
2019-07-10 03:56:01 +02:00
return
2019-10-26 09:08:02 +02:00
elif act == "del":
2019-11-04 02:41:26 +01:00
if idx < 0:
raise ValueError("Index Error")
2019-07-09 12:21:32 +02:00
del vmesses[idx]
2019-10-26 09:08:02 +02:00
elif act == "dup":
2019-11-04 02:41:26 +01:00
if idx < 0:
raise ValueError("Index Error")
2019-10-26 09:08:02 +02:00
cp = vmesses[idx]["info"].copy()
cp["ps"] += ".dup"
vmesses.append({
"menu": menu_item(cp),
"link": item2link(cp),
"info": cp
})
2019-07-09 12:21:32 +02:00
else:
2019-07-10 03:56:01 +02:00
print("Error: Unreconized command.")
2019-11-04 02:41:26 +01:00
except ValueError as e:
print(e)
2019-07-10 03:56:01 +02:00
except IndexError:
print("Error input: Out of range")
2019-10-26 09:23:59 +02:00
except KeyboardInterrupt:
return
2019-07-10 03:56:01 +02:00
except EOFError:
return
2021-12-16 05:43:42 +01:00
2019-10-26 09:21:07 +02:00
def print_help():
print("""
* del - Delete an item, example: del 12
* edit - Edit an item in an external editor (defaultly vi, via $EDITOR env variable) will be run for the json format.
* dup - Duplicate an item, with the 'ps' appended a suffix .dup
* add - Input a new vmess item in to the subscribtion.
* sort - Sort the items by the 'ps'.
* sortdesc - Sort the items by the 'ps' in descedent order. (reversed order)
* save - Save and quit.
* quit - Quit without saveing (same as pressing Ctrl+C).
Press Enter to return to the items menu.
""")
input()
2019-07-09 12:21:32 +02:00
2021-12-16 05:43:42 +01:00
2019-07-11 03:59:18 +02:00
def edit_item(item):
2019-07-10 03:56:01 +02:00
tfile = tempfile.NamedTemporaryFile(delete=False)
tfile.close()
with open(tfile.name, 'w') as f:
2019-07-09 12:21:32 +02:00
json.dump(item, f, indent=4)
2019-10-24 06:53:14 +02:00
editor = os.environ.get("EDITOR", "vi")
os.system("{} {}".format(editor, tfile.name))
2019-07-10 03:56:01 +02:00
with open(tfile.name, 'r') as f:
try:
2019-07-11 03:59:18 +02:00
_in = json.load(f)
finally:
os.remove(tfile.name)
2021-12-16 05:43:42 +01:00
2019-07-11 03:59:18 +02:00
return _in
2019-07-09 12:21:32 +02:00
2021-12-16 05:43:42 +01:00
2019-07-09 12:21:32 +02:00
def output_item(vmesses):
2021-12-16 05:43:42 +01:00
links = map(lambda x: x["link"], vmesses)
2019-08-26 10:49:10 +02:00
with open(option.edit[0], "w") as f:
2019-10-26 06:35:45 +02:00
f.write("\n".join(links)+"\n")
2019-07-09 12:21:32 +02:00
2021-12-16 05:43:42 +01:00
2019-07-25 11:39:26 +02:00
def edit_single_link(vmess):
_vinfo = parseLink(vmess)
if _vinfo is None:
return
try:
_vedited = edit_item(_vinfo)
except json.decoder.JSONDecodeError as e:
print("JSON format error:", e)
return
_link = item2link(_vedited)
print("Edited Link:")
print(_link)
2021-12-16 05:43:42 +01:00
2019-07-09 12:21:32 +02:00
if __name__ == "__main__":
2021-12-16 05:43:42 +01:00
parser = argparse.ArgumentParser(
description="vmess subscribe file editor.")
2019-07-09 12:21:32 +02:00
parser.add_argument('edit',
nargs=1,
2019-07-25 11:39:26 +02:00
type=str,
2019-07-25 11:42:33 +02:00
help="a subscribe text file, base64 encoded or not, or a single vmess:// ss:// link")
2019-07-09 12:21:32 +02:00
option = parser.parse_args()
2019-07-25 11:39:26 +02:00
arg = option.edit[0]
if os.path.exists(arg):
with open(arg) as f:
2020-01-08 17:36:58 +01:00
origdata = f.read().strip()
2019-07-25 11:39:26 +02:00
try:
2020-01-09 08:14:00 +01:00
lines = base64.b64decode(origdata).decode().splitlines()
2019-07-25 11:39:26 +02:00
except (binascii.Error, UnicodeDecodeError):
2019-12-31 11:20:30 +01:00
lines = origdata.splitlines()
2019-07-25 11:39:26 +02:00
finally:
menu_loop(lines)
2019-07-09 12:21:32 +02:00
2019-07-25 11:39:26 +02:00
else:
edit_single_link(arg)
2019-10-26 09:23:59 +02:00
print(" Bye :)")