flask-Humanify 0.1.4__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- flask_humanify/__init__.py +1 -1
- flask_humanify/datasets/ai_dogs.pkl +0 -0
- flask_humanify/datasets/animals.pkl +0 -0
- flask_humanify/datasets/characters.pkl +0 -0
- flask_humanify/datasets/keys.pkl +0 -0
- flask_humanify/features/rate_limiter.py +1 -1
- flask_humanify/humanify.py +559 -20
- flask_humanify/memory_server.py +838 -0
- flask_humanify/templates/audio_challenge.html +208 -0
- flask_humanify/templates/grid_challenge.html +232 -0
- flask_humanify/templates/{oneclick_captcha.html → one_click_challenge.html} +4 -9
- flask_humanify/utils.py +488 -2
- {flask_humanify-0.1.4.dist-info → flask_humanify-0.2.1.dist-info}/METADATA +42 -4
- flask_humanify-0.2.1.dist-info/RECORD +20 -0
- flask_humanify/ipset.py +0 -315
- flask_humanify-0.1.4.dist-info/RECORD +0 -14
- {flask_humanify-0.1.4.dist-info → flask_humanify-0.2.1.dist-info}/WHEEL +0 -0
- {flask_humanify-0.1.4.dist-info → flask_humanify-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {flask_humanify-0.1.4.dist-info → flask_humanify-0.2.1.dist-info}/top_level.txt +0 -0
flask_humanify/ipset.py
DELETED
@@ -1,315 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import logging
|
3
|
-
import socket
|
4
|
-
import time
|
5
|
-
import threading
|
6
|
-
import os
|
7
|
-
import importlib.metadata
|
8
|
-
import importlib.resources
|
9
|
-
import urllib.request
|
10
|
-
from pathlib import Path
|
11
|
-
from typing import Dict, List, Optional, Tuple
|
12
|
-
from datetime import datetime, timedelta
|
13
|
-
from netaddr import IPNetwork, IPAddress
|
14
|
-
|
15
|
-
|
16
|
-
logger = logging.getLogger(__name__)
|
17
|
-
|
18
|
-
|
19
|
-
try:
|
20
|
-
importlib.metadata.distribution("flask-humanify")
|
21
|
-
BASE_DIR = importlib.resources.files("flask_humanify")
|
22
|
-
except importlib.metadata.PackageNotFoundError:
|
23
|
-
BASE_DIR = Path(__file__).parent
|
24
|
-
|
25
|
-
DATASET_DIR = BASE_DIR / "datasets"
|
26
|
-
if not DATASET_DIR.exists():
|
27
|
-
DATASET_DIR.mkdir(parents=True)
|
28
|
-
|
29
|
-
IPSET_DATA_PATH = str(DATASET_DIR / "ipset.json")
|
30
|
-
|
31
|
-
|
32
|
-
class IPSetMemoryServer:
|
33
|
-
"""A singleton memory server that manages IP sets and provides lookup functionality."""
|
34
|
-
|
35
|
-
_instance = None
|
36
|
-
_lock = threading.Lock()
|
37
|
-
|
38
|
-
def __new__(cls, port: int = 9876, data_path: Optional[str] = None):
|
39
|
-
if data_path is None:
|
40
|
-
data_path = IPSET_DATA_PATH
|
41
|
-
|
42
|
-
with cls._lock:
|
43
|
-
if cls._instance is None:
|
44
|
-
cls._instance = super(IPSetMemoryServer, cls).__new__(cls)
|
45
|
-
cls._instance.initialized = False
|
46
|
-
return cls._instance
|
47
|
-
|
48
|
-
def __init__(self, port: int = 9876, data_path: Optional[str] = None):
|
49
|
-
if data_path is None:
|
50
|
-
data_path = IPSET_DATA_PATH
|
51
|
-
|
52
|
-
if getattr(self, "initialized", False):
|
53
|
-
return
|
54
|
-
|
55
|
-
self.port = port
|
56
|
-
self.data_path = data_path
|
57
|
-
self.ip_to_groups: Dict[str, List[str]] = {}
|
58
|
-
self.cidrs_to_ips: Dict[IPNetwork, List[str]] = {}
|
59
|
-
self.last_update: Optional[datetime] = None
|
60
|
-
self.server_socket = None
|
61
|
-
self.server_thread = None
|
62
|
-
self.running = False
|
63
|
-
self.initialized = True
|
64
|
-
|
65
|
-
def is_server_running(self) -> bool:
|
66
|
-
"""Check if the server is already running on the specified port."""
|
67
|
-
try:
|
68
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
69
|
-
s.connect(("127.0.0.1", self.port))
|
70
|
-
return True
|
71
|
-
except (ConnectionRefusedError, socket.error):
|
72
|
-
return False
|
73
|
-
|
74
|
-
def download_data(self, force: bool = False) -> bool:
|
75
|
-
"""Download IP set data from GitHub and update the timestamp."""
|
76
|
-
try:
|
77
|
-
if not force and os.path.exists(self.data_path):
|
78
|
-
with open(self.data_path, "r", encoding="utf-8") as f:
|
79
|
-
try:
|
80
|
-
data = json.load(f)
|
81
|
-
if isinstance(data, dict) and "_timestamp" in data:
|
82
|
-
timestamp = datetime.fromisoformat(data["_timestamp"])
|
83
|
-
if datetime.now() - timestamp < timedelta(days=7):
|
84
|
-
return True
|
85
|
-
except (json.JSONDecodeError, KeyError, ValueError):
|
86
|
-
pass
|
87
|
-
|
88
|
-
url = "https://raw.githubusercontent.com/tn3w/IPSet/refs/heads/master/ipset.json"
|
89
|
-
with urllib.request.urlopen(url, timeout=30) as response:
|
90
|
-
response_data = response.read().decode("utf-8")
|
91
|
-
|
92
|
-
data = json.loads(response_data)
|
93
|
-
data["_timestamp"] = datetime.now().isoformat()
|
94
|
-
|
95
|
-
with open(self.data_path, "w", encoding="utf-8") as f:
|
96
|
-
json.dump(data, f)
|
97
|
-
|
98
|
-
return True
|
99
|
-
except Exception as e:
|
100
|
-
logger.error("Error downloading IP set data: %s", e)
|
101
|
-
return False
|
102
|
-
|
103
|
-
def load_data(self) -> bool:
|
104
|
-
"""Load IP set data into memory."""
|
105
|
-
try:
|
106
|
-
with open(self.data_path, "r", encoding="utf-8") as f:
|
107
|
-
data = json.load(f)
|
108
|
-
|
109
|
-
if "_timestamp" in data:
|
110
|
-
self.last_update = datetime.fromisoformat(data.pop("_timestamp"))
|
111
|
-
|
112
|
-
self.ip_to_groups = {}
|
113
|
-
self.cidrs_to_ips = {}
|
114
|
-
|
115
|
-
for group, ips in data.items():
|
116
|
-
for ip in ips:
|
117
|
-
if "/" in ip:
|
118
|
-
try:
|
119
|
-
ip_obj = IPNetwork(ip)
|
120
|
-
if ip_obj not in self.cidrs_to_ips:
|
121
|
-
self.cidrs_to_ips[ip_obj] = []
|
122
|
-
self.cidrs_to_ips[ip_obj].append(group)
|
123
|
-
except Exception:
|
124
|
-
continue
|
125
|
-
continue
|
126
|
-
|
127
|
-
if ip not in self.ip_to_groups:
|
128
|
-
self.ip_to_groups[ip] = []
|
129
|
-
self.ip_to_groups[ip].append(group)
|
130
|
-
|
131
|
-
return True
|
132
|
-
except Exception as e:
|
133
|
-
logger.error("Error loading IP set data: %s", e)
|
134
|
-
return False
|
135
|
-
|
136
|
-
def check_and_update_data(self) -> None:
|
137
|
-
"""Check if data needs updating and update if necessary."""
|
138
|
-
if self.last_update is None or datetime.now() - self.last_update > timedelta(
|
139
|
-
days=7
|
140
|
-
):
|
141
|
-
threading.Thread(target=self._async_update).start()
|
142
|
-
|
143
|
-
def _async_update(self) -> None:
|
144
|
-
"""Update data in the background without affecting current operations."""
|
145
|
-
if self.download_data(force=True):
|
146
|
-
self.load_data()
|
147
|
-
|
148
|
-
def find_matching_groups(self, ip: str) -> List[str]:
|
149
|
-
"""Find all groups matching the given IP."""
|
150
|
-
self.check_and_update_data()
|
151
|
-
|
152
|
-
matching_groups = self.ip_to_groups.get(ip, [])
|
153
|
-
|
154
|
-
try:
|
155
|
-
ip_obj = IPAddress(ip)
|
156
|
-
ip_version = ip_obj.version
|
157
|
-
|
158
|
-
for cidr, groups in self.cidrs_to_ips.items():
|
159
|
-
if cidr.version != ip_version:
|
160
|
-
continue
|
161
|
-
|
162
|
-
if ip_obj in cidr:
|
163
|
-
for group in groups:
|
164
|
-
if group not in matching_groups:
|
165
|
-
matching_groups.append(group)
|
166
|
-
|
167
|
-
except Exception:
|
168
|
-
return []
|
169
|
-
|
170
|
-
return matching_groups
|
171
|
-
|
172
|
-
def handle_client(
|
173
|
-
self, client_socket: socket.socket, addr: Tuple[str, int]
|
174
|
-
) -> None:
|
175
|
-
"""Handle client connection and queries."""
|
176
|
-
try:
|
177
|
-
while True:
|
178
|
-
data = client_socket.recv(1024).decode("utf-8").strip()
|
179
|
-
if not data:
|
180
|
-
break
|
181
|
-
|
182
|
-
result = self.find_matching_groups(data)
|
183
|
-
response = json.dumps(result)
|
184
|
-
client_socket.send(f"{response}\n".encode("utf-8"))
|
185
|
-
except Exception as e:
|
186
|
-
logger.error("Error handling client %s: %s", addr, e)
|
187
|
-
finally:
|
188
|
-
client_socket.close()
|
189
|
-
|
190
|
-
def run_server(self) -> None:
|
191
|
-
"""Run the memory server."""
|
192
|
-
if self.is_server_running():
|
193
|
-
logger.info("Server already running on port %s", self.port)
|
194
|
-
return
|
195
|
-
|
196
|
-
if not os.path.exists(self.data_path):
|
197
|
-
logger.info("IP data file not found at %s, downloading...", self.data_path)
|
198
|
-
if not self.download_data():
|
199
|
-
logger.error("Failed to download data, cannot start server")
|
200
|
-
return
|
201
|
-
|
202
|
-
if not self.load_data():
|
203
|
-
logger.error("Failed to load data, cannot start server")
|
204
|
-
return
|
205
|
-
|
206
|
-
self.check_and_update_data()
|
207
|
-
try:
|
208
|
-
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
209
|
-
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
210
|
-
self.server_socket.bind(("0.0.0.0", self.port))
|
211
|
-
self.server_socket.listen(10)
|
212
|
-
self.running = True
|
213
|
-
|
214
|
-
logger.info(
|
215
|
-
"Memory server started on port %s with data from %s",
|
216
|
-
self.port,
|
217
|
-
self.data_path,
|
218
|
-
)
|
219
|
-
|
220
|
-
while self.running:
|
221
|
-
try:
|
222
|
-
client_socket, addr = self.server_socket.accept()
|
223
|
-
client_thread = threading.Thread(
|
224
|
-
target=self.handle_client, args=(client_socket, addr)
|
225
|
-
)
|
226
|
-
client_thread.daemon = True
|
227
|
-
client_thread.start()
|
228
|
-
except Exception as e:
|
229
|
-
if self.running:
|
230
|
-
logger.error("Error accepting connection: %s", e)
|
231
|
-
|
232
|
-
except Exception as e:
|
233
|
-
logger.error("Server error: %s", e)
|
234
|
-
finally:
|
235
|
-
if self.server_socket:
|
236
|
-
self.server_socket.close()
|
237
|
-
|
238
|
-
def start(self) -> None:
|
239
|
-
"""Start the server in a background thread."""
|
240
|
-
if self.server_thread and self.server_thread.is_alive():
|
241
|
-
return
|
242
|
-
|
243
|
-
self.server_thread = threading.Thread(target=self.run_server)
|
244
|
-
self.server_thread.daemon = True
|
245
|
-
self.server_thread.start()
|
246
|
-
|
247
|
-
def stop(self) -> None:
|
248
|
-
"""Stop the server."""
|
249
|
-
self.running = False
|
250
|
-
if self.server_socket:
|
251
|
-
self.server_socket.close()
|
252
|
-
|
253
|
-
|
254
|
-
class IPSetClient:
|
255
|
-
"""Client to connect to the IPSetMemoryServer."""
|
256
|
-
|
257
|
-
def __init__(self, host: str = "127.0.0.1", port: int = 9876):
|
258
|
-
self.host = host
|
259
|
-
self.port = port
|
260
|
-
self.socket = None
|
261
|
-
|
262
|
-
def connect(self) -> bool:
|
263
|
-
"""Connect to the memory server."""
|
264
|
-
try:
|
265
|
-
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
266
|
-
self.socket.connect((self.host, self.port))
|
267
|
-
return True
|
268
|
-
except Exception as e:
|
269
|
-
logger.error("Failed to connect to memory server: %s", e)
|
270
|
-
return False
|
271
|
-
|
272
|
-
def lookup_ip(self, ip: str) -> List[str]:
|
273
|
-
"""Look up an IP in the memory server."""
|
274
|
-
if not self.socket:
|
275
|
-
if not self.connect():
|
276
|
-
return []
|
277
|
-
|
278
|
-
try:
|
279
|
-
if self.socket:
|
280
|
-
self.socket.send(f"{ip}\n".encode("utf-8"))
|
281
|
-
response = self.socket.recv(4096).decode("utf-8").strip()
|
282
|
-
return json.loads(response)
|
283
|
-
return []
|
284
|
-
except Exception as e:
|
285
|
-
logger.error("Error looking up IP: %s", e)
|
286
|
-
if self.connect():
|
287
|
-
try:
|
288
|
-
if self.socket:
|
289
|
-
self.socket.send(f"{ip}\n".encode("utf-8"))
|
290
|
-
response = self.socket.recv(4096).decode("utf-8").strip()
|
291
|
-
return json.loads(response)
|
292
|
-
except Exception:
|
293
|
-
pass
|
294
|
-
return []
|
295
|
-
|
296
|
-
def close(self) -> None:
|
297
|
-
"""Close the connection to the memory server."""
|
298
|
-
if self.socket:
|
299
|
-
try:
|
300
|
-
self.socket.close()
|
301
|
-
except Exception:
|
302
|
-
pass
|
303
|
-
self.socket = None
|
304
|
-
|
305
|
-
|
306
|
-
def ensure_server_running(port: int = 9876, data_path: Optional[str] = None) -> None:
|
307
|
-
"""Ensure that the memory server is running."""
|
308
|
-
if data_path is None:
|
309
|
-
data_path = IPSET_DATA_PATH
|
310
|
-
|
311
|
-
server = IPSetMemoryServer(port=port, data_path=data_path)
|
312
|
-
server.start()
|
313
|
-
|
314
|
-
while not server.is_server_running():
|
315
|
-
time.sleep(0.1)
|
@@ -1,14 +0,0 @@
|
|
1
|
-
flask_humanify/__init__.py,sha256=fZr2ga6pOarBfqOQfxFZp7HggAUAldqHfcTsogm9zOs,269
|
2
|
-
flask_humanify/humanify.py,sha256=nG90tCUeGIAHCPQ7iMVKBhIhPdmUqthYwGMG9PSECIk,4321
|
3
|
-
flask_humanify/ipset.py,sha256=ZZZRtgtddkZr2q1zuI6803hJQ8OodVHNdyZvZGqpmMI,10866
|
4
|
-
flask_humanify/utils.py,sha256=CJ4FPhNJ75FuWcAn_ZuZkqRa9HR3jVvYOu9NZaN4L1o,2461
|
5
|
-
flask_humanify/datasets/ipset.json,sha256=YNPqwI109lYkfvZeOPsoDH_dKJxOCs0G2nvx_s2mvqU,30601191
|
6
|
-
flask_humanify/features/rate_limiter.py,sha256=YhAh4xAZTiqdMbS028jA_v6FeZEB1v6JtbOVubjGZao,2203
|
7
|
-
flask_humanify/templates/access_denied.html,sha256=p8ea_9gvv83aYFHaVKKedmQr6M8Z7NyHJK_OT3jdTOs,3169
|
8
|
-
flask_humanify/templates/oneclick_captcha.html,sha256=v0UP5EHjdyOq0-hh_5JUYI6HuSHsQDx2vEkveosAyJU,6123
|
9
|
-
flask_humanify/templates/rate_limited.html,sha256=Bv98MoetuSJGpWkDaQfhl7JwcWJiGaG2gwqxvSphaTM,3114
|
10
|
-
flask_humanify-0.1.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
11
|
-
flask_humanify-0.1.4.dist-info/METADATA,sha256=C-kFzCarOgAjYURn7nYokvsPra5RXDvZTkF79nQYyeI,3053
|
12
|
-
flask_humanify-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
13
|
-
flask_humanify-0.1.4.dist-info/top_level.txt,sha256=9-c6uhxwCpPE3BJYge1Y9Z_bYmWitI0fY5RgqMiFWr0,15
|
14
|
-
flask_humanify-0.1.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|