Created
August 23, 2016 10:37
-
-
Save tonyseek/63491ba1802710a3e7447fa579bf7bcc to your computer and use it in GitHub Desktop.
Migrate the Surge configuration to ShadowsocksX because the Surge Mac 2.0 restricts device numbers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""ShadowsocksX Migration. | |
This script generates ShadowsocksX profiles from the Surge configuration. | |
""" | |
import os | |
import json | |
import time | |
import shutil | |
import logging | |
import argparse | |
import plistlib | |
import configparser | |
logger = logging.getLogger(__name__) | |
DEFAULT_SSX_PLIST = '~/Library/Preferences/clowwindy.ShadowsocksX.plist' | |
def parse_surge_config(filename): | |
parser = configparser.ConfigParser(allow_no_value=True) | |
parser.read([filename]) | |
assert parser.has_section('Proxy'), '[Proxy] section is required.' | |
for option in parser.options('Proxy'): | |
# extract value | |
value = parser.get('Proxy', option, raw=True) | |
if isinstance(value, list): | |
value = value[0] | |
value = value.lower() | |
# skip useless value | |
if value == 'direct': | |
continue | |
# parse value | |
fragments = [fragment.strip() for fragment in value.split(',')] | |
try: | |
proto, host, port, method, password, _ = fragments[:6] | |
except ValueError: | |
logger.warning('Unable to parse %r' % fragments) | |
continue | |
if proto != 'custom': | |
logger.info('Skip unsupported proto %r' % fragments) | |
yield { | |
'method': method, | |
'password': password, | |
'remarks': option, | |
'server': host, | |
'server_port': int(port), | |
} | |
def merge_shadowsocks_profile(ssx_config, new_config): | |
config = dict(ssx_config) | |
config.setdefault('current', 0) | |
config.setdefault('profiles', []).extend(new_config) | |
return config | |
def migrate(surge_config, input_plist, output_plist, print_only): | |
# loads and parse surge config | |
new_config = parse_surge_config(surge_config) | |
# loads and merge the plist file | |
plist = plistlib.load(open(input_plist, 'rb')) | |
ssx_config = json.loads(plist['config'].decode('utf-8')) | |
merged_config = merge_shadowsocks_profile(ssx_config, new_config) | |
plist['config'] = json.dumps(merged_config).encode('utf-8') | |
if print_only: | |
print(plistlib.dumps(plist, fmt=plistlib.FMT_XML).decode('utf-8')) | |
return | |
# backup if overrides | |
if input_plist == output_plist: | |
timestamp = int(time.time() * 100) | |
shutil.copy(output_plist, '{0}.{1}.bak'.format(input_plist, timestamp)) | |
# write the output file | |
with open(output_plist, 'wb') as plist_file: | |
plistlib.dump(plist, plist_file, fmt=plistlib.FMT_BINARY) | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
'--surge-config', help='The input path of surge configuration.', | |
required=True) | |
parser.add_argument( | |
'--input-ssx-plist', help='The input path of ShadowsocksX plist.', | |
default=DEFAULT_SSX_PLIST) | |
parser.add_argument( | |
'--output-ssx-plist', help='The output path of ShadowsocksX plist.', | |
default=DEFAULT_SSX_PLIST) | |
parser.add_argument( | |
'-p', '--print-only', help='Print only.', action='store_true', | |
default=False) | |
args = parser.parse_args() | |
migrate( | |
os.path.expanduser(args.surge_config), | |
os.path.expanduser(args.input_ssx_plist), | |
os.path.expanduser(args.output_ssx_plist), | |
args.print_only, | |
) | |
if not args.print_only: | |
print('Now you can import the plist file:') | |
print('\tdefaults import clowwindy.ShadowsocksX "{0}"'.format( | |
args.output_ssx_plist)) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment