Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- ransomware_bot.py
- Bot de Telegram para consultar victimas de ransomware desde la API publica de
- ransomware.live (v2). Interfaz con botones inline: elegir pais o ver lo mas
- reciente a nivel global.
- Requisitos:
- pip install python-telegram-bot requests
- Configuracion (variables de entorno - NUNCA hardcodear el token):
- Opcion A (recomendada): archivo .env junto al script con la linea:
- TELEGRAM_BOT_TOKEN=123456:ABC-tu-token-de-BotFather
- Opcion B: variable de entorno:
- export TELEGRAM_BOT_TOKEN="123456:ABC-tu-token-de-BotFather"
- Ejecutar:
- python3 ransomware_bot.py
- Comandos:
- /start -> mensaje de bienvenida + menu principal
- /pais MX -> consulta directa por codigo ISO-2
- /reciente-> victimas globales mas recientes
- /help -> ayuda
- Nota: la API gratuita es solo para uso personal. Para uso institucional revisar T&C.
- """
- import html
- import logging
- import os
- from collections import Counter
- from datetime import datetime, timezone
- import requests
- from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
- from telegram.constants import ParseMode
- from telegram.ext import (
- Application,
- CallbackQueryHandler,
- CommandHandler,
- ContextTypes,
- MessageHandler,
- filters,
- )
- # ----------------------------- Config -----------------------------
- API_BASE = "https://api.ransomware.live/v2"
- HEADERS = {"User-Agent": "ivancastl-cti/1.0"}
- TIMEOUT = 30
- MAX_LISTADO = 15 # cuantas victimas listar en el mensaje (Telegram limita a 4096 chars)
- # Paises que apareceran como botones en el menu.
- PAISES = {
- "MX": "๐ฒ๐ฝ Mexico",
- "US": "๐บ๐ธ EE.UU.",
- "BR": "๐ง๐ท Brasil",
- "AR": "๐ฆ๐ท Argentina",
- "CO": "๐จ๐ด Colombia",
- "CL": "๐จ๐ฑ Chile",
- "PE": "๐ต๐ช Peru",
- "ES": "๐ช๐ธ Espana",
- "IT": "๐ฎ๐น Italia",
- "DE": "๐ฉ๐ช Alemania",
- "GB": "๐ฌ๐ง Reino Unido",
- "FR": "๐ซ๐ท Francia",
- "CA": "๐จ๐ฆ Canada",
- "JP": "๐ฏ๐ต Japon",
- }
- logging.basicConfig(
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
- level=logging.INFO,
- )
- logger = logging.getLogger(__name__)
- # ----------------------- Utilidades de API ------------------------
- def _get(endpoint: str):
- """GET a la API. Devuelve lista/dict o None si falla."""
- url = f"{API_BASE}/{endpoint}"
- try:
- r = requests.get(url, headers=HEADERS, timeout=TIMEOUT)
- r.raise_for_status()
- return r.json()
- except requests.exceptions.RequestException as e:
- logger.error("Error consultando %s: %s", url, e)
- return None
- except ValueError:
- logger.error("Respuesta no-JSON de %s", url)
- return None
- def victimas_por_pais(country: str):
- return _get(f"countryvictims/{country}")
- def victimas_recientes():
- return _get("recentvictims")
- def _parse_fecha(valor):
- if not valor:
- return None
- try:
- return datetime.fromisoformat(str(valor).replace("Z", "+00:00"))
- except (ValueError, AttributeError):
- return None
- def _campo(v: dict, *claves):
- """Devuelve el primer campo no vacio entre varias claves posibles.
- (recentvictims usa 'group'/'domain'; countryvictims usa 'group_name'/'website')."""
- for k in claves:
- val = v.get(k)
- if val:
- return val
- return None
- # --------------------- Formateo de reportes -----------------------
- def formatear_reporte(titulo: str, victimas: list) -> str:
- """Construye el texto HTML del reporte para Telegram."""
- if victimas is None:
- return "โ ๏ธ <b>Error</b> al consultar la API. Intenta de nuevo en unos minutos."
- if not victimas:
- return f"<b>{html.escape(titulo)}</b>\n\nNo se encontraron victimas."
- # Orden por fecha de descubrimiento, mas reciente primero
- victimas = sorted(
- victimas,
- key=lambda v: _parse_fecha(v.get("discovered")) or datetime.min.replace(tzinfo=timezone.utc),
- reverse=True,
- )
- grupos = Counter(_campo(v, "group", "group_name") or "desconocido" for v in victimas)
- sectores = Counter(v.get("activity") or "No especificado" for v in victimas)
- lineas = []
- lineas.append(f"<b>{html.escape(titulo)}</b>")
- lineas.append(f"Total: <b>{len(victimas)}</b> victimas")
- lineas.append(f"<i>{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}</i>")
- top_g = ", ".join(f"{g} ({n})" for g, n in grupos.most_common(5))
- top_s = ", ".join(f"{s} ({n})" for s, n in sectores.most_common(5))
- lineas.append(f"\n๐ฆน <b>Top grupos:</b> {html.escape(top_g)}")
- lineas.append(f"๐ญ <b>Top sectores:</b> {html.escape(top_s)}")
- lineas.append(f"\n๐ <b>Ultimas {min(len(victimas), MAX_LISTADO)}:</b>")
- for v in victimas[:MAX_LISTADO]:
- f = _parse_fecha(v.get("discovered"))
- fecha = f.strftime("%Y-%m-%d") if f else "N/A"
- nombre = _campo(v, "post_title", "domain", "website") or "N/A"
- grupo = _campo(v, "group", "group_name") or "N/A"
- pais = v.get("country") or ""
- lineas.append(
- f"\nโข <code>{fecha}</code> {html.escape(str(nombre))}"
- f"\n โณ {html.escape(str(grupo))}"
- + (f" ยท {html.escape(str(pais))}" if pais else "")
- )
- if len(victimas) > MAX_LISTADO:
- lineas.append(f"\n<i>...y {len(victimas) - MAX_LISTADO} mas.</i>")
- return "\n".join(lineas)
- # ------------------------- Teclados -------------------------------
- def menu_principal() -> InlineKeyboardMarkup:
- """Menu con paises (2 por fila) + boton de reciente global."""
- botones = []
- fila = []
- for code, label in PAISES.items():
- fila.append(InlineKeyboardButton(label, callback_data=f"pais:{code}"))
- if len(fila) == 2:
- botones.append(fila)
- fila = []
- if fila:
- botones.append(fila)
- botones.append([InlineKeyboardButton("๐ Mas recientes (global)", callback_data="reciente")])
- botones.append([InlineKeyboardButton("โน๏ธ Ayuda", callback_data="ayuda")])
- return InlineKeyboardMarkup(botones)
- def boton_volver() -> InlineKeyboardMarkup:
- return InlineKeyboardMarkup(
- [[InlineKeyboardButton("โฌ ๏ธ Volver al menu", callback_data="menu")]]
- )
- # ------------------------- Handlers -------------------------------
- TEXTO_BIENVENIDA = (
- "๐ <b>Bienvenido al Ransomware Watch Bot</b>\n\n"
- "Consulto en tiempo real la base de <b>ransomware.live</b> para mostrarte "
- "victimas de ransomware publicadas en sitios de filtracion (leak sites).\n\n"
- "Puedo darte:\n"
- "โข ๐ Un reporte por <b>pais</b> (grupos, sectores y victimas)\n"
- "โข ๐ Las victimas <b>mas recientes</b> a nivel global\n\n"
- "Elige una opcion ๐"
- )
- TEXTO_AYUDA = (
- "<b>โน๏ธ Ayuda</b>\n\n"
- "<b>Botones:</b> toca un pais o 'Mas recientes'.\n\n"
- "<b>Comandos:</b>\n"
- "โข <code>/start</code> โ menu principal\n"
- "โข <code>/pais MX</code> โ consulta por codigo ISO-2 (ej: MX, US, IT, JP)\n"
- "โข <code>/reciente</code> โ victimas globales recientes\n"
- "โข <code>/help</code> โ esta ayuda\n\n"
- "<i>Fuente: ransomware.live ยท API gratuita solo para uso personal.</i>"
- )
- async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
- await update.message.reply_text(
- TEXTO_BIENVENIDA, parse_mode=ParseMode.HTML, reply_markup=menu_principal()
- )
- async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
- await update.message.reply_text(
- TEXTO_AYUDA, parse_mode=ParseMode.HTML, reply_markup=boton_volver()
- )
- async def cmd_pais(update: Update, context: ContextTypes.DEFAULT_TYPE):
- """Comando /pais MX"""
- if not context.args:
- await update.message.reply_text(
- "Uso: <code>/pais MX</code> (codigo ISO-2)",
- parse_mode=ParseMode.HTML,
- )
- return
- code = context.args[0].upper()[:2]
- await _responder_pais(update.message, code)
- async def cmd_reciente(update: Update, context: ContextTypes.DEFAULT_TYPE):
- await _responder_reciente(update.message)
- async def _responder_pais(message, code: str):
- aviso = await message.reply_text(f"๐ Consultando <b>{code}</b>...", parse_mode=ParseMode.HTML)
- data = victimas_por_pais(code)
- nombre = PAISES.get(code, code)
- texto = formatear_reporte(f"Ransomware โ {nombre}", data)
- await aviso.edit_text(texto, parse_mode=ParseMode.HTML, reply_markup=boton_volver(),
- disable_web_page_preview=True)
- async def _responder_reciente(message):
- aviso = await message.reply_text("๐ Trayendo lo mas reciente...", parse_mode=ParseMode.HTML)
- data = victimas_recientes()
- texto = formatear_reporte("Ransomware โ Mas recientes (global)", data)
- await aviso.edit_text(texto, parse_mode=ParseMode.HTML, reply_markup=boton_volver(),
- disable_web_page_preview=True)
- async def on_button(update: Update, context: ContextTypes.DEFAULT_TYPE):
- """Maneja los clics de botones inline."""
- query = update.callback_query
- await query.answer()
- data = query.data
- if data == "menu":
- await query.edit_message_text(
- TEXTO_BIENVENIDA, parse_mode=ParseMode.HTML, reply_markup=menu_principal()
- )
- return
- if data == "ayuda":
- await query.edit_message_text(
- TEXTO_AYUDA, parse_mode=ParseMode.HTML, reply_markup=boton_volver()
- )
- return
- if data == "reciente":
- await query.edit_message_text("๐ Trayendo lo mas reciente...", parse_mode=ParseMode.HTML)
- resultado = victimas_recientes()
- texto = formatear_reporte("Ransomware โ Mas recientes (global)", resultado)
- await query.edit_message_text(texto, parse_mode=ParseMode.HTML,
- reply_markup=boton_volver(), disable_web_page_preview=True)
- return
- if data.startswith("pais:"):
- code = data.split(":", 1)[1]
- nombre = PAISES.get(code, code)
- await query.edit_message_text(f"๐ Consultando <b>{nombre}</b>...", parse_mode=ParseMode.HTML)
- resultado = victimas_por_pais(code)
- texto = formatear_reporte(f"Ransomware โ {nombre}", resultado)
- await query.edit_message_text(texto, parse_mode=ParseMode.HTML,
- reply_markup=boton_volver(), disable_web_page_preview=True)
- return
- async def on_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
- """Si el usuario escribe un codigo de pais suelto (ej: 'MX'), lo interpretamos."""
- txt = (update.message.text or "").strip().upper()
- if len(txt) == 2 and txt.isalpha():
- await _responder_pais(update.message, txt)
- else:
- await update.message.reply_text(
- "No entendi. Usa /start para ver el menu.",
- reply_markup=menu_principal(),
- )
- # ----------------------------- Main -------------------------------
- # ================== TOKEN ==================
- # Pega aqui tu token de @BotFather (reemplaza el texto entre comillas).
- TELEGRAM_BOT_TOKEN = "TU TOKEN AQUI"
- # ==========================================
- def main():
- token = TELEGRAM_BOT_TOKEN
- if not token or token == "PEGA-AQUI-TU-TOKEN":
- raise SystemExit(
- "Falta el token. Edita la variable TELEGRAM_BOT_TOKEN en este archivo "
- "y pega el token que te dio @BotFather."
- )
- app = Application.builder().token(token).build()
- app.add_handler(CommandHandler("start", cmd_start))
- app.add_handler(CommandHandler("help", cmd_help))
- app.add_handler(CommandHandler("pais", cmd_pais))
- app.add_handler(CommandHandler("reciente", cmd_reciente))
- app.add_handler(CallbackQueryHandler(on_button))
- app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, on_text))
- logger.info("Bot iniciado. Esperando mensajes...")
- app.run_polling(allowed_updates=Update.ALL_TYPES)
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment