v2kit 0.4__tar.gz → 0.5__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.
- {v2kit-0.4 → v2kit-0.5}/CHANGELOG.md +17 -1
- {v2kit-0.4 → v2kit-0.5}/PKG-INFO +21 -5
- {v2kit-0.4 → v2kit-0.5}/README.md +2 -2
- {v2kit-0.4 → v2kit-0.5}/SECURITY.md +2 -2
- {v2kit-0.4 → v2kit-0.5}/setup.py +2 -2
- {v2kit-0.4 → v2kit-0.5}/tests/test_http.py +15 -0
- {v2kit-0.4 → v2kit-0.5}/tests/test_shadowsocks.py +16 -1
- {v2kit-0.4 → v2kit-0.5}/tests/test_socks.py +15 -0
- {v2kit-0.4 → v2kit-0.5}/tests/test_trojan.py +14 -0
- {v2kit-0.4 → v2kit-0.5}/tests/test_vless.py +15 -0
- {v2kit-0.4 → v2kit-0.5}/tests/test_vmess.py +14 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit/functions.py +10 -23
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/base.py +19 -2
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/http.py +5 -10
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/shadowsocks.py +5 -7
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/socks.py +5 -11
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/trojan.py +5 -10
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/vless.py +5 -10
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/vmess.py +3 -3
- {v2kit-0.4 → v2kit-0.5}/v2kit/params.py +15 -3
- {v2kit-0.4 → v2kit-0.5}/v2kit/parsers.py +48 -80
- {v2kit-0.4 → v2kit-0.5}/v2kit/utils.py +3 -3
- {v2kit-0.4 → v2kit-0.5}/v2kit/validators.py +3 -3
- {v2kit-0.4 → v2kit-0.5}/v2kit.egg-info/PKG-INFO +21 -5
- {v2kit-0.4 → v2kit-0.5}/AUTHORS.md +0 -0
- {v2kit-0.4 → v2kit-0.5}/LICENSE +0 -0
- {v2kit-0.4 → v2kit-0.5}/MANIFEST.in +0 -0
- {v2kit-0.4 → v2kit-0.5}/requirements-dev.txt +0 -0
- {v2kit-0.4 → v2kit-0.5}/setup.cfg +0 -0
- {v2kit-0.4 → v2kit-0.5}/tests/test_errors.py +0 -0
- {v2kit-0.4 → v2kit-0.5}/tests/test_functions.py +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit/__init__.py +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit/__main__.py +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit/errors.py +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit/models/__init__.py +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit.egg-info/SOURCES.txt +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit.egg-info/dependency_links.txt +0 -0
- {v2kit-0.4 → v2kit-0.5}/v2kit.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
|
+
## [0.5] - 2026-07-31
|
|
9
|
+
### Added
|
|
10
|
+
- `encoded_label` property
|
|
11
|
+
- `uri_label` property
|
|
12
|
+
### Changed
|
|
13
|
+
- `encode_subscription` function modified
|
|
14
|
+
- `parse` function modified
|
|
15
|
+
- `extra` property modified
|
|
16
|
+
- `update_extra` method modified
|
|
17
|
+
- `to_uri` method modified
|
|
18
|
+
- `to_dict` method modified
|
|
19
|
+
- `Protocol` enum updated
|
|
20
|
+
- Scheme bug fixed
|
|
21
|
+
- VMESS parser bug fixed
|
|
22
|
+
- Test system modified
|
|
8
23
|
## [0.4] - 2026-07-02
|
|
9
24
|
### Added
|
|
10
25
|
- `HttpConfig` class
|
|
@@ -50,7 +65,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
50
65
|
- `encode_subscription` function
|
|
51
66
|
- `decode_subscription` function
|
|
52
67
|
|
|
53
|
-
[Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.
|
|
68
|
+
[Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.5...dev
|
|
69
|
+
[0.5]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...v0.5
|
|
54
70
|
[0.4]: https://github.com/sepandhaghighi/v2kit/compare/v0.3...v0.4
|
|
55
71
|
[0.3]: https://github.com/sepandhaghighi/v2kit/compare/v0.2...v0.3
|
|
56
72
|
[0.2]: https://github.com/sepandhaghighi/v2kit/compare/v0.1...v0.2
|
{v2kit-0.4 → v2kit-0.5}/PKG-INFO
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: v2kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: V2Kit: A Lightweight Toolkit for V2Ray Config Manipulation
|
|
5
5
|
Home-page: https://github.com/sepandhaghighi/v2kit
|
|
6
|
-
Download-URL: https://github.com/sepandhaghighi/v2kit/tarball/v0.
|
|
6
|
+
Download-URL: https://github.com/sepandhaghighi/v2kit/tarball/v0.5
|
|
7
7
|
Author: Sepand Haghighi
|
|
8
8
|
Author-email: me@sepand.tech
|
|
9
9
|
License: MIT
|
|
@@ -96,13 +96,13 @@ V2Kit is a lightweight and extensible Python toolkit for working with V2Ray prox
|
|
|
96
96
|
## Installation
|
|
97
97
|
|
|
98
98
|
### Source Code
|
|
99
|
-
- Download [Version 0.
|
|
99
|
+
- Download [Version 0.5](https://github.com/sepandhaghighi/v2kit/archive/v0.5.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
|
|
100
100
|
- `pip install .`
|
|
101
101
|
|
|
102
102
|
### PyPI
|
|
103
103
|
|
|
104
104
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
105
|
-
- `pip install v2kit==0.
|
|
105
|
+
- `pip install v2kit==0.5`
|
|
106
106
|
|
|
107
107
|
## Supported Protocols
|
|
108
108
|
|
|
@@ -211,6 +211,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
211
211
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
212
212
|
|
|
213
213
|
## [Unreleased]
|
|
214
|
+
## [0.5] - 2026-07-31
|
|
215
|
+
### Added
|
|
216
|
+
- `encoded_label` property
|
|
217
|
+
- `uri_label` property
|
|
218
|
+
### Changed
|
|
219
|
+
- `encode_subscription` function modified
|
|
220
|
+
- `parse` function modified
|
|
221
|
+
- `extra` property modified
|
|
222
|
+
- `update_extra` method modified
|
|
223
|
+
- `to_uri` method modified
|
|
224
|
+
- `to_dict` method modified
|
|
225
|
+
- `Protocol` enum updated
|
|
226
|
+
- Scheme bug fixed
|
|
227
|
+
- VMESS parser bug fixed
|
|
228
|
+
- Test system modified
|
|
214
229
|
## [0.4] - 2026-07-02
|
|
215
230
|
### Added
|
|
216
231
|
- `HttpConfig` class
|
|
@@ -256,7 +271,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
256
271
|
- `encode_subscription` function
|
|
257
272
|
- `decode_subscription` function
|
|
258
273
|
|
|
259
|
-
[Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.
|
|
274
|
+
[Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.5...dev
|
|
275
|
+
[0.5]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...v0.5
|
|
260
276
|
[0.4]: https://github.com/sepandhaghighi/v2kit/compare/v0.3...v0.4
|
|
261
277
|
[0.3]: https://github.com/sepandhaghighi/v2kit/compare/v0.2...v0.3
|
|
262
278
|
[0.2]: https://github.com/sepandhaghighi/v2kit/compare/v0.1...v0.2
|
|
@@ -51,13 +51,13 @@ V2Kit is a lightweight and extensible Python toolkit for working with V2Ray prox
|
|
|
51
51
|
## Installation
|
|
52
52
|
|
|
53
53
|
### Source Code
|
|
54
|
-
- Download [Version 0.
|
|
54
|
+
- Download [Version 0.5](https://github.com/sepandhaghighi/v2kit/archive/v0.5.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
|
|
55
55
|
- `pip install .`
|
|
56
56
|
|
|
57
57
|
### PyPI
|
|
58
58
|
|
|
59
59
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
60
|
-
- `pip install v2kit==0.
|
|
60
|
+
- `pip install v2kit==0.5`
|
|
61
61
|
|
|
62
62
|
## Supported Protocols
|
|
63
63
|
|
{v2kit-0.4 → v2kit-0.5}/setup.py
RENAMED
|
@@ -23,7 +23,7 @@ def read_description() -> str:
|
|
|
23
23
|
setup(
|
|
24
24
|
name='v2kit',
|
|
25
25
|
packages=['v2kit', 'v2kit.models'],
|
|
26
|
-
version='0.
|
|
26
|
+
version='0.5',
|
|
27
27
|
description='V2Kit: A Lightweight Toolkit for V2Ray Config Manipulation',
|
|
28
28
|
long_description=read_description(),
|
|
29
29
|
long_description_content_type='text/markdown',
|
|
@@ -31,7 +31,7 @@ setup(
|
|
|
31
31
|
author='Sepand Haghighi',
|
|
32
32
|
author_email='me@sepand.tech',
|
|
33
33
|
url='https://github.com/sepandhaghighi/v2kit',
|
|
34
|
-
download_url='https://github.com/sepandhaghighi/v2kit/tarball/v0.
|
|
34
|
+
download_url='https://github.com/sepandhaghighi/v2kit/tarball/v0.5',
|
|
35
35
|
keywords='v2ray v2ray-config v2ray-tools vmess vless trojan shadowsocks proxy subscription network',
|
|
36
36
|
project_urls={
|
|
37
37
|
'Source': 'https://github.com/sepandhaghighi/v2kit'},
|
|
@@ -137,6 +137,21 @@ def test_to_uri_roundtrip():
|
|
|
137
137
|
assert parsed == config
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
def test_encoded_label():
|
|
141
|
+
config = HttpConfig(
|
|
142
|
+
address="example.com",
|
|
143
|
+
port=1080,
|
|
144
|
+
username="user",
|
|
145
|
+
password="password",
|
|
146
|
+
label="test 1 2",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
parsed = parse(config.to_uri())
|
|
150
|
+
assert parsed == config
|
|
151
|
+
assert config.label == "test 1 2"
|
|
152
|
+
assert config.encoded_label == "test%201%202"
|
|
153
|
+
|
|
154
|
+
|
|
140
155
|
def test_equality():
|
|
141
156
|
config1 = HttpConfig(
|
|
142
157
|
address="example.com",
|
|
@@ -113,6 +113,21 @@ def test_to_uri_roundtrip():
|
|
|
113
113
|
assert parsed == config
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def test_encoded_label():
|
|
117
|
+
config = ShadowsocksConfig(
|
|
118
|
+
encryption="aes-256-gcm",
|
|
119
|
+
password="password",
|
|
120
|
+
address="example.com",
|
|
121
|
+
port=8388,
|
|
122
|
+
label="test 1 2",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
parsed = parse(config.to_uri())
|
|
126
|
+
assert parsed == config
|
|
127
|
+
assert config.label == "test 1 2"
|
|
128
|
+
assert config.encoded_label == "test%201%202"
|
|
129
|
+
|
|
130
|
+
|
|
116
131
|
def test_equality():
|
|
117
132
|
config1 = ShadowsocksConfig(
|
|
118
133
|
encryption="aes-256-gcm",
|
|
@@ -150,7 +165,7 @@ def test_repr():
|
|
|
150
165
|
|
|
151
166
|
assert repr(config) == (
|
|
152
167
|
"ShadowsocksConfig(protocol=<Protocol.SHADOWSOCKS: "
|
|
153
|
-
"'
|
|
168
|
+
"'shadowsocks'>, label=None)"
|
|
154
169
|
)
|
|
155
170
|
|
|
156
171
|
|
|
@@ -135,6 +135,21 @@ def test_to_uri_roundtrip():
|
|
|
135
135
|
assert parsed == config
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
def test_encoded_label():
|
|
139
|
+
config = SocksConfig(
|
|
140
|
+
address="example.com",
|
|
141
|
+
port=1080,
|
|
142
|
+
username="user",
|
|
143
|
+
password="password",
|
|
144
|
+
label="test 1 2",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
parsed = parse(config.to_uri())
|
|
148
|
+
assert parsed == config
|
|
149
|
+
assert config.label == "test 1 2"
|
|
150
|
+
assert config.encoded_label == "test%201%202"
|
|
151
|
+
|
|
152
|
+
|
|
138
153
|
def test_equality():
|
|
139
154
|
config1 = SocksConfig(
|
|
140
155
|
address="example.com",
|
|
@@ -131,6 +131,20 @@ def test_to_uri_roundtrip():
|
|
|
131
131
|
assert parsed == config
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
def test_encoded_label():
|
|
135
|
+
config = TrojanConfig(
|
|
136
|
+
password="password",
|
|
137
|
+
address="example.com",
|
|
138
|
+
port=443,
|
|
139
|
+
label="test 1 2",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
parsed = parse(config.to_uri())
|
|
143
|
+
assert parsed == config
|
|
144
|
+
assert config.label == "test 1 2"
|
|
145
|
+
assert config.encoded_label == "test%201%202"
|
|
146
|
+
|
|
147
|
+
|
|
134
148
|
def test_update_extra():
|
|
135
149
|
config = TrojanConfig(
|
|
136
150
|
password="password",
|
|
@@ -105,6 +105,21 @@ def test_to_uri_roundtrip():
|
|
|
105
105
|
assert parsed == config
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def test_encoded_label():
|
|
109
|
+
config = VLESSConfig(
|
|
110
|
+
uuid="1c4b4bca-e3ff-4ca8-a062-6f399ad3cf45",
|
|
111
|
+
address="example.com",
|
|
112
|
+
port=443,
|
|
113
|
+
label="test 1 2",
|
|
114
|
+
extra={"security": "tls"},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
parsed = parse(config.to_uri())
|
|
118
|
+
assert parsed == config
|
|
119
|
+
assert config.label == "test 1 2"
|
|
120
|
+
assert config.encoded_label == "test%201%202"
|
|
121
|
+
|
|
122
|
+
|
|
108
123
|
def test_equality():
|
|
109
124
|
config1 = VLESSConfig(
|
|
110
125
|
uuid="1c4b4bca-e3ff-4ca8-a062-6f399ad3cf45",
|
|
@@ -147,6 +147,20 @@ def test_to_uri_roundtrip():
|
|
|
147
147
|
assert parsed == config
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
def test_encoded_label():
|
|
151
|
+
config = VMESSConfig(
|
|
152
|
+
uuid="1c4b4bca-e3ff-4ca8-a062-6f399ad3cf45",
|
|
153
|
+
address="example.com",
|
|
154
|
+
port=443,
|
|
155
|
+
label="test 1 2",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
parsed = parse(config.to_uri())
|
|
159
|
+
assert parsed == config
|
|
160
|
+
assert config.label == "test 1 2"
|
|
161
|
+
assert config.encoded_label == "test%201%202"
|
|
162
|
+
|
|
163
|
+
|
|
150
164
|
def test_update_methods():
|
|
151
165
|
config = VMESSConfig(
|
|
152
166
|
uuid="1c4b4bca-e3ff-4ca8-a062-6f399ad3cf45",
|
|
@@ -91,33 +91,20 @@ def encode_subscription(
|
|
|
91
91
|
uri_list = []
|
|
92
92
|
|
|
93
93
|
for item in entries:
|
|
94
|
-
if isinstance(
|
|
95
|
-
item
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
uri_list.append(
|
|
99
|
-
item.to_uri()
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
continue
|
|
103
|
-
|
|
104
|
-
if isinstance(item, str):
|
|
94
|
+
if isinstance(item, BaseConfig):
|
|
95
|
+
uri = item.to_uri()
|
|
96
|
+
elif isinstance(item, str):
|
|
97
|
+
uri = item
|
|
105
98
|
if validate:
|
|
106
|
-
parse(
|
|
99
|
+
parse(uri)
|
|
100
|
+
else:
|
|
101
|
+
raise V2kitValidationError(INVALID_ITEMS_MESSAGE)
|
|
107
102
|
|
|
108
|
-
|
|
103
|
+
uri_list.append(uri)
|
|
109
104
|
|
|
110
|
-
|
|
105
|
+
subscription = "\n".join(uri_list)
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
subscription = "\n".join(
|
|
115
|
-
uri_list
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
return _encode_base64(
|
|
119
|
-
subscription
|
|
120
|
-
)
|
|
107
|
+
return _encode_base64(subscription)
|
|
121
108
|
|
|
122
109
|
|
|
123
110
|
def decode_subscription(
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""Base config model."""
|
|
3
3
|
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
|
+
from urllib.parse import quote
|
|
5
6
|
from typing import Dict, Optional
|
|
6
7
|
from ..params import Protocol
|
|
7
8
|
from ..validators import _validate_label, _validate_dict
|
|
@@ -30,6 +31,7 @@ class BaseConfig(ABC):
|
|
|
30
31
|
"""
|
|
31
32
|
self._protocol = protocol
|
|
32
33
|
self._label = None
|
|
34
|
+
self._encoded_label = None
|
|
33
35
|
self._extra = {}
|
|
34
36
|
|
|
35
37
|
self.update_extra(extra or {})
|
|
@@ -45,10 +47,24 @@ class BaseConfig(ABC):
|
|
|
45
47
|
"""Get the config label."""
|
|
46
48
|
return self._label
|
|
47
49
|
|
|
50
|
+
@property
|
|
51
|
+
def encoded_label(self) -> Optional[str]:
|
|
52
|
+
"""Get the config percent-encoded label."""
|
|
53
|
+
return self._encoded_label
|
|
54
|
+
|
|
48
55
|
@property
|
|
49
56
|
def extra(self) -> Dict[str, object]:
|
|
50
57
|
"""Get extra data."""
|
|
51
|
-
return self._extra
|
|
58
|
+
return self._extra.copy()
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def uri_label(self) -> str:
|
|
62
|
+
"""Get URI label."""
|
|
63
|
+
return (
|
|
64
|
+
f"#{self.encoded_label}"
|
|
65
|
+
if self.encoded_label
|
|
66
|
+
else ""
|
|
67
|
+
)
|
|
52
68
|
|
|
53
69
|
def update_label(
|
|
54
70
|
self,
|
|
@@ -62,6 +78,7 @@ class BaseConfig(ABC):
|
|
|
62
78
|
_validate_label(label)
|
|
63
79
|
|
|
64
80
|
self._label = label
|
|
81
|
+
self._encoded_label = quote(label) if label else None
|
|
65
82
|
|
|
66
83
|
return self
|
|
67
84
|
|
|
@@ -79,7 +96,7 @@ class BaseConfig(ABC):
|
|
|
79
96
|
"Extra",
|
|
80
97
|
)
|
|
81
98
|
|
|
82
|
-
self._extra = extra
|
|
99
|
+
self._extra = extra.copy()
|
|
83
100
|
|
|
84
101
|
return self
|
|
85
102
|
|
|
@@ -5,7 +5,7 @@ from urllib.parse import urlencode
|
|
|
5
5
|
from typing import Dict, Optional
|
|
6
6
|
|
|
7
7
|
from .base import BaseConfig
|
|
8
|
-
from ..params import Protocol
|
|
8
|
+
from ..params import Protocol, PROTOCOL_SCHEMES
|
|
9
9
|
from ..validators import (
|
|
10
10
|
_validate_address,
|
|
11
11
|
_validate_port,
|
|
@@ -128,7 +128,7 @@ class HttpConfig(BaseConfig):
|
|
|
128
128
|
def to_dict(self) -> dict:
|
|
129
129
|
"""Convert HTTP config to dictionary."""
|
|
130
130
|
return {
|
|
131
|
-
"protocol":
|
|
131
|
+
"protocol": self.protocol.value,
|
|
132
132
|
"address": self.address,
|
|
133
133
|
"port": self.port,
|
|
134
134
|
"username": self.username,
|
|
@@ -153,14 +153,9 @@ class HttpConfig(BaseConfig):
|
|
|
153
153
|
f"?{urlencode(self.extra)}"
|
|
154
154
|
if self.extra else ""
|
|
155
155
|
)
|
|
156
|
-
|
|
157
|
-
label = (
|
|
158
|
-
f"#{self.label}"
|
|
159
|
-
if self.label else ""
|
|
160
|
-
)
|
|
161
|
-
|
|
156
|
+
scheme = PROTOCOL_SCHEMES[self.protocol]
|
|
162
157
|
return (
|
|
163
|
-
f"
|
|
158
|
+
f"{scheme}://{auth}"
|
|
164
159
|
f"{self.address}:{self.port}"
|
|
165
|
-
f"{query}{
|
|
160
|
+
f"{query}{self.uri_label}"
|
|
166
161
|
)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import Dict, Optional
|
|
5
5
|
from .base import BaseConfig
|
|
6
|
-
from ..params import Protocol
|
|
6
|
+
from ..params import Protocol, PROTOCOL_SCHEMES
|
|
7
7
|
from ..validators import (
|
|
8
8
|
_validate_port,
|
|
9
9
|
_validate_address,
|
|
@@ -140,7 +140,7 @@ class ShadowsocksConfig(BaseConfig):
|
|
|
140
140
|
def to_dict(self) -> dict:
|
|
141
141
|
"""Convert Shadowsocks config to dictionary."""
|
|
142
142
|
return {
|
|
143
|
-
"protocol":
|
|
143
|
+
"protocol": self.protocol.value,
|
|
144
144
|
"encryption": self.encryption,
|
|
145
145
|
"password": self.password,
|
|
146
146
|
"address": self.address,
|
|
@@ -158,11 +158,9 @@ class ShadowsocksConfig(BaseConfig):
|
|
|
158
158
|
encoded = _encode_base64(
|
|
159
159
|
userinfo
|
|
160
160
|
)
|
|
161
|
-
|
|
162
|
-
label = f"#{self.label}" if self.label else ""
|
|
163
|
-
|
|
161
|
+
scheme = PROTOCOL_SCHEMES[self.protocol]
|
|
164
162
|
return (
|
|
165
|
-
f"
|
|
163
|
+
f"{scheme}://{encoded}@"
|
|
166
164
|
f"{self.address}:{self.port}"
|
|
167
|
-
f"{
|
|
165
|
+
f"{self.uri_label}"
|
|
168
166
|
)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from urllib.parse import urlencode
|
|
5
5
|
from typing import Dict, Optional
|
|
6
6
|
from .base import BaseConfig
|
|
7
|
-
from ..params import Protocol
|
|
7
|
+
from ..params import Protocol, PROTOCOL_SCHEMES
|
|
8
8
|
from ..validators import (
|
|
9
9
|
_validate_port,
|
|
10
10
|
_validate_address,
|
|
@@ -133,7 +133,7 @@ class SocksConfig(BaseConfig):
|
|
|
133
133
|
def to_dict(self) -> dict:
|
|
134
134
|
"""Convert SOCKS config to dictionary."""
|
|
135
135
|
return {
|
|
136
|
-
"protocol":
|
|
136
|
+
"protocol": self.protocol.value,
|
|
137
137
|
"address": self.address,
|
|
138
138
|
"port": self.port,
|
|
139
139
|
"username": self.username,
|
|
@@ -148,12 +148,6 @@ class SocksConfig(BaseConfig):
|
|
|
148
148
|
f"?{urlencode(self.extra)}"
|
|
149
149
|
if self.extra else ""
|
|
150
150
|
)
|
|
151
|
-
|
|
152
|
-
label = (
|
|
153
|
-
f"#{self.label}"
|
|
154
|
-
if self.label else ""
|
|
155
|
-
)
|
|
156
|
-
|
|
157
151
|
auth = ""
|
|
158
152
|
|
|
159
153
|
if self.username:
|
|
@@ -163,10 +157,10 @@ class SocksConfig(BaseConfig):
|
|
|
163
157
|
auth += f":{self.password}"
|
|
164
158
|
|
|
165
159
|
auth += "@"
|
|
166
|
-
|
|
160
|
+
scheme = PROTOCOL_SCHEMES[self.protocol]
|
|
167
161
|
return (
|
|
168
|
-
f"
|
|
162
|
+
f"{scheme}://"
|
|
169
163
|
f"{auth}"
|
|
170
164
|
f"{self.address}:{self.port}"
|
|
171
|
-
f"{query}{
|
|
165
|
+
f"{query}{self.uri_label}"
|
|
172
166
|
)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from urllib.parse import urlencode
|
|
5
5
|
from typing import Dict, Optional
|
|
6
6
|
from .base import BaseConfig
|
|
7
|
-
from ..params import Protocol
|
|
7
|
+
from ..params import Protocol, PROTOCOL_SCHEMES
|
|
8
8
|
from ..validators import (
|
|
9
9
|
_validate_port,
|
|
10
10
|
_validate_address,
|
|
@@ -113,7 +113,7 @@ class TrojanConfig(BaseConfig):
|
|
|
113
113
|
def to_dict(self) -> dict:
|
|
114
114
|
"""Convert Trojan config to dictionary."""
|
|
115
115
|
return {
|
|
116
|
-
"protocol":
|
|
116
|
+
"protocol": self.protocol.value,
|
|
117
117
|
"password": self.password,
|
|
118
118
|
"address": self.address,
|
|
119
119
|
"port": self.port,
|
|
@@ -127,14 +127,9 @@ class TrojanConfig(BaseConfig):
|
|
|
127
127
|
f"?{urlencode(self.extra)}"
|
|
128
128
|
if self.extra else ""
|
|
129
129
|
)
|
|
130
|
-
|
|
131
|
-
label = (
|
|
132
|
-
f"#{self.label}"
|
|
133
|
-
if self.label else ""
|
|
134
|
-
)
|
|
135
|
-
|
|
130
|
+
scheme = PROTOCOL_SCHEMES[self.protocol]
|
|
136
131
|
return (
|
|
137
|
-
f"
|
|
132
|
+
f"{scheme}://{self.password}@"
|
|
138
133
|
f"{self.address}:{self.port}"
|
|
139
|
-
f"{query}{
|
|
134
|
+
f"{query}{self.uri_label}"
|
|
140
135
|
)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from urllib.parse import urlencode
|
|
5
5
|
from typing import Dict, Optional
|
|
6
6
|
from .base import BaseConfig
|
|
7
|
-
from ..params import Protocol
|
|
7
|
+
from ..params import Protocol, PROTOCOL_SCHEMES
|
|
8
8
|
from ..validators import (
|
|
9
9
|
_validate_uuid,
|
|
10
10
|
_validate_port,
|
|
@@ -114,7 +114,7 @@ class VLESSConfig(BaseConfig):
|
|
|
114
114
|
def to_dict(self) -> dict:
|
|
115
115
|
"""Convert VLESS config to dictionary."""
|
|
116
116
|
return {
|
|
117
|
-
"protocol":
|
|
117
|
+
"protocol": self.protocol.value,
|
|
118
118
|
"uuid": self.uuid,
|
|
119
119
|
"address": self.address,
|
|
120
120
|
"port": self.port,
|
|
@@ -128,14 +128,9 @@ class VLESSConfig(BaseConfig):
|
|
|
128
128
|
f"?{urlencode(self.extra)}"
|
|
129
129
|
if self.extra else ""
|
|
130
130
|
)
|
|
131
|
-
|
|
132
|
-
label = (
|
|
133
|
-
f"#{self.label}"
|
|
134
|
-
if self.label else ""
|
|
135
|
-
)
|
|
136
|
-
|
|
131
|
+
scheme = PROTOCOL_SCHEMES[self.protocol]
|
|
137
132
|
return (
|
|
138
|
-
f"
|
|
133
|
+
f"{scheme}://{self.uuid}@"
|
|
139
134
|
f"{self.address}:{self.port}"
|
|
140
|
-
f"{query}{
|
|
135
|
+
f"{query}{self.uri_label}"
|
|
141
136
|
)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import json
|
|
5
5
|
from typing import Dict, Optional
|
|
6
6
|
from .base import BaseConfig
|
|
7
|
-
from ..params import Protocol
|
|
7
|
+
from ..params import Protocol, PROTOCOL_SCHEMES
|
|
8
8
|
from ..validators import (
|
|
9
9
|
_validate_uuid,
|
|
10
10
|
_validate_port,
|
|
@@ -213,5 +213,5 @@ class VMESSConfig(BaseConfig):
|
|
|
213
213
|
ensure_ascii=False,
|
|
214
214
|
)
|
|
215
215
|
)
|
|
216
|
-
|
|
217
|
-
return f"
|
|
216
|
+
scheme = PROTOCOL_SCHEMES[self.protocol]
|
|
217
|
+
return f"{scheme}://{encoded}"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""v2kit params."""
|
|
3
3
|
from enum import Enum
|
|
4
4
|
|
|
5
|
-
V2KIT_VERSION = "0.
|
|
5
|
+
V2KIT_VERSION = "0.5"
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class Protocol(str, Enum):
|
|
@@ -11,12 +11,24 @@ class Protocol(str, Enum):
|
|
|
11
11
|
VMESS = "vmess"
|
|
12
12
|
VLESS = "vless"
|
|
13
13
|
TROJAN = "trojan"
|
|
14
|
-
SHADOWSOCKS = "
|
|
14
|
+
SHADOWSOCKS = "shadowsocks"
|
|
15
15
|
SOCKS = "socks"
|
|
16
16
|
HTTP = "http"
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
PROTOCOL_SCHEMES = {
|
|
20
|
+
Protocol.VMESS: "vmess",
|
|
21
|
+
Protocol.VLESS: "vless",
|
|
22
|
+
Protocol.TROJAN: "trojan",
|
|
23
|
+
Protocol.SHADOWSOCKS: "ss",
|
|
24
|
+
Protocol.SOCKS: "socks",
|
|
25
|
+
Protocol.HTTP: "http",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
SCHEME_TO_PROTOCOL = {value: key for key, value in PROTOCOL_SCHEMES.items()}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
SUPPORTED_PROTOCOLS = set(PROTOCOL_SCHEMES.values())
|
|
20
32
|
|
|
21
33
|
DEFAULT_ENCODING = "utf-8"
|
|
22
34
|
|
|
@@ -3,54 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import Union
|
|
5
5
|
import json
|
|
6
|
-
from urllib.parse import urlparse, parse_qsl
|
|
6
|
+
from urllib.parse import urlparse, parse_qsl, unquote
|
|
7
7
|
from .errors import V2kitParseError
|
|
8
|
-
from .params import Protocol
|
|
8
|
+
from .params import Protocol, SCHEME_TO_PROTOCOL
|
|
9
9
|
from .params import INVALID_URI_FORMAT_MESSAGE, UNSUPPORTED_PROTOCOL_MESSAGE
|
|
10
10
|
from .validators import _validate_non_empty_string
|
|
11
11
|
from .models import VMESSConfig, VLESSConfig, TrojanConfig, ShadowsocksConfig, SocksConfig, HttpConfig
|
|
12
12
|
from .utils import _decode_base64
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def parse(uri: str) -> Union[VMESSConfig, VLESSConfig, TrojanConfig, ShadowsocksConfig, SocksConfig, HttpConfig]:
|
|
16
|
-
"""
|
|
17
|
-
Parse V2Ray URI.
|
|
18
|
-
|
|
19
|
-
:param uri: V2Ray URI.
|
|
20
|
-
"""
|
|
21
|
-
_validate_non_empty_string(uri, "URI")
|
|
22
|
-
|
|
23
|
-
if "://" not in uri:
|
|
24
|
-
raise V2kitParseError(INVALID_URI_FORMAT_MESSAGE)
|
|
25
|
-
|
|
26
|
-
parsed = urlparse(uri)
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
protocol = Protocol(
|
|
30
|
-
parsed.scheme
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
except Exception as exc:
|
|
34
|
-
raise V2kitParseError(UNSUPPORTED_PROTOCOL_MESSAGE.format(protocol=parsed.scheme)) from exc
|
|
35
|
-
|
|
36
|
-
if protocol == Protocol.VMESS:
|
|
37
|
-
return _parse_vmess(uri)
|
|
38
|
-
|
|
39
|
-
if protocol == Protocol.VLESS:
|
|
40
|
-
return _parse_vless(uri)
|
|
41
|
-
|
|
42
|
-
if protocol == Protocol.TROJAN:
|
|
43
|
-
return _parse_trojan(uri)
|
|
44
|
-
|
|
45
|
-
if protocol == Protocol.SHADOWSOCKS:
|
|
46
|
-
return _parse_shadowsocks(uri)
|
|
47
|
-
|
|
48
|
-
if protocol == Protocol.SOCKS:
|
|
49
|
-
return _parse_socks(uri)
|
|
50
|
-
if protocol == Protocol.HTTP:
|
|
51
|
-
return _parse_http(uri)
|
|
52
|
-
|
|
53
|
-
|
|
54
15
|
def _parse_vmess(uri: str) -> VMESSConfig:
|
|
55
16
|
"""
|
|
56
17
|
Parse VMESS URI.
|
|
@@ -67,17 +28,11 @@ def _parse_vmess(uri: str) -> VMESSConfig:
|
|
|
67
28
|
"tls",
|
|
68
29
|
}
|
|
69
30
|
try:
|
|
70
|
-
_, encoded = uri.split(
|
|
71
|
-
|
|
72
|
-
1,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
decoded = _decode_base64(
|
|
76
|
-
encoded
|
|
77
|
-
)
|
|
78
|
-
|
|
31
|
+
_, encoded = uri.split("://", 1)
|
|
32
|
+
decoded = _decode_base64(encoded)
|
|
79
33
|
data = json.loads(decoded)
|
|
80
|
-
|
|
34
|
+
port = int(data.get("port", 0))
|
|
35
|
+
alter_id = int(data.get("aid", 0))
|
|
81
36
|
except Exception as exc:
|
|
82
37
|
raise V2kitParseError(INVALID_URI_FORMAT_MESSAGE) from exc
|
|
83
38
|
|
|
@@ -89,21 +44,11 @@ def _parse_vmess(uri: str) -> VMESSConfig:
|
|
|
89
44
|
return VMESSConfig(
|
|
90
45
|
uuid=data.get("id", ""),
|
|
91
46
|
address=data.get("add", ""),
|
|
92
|
-
port=
|
|
93
|
-
data.get("port", 0)
|
|
94
|
-
),
|
|
47
|
+
port=port,
|
|
95
48
|
label=data.get("ps"),
|
|
96
|
-
alter_id=
|
|
97
|
-
|
|
98
|
-
),
|
|
99
|
-
network=data.get(
|
|
100
|
-
"net",
|
|
101
|
-
"tcp",
|
|
102
|
-
),
|
|
103
|
-
tls=data.get(
|
|
104
|
-
"tls",
|
|
105
|
-
"",
|
|
106
|
-
),
|
|
49
|
+
alter_id=alter_id,
|
|
50
|
+
network=data.get("net", "tcp"),
|
|
51
|
+
tls=data.get("tls", ""),
|
|
107
52
|
extra=extra,
|
|
108
53
|
)
|
|
109
54
|
|
|
@@ -119,7 +64,7 @@ def _parse_vless(uri: str) -> VLESSConfig:
|
|
|
119
64
|
uuid=parsed.username or "",
|
|
120
65
|
address=parsed.hostname or "",
|
|
121
66
|
port=parsed.port or 0,
|
|
122
|
-
label=parsed.fragment or None,
|
|
67
|
+
label=unquote(parsed.fragment) or None,
|
|
123
68
|
extra=dict(parse_qsl(parsed.query)),
|
|
124
69
|
)
|
|
125
70
|
|
|
@@ -135,7 +80,7 @@ def _parse_trojan(uri: str) -> TrojanConfig:
|
|
|
135
80
|
password=parsed.username or "",
|
|
136
81
|
address=parsed.hostname or "",
|
|
137
82
|
port=parsed.port or 0,
|
|
138
|
-
label=parsed.fragment or None,
|
|
83
|
+
label=unquote(parsed.fragment) or None,
|
|
139
84
|
extra=dict(parse_qsl(parsed.query)),
|
|
140
85
|
)
|
|
141
86
|
|
|
@@ -148,16 +93,8 @@ def _parse_shadowsocks(uri: str) -> ShadowsocksConfig:
|
|
|
148
93
|
"""
|
|
149
94
|
parsed = urlparse(uri)
|
|
150
95
|
try:
|
|
151
|
-
userinfo = _decode_base64(
|
|
152
|
-
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
encryption, password = (
|
|
156
|
-
userinfo.split(
|
|
157
|
-
":",
|
|
158
|
-
1,
|
|
159
|
-
)
|
|
160
|
-
)
|
|
96
|
+
userinfo = _decode_base64(parsed.username)
|
|
97
|
+
encryption, password = (userinfo.split(":", 1))
|
|
161
98
|
|
|
162
99
|
except Exception as exc:
|
|
163
100
|
raise V2kitParseError(INVALID_URI_FORMAT_MESSAGE) from exc
|
|
@@ -167,7 +104,7 @@ def _parse_shadowsocks(uri: str) -> ShadowsocksConfig:
|
|
|
167
104
|
password=password,
|
|
168
105
|
address=parsed.hostname or "",
|
|
169
106
|
port=parsed.port or 0,
|
|
170
|
-
label=parsed.fragment or None,
|
|
107
|
+
label=unquote(parsed.fragment) or None,
|
|
171
108
|
extra=dict(parse_qsl(parsed.query)),
|
|
172
109
|
)
|
|
173
110
|
|
|
@@ -185,7 +122,7 @@ def _parse_socks(uri: str) -> SocksConfig:
|
|
|
185
122
|
port=parsed.port or 0,
|
|
186
123
|
username=parsed.username or None,
|
|
187
124
|
password=parsed.password or None,
|
|
188
|
-
label=parsed.fragment or None,
|
|
125
|
+
label=unquote(parsed.fragment) or None,
|
|
189
126
|
extra=dict(parse_qsl(parsed.query)),
|
|
190
127
|
)
|
|
191
128
|
|
|
@@ -203,6 +140,37 @@ def _parse_http(uri: str) -> HttpConfig:
|
|
|
203
140
|
port=parsed.port or 0,
|
|
204
141
|
username=parsed.username or None,
|
|
205
142
|
password=parsed.password or None,
|
|
206
|
-
label=parsed.fragment or None,
|
|
143
|
+
label=unquote(parsed.fragment) or None,
|
|
207
144
|
extra=dict(parse_qsl(parsed.query)),
|
|
208
145
|
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
PARSERS = {
|
|
149
|
+
Protocol.VMESS: _parse_vmess,
|
|
150
|
+
Protocol.VLESS: _parse_vless,
|
|
151
|
+
Protocol.TROJAN: _parse_trojan,
|
|
152
|
+
Protocol.SHADOWSOCKS: _parse_shadowsocks,
|
|
153
|
+
Protocol.SOCKS: _parse_socks,
|
|
154
|
+
Protocol.HTTP: _parse_http,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def parse(uri: str) -> Union[VMESSConfig, VLESSConfig, TrojanConfig, ShadowsocksConfig, SocksConfig, HttpConfig]:
|
|
159
|
+
"""
|
|
160
|
+
Parse V2Ray URI.
|
|
161
|
+
|
|
162
|
+
:param uri: V2Ray URI.
|
|
163
|
+
"""
|
|
164
|
+
_validate_non_empty_string(uri, "URI")
|
|
165
|
+
|
|
166
|
+
if "://" not in uri:
|
|
167
|
+
raise V2kitParseError(INVALID_URI_FORMAT_MESSAGE)
|
|
168
|
+
|
|
169
|
+
parsed = urlparse(uri)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
protocol = SCHEME_TO_PROTOCOL[parsed.scheme]
|
|
173
|
+
except Exception as exc:
|
|
174
|
+
raise V2kitParseError(UNSUPPORTED_PROTOCOL_MESSAGE.format(protocol=parsed.scheme)) from exc
|
|
175
|
+
|
|
176
|
+
return PARSERS[protocol](uri)
|
|
@@ -4,7 +4,7 @@ import base64
|
|
|
4
4
|
from .errors import V2kitParseError
|
|
5
5
|
from .validators import _validate_uri
|
|
6
6
|
from .params import DEFAULT_ENCODING
|
|
7
|
-
from .params import Protocol
|
|
7
|
+
from .params import Protocol, SCHEME_TO_PROTOCOL
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _encode_base64(data: str) -> str:
|
|
@@ -48,9 +48,9 @@ def _get_protocol(uri: str) -> Protocol:
|
|
|
48
48
|
"""
|
|
49
49
|
_validate_uri(uri)
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
scheme = uri.split("://", 1)[0]
|
|
52
52
|
|
|
53
|
-
return
|
|
53
|
+
return SCHEME_TO_PROTOCOL[scheme]
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def _is_protocol(uri: str, protocol: Protocol) -> bool:
|
|
@@ -6,7 +6,7 @@ import base64
|
|
|
6
6
|
import uuid
|
|
7
7
|
from urllib.parse import urlparse
|
|
8
8
|
from .errors import V2kitValidationError, V2kitParseError
|
|
9
|
-
from .params import Protocol, DEFAULT_ENCODING
|
|
9
|
+
from .params import Protocol, SCHEME_TO_PROTOCOL, DEFAULT_ENCODING
|
|
10
10
|
from .params import INVALID_TYPE_MESSAGE, INVALID_ALTER_ID_MESSAGE, INVALID_EMPTY_STRING_MESSAGE
|
|
11
11
|
from .params import INVALID_UUID_MESSAGE, INVALID_PORT_MESSAGE, INVALID_URI_FORMAT_MESSAGE
|
|
12
12
|
from .params import UNSUPPORTED_PROTOCOL_MESSAGE
|
|
@@ -166,10 +166,10 @@ def _validate_uri(uri: str) -> None:
|
|
|
166
166
|
raise V2kitParseError(INVALID_URI_FORMAT_MESSAGE)
|
|
167
167
|
parsed = urlparse(uri)
|
|
168
168
|
try:
|
|
169
|
-
|
|
169
|
+
SCHEME_TO_PROTOCOL[parsed.scheme]
|
|
170
170
|
except Exception as exc:
|
|
171
171
|
raise V2kitParseError(UNSUPPORTED_PROTOCOL_MESSAGE.format(protocol=parsed.scheme)) from exc
|
|
172
|
-
if parsed.scheme == Protocol.VMESS
|
|
172
|
+
if SCHEME_TO_PROTOCOL[parsed.scheme] == Protocol.VMESS:
|
|
173
173
|
try:
|
|
174
174
|
_, encoded = uri.split("://", 1)
|
|
175
175
|
encoded += "=" * (-len(encoded) % 4)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: v2kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: V2Kit: A Lightweight Toolkit for V2Ray Config Manipulation
|
|
5
5
|
Home-page: https://github.com/sepandhaghighi/v2kit
|
|
6
|
-
Download-URL: https://github.com/sepandhaghighi/v2kit/tarball/v0.
|
|
6
|
+
Download-URL: https://github.com/sepandhaghighi/v2kit/tarball/v0.5
|
|
7
7
|
Author: Sepand Haghighi
|
|
8
8
|
Author-email: me@sepand.tech
|
|
9
9
|
License: MIT
|
|
@@ -96,13 +96,13 @@ V2Kit is a lightweight and extensible Python toolkit for working with V2Ray prox
|
|
|
96
96
|
## Installation
|
|
97
97
|
|
|
98
98
|
### Source Code
|
|
99
|
-
- Download [Version 0.
|
|
99
|
+
- Download [Version 0.5](https://github.com/sepandhaghighi/v2kit/archive/v0.5.zip) or [Latest Source](https://github.com/sepandhaghighi/v2kit/archive/dev.zip)
|
|
100
100
|
- `pip install .`
|
|
101
101
|
|
|
102
102
|
### PyPI
|
|
103
103
|
|
|
104
104
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
105
|
-
- `pip install v2kit==0.
|
|
105
|
+
- `pip install v2kit==0.5`
|
|
106
106
|
|
|
107
107
|
## Supported Protocols
|
|
108
108
|
|
|
@@ -211,6 +211,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
211
211
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
212
212
|
|
|
213
213
|
## [Unreleased]
|
|
214
|
+
## [0.5] - 2026-07-31
|
|
215
|
+
### Added
|
|
216
|
+
- `encoded_label` property
|
|
217
|
+
- `uri_label` property
|
|
218
|
+
### Changed
|
|
219
|
+
- `encode_subscription` function modified
|
|
220
|
+
- `parse` function modified
|
|
221
|
+
- `extra` property modified
|
|
222
|
+
- `update_extra` method modified
|
|
223
|
+
- `to_uri` method modified
|
|
224
|
+
- `to_dict` method modified
|
|
225
|
+
- `Protocol` enum updated
|
|
226
|
+
- Scheme bug fixed
|
|
227
|
+
- VMESS parser bug fixed
|
|
228
|
+
- Test system modified
|
|
214
229
|
## [0.4] - 2026-07-02
|
|
215
230
|
### Added
|
|
216
231
|
- `HttpConfig` class
|
|
@@ -256,7 +271,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
256
271
|
- `encode_subscription` function
|
|
257
272
|
- `decode_subscription` function
|
|
258
273
|
|
|
259
|
-
[Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.
|
|
274
|
+
[Unreleased]: https://github.com/sepandhaghighi/v2kit/compare/v0.5...dev
|
|
275
|
+
[0.5]: https://github.com/sepandhaghighi/v2kit/compare/v0.4...v0.5
|
|
260
276
|
[0.4]: https://github.com/sepandhaghighi/v2kit/compare/v0.3...v0.4
|
|
261
277
|
[0.3]: https://github.com/sepandhaghighi/v2kit/compare/v0.2...v0.3
|
|
262
278
|
[0.2]: https://github.com/sepandhaghighi/v2kit/compare/v0.1...v0.2
|
|
File without changes
|
{v2kit-0.4 → v2kit-0.5}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|