97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""
|
|
In-process SOCKS5 bridge for Playwright — stable, no external processes.
|
|
Drop into any Playwright script that needs SOCKS5 proxy (Proxyshard.com, etc.)
|
|
|
|
Usage:
|
|
bridge = SocksBridge(sid="your-sid-here")
|
|
await bridge.start()
|
|
browser = await p.chromium.launch(
|
|
headless=False,
|
|
proxy={'server': bridge.url}, # http://127.0.0.1:<random_port>
|
|
)
|
|
...
|
|
bridge.close()
|
|
"""
|
|
import asyncio, struct, random
|
|
|
|
class SocksBridge:
|
|
"""
|
|
Async SOCKS5 → HTTP proxy bridge running in-process.
|
|
Handles CONNECT tunneling. Auth: username/password (RFC 1929).
|
|
"""
|
|
def __init__(self, sid, socks_host="relay-eu.proxyshard.com", socks_port=1080,
|
|
socks_pass="35qaexbj9fv8qjub", listen_port=None):
|
|
self.sid = sid
|
|
self.user = f"plan-premium-country-de-sid-{sid}-ttl-8000000"
|
|
self.passwd = [REDACTED]
|
|
self.socks_host = socks_host
|
|
self.socks_port = socks_port
|
|
self.lp = listen_port or random.randint(12000, 20000)
|
|
self.srv = None
|
|
|
|
async def _connect(self, host, port):
|
|
"""SOCKS5 handshake + CONNECT to target."""
|
|
r, w = await asyncio.open_connection(self.socks_host, self.socks_port)
|
|
# Greeting: SOCKS5 + 2 auth methods (noauth + user/pass)
|
|
w.write(b'\x05\x02\x00\x02')
|
|
await w.drain()
|
|
resp = await r.readexactly(2)
|
|
if resp[1] == 0x02:
|
|
# Auth required
|
|
ub = self.user.encode()
|
|
pb = self.passwd.encode()
|
|
w.write(b'\x01' + bytes([len(ub)]) + ub + bytes([len(pb)]) + pb)
|
|
await w.drain()
|
|
await r.readexactly(2)
|
|
# CONNECT request
|
|
hb = host.encode()
|
|
w.write(b'\x05\x01\x00\x03' + bytes([len(hb)]) + hb + struct.pack('>H', port))
|
|
await w.drain()
|
|
await r.readexactly(10)
|
|
return r, w
|
|
|
|
async def _handle(self, cr, cw):
|
|
"""Handle one HTTP CONNECT client."""
|
|
try:
|
|
req = await cr.readuntil(b'\r\n\r\n')
|
|
t = req.decode().split()[1]
|
|
host, ps = t.split(':') if ':' in t else (t, '443')
|
|
rr, rw = await self._connect(host, int(ps))
|
|
cw.write(b'HTTP/1.0 200 Connection Established\r\n\r\n')
|
|
await cw.drain()
|
|
|
|
async def relay(r1, w1):
|
|
try:
|
|
while True:
|
|
d = await r1.read(65536)
|
|
if not d:
|
|
break
|
|
w1.write(d)
|
|
await w1.drain()
|
|
except Exception:
|
|
pass
|
|
|
|
await asyncio.gather(relay(cr, rw), relay(rr, cw))
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
try:
|
|
cw.close()
|
|
except Exception:
|
|
pass
|
|
|
|
async def start(self):
|
|
"""Start listening. Returns port number."""
|
|
self.srv = await asyncio.start_server(self._handle, '127.0.0.1', self.lp)
|
|
return self.lp
|
|
|
|
@property
|
|
def url(self):
|
|
"""Proxy URL for Playwright proxy={'server': ...}."""
|
|
return f"http://127.0.0.1:{self.lp}"
|
|
|
|
def close(self):
|
|
"""Stop the bridge."""
|
|
if self.srv:
|
|
self.srv.close()
|