darkwatch 0.5.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.
- darkwatch/__init__.py +3 -0
- darkwatch/__main__.py +5 -0
- darkwatch/cache.py +100 -0
- darkwatch/cli.py +896 -0
- darkwatch/config.py +558 -0
- darkwatch/data/__init__.py +0 -0
- darkwatch/data/telegram_channels.json +5400 -0
- darkwatch/desktop.py +125 -0
- darkwatch/fetch.py +196 -0
- darkwatch/matcher.py +347 -0
- darkwatch/notify.py +246 -0
- darkwatch/report.py +425 -0
- darkwatch/scanner.py +222 -0
- darkwatch/schedule.py +232 -0
- darkwatch/search.py +359 -0
- darkwatch/sources/__init__.py +161 -0
- darkwatch/sources/ahmia.py +351 -0
- darkwatch/sources/hibp.py +140 -0
- darkwatch/sources/leakcheck.py +110 -0
- darkwatch/sources/leaksites.py +196 -0
- darkwatch/sources/recentattacks.py +88 -0
- darkwatch/sources/seeds.py +92 -0
- darkwatch/sources/sites.py +225 -0
- darkwatch/sources/stealers.py +151 -0
- darkwatch/sources/telegram.py +145 -0
- darkwatch/sources/xposedornot.py +195 -0
- darkwatch/storage.py +382 -0
- darkwatch/tor.py +227 -0
- darkwatch/web/__init__.py +7 -0
- darkwatch/web/jobs.py +93 -0
- darkwatch/web/server.py +375 -0
- darkwatch/web/static/app.css +323 -0
- darkwatch/web/static/app.js +730 -0
- darkwatch/web/static/favicon.svg +6 -0
- darkwatch/web/static/index.html +176 -0
- darkwatch/web/static/shader.js +205 -0
- darkwatch/web/static/vendor/DrawSVGPlugin.min.js +11 -0
- darkwatch/web/static/vendor/Flip.min.js +11 -0
- darkwatch/web/static/vendor/ScrambleTextPlugin.min.js +11 -0
- darkwatch/web/static/vendor/ScrollTrigger.min.js +11 -0
- darkwatch/web/static/vendor/gsap.min.js +11 -0
- darkwatch-0.5.1.dist-info/METADATA +351 -0
- darkwatch-0.5.1.dist-info/RECORD +46 -0
- darkwatch-0.5.1.dist-info/WHEEL +4 -0
- darkwatch-0.5.1.dist-info/entry_points.txt +2 -0
- darkwatch-0.5.1.dist-info/licenses/LICENSE +21 -0
darkwatch/__init__.py
ADDED
darkwatch/__main__.py
ADDED
darkwatch/cache.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""On-disk cache for bulk feeds, with conditional GET (ETag / Last-Modified) and a max age.
|
|
2
|
+
|
|
3
|
+
A feed younger than `max_age_hours` is served from disk with no network call. An older one is
|
|
4
|
+
revalidated; a 304 just refreshes the timestamp. If the network fails and a cached copy exists,
|
|
5
|
+
the stale copy is used and the caller is told so, which keeps a run useful offline.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
|
|
19
|
+
log = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CachedFeed:
|
|
24
|
+
body: bytes
|
|
25
|
+
fetched_at: float # epoch seconds of the last successful download or revalidation
|
|
26
|
+
from_network: bool # True if bytes were downloaded this call
|
|
27
|
+
stale: bool # True if the network failed and an old copy was used
|
|
28
|
+
note: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FeedCache:
|
|
32
|
+
def __init__(self, directory: str | Path):
|
|
33
|
+
self.dir = Path(directory)
|
|
34
|
+
self.dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
def _paths(self, name: str) -> tuple[Path, Path]:
|
|
37
|
+
return self.dir / f"{name}.body", self.dir / f"{name}.meta.json"
|
|
38
|
+
|
|
39
|
+
def get(
|
|
40
|
+
self,
|
|
41
|
+
name: str,
|
|
42
|
+
url: str,
|
|
43
|
+
session: requests.Session,
|
|
44
|
+
*,
|
|
45
|
+
max_age_hours: float,
|
|
46
|
+
timeout: int = 120,
|
|
47
|
+
max_bytes: int = 200_000_000,
|
|
48
|
+
validate: Callable[[bytes], None] | None = None,
|
|
49
|
+
) -> CachedFeed:
|
|
50
|
+
"""`validate(body)` raises ValueError for a body that must not replace the cached copy
|
|
51
|
+
(an HTML maintenance page, an empty body, a JSON error object)."""
|
|
52
|
+
body_p, meta_p = self._paths(name)
|
|
53
|
+
meta: dict = {}
|
|
54
|
+
if meta_p.exists() and body_p.exists():
|
|
55
|
+
try:
|
|
56
|
+
meta = json.loads(meta_p.read_text(encoding="utf-8"))
|
|
57
|
+
except ValueError:
|
|
58
|
+
meta = {}
|
|
59
|
+
now = time.time()
|
|
60
|
+
if meta and now - meta.get("fetched_at", 0) < max_age_hours * 3600:
|
|
61
|
+
return CachedFeed(body_p.read_bytes(), meta["fetched_at"], False, False, "fresh cache")
|
|
62
|
+
|
|
63
|
+
headers = {}
|
|
64
|
+
if meta.get("etag"):
|
|
65
|
+
headers["If-None-Match"] = meta["etag"]
|
|
66
|
+
if meta.get("last_modified"):
|
|
67
|
+
headers["If-Modified-Since"] = meta["last_modified"]
|
|
68
|
+
try:
|
|
69
|
+
with session.get(url, headers=headers, timeout=timeout, stream=True) as r:
|
|
70
|
+
if r.status_code == 304 and meta:
|
|
71
|
+
meta["fetched_at"] = now
|
|
72
|
+
meta_p.write_text(json.dumps(meta), encoding="utf-8")
|
|
73
|
+
return CachedFeed(body_p.read_bytes(), now, False, False, "not modified")
|
|
74
|
+
r.raise_for_status()
|
|
75
|
+
buf = bytearray()
|
|
76
|
+
for chunk in r.iter_content(chunk_size=256 * 1024):
|
|
77
|
+
buf.extend(chunk)
|
|
78
|
+
if len(buf) > max_bytes:
|
|
79
|
+
raise ValueError(f"feed {name} exceeds {max_bytes} bytes")
|
|
80
|
+
if validate is not None:
|
|
81
|
+
validate(bytes(buf))
|
|
82
|
+
tmp = body_p.with_suffix(".part")
|
|
83
|
+
tmp.write_bytes(bytes(buf))
|
|
84
|
+
tmp.replace(body_p)
|
|
85
|
+
meta = {
|
|
86
|
+
"url": url,
|
|
87
|
+
"fetched_at": now,
|
|
88
|
+
"etag": r.headers.get("ETag", ""),
|
|
89
|
+
"last_modified": r.headers.get("Last-Modified", ""),
|
|
90
|
+
"bytes": len(buf),
|
|
91
|
+
}
|
|
92
|
+
meta_p.write_text(json.dumps(meta), encoding="utf-8")
|
|
93
|
+
return CachedFeed(bytes(buf), now, True, False, f"downloaded {len(buf)} bytes")
|
|
94
|
+
except (requests.RequestException, ValueError) as exc:
|
|
95
|
+
if body_p.exists():
|
|
96
|
+
log.warning("feed %s refresh failed (%s); using cached copy", name, exc)
|
|
97
|
+
return CachedFeed(
|
|
98
|
+
body_p.read_bytes(), meta.get("fetched_at", 0), False, True, f"stale: {exc}"
|
|
99
|
+
)
|
|
100
|
+
raise
|