vmess2json/vmesseditor.py

199 lines
5.7 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://"
ssscheme = "ss://"
def parseLink(link):
if link.startswith(ssscheme):
return parseSs(link)
elif link.startswith(vmscheme):
return parseVmess(link)
else:
2019-07-10 03:56:01 +02:00
print("ERROR: unsupported line: "+link)
2019-07-09 12:21:32 +02:00
return None
def item2link(item):
if item["net"] == "shadowsocks":
2019-07-10 03:56:01 +02:00
auth = base64.b64encode("{method}:{password}".format(**item).encode()).decode()
addr = "{add}:{port}".format(**item)
sslink = "ss://{}@{}#{}".format(auth, addr, urllib.parse.quote(item["ps"]))
2019-07-09 12:21:32 +02:00
return sslink
else:
return "vmess://{}".format(base64.b64encode(json.dumps(item).encode()).decode())
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):]
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)
2019-07-09 12:21:32 +02:00
if info.find("@") < 0:
# old style link
#paddings
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
def parseVmess(vmesslink):
if vmesslink.startswith(vmscheme):
bs = vmesslink[len(vmscheme):]
#paddings
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 = []
2019-07-11 03:59:18 +02:00
menu_item = lambda x: "[{ps}] {add}:{port}/{net}".format(**x)
2019-07-09 12:21:32 +02:00
for _v in lines:
_vinfo = parseLink(_v)
if _vinfo is not None:
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-07-11 07:43:47 +02:00
Enter index digit XX to edit,
Other commands: Add(a), Delete XX(dXX), Sort by ps(s), Sort by ps desc(d),
2019-07-11 03:22:33 +02:00
Save Write(w), Quit without saving(q)
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-07-11 03:22:33 +02:00
sel = input("Choose >>>")
2019-07-09 12:21:32 +02:00
if sel.isdigit():
2019-07-10 03:56:01 +02:00
idx = int(sel)
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-07-11 03:22:33 +02:00
elif sel == "a":
_v = input("input >>>")
_vinfo = parseLink(_v)
if _vinfo is not None:
vmesses.append({
"menu": menu_item(_vinfo),
"link": _v,
"info": _vinfo
})
2019-07-11 07:43:47 +02:00
elif sel == "s":
vmesses = sorted(vmesses, key=lambda i:i["info"]["ps"])
2019-07-09 12:21:32 +02:00
elif sel == "d":
2019-07-11 07:43:47 +02:00
vmesses = sorted(vmesses, key=lambda i:i["info"]["ps"], reverse=True)
2019-07-09 12:21:32 +02:00
elif sel == "w":
output_item(vmesses)
2019-07-10 03:56:01 +02:00
return
2019-07-09 12:21:32 +02:00
elif sel == "q":
2019-07-10 03:56:01 +02:00
return
2019-07-09 12:21:32 +02:00
elif sel.startswith("d") and sel[1:].isdigit():
2019-07-10 03:56:01 +02:00
idx = int(sel[1:])
2019-07-09 12:21:32 +02:00
del vmesses[idx]
else:
2019-07-10 03:56:01 +02:00
print("Error: Unreconized command.")
except IndexError:
print("Error input: Out of range")
except EOFError:
return
2019-07-09 12:21:32 +02: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-07-10 03:56:01 +02:00
os.system("vim {}".format(tfile.name))
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)
return _in
2019-07-09 12:21:32 +02:00
def output_item(vmesses):
2019-07-10 03:56:01 +02:00
links = map(lambda x:x["link"], vmesses)
2019-07-09 12:21:32 +02:00
with open(option.edit[0].name, "w") as f:
f.write("\n".join(links))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="vmess subscribe file editor.")
parser.add_argument('edit',
nargs=1,
type=argparse.FileType('r'),
help="a subscribe text file, base64 encoded or not")
option = parser.parse_args()
2019-07-11 08:31:37 +02:00
indata = option.edit[0].read().strip()
2019-07-10 03:56:01 +02:00
option.edit[0].close()
2019-07-09 12:21:32 +02:00
try:
2019-07-11 08:31:37 +02:00
blen = len(indata)
if blen % 4 > 0:
indata += "=" * (4 - blen % 4)
2019-07-09 12:21:32 +02:00
lines = base64.b64decode(indata).decode().splitlines()
except (binascii.Error, UnicodeDecodeError):
lines = indata.splitlines()
finally:
2019-07-10 03:56:01 +02:00
menu_loop(lines)