Skip to content

Instantly share code, notes, and snippets.

@puttin
Last active March 9, 2024 18:49
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save puttin/e53d5303941c38af20260bd5f3c493a3 to your computer and use it in GitHub Desktop.
Save puttin/e53d5303941c38af20260bd5f3c493a3 to your computer and use it in GitHub Desktop.
parse SSR subscribe to Surge Mac External list
#!/usr/bin/env python3
import sys
import requests
import base64
# https://github.com/shadowsocksr-backup/shadowsocks-rss/wiki/SSR-QRcode-scheme
# usage: $0 https://example.com/list '/usr/local/bin/ss-local'
subscribe_url = sys.argv[1]
exec_path = sys.argv[2]
# https://stackoverflow.com/a/16891418/1298043
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text # or whatever
# modified from https://stackoverflow.com/a/9807138/1298043
def urlsafe_b64decode_fix_padding(str):
data = str.encode('utf-8')
missing_padding = len(data) % 4
if missing_padding != 0:
data += b'='* (4 - missing_padding)
return base64.urlsafe_b64decode(data).decode("utf-8")
def get_subscribe_list(url):
# request
request = requests.get(url)
response_text = request.text
# decode
decoded_response_text = urlsafe_b64decode_fix_padding(response_text)
lists = decoded_response_text.splitlines()
return lists
def parse_optional_params(str):
lists = str.split('&')
results = {}
for item in lists:
splitted = item.split('=')
if len(splitted) == 2:
decoded_value = urlsafe_b64decode_fix_padding(splitted[1])
results[ splitted[0] ] = decoded_value
return results
def parse_list(lists):
results = []
for item in lists:
encoded_config = remove_prefix(item, "ssr://")
config = urlsafe_b64decode_fix_padding(encoded_config)
# print(config)
splitted = config.split('/?')
key_params = splitted[0].split(':')
# print(key_params)
optional_params = None
if len(splitted) > 1:
optional_params = parse_optional_params(splitted[1])
# print(optional_params)
results.append([key_params, optional_params])
return results
def parse_to_Surge_External_list(servers, exec_path):
i = 1
local_port = 24000
lists = []
for server in servers:
key_params = server[0]
optional_params = server[1]
remarks = group = obfsparam = protoparam = None
if optional_params is not None:
remarks = optional_params['remarks']
group = optional_params['group']
obfsparam = optional_params['obfsparam']
protoparam = optional_params['protoparam']
name = 'Server '+str(i)
if remarks is not None and group is not None:
name = group+' '+remarks
# print(name)
line = name + ' = external, exec = "' + exec_path + '", '
# https://github.com/shadowsocksr-backup/shadowsocksr/blob/manyuser/shadowsocks/shell.py#L175
# https://github.com/shadowsocksr-backup/shadowsocksr-libev/blob/master/src/local.c#L1549
line += 'args = "-s", args = "' + key_params[0] + '", ' # server
line += 'args = "-p", args = "' + key_params[1] + '", ' # server_port
line += 'args = "-O", args = "' + key_params[2] + '", ' # protocol
line += 'args = "-m", args = "' + key_params[3] + '", ' # method
line += 'args = "-o", args = "' + key_params[4] + '", ' # obfs
line += 'args = "-k", args = "' + urlsafe_b64decode_fix_padding(key_params[5]) + '", ' # password
if protoparam is not None: # protocol_param
line += 'args = "-G", args = "' + protoparam + '", '
if obfsparam is not None: # obfs_param
line += 'args = "-g", args = "' + obfsparam + '", '
line += 'args = "-l", args = "' + str(local_port) + '", ' # local_port
# line += 'args = "-t", args = "' + str(60) + '", ' # timeout
line += 'local-port = ' + str(local_port)
# print(line)
lists.append(line)
i += 1
local_port += 1
return lists
encoded_ssr_url_list = get_subscribe_list(subscribe_url)
ssr_servers = parse_list(encoded_ssr_url_list)
print(ssr_servers)
print('---------')
Surge_External_list = parse_to_Surge_External_list(ssr_servers, exec_path)
# print(Surge_External_list)
for item in Surge_External_list:
print(item)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment