RMI Platform
feat: wire historical scrapers (Rekt DB, Chainabuse, TRM, SlowMist) into DataBus
ab145db | """ | |
| 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()) |