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 urllib.parse import urlparse
|
||||||
from html import escape
|
from html import escape
|
||||||
|
|
||||||
|
from bot_config import SEEN_RETENTION_DAYS, ALLOWED_RANSOMWARE_COUNTRIES
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
API_BASE = "https://api-pro.ransomware.live"
|
API_BASE = "https://api-pro.ransomware.live"
|
||||||
@@ -45,9 +47,15 @@ class RansomwareFetcher:
|
|||||||
"SELECT 1 FROM seen_victims WHERE victim_id = ?", (victim_id,)
|
"SELECT 1 FROM seen_victims WHERE victim_id = ?", (victim_id,)
|
||||||
).fetchone() is not None
|
).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):
|
def mark_seen(self, victim_id: str):
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
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:
|
with sqlite3.connect(self.seen_file) as conn:
|
||||||
conn.execute("DELETE FROM seen_victims WHERE seen_at < ?", (cutoff,))
|
conn.execute("DELETE FROM seen_victims WHERE seen_at < ?", (cutoff,))
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -95,6 +103,13 @@ class RansomwareFetcher:
|
|||||||
victims = await self._fetch_month(session, year, month)
|
victims = await self._fetch_month(session, year, month)
|
||||||
all_victims.extend(victims)
|
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 = []
|
new_victims = []
|
||||||
for v in all_victims:
|
for v in all_victims:
|
||||||
discovered_str = v.get("discovered", "")
|
discovered_str = v.get("discovered", "")
|
||||||
@@ -104,6 +119,8 @@ class RansomwareFetcher:
|
|||||||
discovered_dt = datetime.fromisoformat(discovered_str.replace("Z", "+00:00"))
|
discovered_dt = datetime.fromisoformat(discovered_str.replace("Z", "+00:00"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
if discovered_dt.tzinfo is None:
|
||||||
|
discovered_dt = discovered_dt.replace(tzinfo=timezone.utc)
|
||||||
if discovered_dt < cutoff:
|
if discovered_dt < cutoff:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -167,13 +184,11 @@ class RansomwareFetcher:
|
|||||||
"url": victim.get("permalink", ""),
|
"url": victim.get("permalink", ""),
|
||||||
"published_human": published_human,
|
"published_human": published_human,
|
||||||
"source": "ransomware.live",
|
"source": "ransomware.live",
|
||||||
"category": "malware",
|
"category": "ransomware",
|
||||||
"feed_type": "malware",
|
"feed_type": "ransomware",
|
||||||
"thumbnail": screenshot,
|
"thumbnail": screenshot,
|
||||||
"cves": [],
|
"cves": [],
|
||||||
"threat_actors": [],
|
"threat_actors": [],
|
||||||
"malware_families": [group.title()],
|
"malware_families": [group.title()],
|
||||||
"mitre_techniques": [],
|
"mitre_techniques": [],
|
||||||
"iocs": {},
|
|
||||||
"_victim_id": victim.get("id", ""),
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user