Данный бот добавляет текст на изображения из локальной папки на машине и отправляет пользователю, так же есть отстук в телеграмм админу о совершенных действиях других юзеров. import logging import os from telegram import Update, InputFile, ReplyKeyboardMarkup, ReplyKeyboardRemove from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes, filters from PIL import Image, ImageDraw, ImageFont # Настройка логирования logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO, ) # Директории IMAGE_DIR = "путь до папки с изображениями на которые будет добавляться текст" OUTPUT_DIR = "путь до папки с выходными изображениями" FONT_DIR = "путь к папке с используемыми шрифтами" os.makedirs(IMAGE_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True) os.makedirs(FONT_DIR, exist_ok=True) # Конфигурация изображений IMAGE_CONFIG = { "черно-белый логотип.png": { "rotation_angle": 0, "text1": {"x": 92, "y": 287, "font_size": 27, "color": "black", "font": "Myriad Pro Bold.ttf"}, "text2": {"x": 92, "y": 303, "font_size": 45, "color": "black", "font": "Myriad Pro Bold.ttf"}, }, "розовый логотип.png": { "rotation_angle": 0, "text1": {"x": 92, "y": 287, "font_size": 27, "color": "black", "font": "Myriad Pro Bold.ttf"}, "text2": {"x": 92, "y": 303, "font_size": 45, "color": "black", "font": "Myriad Pro Bold.ttf"}, }, "цветной поворотный.png": { "rotation_angle": 90, "text1": {"x": 540, "y": 440, "font_size": 41, "color": "black", "font": "Arial Narrow Bold.ttf"}, "text2": {"x": 700, "y": 430, "font_size": 50, "color": "black", "font": "Arial Narrow Bold.ttf"}, }, } SUPPORTED_FORMATS = (".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff") # Хранение состояния пользователя USER_STATE = {} # Список администраторов ADMINS = [999999] # Замените на реальные ID администраторов # Функция отправки уведомлений администраторам async def notify_admins(context: ContextTypes.DEFAULT_TYPE, message: str) -> None: for admin_id in ADMINS: try: await context.bot.send_message(chat_id=admin_id, text=message) except Exception as e: logging.error(f"Не удалось отправить сообщение админу {admin_id}: {e}") # Команда /start async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: USER_STATE.pop(update.message.from_user.id, None) # Сброс состояния пользователя keyboard = [["Выбрать изображение"]] reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True) await update.message.reply_text( "Привет! Я бот для добавления текста на изображения. Нажмите 'Выбрать изображение', чтобы начать.", reply_markup=reply_markup, ) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) начал сессию.") # Обработка выбора изображения async def choose_image(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id files = [f for f in os.listdir(IMAGE_DIR) if f.lower().endswith(SUPPORTED_FORMATS)] if not files: await update.message.reply_text("Нет доступных изображений в поддерживаемых форматах.", reply_markup=ReplyKeyboardRemove()) return USER_STATE[user_id] = {"step": "choose_file"} keyboard = [[file] for file in files] reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=True) await update.message.reply_text("Выберите изображение из списка:", reply_markup=reply_markup) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) выбирает изображение.") # Обработка выбора файла async def handle_file_selection(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id state = USER_STATE.get(user_id, {}) if state.get("step") != "choose_file": return file_name = update.message.text if not os.path.exists(os.path.join(IMAGE_DIR, file_name)): await update.message.reply_text("Файл не найден. Попробуйте снова.") return USER_STATE[user_id]["file_name"] = file_name USER_STATE[user_id]["step"] = "text1" await update.message.reply_text("Введите первый текст:", reply_markup=ReplyKeyboardRemove()) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) выбрал файл: {file_name}") # Обработка ввода текста async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id state = USER_STATE.get(user_id, {}) if not state: return if state.get("step") == "text1": USER_STATE[user_id]["text1"] = update.message.text USER_STATE[user_id]["step"] = "text2" await update.message.reply_text("Введите второй текст:") # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) ввел первый текст: {update.message.text}") elif state.get("step") == "text2": USER_STATE[user_id]["text2"] = update.message.text await process_image(update, context) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) ввел второй текст: {update.message.text}") # Обработка изображения async def process_image(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id state = USER_STATE.get(user_id, {}) file_name = state.get("file_name") text1 = state.get("text1") text2 = state.get("text2") if not file_name or not text1 or not text2: await update.message.reply_text("Ошибка состояния. Попробуйте начать сначала с команды /start.") return image_path = os.path.join(IMAGE_DIR, file_name) config = IMAGE_CONFIG.get(file_name) if not config: await update.message.reply_text("Конфигурация для изображения не найдена.") return try: # Открываем изображение image = Image.open(image_path) # Рисуем текст 1 font1 = ImageFont.truetype(os.path.join(FONT_DIR, config["text1"]["font"]), config["text1"]["font_size"]) draw = ImageDraw.Draw(image) draw.text((config["text1"]["x"], config["text1"]["y"]), text1, fill=config["text1"]["color"], font=font1) # Рисуем текст 2 font2 = ImageFont.truetype(os.path.join(FONT_DIR, config["text2"]["font"]), config["text2"]["font_size"]) draw.text((config["text2"]["x"], config["text2"]["y"]), text2, fill=config["text2"]["color"], font=font2) # Поворот готового изображения rotation_angle = config.get("rotation_angle", 0) if rotation_angle != 0: image = image.rotate(rotation_angle, expand=True) # Сохранение результата output_path = os.path.join(OUTPUT_DIR, f"processed_{file_name}") image.save(output_path) # Отправляем результат пользователю with open(output_path, "rb") as file: await update.message.reply_photo(photo=InputFile(file), caption="Изображение успешно обработано!") # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) обработал изображение: {file_name}") except Exception as e: logging.error(f"Ошибка: {e}") await update.message.reply_text("Произошла ошибка при обработке изображения.") finally: USER_STATE.pop(user_id, None) await update.message.reply_text("Процесс завершен. Нажмите /start, чтобы начать заново.") # Главная функция запуска бота def main(): token = "" # Вставьте свой токен app = ApplicationBuilder().token(token).build() app.add_handler(CommandHandler("start", start)) app.add_handler(MessageHandler(filters.Regex("^(Выбрать изображение)$"), choose_image)) app.add_handler(MessageHandler(filters.Regex(r"^.+\.(jpg|jpeg|png|bmp|gif|tiff)$"), handle_file_selection)) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) print("Бот запущен. Нажмите Ctrl+C для остановки.") app.run_polling() if __name__ == "__main__": main() Python import logging import os from telegram import Update, InputFile, ReplyKeyboardMarkup, ReplyKeyboardRemove from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes, filters from PIL import Image, ImageDraw, ImageFont # Настройка логирования logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO, ) # Директории IMAGE_DIR = "путь до папки с изображениями на которые будет добавляться текст" OUTPUT_DIR = "путь до папки с выходными изображениями" FONT_DIR = "путь к папке с используемыми шрифтами" os.makedirs(IMAGE_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True) os.makedirs(FONT_DIR, exist_ok=True) # Конфигурация изображений IMAGE_CONFIG = { "черно-белый логотип.png": { "rotation_angle": 0, "text1": {"x": 92, "y": 287, "font_size": 27, "color": "black", "font": "Myriad Pro Bold.ttf"}, "text2": {"x": 92, "y": 303, "font_size": 45, "color": "black", "font": "Myriad Pro Bold.ttf"}, }, "розовый логотип.png": { "rotation_angle": 0, "text1": {"x": 92, "y": 287, "font_size": 27, "color": "black", "font": "Myriad Pro Bold.ttf"}, "text2": {"x": 92, "y": 303, "font_size": 45, "color": "black", "font": "Myriad Pro Bold.ttf"}, }, "цветной поворотный.png": { "rotation_angle": 90, "text1": {"x": 540, "y": 440, "font_size": 41, "color": "black", "font": "Arial Narrow Bold.ttf"}, "text2": {"x": 700, "y": 430, "font_size": 50, "color": "black", "font": "Arial Narrow Bold.ttf"}, }, } SUPPORTED_FORMATS = (".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff") # Хранение состояния пользователя USER_STATE = {} # Список администраторов ADMINS = [999999] # Замените на реальные ID администраторов # Функция отправки уведомлений администраторам async def notify_admins(context: ContextTypes.DEFAULT_TYPE, message: str) -> None: for admin_id in ADMINS: try: await context.bot.send_message(chat_id=admin_id, text=message) except Exception as e: logging.error(f"Не удалось отправить сообщение админу {admin_id}: {e}") # Команда /start async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: USER_STATE.pop(update.message.from_user.id, None) # Сброс состояния пользователя keyboard = [["Выбрать изображение"]] reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True) await update.message.reply_text( "Привет! Я бот для добавления текста на изображения. Нажмите 'Выбрать изображение', чтобы начать.", reply_markup=reply_markup, ) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) начал сессию.") # Обработка выбора изображения async def choose_image(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id files = [f for f in os.listdir(IMAGE_DIR) if f.lower().endswith(SUPPORTED_FORMATS)] if not files: await update.message.reply_text("Нет доступных изображений в поддерживаемых форматах.", reply_markup=ReplyKeyboardRemove()) return USER_STATE[user_id] = {"step": "choose_file"} keyboard = [[file] for file in files] reply_markup = ReplyKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=True) await update.message.reply_text("Выберите изображение из списка:", reply_markup=reply_markup) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) выбирает изображение.") # Обработка выбора файла async def handle_file_selection(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id state = USER_STATE.get(user_id, {}) if state.get("step") != "choose_file": return file_name = update.message.text if not os.path.exists(os.path.join(IMAGE_DIR, file_name)): await update.message.reply_text("Файл не найден. Попробуйте снова.") return USER_STATE[user_id]["file_name"] = file_name USER_STATE[user_id]["step"] = "text1" await update.message.reply_text("Введите первый текст:", reply_markup=ReplyKeyboardRemove()) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) выбрал файл: {file_name}") # Обработка ввода текста async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id state = USER_STATE.get(user_id, {}) if not state: return if state.get("step") == "text1": USER_STATE[user_id]["text1"] = update.message.text USER_STATE[user_id]["step"] = "text2" await update.message.reply_text("Введите второй текст:") # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) ввел первый текст: {update.message.text}") elif state.get("step") == "text2": USER_STATE[user_id]["text2"] = update.message.text await process_image(update, context) # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) ввел второй текст: {update.message.text}") # Обработка изображения async def process_image(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: user_id = update.message.from_user.id state = USER_STATE.get(user_id, {}) file_name = state.get("file_name") text1 = state.get("text1") text2 = state.get("text2") if not file_name or not text1 or not text2: await update.message.reply_text("Ошибка состояния. Попробуйте начать сначала с команды /start.") return image_path = os.path.join(IMAGE_DIR, file_name) config = IMAGE_CONFIG.get(file_name) if not config: await update.message.reply_text("Конфигурация для изображения не найдена.") return try: # Открываем изображение image = Image.open(image_path) # Рисуем текст 1 font1 = ImageFont.truetype(os.path.join(FONT_DIR, config["text1"]["font"]), config["text1"]["font_size"]) draw = ImageDraw.Draw(image) draw.text((config["text1"]["x"], config["text1"]["y"]), text1, fill=config["text1"]["color"], font=font1) # Рисуем текст 2 font2 = ImageFont.truetype(os.path.join(FONT_DIR, config["text2"]["font"]), config["text2"]["font_size"]) draw.text((config["text2"]["x"], config["text2"]["y"]), text2, fill=config["text2"]["color"], font=font2) # Поворот готового изображения rotation_angle = config.get("rotation_angle", 0) if rotation_angle != 0: image = image.rotate(rotation_angle, expand=True) # Сохранение результата output_path = os.path.join(OUTPUT_DIR, f"processed_{file_name}") image.save(output_path) # Отправляем результат пользователю with open(output_path, "rb") as file: await update.message.reply_photo(photo=InputFile(file), caption="Изображение успешно обработано!") # Уведомление админам await notify_admins(context, f"Пользователь {update.message.from_user.username} ({update.message.from_user.id}) обработал изображение: {file_name}") except Exception as e: logging.error(f"Ошибка: {e}") await update.message.reply_text("Произошла ошибка при обработке изображения.") finally: USER_STATE.pop(user_id, None) await update.message.reply_text("Процесс завершен. Нажмите /start, чтобы начать заново.") # Главная функция запуска бота def main(): token = "" # Вставьте свой токен app = ApplicationBuilder().token(token).build() app.add_handler(CommandHandler("start", start)) app.add_handler(MessageHandler(filters.Regex("^(Выбрать изображение)$"), choose_image)) app.add_handler(MessageHandler(filters.Regex(r"^.+\.(jpg|jpeg|png|bmp|gif|tiff)$"), handle_file_selection)) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) print("Бот запущен. Нажмите Ctrl+C для остановки.") app.run_polling() if __name__ == "__main__": main() Для правильной работы: 1.замените параметр 193 строчки token = "" на токен своего бота 2.замените параметр ADMINS = [999999] на реальные для правильной работы отстука о действиях юзеров 3. отредактируйте пути к файлам IMAGE_DIR = "путь до папки с изображениями на которые будет добавляться текст" OUTPUT_DIR = "путь до папки с выходными изображениями" FONT_DIR = "путь к папке с используемыми шрифтами" 4.в части IMAGE_CONFIG замените имена файлов на те, что будут использоваться Используемые библиотеки: pip install python-telegram-bot==20.3 pip install Pillow==10.0.0 Описания параметров нанесения текста (IMAGE_CONFIG) "rotation_angle": 0, - угол поворота изображения ПОСЛЕ нанесения текста "x": 540 - координата текста на изображении по оси Х "y": 540 - координата текста на изображении по оси Y "font_size": 41 - размер текста "color": "black" - цвет текста "font": "Myriad Pro Bold.ttf" - шрифт текста, берущийся из папки которую мы указывали выше