solucion de flujo
This commit is contained in:
parent
c03105a9d0
commit
05439f1b37
1 changed files with 126 additions and 167 deletions
293
bot.py
293
bot.py
|
|
@ -37,18 +37,37 @@ log = logging.getLogger("enresocial_bot")
|
|||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def get_int_env(name: str, default: int = 0) -> int:
|
||||
"""
|
||||
Lee una variable de entorno como int.
|
||||
Si está vacía o mal formateada, devuelve default y escribe un warning.
|
||||
"""
|
||||
raw = os.getenv(name, "").strip()
|
||||
if not raw:
|
||||
return default
|
||||
try:
|
||||
return int(raw)
|
||||
except ValueError:
|
||||
log.warning(f"Variable de entorno {name}='{raw}' no es un entero válido. Usando {default}.")
|
||||
return default
|
||||
|
||||
|
||||
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
|
||||
CHANNEL_ID = int(os.getenv("TELEGRAM_CHANNEL_ID", "0")) # canal opcional
|
||||
GROUP_ID = int(os.getenv("TELEGRAM_GROUP_ID", "0")) # grupo opcional (si quieres limitar)
|
||||
|
||||
# Si algún día quieres limitar /publicar a un chat concreto,
|
||||
# puedes usar estos IDs (de momento no se filtra por ellos).
|
||||
CHANNEL_ID = get_int_env("TELEGRAM_CHANNEL_ID", 0)
|
||||
GROUP_ID = get_int_env("TELEGRAM_GROUP_ID", 0)
|
||||
|
||||
# Mastodon
|
||||
MASTODON_BASE_URL = os.getenv("MASTODON_BASE_URL")
|
||||
MASTODON_ACCESS_TOKEN = os.getenv("MASTODON_ACCESS_TOKEN")
|
||||
|
||||
# Instagram (cuando lo actives)
|
||||
# Instagram
|
||||
IG_BUSINESS_ACCOUNT_ID = os.getenv("IG_BUSINESS_ACCOUNT_ID")
|
||||
IG_ACCESS_TOKEN = os.getenv("IG_ACCESS_TOKEN")
|
||||
PUBLIC_UPLOAD_BASE_URL = os.getenv("PUBLIC_UPLOAD_BASE_URL") # p.ej. https://tudominio.com/uploads
|
||||
PUBLIC_UPLOAD_BASE_URL = os.getenv("PUBLIC_UPLOAD_BASE_URL")
|
||||
UPLOAD_DIR = os.getenv("UPLOAD_DIR", "./uploads")
|
||||
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
|
|
@ -57,35 +76,16 @@ if not TELEGRAM_TOKEN:
|
|||
log.error("Falta TELEGRAM_TOKEN en el .env")
|
||||
sys.exit(1)
|
||||
|
||||
TRIGGER = "/1212"
|
||||
TRIGGER_RE = re.compile(r"^\s*/1212\b", re.IGNORECASE)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PostPayload:
|
||||
text: str
|
||||
image_paths: List[str] # rutas locales de imágenes descargadas
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Ayuda /1212 (comportamiento existente)
|
||||
# ----------------------------
|
||||
def extract_command_payload(msg: Message) -> Optional[str]:
|
||||
"""
|
||||
Si el texto o el caption comienza con /1212, devuelve el resto (payload).
|
||||
"""
|
||||
full_text = (msg.text or msg.caption or "").strip()
|
||||
if not full_text:
|
||||
return None
|
||||
if TRIGGER_RE.match(full_text):
|
||||
return TRIGGER_RE.sub("", full_text, count=1).lstrip()
|
||||
return None
|
||||
image_paths: List[str]
|
||||
|
||||
|
||||
async def download_photos(context: ContextTypes.DEFAULT_TYPE, photos: List[PhotoSize]) -> List[str]:
|
||||
"""
|
||||
Descarga la foto de mayor resolución a un archivo temporal y devuelve la lista de rutas.
|
||||
Telegram envía varias resoluciones de la misma foto; tomamos la mayor.
|
||||
"""
|
||||
if not photos:
|
||||
return []
|
||||
|
|
@ -120,7 +120,6 @@ def post_to_mastodon(payload: PostPayload):
|
|||
def ensure_public_url(local_path: str) -> str:
|
||||
"""
|
||||
Convierte una ruta local en una URL pública donde Instagram pueda descargar la imagen.
|
||||
Opción simple: sirves /uploads con Nginx/Apache en PUBLIC_UPLOAD_BASE_URL.
|
||||
"""
|
||||
if not PUBLIC_UPLOAD_BASE_URL:
|
||||
raise RuntimeError("Falta PUBLIC_UPLOAD_BASE_URL para Instagram.")
|
||||
|
|
@ -130,11 +129,7 @@ def ensure_public_url(local_path: str) -> str:
|
|||
|
||||
def post_to_instagram(payload: PostPayload):
|
||||
"""
|
||||
Publicación a Instagram Business usando Instagram Graph API:
|
||||
1) Crear 'media container' con image_url público y caption.
|
||||
2) Publicar el container.
|
||||
|
||||
IMPORTANTE: necesitas una URL pública (HTTPS) alcanzable por Meta.
|
||||
Publicación a Instagram Business usando Instagram Graph API.
|
||||
"""
|
||||
if not (IG_BUSINESS_ACCOUNT_ID and IG_ACCESS_TOKEN):
|
||||
log.warning("[Instagram] No configurado, omito.")
|
||||
|
|
@ -144,23 +139,24 @@ def post_to_instagram(payload: PostPayload):
|
|||
log.warning("[Instagram] No hay imagen para publicar (IG requiere imagen/video).")
|
||||
return
|
||||
|
||||
# Instagram solo acepta 1 imagen por post básico con /media (para carrusel es otro flujo)
|
||||
img_url = ensure_public_url(payload.image_paths[0])
|
||||
|
||||
# 1) Crear contenedor
|
||||
media_resp = requests.post(
|
||||
f"https://graph.facebook.com/v21.0/{IG_BUSINESS_ACCOUNT_ID}/media",
|
||||
data={"image_url": img_url, "caption": payload.text or "", "access_token": IG_ACCESS_TOKEN},
|
||||
timeout=60
|
||||
data={
|
||||
"image_url": img_url,
|
||||
"caption": payload.text or "",
|
||||
"access_token": IG_ACCESS_TOKEN,
|
||||
},
|
||||
timeout=60,
|
||||
)
|
||||
media_resp.raise_for_status()
|
||||
creation_id = media_resp.json().get("id")
|
||||
|
||||
# 2) Publicar
|
||||
publish_resp = requests.post(
|
||||
f"https://graph.facebook.com/v21.0/{IG_BUSINESS_ACCOUNT_ID}/media_publish",
|
||||
data={"creation_id": creation_id, "access_token": IG_ACCESS_TOKEN},
|
||||
timeout=60
|
||||
timeout=60,
|
||||
)
|
||||
publish_resp.raise_for_status()
|
||||
log.info("[Instagram] Publicado.")
|
||||
|
|
@ -170,7 +166,9 @@ def post_to_instagram(payload: PostPayload):
|
|||
# Borradores para /publicar
|
||||
# ----------------------------
|
||||
DRAFT_KEY = "current_draft"
|
||||
DRAFT_STATE_KEY = "draft_state" # 'idle', 'waiting_title', 'waiting_description', 'waiting_hashtags', 'waiting_time', 'waiting_date'
|
||||
# 'idle', 'waiting_title', 'waiting_description', 'waiting_hashtags',
|
||||
# 'waiting_time', 'waiting_date', 'waiting_photo'
|
||||
DRAFT_STATE_KEY = "draft_state"
|
||||
|
||||
|
||||
def init_draft(
|
||||
|
|
@ -178,7 +176,7 @@ def init_draft(
|
|||
chat_id: int,
|
||||
title: str = "",
|
||||
description: str = "",
|
||||
image_paths=None
|
||||
image_paths: Optional[List[str]] = None,
|
||||
):
|
||||
if image_paths is None:
|
||||
image_paths = []
|
||||
|
|
@ -223,13 +221,16 @@ def build_draft_preview_text(draft: dict) -> str:
|
|||
else:
|
||||
time_str = "—"
|
||||
|
||||
has_image = "Sí ✅" if draft.get("image_paths") else "No —"
|
||||
|
||||
text = (
|
||||
"✏️ *Borrador de publicación*\n\n"
|
||||
f"📌 *Título*: {title}\n\n"
|
||||
f"📝 *Descripción:*\n{description}\n\n"
|
||||
f"🏷 Hashtags: {hashtags}\n"
|
||||
f"⏰ Horario: {time_str}\n"
|
||||
f"📅 Fecha: {date}"
|
||||
f"📅 Fecha: {date}\n"
|
||||
f"🖼 Imagen: {has_image}"
|
||||
)
|
||||
return text
|
||||
|
||||
|
|
@ -245,18 +246,21 @@ def build_draft_keyboard() -> InlineKeyboardMarkup:
|
|||
InlineKeyboardButton("Horario", callback_data="draft:time"),
|
||||
InlineKeyboardButton("Fecha", callback_data="draft:date"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton("📷 Adjuntar foto", callback_data="draft:photo"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton("✅ Publicar", callback_data="draft:publish"),
|
||||
InlineKeyboardButton("❌ Cancelar", callback_data="draft:cancel"),
|
||||
]
|
||||
],
|
||||
]
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
|
||||
async def refresh_draft_message(context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Borra el mensaje de preview anterior (si existe) y envía uno nuevo
|
||||
con la preview + botones, para que siempre quede al final del chat.
|
||||
Elimina el mensaje de preview anterior (si existe) y envía uno nuevo
|
||||
con el borrador actualizado + botones.
|
||||
"""
|
||||
draft = get_draft(context)
|
||||
if not draft:
|
||||
|
|
@ -265,19 +269,17 @@ async def refresh_draft_message(context: ContextTypes.DEFAULT_TYPE):
|
|||
chat_id = draft["chat_id"]
|
||||
old_message_id = draft.get("message_id")
|
||||
|
||||
# Borramos el mensaje anterior del borrador si existe
|
||||
if old_message_id:
|
||||
try:
|
||||
await context.bot.delete_message(chat_id=chat_id, message_id=old_message_id)
|
||||
except Exception as e:
|
||||
log.warning(f"No se pudo borrar el mensaje anterior del borrador: {e}")
|
||||
|
||||
# Enviamos un nuevo mensaje de preview con la botonera
|
||||
sent = await context.bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=build_draft_preview_text(draft),
|
||||
reply_markup=build_draft_keyboard(),
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
draft["message_id"] = sent.message_id
|
||||
|
||||
|
|
@ -293,25 +295,24 @@ async def start_publish(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||
full_text = msg.text or ""
|
||||
parts = full_text.split(" ", 1)
|
||||
|
||||
# Si el usuario pone texto tras /publicar, lo usamos como descripción inicial
|
||||
# Texto que venga detrás de /publicar lo usamos como descripción inicial
|
||||
base_description = ""
|
||||
if len(parts) >= 2 and parts[1].strip():
|
||||
base_description = parts[1].strip()
|
||||
|
||||
# Descargamos foto si la hay
|
||||
image_paths: List[str] = []
|
||||
# Si el usuario lanzó /publicar con una foto ya adjunta, también la usamos
|
||||
if msg.photo:
|
||||
image_paths = await download_photos(context, msg.photo)
|
||||
|
||||
# Iniciamos borrador con título vacío y descripción opcional
|
||||
init_draft(context, msg.chat.id, title="", description=base_description, image_paths=image_paths)
|
||||
|
||||
# Mostramos el borrador con la botonera al final del chat
|
||||
# Primer mensaje interactivo con los botones
|
||||
await refresh_draft_message(context)
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Callback de botones (Título / Descripción / Hashtag / Horario / Fecha / Publicar / Cancelar)
|
||||
# Callback de botones
|
||||
# ----------------------------
|
||||
async def draft_keyboard_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
query = update.callback_query
|
||||
|
|
@ -335,35 +336,43 @@ async def draft_keyboard_callback(update: Update, context: ContextTypes.DEFAULT_
|
|||
set_state(context, "waiting_title")
|
||||
await query.message.reply_text(
|
||||
"Escribe ahora el *título* de la publicación.",
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
elif action == "description":
|
||||
set_state(context, "waiting_description")
|
||||
await query.message.reply_text(
|
||||
"Escribe ahora la *descripción* de la publicación.",
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
elif action == "hashtags":
|
||||
set_state(context, "waiting_hashtags")
|
||||
await query.message.reply_text(
|
||||
"Escribe ahora los *hashtags* en un mensaje (por ejemplo: `#feminismo #sostenibilidad`).",
|
||||
parse_mode="Markdown"
|
||||
"Escribe ahora los *hashtags* (por ejemplo: `#feminismo #sostenibilidad`).",
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
elif action == "time":
|
||||
set_state(context, "waiting_time")
|
||||
await query.message.reply_text(
|
||||
"Escribe ahora el *horario* en formato `HH:MM / HH:MM` (por ejemplo: `19:30 / 21:30`).",
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
elif action == "date":
|
||||
set_state(context, "waiting_date")
|
||||
await query.message.reply_text(
|
||||
"Escribe ahora la *fecha* en formato `DD/MM/AAAA` (por ejemplo: `13/04/2025`).",
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
elif action == "photo":
|
||||
set_state(context, "waiting_photo")
|
||||
await query.message.reply_text(
|
||||
"Ahora envía la *foto* como un mensaje normal (sin comandos). "
|
||||
"Se usará como imagen de la publicación.",
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
elif action == "publish":
|
||||
|
|
@ -379,9 +388,14 @@ async def draft_keyboard_callback(update: Update, context: ContextTypes.DEFAULT_
|
|||
|
||||
|
||||
# ----------------------------
|
||||
# Manejo de los textos de título / descripción / hashtag / hora / fecha
|
||||
# Manejo de texto para el borrador
|
||||
# ----------------------------
|
||||
async def handle_draft_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Aquí llega el texto que el usuario escribe DESPUÉS de pulsar un botón
|
||||
(Título, Descripción, Hashtags, Hora, Fecha).
|
||||
Guardamos el dato y volvemos a mostrar el borrador con los botones.
|
||||
"""
|
||||
msg = update.message
|
||||
if msg is None or not msg.text:
|
||||
return
|
||||
|
|
@ -389,39 +403,40 @@ async def handle_draft_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||
state = get_state(context)
|
||||
draft = get_draft(context)
|
||||
if not draft:
|
||||
return # no hay borrador activo, ignoramos
|
||||
return
|
||||
|
||||
text = msg.text.strip()
|
||||
|
||||
# Título
|
||||
# Opcional: borrar el mensaje de texto del usuario para que el chat quede “limpio”
|
||||
try:
|
||||
await msg.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if state == "waiting_title":
|
||||
draft["title"] = text
|
||||
set_state(context, "idle")
|
||||
await refresh_draft_message(context)
|
||||
return
|
||||
|
||||
# Descripción
|
||||
if state == "waiting_description":
|
||||
draft["description"] = text
|
||||
set_state(context, "idle")
|
||||
await refresh_draft_message(context)
|
||||
return
|
||||
|
||||
# Hashtags
|
||||
if state == "waiting_hashtags":
|
||||
draft["hashtags"] = text
|
||||
set_state(context, "idle")
|
||||
await refresh_draft_message(context)
|
||||
return
|
||||
|
||||
# Horario rango HH:MM / HH:MM
|
||||
if state == "waiting_time":
|
||||
# Validar formato tipo "19:30 / 21:30"
|
||||
m = re.match(r"^\s*(\d{1,2}:\d{2})\s*/\s*(\d{1,2}:\d{2})\s*$", text)
|
||||
if not m:
|
||||
await msg.reply_text(
|
||||
"Formato de horario no válido. Usa `HH:MM / HH:MM`, por ejemplo `19:30 / 21:30`.",
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
return
|
||||
start, end = m.group(1), m.group(2)
|
||||
|
|
@ -431,12 +446,11 @@ async def handle_draft_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||
await refresh_draft_message(context)
|
||||
return
|
||||
|
||||
# Fecha DD/MM/AAAA
|
||||
if state == "waiting_date":
|
||||
if not re.match(r"^\d{1,2}/\d{1,2}/\d{4}$", text):
|
||||
await msg.reply_text(
|
||||
"Formato de fecha no válido. Usa `DD/MM/AAAA`, por ejemplo `13/04/2025`.",
|
||||
parse_mode="Markdown"
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
return
|
||||
draft["date"] = text
|
||||
|
|
@ -444,12 +458,45 @@ async def handle_draft_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||
await refresh_draft_message(context)
|
||||
return
|
||||
|
||||
# Si no estamos esperando nada especial, no hacemos nada
|
||||
# Si no estamos esperando nada, ignoramos
|
||||
return
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Publicar borrador (/publicar) realmente
|
||||
# Manejo de fotos para el borrador
|
||||
# ----------------------------
|
||||
async def handle_draft_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Aquí llega la foto cuando el usuario ha pulsado 'Adjuntar foto'.
|
||||
Descargamos la imagen, la guardamos en el borrador y mostramos de nuevo el borrador.
|
||||
"""
|
||||
msg = update.message
|
||||
if msg is None or not msg.photo:
|
||||
return
|
||||
|
||||
state = get_state(context)
|
||||
draft = get_draft(context)
|
||||
if not draft:
|
||||
return
|
||||
|
||||
if state != "waiting_photo":
|
||||
# Si no estamos esperando una foto, ignoramos (o podríamos decidir otra cosa)
|
||||
return
|
||||
|
||||
# Opcional: borrar el mensaje de foto del usuario para limpiar el chat
|
||||
try:
|
||||
await msg.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
image_paths = await download_photos(context, msg.photo)
|
||||
draft["image_paths"] = image_paths
|
||||
set_state(context, "idle")
|
||||
await refresh_draft_message(context)
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Publicar borrador
|
||||
# ----------------------------
|
||||
async def finalize_and_post_draft(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
query = update.callback_query
|
||||
|
|
@ -463,18 +510,18 @@ async def finalize_and_post_draft(update: Update, context: ContextTypes.DEFAULT_
|
|||
|
||||
title = draft.get("title") or ""
|
||||
description = draft.get("description") or ""
|
||||
base_text_parts = []
|
||||
base_text_parts: List[str] = []
|
||||
|
||||
if title:
|
||||
base_text_parts.append(title)
|
||||
if description:
|
||||
if title:
|
||||
base_text_parts.append("") # línea en blanco entre título y descripción
|
||||
base_text_parts.append("")
|
||||
base_text_parts.append(description)
|
||||
|
||||
base_text = "\n".join(base_text_parts) if base_text_parts else ""
|
||||
|
||||
extra_lines = []
|
||||
extra_lines: List[str] = []
|
||||
|
||||
if draft.get("date"):
|
||||
extra_lines.append(f"📅 {draft['date']}")
|
||||
|
|
@ -520,114 +567,26 @@ async def finalize_and_post_draft(update: Update, context: ContextTypes.DEFAULT_
|
|||
pass
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# Handlers existentes /1212
|
||||
# ----------------------------
|
||||
async def handle_channel_post(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Para posts en CANAL."""
|
||||
if update.channel_post is None:
|
||||
return
|
||||
msg = update.channel_post
|
||||
|
||||
# Debug de ID y tipo
|
||||
log.info(f"[TG] channel_post en chat.id={msg.chat.id} (type=channel)")
|
||||
|
||||
# Limitar opcionalmente a un canal concreto
|
||||
if CHANNEL_ID and msg.chat.id != CHANNEL_ID:
|
||||
return
|
||||
|
||||
payload_text = extract_command_payload(msg)
|
||||
if payload_text is None:
|
||||
return # no empieza por /1212
|
||||
|
||||
image_paths: List[str] = []
|
||||
if msg.photo:
|
||||
image_paths = await download_photos(context, msg.photo)
|
||||
|
||||
payload = PostPayload(text=payload_text, image_paths=image_paths)
|
||||
|
||||
try:
|
||||
post_to_mastodon(payload)
|
||||
except Exception as e:
|
||||
log.exception(f"[Mastodon] Error: {e}")
|
||||
|
||||
try:
|
||||
post_to_instagram(payload)
|
||||
except Exception as e:
|
||||
log.exception(f"[Instagram] Error: {e}")
|
||||
|
||||
try:
|
||||
await msg.reply_text("Publicado en redes ✅", quote=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def handle_group_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Para mensajes en GRUPOS/SUPERGRUPOS que empiecen por /1212 (texto o caption)."""
|
||||
if update.message is None:
|
||||
return
|
||||
msg = update.message
|
||||
|
||||
# Debug de ID y tipo
|
||||
log.info(f"[TG] message en chat.id={msg.chat.id} (type={msg.chat.type})")
|
||||
|
||||
# Limitar opcionalmente a un grupo concreto
|
||||
if GROUP_ID and msg.chat.id != GROUP_ID:
|
||||
return
|
||||
|
||||
# Aseguramos que sólo atendemos si empieza por /1212 en texto o caption
|
||||
if not (TRIGGER_RE.match(msg.text or "") or TRIGGER_RE.match(msg.caption or "")):
|
||||
return
|
||||
|
||||
payload_text = extract_command_payload(msg)
|
||||
if payload_text is None:
|
||||
return
|
||||
|
||||
image_paths: List[str] = []
|
||||
if msg.photo:
|
||||
image_paths = await download_photos(context, msg.photo)
|
||||
|
||||
payload = PostPayload(text=payload_text, image_paths=image_paths)
|
||||
|
||||
try:
|
||||
post_to_mastodon(payload)
|
||||
except Exception as e:
|
||||
log.exception(f"[Mastodon] Error: {e}")
|
||||
|
||||
try:
|
||||
post_to_instagram(payload)
|
||||
except Exception as e:
|
||||
log.exception(f"[Instagram] Error: {e}")
|
||||
|
||||
try:
|
||||
await msg.reply_text("Publicado en redes ✅", quote=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# main()
|
||||
# ----------------------------
|
||||
def main():
|
||||
app = ApplicationBuilder().token(TELEGRAM_TOKEN).build()
|
||||
|
||||
# NUEVO: comando /publicar + flujo de borrador
|
||||
# Único flujo: /publicar + botones + textos + fotos del borrador
|
||||
app.add_handler(CommandHandler("publicar", start_publish))
|
||||
app.add_handler(CallbackQueryHandler(draft_keyboard_callback, pattern=r"^draft:"))
|
||||
# Textos normales para título / descripción / hashtags / hora / fecha
|
||||
|
||||
# Texto para título/descripcion/hashtags/hora/fecha
|
||||
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_draft_text))
|
||||
|
||||
# EXISTENTE: CANALES con /1212
|
||||
app.add_handler(MessageHandler(filters.ChatType.CHANNEL, handle_channel_post))
|
||||
# Fotos para "Adjuntar foto"
|
||||
app.add_handler(MessageHandler(filters.PHOTO, handle_draft_photo))
|
||||
|
||||
# EXISTENTE: GRUPOS/SUPERGRUPOS con /1212
|
||||
group_filter = filters.ChatType.GROUPS & (filters.Regex(TRIGGER_RE) | filters.CaptionRegex(TRIGGER_RE))
|
||||
app.add_handler(MessageHandler(group_filter, handle_group_message))
|
||||
|
||||
log.info("Bot escuchando canal/grupos…")
|
||||
log.info("Bot escuchando /publicar…")
|
||||
app.run_polling(
|
||||
allowed_updates=["message", "channel_post", "callback_query"],
|
||||
drop_pending_updates=False
|
||||
allowed_updates=["message", "callback_query"],
|
||||
drop_pending_updates=False,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue