Skip to content

Commit efd87cd

Browse files
committed
Initial Commit
0 parents  commit efd87cd

12 files changed

+266
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/venv/
2+
*/__pycache__
3+
/.idea/

PyRoxy/Exceptions/__init__.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class ProxyParseError(Exception):
2+
pass
3+
4+
5+
class ProxyInvalidPort(ProxyParseError):
6+
def __init__(self, port: int):
7+
ProxyParseError.__init__(self, "'%d' is too %s" % (port, "small" if port < 1 else "long"))
8+
9+
10+
11+
class ProxyInvalidHost(ProxyParseError):
12+
def __init__(self, host: str):
13+
ProxyParseError.__init__(self, "'%s' is an Invalid IP Address" % host)
14+

PyRoxy/GeoIP/Sqlite/COPYRIGHT.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Database and Contents Copyright (c) 2022 MaxMind, Inc.
5.5 MB
Binary file not shown.

PyRoxy/GeoIP/Sqlite/LICENSE.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Use of this MaxMind product is governed by MaxMind's GeoLite2 End User License Agreement, which can be viewed at https://www.maxmind.com/en/geolite2/eula.
2+
3+
This database incorporates GeoNames [https://www.geonames.org] geographical data, which is made available under the Creative Commons Attribution 4.0 License. To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/.

PyRoxy/GeoIP/__init__.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from pathlib import Path as _path
2+
from maxminddb import open_database as _open
3+
4+
__dir__ = _path(__file__).parent
5+
6+
_reader = _open(__dir__ / 'Sqlite/GeoLite2-Country.mmdb')
7+
8+
def get(ip: str):
9+
return _reader.get(ip)
10+
11+
12+
def get_with_prefix_len(ip: str):
13+
return _reader.get_with_prefix_len(ip)

PyRoxy/Tools/__init__.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from re import compile, IGNORECASE, MULTILINE
2+
from contextlib import suppress
3+
from os import urandom
4+
from socket import inet_ntop, inet_ntoa, AF_INET6
5+
from string import ascii_letters
6+
from struct import pack
7+
from struct import pack as data_pack
8+
from sys import maxsize
9+
from typing import Callable, Any, List
10+
11+
12+
class Random:
13+
latters: List[str] = list(ascii_letters)
14+
rand_str: Callable[[int], str] = lambda length=16: ''.join(Random.rand_choice(*Random.latters) for _ in range(length))
15+
rand_ipv4: Callable[[], str] = lambda: inet_ntoa(data_pack('>I', Random.rand_int(1, 0xffffffff)))
16+
rand_ipv6: Callable[[], str] = lambda: inet_ntop(AF_INET6, pack('>QQ', Random.rand_bits(64), Random.rand_bits(64)))
17+
rand_int: Callable[[int, int], int] = lambda minimum=0, maximum=maxsize: int(Random.rand_float(minimum, maximum))
18+
rand_choice: Callable[[List[Any]], Any] = lambda *data: data[(Random.rand_int(maximum=len(data) - 2) or 0)]
19+
rand: Callable[[], int] = lambda: (int.from_bytes(urandom(7), 'big') >> 3) * (2 ** -53)
20+
21+
@staticmethod
22+
def rand_bits(maximum: int = 255) -> int:
23+
numbytes = (maximum + 7) // 8
24+
return int.from_bytes(urandom(numbytes), 'big') >> (numbytes * 8 - maximum)
25+
26+
@staticmethod
27+
def rand_float(minimum: float = 0.0, maximum: float = (maxsize * 1.0)) -> float:
28+
with suppress(ZeroDivisionError):
29+
return abs((Random.rand() * maximum) % (minimum - (maximum + 1))) + minimum
30+
return 0.0
31+
32+
class Patterns:
33+
Port = compile("^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]"
34+
"{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$")
35+
IP = compile("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")
36+
IPPort = compile("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):(\d{1,5})")
37+
Proxy = compile(r"^(?:\[|)(?:\s+|)(?:socks[45]|http(?:s|))(?:[]|]|)(?:\s+|)(?:](?:\s+|)|\|(?:\s+|)|://(?:\s+|)|)"
38+
r"((?:(?:\d+.){3}\d+|\S+[.]\w{2,3}))"
39+
r"(?:[:]|)((?:(\d+)|))"
40+
r"(?::(.+):(.+)|)$", IGNORECASE | MULTILINE)
41+
URL = compile("\S+[.]\w{2,3}")

PyRoxy/__init__.py

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from contextlib import suppress
2+
from enum import IntEnum, auto
3+
from functools import partial
4+
from ipaddress import ip_address
5+
from pathlib import Path
6+
from socket import socket, SOCK_STREAM, AF_INET, gethostbyname
7+
from threading import Lock, Thread
8+
from typing import Match, AnyStr, Set, Collection
9+
10+
from maxminddb.types import RecordDict
11+
from requests import Session
12+
from socks import socksocket, SOCKS4, SOCKS5, HTTP
13+
from yarl import URL
14+
15+
from PyRoxy import GeoIP, Tools
16+
from PyRoxy.Exceptions import ProxyInvalidHost, ProxyInvalidPort, ProxyParseError
17+
from PyRoxy.Tools import Patterns
18+
19+
20+
class ProxyType(IntEnum):
21+
HTTP = auto()
22+
HTTPS = auto()
23+
SOCKS4 = auto()
24+
SOCKS5 = auto()
25+
26+
def asPySocksType(self):
27+
return SOCKS5 if self == ProxyType.SOCKS5 else \
28+
SOCKS4 if self == ProxyType.SOCKS4 else \
29+
HTTP
30+
31+
@staticmethod
32+
def stringToProxyType(n: str):
33+
return ProxyType.HTTP if not (n.isdigit() and not (int(n) == 1)) else \
34+
int(n) if int(n) in PRINTABLE_PROXY_TYPES else \
35+
ProxyType.SOCKS5 if int(n) == 5 else \
36+
ProxyType.SOCKS4 if int(n) == 4 else \
37+
ProxyType.HTTP
38+
39+
40+
PROXY_TYPES = {"SOCKS4": ProxyType.SOCKS4, "SOCKS5": ProxyType.SOCKS5, "HTTP": ProxyType.HTTP, "HTTPS": ProxyType.HTTPS}
41+
PRINTABLE_PROXY_TYPES = dict(zip(PROXY_TYPES.values(), PROXY_TYPES.keys()))
42+
43+
44+
class Proxy(object):
45+
user: AnyStr | None
46+
password: AnyStr | None
47+
country: AnyStr | RecordDict | None
48+
port: int
49+
type: ProxyType
50+
host: AnyStr
51+
52+
def __init__(self, host: str, port: int = 0, proxy_type: ProxyType = ProxyType.HTTP, user=None, password=None):
53+
if Patterns.URL.match(host): host = gethostbyname(host)
54+
assert self.validate(host, port)
55+
self.host = host
56+
self.type = proxy_type
57+
self.port = port
58+
self.country = GeoIP.get(host)
59+
if self.country: self.country = self.country["country"]["iso_code"]
60+
self.user = user or None
61+
self.password = password or None
62+
63+
def __str__(self):
64+
return "%s://%s:%d%s" % (self.type.name.lower(), self.host, self.port, ("%s:%s" % (self.user, self.password)
65+
if self.password and self.user else ""))
66+
67+
def __repr__(self):
68+
return "<Proxy %s:%d>" % (self.host, self.port)
69+
70+
@staticmethod
71+
def fromString(string: str):
72+
with suppress(KeyboardInterrupt):
73+
proxy: Match[str] | None = Patterns.Proxy.search(string)
74+
return Proxy(proxy.group(1),
75+
int(proxy.group(2)) if proxy.group(3) and proxy.group(2).isdigit() else 80,
76+
ProxyType.stringToProxyType(proxy.group(1)),
77+
proxy.group(3),
78+
proxy.group(4))
79+
raise ProxyParseError("'%s' is an Invalid Proxy URL" % string)
80+
81+
def ip_port(self):
82+
return "%s:%d" % (self.host, self.port)
83+
84+
@staticmethod
85+
def validate(host: str, port: int):
86+
with suppress(ValueError):
87+
if not ip_address(host):
88+
raise ProxyInvalidHost(host)
89+
if not Tools.Patterns.Port.match(str(port)):
90+
raise ProxyInvalidPort(port)
91+
return True
92+
raise ProxyInvalidHost(host)
93+
94+
# noinspection PyShadowingBuiltins
95+
def open_socket(self, family=AF_INET, type=SOCK_STREAM, proto=-1, fileno=None):
96+
return ProxySocket(self, family, type, proto, fileno)
97+
98+
def wrap(self, sock: socket | Session):
99+
if isinstance(sock, socket):
100+
return self.open_socket(sock.family, sock.type, sock.proto, sock.fileno())
101+
sock.proxies = self.asRequest()
102+
return sock
103+
104+
def asRequest(self):
105+
return {"http": self.__str__()}
106+
107+
# noinspection PyUnreachableCode
108+
def check(self, url: str | URL = "https://httpbin.org/get", timeout=5):
109+
if not isinstance(url, URL): url = URL(url)
110+
with suppress(KeyboardInterrupt):
111+
with self.open_socket() as sock:
112+
sock.settimeout(timeout)
113+
return sock.connect((url.host, url.port or 80))
114+
return False
115+
116+
117+
# noinspection PyShadowingBuiltins
118+
class ProxySocket(socksocket):
119+
def __init__(self, proxy: Proxy, family=-1, type=-1, proto=-1, fileno=None):
120+
super().__init__(family, type, proto, fileno)
121+
if proxy.port:
122+
if proxy.user and proxy.password:
123+
self.setproxy(proxy.type.asPySocksType(), proxy.host, proxy.port, username=proxy.user,
124+
password=proxy.password)
125+
return
126+
self.setproxy(proxy.type.asPySocksType(), proxy.host, proxy.port)
127+
return
128+
if proxy.user and proxy.password:
129+
self.setproxy(proxy.type.asPySocksType(), proxy.host, username=proxy.user,
130+
password=proxy.password)
131+
return
132+
self.setproxy(proxy.type.asPySocksType(), proxy.host)
133+
134+
135+
class ProxyChecker:
136+
result: Set[Proxy]
137+
out_lock: Lock
138+
139+
def __init__(self):
140+
self.out_lock = Lock()
141+
self.result = set()
142+
143+
def check(self, proxy: Proxy, url: str | URL = "https://httpbin.org/get", timeout=5):
144+
with suppress(Exception), self.out_lock:
145+
if proxy.check(url, timeout):
146+
self.result.add(proxy)
147+
148+
def checkAll(self, proxies: Collection[Proxy], url: str | URL = "https://httpbin.org/get", timeout=5):
149+
threads = map(partial(Thread, target=self.check, args=(url, timeout,)), proxies)
150+
for thr in threads:
151+
thr.start()
152+
for thr in threads:
153+
thr.join()
154+
155+
156+
class ProxyUtiles:
157+
@staticmethod
158+
def parseAll(proxies: Collection[str]) -> Set[Proxy]:
159+
return set(map(Proxy.fromString, proxies))
160+
161+
@staticmethod
162+
def readFromFile(path: Path | str) -> Set[Proxy]:
163+
if isinstance(path, Path):
164+
with path.open("r+") as read:
165+
lines = read.readlines()
166+
else:
167+
with open(path, "r+") as read:
168+
lines = read.readlines()
169+
170+
return ProxyUtiles.parseAll([prox.strip() for prox in lines])

requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
maxminddb>=2.2.0
2+
requests>=2.27.1
3+
yarl>=1.7.2
4+
pysocks>=1.7.1

setup.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='PyRoxy',
5+
version='',
6+
packages=['PyRoxy', 'PyRoxy.GeoIP', 'PyRoxy.Tools', 'PyRoxy.Exceptions'],
7+
url='https://github.com/MHProDev/PyRoxy',
8+
license='',
9+
author='MH_ProDev',
10+
author_email='',
11+
description=''
12+
)

test.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from PyRoxy import Proxy, ProxyType, ProxyUtiles
2+
3+
if __name__ == '__main__':
4+
print(ProxyUtiles.readFromFile("test.txt"))

test.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[socks5] 209.127.191.180:9279:eclahjmw:sis9bjnxuds5

0 commit comments

Comments
 (0)