airvpn 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airvpn-0.1.0/LICENSE +21 -0
- airvpn-0.1.0/PKG-INFO +46 -0
- airvpn-0.1.0/README.md +26 -0
- airvpn-0.1.0/airvpn/__init__.py +164 -0
- airvpn-0.1.0/airvpn/devices/__init__.py +125 -0
- airvpn-0.1.0/airvpn/devices/models.py +70 -0
- airvpn-0.1.0/airvpn/disconnect.py +30 -0
- airvpn-0.1.0/airvpn/dns_lists/__init__.py +29 -0
- airvpn-0.1.0/airvpn/dns_lists/models.py +26 -0
- airvpn-0.1.0/airvpn/generator/__init__.py +124 -0
- airvpn-0.1.0/airvpn/generator/models.py +73 -0
- airvpn-0.1.0/airvpn/network.py +21 -0
- airvpn-0.1.0/airvpn/notification.py +19 -0
- airvpn-0.1.0/airvpn/status/__init__.py +35 -0
- airvpn-0.1.0/airvpn/status/models.py +301 -0
- airvpn-0.1.0/airvpn/userinfo/__init__.py +27 -0
- airvpn-0.1.0/airvpn/userinfo/models.py +136 -0
- airvpn-0.1.0/airvpn/whatismyip/__init__.py +25 -0
- airvpn-0.1.0/airvpn/whatismyip/models.py +119 -0
- airvpn-0.1.0/airvpn.egg-info/PKG-INFO +46 -0
- airvpn-0.1.0/airvpn.egg-info/SOURCES.txt +25 -0
- airvpn-0.1.0/airvpn.egg-info/dependency_links.txt +1 -0
- airvpn-0.1.0/airvpn.egg-info/requires.txt +2 -0
- airvpn-0.1.0/airvpn.egg-info/top_level.txt +1 -0
- airvpn-0.1.0/pyproject.toml +31 -0
- airvpn-0.1.0/requirements.txt +2 -0
- airvpn-0.1.0/setup.cfg +4 -0
airvpn-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Crunchyroll+
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
airvpn-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: airvpn
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python wrapper for the AirVPN API
|
|
5
|
+
Author: chonker
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ch0nker/airvpn
|
|
8
|
+
Project-URL: Repository, https://github.com/ch0nker/airvpn
|
|
9
|
+
Project-URL: Changelog, https://github.com/ch0nker/airvpn/tree/main/docs/CHANGELOG.md
|
|
10
|
+
Project-URL: Documentation, https://github.com/ch0nker/airvpn/tree/main/docs/api/README.md
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: requests
|
|
18
|
+
Requires-Dist: python-wireguard
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Python AirVPN Wrapper
|
|
22
|
+
|
|
23
|
+
> **Note:** The docstrings and most of the documentation was generated with AI. If you spot any errors I haven't caught yet, please open a PR or an issue.
|
|
24
|
+
|
|
25
|
+
A Python wrapper for AirVPN's [API](https://airvpn.org/apisettings/).
|
|
26
|
+
|
|
27
|
+
## Guide
|
|
28
|
+
- [Changelog](docs/CHANGELOG.md)
|
|
29
|
+
- [Contributing](docs/CONTRIBUTING.md)
|
|
30
|
+
- [Example](#example)
|
|
31
|
+
- [API Documentation](docs/api/README.md)
|
|
32
|
+
- [Test Documentation](docs/tests/README.md)
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
```bash
|
|
36
|
+
pip install airvpn
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Example
|
|
40
|
+
```py
|
|
41
|
+
from airvpn import AirVPN
|
|
42
|
+
|
|
43
|
+
api = AirVPN(API_KEY) # API_KEY is optional for public services like whatismyip
|
|
44
|
+
|
|
45
|
+
print(api.userinfo.login)
|
|
46
|
+
```
|
airvpn-0.1.0/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python AirVPN Wrapper
|
|
2
|
+
|
|
3
|
+
> **Note:** The docstrings and most of the documentation was generated with AI. If you spot any errors I haven't caught yet, please open a PR or an issue.
|
|
4
|
+
|
|
5
|
+
A Python wrapper for AirVPN's [API](https://airvpn.org/apisettings/).
|
|
6
|
+
|
|
7
|
+
## Guide
|
|
8
|
+
- [Changelog](docs/CHANGELOG.md)
|
|
9
|
+
- [Contributing](docs/CONTRIBUTING.md)
|
|
10
|
+
- [Example](#example)
|
|
11
|
+
- [API Documentation](docs/api/README.md)
|
|
12
|
+
- [Test Documentation](docs/tests/README.md)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
```bash
|
|
16
|
+
pip install airvpn
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
```py
|
|
21
|
+
from airvpn import AirVPN
|
|
22
|
+
|
|
23
|
+
api = AirVPN(API_KEY) # API_KEY is optional for public services like whatismyip
|
|
24
|
+
|
|
25
|
+
print(api.userinfo.login)
|
|
26
|
+
```
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from airvpn.network import AirSession
|
|
2
|
+
from airvpn.devices import Devices, Device
|
|
3
|
+
from airvpn.dns_lists import DnsLists
|
|
4
|
+
from airvpn.generator import Generator
|
|
5
|
+
from airvpn.status import Status, Server
|
|
6
|
+
from airvpn.userinfo import UserInfo
|
|
7
|
+
from airvpn.whatismyip import WhatIsMyIp
|
|
8
|
+
|
|
9
|
+
from airvpn.disconnect import disconnect
|
|
10
|
+
from airvpn.notification import send_notification
|
|
11
|
+
|
|
12
|
+
class AirVPN:
|
|
13
|
+
"""Main entry point for interacting with the AirVPN API.
|
|
14
|
+
|
|
15
|
+
Provides access to all available services (devices, DNS lists, config
|
|
16
|
+
generator, network status, user info, and IP lookup) as lazily-created,
|
|
17
|
+
cached properties, plus convenience methods for one-off actions like
|
|
18
|
+
sending notifications and disconnecting sessions.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
api_key: The API key used for authenticated requests, if provided.
|
|
22
|
+
session: The underlying AirSession used to make API requests.
|
|
23
|
+
devices: The devices service, created and cached on first access.
|
|
24
|
+
dns_lists: The DNS lists service, created and cached on first access.
|
|
25
|
+
generator: The config generator service, created and cached on
|
|
26
|
+
first access.
|
|
27
|
+
status: The network status service, created and cached on first
|
|
28
|
+
access.
|
|
29
|
+
userinfo: The user info service, created and cached on first access.
|
|
30
|
+
whatismyip: The IP lookup service, created and cached on first
|
|
31
|
+
access.
|
|
32
|
+
"""
|
|
33
|
+
__service_classes__ = {
|
|
34
|
+
"devices": Devices,
|
|
35
|
+
"dns_lists": DnsLists,
|
|
36
|
+
"generator": Generator,
|
|
37
|
+
"status": Status,
|
|
38
|
+
"userinfo": UserInfo,
|
|
39
|
+
"whatismyip": WhatIsMyIp
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def __init__(self, API_KEY: str = None):
|
|
43
|
+
self.api_key = API_KEY
|
|
44
|
+
self.session = AirSession(API_KEY)
|
|
45
|
+
|
|
46
|
+
self._devices = None
|
|
47
|
+
self._dns_lists = None
|
|
48
|
+
self._generator = None
|
|
49
|
+
self._status = None
|
|
50
|
+
self._userinfo = None
|
|
51
|
+
self._whatismyip = None
|
|
52
|
+
|
|
53
|
+
def get_service(self, service: str):
|
|
54
|
+
"""Instantiate a named service class, enforcing its API key requirement.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
service: The service name (e.g. "devices", "status"), matched
|
|
58
|
+
case-insensitively against the registered service classes.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
An instance of the requested service class.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
AssertionError: If the service name is invalid.
|
|
65
|
+
Exception: If the service requires an API key and none was provided.
|
|
66
|
+
"""
|
|
67
|
+
service_class = AirVPN.__service_classes__.get(service.lower())
|
|
68
|
+
assert service_class, f"Invalid service name: {service}"
|
|
69
|
+
|
|
70
|
+
service = service_class(self.session)
|
|
71
|
+
if service_class.KEY_NEEDED and not self.api_key:
|
|
72
|
+
raise Exception(f"API key is required to use \"{service}\"")
|
|
73
|
+
|
|
74
|
+
assert service, "Failed to create service"
|
|
75
|
+
|
|
76
|
+
return service
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def devices(self) -> Devices:
|
|
80
|
+
"""The devices service, created and cached on first access."""
|
|
81
|
+
if not self._devices:
|
|
82
|
+
self._devices = self.get_service("devices")
|
|
83
|
+
return self._devices
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def dns_lists(self) -> DnsLists:
|
|
87
|
+
"""The DNS lists service, created and cached on first access."""
|
|
88
|
+
if not self._dns_lists:
|
|
89
|
+
self._dns_lists = self.get_service("dns_lists")
|
|
90
|
+
return self._dns_lists
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def generator(self) -> Generator:
|
|
94
|
+
"""The config generator service, created and cached on first access."""
|
|
95
|
+
if not self._generator:
|
|
96
|
+
self._generator = self.get_service("generator")
|
|
97
|
+
return self._generator
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def status(self) -> Status:
|
|
101
|
+
"""The network status service, created and cached on first access."""
|
|
102
|
+
if not self._status:
|
|
103
|
+
self._status = self.get_service("status")
|
|
104
|
+
return self._status
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def userinfo(self) -> UserInfo:
|
|
108
|
+
"""The user info service, created and cached on first access."""
|
|
109
|
+
if not self._userinfo:
|
|
110
|
+
self._userinfo = self.get_service("userinfo")
|
|
111
|
+
return self._userinfo
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def whatismyip(self) -> WhatIsMyIp:
|
|
115
|
+
"""The IP lookup service, created and cached on first access."""
|
|
116
|
+
if not self._whatismyip:
|
|
117
|
+
self._whatismyip = self.get_service("whatismyip")
|
|
118
|
+
return self._whatismyip
|
|
119
|
+
|
|
120
|
+
def send_notification(self, subject: str, body: str):
|
|
121
|
+
"""Send a message to yourself.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
subject: The notification's subject line.
|
|
125
|
+
body: The notification's message content.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if the notification was sent successfully, False otherwise.
|
|
129
|
+
|
|
130
|
+
Access type:
|
|
131
|
+
User-specific, API KEY required.
|
|
132
|
+
"""
|
|
133
|
+
assert self.api_key, "API key is required"
|
|
134
|
+
|
|
135
|
+
return send_notification(self.session, subject, body)
|
|
136
|
+
|
|
137
|
+
def disconnect(self,
|
|
138
|
+
server: Server = None,
|
|
139
|
+
device: Device = None,
|
|
140
|
+
server_name: str = None,
|
|
141
|
+
device_id: str = None):
|
|
142
|
+
"""Requests a disconnection. If none of the filter parameters is specified, disconnect all sessions of the user.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
server: A Server object to disconnect from; its public_name is
|
|
146
|
+
used if server_name is not explicitly provided.
|
|
147
|
+
device: A Device object to disconnect; its id is used if
|
|
148
|
+
device_id is not explicitly provided.
|
|
149
|
+
server_name: Name of the server to disconnect from. Ignored if
|
|
150
|
+
server is provided.
|
|
151
|
+
device_id: ID of the device to disconnect. Ignored if device
|
|
152
|
+
is provided.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The number of sessions that were disconnected.
|
|
156
|
+
|
|
157
|
+
Access type:
|
|
158
|
+
User-specific, API KEY required.
|
|
159
|
+
"""
|
|
160
|
+
assert self.api_key, "API key is required"
|
|
161
|
+
|
|
162
|
+
disconnect(self.session,
|
|
163
|
+
server_name=server.name if server else server_name,
|
|
164
|
+
device_id=device.id if device else device_id)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from airvpn.network import AirSession, Status
|
|
2
|
+
from airvpn.devices.models import Device
|
|
3
|
+
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
class DeviceAction(StrEnum):
|
|
7
|
+
"""Actions available for managing devices via the devices endpoint."""
|
|
8
|
+
LIST = "list"
|
|
9
|
+
ADD = "add"
|
|
10
|
+
DELETE = "delete"
|
|
11
|
+
MODIFY = "modify"
|
|
12
|
+
RENEW = "renew"
|
|
13
|
+
|
|
14
|
+
class Devices:
|
|
15
|
+
"""
|
|
16
|
+
Manages registered devices/keys associated with the account.
|
|
17
|
+
|
|
18
|
+
Access type:
|
|
19
|
+
User-specific, API KEY required
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
KEY_NEEDED = True
|
|
23
|
+
|
|
24
|
+
def __init__(self, session: AirSession):
|
|
25
|
+
self.session = session
|
|
26
|
+
|
|
27
|
+
def action(self,
|
|
28
|
+
action: DeviceAction,
|
|
29
|
+
id: str | None = None,
|
|
30
|
+
name: str | None = None,
|
|
31
|
+
description: str | None = None):
|
|
32
|
+
"""Send a raw devices action request.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
action: The device action to perform.
|
|
36
|
+
id: The device's ID. Required for delete, renew, and modify.
|
|
37
|
+
name: The device's name. Used for add and modify.
|
|
38
|
+
description: The device's description. Used for add and modify.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The parsed JSON response from the devices endpoint.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
AssertionError: If the response contains an error.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
result = self.session.get("devices", params={
|
|
49
|
+
"action": action, "id": id, "name": name, "description": description
|
|
50
|
+
}).json()
|
|
51
|
+
|
|
52
|
+
error = result.get("error", None)
|
|
53
|
+
assert not error, error
|
|
54
|
+
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
def list(self):
|
|
58
|
+
"""List all devices registered to the account.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
A list of Device objects.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
response = self.action(DeviceAction.LIST)
|
|
65
|
+
|
|
66
|
+
return [Device(**device) for device in response.get("devices", [])]
|
|
67
|
+
|
|
68
|
+
def add(self):
|
|
69
|
+
"""Register a new device.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
The ID of the newly created device, or None if not returned.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
response = self.action(DeviceAction.ADD)
|
|
76
|
+
|
|
77
|
+
return response.get("id", None)
|
|
78
|
+
|
|
79
|
+
def delete(self, id: str):
|
|
80
|
+
"""Delete a device.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
id: The ID of the device to delete.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if successful in deleting the device.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
response = self.action(DeviceAction.DELETE, id=id)
|
|
90
|
+
|
|
91
|
+
return response.get("result", "error") == "ok"
|
|
92
|
+
|
|
93
|
+
def renew(self, id: str):
|
|
94
|
+
"""Renew a device.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
id: The ID of the device to renew.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if successful in renewing the device.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
response = self.action(DeviceAction.RENEW, id=id)
|
|
104
|
+
|
|
105
|
+
return response.get("result", "error") == "ok"
|
|
106
|
+
|
|
107
|
+
def modify(self, id: str, name: str | None = None, description: str | None = None):
|
|
108
|
+
"""Modify a device's name and/or description.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
id: The ID of the device to modify.
|
|
112
|
+
name: The new name for the device, if changing it.
|
|
113
|
+
description: The new description for the device, if changing it.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
True if successful in modifying the device.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
AssertionError: If neither name nor description is provided.
|
|
120
|
+
"""
|
|
121
|
+
assert name is not None or description is not None, "You either need to modify name or description."
|
|
122
|
+
|
|
123
|
+
response = self.action(DeviceAction.MODIFY, id=id, name=name, description=description)
|
|
124
|
+
|
|
125
|
+
return response.get("result", "error") == "ok"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Unpack, TypedDict
|
|
2
|
+
|
|
3
|
+
class DeviceDict(TypedDict):
|
|
4
|
+
id: str
|
|
5
|
+
name: str
|
|
6
|
+
description: str
|
|
7
|
+
version: str
|
|
8
|
+
renew_first_unix: int
|
|
9
|
+
renew_first_date: str
|
|
10
|
+
renew_last_unix: int
|
|
11
|
+
renew_last_date: str
|
|
12
|
+
renew_counter: int
|
|
13
|
+
wireguard_public_key: str
|
|
14
|
+
wireguard_ipv4: str
|
|
15
|
+
wireguard_ipv6: str
|
|
16
|
+
vpn_last_from_unix: int
|
|
17
|
+
vpn_last_from_date: str
|
|
18
|
+
vpn_last_to_unix: int
|
|
19
|
+
vpn_last_to_date: str
|
|
20
|
+
vpn_attempt_unix: int
|
|
21
|
+
vpn_attempt_date: str
|
|
22
|
+
vpn_attempt_message: str
|
|
23
|
+
status: str
|
|
24
|
+
|
|
25
|
+
class Device:
|
|
26
|
+
"""Represents a registered device associated with the account.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
id: Unique identifier for the device.
|
|
30
|
+
name: Display name of the device.
|
|
31
|
+
description: Description of the device.
|
|
32
|
+
version: Version of the client/software associated with the device.
|
|
33
|
+
renew_first_unix: Unix timestamp of the device's first renewal.
|
|
34
|
+
renew_first_date: Human-readable date of the device's first renewal.
|
|
35
|
+
renew_last_unix: Unix timestamp of the device's most recent renewal.
|
|
36
|
+
renew_last_date: Human-readable date of the device's most recent renewal.
|
|
37
|
+
renew_counter: Number of times the device has been renewed.
|
|
38
|
+
wireguard_public_key: The device's WireGuard public key.
|
|
39
|
+
wireguard_ipv4: IPv4 address assigned to the device over WireGuard.
|
|
40
|
+
wireguard_ipv6: IPv6 address assigned to the device over WireGuard.
|
|
41
|
+
vpn_last_from_unix: Unix timestamp of the start of the device's last VPN session.
|
|
42
|
+
vpn_last_from_date: Human-readable start date of the device's last VPN session.
|
|
43
|
+
vpn_last_to_unix: Unix timestamp of the end of the device's last VPN session.
|
|
44
|
+
vpn_last_to_date: Human-readable end date of the device's last VPN session.
|
|
45
|
+
vpn_attempt_unix: Unix timestamp of the device's last connection attempt.
|
|
46
|
+
vpn_attempt_date: Human-readable date of the device's last connection attempt.
|
|
47
|
+
vpn_attempt_message: Message/result associated with the last connection attempt.
|
|
48
|
+
status: Current status of the device.
|
|
49
|
+
"""
|
|
50
|
+
def __init__(self, **kwargs: Unpack[DeviceDict]):
|
|
51
|
+
self.id = kwargs.get("id")
|
|
52
|
+
self.name = kwargs.get("name")
|
|
53
|
+
self.description = kwargs.get("description")
|
|
54
|
+
self.version = kwargs.get("version")
|
|
55
|
+
self.renew_first_unix = kwargs.get("renew_first_unix")
|
|
56
|
+
self.renew_first_date = kwargs.get("renew_first_date")
|
|
57
|
+
self.renew_last_unix = kwargs.get("renew_last_unix")
|
|
58
|
+
self.renew_last_date = kwargs.get("renew_last_date")
|
|
59
|
+
self.renew_counter = kwargs.get("renew_counter")
|
|
60
|
+
self.wireguard_public_key = kwargs.get("wireguard_public_key")
|
|
61
|
+
self.wireguard_ipv4 = kwargs.get("wireguard_ipv4")
|
|
62
|
+
self.wireguard_ipv6 = kwargs.get("wireguard_ipv6")
|
|
63
|
+
self.vpn_last_from_unix = kwargs.get("vpn_last_from_unix")
|
|
64
|
+
self.vpn_last_from_date = kwargs.get("vpn_last_from_date")
|
|
65
|
+
self.vpn_last_to_unix = kwargs.get("vpn_last_to_unix")
|
|
66
|
+
self.vpn_last_to_date = kwargs.get("vpn_last_to_date")
|
|
67
|
+
self.vpn_attempt_unix = kwargs.get("vpn_attempt_unix")
|
|
68
|
+
self.vpn_attempt_date = kwargs.get("vpn_attempt_date")
|
|
69
|
+
self.vpn_attempt_message = kwargs.get("vpn_attempt_message")
|
|
70
|
+
self.status = kwargs.get("status")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from airvpn.network import AirSession
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def disconnect(session: AirSession,
|
|
5
|
+
server_name: str = None,
|
|
6
|
+
device_id: str = None):
|
|
7
|
+
"""Requests a disconnection. If none of the filter parameters is specified, disconnect all sessions of the user.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
session: The active AirSession used to make the API request.
|
|
11
|
+
server_name: Name of the server to disconnect from. Ignored if
|
|
12
|
+
server is provided.
|
|
13
|
+
device_id: ID of the device to disconnect. Ignored if device
|
|
14
|
+
is provided.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The number of sessions that were disconnected.
|
|
18
|
+
|
|
19
|
+
Access type:
|
|
20
|
+
User-specific, API KEY required.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
response = session.get("disconnect", params={
|
|
24
|
+
"server": server_name,
|
|
25
|
+
"device": device_id
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
json = response.json()
|
|
29
|
+
|
|
30
|
+
return json.get("sessions_disconnected", 0)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
I can't really understand the documentation for dns_lists.
|
|
3
|
+
|
|
4
|
+
If you can understand it or can get it to change at all please open an issue or create a PR with how you did it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from airvpn.network import AirSession
|
|
8
|
+
from airvpn.dns_lists.models import Dns
|
|
9
|
+
|
|
10
|
+
class DnsLists:
|
|
11
|
+
"""Fetches available DNS filtering lists.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
lists: A dict mapping list keys to their corresponding Dns objects.
|
|
15
|
+
|
|
16
|
+
Access type:
|
|
17
|
+
Public, no API KEY required
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
KEY_NEEDED = False
|
|
21
|
+
|
|
22
|
+
def __init__(self, session: AirSession):
|
|
23
|
+
response = session.get("dns_lists")
|
|
24
|
+
json = response.json()
|
|
25
|
+
|
|
26
|
+
self.lists: dict[str, Dns] = {}
|
|
27
|
+
|
|
28
|
+
for key, dns in json.get("lists", {}).items():
|
|
29
|
+
self.lists[key] = Dns(**dns)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Unpack, TypedDict
|
|
2
|
+
|
|
3
|
+
class DnsDict(TypedDict, total=False):
|
|
4
|
+
name: str
|
|
5
|
+
description: str
|
|
6
|
+
home: str | None
|
|
7
|
+
last_update_unix: int
|
|
8
|
+
n_items: int
|
|
9
|
+
|
|
10
|
+
class Dns:
|
|
11
|
+
"""Represents a DNS filtering list/profile.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
name: The DNS list's name.
|
|
15
|
+
description: Description of the DNS list.
|
|
16
|
+
home: URL of the DNS list's home page/source, if available;
|
|
17
|
+
otherwise None.
|
|
18
|
+
last_update_unix: Unix timestamp of when the DNS list was last updated.
|
|
19
|
+
n_items: Number of entries in the DNS list.
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, **kwargs: Unpack[DnsDict]):
|
|
22
|
+
self.name = kwargs.get("name")
|
|
23
|
+
self.description = kwargs.get("description")
|
|
24
|
+
self.home = kwargs.get("home")
|
|
25
|
+
self.last_update_unix = kwargs.get("last_update_unix")
|
|
26
|
+
self.n_items = kwargs.get("n_items")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# https://airvpn.org/api/generator/?protocols=wireguard_3_udp_1637&servers=xamidimura&device=Default
|
|
2
|
+
# https://airvpn.org/api/generator/?protocols=openvpn_3_udp_443%2Copenvpn_3_tcp_443%2Cwireguard_3_udp_1637%2Copenvpn_4_udp_443&servers=asellus&device=Default
|
|
3
|
+
# https://airvpn.org/api/generator/?protocols=openvpn_3_udp_443%2Copenvpn_3_tcp_443%2Cwireguard_3_udp_1637%2Copenvpn_4_udp_443&servers=grus%2Casellus&device=Default
|
|
4
|
+
# https://airvpn.org/api/generator/?protocols=openvpn_3_udp_443%2Copenvpn_3_tcp_443%2Cwireguard_3_udp_1637%2Copenvpn_4_udp_443&servers=america%2Casia%2Cearth%2Cbr%2Cca%2Cgrus%2Casellus%2Cvulpecula%2Cxamidimura&system=linux&device=Default
|
|
5
|
+
|
|
6
|
+
from typing import Unpack
|
|
7
|
+
from airvpn.network import AirSession
|
|
8
|
+
from airvpn.generator.models import SystemType, ProtocolType, VpnType, OptionsDict
|
|
9
|
+
|
|
10
|
+
class Generator:
|
|
11
|
+
"""Generates VPN configuration files via the AirVPN config generator API.
|
|
12
|
+
|
|
13
|
+
Wraps the same endpoint used by https://airvpn.org/generator, allowing
|
|
14
|
+
programmatic creation of OpenVPN/WireGuard configs for one or more
|
|
15
|
+
servers, systems, and protocols.
|
|
16
|
+
|
|
17
|
+
Access type:
|
|
18
|
+
User-specific, API KEY required.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
KEY_NEEDED = True
|
|
22
|
+
|
|
23
|
+
def __init__(self, session: AirSession):
|
|
24
|
+
self.session = session
|
|
25
|
+
|
|
26
|
+
def create_config(self,
|
|
27
|
+
server: str,
|
|
28
|
+
device: str,
|
|
29
|
+
system: SystemType = SystemType.WINDOWS,
|
|
30
|
+
vpn_type: VpnType = VpnType.WIREGUARD,
|
|
31
|
+
protocol_type: ProtocolType = ProtocolType.UDP,
|
|
32
|
+
port: int = 1637,
|
|
33
|
+
entry_ip: int = 3,
|
|
34
|
+
download: str = "auto",
|
|
35
|
+
files_binary: str = "",
|
|
36
|
+
files_prefix: str = "",
|
|
37
|
+
openvpn_directives: str = "",
|
|
38
|
+
openvpn_data_ciphers: str = "",
|
|
39
|
+
resolve: bool = False,
|
|
40
|
+
openvpn_allserver: bool = False,
|
|
41
|
+
proxy_mode: str = "none",
|
|
42
|
+
proxy_host: str = "127.0.0.1",
|
|
43
|
+
proxy_port: str = "8080",
|
|
44
|
+
proxy_login: str = "",
|
|
45
|
+
proxy_password: str = "",
|
|
46
|
+
proxy_auth: str = "none",
|
|
47
|
+
wireguard_mtu: int = 1320,
|
|
48
|
+
wireguard_persistent_keepalive: int = 15,
|
|
49
|
+
iplayer_entry: str = "ipv4",
|
|
50
|
+
iplayer_exit: str = "both",
|
|
51
|
+
**kwargs: Unpack[OptionsDict]
|
|
52
|
+
):
|
|
53
|
+
"""Generate a VPN configuration file for one or more servers.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
server: Server name(s) to generate the config for. Multiple
|
|
57
|
+
servers, countries, continents, or "earth" can be combined
|
|
58
|
+
with commas (e.g. "america,asia,earth,br,ca").
|
|
59
|
+
device: The device profile name to associate with this config.
|
|
60
|
+
system: Target operating system for the generated config.
|
|
61
|
+
vpn_type: VPN protocol family (e.g. WireGuard, OpenVPN).
|
|
62
|
+
protocol_type: Transport protocol (UDP or TCP).
|
|
63
|
+
port: Port number to connect on.
|
|
64
|
+
entry_ip: IP version/entry point selector for the connection.
|
|
65
|
+
download: Download mode for the generated files ("auto" or
|
|
66
|
+
a specific format).
|
|
67
|
+
files_binary: Optional binary/executable to bundle with the config.
|
|
68
|
+
files_prefix: Optional filename prefix for generated files.
|
|
69
|
+
openvpn_directives: Additional custom directives to inject into
|
|
70
|
+
an OpenVPN config.
|
|
71
|
+
openvpn_data_ciphers: Custom data cipher list for OpenVPN.
|
|
72
|
+
resolve: Whether to resolve server hostnames instead of using
|
|
73
|
+
raw IPs in the config.
|
|
74
|
+
openvpn_allserver: Whether to include all servers in a single
|
|
75
|
+
OpenVPN config.
|
|
76
|
+
proxy_mode: Proxy mode to configure in the generated config
|
|
77
|
+
("none" or a specific proxy type).
|
|
78
|
+
proxy_auth: Proxy authentication method/type, if proxy_mode
|
|
79
|
+
is enabled.
|
|
80
|
+
proxy_host: Proxy host address, if proxy_mode is enabled.
|
|
81
|
+
proxy_port: Proxy port, if proxy_mode is enabled.
|
|
82
|
+
proxy_login: Proxy authentication username, if required.
|
|
83
|
+
proxy_password: Proxy authentication password, if required.
|
|
84
|
+
wireguard_mtu: MTU value for WireGuard configs.
|
|
85
|
+
wireguard_persistent_keepalive: Persistent keepalive interval,
|
|
86
|
+
in seconds, for WireGuard configs.
|
|
87
|
+
iplayer_entry: IP layer to use for the entry connection
|
|
88
|
+
("ipv4", "ipv6", or "both").
|
|
89
|
+
iplayer_exit: IP layer to use for the exit connection
|
|
90
|
+
("ipv4", "ipv6", or "both").
|
|
91
|
+
**kwargs: Additional generator options not covered by the named
|
|
92
|
+
parameters above. These override any matching keys built
|
|
93
|
+
from the named parameters. Supported keys include:
|
|
94
|
+
|
|
95
|
+
openvpn_version: OpenVPN version to target.
|
|
96
|
+
openvpn_noembedkeys: Whether to omit embedding keys directly
|
|
97
|
+
in the OpenVPN config (reference external key files instead).
|
|
98
|
+
openvpn_allservers: Whether to include all servers in a single
|
|
99
|
+
OpenVPN config. Note: this is a separate key from the
|
|
100
|
+
openvpn_allserver named parameter above and does not
|
|
101
|
+
override it.
|
|
102
|
+
|
|
103
|
+
See OptionsDict for the full list of supported keys.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The raw text response from the generator endpoint (typically
|
|
107
|
+
the generated config file contents).
|
|
108
|
+
"""
|
|
109
|
+
options = locals()
|
|
110
|
+
|
|
111
|
+
options.pop("self")
|
|
112
|
+
options.pop("server")
|
|
113
|
+
options.pop("kwargs")
|
|
114
|
+
|
|
115
|
+
protocol = f"{vpn_type}_{entry_ip}_{protocol_type}_{port}"
|
|
116
|
+
|
|
117
|
+
options["protocols"] = protocol
|
|
118
|
+
options["servers"] = server
|
|
119
|
+
|
|
120
|
+
options = options | kwargs
|
|
121
|
+
|
|
122
|
+
response = self.session.get("generator", params=options)
|
|
123
|
+
|
|
124
|
+
return response.text
|