rugmuncher-backend / backend /app /historical_scraper_tool.py
RMI Platform
feat: wire historical scrapers (Rekt DB, Chainabuse, TRM, SlowMist) into DataBus
ab145db
Raw
History Blame Contribute Delete
7.08 kB
"""
RAG Historical Scraper Tool β€” Wire historical sources into DataBus
================================================================
Registers Rekt DB, Chainabuse, SlowMist, Immunefi scrapers as DataBus chains
for on-demand and scheduled ingestion into RAG collections.
This fills the major gap identified in RAG_MODERNIZATION.md:
"Firehose sources not wired (cadences defined, fetchers missing)"
DataBus endpoints added:
- POST /api/v1/databus/fetch/historical_ingest
- POST /api/v1/databus/fetch/historical_ingest/{source_id}
- GET /api/v1/databus/fetch/historical_sources
Sources: rekt_db (DeFi hacks), chainabuse (scam reports), slowmist, immunefi, trm_crime_report
"""
import asyncio
import json
import logging
from datetime import UTC, datetime
from typing import Any
logger = logging.getLogger("historical_scraper_tool")
# Import the scrapers from rag_historical
try:
from app.rag_historical import ingest_historical_source, ingest_all_historical, SOURCES
except ImportError:
# Will be available when run as module
ingest_historical_source = None
ingest_all_historical = None
SOURCES = {}
# ── Provider Functions ───────────────────────────────────────────────
async def _fetch_historical_ingest(source_id: str = "all", **kwargs) -> dict | None:
"""Run historical ingestion for a specific source or all sources.
source_id: "rekt_db", "chainabuse", "slowmist_hacked", "trm_crime_report", or "all"
"""
# Import here to allow circular imports
from app.rag_historical import ingest_historical_source, ingest_all_historical, SOURCES
force = kwargs.get("force", False)
if source_id == "all":
result = await ingest_all_historical()
elif source_id in SOURCES:
result = await ingest_historical_source(source_id)
else:
return {"error": f"Unknown source: {source_id}", "valid_sources": list(SOURCES.keys())}
return result
async def _fetch_historical_sources(**kwargs) -> dict | None:
"""List all available historical sources and their configuration."""
from app.rag_historical import SOURCES
sources_info = []
for source_id, info in SOURCES.items():
sources_info.append({
"id": source_id,
"name": info.get("name", source_id),
"url": info.get("url", ""),
"collection": info.get("collection", "unknown"),
"cadence": info.get("cadence", "unknown"),
"content_type": info.get("content_type", "unknown"),
"description": info.get("description", ""),
})
return {
"sources": sources_info,
"total_sources": len(sources_info),
"timestamp": datetime.now(UTC).isoformat(),
}
async def _fetch_defi_hacks(limit: int = 50, **kwargs) -> dict | None:
"""Fetch recent DeFi hacks from Rekt DB (cached 24h)."""
from app.rag_historical import scrape_rekt_db
try:
docs = await scrape_rekt_db()
hacks = docs[:limit] if docs else []
return {
"hacks": hacks,
"count": len(hacks),
"source": "rekt_db",
"cached": True, # Will be cached by DataBus
}
except Exception as e:
logger.warning(f"DeFi hacks fetch failed: {e}")
return None
async def _fetch_scam_reports(limit: int = 50, **kwargs) -> dict | None:
"""Fetch recent scam reports from Chainabuse (cached 24h)."""
from app.rag_historical import scrape_chainabuse
try:
docs = await scrape_chainabuse()
reports = docs[:limit] if docs else []
return {
"reports": reports,
"count": len(reports),
"source": "chainabuse",
}
except Exception as e:
logger.warning(f"Scam reports fetch failed: {e}")
return None
# ── Chain Registration ───────────────────────────────────────────────
def register_historical_chains() -> dict[str, Any]:
"""Register historical scraper chains with DataBus.
Must be called during app initialization.
Returns dict of chain_name -> ProviderChain.
"""
from app.databus.providers import Provider, ProviderChain, ProviderTier
chains = {}
# Historical ingestion (admin only, background task)
chains["historical_ingest"] = ProviderChain(
data_type="historical_ingest",
description="Ingest historical scam/hack data into RAG (Rekt DB, Chainabuse, etc.)",
providers=[
Provider(
"historical_scraper",
ProviderTier.LOCAL,
_fetch_historical_ingest,
weight=10.0,
rate_limit_rps=0.1, # Very slow - don't spam API
is_local=True,
),
],
)
# List available sources
chains["historical_sources"] = ProviderChain(
data_type="historical_sources",
description="List all historical data sources",
providers=[
Provider(
"sources_lister",
ProviderTier.LOCAL,
_fetch_historical_sources,
weight=10.0,
rate_limit_rps=5.0,
is_local=True,
),
],
)
# DeFi hacks endpoint (cached)
chains["defi_hacks"] = ProviderChain(
data_type="defi_hacks",
description="Recent DeFi hacks from Rekt DB",
providers=[
Provider(
"rekt_db_scraper",
ProviderTier.LOCAL,
_fetch_defi_hacks,
weight=10.0,
rate_limit_rps=0.1,
is_local=True,
),
],
)
# Scam reports endpoint (cached)
chains["scam_reports"] = ProviderChain(
data_type="scam_reports",
description="Scam reports from Chainabuse",
providers=[
Provider(
"chainabuse_scraper",
ProviderTier.LOCAL,
_fetch_scam_reports,
weight=10.0,
rate_limit_rps=0.1,
is_local=True,
),
],
)
logger.info(
f"Historical scraper chains registered: "
f"{list(chains.keys())}"
)
return chains
# ── Quick CLI Test ───────────────────────────────────────────────────
if __name__ == "__main__":
import sys
async def main():
if len(sys.argv) > 1:
source = sys.argv[1]
result = await _fetch_historical_ingest(source)
print(json.dumps(result, indent=2, default=str))
else:
sources = await _fetch_historical_sources()
print(json.dumps(sources, indent=2, default=str))
asyncio.run(main())