Using-Pink-Paper-Plane-Subscription-on-Linux

Tired of the the tedious task of building a Paper-Plane internet traffic transfer, I purchase somebody’s service. After several days’ trial, I’m very satisfied except that linux client couldn’t use subscribe function. After all, I know some computer knowledge, so I decide to implement this function myself.

Sharing Link Scheme

Paper-Plane sharing link is a string using urlsafe_base64 encoding. It’s format is as follows:

ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0)

The whole subscription link is many sharing link separated by ‘\n’ or space.

Base64 Decoding

There are some useful functions in the Python standard library base64, what is used here is urlsafe_b64decode.

Attentions

  1. Paper-Plane python version will go wrong when using a config file, inputing parameters via the command line can avoid this problem.
  2. If you are not familiar with Base64, you’d better read its introduce carefully, this will save you a lot of unnecessary time wasted by bugs.

My Results

  1. Python script to output configure files named by its remarks.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    #!/usr/bin/env python3
    import requests
    import base64
    import json

    UPDATE_LINK = "your subscription link"

    def fill_missing_padding(originString):
    str_len = len(originString)
    padding = 4 - str_len % 4
    return originString + "=" * padding

    def decode_link(link):
    data = link[6:]
    data = base64.urlsafe_b64decode(fill_missing_padding(data)).decode()
    ret = {}

    ret["server"] = data[:data.index(":")]
    data = data[data.index(":") + 1:]
    ret["server_port"] = int(data[:data.index(":")])
    data = data[data.index(":") + 1:]
    ret["protocol"] = data[:data.index(":")]
    data = data[data.index(":") + 1:]
    ret['method'] = data[:data.index(":")]
    data = data[data.index(":") + 1:]
    ret['obfs'] = data[:data.index(":")]
    data = data[data.index(":") + 1:]
    ret['password'] = base64.urlsafe_b64decode(fill_missing_padding(data[:data.index("/")])).decode()
    data = data[data.index("/") + 1:]
    ret['obfs_param'] = base64.urlsafe_b64decode(fill_missing_padding(data[data.index('=') + 1 : data.index('&')])).decode()
    data = data[data.index('&') + 1:]
    ret['protocol_param'] = base64.urlsafe_b64decode(fill_missing_padding(data[data.index('=') + 1:data.index('&')])).decode()
    data = data[data.index('&') + 1:]
    ret['remarks'] = base64.urlsafe_b64decode(fill_missing_padding(data[data.index('=') + 1:data.index('&')])).decode('utf-8')
    data = data[data.index('&') + 1:]
    ret['group'] = base64.urlsafe_b64decode(fill_missing_padding(data[data.index('=') + 1:])).decode('utf-8')
    return ret

    def get_nodes_link():
    req = requests.get(UPDATE_LINK)
    uncoded = base64.b64decode(fill_missing_padding(req.text))
    return filter(lambda x: x.startswith("ssr:"), uncoded.decode().split("\n"))


    if __name__ == "__main__":
    num = 0
    for url in get_nodes_link():
    num += 1
    conf_dict = decode_link(url)
    name = str(num).zfill(2) + conf_dict['remarks']
    with open(name + '.json', 'w') as conf_file:
    json.dump(conf_dict, conf_file, ensure_ascii=False, indent=4)
  1. Python script to transfer configure file to standard output.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/usr/bin/env python3
    import sys
    import json

    filename = sys.argv[1]
    with open(filename,'r') as fp:
    conf = json.load(fp)
    print("-s {server} -p {server_port} -k {password}\
    -m {method} -O {protocol} -G {protocol_param}\
    -o {obfs} -g {obfs_param} -b 127.0.0.1 -l 1080".format(**conf))