diff --git a/telegram/README_BOT.md b/telegram/README_BOT.md
new file mode 100644
index 0000000..d11eb48
--- /dev/null
+++ b/telegram/README_BOT.md
@@ -0,0 +1,162 @@
+# Telegram Bot для создания постов и планирования путешествий
+
+## Функции бота
+
+### 📝 Создание постов
+- Создание заголовка и основного текста поста
+- Загрузка фото в лучшем качестве или для быстрой загрузки в `public/images/`
+- Добавление видео
+- Интеграция YouTube видео через Hugo shortcodes
+- Локации с Яндекс.Карт
+- Автоматическое создание Hugo markdown файлов в `content/post/`
+- Генерация правильного front matter для Hugo
+
+### 🌍 Календарь поездок
+- Создание названия и описания поездки
+- Добавление фото к поездкам
+- Автоматическое обновление файла `content/plan.md`
+
+### 📅 Управление календарём поездок
+- Добавление месячных поездок (системные ежемесячные варианты)
+- Добавление специальных поездок (праздничные и особенные события)
+- Удаление существующих поездок из списка
+- Просмотр всех доступных вариантов поездок
+- Автоматическое обновление выпадающего списка в форме на сайте
+
+### 🔄 Git интеграция
+- Автоматический commit всех изменений после создания поста
+- Автоматический commit при обновлении календаря поездок
+- Автоматический push в репозиторий Gitea/GitHub
+- Осмысленные commit сообщения с указанием источника (Telegram бот)
+
+## Установка и запуск
+
+1. **Установите зависимости:**
+ ```bash
+ pip3 install -r requirements.txt
+ ```
+
+2. **Настройте токен бота:**
+ ```bash
+ export TELEGRAM_BOT_TOKEN="ваш_токен_здесь"
+ ```
+
+ Или создайте файл `.env`:
+ ```
+ TELEGRAM_BOT_TOKEN=ваш_токен_здесь
+ ```
+
+3. **Запустите бота:**
+ ```bash
+ ./start_bot.sh
+ ```
+
+ Или напрямую:
+ ```bash
+ python3 telegram_bot.py
+ ```
+
+## Использование
+
+1. Запустите бота командой `/start`
+2. Выберите действие из главного меню:
+ - **📝 Создать пост** - для создания новых постов
+ - **🌍 Хочу поехать** - для добавления записей в календарь поездок
+ - **📅 Управление календарём** - для управления вариантами поездок в форме
+
+### Создание поста
+1. Введите заголовок поста
+2. Введите основной текст поста
+3. Используйте кнопки для добавления:
+ - 📸 Фото (с выбором качества)
+ - 🎥 Видео
+ - 🔗 YouTube ссылки
+ - 📍 Локации с Яндекс.Карт
+4. Нажмите "✅ Опубликовать пост"
+5. Бот автоматически создаст Hugo markdown файл и сделает Git commit
+
+### Календарь поездок
+1. Введите название поездки
+2. Опишите детали поездки
+3. При желании добавьте фото
+4. Сохраните запись
+5. Бот автоматически обновит `plan.md` и сделает Git commit
+
+### Управление календарём поездок
+1. **Добавить месячную поездку:**
+ - Выберите "➕ Добавить месячную поездку"
+ - Введите название в формате "Полёты в [месяц] [год] года"
+ - Новая опция автоматически добавится в форму на сайте
+
+2. **Добавить специальную поездку:**
+ - Выберите "✨ Добавить специальную поездку"
+ - Введите название события (например: "Новогодние каникулы в горах")
+ - Опция будет добавлена в выпадающий список
+
+3. **Удалить поездку:**
+ - Выберите "❌ Удалить поездку"
+ - Выберите поездку из списка для удаления
+ - Опция будет удалена из формы на сайте
+
+4. **Просмотреть список:**
+ - Выберите "📋 Просмотреть список"
+ - Увидите все текущие варианты поездок
+
+## Интеграция с Hugo и Git
+
+### Структура файлов
+- `content/post/` - markdown файлы постов
+- `public/images/` - загруженные изображения и видео
+- `content/plan.md` - файл календаря поездок
+
+### Создаваемые файлы
+
+#### Hugo пост (content/post/название-поста-20250802.md):
+```markdown
++++
+title = 'Название поста'
+slug = 'название-поста-20250802'
+date = "2025-08-02T14:30:00"
+image = 'images/post_20250802_143000_1.jpg'
++++
+
+Основной текст поста
+
+## Фотографии
+
+{{< gallery dir="/images/" />}}
+
+## Видео
+
+{{< youtube dQw4w9WgXcQ >}}
+
+## Локации
+
+📍 [Посмотреть на карте](https://yandex.ru/maps/...)
+
+{{< rawhtml >}}
+{{< back-to-top >}}
+{{< /rawhtml >}}
+```
+
+### Git коммиты
+
+#### Пример коммита для поста:
+```
+Добавлен новый пост: Название поста
+
+🤖 Создано через Telegram бота
+```
+
+#### Пример коммита для календаря:
+```
+Обновлен календарь поездок: Поездка в горы
+
+🤖 Создано через Telegram бота
+```
+
+## Требования
+
+- Git репозиторий должен быть настроен с правами на push
+- Бот должен запускаться из корня Hugo проекта
+- Необходимые Python библиотеки: `python-telegram-bot`, `requests`
\ No newline at end of file
diff --git a/telegram/TODO.md b/telegram/TODO.md
new file mode 100644
index 0000000..208dd42
--- /dev/null
+++ b/telegram/TODO.md
@@ -0,0 +1,106 @@
+# TODO для Telegram бота
+
+## ✅ Выполненные задачи
+
+- [x] **Add location input for post creation**
+ - ✅ Добавлен запрос направления/города при создании поста
+ - ✅ Новый шаг между заголовком и текстом поста
+
+- [x] **Update image naming to match existing pattern (City-YYYYMMDD-N.jpg)**
+ - ✅ Изменено именование файлов с `post_timestamp_N.jpg` на `City-YYYYMMDD-N.jpg`
+ - ✅ Используется введенное направление и текущая дата
+
+- [x] **Update Hugo post generation to use correct image paths**
+ - ✅ Обновлена генерация Hugo постов для использования правильных путей к изображениям
+ - ✅ Учтена новая схема именования файлов
+
+- [x] **Create function to transliterate city names to Latin**
+ - ✅ Функция транслитерации с русского на латиницу
+ - ✅ Обработка популярных городов/направлений (30+ городов)
+ - ✅ Fallback для неизвестных названий
+
+- [x] **Update post workflow to include location step**
+ - ✅ Изменена последовательность создания поста:
+ 1. Заголовок
+ 2. **Направление/город**
+ 3. Основной текст
+ 4. Медиафайлы
+ 5. Публикация
+
+## 🔄 Обновленный workflow бота
+
+### 📝 Создание постов:
+1. **Заголовок поста** - пользователь вводит название
+2. **Описание для превью** - краткое описание (1-2 слова)
+3. **Направление/город** - пользователь указывает локацию (Москва, Алтай, и т.д.)
+4. **Основной текст** - описание поста
+5. **Медиафайлы** (опционально):
+ - Фото с автоматическим именованием `Город-YYYYMMDD-N.jpg`
+ - Видео с тем же принципом именования
+ - YouTube ссылки
+ - Локации Яндекс.Карт
+6. **Публикация** - создание Hugo файла и Git commit
+
+### 📅 Управление календарём поездок:
+1. **Добавить месячную поездку** - системные ежемесячные поездки (например: "Полёты в октябре 2025 года")
+2. **Добавить специальную поездку** - особенные события (например: "Новогодние каникулы в горах")
+3. **Удалить поездку** - удаление существующих опций из выпадающего списка
+4. **Просмотреть список** - показать все текущие варианты поездок
+5. **Автоматическое обновление** - изменения сразу отражаются в form на сайте
+
+## 📝 Обновления
+
+### Новое приветственное сообщение:
+```
+🤖 Добро пожаловать в бот предназначенный для загрузки контента на сайт "Пока ты спал"!
+
+Выберите действие:
+```
+
+### Добавлено поле description:
+- Отображается в Hugo front matter как `description = 'Поход'`
+- Используется для превью постов
+- Запрашивается после заголовка, до локации
+
+### Новая последовательность создания поста:
+1. Заголовок: "Поездка в горы Алтая"
+2. **Описание: "Поход"** ← НОВОЕ
+3. Локация: "Алтай" → Altai (с пояснением про организацию фото)
+4. Основной текст: "Невероятная поездка..."
+5. Медиафайлы и публикация
+
+## 📸 Обновления по фото
+
+### Упрощенная загрузка фото:
+- Убран выбор качества
+- Только загрузка через файл/документ для максимального качества
+- Понятные сообщения об ошибках при неправильной загрузке
+
+### Логика фото:
+- **Первое фото** = главное для превью (в front matter как `image`)
+- **Остальные фото** = дополнительные для галереи
+- Статус указывается при добавлении каждого фото
+
+### Текст при запросе направления:
+```
+Теперь введите направление/город поездки (на русском):
+Это нужно для организации фото по названиям файлов.
+Например: Москва, Питер, Алтай, Кавказ, Тула...
+```
+
+### Текст при загрузке фото:
+```
+📸 Отправьте фото как файл через Telegram:
+
+Для сохранения качества обязательно отправляйте фото как документ/файл!
+Первое фото будет главным (в превью), остальные добавятся в галерею.
+```
+
+## Текущая схема именования
+Существующие файлы: `Aleksin-20210515-1.jpg`, `Altai-20220912-1.jpg`, etc.
+Нужно: `Направление-YYYYMMDD-номер.jpg`
+
+## Примеры направлений в проекте
+- Aleksin, Altai, Moscow, Piter, Tula, Tver, Yaroslavl, Vladimir, Serpuhov, Dmitrov, Kalyazin
+- Kavkaz, Murmansk, Kaliningrad, KBR (Кабардино-Балкария)
+- Pokrov, Sergiev, Rostov, Ryazan, Spirovo
\ No newline at end of file
diff --git a/telegram/env_example.txt b/telegram/env_example.txt
new file mode 100644
index 0000000..09320d4
--- /dev/null
+++ b/telegram/env_example.txt
@@ -0,0 +1,2 @@
+# Telegram Bot Configuration
+TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN_HERE
\ No newline at end of file
diff --git a/telegram/requirements.txt b/telegram/requirements.txt
new file mode 100644
index 0000000..5050803
--- /dev/null
+++ b/telegram/requirements.txt
@@ -0,0 +1 @@
+python-telegram-bot==20.7
\ No newline at end of file
diff --git a/telegram/start_bot.sh b/telegram/start_bot.sh
new file mode 100755
index 0000000..4337723
--- /dev/null
+++ b/telegram/start_bot.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# Скрипт запуска Telegram бота
+
+echo "🤖 Запуск Telegram бота..."
+
+# Проверка наличия Python
+if ! command -v python3 &> /dev/null; then
+ echo "❌ Python3 не найден. Установите Python3."
+ exit 1
+fi
+
+# Проверка наличия pip
+if ! command -v pip3 &> /dev/null; then
+ echo "❌ pip3 не найден. Установите pip3."
+ exit 1
+fi
+
+# Установка зависимостей
+echo "📦 Установка зависимостей..."
+pip3 install -r requirements.txt
+
+# Проверка переменной окружения
+if [ -z "$TELEGRAM_BOT_TOKEN" ]; then
+ echo "⚠️ Не установлен TELEGRAM_BOT_TOKEN"
+ echo "Установите токен бота:"
+ echo "export TELEGRAM_BOT_TOKEN='ваш_токен_здесь'"
+ echo ""
+ echo "Или создайте файл .env и добавьте туда:"
+ echo "TELEGRAM_BOT_TOKEN=ваш_токен_здесь"
+ echo ""
+ read -p "Введите токен бота: " token
+ export TELEGRAM_BOT_TOKEN="$token"
+fi
+
+# Запуск бота
+echo "🚀 Запуск бота..."
+python3 telegram_bot.py
\ No newline at end of file
diff --git a/telegram/telegram_bot.py b/telegram/telegram_bot.py
new file mode 100644
index 0000000..0a69944
--- /dev/null
+++ b/telegram/telegram_bot.py
@@ -0,0 +1,1188 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+import requests
+import re
+import subprocess
+from datetime import datetime
+from pathlib import Path
+from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton
+from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, ContextTypes, filters
+import json
+
+# Настройка логирования
+logging.basicConfig(
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO
+)
+logger = logging.getLogger(__name__)
+
+# Токен бота (замените на ваш токен)
+BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', 'YOUR_BOT_TOKEN_HERE')
+
+# Пути к Hugo проекту
+HUGO_PROJECT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)))
+CONTENT_POST_PATH = os.path.join(HUGO_PROJECT_PATH, 'content', 'post')
+PUBLIC_IMAGES_PATH = os.path.join(HUGO_PROJECT_PATH, 'public', 'images')
+PLAN_FILE_PATH = os.path.join(HUGO_PROJECT_PATH, 'content', 'plan.md')
+
+# Создаем папки если их нет
+os.makedirs(CONTENT_POST_PATH, exist_ok=True)
+os.makedirs(PUBLIC_IMAGES_PATH, exist_ok=True)
+
+# Состояния бота
+class BotStates:
+ MAIN_MENU = 'main_menu'
+ CREATE_POST = 'create_post'
+ POST_TEXT = 'post_text'
+ POST_PHOTO = 'post_photo'
+ POST_VIDEO = 'post_video'
+ POST_YOUTUBE = 'post_youtube'
+ POST_LOCATION = 'post_location'
+ TRAVEL_CALENDAR = 'travel_calendar'
+ TRAVEL_TEXT = 'travel_text'
+ TRAVEL_PHOTO = 'travel_photo'
+ POST_LOCATION_NAME = 'post_location_name'
+ POST_DESCRIPTION = 'post_description'
+ CALENDAR_MANAGE = 'calendar_manage'
+ CALENDAR_ADD_MONTH = 'calendar_add_month'
+ CALENDAR_ADD_SPECIAL = 'calendar_add_special'
+ CALENDAR_REMOVE = 'calendar_remove'
+
+# Хранилище состояний пользователей
+user_states = {}
+user_data = {}
+
+def load_data(filename):
+ """Загрузка данных из JSON файла"""
+ try:
+ with open(filename, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError):
+ return []
+
+def save_data(filename, data):
+ """Сохранение данных в JSON файл"""
+ with open(filename, 'w', encoding='utf-8') as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+
+def generate_post_slug(title, date):
+ """Генерация slug для поста в формате Hugo"""
+ # Убираем специальные символы и заменяем пробелы на дефисы
+ slug = re.sub(r'[^\w\s-]', '', title.lower())
+ slug = re.sub(r'[-\s]+', '-', slug).strip('-')
+
+ # Добавляем дату в формате YYYYMMDD
+ date_str = date.strftime('%Y%m%d')
+
+ # Если slug пустой, используем дату
+ if not slug:
+ slug = f"post-{date_str}"
+ else:
+ slug = f"{slug}-{date_str}"
+
+ return slug
+
+def download_telegram_file(file_id, file_name, context):
+ """Скачивание файла из Telegram"""
+ try:
+ file = context.bot.get_file(file_id)
+ file_path = os.path.join(PUBLIC_IMAGES_PATH, file_name)
+
+ # Скачиваем файл
+ response = requests.get(file.file_path)
+ with open(file_path, 'wb') as f:
+ f.write(response.content)
+
+ return f"images/{file_name}"
+ except Exception as e:
+ logger.error(f"Ошибка при скачивании файла: {e}")
+ return None
+
+def create_hugo_post(post_data):
+ """Создание поста в формате Hugo"""
+ try:
+ # Создаем slug и имя файла
+ post_date = datetime.now()
+ post_slug = generate_post_slug(post_data.get('title', 'Новый пост'), post_date)
+ file_name = f"{post_slug}.md"
+ file_path = os.path.join(CONTENT_POST_PATH, file_name)
+
+ # Генерируем front matter
+ front_matter = f"""+++
+title = '{post_data.get('title', 'Новый пост')}'
+slug = '{post_slug}'
+date = "{post_date.strftime('%Y-%m-%dT%H:%M:%S')}"
+"""
+
+ # Добавляем описание если есть
+ if post_data.get('description'):
+ front_matter += f"description = '{post_data['description']}'\n"
+
+ # Добавляем изображение если есть
+ if post_data.get('main_image'):
+ front_matter += f"image = '{post_data['main_image']}'\n"
+
+ front_matter += "+++\n\n"
+
+ # Основной текст поста
+ content = post_data.get('text', '')
+
+ # Добавляем фотографии в галерею
+ if post_data.get('photos'):
+ if len(post_data['photos']) > 1:
+ content += "\n\n## Фотографии\n\n"
+ content += "{{< gallery dir=\"/images/\" />}}\n"
+ elif len(post_data['photos']) == 1:
+ # Если одна фотография, то она уже указана как main_image в front matter
+ pass
+
+ # Добавляем YouTube видео
+ if post_data.get('youtube_links'):
+ content += "\n\n## Видео\n\n"
+ for link in post_data['youtube_links']:
+ video_id = extract_youtube_id(link)
+ if video_id:
+ content += f'{{< youtube {video_id} >}}\n\n'
+
+ # Добавляем локации
+ if post_data.get('locations'):
+ content += "\n\n## Локации\n\n"
+ for location in post_data['locations']:
+ content += f"📍 [Посмотреть на карте]({location})\n\n"
+
+ # Добавляем кнопку "Назад наверх"
+ content += "\n{{< rawhtml >}}\n{{< back-to-top >}}\n{{< /rawhtml >}}\n"
+
+ # Записываем файл
+ with open(file_path, 'w', encoding='utf-8') as f:
+ f.write(front_matter + content)
+
+ return file_path
+
+ except Exception as e:
+ logger.error(f"Ошибка при создании поста: {e}")
+ return None
+
+def extract_youtube_id(url):
+ """Извлечение ID видео из YouTube URL"""
+ patterns = [
+ r'(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/embed/)([^&\n?#]+)',
+ r'youtube\.com/watch\?.*v=([^&\n?#]+)'
+ ]
+
+ for pattern in patterns:
+ match = re.search(pattern, url)
+ if match:
+ return match.group(1)
+ return None
+
+def add_travel_calendar_entry(entry_data):
+ """Добавление записи в календарь поездок"""
+ try:
+ # Читаем существующий файл plan.md
+ with open(PLAN_FILE_PATH, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Находим место для вставки новой записи (после календаря)
+ calendar_end = content.find('')
+ if calendar_end == -1:
+ return False
+
+ # Создаем запись
+ new_entry = f"\n\n---\n\n### 📅 {datetime.now().strftime('%d.%m.%Y')}\n\n"
+ new_entry += f"**{entry_data.get('title', 'Новая поездка')}**\n\n"
+ new_entry += entry_data.get('text', '') + "\n\n"
+
+ if entry_data.get('photo'):
+ new_entry += f"\n\n"
+
+ # Вставляем запись
+ insert_position = content.find('\n\nЖелаете отправиться в путешествие?')
+ if insert_position != -1:
+ content = content[:insert_position] + new_entry + content[insert_position:]
+ else:
+ content += new_entry
+
+ # Записываем обновленный файл
+ with open(PLAN_FILE_PATH, 'w', encoding='utf-8') as f:
+ f.write(content)
+
+ return True
+
+ except Exception as e:
+ logger.error(f"Ошибка при добавлении записи в календарь: {e}")
+ return False
+
+def get_current_travel_options():
+ """Получение текущих опций поездок из plan.md"""
+ try:
+ with open(PLAN_FILE_PATH, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Ищем секцию select с опциями
+ select_start = content.find('')
+
+ if select_start == -1 or select_end == -1:
+ return []
+
+ select_content = content[select_start:select_end]
+
+ # Извлекаем опции
+ import re
+ options = re.findall(r'', select_content)
+ # Фильтруем пустые опции и исключаем "Свой вариант без БВС"
+ filtered_options = [(value, text) for value, text in options
+ if value and text and value != "Свой вариант без БВС"]
+
+ return filtered_options
+
+ except Exception as e:
+ logger.error(f"Ошибка при чтении опций календаря: {e}")
+ return []
+
+def add_travel_option(option_text):
+ """Добавление новой опции поездки в plan.md"""
+ try:
+ with open(PLAN_FILE_PATH, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Ищем место для вставки новой опции (перед "Свой вариант без БВС")
+ target = ''
+ target_pos = content.find(target)
+
+ if target_pos == -1:
+ return False
+
+ # Создаем новую опцию
+ new_option = f' \n '
+
+ # Вставляем новую опцию
+ new_content = content[:target_pos] + new_option + content[target_pos:]
+
+ # Записываем обновленный файл
+ with open(PLAN_FILE_PATH, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+
+ return True
+
+ except Exception as e:
+ logger.error(f"Ошибка при добавлении опции поездки: {e}")
+ return False
+
+def remove_travel_option(option_text):
+ """Удаление опции поездки из plan.md"""
+ try:
+ with open(PLAN_FILE_PATH, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # Ищем и удаляем опцию
+ pattern = f' \n'
+ new_content = re.sub(pattern, '', content)
+
+ # Если не нашли точно такую строку, попробуем без лишних пробелов
+ if new_content == content:
+ pattern = f''
+ new_content = re.sub(pattern + r'\s*', '', content)
+
+ if new_content != content:
+ with open(PLAN_FILE_PATH, 'w', encoding='utf-8') as f:
+ f.write(new_content)
+ return True
+ else:
+ return False
+
+ except Exception as e:
+ logger.error(f"Ошибка при удалении опции поездки: {e}")
+ return False
+
+def run_git_command(command, cwd=None):
+ """Выполнение Git команды"""
+ try:
+ if cwd is None:
+ cwd = HUGO_PROJECT_PATH
+
+ result = subprocess.run(
+ command,
+ shell=True,
+ cwd=cwd,
+ capture_output=True,
+ text=True,
+ timeout=30
+ )
+
+ if result.returncode == 0:
+ logger.info(f"Git команда выполнена: {command}")
+ return True, result.stdout
+ else:
+ logger.error(f"Ошибка Git команды: {command}, {result.stderr}")
+ return False, result.stderr
+
+ except subprocess.TimeoutExpired:
+ logger.error(f"Таймаут Git команды: {command}")
+ return False, "Timeout"
+ except Exception as e:
+ logger.error(f"Исключение при выполнении Git команды: {e}")
+ return False, str(e)
+
+def git_add_commit_push(files, commit_message):
+ """Добавление, коммит и пуш файлов в Git"""
+ try:
+ # Добавляем файлы
+ for file_path in files:
+ success, output = run_git_command(f"git add \"{file_path}\"")
+ if not success:
+ return False, f"Ошибка при добавлении файла {file_path}: {output}"
+
+ # Создаем коммит
+ success, output = run_git_command(f"git commit -m \"{commit_message}\"")
+ if not success:
+ return False, f"Ошибка при создании коммита: {output}"
+
+ # Пушим изменения
+ success, output = run_git_command("git push")
+ if not success:
+ return False, f"Ошибка при пуше: {output}"
+
+ return True, "Изменения успешно отправлены в репозиторий"
+
+ except Exception as e:
+ logger.error(f"Ошибка Git операций: {e}")
+ return False, str(e)
+
+def transliterate_city_name(city_name):
+ """Транслитерация названия города с русского на латиницу"""
+ # Словарь для известных городов и направлений
+ known_cities = {
+ 'москва': 'Moscow',
+ 'питер': 'Piter',
+ 'санкт-петербург': 'Piter',
+ 'спб': 'Piter',
+ 'тула': 'Tula',
+ 'тверь': 'Tver',
+ 'ярославль': 'Yaroslavl',
+ 'владимир': 'Vladimir',
+ 'серпухов': 'Serpuhov',
+ 'дмитров': 'Dmitrov',
+ 'калязин': 'Kalyazin',
+ 'кавказ': 'Kavkaz',
+ 'мурманск': 'Murmansk',
+ 'калининград': 'Kaliningrad',
+ 'кбр': 'KBR',
+ 'кабардино-балкария': 'KBR',
+ 'покров': 'Pokrov',
+ 'сергиев': 'Sergiev',
+ 'сергиев посад': 'Sergiev',
+ 'ростов': 'Rostov',
+ 'рязань': 'Ryazan',
+ 'спирово': 'Spirovo',
+ 'алексин': 'Aleksin',
+ 'алтай': 'Altai',
+ 'клин': 'Klin',
+ 'коломна': 'Kolomna',
+ 'можайск': 'Mojaisk',
+ 'дубна': 'Dubna',
+ 'кашира': 'Kashira',
+ 'волоколамск': 'Volok',
+ 'елки-палки': 'Elki-palki',
+ 'беломорск': 'Belayagora',
+ 'ржев': 'Rzhev',
+ 'ржев': 'Rzhev'
+ }
+
+ # Приводим к нижнему регистру
+ city_lower = city_name.lower().strip()
+
+ # Проверяем известные города
+ if city_lower in known_cities:
+ return known_cities[city_lower]
+
+ # Простая транслитерация для неизвестных названий
+ translit_dict = {
+ 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'e',
+ 'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm',
+ 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
+ 'ф': 'f', 'х': 'h', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch',
+ 'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya',
+ ' ': '', '-': '', '_': ''
+ }
+
+ result = ''
+ for char in city_lower:
+ if char in translit_dict:
+ result += translit_dict[char]
+ elif char.isalpha():
+ result += char
+
+ # Делаем первую букву заглавной
+ return result.capitalize() if result else 'Unknown'
+
+def generate_image_filename(location, date, image_number, extension='jpg'):
+ """Генерация имени файла изображения по схеме Location-YYYYMMDD-N.ext"""
+ date_str = date.strftime('%Y%m%d')
+ location_latin = transliterate_city_name(location)
+ return f"{location_latin}-{date_str}-{image_number}.{extension}"
+
+def get_main_keyboard():
+ """Главная клавиатура"""
+ keyboard = [
+ [KeyboardButton("📝 Создать пост")],
+ [KeyboardButton("🌍 Хочу поехать")],
+ [KeyboardButton("📅 Управление календарём")]
+ ]
+ return ReplyKeyboardMarkup(keyboard, resize_keyboard=True)
+
+def get_post_creation_keyboard():
+ """Клавиатура для создания поста"""
+ keyboard = [
+ [InlineKeyboardButton("📸 Добавить фото", callback_data="post_photo")],
+ [InlineKeyboardButton("🎥 Добавить видео", callback_data="post_video")],
+ [InlineKeyboardButton("🔗 YouTube ссылка", callback_data="post_youtube")],
+ [InlineKeyboardButton("📍 Локация (Яндекс.Карты)", callback_data="post_location")],
+ [InlineKeyboardButton("✅ Опубликовать пост", callback_data="publish_post")],
+ [InlineKeyboardButton("❌ Отмена", callback_data="cancel")]
+ ]
+ return InlineKeyboardMarkup(keyboard)
+
+def get_photo_quality_keyboard():
+ """Клавиатура для выбора качества фото (не используется, оставлена для совместимости)"""
+ keyboard = [
+ [InlineKeyboardButton("🔙 Назад", callback_data="back_to_post")]
+ ]
+ return InlineKeyboardMarkup(keyboard)
+
+def get_calendar_management_keyboard():
+ """Клавиатура для управления календарём"""
+ keyboard = [
+ [InlineKeyboardButton("➕ Добавить месячную поездку", callback_data="calendar_add_monthly")],
+ [InlineKeyboardButton("✨ Добавить специальную поездку", callback_data="calendar_add_special")],
+ [InlineKeyboardButton("❌ Удалить поездку", callback_data="calendar_remove")],
+ [InlineKeyboardButton("📋 Просмотреть список", callback_data="calendar_list")],
+ [InlineKeyboardButton("🔙 Назад", callback_data="back_to_main")]
+ ]
+ return InlineKeyboardMarkup(keyboard)
+
+async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Команда /start"""
+ user_id = update.effective_user.id
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+
+ welcome_text = (
+ "🤖 Добро пожаловать в бот предназначенный для загрузки контента на сайт \"Пока ты спал\"!\n\n"
+ "Выберите действие:"
+ )
+
+ await update.message.reply_text(
+ welcome_text,
+ reply_markup=get_main_keyboard()
+ )
+
+async def handle_main_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка главного меню"""
+ user_id = update.effective_user.id
+ text = update.message.text
+
+ if text == "📝 Создать пост":
+ user_states[user_id] = BotStates.CREATE_POST
+ user_data[user_id] = {
+ 'type': 'post',
+ 'title': '',
+ 'description': '',
+ 'location': '',
+ 'text': '',
+ 'photos': [],
+ 'videos': [],
+ 'youtube_links': [],
+ 'locations': [],
+ 'main_image': None
+ }
+
+ await update.message.reply_text(
+ "📝 Создание нового поста для Hugo сайта\n\n"
+ "Сначала введите заголовок поста:",
+ reply_markup=None
+ )
+ user_states[user_id] = BotStates.POST_DESCRIPTION
+
+ elif text == "🌍 Хочу поехать":
+ user_states[user_id] = BotStates.TRAVEL_CALENDAR
+ user_data[user_id] = {
+ 'type': 'travel',
+ 'title': '',
+ 'text': '',
+ 'photo': None
+ }
+
+ await update.message.reply_text(
+ "🌍 Добавление записи в календарь поездок\n\n"
+ "Введите название поездки:",
+ reply_markup=None
+ )
+ user_states[user_id] = BotStates.TRAVEL_TEXT
+
+ elif text == "📅 Управление календарём":
+ user_states[user_id] = BotStates.CALENDAR_MANAGE
+ await update.message.reply_text(
+ "📅 **Управление календарём поездок**\n\n"
+ "Выберите действие:",
+ reply_markup=get_calendar_management_keyboard()
+ )
+
+async def handle_post_description(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка описания поста"""
+ user_id = update.effective_user.id
+ text = update.message.text
+
+ if not user_data[user_id].get('title'):
+ # Первое сообщение - заголовок
+ user_data[user_id]['title'] = text
+ await update.message.reply_text(
+ f"✅ Заголовок сохранен: {text}\n\n"
+ "Теперь введите краткое описание для превью (1-2 слова):\n"
+ "Например: Поход, Экскурсия, Отдых, Приключение..."
+ )
+ elif not user_data[user_id].get('description'):
+ # Второе сообщение - описание
+ user_data[user_id]['description'] = text
+ await update.message.reply_text(
+ f"✅ Описание сохранено: {text}\n\n"
+ "Теперь введите направление/город поездки (на русском):\n"
+ "Это нужно для организации фото по названиям файлов.\n"
+ "Например: Москва, Питер, Алтай, Кавказ, Тула..."
+ )
+ user_states[user_id] = BotStates.POST_LOCATION_NAME
+
+async def handle_post_location_name(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка названия локации для поста"""
+ user_id = update.effective_user.id
+ text = update.message.text
+
+ # Третье сообщение - локация
+ user_data[user_id]['location'] = text
+ location_latin = transliterate_city_name(text)
+ await update.message.reply_text(
+ f"✅ Направление сохранено: {text} → {location_latin}\n\n"
+ "Теперь введите основной текст поста:"
+ )
+ user_states[user_id] = BotStates.POST_TEXT
+
+async def handle_post_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка основного текста поста"""
+ user_id = update.effective_user.id
+ text = update.message.text
+
+ # Четвертое сообщение - основной текст
+ user_data[user_id]['text'] = text
+ await update.message.reply_text(
+ f"✅ Текст поста сохранен!\n\n"
+ f"📝 Заголовок: {user_data[user_id]['title']}\n"
+ f"📋 Описание: {user_data[user_id]['description']}\n"
+ f"📍 Направление: {user_data[user_id]['location']}\n\n"
+ "Теперь выберите дополнительные опции:",
+ reply_markup=get_post_creation_keyboard()
+ )
+
+async def handle_travel_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка текста для календаря поездок"""
+ user_id = update.effective_user.id
+ text = update.message.text
+
+ if not user_data[user_id].get('title'):
+ # Первое сообщение - название
+ user_data[user_id]['title'] = text
+ await update.message.reply_text(
+ f"✅ Название сохранено: {text}\n\n"
+ "Теперь опишите детали поездки:"
+ )
+ else:
+ # Второе сообщение - описание
+ user_data[user_id]['text'] = text
+
+ keyboard = [
+ [InlineKeyboardButton("📸 Добавить фото", callback_data="travel_photo")],
+ [InlineKeyboardButton("✅ Сохранить", callback_data="save_travel")],
+ [InlineKeyboardButton("❌ Отмена", callback_data="cancel")]
+ ]
+
+ await update.message.reply_text(
+ f"✅ Описание сохранено!\n\n"
+ f"Название: {user_data[user_id]['title']}\n"
+ f"Описание: {text}\n\n"
+ "Хотите добавить фото?",
+ reply_markup=InlineKeyboardMarkup(keyboard)
+ )
+
+async def handle_callback_query(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка callback запросов"""
+ query = update.callback_query
+ await query.answer()
+
+ user_id = query.from_user.id
+ data = query.data
+
+ if data == "post_photo":
+ user_states[user_id] = BotStates.POST_PHOTO
+ await query.edit_message_text(
+ "📸 Отправьте фото как файл через Telegram:\n\n"
+ "Для сохранения качества обязательно отправляйте фото как документ/файл!\n"
+ "Первое фото будет главным (в превью), остальные добавятся в галерею."
+ )
+
+ elif data == "post_video":
+ user_states[user_id] = BotStates.POST_VIDEO
+ await query.edit_message_text(
+ "🎥 Отправьте видео:"
+ )
+
+ elif data == "post_youtube":
+ user_states[user_id] = BotStates.POST_YOUTUBE
+ await query.edit_message_text(
+ "🔗 Отправьте ссылку на YouTube видео:"
+ )
+
+ elif data == "post_location":
+ user_states[user_id] = BotStates.POST_LOCATION
+ await query.edit_message_text(
+ "📍 Отправьте ссылку на Яндекс.Карты:"
+ )
+
+ elif data == "travel_photo":
+ user_states[user_id] = BotStates.TRAVEL_PHOTO
+ await query.edit_message_text(
+ "📸 Отправьте фото как файл для календаря поездок:"
+ )
+
+ elif data == "publish_post":
+ await publish_hugo_post(query, user_id, context)
+
+ elif data == "save_travel":
+ await save_travel_entry(query, user_id, context)
+
+ elif data == "calendar_add_monthly":
+ user_states[user_id] = BotStates.CALENDAR_ADD_MONTH
+ await query.edit_message_text(
+ "➕ **Добавление месячной поездки**\n\n"
+ "Введите название поездки в формате:\n"
+ "`Полёты в [месяц] [год] года`\n\n"
+ "Например:\n"
+ "• Полёты в октябре 2025 года\n"
+ "• Полёты в ноябре 2025 года\n"
+ "• Полёты в декабре 2025 года"
+ )
+
+ elif data == "calendar_add_special":
+ user_states[user_id] = BotStates.CALENDAR_ADD_SPECIAL
+ await query.edit_message_text(
+ "✨ **Добавление специальной поездки**\n\n"
+ "Введите название специальной поездки:\n\n"
+ "Например:\n"
+ "• Новогодние каникулы в горах\n"
+ "• Майские праздники на природе\n"
+ "• Летний фестиваль"
+ )
+
+ elif data == "calendar_remove":
+ await handle_calendar_remove_callback(query, user_id, context)
+
+ elif data == "calendar_list":
+ await handle_calendar_list_callback(query, user_id, context)
+
+ elif data == "back_to_main":
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+ await query.edit_message_text("Выберите действие:")
+ await context.bot.send_message(
+ chat_id=user_id,
+ text="Главное меню:",
+ reply_markup=get_main_keyboard()
+ )
+
+ elif data == "cancel":
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+ await query.edit_message_text("❌ Операция отменена")
+ await context.bot.send_message(
+ chat_id=user_id,
+ text="Выберите действие:",
+ reply_markup=get_main_keyboard()
+ )
+
+ elif data.startswith("remove_trip_"):
+ trip_value = data.replace("remove_trip_", "")
+ success = remove_travel_option(trip_value)
+
+ if success:
+ # Создаем Git коммит
+ commit_message = f"Удалена поездка: {trip_value}\n\n🤖 Создано через Telegram бота"
+ git_success, git_message = git_add_commit_push(["content/plan.md"], commit_message)
+
+ message = f"✅ Поездка удалена!\n\n"
+ message += f"❌ Удалено: {trip_value}\n"
+ message += f"💡 Обновлен файл: content/plan.md\n"
+
+ # Добавляем информацию о Git операции
+ if git_success:
+ message += f"🔄 {git_message}"
+ else:
+ message += f"⚠️ Git ошибка: {git_message}"
+ else:
+ message = "❌ Ошибка при удалении поездки"
+
+ await query.edit_message_text(
+ message,
+ reply_markup=InlineKeyboardMarkup([[
+ InlineKeyboardButton("🔙 Назад", callback_data="back_to_main")
+ ]])
+ )
+
+ elif data == "back_to_post":
+ await query.edit_message_text(
+ "📝 Создание поста\n\n"
+ "Выберите дополнительные опции:",
+ reply_markup=get_post_creation_keyboard()
+ )
+
+async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка фотографий"""
+ user_id = update.effective_user.id
+ state = user_states.get(user_id)
+
+ if state == BotStates.POST_PHOTO:
+ # Проверяем, что фото отправлено как документ
+ if update.message.document:
+ photo = update.message.document
+
+ # Генерируем имя файла по схеме Location-YYYYMMDD-N.ext
+ current_date = datetime.now()
+ file_extension = 'jpg'
+ if hasattr(photo, 'file_name') and photo.file_name:
+ file_extension = photo.file_name.split('.')[-1].lower()
+
+ location = user_data[user_id].get('location', 'Unknown')
+ image_number = len(user_data[user_id]['photos']) + 1
+ file_name = generate_image_filename(location, current_date, image_number, file_extension)
+
+ # Скачиваем файл
+ image_path = download_telegram_file(photo.file_id, file_name, context)
+
+ if image_path:
+ user_data[user_id]['photos'].append(image_path)
+
+ # Первое фото делаем главным
+ if not user_data[user_id].get('main_image'):
+ user_data[user_id]['main_image'] = image_path
+ status = "главное фото для превью"
+ else:
+ status = "дополнительное фото для галереи"
+
+ await update.message.reply_text(
+ f"✅ Фото добавлено как {status}!\n"
+ f"📸 Всего фото: {len(user_data[user_id]['photos'])}\n"
+ f"💾 Сохранено: {image_path}",
+ reply_markup=get_post_creation_keyboard()
+ )
+ else:
+ await update.message.reply_text(
+ "❌ Ошибка при загрузке фото",
+ reply_markup=get_post_creation_keyboard()
+ )
+ else:
+ # Если фото отправлено обычным способом
+ await update.message.reply_text(
+ "⚠️ Пожалуйста, отправьте фото как файл/документ!\n\n"
+ "Это необходимо для сохранения качества.\n"
+ "Нажмите на скрепку и выберите 'Файл'.",
+ reply_markup=get_post_creation_keyboard()
+ )
+
+ elif state == BotStates.TRAVEL_PHOTO:
+ # Проверяем, что фото отправлено как документ
+ if update.message.document:
+ photo = update.message.document
+
+ # Генерируем имя файла
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+ file_extension = 'jpg'
+ if hasattr(photo, 'file_name') and photo.file_name:
+ file_extension = photo.file_name.split('.')[-1].lower()
+
+ file_name = f"travel_{timestamp}.{file_extension}"
+
+ # Скачиваем файл
+ image_path = download_telegram_file(photo.file_id, file_name, context)
+
+ if image_path:
+ user_data[user_id]['photo'] = image_path
+
+ keyboard = [
+ [InlineKeyboardButton("✅ Сохранить", callback_data="save_travel")],
+ [InlineKeyboardButton("❌ Отмена", callback_data="cancel")]
+ ]
+
+ await update.message.reply_text(
+ f"✅ Фото добавлено!\n"
+ f"💾 Сохранено: {image_path}",
+ reply_markup=InlineKeyboardMarkup(keyboard)
+ )
+ else:
+ await update.message.reply_text("❌ Ошибка при загрузке фото")
+ else:
+ # Если фото отправлено обычным способом
+ await update.message.reply_text(
+ "⚠️ Пожалуйста, отправьте фото как файл/документ!\n\n"
+ "Это необходимо для сохранения качества.\n"
+ "Нажмите на скрепку и выберите 'Файл'."
+ )
+
+async def handle_video(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка видео"""
+ user_id = update.effective_user.id
+ state = user_states.get(user_id)
+
+ if state == BotStates.POST_VIDEO:
+ video = update.message.video
+
+ # Генерируем имя файла по схеме Location-YYYYMMDD-N.mp4
+ current_date = datetime.now()
+ location = user_data[user_id].get('location', 'Unknown')
+ video_number = len(user_data[user_id]['videos']) + 1
+ file_name = generate_image_filename(location, current_date, video_number, 'mp4')
+
+ # Скачиваем файл
+ video_path = download_telegram_file(video.file_id, file_name, context)
+
+ if video_path:
+ user_data[user_id]['videos'].append(video_path)
+ await update.message.reply_text(
+ f"✅ Видео добавлено к посту!\n"
+ f"Сохранено: {video_path}",
+ reply_markup=get_post_creation_keyboard()
+ )
+ else:
+ await update.message.reply_text(
+ "❌ Ошибка при загрузке видео",
+ reply_markup=get_post_creation_keyboard()
+ )
+
+async def handle_text_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка текстового ввода"""
+ user_id = update.effective_user.id
+ state = user_states.get(user_id)
+ text = update.message.text
+
+ if state == BotStates.POST_YOUTUBE:
+ user_data[user_id]['youtube_links'].append(text)
+ await update.message.reply_text(
+ "✅ YouTube ссылка добавлена!",
+ reply_markup=get_post_creation_keyboard()
+ )
+
+ elif state == BotStates.POST_LOCATION:
+ user_data[user_id]['locations'].append(text)
+ await update.message.reply_text(
+ "✅ Локация добавлена!",
+ reply_markup=get_post_creation_keyboard()
+ )
+
+async def publish_hugo_post(query, user_id, context):
+ """Публикация поста в Hugo"""
+ post_data = user_data[user_id].copy()
+
+ # Создаем Hugo пост
+ file_path = create_hugo_post(post_data)
+
+ if file_path:
+ # Подготавливаем файлы для Git коммита
+ files_to_commit = []
+
+ # Добавляем markdown файл поста
+ relative_post_path = os.path.relpath(file_path, HUGO_PROJECT_PATH)
+ files_to_commit.append(relative_post_path)
+
+ # Добавляем все загруженные изображения и видео
+ for photo_path in post_data.get('photos', []):
+ if photo_path.startswith('images/'):
+ # Конвертируем относительный путь в путь от корня проекта
+ full_image_path = f"public/{photo_path}"
+ files_to_commit.append(full_image_path)
+
+ for video_path in post_data.get('videos', []):
+ if video_path.startswith('images/'):
+ # Конвертируем относительный путь в путь от корня проекта
+ full_video_path = f"public/{video_path}"
+ files_to_commit.append(full_video_path)
+
+ # Создаем Git коммит
+ commit_message = f"Добавлен новый пост: {post_data['title']}\n\n🤖 Создано через Telegram бота"
+ git_success, git_message = git_add_commit_push(files_to_commit, commit_message)
+
+ # Формирование сообщения о публикации
+ message = f"✅ Пост опубликован в Hugo!\n\n"
+ message += f"📝 Заголовок: {post_data['title']}\n"
+ message += f"📋 Описание: {post_data['description']}\n"
+ message += f"📄 Файл: {os.path.basename(file_path)}\n"
+
+ if post_data['photos']:
+ message += f"📸 Фото: {len(post_data['photos'])} шт.\n"
+ if post_data['videos']:
+ message += f"🎥 Видео: {len(post_data['videos'])} шт.\n"
+ if post_data['youtube_links']:
+ message += f"🔗 YouTube: {len(post_data['youtube_links'])} ссылок\n"
+ if post_data['locations']:
+ message += f"📍 Локации: {len(post_data['locations'])} шт.\n"
+
+ message += f"\n💡 Файл сохранен: content/post/{os.path.basename(file_path)}\n"
+
+ # Добавляем информацию о Git операции
+ if git_success:
+ message += f"🔄 {git_message}"
+ else:
+ message += f"⚠️ Git ошибка: {git_message}"
+
+ await query.edit_message_text(message)
+ else:
+ await query.edit_message_text("❌ Ошибка при создании поста")
+
+ # Возврат в главное меню
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+
+ await query.message.reply_text(
+ "Выберите действие:",
+ reply_markup=get_main_keyboard()
+ )
+
+async def save_travel_entry(query, user_id, context):
+ """Сохранение записи в календарь поездок"""
+ travel_data = user_data[user_id].copy()
+
+ # Добавляем запись в план
+ success = add_travel_calendar_entry(travel_data)
+
+ if success:
+ # Подготавливаем файлы для Git коммита
+ files_to_commit = ["content/plan.md"]
+
+ # Добавляем фото если есть
+ if travel_data.get('photo') and travel_data['photo'].startswith('images/'):
+ full_image_path = f"public/{travel_data['photo']}"
+ files_to_commit.append(full_image_path)
+
+ # Создаем Git коммит
+ commit_message = f"Обновлен календарь поездок: {travel_data['title']}\n\n🤖 Создано через Telegram бота"
+ git_success, git_message = git_add_commit_push(files_to_commit, commit_message)
+
+ message = f"✅ Запись добавлена в календарь поездок!\n\n"
+ message += f"📝 Название: {travel_data['title']}\n"
+ message += f"📋 Описание: {travel_data['text']}\n"
+
+ if travel_data.get('photo'):
+ message += f"📸 Фото: {travel_data['photo']}\n"
+
+ message += f"\n💡 Обновлен файл: content/plan.md\n"
+
+ # Добавляем информацию о Git операции
+ if git_success:
+ message += f"🔄 {git_message}"
+ else:
+ message += f"⚠️ Git ошибка: {git_message}"
+
+ await query.edit_message_text(message)
+ else:
+ await query.edit_message_text("❌ Ошибка при добавлении записи")
+
+ # Возврат в главное меню
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+
+ await query.message.reply_text(
+ "Выберите действие:",
+ reply_markup=get_main_keyboard()
+ )
+
+async def handle_calendar_add_monthly(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка добавления месячной поездки"""
+ user_id = update.effective_user.id
+ text = update.message.text.strip()
+
+ # Проверяем формат ввода (должен содержать месяц и год)
+ if not text:
+ await update.message.reply_text(
+ "❌ Пожалуйста, введите название поездки.\n"
+ "Например: Полёты в октябре 2025 года"
+ )
+ return
+
+ # Добавляем опцию в план
+ success = add_travel_option(text)
+
+ if success:
+ # Создаем Git коммит
+ commit_message = f"Добавлена месячная поездка: {text}\n\n🤖 Создано через Telegram бота"
+ git_success, git_message = git_add_commit_push(["content/plan.md"], commit_message)
+
+ message = f"✅ Месячная поездка добавлена!\n\n"
+ message += f"📅 Название: {text}\n"
+ message += f"💡 Обновлен файл: content/plan.md\n"
+
+ # Добавляем информацию о Git операции
+ if git_success:
+ message += f"🔄 {git_message}"
+ else:
+ message += f"⚠️ Git ошибка: {git_message}"
+
+ await update.message.reply_text(message, reply_markup=get_main_keyboard())
+ else:
+ await update.message.reply_text(
+ "❌ Ошибка при добавлении поездки",
+ reply_markup=get_main_keyboard()
+ )
+
+ # Возврат в главное меню
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+
+async def handle_calendar_add_special(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Обработка добавления специальной поездки"""
+ user_id = update.effective_user.id
+ text = update.message.text.strip()
+
+ if not text:
+ await update.message.reply_text(
+ "❌ Пожалуйста, введите название специальной поездки.\n"
+ "Например: Новогодние каникулы в горах"
+ )
+ return
+
+ # Добавляем опцию в план
+ success = add_travel_option(text)
+
+ if success:
+ # Создаем Git коммит
+ commit_message = f"Добавлена специальная поездка: {text}\n\n🤖 Создано через Telegram бота"
+ git_success, git_message = git_add_commit_push(["content/plan.md"], commit_message)
+
+ message = f"✅ Специальная поездка добавлена!\n\n"
+ message += f"✨ Название: {text}\n"
+ message += f"💡 Обновлен файл: content/plan.md\n"
+
+ # Добавляем информацию о Git операции
+ if git_success:
+ message += f"🔄 {git_message}"
+ else:
+ message += f"⚠️ Git ошибка: {git_message}"
+
+ await update.message.reply_text(message, reply_markup=get_main_keyboard())
+ else:
+ await update.message.reply_text(
+ "❌ Ошибка при добавлении поездки",
+ reply_markup=get_main_keyboard()
+ )
+
+ # Возврат в главное меню
+ user_states[user_id] = BotStates.MAIN_MENU
+ user_data[user_id] = {}
+
+async def handle_calendar_remove_callback(query, user_id, context):
+ """Обработка удаления поездки из календаря"""
+ options = get_current_travel_options()
+
+ if not options:
+ await query.edit_message_text(
+ "❌ Нет доступных поездок для удаления",
+ reply_markup=InlineKeyboardMarkup([[
+ InlineKeyboardButton("🔙 Назад", callback_data="back_to_main")
+ ]])
+ )
+ return
+
+ # Создаем клавиатуру с опциями для удаления
+ keyboard = []
+ for value, text in options:
+ keyboard.append([InlineKeyboardButton(
+ f"❌ {text}",
+ callback_data=f"remove_trip_{value}"
+ )])
+ keyboard.append([InlineKeyboardButton("🔙 Назад", callback_data="back_to_main")])
+
+ await query.edit_message_text(
+ "❌ **Удаление поездки**\n\n"
+ "Выберите поездку для удаления:",
+ reply_markup=InlineKeyboardMarkup(keyboard)
+ )
+
+async def handle_calendar_list_callback(query, user_id, context):
+ """Показать список текущих поездок"""
+ options = get_current_travel_options()
+
+ if not options:
+ message = "📋 **Список поездок**\n\n❌ Пока нет добавленных поездок"
+ else:
+ message = "📋 **Список поездок**\n\n"
+ for i, (value, text) in enumerate(options, 1):
+ message += f"{i}. {text}\n"
+
+ await query.edit_message_text(
+ message,
+ reply_markup=InlineKeyboardMarkup([[
+ InlineKeyboardButton("🔙 Назад", callback_data="back_to_main")
+ ]])
+ )
+
+def main():
+ """Основная функция запуска бота"""
+ # Создание приложения
+ application = Application.builder().token(BOT_TOKEN).build()
+
+ # Добавление обработчиков
+ application.add_handler(CommandHandler("start", start))
+ application.add_handler(CallbackQueryHandler(handle_callback_query))
+
+ # Обработчики для разных типов контента
+ application.add_handler(MessageHandler(filters.PHOTO | filters.Document.IMAGE, handle_photo))
+ application.add_handler(MessageHandler(filters.VIDEO, handle_video))
+
+ # Обработчик текстовых сообщений
+ application.add_handler(MessageHandler(
+ filters.TEXT & ~filters.COMMAND,
+ lambda update, context: handle_message_by_state(update, context)
+ ))
+
+ print("🤖 Hugo Bot запущен!")
+ print(f"📁 Посты сохраняются в: {CONTENT_POST_PATH}")
+ print(f"📸 Изображения сохраняются в: {PUBLIC_IMAGES_PATH}")
+ print(f"🌍 Календарь поездок: {PLAN_FILE_PATH}")
+ application.run_polling()
+
+async def handle_message_by_state(update: Update, context: ContextTypes.DEFAULT_TYPE):
+ """Маршрутизация сообщений по состояниям"""
+ user_id = update.effective_user.id
+ state = user_states.get(user_id, BotStates.MAIN_MENU)
+
+ if state == BotStates.MAIN_MENU:
+ await handle_main_menu(update, context)
+ elif state == BotStates.POST_DESCRIPTION:
+ await handle_post_description(update, context)
+ elif state == BotStates.POST_LOCATION_NAME:
+ await handle_post_location_name(update, context)
+ elif state == BotStates.POST_TEXT:
+ await handle_post_text(update, context)
+ elif state == BotStates.TRAVEL_TEXT:
+ await handle_travel_text(update, context)
+ elif state == BotStates.CALENDAR_ADD_MONTH:
+ await handle_calendar_add_monthly(update, context)
+ elif state == BotStates.CALENDAR_ADD_SPECIAL:
+ await handle_calendar_add_special(update, context)
+ elif state in [BotStates.POST_YOUTUBE, BotStates.POST_LOCATION]:
+ await handle_text_input(update, context)
+ else:
+ await update.message.reply_text(
+ "Используйте /start для начала работы с ботом."
+ )
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/temp_aug/Bogolubovo_20250419_0.md b/temp_aug/Bogolubovo_20250419_0.md
deleted file mode 100644
index 7768854..0000000
--- a/temp_aug/Bogolubovo_20250419_0.md
+++ /dev/null
@@ -1,29 +0,0 @@
-+++
-title = 'Боголюбово'
-slug = 'Bogolubovo0425'
-image = 'images/Bogolubovo_20250419_1.jpg'
-date = "2025-04-19T00:00:00"
-description = 'Покров на Нерли'
-disqus_identifier = '128'
-+++
-В этот раз тоже не без приключений - сразу после рассвета пошёл дождь.
-Но полёты всё же состоялись.
-
-
-
-
-
-
-
-
-
-
-
-Локация
-{{< rawhtml >}}
-
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
\ No newline at end of file
diff --git a/temp_aug/Bogolubovo_20250419_1.jpg b/temp_aug/Bogolubovo_20250419_1.jpg
deleted file mode 100644
index 96657b4..0000000
Binary files a/temp_aug/Bogolubovo_20250419_1.jpg and /dev/null differ
diff --git a/temp_aug/Bogolubovo_20250419_2.jpg b/temp_aug/Bogolubovo_20250419_2.jpg
deleted file mode 100644
index 13c8877..0000000
Binary files a/temp_aug/Bogolubovo_20250419_2.jpg and /dev/null differ
diff --git a/temp_aug/Bogolubovo_20250419_3.jpg b/temp_aug/Bogolubovo_20250419_3.jpg
deleted file mode 100644
index f96b7c7..0000000
Binary files a/temp_aug/Bogolubovo_20250419_3.jpg and /dev/null differ
diff --git a/temp_aug/Bogolubovo_20250419_4.jpg b/temp_aug/Bogolubovo_20250419_4.jpg
deleted file mode 100644
index 19cae23..0000000
Binary files a/temp_aug/Bogolubovo_20250419_4.jpg and /dev/null differ
diff --git a/temp_aug/Bogolubovo_20250419_5.jpg b/temp_aug/Bogolubovo_20250419_5.jpg
deleted file mode 100644
index dd283e6..0000000
Binary files a/temp_aug/Bogolubovo_20250419_5.jpg and /dev/null differ
diff --git a/temp_aug/Bogolubovo_20250419_6.jpg b/temp_aug/Bogolubovo_20250419_6.jpg
deleted file mode 100644
index b85df4c..0000000
Binary files a/temp_aug/Bogolubovo_20250419_6.jpg and /dev/null differ
diff --git a/temp_aug/Dronoslet_20240705_0.md b/temp_aug/Dronoslet_20240705_0.md
deleted file mode 100644
index 4b352c6..0000000
--- a/temp_aug/Dronoslet_20240705_0.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = 'Дронослёт'
-slug = 'Dronoslet'
-image = 'images/Dronoslet_20240705_1.jpg'
-date = "2024-06-05T00:00:00"
-description = 'Дюкинский заказник'
-disqus_identifier = '104'
-+++
-В отпуск удалось вырваться в это место - уголок Карелии во Владимирской области. Красивое место, о котором я рассказывал чуть раньше. Собиралось ехать много людей, но кто-то не смог, кто-то развернулся из-за непогоды, остались самые стойкие. И это было вознаграждено - непогода практически обошла нас стороной, полёты прошли отлично. Ну и у костра удалось посидеть не раз.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{< youtube id="53475UN_nDo" >}}
-
-Локация
-{{< rawhtml >}}
-
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
\ No newline at end of file
diff --git a/temp_aug/Dukyn_20250425_0.md b/temp_aug/Dukyn_20250425_0.md
deleted file mode 100644
index 4808f99..0000000
--- a/temp_aug/Dukyn_20250425_0.md
+++ /dev/null
@@ -1,24 +0,0 @@
-+++
-title = 'Дюкинский заказник'
-slug = 'Dukyn2504'
-image = 'images/Dukyn_20250425_1.jpg'
-date = "2025-04-25T00:00:00"
-description = 'скалы'
-disqus_identifier = '129'
-+++
-Очередной выезд в маленькую Карелию, Владимирская область. Скалы, лес, отличная погода и вот это вот всё. Да, ещё запускал одновременно два дрона с двух рук. Планируется выезд туда же с палатками, но можно и одним днём. Нужна погода.
-
-
-
-
-
-{{< youtube id="jXGd3HZl3io" >}}
-
-Локация
-{{< rawhtml >}}
-
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
\ No newline at end of file
diff --git a/temp_aug/Kanyon_20250510_0.md b/temp_aug/Kanyon_20250510_0.md
deleted file mode 100644
index a6619e4..0000000
--- a/temp_aug/Kanyon_20250510_0.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = 'Река Держа'
-slug = 'Kanyon2505'
-image = 'images/Kanyon_20250510_1.jpg'
-date = "2025-05-10T00:00:00"
-description = 'Тверская область'
-disqus_identifier = '132'
-+++
-Очередной выезд выходного дня.
-И снова мы находим уголки с природой Карелии, но неподалёку от Москвы.
-Здесь скалы не такие величественные, как в Дюкинском заказнике, но зато есть красивые спуски к реке.
-Виды по пути тоже довольно живописные.
-
-
-
-
-
-
-
-
-
-
-
-Видео: https://youtu.be/3dQxelsU6mg
-
-{{< youtube id="3dQxelsU6mg" >}}
-
-Локация
-{{< rawhtml >}}
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
diff --git a/temp_aug/Kanyon_20250510_2.jpg b/temp_aug/Kanyon_20250510_2.jpg
deleted file mode 100644
index 08a8440..0000000
Binary files a/temp_aug/Kanyon_20250510_2.jpg and /dev/null differ
diff --git a/temp_aug/Kanyon_20250510_4.jpg b/temp_aug/Kanyon_20250510_4.jpg
deleted file mode 100644
index 7b557fd..0000000
Binary files a/temp_aug/Kanyon_20250510_4.jpg and /dev/null differ
diff --git a/temp_aug/Kanyon_20250510_6.jpg b/temp_aug/Kanyon_20250510_6.jpg
deleted file mode 100644
index af87a01..0000000
Binary files a/temp_aug/Kanyon_20250510_6.jpg and /dev/null differ
diff --git a/temp_aug/Petushki_20250517_0.md b/temp_aug/Petushki_20250517_0.md
deleted file mode 100644
index d88df74..0000000
--- a/temp_aug/Petushki_20250517_0.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = 'Дронослёт в Петушках'
-slug = 'Petushki2505'
-image = 'images/Petushki_20250517_1.jpg'
-date = "2025-05-17T00:00:00"
-description = 'у реки'
-disqus_identifier = '131'
-+++
-Очередной выезд выходного дня. И снова мы находим уголки с красивой природой.
-
-
-
-
-
-
-
-
-
-Видео https://youtu.be/4EHtC6LAdc4
-{{< youtube id="4EHtC6LAdc4" >}}
-
-Локация
-{{< rawhtml >}}
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
diff --git a/temp_aug/Petushki_20250517_2.jpg b/temp_aug/Petushki_20250517_2.jpg
deleted file mode 100644
index 6032afc..0000000
Binary files a/temp_aug/Petushki_20250517_2.jpg and /dev/null differ
diff --git a/temp_aug/Petushki_20250517_3.jpg b/temp_aug/Petushki_20250517_3.jpg
deleted file mode 100644
index 89f06b0..0000000
Binary files a/temp_aug/Petushki_20250517_3.jpg and /dev/null differ
diff --git a/temp_aug/Radiotele_20250406_1.jpg b/temp_aug/Radiotele_20250406_1.jpg
deleted file mode 100644
index d92bd1e..0000000
Binary files a/temp_aug/Radiotele_20250406_1.jpg and /dev/null differ
diff --git a/temp_aug/Radiotele_20250406_2.jpg b/temp_aug/Radiotele_20250406_2.jpg
deleted file mode 100644
index cea229a..0000000
Binary files a/temp_aug/Radiotele_20250406_2.jpg and /dev/null differ
diff --git a/temp_aug/Radiotele_20250406_3.jpg b/temp_aug/Radiotele_20250406_3.jpg
deleted file mode 100644
index 588552e..0000000
Binary files a/temp_aug/Radiotele_20250406_3.jpg and /dev/null differ
diff --git a/temp_aug/Radiotele_20250406_4.jpg b/temp_aug/Radiotele_20250406_4.jpg
deleted file mode 100644
index 81821b7..0000000
Binary files a/temp_aug/Radiotele_20250406_4.jpg and /dev/null differ
diff --git a/temp_aug/Radiotele_20250406_5.jpg b/temp_aug/Radiotele_20250406_5.jpg
deleted file mode 100644
index 10aabd5..0000000
Binary files a/temp_aug/Radiotele_20250406_5.jpg and /dev/null differ
diff --git a/temp_aug/Rjev_20251005_0.md b/temp_aug/Rjev_20251005_0.md
deleted file mode 100644
index 01f10eb..0000000
--- a/temp_aug/Rjev_20251005_0.md
+++ /dev/null
@@ -1,31 +0,0 @@
-+++
-title = 'Ржевский мемориал'
-slug = 'Rjev2505'
-image = 'images/Rjev_20251005_1.jpg'
-date = "2025-05-10T00:00:00"
-description = 'лето'
-disqus_identifier = '133'
-+++
-Мемориал оставляет неизгладимое впечатление: такой возвышенный, но эхо войны витает над этим местом. Что касается съёмок - у моих друзей получились прекрасные кадры на закате, но я поехать в тот раз не смог. Соответственно так и не смог заснять подсветку, видимо надо запланировать поездку осенью.
-
-
-
-
-
-
-
-
-
-
-
-Видео: https://youtu.be/Ro0iHYZnvW8
-{{< youtube id="Ro0iHYZnvW8" >}}
-
-Локация
-{{< rawhtml >}}
-
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
\ No newline at end of file
diff --git a/temp_aug/Snazin_20250423_0.md b/temp_aug/Snazin_20250423_0.md
deleted file mode 100644
index 752361f..0000000
--- a/temp_aug/Snazin_20250423_0.md
+++ /dev/null
@@ -1,39 +0,0 @@
-+++
-title = 'Усадьба Сназина'
-slug = 'Snazin'
-image = 'images/Snazin_20250423_1.jpg'
-date = "2025-04-23T00:00:00"
-description = 'на стыке времён'
-disqus_identifier = '130'
-+++
-Поездка выходного дня.
-Усадьба основана генерал–майором Иваном Сназиным в 1797 году.
-Возраст усадьбы ощущается уже на подъезде к ней: малые архитектурные формы поблекли, аллеи превратились в заросли.
-Сама усадьба к сожалению в довольно плачевном состоянии - снаружи конструкции частично разрушены, а внутри находиться явно небезопасно.
-Собственно поэтому на помощь пришёл дрон, позволив рассмотреть усадьбу изнутри.
-Тем не менее готические контуры усадьбы в сочетании со львами и памятником вождю пролетариата намекают на необычную историю.
-
-Усадьба после смерти перешла по наследству внучке Ивана Сназина. Перестройкой усадьбы в готический замок занимался Владимир фон Гаслер, муж наследницы.
-Позже в парке построили пионерский лагерь, во время Великой Отечественной войны здесь был госпиталь.
-
-Говорят, что в усадьбе обитает привидение - якобы в пруду утопилась местная крестьянка.
-Лично мы привидение не встретили, но архитектура усадьбы, окружающая природа и атмосфера места точно не оставят вас равнодушными.
-
-
-
-
-
-
-
-
-
-{{< youtube id="PZOuu1K0zy4" >}}
-
-Локация
-{{< rawhtml >}}
-
-{{< /rawhtml >}}
-
-{{< rawhtml >}}
-Вернуться в начало страницы
-{{< /rawhtml >}}
\ No newline at end of file