xn-auth 0.2.66__tar.gz → 0.2.68.dev1__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.
- xn_auth-0.2.68.dev1/.env.dist +3 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/PKG-INFO +4 -1
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/pyproject.toml +9 -1
- xn_auth-0.2.68.dev1/tests/test_proxy.py +86 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/x_auth/models.py +15 -30
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/x_auth/types.py +3 -5
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/xn_auth.egg-info/PKG-INFO +4 -1
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/xn_auth.egg-info/SOURCES.txt +1 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/xn_auth.egg-info/requires.txt +3 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/xn_auth.egg-info/scm_file_list.json +9 -8
- xn_auth-0.2.68.dev1/xn_auth.egg-info/scm_version.json +8 -0
- xn_auth-0.2.66/.env.dist +0 -1
- xn_auth-0.2.66/xn_auth.egg-info/scm_version.json +0 -8
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/.gitignore +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/.pre-commit-config.yaml +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/CLAUDE.md +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/README.md +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/makefile +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/setup.cfg +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/x_auth/controller.py +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/x_auth/enums.py +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/x_auth/exceptions.py +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/x_auth/middleware.py +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/xn_auth.egg-info/dependency_links.txt +0 -0
- {xn_auth-0.2.66 → xn_auth-0.2.68.dev1}/xn_auth.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xn-auth
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.68.dev1
|
|
4
4
|
Summary: Auth adapter for XN-Api framework
|
|
5
5
|
Author-email: Artemiev <mixartemev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,7 +17,10 @@ Requires-Dist: pyjwt
|
|
|
17
17
|
Requires-Dist: xn-model
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: build; extra == "dev"
|
|
20
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
20
21
|
Requires-Dist: python-dotenv; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
21
24
|
Requires-Dist: twine; extra == "dev"
|
|
22
25
|
|
|
23
26
|
# X-Auth
|
|
@@ -22,7 +22,10 @@ dependencies = [
|
|
|
22
22
|
[project.optional-dependencies]
|
|
23
23
|
dev = [
|
|
24
24
|
"build",
|
|
25
|
+
"pre-commit",
|
|
25
26
|
"python-dotenv",
|
|
27
|
+
"pytest",
|
|
28
|
+
"pytest-asyncio",
|
|
26
29
|
"twine"
|
|
27
30
|
]
|
|
28
31
|
|
|
@@ -42,4 +45,9 @@ version_scheme = "python-simplified-semver" # if "feature" in `branch_name` SEMV
|
|
|
42
45
|
local_scheme = "no-local-version"
|
|
43
46
|
|
|
44
47
|
[tool.ruff]
|
|
45
|
-
line-length = 120
|
|
48
|
+
line-length = 120
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
asyncio_mode = "strict"
|
|
52
|
+
markers = ["live: реально ходит в webshare, нужен WST"]
|
|
53
|
+
addopts = "-m 'not live'"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Реальные вызовы Proxy: живые запросы в webshare, load_list ещё и пишет в базу."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from os import getenv
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import pytest_asyncio
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
from tortoise import Tortoise
|
|
10
|
+
|
|
11
|
+
from x_auth.models import Proxy
|
|
12
|
+
from x_auth.types import Replacement
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
WST = getenv("WS_TOKEN")
|
|
17
|
+
|
|
18
|
+
pytestmark = pytest.mark.asyncio
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest_asyncio.fixture
|
|
22
|
+
async def db():
|
|
23
|
+
await Tortoise.init(
|
|
24
|
+
{
|
|
25
|
+
"connections": {"default": getenv("PG_DSN")},
|
|
26
|
+
"apps": {"models": {"models": ["x_auth.models"]}},
|
|
27
|
+
"use_tz": True,
|
|
28
|
+
"timezone": "UTC",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
yield
|
|
32
|
+
await Tortoise.close_connections()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def test_load_list(db):
|
|
36
|
+
prxs = await Proxy.get_list(WST)
|
|
37
|
+
assert prxs, "webshare вернул пустой список"
|
|
38
|
+
was = await Proxy.all().count()
|
|
39
|
+
|
|
40
|
+
await Proxy.load_list(WST)
|
|
41
|
+
|
|
42
|
+
print(f"\nиз webshare {len(prxs)} шт., в базе было {was}, стало {await Proxy.all().count()}")
|
|
43
|
+
for r in prxs:
|
|
44
|
+
prx = await Proxy.get_or_none(id=r.id)
|
|
45
|
+
assert prx, f"{r.host}:{r.port} (id={r.id}) не доехал до базы"
|
|
46
|
+
assert (prx.host, prx.port) == (r.host, r.port)
|
|
47
|
+
assert (prx.username, prx.password) == (r.username, r.password)
|
|
48
|
+
assert prx.valid is r.valid
|
|
49
|
+
# last_verification из webshare до базы не доезжает: TsTrait.updated_at это auto_now
|
|
50
|
+
assert prx.updated_at != r.updated_at
|
|
51
|
+
country = await prx.country
|
|
52
|
+
assert country.short == r.country_code
|
|
53
|
+
print(f" {prx.id} {country.short} {prx.str()} valid={prx.valid} upd={prx.updated_at:%F %T}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def test_load_list_idempotent(db):
|
|
57
|
+
"""Повторный прогон не должен плодить дубли: апсерт идёт по pk и по (host, port)."""
|
|
58
|
+
await Proxy.load_list(WST)
|
|
59
|
+
was = await Proxy.all().count()
|
|
60
|
+
|
|
61
|
+
await Proxy.load_list(WST)
|
|
62
|
+
|
|
63
|
+
assert await Proxy.all().count() == was
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def test_get_replaced():
|
|
67
|
+
"""В базу не ходит, поэтому без фикстуры db."""
|
|
68
|
+
reps = await Proxy.get_replaced(WST)
|
|
69
|
+
|
|
70
|
+
assert isinstance(reps, list)
|
|
71
|
+
if not reps:
|
|
72
|
+
pytest.skip("у аккаунта нет замен (count=0) - разбор ответа проверить не на чем")
|
|
73
|
+
|
|
74
|
+
print(f"\nзамен: {len(reps)}")
|
|
75
|
+
for r in reps:
|
|
76
|
+
print(f" {r.created_at:%F %T} {r.reason}: {r.proxy}:{r.proxy_port} ({r.proxy_country_code})")
|
|
77
|
+
print(f" -> {r.replaced_with}:{r.replaced_with_port} ({r.replaced_with_country_code})")
|
|
78
|
+
|
|
79
|
+
for r in reps:
|
|
80
|
+
assert isinstance(r, Replacement)
|
|
81
|
+
# reason в структуре Literal - на неизвестном значении convert упал бы ещё в get_replaced
|
|
82
|
+
assert r.reason in ("auto_invalidated", "auto_out_of_rotation")
|
|
83
|
+
assert r.proxy and r.proxy_port
|
|
84
|
+
assert r.replaced_with and r.replaced_with_port
|
|
85
|
+
assert len(r.proxy_country_code) == len(r.replaced_with_country_code) == 2
|
|
86
|
+
assert isinstance(r.created_at, datetime)
|
|
@@ -4,6 +4,7 @@ from aiogram.utils.web_app import WebAppUser
|
|
|
4
4
|
from aiohttp import ClientSession
|
|
5
5
|
from litestar.exceptions import NotFoundException
|
|
6
6
|
from msgspec import convert
|
|
7
|
+
from msgspec.structs import asdict
|
|
7
8
|
from pyrogram.enums.client_platform import ClientPlatform
|
|
8
9
|
from pyrogram.types import User as PyroUser
|
|
9
10
|
from aiogram.types import User as AioUser
|
|
@@ -117,21 +118,6 @@ class Proxy(Model, TsTrait):
|
|
|
117
118
|
class Meta:
|
|
118
119
|
unique_together = (("host", "port"),)
|
|
119
120
|
|
|
120
|
-
@staticmethod
|
|
121
|
-
async def load(wst: str):
|
|
122
|
-
async with ClientSession("https://proxy.webshare.io/api/v2/") as s:
|
|
123
|
-
resp = await s.post("download_token/proxy_list/", headers={"Authorization": f"Token {wst}"})
|
|
124
|
-
dtok = (await resp.json())["key"]
|
|
125
|
-
resp = await s.get(f"proxy/list/download/{dtok}/-/any/username/direct/-/")
|
|
126
|
-
res = (await resp.text()).split("\r\n")
|
|
127
|
-
proxies = [
|
|
128
|
-
Proxy(host=p[0], port=p[1], username=p[2], password=p[3], success=True)
|
|
129
|
-
for r in res
|
|
130
|
-
if r and (p := r.split(":"))
|
|
131
|
-
]
|
|
132
|
-
await Proxy.bulk_create(proxies, 10, False, ["username", "password", "success"], ["host", "port"])
|
|
133
|
-
return proxies
|
|
134
|
-
|
|
135
121
|
@staticmethod
|
|
136
122
|
async def get_list(wst: str) -> list[types.Proxy]:
|
|
137
123
|
async with ClientSession("https://proxy.webshare.io/api/v2/") as s:
|
|
@@ -146,19 +132,16 @@ class Proxy(Model, TsTrait):
|
|
|
146
132
|
ccl = cls._meta.fields_map["country"].related_model
|
|
147
133
|
cmap = dict(await ccl.filter(short__in=[p.country_code for p in prxs]).values_list("short", "id"))
|
|
148
134
|
for r in prxs:
|
|
149
|
-
df =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
await prx.update_from_dict({**df, "id": r.id}).save()
|
|
158
|
-
elif prx := await cls.get_or_none(id=r.id):
|
|
159
|
-
await prx.update_from_dict({**df, "host": r.proxy_address, "port": r.port}).save()
|
|
135
|
+
df = asdict(r) # имена полей структуры уже совпадают с колонками модели
|
|
136
|
+
pk, host, port = df.pop("id"), df.pop("host"), df.pop("port")
|
|
137
|
+
df["country_id"] = cmap[df.pop("country_code")]
|
|
138
|
+
# у webshare независимо меняются и id, и пара (host, port) - ищем по обоим уникальным ключам
|
|
139
|
+
if prx := await cls.get_or_none(host=host, port=port):
|
|
140
|
+
await prx.update_from_dict({**df, "id": pk}).save()
|
|
141
|
+
elif prx := await cls.get_or_none(id=pk):
|
|
142
|
+
await prx.update_from_dict({**df, "host": host, "port": port}).save()
|
|
160
143
|
else:
|
|
161
|
-
await cls.create(**df, host=
|
|
144
|
+
await cls.create(**df, id=pk, host=host, port=port)
|
|
162
145
|
|
|
163
146
|
@staticmethod
|
|
164
147
|
async def get_replaced(wst: str):
|
|
@@ -214,13 +197,15 @@ class Session(TortModel):
|
|
|
214
197
|
api_id: int
|
|
215
198
|
dc: ForeignKeyRelation[Dc] = ForeignKeyField("models.Dc", "sessions", on_update=CASCADE, db_default=2)
|
|
216
199
|
dc_id: int
|
|
217
|
-
|
|
200
|
+
# default дублирует db_default не зря: при явном pk tortoise вставляет строку целиком и шлёт по
|
|
201
|
+
# таким колонкам NULL вместо того, что б их опустить и дать сработать дефолту базы
|
|
202
|
+
test_mode = BooleanField(db_default=False, default=False)
|
|
218
203
|
auth_key = BinaryField(null=True)
|
|
219
|
-
date = IntField(db_default=0) # todo: refact to datetime?
|
|
204
|
+
date = IntField(db_default=0, default=0) # todo: refact to datetime?
|
|
220
205
|
user: OneToOneNullableRelation[Username] = OneToOneField("models.Username", "session", on_update=CASCADE, null=True)
|
|
221
206
|
user_id: int
|
|
222
207
|
is_bot = CharField(42, null=True)
|
|
223
|
-
state: dict = JSONField(db_default={})
|
|
208
|
+
state: dict = JSONField(db_default={}, default=dict)
|
|
224
209
|
proxy: ForeignKeyNullableRelation[Proxy] = ForeignKeyField("models.Proxy", "sessions", on_update=CASCADE, null=True)
|
|
225
210
|
|
|
226
211
|
peers: BackwardFKRelation["Peer"]
|
|
@@ -14,17 +14,15 @@ class AuthUser(Struct):
|
|
|
14
14
|
role: Role
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class Proxy(Struct):
|
|
17
|
+
class Proxy(Struct, rename={"host": "proxy_address", "updated_at": "last_verification"}):
|
|
18
18
|
id: str
|
|
19
19
|
username: str
|
|
20
20
|
password: str
|
|
21
|
-
|
|
21
|
+
host: str
|
|
22
22
|
port: int
|
|
23
23
|
valid: bool
|
|
24
|
-
|
|
24
|
+
updated_at: datetime
|
|
25
25
|
country_code: str
|
|
26
|
-
city_name: str
|
|
27
|
-
created_at: datetime
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
class Replacement(Struct):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xn-auth
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.68.dev1
|
|
4
4
|
Summary: Auth adapter for XN-Api framework
|
|
5
5
|
Author-email: Artemiev <mixartemev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -17,7 +17,10 @@ Requires-Dist: pyjwt
|
|
|
17
17
|
Requires-Dist: xn-model
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: build; extra == "dev"
|
|
20
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
20
21
|
Requires-Dist: python-dotenv; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
21
24
|
Requires-Dist: twine; extra == "dev"
|
|
22
25
|
|
|
23
26
|
# X-Auth
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": [
|
|
3
|
-
".pre-commit-config.yaml",
|
|
4
|
-
"makefile",
|
|
5
|
-
"pyproject.toml",
|
|
6
3
|
".env.dist",
|
|
7
|
-
"README.md",
|
|
8
4
|
".gitignore",
|
|
5
|
+
".pre-commit-config.yaml",
|
|
9
6
|
"CLAUDE.md",
|
|
10
|
-
"
|
|
7
|
+
"README.md",
|
|
8
|
+
"makefile",
|
|
9
|
+
"pyproject.toml",
|
|
10
|
+
"tests/test_proxy.py",
|
|
11
11
|
"x_auth/controller.py",
|
|
12
|
-
"x_auth/
|
|
13
|
-
"x_auth/types.py",
|
|
12
|
+
"x_auth/enums.py",
|
|
14
13
|
"x_auth/exceptions.py",
|
|
15
|
-
"x_auth/middleware.py"
|
|
14
|
+
"x_auth/middleware.py",
|
|
15
|
+
"x_auth/models.py",
|
|
16
|
+
"x_auth/types.py"
|
|
16
17
|
]
|
|
17
18
|
}
|
xn_auth-0.2.66/.env.dist
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VENV=venv
|
|
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
|