rugmuncher-backend / backend /app /audit_report_validator.py
RMI Platform
Nightly Builder v3: Fake Audit Report Validator (audit_report_validator.py)
3b769da
Raw
History Blame Contribute Delete
30.6 kB
"""
Fake Audit Report / Security Review Validator
=============================================
Validates security audit report claims made by tokens/projects.
Detects forged audit reports from Certik, Hacken, SlowMist, and
other major security firms used to promote scam tokens.
Signals detected:
- Forged report IDs/hashes that don't match auditor databases
- Metadata anomalies (dates, auditor name misspellings, logo mismatches)
- Template reuse β€” similar report text across unrelated projects
- URL/domain analysis for fake audit hosting pages
- Claim-vs-reality discrepancy (audit claims "safe" but contract is malicious)
- Report timeline anomalies (audit after deploy, future dates)
- Standard language detection (generic copy-paste report text)
- Invite-only audit scams (non-existent "Certik Priority" programs)
- Verified badge farming via fake audit blogs
- Known fake auditor wallet addresses deploying tokens
- Cross-referencing with public auditor verified lists
- Dashboard embed scams (iframe fake audit dashboards)
Tier : Premium ($0.08)
Price : 80000 atoms
Endpoint: POST /api/v1/x402-tools/audit_validate
"""
import json
import logging
import re
import time
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any
from urllib.parse import urlparse
import httpx
logger = logging.getLogger(__name__)
# ── Constants ──────────────────────────────────────────────────────
KNOWN_AUDITORS: dict[str, dict[str, Any]] = {
"certik": {
"name": "Certik",
"url": "https://www.certik.com",
"verify_url": "https://www.certik.com/projects/{}",
"known_domains": ["certik.com", "certik.org", "certik.io", "skynet.certik.com"],
},
"hacken": {
"name": "Hacken",
"url": "https://hacken.io",
"verify_url": "https://hacken.io/audits/#{}",
"known_domains": ["hacken.io", "hacken.com", "proofofhacken.io"],
},
"slowmist": {
"name": "SlowMist",
"url": "https://www.slowmist.com",
"verify_url": "https://www.slowmist.com/en/audit-{}.html",
"known_domains": ["slowmist.com", "slowmist.io"],
},
"trailofbits": {
"name": "Trail of Bits",
"url": "https://www.trailofbits.com",
"verify_url": "https://blog.trailofbits.com/?s={}",
"known_domains": ["trailofbits.com"],
},
"consensys": {
"name": "ConsenSys Diligence",
"url": "https://consensys.io/diligence",
"verify_url": "https://consensys.io/diligence/audits/{}",
"known_domains": ["consensys.io", "diligence.consensys.io"],
},
"openzeppelin": {
"name": "OpenZeppelin",
"url": "https://www.openzeppelin.com/security-audits",
"verify_url": "https://blog.openzeppelin.com/{}",
"known_domains": ["openzeppelin.com", "docs.openzeppelin.com"],
},
"quantstamp": {
"name": "Quantstamp",
"url": "https://quantstamp.com",
"verify_url": "https://quantstamp.com/audit/{}",
"known_domains": ["quantstamp.com"],
},
"peckshield": {
"name": "PeckShield",
"url": "https://peckshield.com",
"verify_url": "https://peckshield.com/audit/{}",
"known_domains": ["peckshield.com", "peckshield.io"],
},
"salus": {
"name": "Salus Security",
"url": "https://salusec.io",
"verify_url": "https://salusec.io/audit-reports/{}",
"known_domains": ["salusec.io", "salus.xyz"],
},
"verichains": {
"name": "Verichains",
"url": "https://www.verichains.io",
"verify_url": "https://www.verichains.io/audits/{}",
"known_domains": ["verichains.io"],
},
"solidproof": {
"name": "SolidProof",
"url": "https://solidproof.io",
"verify_url": "https://github.com/solidproof/projects/{}",
"known_domains": ["solidproof.io"],
},
"rugdoc": {
"name": "RugDoc",
"url": "https://rugdoc.io",
"verify_url": "https://rugdoc.io/audit/{}",
"known_domains": ["rugdoc.io"],
},
"goplus": {
"name": "GoPlus Security",
"url": "https://gopluslabs.io",
"verify_url": "https://gopluslabs.io/audits/{}",
"known_domains": ["gopluslabs.io"],
},
}
# Fake auditor names commonly seen in scam tokens
FAKE_AUDITOR_PATTERNS: list[str] = [
"certik", "certick", "certi.guide", "certik.pro", "certik-verify",
"hacken", "hackenpro", "hacken-verify", "hackenaudit",
"slowmist", "slow-mist", "slowmistpro", "slowmist.io",
"solidproof", "solid-proof", "solid.proof",
"audited", "secured by", "verified by",
"pangolin audit", "mythx certified", "mythril scanned",
"goplus", "gopluslabs",
]
# Generic / template report sentences that suggest copy-paste audit
TEMPLATE_PHRASES: list[str] = [
"we have thoroughly reviewed the smart contract",
"no critical vulnerabilities were found",
"the contract appears to be secure",
"our team of experienced auditors",
"this report is provided as is",
"the audit does not guarantee",
"all findings have been resolved",
"the code follows best practices",
"we found no security issues",
"the project has passed our security review",
"this confirms the safety of the contract",
"we have completed the security audit",
"the token contract has been audited",
"no centralization risks found",
"liquidity is locked permanently",
"ownership has been renounced",
]
SUSPICIOUS_TLD_PATTERNS: list[str] = [".xyz", ".top", ".loan", ".click", ".work", ".gq", ".tk", ".ml", ".cf"]
# Maximum allowed difference between deploy and audit claim dates (days)
MAX_AUDIT_ANTEDATE_DAYS = 7 # audit before deploy is suspicious
MAX_AUDIT_POSTDATE_DAYS = 180 # audit more than 6 months after deploy unlikely
SENTENCE_END_RE = re.compile(r"[.!?]\s+")
# ── Risk Levels ──────────────────────────────────────────────────
class AuditRisk(Enum):
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
SAFE = "safe"
class SignalType(Enum):
FORGED_REPORT = "forged_report"
METADATA_ANOMALY = "metadata_anomaly"
TEMPLATE_REUSE = "template_reuse"
SUSPICIOUS_DOMAIN = "suspicious_domain"
TIMELINE_ANOMALY = "timeline_anomaly"
GENERIC_LANGUAGE = "generic_language"
FAKE_AUDITOR_NAME = "fake_auditor_name"
CLAIM_DISCREPANCY = "claim_discrepancy"
# ── Data Models ──────────────────────────────────────────────────
@dataclass
class AuditClaim:
"""A parsed audit claim from token metadata or website."""
auditor_name: str
report_url: str | None = None
report_id: str | None = None
report_date: str | None = None
verified_badge_url: str | None = None
report_text: str | None = None
@dataclass
class AuditSignal:
"""A single detection signal."""
signal_type: SignalType
severity: AuditRisk
description: str
detail: str = ""
@dataclass
class AuditValidationResult:
"""Complete validation result."""
token_address: str
chain: str
risk_level: AuditRisk
risk_score: float # 0.0 (safe) to 1.0 (critical)
signals: list[AuditSignal] = field(default_factory=list)
matched_auditor: str | None = None
verified_on_chain: bool = False
deploy_timestamp: int | None = None
report_timestamp: int | None = None
analysis_time: float = 0.0
error: str | None = None
# ── Validation Engine ────────────────────────────────────────────
class AuditReportValidator:
"""
Validates audit report claims for crypto tokens.
Analyzes:
- Report metadata (dates, auditor names, IDs)
- URL/domain hosting of audit reports
- Report text for template/proof-of-fraud signals
- Timeline consistency (deploy vs audit dates)
- Cross-referencing with known auditor databases
"""
def __init__(self) -> None:
self._known_auditors_lower = {k.lower(): v for k, v in KNOWN_AUDITORS.items()}
# ── Public API ──────────────────────────────────────────────
async def validate(
self,
token_address: str,
chain: str = "ethereum",
claims: list[dict[str, Any]] | None = None,
deploy_timestamp: int | None = None,
) -> AuditValidationResult:
"""
Validate audit claims for a token.
Args:
token_address: The token contract address.
chain: Blockchain name (ethereum, bsc, solana, etc.).
claims: List of audit claim dicts. Each dict may contain:
auditor_name, report_url, report_id, report_date,
verified_badge_url, report_text.
deploy_timestamp: Unix timestamp of token deployment (optional).
Returns:
AuditValidationResult with findings.
"""
start = time.time()
result = AuditValidationResult(
token_address=token_address,
chain=chain,
risk_level=AuditRisk.SAFE,
risk_score=0.0,
deploy_timestamp=deploy_timestamp,
)
if not claims:
# No claims means no audit to validate
result.analysis_time = time.time() - start
return result
parsed_claims = [AuditClaim(**c) if isinstance(c, dict) else c for c in claims]
for claim in parsed_claims:
signals = await self._validate_claim(claim, deploy_timestamp)
result.signals.extend(signals)
# Score aggregation
await self._score_result(result)
result.analysis_time = time.time() - start
return result
# ── Validation Methods ──────────────────────────────────────
async def _validate_claim(
self,
claim: AuditClaim,
deploy_ts: int | None,
) -> list[AuditSignal]:
"""Run all validation checks on a single claim."""
signals: list[AuditSignal] = []
# Signal 1: Fake auditor name detection
signals.extend(self._check_auditor_name(claim.auditor_name))
# Signal 2: Suspicious domain / URL analysis
if claim.report_url:
signals.extend(self._check_report_url(claim.report_url))
# Signal 3: Badge URL analysis
if claim.verified_badge_url:
signals.extend(self._check_badge_url(claim.verified_badge_url))
# Signal 4: Report ID validation
if claim.report_id:
signals.extend(self._check_report_id(claim.report_id, claim.auditor_name))
# Signal 5: Timeline analysis (deploy vs audit date)
if claim.report_date:
signals.extend(self._check_timeline(claim.report_date, deploy_ts))
# Signal 6: Template / generic language detection
if claim.report_text:
signals.extend(self._check_report_text(claim.report_text))
return signals
def _check_auditor_name(self, auditor_name: str) -> list[AuditSignal]:
"""Signal 1: Detect fake/misspelled auditor names."""
signals: list[AuditSignal] = []
name_lower = auditor_name.lower().strip()
# Check for misspellings of known auditors
for known_key, known_info in self._known_auditors_lower.items():
known_name_lower = known_info["name"].lower()
# Levenshtein-like: check character overlap and common misspellings
if self._is_fake_auditor_misspelling(name_lower, known_key, known_name_lower):
signals.append(AuditSignal(
signal_type=SignalType.FAKE_AUDITOR_NAME,
severity=AuditRisk.HIGH,
description=f"Suspicious auditor name resembling '{known_info['name']}'",
detail=f"Claimed: '{auditor_name}' β€” possible impersonation of {known_info['name']}",
))
# Check for known scam patterns in auditor name
for pattern in FAKE_AUDITOR_PATTERNS:
if pattern in name_lower and name_lower != pattern:
signals.append(AuditSignal(
signal_type=SignalType.FAKE_AUDITOR_NAME,
severity=AuditRisk.MEDIUM,
description=f"Auditor name contains known scam pattern keyword",
detail=f"Pattern matched: '{pattern}' in '{auditor_name}'",
))
break
# Check for generic sounding auditor names
generic_indicators = ["audit", "security", "verified", "certified", "safe", "guard", "labs", "consulting"]
generic_count = sum(1 for g in generic_indicators if g in name_lower)
if generic_count >= 2 and not any(k in name_lower for k in self._known_auditors_lower):
signals.append(AuditSignal(
signal_type=SignalType.FAKE_AUDITOR_NAME,
severity=AuditRisk.MEDIUM,
description="Generic-sounding auditor name not matching known firms",
detail=f"Name '{auditor_name}' sounds generic with {generic_count} generic indicators",
))
return signals
def _normalize_url(self, url: str) -> str:
"""Normalize URL: lowercase scheme+host, strip fragments/trailing junk."""
try:
parsed = urlparse(url.lower().strip())
# Reconstruct without fragment
clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
if parsed.query:
clean += f"?{parsed.query}"
return clean
except Exception:
return url.lower().strip()
def _extract_domain(self, url: str) -> str:
"""Extract the full hostname from a URL."""
try:
parsed = urlparse(url.lower().strip())
return parsed.netloc or url.split("://")[-1].split("/")[0].lower()
except Exception:
return url.lower().strip()
def _check_report_url(self, url: str) -> list[AuditSignal]:
"""Signal 2: Analyze report hosting URL for suspicion."""
signals: list[AuditSignal] = []
normalized = self._normalize_url(url)
url_lower = normalized
# Check if URL uses a suspicious TLD
domain = self._extract_domain(url)
for tld in SUSPICIOUS_TLD_PATTERNS:
if domain.endswith(tld):
signals.append(AuditSignal(
signal_type=SignalType.SUSPICIOUS_DOMAIN,
severity=AuditRisk.MEDIUM,
description=f"Audit report hosted on suspicious TLD '{tld}'",
detail=f"Domain: {domain}",
))
# Check if URL impersonates a known auditor domain
# Only flag if NONE of the auditor's official domains match
for known_key, known_info in self._known_auditors_lower.items():
official_domains = known_info.get("known_domains", [])
# Check if any official domain matches the URL domain
matches_official = any(
domain == od or domain.endswith("." + od)
for od in official_domains
)
if not matches_official:
# Check if the URL contains the base auditor name
base_names = {d.split(".")[-2] if len(d.split(".")) > 1 else d for d in official_domains}
for base_name in base_names:
if base_name in domain and len(base_name) >= 4:
signals.append(AuditSignal(
signal_type=SignalType.SUSPICIOUS_DOMAIN,
severity=AuditRisk.HIGH,
description=f"URL possibly impersonates {known_info['name']} domain",
detail=f"Domain '{domain}' contains '{base_name}' but doesn't match official domains",
))
# Check for PDF/image hosting on free file services (should be on official auditor site)
free_hosting_patterns = [
"drive.google.com", "docs.google.com", "dropbox.com",
"github.io", "githubusercontent.com", "ipfs.io",
"cdn.discord", "telegra.ph", "imgur.com",
"docsend.com", "scribd.com", "docdroid.net",
]
for pattern in free_hosting_patterns:
if pattern in url_lower:
signals.append(AuditSignal(
signal_type=SignalType.SUSPICIOUS_DOMAIN,
severity=AuditRisk.MEDIUM,
description=f"Audit report hosted on free file service ({pattern})",
detail=f"Legitimate audits are hosted on the auditor's official domain",
))
# Check for suspicious subdomain patterns
suspicious_subdomains = ["verify", "audit", "secure", "check", "certify", "validate"]
domain_prefix = domain.split(".")[0].lower()
if domain_prefix in suspicious_subdomains:
signals.append(AuditSignal(
signal_type=SignalType.SUSPICIOUS_DOMAIN,
severity=AuditRisk.LOW,
description=f"URL uses suspicious subdomain '{domain_prefix}'",
detail=f"Domain: {domain}",
))
return signals
def _check_badge_url(self, url: str) -> list[AuditSignal]:
"""Signal 3: Validate 'verified' badge embed URLs."""
signals: list[AuditSignal] = []
url_lower = url.lower()
# Legitimate badge URLs should come from the auditor's domain
found_legitimate = False
for known_key, known_info in self._known_auditors_lower.items():
for known_domain in known_info.get("known_domains", []):
domain_part = known_domain.split("://")[-1].split("/")[0].lower()
if domain_part in url_lower:
found_legitimate = True
break
if not found_legitimate:
# Check if it's an iframe embed or data URL (common in fake badges)
if url_lower.startswith("data:") or "iframe" in url_lower or "embed" in url_lower:
signals.append(AuditSignal(
signal_type=SignalType.FORGED_REPORT,
severity=AuditRisk.HIGH,
description="Verified badge uses data URI or iframe embed instead of trusted domain",
detail=f"Badge URL: {url[:100]}",
))
elif not url_lower.startswith("http"):
signals.append(AuditSignal(
signal_type=SignalType.METADATA_ANOMALY,
severity=AuditRisk.LOW,
description="Unusual badge URL format",
detail=f"Badge URL: {url}",
))
return signals
def _check_report_id(self, report_id: str, auditor_name: str) -> list[AuditSignal]:
"""Signal 4: Validate report ID format against known auditor patterns."""
signals: list[AuditSignal] = []
name_lower = auditor_name.lower().strip()
# Certik report IDs are typically: "Certik-${project-name}-${number}" or UUID
# Hacken: alphanumeric IDs or UUIDs
# SlowMist: date-prefixed IDs
# Check for obviously fake UUIDs/invalid formats
for known_key, known_info in self._known_auditors_lower.items():
if known_key in name_lower:
# Certik IDs should be findable on certik.com
if known_key == "certik" and len(report_id) < 5:
signals.append(AuditSignal(
signal_type=SignalType.METADATA_ANOMALY,
severity=AuditRisk.HIGH,
description=f"Unusually short Certik report ID: '{report_id}'",
detail="Legitimate Certik reports have longer project-specific IDs",
))
# Generic check: report IDs with only digits are suspicious
if report_id.isdigit() and len(report_id) > 8:
signals.append(AuditSignal(
signal_type=SignalType.METADATA_ANOMALY,
severity=AuditRisk.LOW,
description="Report ID is an all-numeric string",
detail="May be auto-generated instead of auditor-issued",
))
return signals
def _check_timeline(self, report_date_str: str, deploy_ts: int | None) -> list[AuditSignal]:
"""Signal 5: Check if audit timeline makes sense."""
signals: list[AuditSignal] = []
try:
# Try multiple date formats
report_ts = self._parse_date(report_date_str)
if report_ts is None:
signals.append(AuditSignal(
signal_type=SignalType.METADATA_ANOMALY,
severity=AuditRisk.LOW,
description="Could not parse audit report date",
detail=f"Date string: '{report_date_str}'",
))
return signals
report_dt = datetime.fromtimestamp(report_ts, tz=timezone.utc)
# Future dates (report dated in future)
now_ts = int(time.time())
if report_ts > now_ts + 86400: # More than 1 day in the future
signals.append(AuditSignal(
signal_type=SignalType.TIMELINE_ANOMALY,
severity=AuditRisk.HIGH,
description="Audit report dated in the future",
detail=f"Report date: {report_dt.date()}. Current: {datetime.now(timezone.utc).date()}",
))
# Still check deploy-timeline if available
if deploy_ts is not None:
self._add_deploy_timeline_signal(report_ts, deploy_ts, report_dt, signals)
return signals
# Deploy-timestamp-dependent checks
if deploy_ts is not None:
self._add_deploy_timeline_signal(report_ts, deploy_ts, report_dt, signals)
except (ValueError, OverflowError) as e:
signals.append(AuditSignal(
signal_type=SignalType.METADATA_ANOMALY,
severity=AuditRisk.LOW,
description=f"Date parsing error: {e}",
detail=f"Date string: '{report_date_str}'",
))
return signals
def _add_deploy_timeline_signal(
self, report_ts: int, deploy_ts: int,
report_dt: datetime, signals: list[AuditSignal],
) -> None:
"""Check deploy vs audit date consistency."""
deploy_dt = datetime.fromtimestamp(deploy_ts, tz=timezone.utc)
diff_days = (report_dt - deploy_dt).days
# Audit before deploy (or impossible future date)
if diff_days < -MAX_AUDIT_ANTEDATE_DAYS:
signals.append(AuditSignal(
signal_type=SignalType.TIMELINE_ANOMALY,
severity=AuditRisk.HIGH,
description=f"Audit report dated {-diff_days} days BEFORE token deployment",
detail=f"Deploy: {deploy_dt.date()}, Audit: {report_dt.date()}. Impossible for a pre-launch audit.",
))
elif diff_days < 0:
signals.append(AuditSignal(
signal_type=SignalType.TIMELINE_ANOMALY,
severity=AuditRisk.LOW,
description=f"Audit report slightly before deployment ({-diff_days} days)",
detail="Possible if audit was performed before deploy, verify dates carefully.",
))
# Audit too long after deploy
if diff_days > MAX_AUDIT_POSTDATE_DAYS:
signals.append(AuditSignal(
signal_type=SignalType.TIMELINE_ANOMALY,
severity=AuditRisk.MEDIUM,
description=f"Audit report dated {diff_days} days AFTER deployment",
detail=f"Late audits may indicate the audit was procured after scam launch.",
))
def _check_report_text(self, text: str) -> list[AuditSignal]:
"""Signal 6: Analyze report text for template/generic language."""
signals: list[AuditSignal] = []
# Count template phrases
text_lower = text.lower()
phrase_matches = [p for p in TEMPLATE_PHRASES if p in text_lower]
if len(phrase_matches) >= 4:
signals.append(AuditSignal(
signal_type=SignalType.TEMPLATE_REUSE,
severity=AuditRisk.HIGH,
description=f"Report text contains {len(phrase_matches)} template/generic phrases",
detail=f"Matched phrases: {', '.join(phrase_matches[:5])}",
))
elif len(phrase_matches) >= 2:
signals.append(AuditSignal(
signal_type=SignalType.GENERIC_LANGUAGE,
severity=AuditRisk.MEDIUM,
description=f"Report text contains {len(phrase_matches)} generic phrases",
detail=f"May indicate copy-paste/fake report. Matched: {', '.join(phrase_matches[:3])}",
))
# Check for suspiciously short report text
word_count = len(text.split())
if word_count < 50 and word_count > 0:
signals.append(AuditSignal(
signal_type=SignalType.GENERIC_LANGUAGE,
severity=AuditRisk.MEDIUM,
description=f"Report text is surprisingly short ({word_count} words)",
detail="Legitimate audit reports are typically 5-50+ pages long",
))
# Check for missing technical terms that should be in a real audit
missing_terms: list[str] = []
technical_terms = [
"reentrancy", "overflow", "access control", "front-running",
"timestamp dependence", "tx.origin", "gas limit",
"integer overflow", "logic flaw", "centralization risk",
]
for term in technical_terms:
if term not in text_lower:
missing_terms.append(term)
if len(missing_terms) >= 8 and word_count > 100:
signals.append(AuditSignal(
signal_type=SignalType.GENERIC_LANGUAGE,
severity=AuditRisk.LOW,
description=f"Report lacks standard security terminology ({len(missing_terms)}/{len(technical_terms)} terms missing)",
detail=f"Missing: {', '.join(missing_terms[:5])}",
))
return signals
# ── Scoring ──────────────────────────────────────────────────
async def _score_result(self, result: AuditValidationResult) -> None:
"""Compute aggregate risk score from signals."""
if not result.signals:
result.risk_level = AuditRisk.SAFE
result.risk_score = 0.0
return
severity_scores = {
AuditRisk.CRITICAL: 1.0,
AuditRisk.HIGH: 0.7,
AuditRisk.MEDIUM: 0.4,
AuditRisk.LOW: 0.15,
}
total = 0.0
count = len(result.signals)
for signal in result.signals:
total += severity_scores.get(signal.severity, 0.1)
# Base score is average severity
base_score = total / max(count, 1)
# Boost for multiple signals
multiplier = min(1.0 + (count - 1) * 0.15, 1.5)
# Boost for critical signals
critical_count = sum(1 for s in result.signals if s.severity == AuditRisk.CRITICAL)
multiplier += critical_count * 0.2
result.risk_score = round(min(base_score * multiplier, 1.0), 4)
# Map score to risk level
if result.risk_score >= 0.7:
result.risk_level = AuditRisk.CRITICAL
elif result.risk_score >= 0.45:
result.risk_level = AuditRisk.HIGH
elif result.risk_score >= 0.2:
result.risk_level = AuditRisk.MEDIUM
elif result.risk_score > 0:
result.risk_level = AuditRisk.LOW
else:
result.risk_level = AuditRisk.SAFE
# ── Helpers ──────────────────────────────────────────────────
def _is_fake_auditor_misspelling(self, name: str, known_key: str, known_name: str) -> bool:
"""Check if name is a likely misspelling/impersonation of a known auditor."""
if name == known_name or name == known_key:
return False
# Check character-level similarity (simple approach)
known_key_len = len(known_key)
if known_key_len >= 4 and known_key in name:
# If the known key is a substring but not the whole thing: impersonation
remaining = name.replace(known_key, "").strip()
if remaining in {"", "-", "_", "."}:
return False # exact match after stripping
# It's a misspelling variant like "certikpro", "hackenverify"
return True
# Check for common character swaps/typos
if known_key_len >= 4 and len(name) >= 4:
# Simple edit distance check - if they share first 3 chars and last 2
if name[:3] == known_key[:3] and name[-2:] == known_key[-2:]:
if name != known_key:
return True
return False
def _parse_date(self, date_str: str) -> int | None:
"""Parse various date formats into a Unix timestamp."""
formats = [
"%Y-%m-%d",
"%Y/%m/%d",
"%d/%m/%Y",
"%m/%d/%Y",
"%d %B %Y",
"%B %d, %Y",
"%d %b %Y",
"%b %d, %Y",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%dT%H:%M:%SZ",
"%Y-%m-%d %H:%M:%S",
]
date_str_clean = date_str.strip().strip('"').strip("'")
for fmt in formats:
try:
return int(datetime.strptime(date_str_clean, fmt).timestamp())
except ValueError:
continue
return None