protonne.proton
1from dataclasses import dataclass 2from pathlib import Path 3 4from morbin import Morbin, Output 5 6 7@dataclass 8class Server: 9 name: str 10 country: str 11 protocol: str 12 load: str 13 plan: str 14 features: str | None 15 16 17@dataclass(init=False) 18class KillSwitch: 19 active: bool 20 on: bool 21 permanent: bool 22 23 def __init__(self, status: str): 24 if status == "Off": 25 self.on = False 26 self.active = False 27 self.permanent = False 28 elif status.startswith("On (Inactive,"): 29 self.on = True 30 self.active = False 31 self.permanent = False 32 elif status == "On": 33 self.on = True 34 self.active = True 35 self.permanent = False 36 elif status == "Permanent": 37 self.on = True 38 self.active = True 39 self.permanent = True 40 41 42@dataclass(init=False) 43class Connection: 44 IP: str 45 killswitch: KillSwitch 46 raw: str 47 server: Server 48 time: str 49 50 def __init__(self, status: str): 51 self.raw = status 52 lines = [line for line in status.splitlines() if ":" in line] 53 connection = { 54 line.split(":", 1)[0]: line.split(":", 1)[1].replace("\t", "").strip() 55 for line in lines 56 } 57 self.IP = connection["IP"] 58 self.server = Server( 59 connection["Server"], 60 connection["Country"], 61 connection["Protocol"], 62 connection["Server Load"], 63 connection["Server Plan"], 64 connection.get("Server Features"), 65 ) 66 self.killswitch = KillSwitch(connection["Kill switch"]) 67 self.time = connection["Connection time"] 68 69 def __str__(self) -> str: 70 return self.raw 71 72 73class Proton(Morbin): 74 @property 75 def program(self) -> str: 76 return "protonvpn-cli" 77 78 # Seat |=============================== Core ===============================| 79 80 def config(self, args: str = "") -> Output: 81 return self.run(f"config {args}") 82 83 def connect( 84 self, 85 server: str = "", 86 protocol: str = "", 87 country_code: str = "", 88 fastest: bool = False, 89 random: bool = False, 90 secure_core: bool = False, 91 p2p: bool = False, 92 tor: bool = False, 93 ) -> Output: 94 """ 95 #### :params: 96 97 `server`: Connect to this specific server (e.g. `CH#4`, `CH-US-1`, `HK5-Tor`). 98 99 `protocol`: Connect using this protocol (`tcp` or `udp`). 100 101 `country_code`: Connect to a server in this country (`SE`, `PT`, `BR`, `AR`, etc.). 102 103 `fastest`: Connect to the fastest server. 104 105 `random`: Connect to a random server. 106 107 `secure_core`: Connect to the fastest Secure-Core server. 108 109 `p2p`: Connect to the fastest P2P server. 110 111 `tor`: Connect to the fastest Tor server. 112 113 Supplying an argument for `server` will override other parameters except `protocol`. 114 115 If supplying one of the `bool` params (`fastest`, `random`, `secure_core`, `p2p`, `tor`), supply only one. 116 They will be checked in the order they appear in the function signature and the first that is `True` will be used. 117 """ 118 args = "" 119 if server: 120 args = server 121 elif fastest: 122 args = "--fastest" 123 elif random: 124 args = "--random" 125 elif secure_core: 126 args = "--sc" 127 elif p2p: 128 args = "--p2p" 129 elif tor: 130 args = "--tor" 131 if country_code and (not server): 132 args += f" --cc {country_code}" 133 if protocol: 134 args += f" --protocol {protocol}" 135 return self.run(f"connect {args}") 136 137 def disconnect(self) -> Output: 138 return self.run("disconnect") 139 140 def killswitch(self, arg: str) -> Output: 141 """`arg` should be one of `on`, `off`, or `permanent`.""" 142 return self.run(f"killswitch --{arg}") 143 144 def login(self, username: str) -> Output: 145 return self.run(f"login {username}") 146 147 def logout(self) -> Output: 148 return self.run("logout") 149 150 def netshield(self, args: str = "") -> Output: 151 return self.run(f"netshield {args}") 152 153 def reconnect(self) -> Output: 154 return self.run("reconnect") 155 156 def status(self) -> Output: 157 """Execute status command.""" 158 return self.run("status") 159 160 # Seat |=========================== Convenience ===========================| 161 162 @property 163 def connected(self) -> bool: 164 """Returns whether this device is connected to Proton.""" 165 with self.capturing_output(): 166 status = self.status().stdout 167 return "Proton VPN Connection Status" in status 168 169 @property 170 def connection(self) -> Connection | None: 171 """If this device is connected, a `Connection` object will be returned. 172 If disconnected, `None` will be returned. 173 174 Accessing this property can be time consuming due to the protonvpn-cli backend. 175 Ideally store it in a local variable until you need to check for an updated connection status. 176 """ 177 with self.capturing_output(): 178 if self.connected: 179 return Connection(self.status().stdout) 180 return None 181 182 def clear_cache(self): 183 """Clears files from `home/.cache/protonvpn`. 184 185 This seems to help with the time taken to connect sometimes, 186 but if the permanent kill switch is engaged and you try to connect after clearing the cache, you will get an error. 187 """ 188 path = Path.home() / ".cache" / "protonvpn" 189 if path.exists(): 190 [path.unlink() for path in path.glob("*.*")] 191 192 def connect_fastest(self) -> Output: 193 """Connect to the fastest server.""" 194 return self.connect(fastest=True) 195 196 def connect_random(self) -> Output: 197 """Connect to a random server.""" 198 return self.connect(random=True) 199 200 def disable_killswitch(self) -> Output: 201 return self.killswitch("off") 202 203 def enable_killswitch(self) -> Output: 204 return self.killswitch("on") 205 206 def enable_permanent_killswitch(self) -> Output: 207 return self.killswitch("permanent")
18@dataclass(init=False) 19class KillSwitch: 20 active: bool 21 on: bool 22 permanent: bool 23 24 def __init__(self, status: str): 25 if status == "Off": 26 self.on = False 27 self.active = False 28 self.permanent = False 29 elif status.startswith("On (Inactive,"): 30 self.on = True 31 self.active = False 32 self.permanent = False 33 elif status == "On": 34 self.on = True 35 self.active = True 36 self.permanent = False 37 elif status == "Permanent": 38 self.on = True 39 self.active = True 40 self.permanent = True
24 def __init__(self, status: str): 25 if status == "Off": 26 self.on = False 27 self.active = False 28 self.permanent = False 29 elif status.startswith("On (Inactive,"): 30 self.on = True 31 self.active = False 32 self.permanent = False 33 elif status == "On": 34 self.on = True 35 self.active = True 36 self.permanent = False 37 elif status == "Permanent": 38 self.on = True 39 self.active = True 40 self.permanent = True
43@dataclass(init=False) 44class Connection: 45 IP: str 46 killswitch: KillSwitch 47 raw: str 48 server: Server 49 time: str 50 51 def __init__(self, status: str): 52 self.raw = status 53 lines = [line for line in status.splitlines() if ":" in line] 54 connection = { 55 line.split(":", 1)[0]: line.split(":", 1)[1].replace("\t", "").strip() 56 for line in lines 57 } 58 self.IP = connection["IP"] 59 self.server = Server( 60 connection["Server"], 61 connection["Country"], 62 connection["Protocol"], 63 connection["Server Load"], 64 connection["Server Plan"], 65 connection.get("Server Features"), 66 ) 67 self.killswitch = KillSwitch(connection["Kill switch"]) 68 self.time = connection["Connection time"] 69 70 def __str__(self) -> str: 71 return self.raw
51 def __init__(self, status: str): 52 self.raw = status 53 lines = [line for line in status.splitlines() if ":" in line] 54 connection = { 55 line.split(":", 1)[0]: line.split(":", 1)[1].replace("\t", "").strip() 56 for line in lines 57 } 58 self.IP = connection["IP"] 59 self.server = Server( 60 connection["Server"], 61 connection["Country"], 62 connection["Protocol"], 63 connection["Server Load"], 64 connection["Server Plan"], 65 connection.get("Server Features"), 66 ) 67 self.killswitch = KillSwitch(connection["Kill switch"]) 68 self.time = connection["Connection time"]
74class Proton(Morbin): 75 @property 76 def program(self) -> str: 77 return "protonvpn-cli" 78 79 # Seat |=============================== Core ===============================| 80 81 def config(self, args: str = "") -> Output: 82 return self.run(f"config {args}") 83 84 def connect( 85 self, 86 server: str = "", 87 protocol: str = "", 88 country_code: str = "", 89 fastest: bool = False, 90 random: bool = False, 91 secure_core: bool = False, 92 p2p: bool = False, 93 tor: bool = False, 94 ) -> Output: 95 """ 96 #### :params: 97 98 `server`: Connect to this specific server (e.g. `CH#4`, `CH-US-1`, `HK5-Tor`). 99 100 `protocol`: Connect using this protocol (`tcp` or `udp`). 101 102 `country_code`: Connect to a server in this country (`SE`, `PT`, `BR`, `AR`, etc.). 103 104 `fastest`: Connect to the fastest server. 105 106 `random`: Connect to a random server. 107 108 `secure_core`: Connect to the fastest Secure-Core server. 109 110 `p2p`: Connect to the fastest P2P server. 111 112 `tor`: Connect to the fastest Tor server. 113 114 Supplying an argument for `server` will override other parameters except `protocol`. 115 116 If supplying one of the `bool` params (`fastest`, `random`, `secure_core`, `p2p`, `tor`), supply only one. 117 They will be checked in the order they appear in the function signature and the first that is `True` will be used. 118 """ 119 args = "" 120 if server: 121 args = server 122 elif fastest: 123 args = "--fastest" 124 elif random: 125 args = "--random" 126 elif secure_core: 127 args = "--sc" 128 elif p2p: 129 args = "--p2p" 130 elif tor: 131 args = "--tor" 132 if country_code and (not server): 133 args += f" --cc {country_code}" 134 if protocol: 135 args += f" --protocol {protocol}" 136 return self.run(f"connect {args}") 137 138 def disconnect(self) -> Output: 139 return self.run("disconnect") 140 141 def killswitch(self, arg: str) -> Output: 142 """`arg` should be one of `on`, `off`, or `permanent`.""" 143 return self.run(f"killswitch --{arg}") 144 145 def login(self, username: str) -> Output: 146 return self.run(f"login {username}") 147 148 def logout(self) -> Output: 149 return self.run("logout") 150 151 def netshield(self, args: str = "") -> Output: 152 return self.run(f"netshield {args}") 153 154 def reconnect(self) -> Output: 155 return self.run("reconnect") 156 157 def status(self) -> Output: 158 """Execute status command.""" 159 return self.run("status") 160 161 # Seat |=========================== Convenience ===========================| 162 163 @property 164 def connected(self) -> bool: 165 """Returns whether this device is connected to Proton.""" 166 with self.capturing_output(): 167 status = self.status().stdout 168 return "Proton VPN Connection Status" in status 169 170 @property 171 def connection(self) -> Connection | None: 172 """If this device is connected, a `Connection` object will be returned. 173 If disconnected, `None` will be returned. 174 175 Accessing this property can be time consuming due to the protonvpn-cli backend. 176 Ideally store it in a local variable until you need to check for an updated connection status. 177 """ 178 with self.capturing_output(): 179 if self.connected: 180 return Connection(self.status().stdout) 181 return None 182 183 def clear_cache(self): 184 """Clears files from `home/.cache/protonvpn`. 185 186 This seems to help with the time taken to connect sometimes, 187 but if the permanent kill switch is engaged and you try to connect after clearing the cache, you will get an error. 188 """ 189 path = Path.home() / ".cache" / "protonvpn" 190 if path.exists(): 191 [path.unlink() for path in path.glob("*.*")] 192 193 def connect_fastest(self) -> Output: 194 """Connect to the fastest server.""" 195 return self.connect(fastest=True) 196 197 def connect_random(self) -> Output: 198 """Connect to a random server.""" 199 return self.connect(random=True) 200 201 def disable_killswitch(self) -> Output: 202 return self.killswitch("off") 203 204 def enable_killswitch(self) -> Output: 205 return self.killswitch("on") 206 207 def enable_permanent_killswitch(self) -> Output: 208 return self.killswitch("permanent")
Base class for creating python bindings for cli programs.
At a minimum, any subclass must implement a program property that returns the name used to invoke the cli.
The run function can then be used to build bindings.
>>> class Pip(Morbin):
>>> @property
>>> def program(self)->str:
>>> return 'pip'
>>>
>>> def install(self, package:str, *args:str)->Output:
>>> return self.run("install", package, *args)
>>>
>>> def upgrade(self, package:str)->Output:
>>> return self.install(package, "--upgrade")
>>>
>>> def install_requirements(self)->Output:
>>> return self.install("-r", "requirements.txt")
>>>
>>> pip = Pip()
>>> pip.upgrade("morbin")
84 def connect( 85 self, 86 server: str = "", 87 protocol: str = "", 88 country_code: str = "", 89 fastest: bool = False, 90 random: bool = False, 91 secure_core: bool = False, 92 p2p: bool = False, 93 tor: bool = False, 94 ) -> Output: 95 """ 96 #### :params: 97 98 `server`: Connect to this specific server (e.g. `CH#4`, `CH-US-1`, `HK5-Tor`). 99 100 `protocol`: Connect using this protocol (`tcp` or `udp`). 101 102 `country_code`: Connect to a server in this country (`SE`, `PT`, `BR`, `AR`, etc.). 103 104 `fastest`: Connect to the fastest server. 105 106 `random`: Connect to a random server. 107 108 `secure_core`: Connect to the fastest Secure-Core server. 109 110 `p2p`: Connect to the fastest P2P server. 111 112 `tor`: Connect to the fastest Tor server. 113 114 Supplying an argument for `server` will override other parameters except `protocol`. 115 116 If supplying one of the `bool` params (`fastest`, `random`, `secure_core`, `p2p`, `tor`), supply only one. 117 They will be checked in the order they appear in the function signature and the first that is `True` will be used. 118 """ 119 args = "" 120 if server: 121 args = server 122 elif fastest: 123 args = "--fastest" 124 elif random: 125 args = "--random" 126 elif secure_core: 127 args = "--sc" 128 elif p2p: 129 args = "--p2p" 130 elif tor: 131 args = "--tor" 132 if country_code and (not server): 133 args += f" --cc {country_code}" 134 if protocol: 135 args += f" --protocol {protocol}" 136 return self.run(f"connect {args}")
:params:
server: Connect to this specific server (e.g. CH#4, CH-US-1, HK5-Tor).
protocol: Connect using this protocol (tcp or udp).
country_code: Connect to a server in this country (SE, PT, BR, AR, etc.).
fastest: Connect to the fastest server.
random: Connect to a random server.
secure_core: Connect to the fastest Secure-Core server.
p2p: Connect to the fastest P2P server.
tor: Connect to the fastest Tor server.
Supplying an argument for server will override other parameters except protocol.
If supplying one of the bool params (fastest, random, secure_core, p2p, tor), supply only one.
They will be checked in the order they appear in the function signature and the first that is True will be used.
141 def killswitch(self, arg: str) -> Output: 142 """`arg` should be one of `on`, `off`, or `permanent`.""" 143 return self.run(f"killswitch --{arg}")
arg should be one of on, off, or permanent.
If this device is connected, a Connection object will be returned.
If disconnected, None will be returned.
Accessing this property can be time consuming due to the protonvpn-cli backend. Ideally store it in a local variable until you need to check for an updated connection status.
183 def clear_cache(self): 184 """Clears files from `home/.cache/protonvpn`. 185 186 This seems to help with the time taken to connect sometimes, 187 but if the permanent kill switch is engaged and you try to connect after clearing the cache, you will get an error. 188 """ 189 path = Path.home() / ".cache" / "protonvpn" 190 if path.exists(): 191 [path.unlink() for path in path.glob("*.*")]
Clears files from home/.cache/protonvpn.
This seems to help with the time taken to connect sometimes, but if the permanent kill switch is engaged and you try to connect after clearing the cache, you will get an error.
193 def connect_fastest(self) -> Output: 194 """Connect to the fastest server.""" 195 return self.connect(fastest=True)
Connect to the fastest server.
197 def connect_random(self) -> Output: 198 """Connect to a random server.""" 199 return self.connect(random=True)
Connect to a random server.
Inherited Members
- morbin.morbin.Morbin
- Morbin
- capture_output
- shell
- capturing_output
- run