from telethon import TelegramClient, events
import asyncio
import logging
import os
from pathlib import Path
# Intentar cargar variables de entorno desde .env
try:
from dotenv import load_dotenv
load_dotenv()
logger_init = logging.getLogger(__name__)
logger_init.info("✅ Variables de entorno cargadas desde .env")
except ImportError:
pass # python-dotenv no está instalado, usar valores directos
# Configuración de logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
# ===================== CONFIGURACIÓN =====================
# Opción 1: Usar variables de entorno (recomendado para seguridad)
API_ID = os.getenv('API_ID') or 'TU_API_ID'
API_HASH = os.getenv('API_HASH') or 'TU_API_HASH'
BOT_TOKEN = os.getenv('BOT_TOKEN') or 'TU_BOT_TOKEN'
SOURCE_CHANNEL = os.getenv('SOURCE_CHANNEL') or '@canal_origen'
TARGET_CHANNEL = os.getenv('TARGET_CHANNEL') or '@canal_destino'
# Extensiones de archivo a repostear (puedes agregar más)
ALLOWED_EXTENSIONS = ('.zip', '.rar', '.7z', '.tar', '.gz', '.3mf', '.stl', '.gcode')
# Opciones adicionales
FORWARD_AS_COPY = False # Si True, copia el mensaje sin mostrar "Forwarded from"
ADD_WATERMARK = False # Si True, agrega un texto al repostear
WATERMARK_TEXT = "\n\n📦 Reposteado automáticamente"
# =========================================================
# Crear el cliente del bot
bot = TelegramClient('bot_session', API_ID, API_HASH)
def is_allowed_file(file_name):
"""
Verifica si el archivo tiene una extensión permitida
"""
if not file_name:
return False
return any(file_name.lower().endswith(ext) for ext in ALLOWED_EXTENSIONS)
async def get_file_info(message):
"""
Extrae información del archivo del mensaje
"""
if not message.document:
return None
file_name = None
file_size = message.document.size
mime_type = message.document.mime_type
for attribute in message.document.attributes:
if hasattr(attribute, 'file_name'):
file_name = attribute.file_name
break
return {
'name': file_name,
'size': file_size,
'mime_type': mime_type
}
def format_file_size(size_bytes):
"""
Convierte bytes a formato legible (KB, MB, GB)
"""
for unit in ['B', 'KB', 'MB', 'GB']:
if size_bytes < 1024.0:
return f"{size_bytes:.2f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:.2f} TB"
@bot.on(events.NewMessage(chats=SOURCE_CHANNEL))
async def repost_handler(event):
"""
Maneja los nuevos mensajes del canal origen y los repostea al destino
"""
try:
message = event.message
# Verificar si el mensaje tiene un archivo
if message.media and message.document:
file_info = await get_file_info(message)
if not file_info or not file_info['name']:
logger.debug("⏭️ Mensaje sin nombre de archivo, ignorado")
return
file_name = file_info['name']
# Verificar si es una extensión permitida
if is_allowed_file(file_name):
file_size = format_file_size(file_info['size'])
logger.info(f"📥 Reposteando archivo: {file_name} ({file_size})")
if FORWARD_AS_COPY:
# Opción: Enviar como copia (sin "Forwarded from")
caption = message.message or ""
if ADD_WATERMARK:
caption += WATERMARK_TEXT
await bot.send_file(
TARGET_CHANNEL,
file=message.media,
caption=caption
)
else:
# Opción: Forward directo (muestra "Forwarded from")
await bot.forward_messages(
entity=TARGET_CHANNEL,
messages=message.id,
from_peer=SOURCE_CHANNEL
)
logger.info(f"✅ Archivo reposteado exitosamente: {file_name}")
else:
logger.debug(f"⏭️ Archivo ignorado (extensión no permitida): {file_name}")
else:
logger.debug("⏭️ Mensaje sin archivo, ignorado")
except Exception as e:
logger.error(f"❌ Error al repostear mensaje: {e}", exc_info=True)
@bot.on(events.NewMessage(pattern='/start'))
async def start_handler(event):
"""
Comando /start para verificar que el bot funciona
"""
await event.respond(
"🤖 **Bot de Reposteo Activo**\n\n"
f"📡 Canal origen: `{SOURCE_CHANNEL}`\n"
f"📤 Canal destino: `{TARGET_CHANNEL}`\n"
f"📁 Extensiones: {', '.join(ALLOWED_EXTENSIONS)}\n\n"
"✅ El bot está funcionando correctamente."
)
@bot.on(events.NewMessage(pattern='/status'))
async def status_handler(event):
"""
Comando /status para verificar el estado del bot
"""
try:
# Verificar acceso a los canales
source_entity = await bot.get_entity(SOURCE_CHANNEL)
target_entity = await bot.get_entity(TARGET_CHANNEL)
await event.respond(
"📊 **Estado del Bot**\n\n"
f"✅ Canal origen: {source_entity.title}\n"
f"✅ Canal destino: {target_entity.title}\n"
f"✅ Extensiones activas: {len(ALLOWED_EXTENSIONS)}\n"
f"✅ Bot funcionando correctamente"
)
except Exception as e:
await event.respond(f"❌ Error: {e}")
async def main():
"""
Función principal para iniciar el bot
"""
logger.info("🤖 Iniciando bot...")
# Validar configuración
if API_ID == 'TU_API_ID' or API_HASH == 'TU_API_HASH':
logger.error("❌ ERROR: Debes configurar API_ID y API_HASH")
return
if BOT_TOKEN == 'TU_BOT_TOKEN':
logger.error("❌ ERROR: Debes configurar BOT_TOKEN")
return
# Conectar con el token del bot
await bot.start(bot_token=BOT_TOKEN)
# Obtener información del bot
me = await bot.get_me()
logger.info(f"✅ Bot conectado: @{me.username}")
logger.info(f"📡 Escuchando mensajes en: {SOURCE_CHANNEL}")
logger.info(f"📤 Reposteando a: {TARGET_CHANNEL}")
logger.info(f"📁 Extensiones permitidas: {', '.join(ALLOWED_EXTENSIONS)}")
logger.info(f"🔄 Modo: {'Copia' if FORWARD_AS_COPY else 'Forward'}")
logger.info("🟢 Bot funcionando. Presiona Ctrl+C para detener.")
logger.info("💡 Tip: Envía /start o /status al bot para verificar su estado")
# Mantener el bot corriendo
await bot.run_until_disconnected()
if __name__ == '__main__':
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("\n🛑 Bot detenido por el usuario")
except Exception as e:
logger.error(f"❌ Error fatal: {e}", exc_info=True)