understanding commited on
Commit
f0391dc
·
verified ·
1 Parent(s): 0e65d47

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -96
app.py CHANGED
@@ -1,15 +1,22 @@
1
- # app.py (Partial - showing key changes)
2
 
3
  import os
4
  import asyncio
5
  import json
6
  import logging
7
- import sys
8
 
9
  # --- Detailed Logging Setup ---
10
- # ... (keep the detailed logging setup as before) ...
11
- logger = logging.getLogger(__name__)
12
- logging.getLogger('telethon').setLevel(logging.DEBUG)
 
 
 
 
 
 
 
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
- # ... (rest of imports, env var loading) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  # --- Session Name (CHANGE THIS OFTEN FOR DEBUGGING CONNECTION HANGS) ---
24
- SESSION_VERSION = "v3_debug_run" # <<<<<<< CHANGE THIS TO A NEW VALUE
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" # Changed from TEMP_DIR to match Dockerfile
44
- SESSION_DIR = "session" # Directory for Telethon session specified in Dockerfile
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() # Start with defaults
89
- bot_config.update(loaded_values) # Override with 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
- # Always save after loading/initializing to ensure file exists with all keys and consistent format
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) # Attempt to save a clean default config
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
- # Avoid saving if a critical non-JSON error occurred during load, to prevent infinite loops if save also fails
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: # Only log intent if it's a user-triggered save
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: {bot_config}")
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" # Add session version for uniqueness
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) #Pillow 10+
 
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: # Pillow 9.2.0+ textbbox
179
  line_bbox = draw.textbbox((0, 0), line, font=font_to_use)
180
  line_height = line_bbox[3] - line_bbox[1]
181
- except AttributeError: # Fallback for older Pillow versions or basic font
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") # Consider JPEG for smaller size if transparency not needed
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 from user {event.sender_id}")
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) # For checking if value actually changed
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 # No specific validation, user responsible
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: # if it was a nested 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 (e.g., for sizes, coordinates).")
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 (state was 'idle' or no session).")
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: # Initialize session if it doesn't exist
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
- # Let specific command handlers (defined with patterns) take precedence
373
- # This generic handler processes messages based on user state.
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! It will be used for new images.")
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 based on its name or type. Please send a valid .ttf font file, or use /cancel.")
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! Remember to `/setfont` and use `/setconfig` if needed.")
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 for the image.")
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. Please try the `/create` command again.")
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. Please check if the template and font are set correctly, and if the raw image is valid.")
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'] = {} # Clear session data for this user
498
  logger.info(f"DEBUG: [ChatID: {chat_id}] Post creation process complete. State set to 'idle'.")
499
  return
500
 
501
- # If message wasn't handled by specific states above and is not a command
502
- if current_state != 'idle': # Only reply if in an active state and message wasn't processed
503
- logger.debug(f"DEBUG: [ChatID: {chat_id}] Unhandled message in state '{current_state}'. Text: '{str(event.raw_text)[:30]}...'")
504
- await event.reply(f"I'm currently waiting for: `{current_state}`. If you're stuck, try `/cancel`.")
 
 
 
 
505
 
506
 
507
  # --- Main Bot Execution ---
508
  async def main():
509
  logger.info("===== Bot main() function starting =====")
510
- load_bot_config() # Load or create initial config on startup
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 # Standard library, should be available
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 in the container.")
522
  except Exception as ex:
523
- logger.warning(f"DEBUG: Network test to {host_to_test}:{port_to_test} FAILED with other exception: {ex}. This might indicate broader network issues.")
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: # Specific catch for network related OS errors during start
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...") # Ensure this prints before loop starts
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: