Files
AKharytonchyk d381a16182 Standardize project structure to match ComfyUI custom node conventions
- Fix pyproject.toml to proper Python package format
- Add node_list.json for ComfyUI Manager compatibility
- Reorganize class methods to follow ComfyUI patterns
- Add setup.py for Python package installation
- Add importable workflow JSON example
- Include proper version handling and metadata
- Follow standard ComfyUI custom node directory structure
2025-06-23 00:38:42 +02:00

231 lines
7.9 KiB
Python

import asyncio
import threading
import queue
import time
from typing import Dict, Any, Optional, Tuple
import logging
try:
from telegram import Update
from telegram.ext import Application, MessageHandler, filters, ContextTypes
except ImportError:
print("Please install python-telegram-bot: pip install python-telegram-bot")
raise
class TelegramListener:
"""
A ComfyUI node that listens to Telegram messages and outputs the text content.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"bot_token": ("STRING", {
"default": "",
"multiline": False,
"placeholder": "Enter your Telegram bot token here"
}),
"timeout": ("INT", {
"default": 10,
"min": 1,
"max": 300,
"step": 1
}),
}
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("message_text", "chat_id")
FUNCTION = "listen_for_message"
CATEGORY = "telegram"
OUTPUT_NODE = False
def __init__(self):
self.bot_token = None
self.application = None
self.message_queue = queue.Queue()
self.chat_ids = {} # Store chat IDs for responses
self.is_running = False
self.bot_thread = None
def listen_for_message(self, bot_token: str, timeout: int) -> Tuple[str, str]:
"""
Listen for Telegram messages and return the message text and chat ID.
"""
if not bot_token or not bot_token.strip():
return ("Error: Bot token is required", "")
if not bot_token.startswith("bot") and ":" not in bot_token:
return ("Error: Invalid bot token format", "")
# If bot token changed or not running, restart the bot
if self.bot_token != bot_token or not self.is_running:
self._stop_bot()
try:
self._start_bot(bot_token)
except Exception as e:
return (f"Error starting bot: {str(e)}", "")
# Wait for a message with timeout
start_time = time.time()
while time.time() - start_time < timeout:
try:
message_data = self.message_queue.get(timeout=1)
chat_id = str(message_data['chat_id'])
message_text = message_data['text']
# Store chat ID for potential response
self.chat_ids[chat_id] = message_data['chat_id']
return (message_text, chat_id)
except queue.Empty:
continue
return ("No message received within timeout", "")
def _start_bot(self, bot_token: str):
"""Start the Telegram bot in a separate thread."""
self.bot_token = bot_token
def run_bot():
try:
# Create application
self.application = Application.builder().token(bot_token).build()
# Add message handler
message_handler = MessageHandler(
filters.TEXT & ~filters.COMMAND,
self._handle_message
)
self.application.add_handler(message_handler)
# Run the bot
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self.is_running = True
loop.run_until_complete(self.application.run_polling(stop_signals=None))
except Exception as e:
logging.error(f"Bot error: {e}")
self.is_running = False
self.bot_thread = threading.Thread(target=run_bot, daemon=True)
self.bot_thread.start()
def _stop_bot(self):
"""Stop the running bot."""
if self.application and self.is_running:
try:
# Stop the application
asyncio.run(self.application.stop())
self.is_running = False
except Exception as e:
logging.error(f"Error stopping bot: {e}")
async def _handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle incoming Telegram messages."""
if update.message and update.message.text:
message_data = {
'text': update.message.text,
'chat_id': update.message.chat_id,
'user_id': update.message.from_user.id,
'username': update.message.from_user.username or "",
'timestamp': time.time()
}
self.message_queue.put(message_data)
class SaveToTelegram:
"""
A ComfyUI node that sends messages back to Telegram chats.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"bot_token": ("STRING", {
"default": "",
"multiline": False,
"placeholder": "Enter your Telegram bot token here"
}),
"chat_id": ("STRING", {
"default": "",
"multiline": False,
"placeholder": "Chat ID from Telegram Listener"
}),
"message": ("STRING", {
"default": "",
"multiline": True,
"placeholder": "Message to send"
}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("status",)
FUNCTION = "send_message"
CATEGORY = "telegram"
OUTPUT_NODE = True
def __init__(self):
self.applications = {} # Store applications by bot token
def send_message(self, bot_token: str, chat_id: str, message: str) -> Tuple[str]:
"""
Send a message to a Telegram chat.
"""
if not bot_token:
return ("Error: Bot token is required",)
if not chat_id:
return ("Error: Chat ID is required",)
if not message:
return ("Error: Message is required",)
try:
# Convert chat_id to int if it's numeric
try:
chat_id_int = int(chat_id)
except ValueError:
return (f"Error: Invalid chat ID format: {chat_id}",)
# Get or create application for this bot token
if bot_token not in self.applications:
self.applications[bot_token] = Application.builder().token(bot_token).build()
application = self.applications[bot_token]
# Send message synchronously
async def send_async():
await application.bot.send_message(chat_id=chat_id_int, text=message)
# Run in event loop
try:
loop = asyncio.get_event_loop()
if loop.is_running():
# If loop is already running, create a new one in a thread
def run_in_thread():
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
new_loop.run_until_complete(send_async())
new_loop.close()
thread = threading.Thread(target=run_in_thread)
thread.start()
thread.join()
else:
loop.run_until_complete(send_async())
except RuntimeError:
# No event loop, create one
asyncio.run(send_async())
return (f"Message sent successfully to chat {chat_id}",)
except Exception as e:
return (f"Error sending message: {str(e)}",)