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:
|