Skip to content

Commit 441f088

Browse files
kn1lvladkens
andauthored
Pass effective proxy to XClIdGen (#319)
Co-authored-by: vladkens <v.pronsky@gmail.com>
1 parent c0e81bf commit 441f088

6 files changed

Lines changed: 109 additions & 18 deletions

File tree

tests/test_http.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ def test_curl_client_strips_user_agent_from_session():
461461
def test_httpx_client_resolves_ua_hint_to_real_string():
462462
from twscrape.http import HttpxClient
463463

464-
client = HttpxClient(headers={"user-agent": "@firefox"})
464+
expected_ua, _ = _resolve_browser("@chrome", seed=0)
465+
client = HttpxClient(headers={"user-agent": "@chrome"}, seed=0)
465466
ua = dict(client._client.headers).get("user-agent", "")
466-
assert "Firefox/" in ua
467+
assert ua == expected_ua
467468
assert "@" not in ua

tests/test_queue_client.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import pytest
44

5+
from twscrape.account import Account
56
from twscrape.accounts_pool import AccountsPool
67
from twscrape.http import ConnectError, NetworkError
7-
from twscrape.queue_client import QueueClient
8+
from twscrape.queue_client import QueueClient, XClIdGenStore
89
from twscrape.utils import utc
910

1011
from .mock_http import MockClient
@@ -150,6 +151,48 @@ async def get_data_stream():
150151
assert client.ctx is None
151152

152153

154+
async def test_queue_client_passes_effective_proxy_to_xclid(pool_mock: AccountsPool, monkeypatch):
155+
mock = MockClient()
156+
seen = {}
157+
158+
class FakeXClIdGen:
159+
def calc(self, *args, **kwargs):
160+
return "mocked-clid"
161+
162+
async def fake_get(cls, username, proxy=None, fresh=False):
163+
seen["username"] = username
164+
seen["proxy"] = proxy
165+
seen["fresh"] = fresh
166+
return FakeXClIdGen()
167+
168+
monkeypatch.setattr(Account, "make_client", lambda self, proxy=None: mock)
169+
monkeypatch.setattr(XClIdGenStore, "get", classmethod(fake_get))
170+
171+
await pool_mock.add_account(
172+
"user1",
173+
"pass1",
174+
"email1",
175+
"email_pass1",
176+
proxy="127.0.0.1:7897",
177+
)
178+
await pool_mock.set_active("user1", True)
179+
180+
client = QueueClient(pool_mock, "SearchTimeline")
181+
await client.__aenter__()
182+
183+
mock.add_response(json={"ok": True})
184+
rep = await client.get(URL)
185+
186+
assert rep is not None
187+
assert seen == {
188+
"username": "user1",
189+
"proxy": "http://127.0.0.1:7897",
190+
"fresh": False,
191+
}
192+
193+
await client.__aexit__(None, None, None)
194+
195+
153196
# --- ConnectError ---
154197

155198

tests/test_xclid.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import twscrape.xclid as xclid
2+
3+
4+
class FakeClient:
5+
def __init__(self):
6+
self.closed = False
7+
8+
async def aclose(self):
9+
self.closed = True
10+
11+
12+
async def test_xclid_create_passes_proxy_to_client(monkeypatch):
13+
seen = {}
14+
fake_client = FakeClient()
15+
16+
def fake_make_client(*, headers=None, proxy=None):
17+
seen["headers"] = headers
18+
seen["proxy"] = proxy
19+
return fake_client
20+
21+
async def fake_get_tw_page_text(url, clt):
22+
seen["url"] = url
23+
seen["client"] = clt
24+
return "<html></html>"
25+
26+
async def fake_load_keys(soup, clt):
27+
seen["load_client"] = clt
28+
return [1, 2, 3], "anim-key"
29+
30+
monkeypatch.setattr(xclid, "_make_http_client", fake_make_client)
31+
monkeypatch.setattr(xclid, "get_tw_page_text", fake_get_tw_page_text)
32+
monkeypatch.setattr(xclid, "load_keys", fake_load_keys)
33+
34+
gen = await xclid.XClIdGen.create(proxy="http://127.0.0.1:7897")
35+
36+
assert gen.vk_bytes == [1, 2, 3]
37+
assert gen.anim_key == "anim-key"
38+
assert seen["headers"] == {"user-agent": "@chrome"}
39+
assert seen["proxy"] == "http://127.0.0.1:7897"
40+
assert seen["url"] == "https://x.com/tesla"
41+
assert seen["client"] is fake_client
42+
assert seen["load_client"] is fake_client
43+
assert fake_client.closed is True

twscrape/account.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ def to_rs(self):
5151
rs["last_used"] = rs["last_used"].isoformat() if rs["last_used"] else None
5252
return rs
5353

54-
def make_client(self, proxy: str | None = None) -> HttpClient:
54+
def resolve_proxy(self, proxy: str | None = None) -> str | None:
5555
proxies = [proxy, os.getenv("TWS_PROXY"), self.proxy]
5656
proxies = [x for x in proxies if x is not None]
57-
proxy = parse_proxy(proxies[0]) if proxies else None
57+
return parse_proxy(proxies[0]) if proxies else None
5858

59+
def make_client(self, proxy: str | None = None) -> HttpClient:
60+
proxy = self.resolve_proxy(proxy)
5961
headers = {**self.headers}
6062
headers["user-agent"] = self.user_agent
6163
headers["content-type"] = "application/json"

twscrape/queue_client.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ class AbortReqError(Exception): ...
2222

2323

2424
class XClIdGenStore:
25-
items: dict[str, XClIdGen] = {} # username -> XClIdGen
25+
items: dict[tuple[str, str | None], XClIdGen] = {} # (username, proxy) -> XClIdGen
2626

2727
@classmethod
28-
async def get(cls, username: str, fresh=False) -> XClIdGen:
29-
if username in cls.items and not fresh:
30-
return cls.items[username]
28+
async def get(cls, username: str, proxy: str | None = None, fresh=False) -> XClIdGen:
29+
key = (username, proxy)
30+
if key in cls.items and not fresh:
31+
return cls.items[key]
3132

3233
tries = 0
3334
while tries < 3:
3435
try:
35-
clid_gen = await XClIdGen.create()
36-
cls.items[username] = clid_gen
36+
clid_gen = await XClIdGen.create(proxy=proxy)
37+
cls.items[key] = clid_gen
3738
return clid_gen
3839
except Exception as e:
3940
tries += 1
@@ -48,10 +49,11 @@ async def get(cls, username: str, fresh=False) -> XClIdGen:
4849

4950

5051
class Ctx:
51-
def __init__(self, acc: Account, clt: HttpClient):
52+
def __init__(self, acc: Account, clt: HttpClient, proxy: str | None = None):
5253
self.req_count = 0
5354
self.acc = acc
5455
self.clt = clt
56+
self.proxy = proxy
5557

5658
async def aclose(self):
5759
await self.clt.aclose()
@@ -63,7 +65,7 @@ async def req(self, method: HttpMethod, url: str, params: ReqParams = None) -> R
6365

6466
tries = 0
6567
while tries < 3:
66-
gen = await XClIdGenStore.get(self.acc.username, fresh=tries > 0)
68+
gen = await XClIdGenStore.get(self.acc.username, proxy=self.proxy, fresh=tries > 0)
6769
hdr = {"x-client-transaction-id": gen.calc(method, path)}
6870
rep = await self.clt.request(method, url, params=params, headers=hdr)
6971
if rep.status_code != 404:
@@ -157,7 +159,7 @@ async def _get_ctx(self):
157159
return None
158160

159161
clt = acc.make_client(proxy=self.proxy)
160-
self.ctx = Ctx(acc, clt)
162+
self.ctx = Ctx(acc, clt, proxy=acc.resolve_proxy(self.proxy))
161163
return self.ctx
162164

163165
async def _check_rep(self, rep: Response) -> None:

twscrape/xclid.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from .http import make_client as _make_http_client
1414

1515

16-
def _make_client() -> HttpClient:
17-
return _make_http_client(headers={"user-agent": "@chrome"})
16+
def _make_client(proxy: str | None = None) -> HttpClient:
17+
return _make_http_client(headers={"user-agent": "@chrome"}, proxy=proxy)
1818

1919

2020
async def get_tw_page_text(url: str, clt: HttpClient):
@@ -315,8 +315,8 @@ async def load_keys(soup: bs4.BeautifulSoup, clt: HttpClient) -> tuple[list[int]
315315

316316
class XClIdGen:
317317
@staticmethod
318-
async def create() -> "XClIdGen":
319-
clt = _make_client()
318+
async def create(proxy: str | None = None) -> "XClIdGen":
319+
clt = _make_client(proxy=proxy)
320320
try:
321321
text = await get_tw_page_text("https://x.com/tesla", clt)
322322
soup = bs4.BeautifulSoup(text, "html.parser")

0 commit comments

Comments
 (0)