swiftshadow 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
swiftshadow/__init__.py CHANGED
@@ -1,7 +1,8 @@
1
+ from asyncio import run
1
2
  from typing import Literal
2
- from swiftshadow.providers import Providers
3
+
3
4
  from swiftshadow.models import Proxy
4
- from asyncio import run
5
+ from swiftshadow.providers import Providers
5
6
 
6
7
 
7
8
  def QuickProxy(
swiftshadow/cache.py CHANGED
@@ -1,4 +1,4 @@
1
- from datetime import datetime, timezone, timedelta
1
+ from datetime import datetime, timedelta, timezone
2
2
 
3
3
 
4
4
  def getExpiry(timeToLive: int) -> datetime:
swiftshadow/classes.py CHANGED
@@ -5,13 +5,15 @@ from pathlib import Path
5
5
  from appdirs import user_cache_dir
6
6
  from logging import FileHandler, getLogger, Formatter, StreamHandler, INFO, DEBUG
7
7
  from sys import stdout
8
- from pickle import load, dump
8
+ from pickle import load, dump, loads, dumps
9
9
  from swiftshadow.cache import checkExpiry, getExpiry
10
10
  from swiftshadow.models import CacheData, Proxy as Proxy
11
11
 
12
12
  from swiftshadow.exceptions import UnsupportedProxyProtocol
13
13
  from swiftshadow.providers import Providers
14
14
  from asyncio import run
15
+ import aiofiles
16
+
15
17
 
16
18
  logger = getLogger("swiftshadow")
17
19
  logger.setLevel(INFO)
@@ -33,6 +35,7 @@ class ProxyInterface:
33
35
  protocol (Literal['https', 'http']): Proxy protocol to use. Defaults to 'http'.
34
36
  maxproxies (int): Maximum number of proxies to collect from providers. Defaults to 10.
35
37
  autorotate (bool): Whether to automatically rotate proxy on each get() call. Defaults to False.
38
+ autoUpdate (bool): Whether to automatically update proxies upon class initalisation. Defaults to True.
36
39
  cachePeriod (int): Number of minutes before cache is considered expired. Defaults to 10.
37
40
  cacheFolderPath (Path): Filesystem path for cache storage. Uses system cache dir by default.
38
41
  proxies (list[Proxy]): List of available proxy objects.
@@ -60,6 +63,7 @@ class ProxyInterface:
60
63
  protocol: Literal["https", "http"] = "http",
61
64
  maxProxies: int = 10,
62
65
  autoRotate: bool = False,
66
+ autoUpdate: bool = True,
63
67
  cachePeriod: int = 10,
64
68
  cacheFolderPath: Path | None = None,
65
69
  debug: bool = False,
@@ -72,6 +76,7 @@ class ProxyInterface:
72
76
  protocol: Proxy protocol to retrieve. Choose between 'http' or 'https'.
73
77
  maxProxies: Maximum proxies to collect from all providers combined.
74
78
  autoRotate: Enable automatic proxy rotation on every get() call.
79
+ autoUpdate (bool): Whether to automatically update proxies upon class initalisation.
75
80
  cachePeriod: Cache validity duration in minutes.
76
81
  cacheFolderPath: Custom path for cache storage. Uses system cache dir if None.
77
82
  debug: Enable debug logging level when True.
@@ -108,8 +113,74 @@ class ProxyInterface:
108
113
  self.proxies: list[Proxy] = []
109
114
  self.current: Proxy | None = None
110
115
  self.cacheExpiry: datetime | None = None
116
+ self.autoUpdate = autoUpdate
117
+
118
+ if self.autoUpdate:
119
+ self.update()
120
+
121
+ async def async_update(self):
122
+ """
123
+ Updates proxy list from providers or cache in async.
124
+
125
+ First attempts to load valid proxies from cache. If cache is expired/missing,
126
+ fetches fresh proxies from registered providers that match country and protocol filters.
127
+ Updates cache file with new proxies if fetched from providers.
128
+
129
+ Raises:
130
+ ValueError: If no proxies found after provider scraping.
131
+ """
132
+ try:
133
+ async with aiofiles.open(
134
+ self.cacheFolderPath.joinpath("swiftshadow.pickle"), "rb"
135
+ ) as cacheFile:
136
+ pickled_bytes = await cacheFile.read()
137
+ cache: CacheData = loads(pickled_bytes)
111
138
 
112
- self.update()
139
+ if not checkExpiry(cache.expiryIn):
140
+ self.proxies = cache.proxies
141
+ logger.info("Loaded proxies from cache.")
142
+ logger.debug(
143
+ f"Cache with {len(cache.proxies)} proxies, expire in {cache.expiryIn}"
144
+ )
145
+ self.current = self.proxies[0]
146
+ self.cacheExpiry = cache.expiryIn
147
+ logger.debug(f"Cache set to expire at {cache.expiryIn}")
148
+ return
149
+ else:
150
+ logger.info("Cache Expired")
151
+ except FileNotFoundError:
152
+ logger.info("No cache found, will be created after update.")
153
+
154
+ self.proxies = []
155
+
156
+ for provider in Providers:
157
+ if self.protocol not in provider.protocols:
158
+ continue
159
+ if (len(self.countries) != 0) and (not provider.countryFilter):
160
+ continue
161
+ providerProxies: list[Proxy] = await provider.providerFunction(
162
+ self.countries, self.protocol
163
+ )
164
+ logger.debug(
165
+ f"{len(providerProxies)} proxies from {provider.providerFunction.__name__}"
166
+ )
167
+ self.proxies.extend(providerProxies)
168
+
169
+ if len(self.proxies) >= self.maxproxies:
170
+ break
171
+
172
+ if len(self.proxies) == 0:
173
+ raise ValueError("No proxies where found for the current filter settings.")
174
+
175
+ async with aiofiles.open(
176
+ self.cacheFolderPath.joinpath("swiftshadow.pickle"), "wb+"
177
+ ) as cacheFile:
178
+ cacheExpiry = getExpiry(self.cachePeriod)
179
+ self.cacheExpiry = cacheExpiry
180
+ cache = CacheData(cacheExpiry, self.proxies)
181
+ pickled_bytes = dumps(cache)
182
+ _ = await cacheFile.write(pickled_bytes)
183
+ self.current = self.proxies[0]
113
184
 
114
185
  def update(self):
115
186
  """
@@ -135,6 +206,7 @@ class ProxyInterface:
135
206
  f"Cache with {len(cache.proxies)} proxies, expire in {cache.expiryIn}"
136
207
  )
137
208
  self.current = self.proxies[0]
209
+ logger.debug(f"Cache set to expire at {cache.expiryIn}")
138
210
  self.cacheExpiry = cache.expiryIn
139
211
  return
140
212
  else:
@@ -142,6 +214,8 @@ class ProxyInterface:
142
214
  except FileNotFoundError:
143
215
  logger.info("No cache found, will be created after update.")
144
216
 
217
+ self.proxies = []
218
+
145
219
  for provider in Providers:
146
220
  if self.protocol not in provider.protocols:
147
221
  continue
@@ -184,11 +258,13 @@ class ProxyInterface:
184
258
  Raises:
185
259
  ValueError: If validate_cache=True but no cache exists.
186
260
  """
187
- if self.cacheExpiry and not validate_cache:
188
- if checkExpiry(self.cacheExpiry):
189
- self.update()
190
- else:
191
- raise ValueError("No cache availabel but validate_cache is true.")
261
+ if validate_cache:
262
+ if self.cacheExpiry:
263
+ if checkExpiry(self.cacheExpiry):
264
+ logger.debug("Cache Expired on rotate call, updating.")
265
+ self.update()
266
+ else:
267
+ raise ValueError("No cache available but validate_cache is true.")
192
268
  self.current = choice(self.proxies)
193
269
 
194
270
  def get(self) -> Proxy:
@@ -206,7 +282,7 @@ class ProxyInterface:
206
282
  """
207
283
 
208
284
  if self.autorotate:
209
- self.rotate()
285
+ self.rotate(validate_cache=self.autoUpdate)
210
286
  if self.current:
211
287
  return self.current
212
288
  else:
swiftshadow/helpers.py CHANGED
@@ -2,6 +2,7 @@ from datetime import datetime
2
2
  from typing import Literal
3
3
 
4
4
  from requests import get
5
+
5
6
  from swiftshadow.models import Proxy
6
7
 
7
8
 
swiftshadow/providers.py CHANGED
@@ -3,7 +3,7 @@ from typing import Literal
3
3
  from requests import get
4
4
 
5
5
  from swiftshadow.helpers import plaintextToProxies
6
- from swiftshadow.models import Proxy, Provider
6
+ from swiftshadow.models import Provider, Proxy
7
7
  from swiftshadow.types import MonosansProxyDict
8
8
  from swiftshadow.validator import validate_proxies
9
9
 
swiftshadow/validator.py CHANGED
@@ -1,10 +1,47 @@
1
1
  import asyncio
2
+ import re
2
3
 
3
4
  import aiohttp
4
5
 
5
6
  from swiftshadow.models import Proxy
6
7
 
7
8
 
9
+ def find_ipv4_in_string(input_string: str) -> str | None:
10
+ """
11
+ Extract IPv4 Address from input and return the same.
12
+
13
+ Args:
14
+ input_string: Input string
15
+
16
+ Returns:
17
+ ipv4_address: If found
18
+ """
19
+ ipv4_pattern = r"\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b"
20
+
21
+ match = re.search(ipv4_pattern, input_string)
22
+
23
+ if match:
24
+ return match.group(0)
25
+ else:
26
+ return None
27
+
28
+
29
+ async def get_host_ip(async_session: aiohttp.ClientSession) -> str | None:
30
+ """
31
+ Gets the hosts external IP for validation.
32
+
33
+ Args:
34
+ async_session: AioHTTP client session object
35
+
36
+ Returns:
37
+ text: Host IP
38
+ """
39
+ async with async_session.get("http://checkip.amazonaws.com") as response:
40
+ text = await response.text()
41
+ ip = find_ipv4_in_string(text)
42
+ return ip
43
+
44
+
8
45
  async def check_proxy(async_session: aiohttp.ClientSession, proxy: Proxy) -> str:
9
46
  """
10
47
  Check one proxy abject.
@@ -38,13 +75,22 @@ async def validate_proxies(proxies: list[Proxy]) -> list[Proxy]:
38
75
  working_proxies: list[Proxy] = []
39
76
  async with aiohttp.ClientSession() as async_session:
40
77
  tasks = []
78
+
79
+ host_task = asyncio.create_task(coro=get_host_ip(async_session))
80
+ tasks.append(host_task)
81
+
41
82
  for proxy in proxies:
42
83
  task = asyncio.create_task(coro=check_proxy(async_session, proxy))
43
84
  tasks.append(task)
85
+
44
86
  results = await asyncio.gather(*tasks, return_exceptions=True)
87
+ host_ip = results[0]
88
+ results = results[1:]
89
+
45
90
  for proxy, result in zip(proxies, results):
46
91
  if type(result) is not str:
47
92
  continue
48
- if result.count(".") == 3:
93
+ result_ip = find_ipv4_in_string(result)
94
+ if result_ip and result_ip != host_ip:
49
95
  working_proxies.append(proxy)
50
96
  return working_proxies
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swiftshadow
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Free IP Proxy rotator for python
5
5
  Author-email: sachin-sankar <mail.sachinsankar@gmail.com>
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.12
8
+ Requires-Dist: aiofiles>=24.1.0
8
9
  Requires-Dist: aiohttp>=3.11.11
9
10
  Requires-Dist: appdirs>=1.4.4
10
11
  Requires-Dist: requests>=2.32.3
@@ -0,0 +1,14 @@
1
+ swiftshadow/__init__.py,sha256=oxJDmG0YO0XyYGQpa9V_o38lZnoie7U_d4Ik_h46UXs,922
2
+ swiftshadow/cache.py,sha256=Mg8xsD6K3K012sILBwD2EZH6CE5kWCQNKCfZ5yadalI,800
3
+ swiftshadow/classes.py,sha256=nTR_zykns2oxJ4Bj2UXxoXmLqNOMtkLqy17p_LH8odc,11423
4
+ swiftshadow/exceptions.py,sha256=qu4eXyrkWD9qd4HCIR-8vRfVcqLlTupo4sD72alCdug,129
5
+ swiftshadow/helpers.py,sha256=kC5PvfvDCQwigAmfkxlhb4PhcmgdCNpJksMiqyx9lU4,960
6
+ swiftshadow/models.py,sha256=YyfZV98tPdLnF1O3WmTNUNoK4t0GuchfEftzjiM03ck,1678
7
+ swiftshadow/providers.py,sha256=Z_NEOlMx0rr1Z5rfz-rA1kQ4YHKGRuI65jBxMGOyJkE,5660
8
+ swiftshadow/types.py,sha256=Alyw3n54OESX1vSR-0kTvpYTlJ8LKfy5J9WZbtglHpE,894
9
+ swiftshadow/validator.py,sha256=z0dmRKxhvPETSXfC2hTImL0t8pw0zjSYIhUaDMJcJhU,2469
10
+ swiftshadow-2.1.0.dist-info/METADATA,sha256=NTPUMBc5xlCEkcryOEC81Au_MCD8JPO8QxrhkEDo7AM,3293
11
+ swiftshadow-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ swiftshadow-2.1.0.dist-info/entry_points.txt,sha256=yMj0uEagcmXK2dmMmNXWebTpTT9j5K03oaRrd2wkyLA,49
13
+ swiftshadow-2.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
+ swiftshadow-2.1.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- swiftshadow/__init__.py,sha256=DCxCxaMrluQDVJLyK5K61NxSaadD1d-nyTrFKsjfNDI,921
2
- swiftshadow/cache.py,sha256=eA_AWa8EsPdB6xD__ButvQdqETC4i89qEgxmHQV2XWU,800
3
- swiftshadow/classes.py,sha256=4hr0h2aOb-CXYPrf_ESf05RSEdRVLmYtvx09npnUVhQ,8242
4
- swiftshadow/exceptions.py,sha256=qu4eXyrkWD9qd4HCIR-8vRfVcqLlTupo4sD72alCdug,129
5
- swiftshadow/helpers.py,sha256=hHJ_JjRx2UFC5Ircl75LeYKBNDYTY_xMy2iWCk-UPqo,959
6
- swiftshadow/models.py,sha256=YyfZV98tPdLnF1O3WmTNUNoK4t0GuchfEftzjiM03ck,1678
7
- swiftshadow/providers.py,sha256=myJ6t-WD20wrjc7qDhxCpX1-oi7ipQutp34XKM2tjeI,5660
8
- swiftshadow/types.py,sha256=Alyw3n54OESX1vSR-0kTvpYTlJ8LKfy5J9WZbtglHpE,894
9
- swiftshadow/validator.py,sha256=dBcb6Lev6ojsk9f45IjyEUHZXsCcylyn6RoZA1WPDNQ,1345
10
- swiftshadow-2.0.0.dist-info/METADATA,sha256=MSM2WB68lsN__C1JiNwMH2sPMah2GrPUoCwyNbPoF9U,3261
11
- swiftshadow-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- swiftshadow-2.0.0.dist-info/entry_points.txt,sha256=yMj0uEagcmXK2dmMmNXWebTpTT9j5K03oaRrd2wkyLA,49
13
- swiftshadow-2.0.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
- swiftshadow-2.0.0.dist-info/RECORD,,