Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import asyncio
|
3 |
+
import json
|
4 |
+
from telethon import TelegramClient, events
|
5 |
+
from telethon.tl import types # For DocumentAttributeFilename
|
6 |
+
from PIL import Image, ImageDraw, ImageFont
|
7 |
+
|
8 |
+
# --- Configuration ---
|
9 |
+
try:
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
load_dotenv()
|
12 |
+
except ImportError:
|
13 |
+
pass
|
14 |
+
|
15 |
+
API_ID = os.environ.get('API_ID')
|
16 |
+
API_HASH = os.environ.get('API_HASH')
|
17 |
+
BOT_TOKEN = os.environ.get('BOT_TOKEN')
|
18 |
+
|
19 |
+
if not all([API_ID, API_HASH, BOT_TOKEN]):
|
20 |
+
print("CRITICAL: API_ID, API_HASH, and BOT_TOKEN environment variables must be set.")
|
21 |
+
exit(1)
|
22 |
+
try:
|
23 |
+
API_ID = int(API_ID)
|
24 |
+
except ValueError:
|
25 |
+
print("CRITICAL: API_ID must be an integer.")
|
26 |
+
exit(1)
|
27 |
+
|
28 |
+
# Adjusted session path
|
29 |
+
SESSION_NAME = 'session/image_bot_session' # Store session file in 'session' subdirectory
|
30 |
+
bot = TelegramClient(SESSION_NAME, API_ID, API_HASH)
|
31 |
+
|
32 |
+
|
33 |
+
# --- Paths and Constants ---
|
34 |
+
DATA_DIR = "data"
|
35 |
+
DOWNLOADS_DIR = "downloads" # Changed from TEMP_DIR
|
36 |
+
CONFIG_FILENAME = "config.json"
|
37 |
+
TEMPLATE_FILENAME = "user_template.png"
|
38 |
+
USER_FONT_FILENAME = "user_font.ttf"
|
39 |
+
|
40 |
+
CONFIG_PATH = os.path.join(DATA_DIR, CONFIG_FILENAME)
|
41 |
+
TEMPLATE_PATH = os.path.join(DATA_DIR, TEMPLATE_FILENAME)
|
42 |
+
USER_FONT_PATH = os.path.join(DATA_DIR, USER_FONT_FILENAME)
|
43 |
+
|
44 |
+
# Ensure base directories exist (Dockerfile should create them, but this is a good fallback)
|
45 |
+
# The Dockerfile now creates these, but os.makedirs with exist_ok=True is safe.
|
46 |
+
os.makedirs(DATA_DIR, exist_ok=True)
|
47 |
+
os.makedirs(DOWNLOADS_DIR, exist_ok=True)
|
48 |
+
os.makedirs("session", exist_ok=True) # Ensure the 'session' directory for Telethon exists
|
49 |
+
|
50 |
+
# --- Default Configuration (if config.json is missing/invalid) ---
|
51 |
+
DEFAULT_CONFIG = {
|
52 |
+
"raw_image_box": {"x": 40, "y": 40, "width": 1000, "height": 780},
|
53 |
+
"caption_area": {"x": 60, "y": 870},
|
54 |
+
"caption_font_size": 45,
|
55 |
+
"caption_color": "white",
|
56 |
+
"line_spacing": 10
|
57 |
+
}
|
58 |
+
bot_config = {}
|
59 |
+
|
60 |
+
# --- Config Management ---
|
61 |
+
def load_bot_config():
|
62 |
+
global bot_config
|
63 |
+
try:
|
64 |
+
if os.path.exists(CONFIG_PATH):
|
65 |
+
with open(CONFIG_PATH, 'r') as f:
|
66 |
+
loaded_values = json.load(f)
|
67 |
+
bot_config = DEFAULT_CONFIG.copy()
|
68 |
+
bot_config.update(loaded_values)
|
69 |
+
else:
|
70 |
+
bot_config = DEFAULT_CONFIG.copy()
|
71 |
+
save_bot_config() # Ensure file exists with all keys
|
72 |
+
print(f"Configuration loaded/initialized: {bot_config}")
|
73 |
+
except Exception as e:
|
74 |
+
print(f"Error loading config, using defaults: {e}")
|
75 |
+
bot_config = DEFAULT_CONFIG.copy()
|
76 |
+
save_bot_config()
|
77 |
+
|
78 |
+
def save_bot_config():
|
79 |
+
global bot_config
|
80 |
+
try:
|
81 |
+
with open(CONFIG_PATH, 'w') as f:
|
82 |
+
json.dump(bot_config, f, indent=4)
|
83 |
+
except Exception as e:
|
84 |
+
print(f"Error saving config: {e}")
|
85 |
+
|
86 |
+
# --- User State Management ---
|
87 |
+
user_sessions = {}
|
88 |
+
|
89 |
+
# --- Helper Functions (Image Processing) ---
|
90 |
+
async def generate_image_with_frame(raw_image_path, frame_template_path, caption_text, chat_id):
|
91 |
+
output_filename = f"final_image_{chat_id}.png"
|
92 |
+
# Save final output to DOWNLOADS_DIR before sending, then clean up
|
93 |
+
output_path = os.path.join(DOWNLOADS_DIR, output_filename)
|
94 |
+
|
95 |
+
|
96 |
+
cfg_raw_box = bot_config.get("raw_image_box", DEFAULT_CONFIG["raw_image_box"])
|
97 |
+
cfg_caption_area = bot_config.get("caption_area", DEFAULT_CONFIG["caption_area"])
|
98 |
+
cfg_font_size = bot_config.get("caption_font_size", DEFAULT_CONFIG["caption_font_size"])
|
99 |
+
cfg_caption_color = bot_config.get("caption_color", DEFAULT_CONFIG["caption_color"])
|
100 |
+
cfg_line_spacing = bot_config.get("line_spacing", DEFAULT_CONFIG["line_spacing"])
|
101 |
+
|
102 |
+
try:
|
103 |
+
raw_img = Image.open(raw_image_path).convert("RGBA")
|
104 |
+
frame_template_img = Image.open(frame_template_path).convert("RGBA")
|
105 |
+
|
106 |
+
raw_img_copy = raw_img.copy()
|
107 |
+
raw_img_copy.thumbnail((cfg_raw_box['width'], cfg_raw_box['height']), Image.LANCZOS)
|
108 |
+
|
109 |
+
final_img = frame_template_img.copy()
|
110 |
+
paste_x = cfg_raw_box['x'] + (cfg_raw_box['width'] - raw_img_copy.width) // 2
|
111 |
+
paste_y = cfg_raw_box['y'] + (cfg_raw_box['height'] - raw_img_copy.height) // 2
|
112 |
+
final_img.paste(raw_img_copy, (paste_x, paste_y), raw_img_copy if raw_img_copy.mode == 'RGBA' else None)
|
113 |
+
|
114 |
+
draw = ImageDraw.Draw(final_img)
|
115 |
+
font_to_use = None
|
116 |
+
if os.path.exists(USER_FONT_PATH):
|
117 |
+
try:
|
118 |
+
font_to_use = ImageFont.truetype(USER_FONT_PATH, cfg_font_size)
|
119 |
+
except IOError:
|
120 |
+
print(f"User font at '{USER_FONT_PATH}' found but couldn't be loaded. Using fallback.")
|
121 |
+
if not font_to_use:
|
122 |
+
try:
|
123 |
+
font_to_use = ImageFont.truetype("arial.ttf", cfg_font_size)
|
124 |
+
except IOError:
|
125 |
+
print("Arial.ttf not found, using Pillow's load_default(). This may look very basic.")
|
126 |
+
font_to_use = ImageFont.load_default().font_variant(size=cfg_font_size)
|
127 |
+
|
128 |
+
lines = caption_text.split('\n')
|
129 |
+
current_y = cfg_caption_area['y']
|
130 |
+
for line in lines:
|
131 |
+
try:
|
132 |
+
line_bbox = draw.textbbox((0, 0), line, font=font_to_use)
|
133 |
+
line_height = line_bbox[3] - line_bbox[1]
|
134 |
+
except AttributeError:
|
135 |
+
(text_width, text_height) = draw.textsize(line, font=font_to_use)
|
136 |
+
line_height = text_height
|
137 |
+
|
138 |
+
draw.text((cfg_caption_area['x'], current_y), line, font=font_to_use, fill=cfg_caption_color)
|
139 |
+
current_y += line_height + cfg_line_spacing
|
140 |
+
|
141 |
+
final_img.convert("RGB").save(output_path, "PNG")
|
142 |
+
return output_path
|
143 |
+
|
144 |
+
except FileNotFoundError as fnf_err:
|
145 |
+
print(f"Error: Image file not found during processing. Template: {frame_template_path}, Raw: {raw_image_path}. Error: {fnf_err}")
|
146 |
+
return None
|
147 |
+
except Exception as e:
|
148 |
+
print(f"Error during image processing: {e}")
|
149 |
+
import traceback
|
150 |
+
traceback.print_exc()
|
151 |
+
return None
|
152 |
+
|
153 |
+
# --- Bot Event Handlers --- (Mostly same as before, checking paths for DOWNLOADS_DIR)
|
154 |
+
@bot.on(events.NewMessage(pattern='/start'))
|
155 |
+
async def start_handler(event):
|
156 |
+
chat_id = event.chat_id
|
157 |
+
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
158 |
+
start_message = (
|
159 |
+
"Welcome! This bot helps you create images with a template.\n\n"
|
160 |
+
"**Initial Setup (if first time or after a reset):**\n"
|
161 |
+
"1. `/settemplate` - Upload your frame template image (PNG/JPG).\n"
|
162 |
+
"2. `/setfont` - Upload your .ttf font file for captions.\n"
|
163 |
+
"3. `/help_config` - Learn how to set layout parameters.\n"
|
164 |
+
"4. `/setconfig <key> <value>` - Adjust layout as needed.\n\n"
|
165 |
+
"**Regular Use:**\n"
|
166 |
+
"`/create` - Start creating a new image.\n"
|
167 |
+
"`/viewconfig` - See current layout settings.\n"
|
168 |
+
"`/cancel` - Cancel current operation.\n\n"
|
169 |
+
"**Note on Hosting:** The `data/` and `session/` directories should ideally use persistent storage on your hosting platform. "
|
170 |
+
"If using ephemeral storage (common on free tiers), your uploaded template, font, config, and session might be lost on restarts, requiring setup again."
|
171 |
+
)
|
172 |
+
await event.reply(start_message)
|
173 |
+
|
174 |
+
@bot.on(events.NewMessage(pattern='/settemplate'))
|
175 |
+
async def set_template_handler(event):
|
176 |
+
chat_id = event.chat_id
|
177 |
+
user_sessions[chat_id] = {'state': 'awaiting_template_image', 'data': {}}
|
178 |
+
await event.reply("Please send your frame template image (e.g., a PNG or JPG).")
|
179 |
+
|
180 |
+
@bot.on(events.NewMessage(pattern='/setfont'))
|
181 |
+
async def set_font_handler(event):
|
182 |
+
chat_id = event.chat_id
|
183 |
+
user_sessions[chat_id] = {'state': 'awaiting_font_file', 'data': {}}
|
184 |
+
await event.reply("Please send your `.ttf` font file as a document/file.")
|
185 |
+
|
186 |
+
@bot.on(events.NewMessage(pattern='/viewconfig'))
|
187 |
+
async def view_config_handler(event):
|
188 |
+
load_bot_config()
|
189 |
+
config_str = "Current Bot Configuration:\n"
|
190 |
+
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"
|
191 |
+
for key, value in bot_config.items():
|
192 |
+
config_str += f"- {key}: {json.dumps(value)}\n"
|
193 |
+
await event.reply(config_str)
|
194 |
+
|
195 |
+
@bot.on(events.NewMessage(pattern='/help_config'))
|
196 |
+
async def help_config_handler(event):
|
197 |
+
await event.reply(
|
198 |
+
"**Configuration Commands (`/setconfig <key> <value>`):**\n"
|
199 |
+
"`raw_image_box_x <num>` (e.g., 40)\n"
|
200 |
+
"`raw_image_box_y <num>`\n"
|
201 |
+
"`raw_image_box_width <num>`\n"
|
202 |
+
"`raw_image_box_height <num>`\n"
|
203 |
+
"`caption_area_x <num>`\n"
|
204 |
+
"`caption_area_y <num>`\n"
|
205 |
+
"`caption_font_size <num>` (e.g., 45)\n"
|
206 |
+
"`caption_color <name_or_hex>` (e.g., white or #FFFFFF)\n"
|
207 |
+
"`line_spacing <num>` (e.g., 10)\n\n"
|
208 |
+
"Example: `/setconfig caption_font_size 50`\n"
|
209 |
+
"Use `/viewconfig` to see current values. "
|
210 |
+
"These settings define how the raw image and caption are placed on your template."
|
211 |
+
)
|
212 |
+
|
213 |
+
@bot.on(events.NewMessage(pattern=r'/setconfig (\w+) (.+)'))
|
214 |
+
async def set_config_handler(event):
|
215 |
+
key_to_set = event.pattern_match.group(1).strip()
|
216 |
+
value_str = event.pattern_match.group(2).strip()
|
217 |
+
|
218 |
+
load_bot_config()
|
219 |
+
original_config_copy = json.dumps(bot_config)
|
220 |
+
updated = False
|
221 |
+
sub_key = None # Initialize sub_key
|
222 |
+
|
223 |
+
try:
|
224 |
+
if key_to_set.startswith("raw_image_box_"):
|
225 |
+
sub_key = key_to_set.replace("raw_image_box_", "")
|
226 |
+
if "raw_image_box" in bot_config and sub_key in bot_config["raw_image_box"]:
|
227 |
+
bot_config["raw_image_box"][sub_key] = int(value_str)
|
228 |
+
updated = True
|
229 |
+
elif key_to_set.startswith("caption_area_"):
|
230 |
+
sub_key = key_to_set.replace("caption_area_", "")
|
231 |
+
if "caption_area" in bot_config and sub_key in bot_config["caption_area"]:
|
232 |
+
bot_config["caption_area"][sub_key] = int(value_str)
|
233 |
+
updated = True
|
234 |
+
elif key_to_set in ["caption_font_size", "line_spacing"]:
|
235 |
+
bot_config[key_to_set] = int(value_str)
|
236 |
+
updated = True
|
237 |
+
elif key_to_set == "caption_color":
|
238 |
+
bot_config[key_to_set] = value_str
|
239 |
+
updated = True
|
240 |
+
|
241 |
+
if updated:
|
242 |
+
if json.dumps(bot_config) != original_config_copy:
|
243 |
+
save_bot_config()
|
244 |
+
# Construct reply value more carefully
|
245 |
+
reply_value = value_str
|
246 |
+
if sub_key: # if it was a nested key
|
247 |
+
if key_to_set.startswith("raw_image_box_"):
|
248 |
+
reply_value = bot_config.get("raw_image_box", {}).get(sub_key, value_str)
|
249 |
+
elif key_to_set.startswith("caption_area_"):
|
250 |
+
reply_value = bot_config.get("caption_area", {}).get(sub_key, value_str)
|
251 |
+
else:
|
252 |
+
reply_value = bot_config.get(key_to_set, value_str)
|
253 |
+
await event.reply(f"Configuration updated: {key_to_set} = {reply_value}")
|
254 |
+
else:
|
255 |
+
await event.reply(f"Value for {key_to_set} is already {value_str}. No change made.")
|
256 |
+
else:
|
257 |
+
await event.reply(f"Unknown or non-configurable key: '{key_to_set}'. See `/help_config`.")
|
258 |
+
except ValueError:
|
259 |
+
await event.reply(f"Invalid value for '{key_to_set}'. Please provide a number where expected (e.g., for sizes, coordinates).")
|
260 |
+
except Exception as e:
|
261 |
+
await event.reply(f"Error setting config: {e}")
|
262 |
+
|
263 |
+
@bot.on(events.NewMessage(pattern='/create'))
|
264 |
+
async def create_post_handler(event):
|
265 |
+
chat_id = event.chat_id
|
266 |
+
if not os.path.exists(TEMPLATE_PATH):
|
267 |
+
await event.reply("No template found. Please set one using `/settemplate` first.")
|
268 |
+
return
|
269 |
+
if not os.path.exists(USER_FONT_PATH):
|
270 |
+
await event.reply("Warning: No user font found (use `/setfont`). A fallback font will be attempted, but results may vary.")
|
271 |
+
|
272 |
+
user_sessions[chat_id] = {'state': 'awaiting_raw_image_for_create', 'data': {}}
|
273 |
+
await event.reply("Please send the raw image you want to use.")
|
274 |
+
|
275 |
+
@bot.on(events.NewMessage(pattern='/cancel'))
|
276 |
+
async def cancel_handler(event):
|
277 |
+
chat_id = event.chat_id
|
278 |
+
if chat_id in user_sessions and user_sessions[chat_id]['state'] != 'idle':
|
279 |
+
temp_raw_path = user_sessions[chat_id].get('data', {}).get('raw_image_path')
|
280 |
+
if temp_raw_path and os.path.exists(temp_raw_path):
|
281 |
+
try: os.remove(temp_raw_path)
|
282 |
+
except OSError as e: print(f"Error deleting temp file {temp_raw_path}: {e}")
|
283 |
+
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
284 |
+
await event.reply("Operation cancelled.")
|
285 |
+
else:
|
286 |
+
await event.reply("Nothing to cancel.")
|
287 |
+
|
288 |
+
@bot.on(events.NewMessage)
|
289 |
+
async def message_handler(event): # pylint: disable=too-many-branches
|
290 |
+
chat_id = event.chat_id
|
291 |
+
if chat_id not in user_sessions:
|
292 |
+
user_sessions[chat_id] = {'state': 'idle', 'data': {}}
|
293 |
+
|
294 |
+
if event.text and event.text.startswith(('/', '#', '.')):
|
295 |
+
return # Let specific command handlers take precedence
|
296 |
+
|
297 |
+
current_state = user_sessions.get(chat_id, {}).get('state', 'idle')
|
298 |
+
session_data = user_sessions.get(chat_id, {}).get('data', {})
|
299 |
+
|
300 |
+
if event.document and current_state == 'awaiting_font_file':
|
301 |
+
is_ttf = False
|
302 |
+
if hasattr(event.document, 'mime_type') and \
|
303 |
+
('font' in event.document.mime_type or 'ttf' in event.document.mime_type or 'opentype' in event.document.mime_type):
|
304 |
+
is_ttf = True
|
305 |
+
if not is_ttf and hasattr(event.document, 'attributes'):
|
306 |
+
for attr in event.document.attributes:
|
307 |
+
if isinstance(attr, types.DocumentAttributeFilename) and attr.file_name.lower().endswith('.ttf'):
|
308 |
+
is_ttf = True
|
309 |
+
break
|
310 |
+
if is_ttf:
|
311 |
+
await event.reply("Downloading font file...")
|
312 |
+
try:
|
313 |
+
await bot.download_media(event.message.document, USER_FONT_PATH)
|
314 |
+
user_sessions[chat_id]['state'] = 'idle'
|
315 |
+
await event.reply(f"Font saved as '{USER_FONT_FILENAME}' in `data/` directory!")
|
316 |
+
except Exception as e:
|
317 |
+
await event.reply(f"Sorry, I couldn't save the font. Error: {e}")
|
318 |
+
user_sessions[chat_id]['state'] = 'idle'
|
319 |
+
else:
|
320 |
+
await event.reply("That doesn't look like a .ttf font file. Please send a valid .ttf font file, or use /cancel.")
|
321 |
+
return
|
322 |
+
|
323 |
+
if event.photo:
|
324 |
+
if current_state == 'awaiting_template_image':
|
325 |
+
await event.reply("Downloading template image...")
|
326 |
+
try:
|
327 |
+
await bot.download_media(event.message.photo, TEMPLATE_PATH)
|
328 |
+
user_sessions[chat_id]['state'] = 'idle'
|
329 |
+
await event.reply(f"Template saved as '{TEMPLATE_FILENAME}' in `data/` directory!")
|
330 |
+
except Exception as e:
|
331 |
+
await event.reply(f"Couldn't save template image. Error: {e}")
|
332 |
+
user_sessions[chat_id]['state'] = 'idle'
|
333 |
+
return
|
334 |
+
elif current_state == 'awaiting_raw_image_for_create':
|
335 |
+
raw_img_filename = f"raw_{chat_id}_{event.message.id}.jpg"
|
336 |
+
# Save raw images to DOWNLOADS_DIR
|
337 |
+
raw_img_temp_path = os.path.join(DOWNLOADS_DIR, raw_img_filename)
|
338 |
+
await event.reply("Downloading raw image...")
|
339 |
+
try:
|
340 |
+
await bot.download_media(event.message.photo, raw_img_temp_path)
|
341 |
+
session_data['raw_image_path'] = raw_img_temp_path
|
342 |
+
user_sessions[chat_id]['state'] = 'awaiting_caption_for_create'
|
343 |
+
await event.reply("Raw image received. Now, please send the caption text.")
|
344 |
+
except Exception as e:
|
345 |
+
await event.reply(f"Couldn't save raw image. Error: {e}")
|
346 |
+
user_sessions[chat_id]['state'] = 'idle'
|
347 |
+
return
|
348 |
+
|
349 |
+
if event.text and not event.text.startswith('/') and current_state == 'awaiting_caption_for_create':
|
350 |
+
caption_text = event.text
|
351 |
+
raw_image_path = session_data.get('raw_image_path')
|
352 |
+
|
353 |
+
if not raw_image_path or not os.path.exists(raw_image_path):
|
354 |
+
await event.reply("Error: Raw image data not found. Try `/create` again.")
|
355 |
+
user_sessions[chat_id]['state'] = 'idle'
|
356 |
+
return
|
357 |
+
|
358 |
+
processing_msg = await event.reply("⏳ Processing your image, please wait...")
|
359 |
+
final_image_path = await generate_image_with_frame(raw_image_path, TEMPLATE_PATH, caption_text, chat_id)
|
360 |
+
|
361 |
+
try: await bot.delete_messages(chat_id, processing_msg)
|
362 |
+
except Exception: pass # noqa
|
363 |
+
|
364 |
+
if final_image_path and os.path.exists(final_image_path):
|
365 |
+
try:
|
366 |
+
await bot.send_file(chat_id, final_image_path, caption="🖼️ Here's your generated image!")
|
367 |
+
except Exception as e:
|
368 |
+
await event.reply(f"Sorry, couldn't send the final image. Error: {e}")
|
369 |
+
finally:
|
370 |
+
if os.path.exists(final_image_path): os.remove(final_image_path)
|
371 |
+
else:
|
372 |
+
await event.reply("❌ Sorry, something went wrong while creating the image.")
|
373 |
+
|
374 |
+
if os.path.exists(raw_image_path): os.remove(raw_image_path)
|
375 |
+
user_sessions[chat_id]['state'] = 'idle'
|
376 |
+
user_sessions[chat_id]['data'] = {}
|
377 |
+
return
|
378 |
+
|
379 |
+
if event.text and not event.text.startswith('/') and current_state != 'idle':
|
380 |
+
await event.reply(f"I'm currently waiting for: `{current_state}`. If you're stuck, try `/cancel`.")
|
381 |
+
|
382 |
+
# --- Main Bot Execution ---
|
383 |
+
async def main():
|
384 |
+
print("Bot starting...")
|
385 |
+
load_bot_config()
|
386 |
+
|
387 |
+
try:
|
388 |
+
print(f"Connecting to Telegram with API_ID: {str(API_ID)[:4]}... (masked)")
|
389 |
+
await bot.start(bot_token=BOT_TOKEN)
|
390 |
+
print("Bot is connected and running!")
|
391 |
+
me = await bot.get_me()
|
392 |
+
print(f"Logged in as: {me.username} (ID: {me.id})")
|
393 |
+
await bot.run_until_disconnected()
|
394 |
+
except Exception as e:
|
395 |
+
print(f"CRITICAL: An error occurred while running the bot: {e}")
|
396 |
+
import traceback
|
397 |
+
traceback.print_exc()
|
398 |
+
finally:
|
399 |
+
print("Bot is stopping...")
|
400 |
+
if bot.is_connected():
|
401 |
+
await bot.disconnect()
|
402 |
+
print("Bot has disconnected.")
|
403 |
+
|
404 |
+
if __name__ == '__main__':
|
405 |
+
asyncio.run(main())
|
406 |
+
|