forked from trishmapow/nordvpn-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nordvpn_best.py
148 lines (131 loc) · 4.26 KB
/
nordvpn_best.py
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
NordVPN Server Finder
github/trishmapow, 2019
v2.1 fping support on Linux
"""
import requests
from tabulate import tabulate
import argparse
import subprocess
import platform
NORD_API_BASE = "https://api.nordvpn.com/v1"
MAX_LOAD_DEFAULT = 30
VERBOSE = False
def get_servers(country_code: str, city: str = None, max_load: int = MAX_LOAD_DEFAULT):
# trial and error, undocumented API
fields = [
"fields[servers.name]",
"fields[servers.locations.country.code]",
"fields[servers.locations.country.city.name]",
]
fields.extend(
[
"fields[station]",
"fields[load]",
"fields[specifications]",
"fields[servers.groups.title]",
]
)
# perhaps there's a country filter? would reduce network usage
load_filter = f"filters[servers.load][$lt]={max_load}"
url = NORD_API_BASE + "/servers?limit=16384&" + "&".join(fields) + f"&{load_filter}"
if VERBOSE:
print(url)
servers = requests.request("GET", url)
filtered = [
srv
for srv in servers.json()
if srv["locations"][0]["country"]["code"].lower() == country_code.lower()
]
if city is None:
return filtered
else:
return [
srv
for srv in filtered
if srv["locations"][0]["country"]["city"]["name"].lower() == city.lower()
]
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Shows low load NordVPN servers in a given city and/or country."
)
parser.add_argument(
"location",
metavar="LOC",
type=str,
help="'city, country_code' or 'country_code' e.g. US, GB",
)
parser.add_argument("--load", type=int, help="set maximum load (1-99)")
parser.add_argument("--debug", action="store_true")
parser.add_argument(
"--fping",
action="store_true",
help="show avg ping (ms) for servers, requires 'fping'",
)
args = parser.parse_args()
if args.debug:
VERBOSE = True
# check system is linux and fping exists
if args.fping:
if platform.system().lower() == "linux":
f = subprocess.run(["which", "fping"], stdout=subprocess.DEVNULL)
if f.returncode != 0:
parser.error("fping not found")
else:
parser.error("fping only supported on Linux")
# parse loc
loc = list(map(lambda x: x.strip(), args.location.split(",")))
if len(loc) == 2:
city = loc[0]
country = loc[1]
elif len(loc) == 1:
city = None
country = loc[0]
else:
parser.error("'city, country_code' or 'country_code' expected")
if VERBOSE:
print(f"city={city} country={country}")
# parse load
if args.load is not None and args.load >= 1 and args.load <= 99:
servers = get_servers(country, city, args.load)
else:
servers = get_servers(country, city)
# construct table
headers = ["Name", "Load %", "Mbps", "IP", "Groups"]
x = [
[
s["name"],
s["load"],
[
x["values"][0]["value"]
for x in s["specifications"]
if x["identifier"] == "network_mbps"
][0],
s["station"],
(", ".join([g["title"] for g in s["groups"]][:-1])),
]
for s in servers
]
if len(servers) == 0:
print(
f"No servers found with given location and load. Check location and/or increase load parameter."
)
else:
if args.fping:
ips = [s["station"] for s in servers]
fping_args = ["fping", "-q", "-i 1", "-c 3"]
fping_args.extend(ips)
f = subprocess.run(
fping_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
avgs = [
(l.split("/")[7] if len(l.split("/")) == 9 else "-1")
for l in f.stderr.decode().splitlines()
]
headers.append("Ping (ms)")
x = [x[row] + [avgs[row]] for row in range(0, len(x) - 1)]
# output
print(tabulate(x, headers=headers))
print(
f"{len(servers)} servers online in {city or ''} {country} with <{args.load or MAX_LOAD_DEFAULT}% load"
)