File size: 5,484 Bytes
6993919 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | #!/usr/bin/env python3
"""
Ingest CryptoScamDB blacklist data into RAG known_scams collection.
Source: https://github.com/CryptoScamDB/blacklist
"""
import asyncio
import hashlib
import logging
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
logger = logging.getLogger("ingest_cryptoscamdb_blacklist")
RAG_API = "http://localhost:8000/api/v1/rag/ingest"
COLLECTION = "known_scams"
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data")
def make_doc_id(address: str, source: str) -> str:
return hashlib.sha256(f"known_scams:{address.lower()}:{source}".encode()).hexdigest()[:16]
async def ingest_via_api(content: str, metadata: dict) -> bool:
try:
import httpx
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.post(RAG_API, json={"collection": COLLECTION, "content": content, "metadata": metadata})
return resp.status_code == 200
except Exception:
return False
async def ingest_yaml():
"""Parse the YAML blacklist and ingest entries with addresses."""
yaml_path = os.path.join(DATA_DIR, "cryptoscamdb_urls.yaml")
if not os.path.exists(yaml_path):
logger.error(f"YAML file not found: {yaml_path}")
return {"ingested": 0, "no_address": 0}
# Simple YAML parser (avoid pyyaml dependency for this structure)
entries = []
current = {}
with open(yaml_path, encoding="utf-8", errors="replace") as f:
for line in f:
stripped = line.rstrip()
if not stripped or stripped.startswith("#"):
continue
if stripped.startswith("- name:"):
if current:
entries.append(current)
current = {"name": stripped[7:].strip().strip('"')}
elif stripped.startswith(" url:"):
current["url"] = stripped[6:].strip().strip('"')
elif stripped.startswith(" category:"):
current["category"] = stripped[11:].strip().strip('"')
elif stripped.startswith(" subcategory:"):
current["subcategory"] = stripped[14:].strip().strip('"')
elif stripped.startswith(" description:"):
current["description"] = stripped[14:].strip().strip('"')
elif stripped.startswith(" reporter:"):
current["reporter"] = stripped[11:].strip().strip('"')
elif stripped.startswith(" ETH:"):
current["chain"] = "ethereum"
elif stripped.startswith(' - "0x') or stripped.startswith(" - 0x"):
addr = stripped.split('"')[-2] if '"' in stripped else stripped.split("-")[-1].strip()
current.setdefault("addresses", []).append(addr)
elif stripped.startswith(" BTC:"):
current["chain"] = "bitcoin"
elif stripped.startswith(" BSC:") or stripped.startswith(" BNB:"):
current["chain"] = "bsc"
if current:
entries.append(current)
logger.info(f"Parsed {len(entries)} entries from YAML")
ingested = 0
no_address = 0
for i, entry in enumerate(entries):
name = entry.get("name", "")
category = entry.get("category", "unknown")
subcategory = entry.get("subcategory", "")
description = entry.get("description", "")
url = entry.get("url", "")
addresses = entry.get("addresses", [])
if not addresses:
# Ingest URL-based entry without blockchain address
content = f"CryptoScamDB: {name}. Category: {category}/{subcategory}. URL: {url}. {description}"
metadata = {
"source": "cryptoscamdb",
"category": category.lower(),
"subcategory": subcategory.lower(),
"name": name,
"url": url,
"severity": "high" if category == "Phishing" else "medium",
}
if await ingest_via_api(content, metadata):
no_address += 1
else:
for addr in addresses:
if not addr or len(addr) < 10:
continue
chain = entry.get("chain", "ethereum")
content = f"CryptoScamDB flagged address: {addr} on {chain}. Project: {name}. Category: {category}/{subcategory}. {description}"
metadata = {
"source": "cryptoscamdb",
"address": addr.lower(),
"chain": chain,
"category": category.lower(),
"subcategory": subcategory.lower(),
"name": name,
"url": url,
"severity": "high" if category in ("Phishing", "Scam") else "medium",
}
if await ingest_via_api(content, metadata):
ingested += 1
if (i + 1) % 500 == 0:
logger.info(f"Progress: {i + 1}/{len(entries)} | addr_entries: {ingested}, url_only: {no_address}")
await asyncio.sleep(0.1)
logger.info(f"COMPLETE: addr_entries={ingested}, url_only={no_address}")
return {"ingested": ingested, "no_address": no_address}
async def main():
result = await ingest_yaml()
logger.info(f"Final: {result}")
if __name__ == "__main__":
asyncio.run(main())
|