Filter ransomware.live to LV/EE/LT, dedicated Telegram topic, harden fetcher
- New alert topic (/on_ransomware) separate from RSS malware articles, so a busy leak day doesn't bury other malware coverage. - Filter victims to Latvia/Estonia/Lithuania before they ever touch the seen-DB — the global feed is already on ransomware.live's own site. - Retention bumped to 14 days (bot_config.SEEN_RETENTION_DAYS). - Add is_first_run(): lets the caller distinguish a genuinely fresh seen-DB from a restart of an already-running bot. - Fix a naive-vs-aware datetime comparison that would TypeError-crash a poll cycle if the API ever returned a timestamp without a UTC offset. - Drop the unused _victim_id/iocs fields from to_article()'s output. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+20
-5
@@ -14,6 +14,8 @@ from typing import Dict, List, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
from html import escape
|
||||
|
||||
from bot_config import SEEN_RETENTION_DAYS, ALLOWED_RANSOMWARE_COUNTRIES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
API_BASE = "https://api-pro.ransomware.live"
|
||||
@@ -45,9 +47,15 @@ class RansomwareFetcher:
|
||||
"SELECT 1 FROM seen_victims WHERE victim_id = ?", (victim_id,)
|
||||
).fetchone() is not None
|
||||
|
||||
def is_first_run(self) -> bool:
|
||||
"""True if no victims have ever been recorded as seen (fresh DB)."""
|
||||
with sqlite3.connect(self.seen_file) as conn:
|
||||
count = conn.execute("SELECT COUNT(1) FROM seen_victims").fetchone()[0]
|
||||
return count == 0
|
||||
|
||||
def mark_seen(self, victim_id: str):
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
|
||||
cutoff = (datetime.now(timezone.utc) - timedelta(days=SEEN_RETENTION_DAYS)).isoformat()
|
||||
with sqlite3.connect(self.seen_file) as conn:
|
||||
conn.execute("DELETE FROM seen_victims WHERE seen_at < ?", (cutoff,))
|
||||
conn.execute(
|
||||
@@ -95,6 +103,13 @@ class RansomwareFetcher:
|
||||
victims = await self._fetch_month(session, year, month)
|
||||
all_victims.extend(victims)
|
||||
|
||||
# Only care about victims in these countries — the general global feed
|
||||
# is available directly on ransomware.live's own site.
|
||||
all_victims = [
|
||||
v for v in all_victims
|
||||
if (v.get("country") or "").strip().upper() in ALLOWED_RANSOMWARE_COUNTRIES
|
||||
]
|
||||
|
||||
new_victims = []
|
||||
for v in all_victims:
|
||||
discovered_str = v.get("discovered", "")
|
||||
@@ -104,6 +119,8 @@ class RansomwareFetcher:
|
||||
discovered_dt = datetime.fromisoformat(discovered_str.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
continue
|
||||
if discovered_dt.tzinfo is None:
|
||||
discovered_dt = discovered_dt.replace(tzinfo=timezone.utc)
|
||||
if discovered_dt < cutoff:
|
||||
continue
|
||||
|
||||
@@ -167,13 +184,11 @@ class RansomwareFetcher:
|
||||
"url": victim.get("permalink", ""),
|
||||
"published_human": published_human,
|
||||
"source": "ransomware.live",
|
||||
"category": "malware",
|
||||
"feed_type": "malware",
|
||||
"category": "ransomware",
|
||||
"feed_type": "ransomware",
|
||||
"thumbnail": screenshot,
|
||||
"cves": [],
|
||||
"threat_actors": [],
|
||||
"malware_families": [group.title()],
|
||||
"mitre_techniques": [],
|
||||
"iocs": {},
|
||||
"_victim_id": victim.get("id", ""),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user