Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,15 +1,22 @@
|
|
| 1 |
-
|
| 2 |
|
| 3 |
import os
|
| 4 |
import asyncio
|
| 5 |
import json
|
| 6 |
import logging
|
| 7 |
-
import sys
|
| 8 |
|
| 9 |
# --- Detailed Logging Setup ---
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
logging.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
logger.debug("DEBUG: Importing Telethon...")
|
| 15 |
from telethon import TelegramClient, events # type: ignore
|
|
@@ -18,10 +25,49 @@ from telethon.tl import types # For DocumentAttributeFilename
|
|
| 18 |
logger.info(f"DEBUG: Using Telethon version: {telethon.__version__}") # Log the version
|
| 19 |
logger.debug("DEBUG: Telethon imported.")
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
# --- Session Name (CHANGE THIS OFTEN FOR DEBUGGING CONNECTION HANGS) ---
|
| 24 |
-
SESSION_VERSION = "
|
| 25 |
SESSION_NAME = f'session/image_bot_session_{SESSION_VERSION}'
|
| 26 |
logger.info(f"DEBUG: Using session name: {SESSION_NAME}")
|
| 27 |
|
|
@@ -33,15 +79,12 @@ except Exception as e:
|
|
| 33 |
logger.critical(f"CRITICAL ERROR: Failed to initialize TelegramClient: {e}", exc_info=True)
|
| 34 |
sys.exit(1)
|
| 35 |
|
| 36 |
-
# ... (rest of the script, including the main() function) ...
|
| 37 |
-
|
| 38 |
-
|
| 39 |
|
| 40 |
# --- Paths and Directory Setup ---
|
| 41 |
logger.info("DEBUG: Defining paths and ensuring directories...")
|
| 42 |
DATA_DIR = "data"
|
| 43 |
-
DOWNLOADS_DIR = "downloads"
|
| 44 |
-
SESSION_DIR = "session"
|
| 45 |
|
| 46 |
CONFIG_FILENAME = "config.json"
|
| 47 |
TEMPLATE_FILENAME = "user_template.png"
|
|
@@ -51,15 +94,13 @@ CONFIG_PATH = os.path.join(DATA_DIR, CONFIG_FILENAME)
|
|
| 51 |
TEMPLATE_PATH = os.path.join(DATA_DIR, TEMPLATE_FILENAME)
|
| 52 |
USER_FONT_PATH = os.path.join(DATA_DIR, USER_FONT_FILENAME)
|
| 53 |
|
| 54 |
-
# Dockerfile should create these, but Python checks are good for robustness
|
| 55 |
for dir_path in [DATA_DIR, DOWNLOADS_DIR, SESSION_DIR]:
|
| 56 |
logger.debug(f"DEBUG: Ensuring directory exists: {dir_path}")
|
| 57 |
try:
|
| 58 |
os.makedirs(dir_path, exist_ok=True)
|
| 59 |
logger.info(f"DEBUG: Directory '{dir_path}' ensured (created if didn't exist).")
|
| 60 |
-
# Optional: Check writability, though chmod 777 in Dockerfile should cover this
|
| 61 |
if not os.access(dir_path, os.W_OK):
|
| 62 |
-
logger.warning(f"DEBUG: Directory '{dir_path}' might not be writable despite creation!")
|
| 63 |
except Exception as e:
|
| 64 |
logger.error(f"DEBUG: Error ensuring directory '{dir_path}': {e}", exc_info=True)
|
| 65 |
logger.info("DEBUG: Paths defined and directories ensured.")
|
|
@@ -74,7 +115,7 @@ DEFAULT_CONFIG = {
|
|
| 74 |
"line_spacing": 10
|
| 75 |
}
|
| 76 |
bot_config = {}
|
| 77 |
-
logger.debug(f"DEBUG: Default config: {DEFAULT_CONFIG}")
|
| 78 |
|
| 79 |
# --- Config Management ---
|
| 80 |
def load_bot_config():
|
|
@@ -85,36 +126,35 @@ def load_bot_config():
|
|
| 85 |
with open(CONFIG_PATH, 'r') as f:
|
| 86 |
loaded_values = json.load(f)
|
| 87 |
logger.info(f"DEBUG: Successfully read from {CONFIG_PATH}.")
|
| 88 |
-
bot_config = DEFAULT_CONFIG.copy()
|
| 89 |
-
bot_config.update(loaded_values)
|
| 90 |
logger.info("DEBUG: Merged loaded config with defaults.")
|
| 91 |
else:
|
| 92 |
logger.info(f"DEBUG: Config file {CONFIG_PATH} not found. Using defaults.")
|
| 93 |
bot_config = DEFAULT_CONFIG.copy()
|
| 94 |
-
#
|
| 95 |
-
save_bot_config(initial_load=True)
|
| 96 |
logger.info(f"DEBUG: Final configuration after load/init: {bot_config}")
|
| 97 |
except json.JSONDecodeError as e:
|
| 98 |
logger.error(f"DEBUG: Error decoding JSON from {CONFIG_PATH}. Using defaults and overwriting. Error: {e}", exc_info=True)
|
| 99 |
bot_config = DEFAULT_CONFIG.copy()
|
| 100 |
-
save_bot_config(initial_load=True)
|
| 101 |
except Exception as e:
|
| 102 |
logger.error(f"DEBUG: Critical error loading config, using defaults: {e}", exc_info=True)
|
| 103 |
bot_config = DEFAULT_CONFIG.copy()
|
| 104 |
-
|
| 105 |
-
if not os.path.exists(CONFIG_PATH): # Save only if file didn't exist
|
| 106 |
save_bot_config(initial_load=True)
|
| 107 |
|
| 108 |
|
| 109 |
def save_bot_config(initial_load=False):
|
| 110 |
global bot_config
|
| 111 |
-
if not initial_load:
|
| 112 |
logger.info(f"DEBUG: Attempting to save bot configuration to {CONFIG_PATH}...")
|
| 113 |
try:
|
| 114 |
with open(CONFIG_PATH, 'w') as f:
|
| 115 |
json.dump(bot_config, f, indent=4)
|
|
|
|
| 116 |
if not initial_load:
|
| 117 |
-
logger.info(f"DEBUG: Configuration saved successfully
|
| 118 |
except Exception as e:
|
| 119 |
logger.error(f"DEBUG: Error saving config to {CONFIG_PATH}: {e}", exc_info=True)
|
| 120 |
|
|
@@ -125,11 +165,10 @@ logger.debug("DEBUG: User sessions initialized as empty dict.")
|
|
| 125 |
# --- Helper Functions (Image Processing) ---
|
| 126 |
async def generate_image_with_frame(raw_image_path, frame_template_path, caption_text, chat_id):
|
| 127 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Starting image generation. Raw: '{raw_image_path}', Template: '{frame_template_path}', Caption: '{caption_text[:30]}...'")
|
| 128 |
-
output_filename = f"final_image_{chat_id}_{SESSION_VERSION}.png"
|
| 129 |
output_path = os.path.join(DOWNLOADS_DIR, output_filename)
|
| 130 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Output path will be: {output_path}")
|
| 131 |
|
| 132 |
-
# Use loaded configuration
|
| 133 |
cfg_raw_box = bot_config.get("raw_image_box", DEFAULT_CONFIG["raw_image_box"])
|
| 134 |
cfg_caption_area = bot_config.get("caption_area", DEFAULT_CONFIG["caption_area"])
|
| 135 |
cfg_font_size = bot_config.get("caption_font_size", DEFAULT_CONFIG["caption_font_size"])
|
|
@@ -165,20 +204,21 @@ async def generate_image_with_frame(raw_image_path, frame_template_path, caption
|
|
| 165 |
if not font_to_use:
|
| 166 |
try:
|
| 167 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Attempting to load 'arial.ttf' as fallback.")
|
| 168 |
-
font_to_use = ImageFont.truetype("arial.ttf", cfg_font_size)
|
| 169 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Fallback font 'arial.ttf' loaded.")
|
| 170 |
except IOError:
|
| 171 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Arial.ttf not found, using Pillow's load_default(). This may look very basic.")
|
| 172 |
-
font_to_use = ImageFont.load_default().font_variant(size=cfg_font_size)
|
|
|
|
| 173 |
|
| 174 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Drawing caption text...")
|
| 175 |
lines = caption_text.split('\n')
|
| 176 |
current_y = cfg_caption_area['y']
|
| 177 |
for i, line in enumerate(lines):
|
| 178 |
-
try:
|
| 179 |
line_bbox = draw.textbbox((0, 0), line, font=font_to_use)
|
| 180 |
line_height = line_bbox[3] - line_bbox[1]
|
| 181 |
-
except AttributeError:
|
| 182 |
(text_width, text_height) = draw.textsize(line, font=font_to_use)
|
| 183 |
line_height = text_height
|
| 184 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Drawing line {i+1}/{len(lines)}: '{line[:20]}...' at y={current_y}, height={line_height}")
|
|
@@ -186,7 +226,7 @@ async def generate_image_with_frame(raw_image_path, frame_template_path, caption
|
|
| 186 |
current_y += line_height + cfg_line_spacing
|
| 187 |
|
| 188 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Saving final image to {output_path}")
|
| 189 |
-
final_img.convert("RGB").save(output_path, "PNG")
|
| 190 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Image generation complete: {output_path}")
|
| 191 |
return output_path
|
| 192 |
|
|
@@ -201,7 +241,7 @@ async def generate_image_with_frame(raw_image_path, frame_template_path, caption
|
|
| 201 |
@bot.on(events.NewMessage(pattern='/start'))
|
| 202 |
async def start_handler(event):
|
| 203 |
chat_id = event.chat_id
|
| 204 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /start command
|
| 205 |
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
| 206 |
start_message = (
|
| 207 |
"Welcome! This bot helps you create images with a template.\n\n"
|
|
@@ -214,33 +254,32 @@ async def start_handler(event):
|
|
| 214 |
"`/create` - Start creating a new image.\n"
|
| 215 |
"`/viewconfig` - See current layout settings.\n"
|
| 216 |
"`/cancel` - Cancel current operation.\n\n"
|
|
|
|
| 217 |
"**Note on Hosting:** The `data/` and `session/` directories should ideally use persistent storage on your hosting platform. "
|
| 218 |
"If using ephemeral storage (common on free tiers), your uploaded template, font, config, and session might be lost on restarts, requiring setup again."
|
| 219 |
)
|
| 220 |
await event.reply(start_message)
|
| 221 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Sent welcome message for /start.")
|
| 222 |
|
| 223 |
-
# ... (Other command handlers: /settemplate, /setfont, /viewconfig, /help_config, /setconfig, /create, /cancel)
|
| 224 |
-
# Add logger.info(f"DEBUG: [ChatID: {chat_id}] Received {command_name} command...") to each
|
| 225 |
|
| 226 |
@bot.on(events.NewMessage(pattern='/settemplate'))
|
| 227 |
async def set_template_handler(event):
|
| 228 |
chat_id = event.chat_id
|
| 229 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /settemplate command.")
|
| 230 |
user_sessions[chat_id] = {'state': 'awaiting_template_image', 'data': {}}
|
| 231 |
await event.reply("Please send your frame template image (e.g., a PNG or JPG).")
|
| 232 |
|
| 233 |
@bot.on(events.NewMessage(pattern='/setfont'))
|
| 234 |
async def set_font_handler(event):
|
| 235 |
chat_id = event.chat_id
|
| 236 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /setfont command.")
|
| 237 |
user_sessions[chat_id] = {'state': 'awaiting_font_file', 'data': {}}
|
| 238 |
await event.reply("Please send your `.ttf` font file as a document/file.")
|
| 239 |
|
| 240 |
@bot.on(events.NewMessage(pattern='/viewconfig'))
|
| 241 |
async def view_config_handler(event):
|
| 242 |
chat_id = event.chat_id
|
| 243 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /viewconfig command.")
|
| 244 |
load_bot_config() # Ensure config is fresh
|
| 245 |
config_str = "Current Bot Configuration:\n"
|
| 246 |
config_str += f"- Font: {'User font set (' + USER_FONT_FILENAME + ')' if os.path.exists(USER_FONT_PATH) else 'Using fallback font (Arial or Pillow default)'}\n"
|
|
@@ -251,7 +290,7 @@ async def view_config_handler(event):
|
|
| 251 |
@bot.on(events.NewMessage(pattern='/help_config'))
|
| 252 |
async def help_config_handler(event):
|
| 253 |
chat_id = event.chat_id
|
| 254 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /help_config command.")
|
| 255 |
await event.reply(
|
| 256 |
"**Configuration Commands (`/setconfig <key> <value>`):**\n"
|
| 257 |
"`raw_image_box_x <num>` (e.g., 40)\n"
|
|
@@ -273,12 +312,12 @@ async def set_config_handler(event):
|
|
| 273 |
chat_id = event.chat_id
|
| 274 |
key_to_set = event.pattern_match.group(1).strip()
|
| 275 |
value_str = event.pattern_match.group(2).strip()
|
| 276 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /setconfig command. Key: '{key_to_set}', Value: '{value_str}'")
|
| 277 |
|
| 278 |
load_bot_config()
|
| 279 |
-
original_config_copy = json.dumps(bot_config)
|
| 280 |
updated = False
|
| 281 |
-
sub_key = None
|
| 282 |
|
| 283 |
try:
|
| 284 |
if key_to_set.startswith("raw_image_box_"):
|
|
@@ -295,15 +334,14 @@ async def set_config_handler(event):
|
|
| 295 |
bot_config[key_to_set] = int(value_str)
|
| 296 |
updated = True
|
| 297 |
elif key_to_set == "caption_color":
|
| 298 |
-
bot_config[key_to_set] = value_str
|
| 299 |
updated = True
|
| 300 |
|
| 301 |
if updated:
|
| 302 |
if json.dumps(bot_config) != original_config_copy:
|
| 303 |
save_bot_config()
|
| 304 |
-
# Construct reply value more carefully
|
| 305 |
reply_value = value_str
|
| 306 |
-
if sub_key:
|
| 307 |
if key_to_set.startswith("raw_image_box_"):
|
| 308 |
reply_value = bot_config.get("raw_image_box", {}).get(sub_key, value_str)
|
| 309 |
elif key_to_set.startswith("caption_area_"):
|
|
@@ -319,7 +357,7 @@ async def set_config_handler(event):
|
|
| 319 |
await event.reply(f"Unknown or non-configurable key: '{key_to_set}'. See `/help_config`.")
|
| 320 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Unknown config key attempted: '{key_to_set}'.")
|
| 321 |
except ValueError:
|
| 322 |
-
await event.reply(f"Invalid value for '{key_to_set}'. Please provide a number where expected
|
| 323 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Invalid value for config key '{key_to_set}': '{value_str}'.")
|
| 324 |
except Exception as e:
|
| 325 |
await event.reply(f"Error setting config: {e}")
|
|
@@ -329,7 +367,7 @@ async def set_config_handler(event):
|
|
| 329 |
@bot.on(events.NewMessage(pattern='/create'))
|
| 330 |
async def create_post_handler(event):
|
| 331 |
chat_id = event.chat_id
|
| 332 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /create command.")
|
| 333 |
if not os.path.exists(TEMPLATE_PATH):
|
| 334 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] /create failed: Template not found at {TEMPLATE_PATH}.")
|
| 335 |
await event.reply("No template found. Please set one using `/settemplate` first.")
|
|
@@ -346,7 +384,7 @@ async def create_post_handler(event):
|
|
| 346 |
@bot.on(events.NewMessage(pattern='/cancel'))
|
| 347 |
async def cancel_handler(event):
|
| 348 |
chat_id = event.chat_id
|
| 349 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] Received /cancel command.")
|
| 350 |
if chat_id in user_sessions and user_sessions[chat_id]['state'] != 'idle':
|
| 351 |
temp_raw_path = user_sessions[chat_id].get('data', {}).get('raw_image_path')
|
| 352 |
if temp_raw_path and os.path.exists(temp_raw_path):
|
|
@@ -360,28 +398,22 @@ async def cancel_handler(event):
|
|
| 360 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Operation cancelled, state reset to 'idle'.")
|
| 361 |
else:
|
| 362 |
await event.reply("Nothing to cancel.")
|
| 363 |
-
logger.info(f"DEBUG: [ChatID: {chat_id}] /cancel received but nothing to cancel
|
| 364 |
|
| 365 |
@bot.on(events.NewMessage)
|
| 366 |
async def message_handler(event): # pylint: disable=too-many-branches, too-many-statements
|
| 367 |
chat_id = event.chat_id
|
| 368 |
-
if chat_id not in user_sessions:
|
| 369 |
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
| 370 |
-
logger.debug(f"DEBUG: [ChatID: {chat_id}] New user or session cleared, initialized session.")
|
| 371 |
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
if event.text and event.text.startswith(('/', '#', '.')): # Common command prefixes
|
| 375 |
-
logger.debug(f"DEBUG: [ChatID: {chat_id}] Message looks like a command ('{event.text[:20]}...'), letting patterned handlers manage it.")
|
| 376 |
return
|
| 377 |
|
| 378 |
-
|
| 379 |
current_state = user_sessions.get(chat_id, {}).get('state', 'idle')
|
| 380 |
session_data = user_sessions.get(chat_id, {}).get('data', {})
|
| 381 |
-
logger.debug(f"DEBUG: [ChatID: {chat_id}] Message received. Current state: '{current_state}'. Message text (first 30 chars): '{str(event.raw_text)[:30]}...' Is photo: {event.photo is not None}. Is document: {event.document is not None}.")
|
| 382 |
|
| 383 |
-
|
| 384 |
-
# Handle font upload (as document)
|
| 385 |
if event.document and current_state == 'awaiting_font_file':
|
| 386 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Document received in 'awaiting_font_file' state.")
|
| 387 |
doc_attrs = event.document.attributes
|
|
@@ -391,32 +423,29 @@ async def message_handler(event): # pylint: disable=too-many-branches, too-many-
|
|
| 391 |
filename = attr.file_name
|
| 392 |
break
|
| 393 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Document filename: '{filename}', MIME type: '{event.document.mime_type}'")
|
| 394 |
-
|
| 395 |
is_ttf = False
|
| 396 |
if filename.lower().endswith('.ttf'):
|
| 397 |
is_ttf = True
|
| 398 |
elif hasattr(event.document, 'mime_type') and \
|
| 399 |
('font' in event.document.mime_type or 'ttf' in event.document.mime_type or 'opentype' in event.document.mime_type):
|
| 400 |
is_ttf = True
|
| 401 |
-
|
| 402 |
if is_ttf:
|
| 403 |
await event.reply("Downloading font file...")
|
| 404 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Downloading TTF font: {filename}")
|
| 405 |
try:
|
| 406 |
await bot.download_media(event.message.document, USER_FONT_PATH)
|
| 407 |
user_sessions[chat_id]['state'] = 'idle'
|
| 408 |
-
await event.reply(f"Font saved as '{USER_FONT_FILENAME}' in `data/` directory!
|
| 409 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Font saved successfully. State set to 'idle'.")
|
| 410 |
except Exception as e:
|
| 411 |
await event.reply(f"Sorry, I couldn't save the font. Error: {e}")
|
| 412 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Error saving font: {e}", exc_info=True)
|
| 413 |
user_sessions[chat_id]['state'] = 'idle'
|
| 414 |
else:
|
| 415 |
-
await event.reply("That doesn't look like a .ttf font file
|
| 416 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Received document is not a TTF: {filename}")
|
| 417 |
return
|
| 418 |
|
| 419 |
-
# Handle image uploads for template or raw image
|
| 420 |
if event.photo:
|
| 421 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Photo received in state '{current_state}'.")
|
| 422 |
if current_state == 'awaiting_template_image':
|
|
@@ -425,14 +454,13 @@ async def message_handler(event): # pylint: disable=too-many-branches, too-many-
|
|
| 425 |
try:
|
| 426 |
await bot.download_media(event.message.photo, TEMPLATE_PATH)
|
| 427 |
user_sessions[chat_id]['state'] = 'idle'
|
| 428 |
-
await event.reply(f"Template saved as '{TEMPLATE_FILENAME}' in `data/` directory!
|
| 429 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Template saved. State set to 'idle'.")
|
| 430 |
except Exception as e:
|
| 431 |
await event.reply(f"Couldn't save template image. Error: {e}")
|
| 432 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Error saving template: {e}", exc_info=True)
|
| 433 |
user_sessions[chat_id]['state'] = 'idle'
|
| 434 |
return
|
| 435 |
-
|
| 436 |
elif current_state == 'awaiting_raw_image_for_create':
|
| 437 |
raw_img_filename = f"raw_{chat_id}_{event.message.id}_{SESSION_VERSION}.jpg"
|
| 438 |
raw_img_temp_path = os.path.join(DOWNLOADS_DIR, raw_img_filename)
|
|
@@ -442,7 +470,7 @@ async def message_handler(event): # pylint: disable=too-many-branches, too-many-
|
|
| 442 |
await bot.download_media(event.message.photo, raw_img_temp_path)
|
| 443 |
session_data['raw_image_path'] = raw_img_temp_path
|
| 444 |
user_sessions[chat_id]['state'] = 'awaiting_caption_for_create'
|
| 445 |
-
await event.reply("Raw image received. Now, please send the caption text
|
| 446 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Raw image saved. State set to 'awaiting_caption_for_create'.")
|
| 447 |
except Exception as e:
|
| 448 |
await event.reply(f"Couldn't save raw image. Error: {e}")
|
|
@@ -450,29 +478,22 @@ async def message_handler(event): # pylint: disable=too-many-branches, too-many-
|
|
| 450 |
user_sessions[chat_id]['state'] = 'idle'
|
| 451 |
return
|
| 452 |
|
| 453 |
-
# Handle text input for caption
|
| 454 |
if event.text and not event.text.startswith('/') and current_state == 'awaiting_caption_for_create':
|
| 455 |
caption_text = event.text
|
| 456 |
raw_image_path = session_data.get('raw_image_path')
|
| 457 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Caption text received: '{caption_text[:30]}...'. Raw image path: {raw_image_path}")
|
| 458 |
-
|
| 459 |
if not raw_image_path or not os.path.exists(raw_image_path):
|
| 460 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Raw image path not found or file missing: {raw_image_path}. State reset.")
|
| 461 |
-
await event.reply("Error: Raw image data not found.
|
| 462 |
user_sessions[chat_id]['state'] = 'idle'
|
| 463 |
return
|
| 464 |
-
|
| 465 |
processing_msg = await event.reply("⏳ Processing your image, please wait...")
|
| 466 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Starting image processing with caption.")
|
| 467 |
final_image_path = await generate_image_with_frame(raw_image_path, TEMPLATE_PATH, caption_text, chat_id)
|
| 468 |
-
|
| 469 |
-
try: # Try to delete "Processing..." message
|
| 470 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Attempting to delete 'Processing...' message.")
|
| 471 |
await bot.delete_messages(chat_id, processing_msg)
|
| 472 |
-
except Exception as e:
|
| 473 |
-
logger.warning(f"DEBUG: [ChatID: {chat_id}] Could not delete 'Processing...' message: {e}")
|
| 474 |
-
|
| 475 |
-
|
| 476 |
if final_image_path and os.path.exists(final_image_path):
|
| 477 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Final image created: {final_image_path}. Sending to user.")
|
| 478 |
try:
|
|
@@ -486,49 +507,47 @@ async def message_handler(event): # pylint: disable=too-many-branches, too-many-
|
|
| 486 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Deleting final image from server: {final_image_path}")
|
| 487 |
os.remove(final_image_path)
|
| 488 |
else:
|
| 489 |
-
await event.reply("❌ Sorry, something went wrong while creating the image.
|
| 490 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Image generation failed or final_image_path is invalid.")
|
| 491 |
-
|
| 492 |
-
# Cleanup raw image and reset state
|
| 493 |
if os.path.exists(raw_image_path):
|
| 494 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Deleting temporary raw image: {raw_image_path}")
|
| 495 |
os.remove(raw_image_path)
|
| 496 |
user_sessions[chat_id]['state'] = 'idle'
|
| 497 |
-
user_sessions[chat_id]['data'] = {}
|
| 498 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Post creation process complete. State set to 'idle'.")
|
| 499 |
return
|
| 500 |
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
|
| 506 |
|
| 507 |
# --- Main Bot Execution ---
|
| 508 |
async def main():
|
| 509 |
logger.info("===== Bot main() function starting =====")
|
| 510 |
-
load_bot_config()
|
| 511 |
|
| 512 |
-
# --- Network Test (Optional but recommended for debugging connection hangs) ---
|
| 513 |
logger.info("DEBUG: Performing a simple network test to google.com...")
|
| 514 |
try:
|
| 515 |
-
import socket
|
| 516 |
-
host_to_test = "google.com"
|
| 517 |
-
port_to_test = 80 # HTTP port
|
| 518 |
socket.create_connection((host_to_test, port_to_test), timeout=10)
|
| 519 |
logger.info(f"DEBUG: Network test to {host_to_test}:{port_to_test} was SUCCESSFUL.")
|
| 520 |
except OSError as e:
|
| 521 |
-
logger.warning(f"DEBUG: Network test to {host_to_test}:{port_to_test} FAILED - OSError: {e}. This might indicate broader network issues
|
| 522 |
except Exception as ex:
|
| 523 |
-
logger.warning(f"DEBUG: Network test to {host_to_test}:{port_to_test} FAILED with other exception: {ex}.
|
| 524 |
logger.info("DEBUG: Network test completed.")
|
| 525 |
-
# --- End Network Test ---
|
| 526 |
|
| 527 |
try:
|
| 528 |
logger.info(f"DEBUG: Attempting to connect to Telegram with bot token...")
|
| 529 |
logger.info(f"DEBUG: Using API_ID: {str(API_ID)[:4]}... (masked), Session: {SESSION_NAME}")
|
| 530 |
|
| 531 |
-
# The actual connection happens here
|
| 532 |
await bot.start(bot_token=BOT_TOKEN)
|
| 533 |
logger.info("SUCCESS: Bot connected to Telegram successfully!")
|
| 534 |
|
|
@@ -538,7 +557,7 @@ async def main():
|
|
| 538 |
logger.info("Bot is now running and listening for messages...")
|
| 539 |
await bot.run_until_disconnected()
|
| 540 |
|
| 541 |
-
except OSError as e:
|
| 542 |
logger.critical(f"CRITICAL ERROR: OSError during bot.start() or connection phase: {e}. This often indicates network connectivity problems from the container to Telegram servers.", exc_info=True)
|
| 543 |
except Exception as e:
|
| 544 |
logger.critical(f"CRITICAL ERROR: An unhandled exception occurred while starting or running the bot: {e}", exc_info=True)
|
|
@@ -557,7 +576,7 @@ async def main():
|
|
| 557 |
|
| 558 |
if __name__ == '__main__':
|
| 559 |
logger.info("DEBUG: Script is being run directly (__name__ == '__main__').")
|
| 560 |
-
print("DEBUG: Starting asyncio event loop...")
|
| 561 |
try:
|
| 562 |
asyncio.run(main())
|
| 563 |
except KeyboardInterrupt:
|
|
|
|
| 1 |
+
print("DEBUG: app.py script execution starting...")
|
| 2 |
|
| 3 |
import os
|
| 4 |
import asyncio
|
| 5 |
import json
|
| 6 |
import logging
|
| 7 |
+
import sys # For stdout logging and sys.exit
|
| 8 |
|
| 9 |
# --- Detailed Logging Setup ---
|
| 10 |
+
print("DEBUG: Setting up logging...")
|
| 11 |
+
logging.basicConfig(
|
| 12 |
+
level=logging.DEBUG, # Capture all DEBUG, INFO, WARNING, ERROR, CRITICAL
|
| 13 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s',
|
| 14 |
+
stream=sys.stdout, # Ensure logs go to standard output for Hugging Face
|
| 15 |
+
force=True # In case logging was already configured by another lib (unlikely here)
|
| 16 |
+
)
|
| 17 |
+
logger = logging.getLogger(__name__) # Get a logger for this module
|
| 18 |
+
logging.getLogger('telethon').setLevel(logging.DEBUG) # Max verbosity for Telethon
|
| 19 |
+
print("DEBUG: Logging setup complete.")
|
| 20 |
|
| 21 |
logger.debug("DEBUG: Importing Telethon...")
|
| 22 |
from telethon import TelegramClient, events # type: ignore
|
|
|
|
| 25 |
logger.info(f"DEBUG: Using Telethon version: {telethon.__version__}") # Log the version
|
| 26 |
logger.debug("DEBUG: Telethon imported.")
|
| 27 |
|
| 28 |
+
logger.debug("DEBUG: Importing Pillow...")
|
| 29 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 30 |
+
logger.debug("DEBUG: Pillow imported.")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# --- Configuration Loading and Environment Variable Check ---
|
| 34 |
+
logger.info("DEBUG: Loading environment variables...")
|
| 35 |
+
try:
|
| 36 |
+
logger.debug("DEBUG: Attempting to import dotenv...")
|
| 37 |
+
from dotenv import load_dotenv
|
| 38 |
+
logger.debug("DEBUG: dotenv imported. Attempting to load .env file...")
|
| 39 |
+
if load_dotenv():
|
| 40 |
+
logger.info("DEBUG: .env file loaded successfully.")
|
| 41 |
+
else:
|
| 42 |
+
logger.info("DEBUG: No .env file found or it's empty, relying on pre-set environment variables.")
|
| 43 |
+
except ImportError:
|
| 44 |
+
logger.info("DEBUG: python-dotenv not installed, relying on pre-set environment variables.")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
logger.error(f"DEBUG: Error loading .env using dotenv: {e}")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
API_ID = os.environ.get('API_ID')
|
| 50 |
+
API_HASH = os.environ.get('API_HASH')
|
| 51 |
+
BOT_TOKEN = os.environ.get('BOT_TOKEN')
|
| 52 |
+
|
| 53 |
+
logger.info(f"DEBUG: API_ID loaded: {'Set' if API_ID else 'NOT SET'}")
|
| 54 |
+
logger.info(f"DEBUG: API_HASH loaded: {'Set' if API_HASH else 'NOT SET'}")
|
| 55 |
+
logger.info(f"DEBUG: BOT_TOKEN loaded: {'Set' if BOT_TOKEN else 'NOT SET'}")
|
| 56 |
+
|
| 57 |
+
if not all([API_ID, API_HASH, BOT_TOKEN]):
|
| 58 |
+
logger.critical("CRITICAL ERROR: API_ID, API_HASH, and BOT_TOKEN environment variables must be set in Hugging Face Secrets.")
|
| 59 |
+
logger.critical("Bot cannot start. Please set these secrets in your Space settings.")
|
| 60 |
+
sys.exit(1) # Exit script if critical env vars are missing
|
| 61 |
+
|
| 62 |
+
try:
|
| 63 |
+
API_ID = int(API_ID)
|
| 64 |
+
logger.info(f"DEBUG: API_ID successfully converted to int: {str(API_ID)[:4]}... (masked for log)")
|
| 65 |
+
except ValueError:
|
| 66 |
+
logger.critical(f"CRITICAL ERROR: API_ID ('{os.environ.get('API_ID')}') must be an integer.")
|
| 67 |
+
sys.exit(1)
|
| 68 |
|
| 69 |
# --- Session Name (CHANGE THIS OFTEN FOR DEBUGGING CONNECTION HANGS) ---
|
| 70 |
+
SESSION_VERSION = "v4_fresh_20250513_0130" # <<<<<<< CHANGE THIS TO A NEW UNIQUE STRING
|
| 71 |
SESSION_NAME = f'session/image_bot_session_{SESSION_VERSION}'
|
| 72 |
logger.info(f"DEBUG: Using session name: {SESSION_NAME}")
|
| 73 |
|
|
|
|
| 79 |
logger.critical(f"CRITICAL ERROR: Failed to initialize TelegramClient: {e}", exc_info=True)
|
| 80 |
sys.exit(1)
|
| 81 |
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
# --- Paths and Directory Setup ---
|
| 84 |
logger.info("DEBUG: Defining paths and ensuring directories...")
|
| 85 |
DATA_DIR = "data"
|
| 86 |
+
DOWNLOADS_DIR = "downloads"
|
| 87 |
+
SESSION_DIR = "session"
|
| 88 |
|
| 89 |
CONFIG_FILENAME = "config.json"
|
| 90 |
TEMPLATE_FILENAME = "user_template.png"
|
|
|
|
| 94 |
TEMPLATE_PATH = os.path.join(DATA_DIR, TEMPLATE_FILENAME)
|
| 95 |
USER_FONT_PATH = os.path.join(DATA_DIR, USER_FONT_FILENAME)
|
| 96 |
|
|
|
|
| 97 |
for dir_path in [DATA_DIR, DOWNLOADS_DIR, SESSION_DIR]:
|
| 98 |
logger.debug(f"DEBUG: Ensuring directory exists: {dir_path}")
|
| 99 |
try:
|
| 100 |
os.makedirs(dir_path, exist_ok=True)
|
| 101 |
logger.info(f"DEBUG: Directory '{dir_path}' ensured (created if didn't exist).")
|
|
|
|
| 102 |
if not os.access(dir_path, os.W_OK):
|
| 103 |
+
logger.warning(f"DEBUG: Directory '{dir_path}' might not be writable despite creation (check Dockerfile chmod and persistent storage mount options)!")
|
| 104 |
except Exception as e:
|
| 105 |
logger.error(f"DEBUG: Error ensuring directory '{dir_path}': {e}", exc_info=True)
|
| 106 |
logger.info("DEBUG: Paths defined and directories ensured.")
|
|
|
|
| 115 |
"line_spacing": 10
|
| 116 |
}
|
| 117 |
bot_config = {}
|
| 118 |
+
logger.debug(f"DEBUG: Default config defined: {DEFAULT_CONFIG}")
|
| 119 |
|
| 120 |
# --- Config Management ---
|
| 121 |
def load_bot_config():
|
|
|
|
| 126 |
with open(CONFIG_PATH, 'r') as f:
|
| 127 |
loaded_values = json.load(f)
|
| 128 |
logger.info(f"DEBUG: Successfully read from {CONFIG_PATH}.")
|
| 129 |
+
bot_config = DEFAULT_CONFIG.copy()
|
| 130 |
+
bot_config.update(loaded_values)
|
| 131 |
logger.info("DEBUG: Merged loaded config with defaults.")
|
| 132 |
else:
|
| 133 |
logger.info(f"DEBUG: Config file {CONFIG_PATH} not found. Using defaults.")
|
| 134 |
bot_config = DEFAULT_CONFIG.copy()
|
| 135 |
+
save_bot_config(initial_load=True) # Ensure file exists with all keys
|
|
|
|
| 136 |
logger.info(f"DEBUG: Final configuration after load/init: {bot_config}")
|
| 137 |
except json.JSONDecodeError as e:
|
| 138 |
logger.error(f"DEBUG: Error decoding JSON from {CONFIG_PATH}. Using defaults and overwriting. Error: {e}", exc_info=True)
|
| 139 |
bot_config = DEFAULT_CONFIG.copy()
|
| 140 |
+
save_bot_config(initial_load=True)
|
| 141 |
except Exception as e:
|
| 142 |
logger.error(f"DEBUG: Critical error loading config, using defaults: {e}", exc_info=True)
|
| 143 |
bot_config = DEFAULT_CONFIG.copy()
|
| 144 |
+
if not os.path.exists(CONFIG_PATH):
|
|
|
|
| 145 |
save_bot_config(initial_load=True)
|
| 146 |
|
| 147 |
|
| 148 |
def save_bot_config(initial_load=False):
|
| 149 |
global bot_config
|
| 150 |
+
if not initial_load:
|
| 151 |
logger.info(f"DEBUG: Attempting to save bot configuration to {CONFIG_PATH}...")
|
| 152 |
try:
|
| 153 |
with open(CONFIG_PATH, 'w') as f:
|
| 154 |
json.dump(bot_config, f, indent=4)
|
| 155 |
+
# Avoid logging full config on every save during initial_load to reduce noise
|
| 156 |
if not initial_load:
|
| 157 |
+
logger.info(f"DEBUG: Configuration saved successfully.")
|
| 158 |
except Exception as e:
|
| 159 |
logger.error(f"DEBUG: Error saving config to {CONFIG_PATH}: {e}", exc_info=True)
|
| 160 |
|
|
|
|
| 165 |
# --- Helper Functions (Image Processing) ---
|
| 166 |
async def generate_image_with_frame(raw_image_path, frame_template_path, caption_text, chat_id):
|
| 167 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Starting image generation. Raw: '{raw_image_path}', Template: '{frame_template_path}', Caption: '{caption_text[:30]}...'")
|
| 168 |
+
output_filename = f"final_image_{chat_id}_{SESSION_VERSION}.png"
|
| 169 |
output_path = os.path.join(DOWNLOADS_DIR, output_filename)
|
| 170 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Output path will be: {output_path}")
|
| 171 |
|
|
|
|
| 172 |
cfg_raw_box = bot_config.get("raw_image_box", DEFAULT_CONFIG["raw_image_box"])
|
| 173 |
cfg_caption_area = bot_config.get("caption_area", DEFAULT_CONFIG["caption_area"])
|
| 174 |
cfg_font_size = bot_config.get("caption_font_size", DEFAULT_CONFIG["caption_font_size"])
|
|
|
|
| 204 |
if not font_to_use:
|
| 205 |
try:
|
| 206 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Attempting to load 'arial.ttf' as fallback.")
|
| 207 |
+
font_to_use = ImageFont.truetype("arial.ttf", cfg_font_size) # Common system font
|
| 208 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Fallback font 'arial.ttf' loaded.")
|
| 209 |
except IOError:
|
| 210 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Arial.ttf not found, using Pillow's load_default(). This may look very basic.")
|
| 211 |
+
font_to_use = ImageFont.load_default().font_variant(size=cfg_font_size)
|
| 212 |
+
|
| 213 |
|
| 214 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Drawing caption text...")
|
| 215 |
lines = caption_text.split('\n')
|
| 216 |
current_y = cfg_caption_area['y']
|
| 217 |
for i, line in enumerate(lines):
|
| 218 |
+
try:
|
| 219 |
line_bbox = draw.textbbox((0, 0), line, font=font_to_use)
|
| 220 |
line_height = line_bbox[3] - line_bbox[1]
|
| 221 |
+
except AttributeError:
|
| 222 |
(text_width, text_height) = draw.textsize(line, font=font_to_use)
|
| 223 |
line_height = text_height
|
| 224 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Drawing line {i+1}/{len(lines)}: '{line[:20]}...' at y={current_y}, height={line_height}")
|
|
|
|
| 226 |
current_y += line_height + cfg_line_spacing
|
| 227 |
|
| 228 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Saving final image to {output_path}")
|
| 229 |
+
final_img.convert("RGB").save(output_path, "PNG")
|
| 230 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Image generation complete: {output_path}")
|
| 231 |
return output_path
|
| 232 |
|
|
|
|
| 241 |
@bot.on(events.NewMessage(pattern='/start'))
|
| 242 |
async def start_handler(event):
|
| 243 |
chat_id = event.chat_id
|
| 244 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /start command.")
|
| 245 |
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
| 246 |
start_message = (
|
| 247 |
"Welcome! This bot helps you create images with a template.\n\n"
|
|
|
|
| 254 |
"`/create` - Start creating a new image.\n"
|
| 255 |
"`/viewconfig` - See current layout settings.\n"
|
| 256 |
"`/cancel` - Cancel current operation.\n\n"
|
| 257 |
+
f"**DEBUG INFO:** Session Suffix: `{SESSION_VERSION}`\n"
|
| 258 |
"**Note on Hosting:** The `data/` and `session/` directories should ideally use persistent storage on your hosting platform. "
|
| 259 |
"If using ephemeral storage (common on free tiers), your uploaded template, font, config, and session might be lost on restarts, requiring setup again."
|
| 260 |
)
|
| 261 |
await event.reply(start_message)
|
| 262 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Sent welcome message for /start.")
|
| 263 |
|
|
|
|
|
|
|
| 264 |
|
| 265 |
@bot.on(events.NewMessage(pattern='/settemplate'))
|
| 266 |
async def set_template_handler(event):
|
| 267 |
chat_id = event.chat_id
|
| 268 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /settemplate command.")
|
| 269 |
user_sessions[chat_id] = {'state': 'awaiting_template_image', 'data': {}}
|
| 270 |
await event.reply("Please send your frame template image (e.g., a PNG or JPG).")
|
| 271 |
|
| 272 |
@bot.on(events.NewMessage(pattern='/setfont'))
|
| 273 |
async def set_font_handler(event):
|
| 274 |
chat_id = event.chat_id
|
| 275 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /setfont command.")
|
| 276 |
user_sessions[chat_id] = {'state': 'awaiting_font_file', 'data': {}}
|
| 277 |
await event.reply("Please send your `.ttf` font file as a document/file.")
|
| 278 |
|
| 279 |
@bot.on(events.NewMessage(pattern='/viewconfig'))
|
| 280 |
async def view_config_handler(event):
|
| 281 |
chat_id = event.chat_id
|
| 282 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /viewconfig command.")
|
| 283 |
load_bot_config() # Ensure config is fresh
|
| 284 |
config_str = "Current Bot Configuration:\n"
|
| 285 |
config_str += f"- Font: {'User font set (' + USER_FONT_FILENAME + ')' if os.path.exists(USER_FONT_PATH) else 'Using fallback font (Arial or Pillow default)'}\n"
|
|
|
|
| 290 |
@bot.on(events.NewMessage(pattern='/help_config'))
|
| 291 |
async def help_config_handler(event):
|
| 292 |
chat_id = event.chat_id
|
| 293 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /help_config command.")
|
| 294 |
await event.reply(
|
| 295 |
"**Configuration Commands (`/setconfig <key> <value>`):**\n"
|
| 296 |
"`raw_image_box_x <num>` (e.g., 40)\n"
|
|
|
|
| 312 |
chat_id = event.chat_id
|
| 313 |
key_to_set = event.pattern_match.group(1).strip()
|
| 314 |
value_str = event.pattern_match.group(2).strip()
|
| 315 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /setconfig command. Key: '{key_to_set}', Value: '{value_str}'")
|
| 316 |
|
| 317 |
load_bot_config()
|
| 318 |
+
original_config_copy = json.dumps(bot_config)
|
| 319 |
updated = False
|
| 320 |
+
sub_key = None # Initialize to avoid UnboundLocalError if key doesn't match known patterns
|
| 321 |
|
| 322 |
try:
|
| 323 |
if key_to_set.startswith("raw_image_box_"):
|
|
|
|
| 334 |
bot_config[key_to_set] = int(value_str)
|
| 335 |
updated = True
|
| 336 |
elif key_to_set == "caption_color":
|
| 337 |
+
bot_config[key_to_set] = value_str
|
| 338 |
updated = True
|
| 339 |
|
| 340 |
if updated:
|
| 341 |
if json.dumps(bot_config) != original_config_copy:
|
| 342 |
save_bot_config()
|
|
|
|
| 343 |
reply_value = value_str
|
| 344 |
+
if sub_key:
|
| 345 |
if key_to_set.startswith("raw_image_box_"):
|
| 346 |
reply_value = bot_config.get("raw_image_box", {}).get(sub_key, value_str)
|
| 347 |
elif key_to_set.startswith("caption_area_"):
|
|
|
|
| 357 |
await event.reply(f"Unknown or non-configurable key: '{key_to_set}'. See `/help_config`.")
|
| 358 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Unknown config key attempted: '{key_to_set}'.")
|
| 359 |
except ValueError:
|
| 360 |
+
await event.reply(f"Invalid value for '{key_to_set}'. Please provide a number where expected.")
|
| 361 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Invalid value for config key '{key_to_set}': '{value_str}'.")
|
| 362 |
except Exception as e:
|
| 363 |
await event.reply(f"Error setting config: {e}")
|
|
|
|
| 367 |
@bot.on(events.NewMessage(pattern='/create'))
|
| 368 |
async def create_post_handler(event):
|
| 369 |
chat_id = event.chat_id
|
| 370 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /create command.")
|
| 371 |
if not os.path.exists(TEMPLATE_PATH):
|
| 372 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] /create failed: Template not found at {TEMPLATE_PATH}.")
|
| 373 |
await event.reply("No template found. Please set one using `/settemplate` first.")
|
|
|
|
| 384 |
@bot.on(events.NewMessage(pattern='/cancel'))
|
| 385 |
async def cancel_handler(event):
|
| 386 |
chat_id = event.chat_id
|
| 387 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Received /cancel command.")
|
| 388 |
if chat_id in user_sessions and user_sessions[chat_id]['state'] != 'idle':
|
| 389 |
temp_raw_path = user_sessions[chat_id].get('data', {}).get('raw_image_path')
|
| 390 |
if temp_raw_path and os.path.exists(temp_raw_path):
|
|
|
|
| 398 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Operation cancelled, state reset to 'idle'.")
|
| 399 |
else:
|
| 400 |
await event.reply("Nothing to cancel.")
|
| 401 |
+
logger.info(f"DEBUG: [ChatID: {chat_id}] /cancel received but nothing to cancel.")
|
| 402 |
|
| 403 |
@bot.on(events.NewMessage)
|
| 404 |
async def message_handler(event): # pylint: disable=too-many-branches, too-many-statements
|
| 405 |
chat_id = event.chat_id
|
| 406 |
+
if chat_id not in user_sessions:
|
| 407 |
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
|
|
|
| 408 |
|
| 409 |
+
if event.text and event.text.startswith(('/', '#', '.')):
|
| 410 |
+
logger.debug(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Message ('{event.text[:20]}...') looks like a command, letting patterned handlers manage it.")
|
|
|
|
|
|
|
| 411 |
return
|
| 412 |
|
|
|
|
| 413 |
current_state = user_sessions.get(chat_id, {}).get('state', 'idle')
|
| 414 |
session_data = user_sessions.get(chat_id, {}).get('data', {})
|
| 415 |
+
logger.debug(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Message received. Current state: '{current_state}'. Message text (first 30 chars): '{str(event.raw_text)[:30]}...' Is photo: {event.photo is not None}. Is document: {event.document is not None}.")
|
| 416 |
|
|
|
|
|
|
|
| 417 |
if event.document and current_state == 'awaiting_font_file':
|
| 418 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Document received in 'awaiting_font_file' state.")
|
| 419 |
doc_attrs = event.document.attributes
|
|
|
|
| 423 |
filename = attr.file_name
|
| 424 |
break
|
| 425 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Document filename: '{filename}', MIME type: '{event.document.mime_type}'")
|
|
|
|
| 426 |
is_ttf = False
|
| 427 |
if filename.lower().endswith('.ttf'):
|
| 428 |
is_ttf = True
|
| 429 |
elif hasattr(event.document, 'mime_type') and \
|
| 430 |
('font' in event.document.mime_type or 'ttf' in event.document.mime_type or 'opentype' in event.document.mime_type):
|
| 431 |
is_ttf = True
|
|
|
|
| 432 |
if is_ttf:
|
| 433 |
await event.reply("Downloading font file...")
|
| 434 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Downloading TTF font: {filename}")
|
| 435 |
try:
|
| 436 |
await bot.download_media(event.message.document, USER_FONT_PATH)
|
| 437 |
user_sessions[chat_id]['state'] = 'idle'
|
| 438 |
+
await event.reply(f"Font saved as '{USER_FONT_FILENAME}' in `data/` directory!")
|
| 439 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Font saved successfully. State set to 'idle'.")
|
| 440 |
except Exception as e:
|
| 441 |
await event.reply(f"Sorry, I couldn't save the font. Error: {e}")
|
| 442 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Error saving font: {e}", exc_info=True)
|
| 443 |
user_sessions[chat_id]['state'] = 'idle'
|
| 444 |
else:
|
| 445 |
+
await event.reply("That doesn't look like a .ttf font file. Please send a valid .ttf font file, or use /cancel.")
|
| 446 |
logger.warning(f"DEBUG: [ChatID: {chat_id}] Received document is not a TTF: {filename}")
|
| 447 |
return
|
| 448 |
|
|
|
|
| 449 |
if event.photo:
|
| 450 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Photo received in state '{current_state}'.")
|
| 451 |
if current_state == 'awaiting_template_image':
|
|
|
|
| 454 |
try:
|
| 455 |
await bot.download_media(event.message.photo, TEMPLATE_PATH)
|
| 456 |
user_sessions[chat_id]['state'] = 'idle'
|
| 457 |
+
await event.reply(f"Template saved as '{TEMPLATE_FILENAME}' in `data/` directory!")
|
| 458 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Template saved. State set to 'idle'.")
|
| 459 |
except Exception as e:
|
| 460 |
await event.reply(f"Couldn't save template image. Error: {e}")
|
| 461 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Error saving template: {e}", exc_info=True)
|
| 462 |
user_sessions[chat_id]['state'] = 'idle'
|
| 463 |
return
|
|
|
|
| 464 |
elif current_state == 'awaiting_raw_image_for_create':
|
| 465 |
raw_img_filename = f"raw_{chat_id}_{event.message.id}_{SESSION_VERSION}.jpg"
|
| 466 |
raw_img_temp_path = os.path.join(DOWNLOADS_DIR, raw_img_filename)
|
|
|
|
| 470 |
await bot.download_media(event.message.photo, raw_img_temp_path)
|
| 471 |
session_data['raw_image_path'] = raw_img_temp_path
|
| 472 |
user_sessions[chat_id]['state'] = 'awaiting_caption_for_create'
|
| 473 |
+
await event.reply("Raw image received. Now, please send the caption text.")
|
| 474 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Raw image saved. State set to 'awaiting_caption_for_create'.")
|
| 475 |
except Exception as e:
|
| 476 |
await event.reply(f"Couldn't save raw image. Error: {e}")
|
|
|
|
| 478 |
user_sessions[chat_id]['state'] = 'idle'
|
| 479 |
return
|
| 480 |
|
|
|
|
| 481 |
if event.text and not event.text.startswith('/') and current_state == 'awaiting_caption_for_create':
|
| 482 |
caption_text = event.text
|
| 483 |
raw_image_path = session_data.get('raw_image_path')
|
| 484 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Caption text received: '{caption_text[:30]}...'. Raw image path: {raw_image_path}")
|
|
|
|
| 485 |
if not raw_image_path or not os.path.exists(raw_image_path):
|
| 486 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Raw image path not found or file missing: {raw_image_path}. State reset.")
|
| 487 |
+
await event.reply("Error: Raw image data not found. Try `/create` again.")
|
| 488 |
user_sessions[chat_id]['state'] = 'idle'
|
| 489 |
return
|
|
|
|
| 490 |
processing_msg = await event.reply("⏳ Processing your image, please wait...")
|
| 491 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Starting image processing with caption.")
|
| 492 |
final_image_path = await generate_image_with_frame(raw_image_path, TEMPLATE_PATH, caption_text, chat_id)
|
| 493 |
+
try:
|
|
|
|
| 494 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Attempting to delete 'Processing...' message.")
|
| 495 |
await bot.delete_messages(chat_id, processing_msg)
|
| 496 |
+
except Exception as e: logger.warning(f"DEBUG: [ChatID: {chat_id}] Could not delete 'Processing...' message: {e}")
|
|
|
|
|
|
|
|
|
|
| 497 |
if final_image_path and os.path.exists(final_image_path):
|
| 498 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Final image created: {final_image_path}. Sending to user.")
|
| 499 |
try:
|
|
|
|
| 507 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Deleting final image from server: {final_image_path}")
|
| 508 |
os.remove(final_image_path)
|
| 509 |
else:
|
| 510 |
+
await event.reply("❌ Sorry, something went wrong while creating the image.")
|
| 511 |
logger.error(f"DEBUG: [ChatID: {chat_id}] Image generation failed or final_image_path is invalid.")
|
|
|
|
|
|
|
| 512 |
if os.path.exists(raw_image_path):
|
| 513 |
logger.debug(f"DEBUG: [ChatID: {chat_id}] Deleting temporary raw image: {raw_image_path}")
|
| 514 |
os.remove(raw_image_path)
|
| 515 |
user_sessions[chat_id]['state'] = 'idle'
|
| 516 |
+
user_sessions[chat_id]['data'] = {}
|
| 517 |
logger.info(f"DEBUG: [ChatID: {chat_id}] Post creation process complete. State set to 'idle'.")
|
| 518 |
return
|
| 519 |
|
| 520 |
+
if current_state != 'idle':
|
| 521 |
+
logger.debug(f"DEBUG: [ChatID: {chat_id}, User: {event.sender_id}] Unhandled message in state '{current_state}'. Text: '{str(event.raw_text)[:30]}...'")
|
| 522 |
+
# Avoid replying to every single message if it's not a command and not in a specific waiting state
|
| 523 |
+
# This can be noisy. Only reply if a specific input was expected.
|
| 524 |
+
# if current_state not in ['awaiting_template_image', 'awaiting_font_file', 'awaiting_raw_image_for_create', 'awaiting_caption_for_create']:
|
| 525 |
+
# pass # Do nothing for other states if non-command text
|
| 526 |
+
# else: # If in a specific waiting state, then the message was unexpected.
|
| 527 |
+
# await event.reply(f"I'm currently waiting for: `{current_state}`. If you're stuck, try `/cancel`.")
|
| 528 |
|
| 529 |
|
| 530 |
# --- Main Bot Execution ---
|
| 531 |
async def main():
|
| 532 |
logger.info("===== Bot main() function starting =====")
|
| 533 |
+
load_bot_config()
|
| 534 |
|
|
|
|
| 535 |
logger.info("DEBUG: Performing a simple network test to google.com...")
|
| 536 |
try:
|
| 537 |
+
import socket
|
| 538 |
+
host_to_test = "google.com"; port_to_test = 80
|
|
|
|
| 539 |
socket.create_connection((host_to_test, port_to_test), timeout=10)
|
| 540 |
logger.info(f"DEBUG: Network test to {host_to_test}:{port_to_test} was SUCCESSFUL.")
|
| 541 |
except OSError as e:
|
| 542 |
+
logger.warning(f"DEBUG: Network test to {host_to_test}:{port_to_test} FAILED - OSError: {e}. This might indicate broader network issues.")
|
| 543 |
except Exception as ex:
|
| 544 |
+
logger.warning(f"DEBUG: Network test to {host_to_test}:{port_to_test} FAILED with other exception: {ex}.")
|
| 545 |
logger.info("DEBUG: Network test completed.")
|
|
|
|
| 546 |
|
| 547 |
try:
|
| 548 |
logger.info(f"DEBUG: Attempting to connect to Telegram with bot token...")
|
| 549 |
logger.info(f"DEBUG: Using API_ID: {str(API_ID)[:4]}... (masked), Session: {SESSION_NAME}")
|
| 550 |
|
|
|
|
| 551 |
await bot.start(bot_token=BOT_TOKEN)
|
| 552 |
logger.info("SUCCESS: Bot connected to Telegram successfully!")
|
| 553 |
|
|
|
|
| 557 |
logger.info("Bot is now running and listening for messages...")
|
| 558 |
await bot.run_until_disconnected()
|
| 559 |
|
| 560 |
+
except OSError as e:
|
| 561 |
logger.critical(f"CRITICAL ERROR: OSError during bot.start() or connection phase: {e}. This often indicates network connectivity problems from the container to Telegram servers.", exc_info=True)
|
| 562 |
except Exception as e:
|
| 563 |
logger.critical(f"CRITICAL ERROR: An unhandled exception occurred while starting or running the bot: {e}", exc_info=True)
|
|
|
|
| 576 |
|
| 577 |
if __name__ == '__main__':
|
| 578 |
logger.info("DEBUG: Script is being run directly (__name__ == '__main__').")
|
| 579 |
+
print("DEBUG: Starting asyncio event loop...")
|
| 580 |
try:
|
| 581 |
asyncio.run(main())
|
| 582 |
except KeyboardInterrupt:
|