From 8d3e509f9254f4b71913ddb2e7d3e11759195545 Mon Sep 17 00:00:00 2001 From: Kirchik Date: Mon, 11 Aug 2025 19:38:28 +0200 Subject: [PATCH] Apply .gitignore rules - remove ignored files --- migration-s3/1-upload-to-s3.sh | 133 -- migration-s3/2-update-content.sh | 169 --- migration-s3/3-update-config.sh | 301 ----- migration-s3/4-verify-migration.sh | 254 ---- migration-s3/README.md | 46 - migration-s3/add-new-photos.sh | 247 ---- migration-s3/config.sh | 82 -- migration-s3/setup-aws.sh | 341 ----- .../_gen/images/plan.webpZone.Identifier | 4 - telegram/README_BOT.md | 162 --- telegram/TODO.md | 106 -- telegram/env_example.txt | 2 - telegram/requirements.txt | 1 - telegram/start_bot.sh | 38 - telegram/telegram_bot.py | 1188 ----------------- .../layouts/partials/scripts/index.html | 13 - 16 files changed, 3087 deletions(-) delete mode 100755 migration-s3/1-upload-to-s3.sh delete mode 100755 migration-s3/2-update-content.sh delete mode 100755 migration-s3/3-update-config.sh delete mode 100755 migration-s3/4-verify-migration.sh delete mode 100644 migration-s3/README.md delete mode 100755 migration-s3/add-new-photos.sh delete mode 100755 migration-s3/config.sh delete mode 100755 migration-s3/setup-aws.sh delete mode 100644 resources/_gen/images/plan.webpZone.Identifier delete mode 100644 telegram/README_BOT.md delete mode 100644 telegram/TODO.md delete mode 100644 telegram/env_example.txt delete mode 100644 telegram/requirements.txt delete mode 100755 telegram/start_bot.sh delete mode 100644 telegram/telegram_bot.py delete mode 100644 themes/hugo-theme-massively/layouts/partials/scripts/index.html diff --git a/migration-s3/1-upload-to-s3.sh b/migration-s3/1-upload-to-s3.sh deleted file mode 100755 index 4c2cf18..0000000 --- a/migration-s3/1-upload-to-s3.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/bin/bash - -# Скрипт для загрузки всех изображений в S3 -# Этот скрипт загружает все изображения из static/images в S3 bucket - -set -e # Остановка при ошибке - -# Загрузка конфигурации -source "$(dirname "$0")/config.sh" - -# Инициализация -init_migration - -log "Начало загрузки изображений в S3..." - -# Проверка существования bucket -if ! aws s3 ls "s3://$S3_BUCKET" &> /dev/null; then - error_log "S3 bucket '$S3_BUCKET' не найден или недоступен" - exit 1 -fi - -# Подсчет файлов для загрузки -total_files=$(find "$LOCAL_IMAGES_DIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -o -iname "*.webp" \) | wc -l) -log "Найдено $total_files изображений для загрузки" - -# Счетчик загруженных файлов -uploaded_count=0 -error_count=0 - -# Функция для загрузки файла -upload_file() { - local file_path="$1" - local relative_path="${file_path#$LOCAL_IMAGES_DIR/}" - local s3_key="images/$relative_path" - - # Определение MIME типа - local mime_type="" - case "${file_path##*.}" in - jpg|jpeg) mime_type="image/jpeg" ;; - png) mime_type="image/png" ;; - gif) mime_type="image/gif" ;; - webp) mime_type="image/webp" ;; - *) mime_type="application/octet-stream" ;; - esac - - # Загрузка файла - if aws s3 cp "$file_path" "s3://$S3_BUCKET/$s3_key" \ - --content-type "$mime_type" \ - --cache-control "max-age=31536000" \ - --metadata-directive REPLACE; then - - uploaded_count=$((uploaded_count + 1)) - log "[$uploaded_count/$total_files] Загружено: $relative_path" - else - error_count=$((error_count + 1)) - error_log "Ошибка загрузки: $relative_path" - fi -} - -# Загрузка всех изображений -log "Загрузка изображений..." -export -f upload_file log error_log -export LOCAL_IMAGES_DIR S3_BUCKET uploaded_count error_count total_files LOG_FILE ERROR_LOG - -find "$LOCAL_IMAGES_DIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -o -iname "*.webp" \) | while read -r file; do - upload_file "$file" -done - -# Загрузка фавикона и фоновых изображений -log "Загрузка специальных изображений..." - -# Фавикон -if [ -f "./static/images/favicon.ico" ]; then - aws s3 cp "./static/images/favicon.ico" "s3://$S3_BUCKET/images/favicon.ico" \ - --content-type "image/x-icon" \ - --cache-control "max-age=31536000" - log "Загружен favicon.ico" -fi - -# Фоновые изображения -for bg_file in "./static/images/bg-"*.jpg "./static/images/DESKTOP_NEW_1.jpg"; do - if [ -f "$bg_file" ]; then - basename_file=$(basename "$bg_file") - aws s3 cp "$bg_file" "s3://$S3_BUCKET/images/$basename_file" \ - --content-type "image/jpeg" \ - --cache-control "max-age=31536000" - log "Загружен фоновый файл: $basename_file" - fi -done - -# Настройка публичного доступа для чтения -log "Настройка публичного доступа..." -aws s3api put-object-acl --bucket "$S3_BUCKET" --key "images/" --acl public-read || true - -# Применение политики bucket для публичного чтения -cat > "$TEMP_DIR/bucket-policy.json" << EOF -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::$S3_BUCKET/images/*" - } - ] -} -EOF - -aws s3api put-bucket-policy --bucket "$S3_BUCKET" --policy file://"$TEMP_DIR/bucket-policy.json" - -# Статистика загрузки -log "Загрузка завершена!" -log "Загружено файлов: $uploaded_count" -log "Ошибок: $error_count" -log "Общий размер: $(du -sh "$LOCAL_IMAGES_DIR" | cut -f1)" - -# Проверка нескольких случайных файлов -log "Проверка доступности загруженных файлов..." -sample_files=($(find "$LOCAL_IMAGES_DIR" -type f -name "*.jpg" | head -3)) -for file in "${sample_files[@]}"; do - relative_path="${file#$LOCAL_IMAGES_DIR/}" - s3_url="$S3_BASE_URL/images/$relative_path" - - if curl -I "$s3_url" &> /dev/null; then - log "✓ Файл доступен: $s3_url" - else - error_log "✗ Файл недоступен: $s3_url" - fi -done - -log "Скрипт 1 завершен. Теперь запустите: ./2-update-content.sh" \ No newline at end of file diff --git a/migration-s3/2-update-content.sh b/migration-s3/2-update-content.sh deleted file mode 100755 index 02186e3..0000000 --- a/migration-s3/2-update-content.sh +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/bash - -# Скрипт для обновления ссылок на изображения в контенте -# Заменяет все ссылки /images/ на S3 URLs - -set -e # Остановка при ошибке - -# Загрузка конфигурации -source "$(dirname "$0")/config.sh" - -log "Начало обновления ссылок в контенте..." - -# Функция для обновления файла -update_file() { - local file_path="$1" - local backup_path="$BACKUP_DIR/$(basename "$file_path").backup" - - # Создание backup - cp "$file_path" "$backup_path" - - # Временный файл для изменений - local temp_file="$TEMP_DIR/$(basename "$file_path").tmp" - - # Замена ссылок в файле - sed -E "s|['\"]/?images/([^'\"]*)['\"]|\"$S3_BASE_URL/images/\1\"|g" "$file_path" > "$temp_file" - - # Проверка на изменения - if ! diff -q "$file_path" "$temp_file" &> /dev/null; then - mv "$temp_file" "$file_path" - log "Обновлен файл: $file_path" - return 0 - else - rm "$temp_file" - return 1 - fi -} - -# Счетчики -updated_files=0 -total_files=0 - -# Обновление всех markdown файлов в content/ -log "Обновление markdown файлов..." -while IFS= read -r -d '' file; do - total_files=$((total_files + 1)) - if update_file "$file"; then - updated_files=$((updated_files + 1)) - fi -done < <(find "$CONTENT_DIR" -name "*.md" -print0) - -# Обновление конфигурационных файлов -log "Обновление конфигурационных файлов..." - -# config.toml -if [ -f "$CONFIG_FILE" ]; then - total_files=$((total_files + 1)) - if update_file "$CONFIG_FILE"; then - updated_files=$((updated_files + 1)) - fi -fi - -# config-prod.toml -if [ -f "$CONFIG_PROD_FILE" ]; then - total_files=$((total_files + 1)) - if update_file "$CONFIG_PROD_FILE"; then - updated_files=$((updated_files + 1)) - fi -fi - -# Обновление HTML файлов в layouts (если есть хардкод) -log "Проверка HTML файлов в layouts..." -while IFS= read -r -d '' file; do - total_files=$((total_files + 1)) - if update_file "$file"; then - updated_files=$((updated_files + 1)) - fi -done < <(find "./layouts" -name "*.html" -print0 2>/dev/null || true) - -# Обновление CSS файлов (фоновые изображения) -log "Обновление CSS файлов..." -while IFS= read -r -d '' file; do - total_files=$((total_files + 1)) - # Специальная обработка для CSS - замена url() функций - backup_path="$BACKUP_DIR/$(basename "$file").backup" - cp "$file" "$backup_path" - - temp_file="$TEMP_DIR/$(basename "$file").tmp" - sed -E "s|url\(['\"]?/?images/([^'\"]*)['\"]?\)|url(\"$S3_BASE_URL/images/\1\")|g" "$file" > "$temp_file" - - if ! diff -q "$file" "$temp_file" &> /dev/null; then - mv "$temp_file" "$file" - updated_files=$((updated_files + 1)) - log "Обновлен CSS файл: $file" - else - rm "$temp_file" - fi -done < <(find "./static/css" "./themes" -name "*.css" -print0 2>/dev/null || true) - -# Создание файла с отчетом об изменениях -report_file="$TEMP_DIR/content-changes-report.txt" -log "Создание отчета об изменениях..." - -{ - echo "Отчет об обновлении контента" - echo "============================" - echo "Дата: $(date)" - echo "Обновлено файлов: $updated_files из $total_files" - echo "S3 Base URL: $S3_BASE_URL" - echo "" - echo "Измененные файлы:" - echo "-----------------" - - # Список измененных файлов - find "$BACKUP_DIR" -name "*.backup" | while read -r backup; do - original="${backup%.backup}" - if [ -f "$original" ]; then - echo "- $original" - fi - done - - echo "" - echo "Примеры замен:" - echo "-------------" - echo "Было: 'images/photo.jpg'" - echo "Стало: '$S3_BASE_URL/images/photo.jpg'" - echo "" - echo "Было: ![Alt](/images/photo.jpg)" - echo "Стало: ![Alt]($S3_BASE_URL/images/photo.jpg)" - -} > "$report_file" - -log "Отчет создан: $report_file" - -# Проверка синтаксиса Hugo (если доступно) -if command -v hugo &> /dev/null; then - log "Проверка синтаксиса Hugo..." - if hugo --verbose --printPathWarnings 2>&1 | grep -i error; then - error_log "Найдены ошибки в Hugo конфигурации" - else - log "Hugo синтаксис корректен" - fi -fi - -# Финальная статистика -log "Обновление контента завершено!" -log "Проверено файлов: $total_files" -log "Обновлено файлов: $updated_files" -log "Backup создан в: $BACKUP_DIR" - -# Создание файла для отката изменений -rollback_script="$TEMP_DIR/rollback-content.sh" -{ - echo "#!/bin/bash" - echo "# Скрипт для отката изменений контента" - echo "set -e" - echo "" - find "$BACKUP_DIR" -name "*.backup" | while read -r backup; do - original="${backup%.backup}" - if [ -f "$original" ]; then - echo "cp '$backup' '$original'" - fi - done - echo "" - echo "echo 'Откат завершен'" -} > "$rollback_script" -chmod +x "$rollback_script" - -log "Скрипт отката создан: $rollback_script" -log "Скрипт 2 завершен. Теперь запустите: ./3-update-config.sh" \ No newline at end of file diff --git a/migration-s3/3-update-config.sh b/migration-s3/3-update-config.sh deleted file mode 100755 index 894f2a6..0000000 --- a/migration-s3/3-update-config.sh +++ /dev/null @@ -1,301 +0,0 @@ -#!/bin/bash - -# Скрипт для обновления конфигурации Hugo для работы с S3 -# Обновляет shortcodes и настройки для работы с внешними изображениями - -set -e # Остановка при ошибке - -# Загрузка конфигурации -source "$(dirname "$0")/config.sh" - -log "Начало обновления конфигурации Hugo..." - -# Обновление shortcode figure.html -log "Обновление shortcode figure.html..." -figure_shortcode="./layouts/shortcodes/figure.html" - -if [ -f "$figure_shortcode" ]; then - # Создание backup - cp "$figure_shortcode" "$BACKUP_DIR/figure.html.backup" - - # Создание обновленного shortcode - cat > "$figure_shortcode" << 'EOF' - - -{{- if not ($.Page.Scratch.Get "figurecount") }}{{ end }} -{{- $.Page.Scratch.Add "figurecount" 1 -}} - - -{{- $thumb := .Get "src" | default (printf "%s." (.Get "thumb") | replace (.Get "link") ".") }} - - -{{- $thumbURL := "" }} -{{- if or (hasPrefix $thumb "http://") (hasPrefix $thumb "https://") }} - {{- $thumbURL = $thumb }} -{{- else }} - {{- $thumbURL = $thumb | relURL }} -{{- end }} - -{{- $linkURL := "" }} -{{- $link := .Get "link" | default (.Get "src") }} -{{- if or (hasPrefix $link "http://") (hasPrefix $link "https://") }} - {{- $linkURL = $link }} -{{- else }} - {{- $linkURL = $link | relURL }} -{{- end }} - -
-
-
- -
- {{ with $linkURL }}{{ end }} - {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr")}} -
- {{- with .Get "title" }}

{{.}}

{{ end }} - {{- if or (.Get "caption") (.Get "attr")}} -

- {{- .Get "caption" -}} - {{- with .Get "attrlink"}}{{ .Get "attr" }}{{ else }}{{ .Get "attr"}}{{ end -}} -

- {{- end }} -
- {{- end }} -
-
-EOF - - log "Обновлен figure.html shortcode" -else - log "figure.html shortcode не найден, создаем новый..." - mkdir -p "./layouts/shortcodes" - # Создать тот же код что выше -fi - -# Обновление shortcode gallery.html -log "Обновление shortcode gallery.html..." -gallery_shortcode="./layouts/shortcodes/gallery.html" - -if [ -f "$gallery_shortcode" ]; then - # Создание backup - cp "$gallery_shortcode" "$BACKUP_DIR/gallery.html.backup" - - # Создание обновленного shortcode - cat > "$gallery_shortcode" << 'EOF' - - -{{- if not ($.Page.Scratch.Get "figurecount") }}{{ end }} -{{- $.Page.Scratch.Add "figurecount" 1 }} - - - - -{{ partial "back-to-top.html" . }} -EOF - - log "Обновлен gallery.html shortcode" -fi - -# Обновление конфигурации Hugo для оптимизации внешних изображений -log "Обновление конфигурации Hugo..." - -# Добавление настроек для работы с внешними изображениями -config_addition=" -# S3 Images Configuration -[markup] - [markup.goldmark] - [markup.goldmark.renderer] - unsafe = true - -[security] - [security.http] - urls = ['.*'] - -[caches] - [caches.images] - dir = ':cacheDir/_gen' - maxAge = '24h' - -[imaging] - quality = 85 - resampleFilter = 'lanczos' - anchor = 'smart' -" - -# Добавление в config.toml если еще не добавлено -if ! grep -q "S3 Images Configuration" "$CONFIG_FILE"; then - echo "$config_addition" >> "$CONFIG_FILE" - log "Добавлены настройки S3 в config.toml" -fi - -# Добавление в config-prod.toml если существует -if [ -f "$CONFIG_PROD_FILE" ] && ! grep -q "S3 Images Configuration" "$CONFIG_PROD_FILE"; then - echo "$config_addition" >> "$CONFIG_PROD_FILE" - log "Добавлены настройки S3 в config-prod.toml" -fi - -# Создание партиала для предзагрузки изображений (опционально) -preload_partial="./layouts/partials/preload-images.html" -mkdir -p "./layouts/partials" - -cat > "$preload_partial" << 'EOF' - -{{- with .Params.image }} - -{{- end }} - -{{- if .IsHome }} - - -{{- end }} -EOF - -log "Создан партиал для предзагрузки изображений: $preload_partial" - -# Обновление head.html для добавления предзагрузки -head_file="./layouts/partials/htmlhead.html" -if [ -f "$head_file" ] && ! grep -q "preload-images" "$head_file"; then - # Создание backup - cp "$head_file" "$BACKUP_DIR/htmlhead.html.backup" - - # Добавление предзагрузки перед закрывающим тегом head - sed -i.bak 's||{{ partial "preload-images.html" . }}\n|' "$head_file" - rm "$head_file.bak" - log "Добавлена предзагрузка изображений в htmlhead.html" -fi - -# Создание CSS для ускорения загрузки изображений -image_optimization_css="./static/css/image-optimization.css" -mkdir -p "./static/css" - -cat > "$image_optimization_css" << 'EOF' -/* Image optimization styles for S3 images */ - -/* Lazy loading fallback */ -img[loading="lazy"] { - opacity: 0; - transition: opacity 0.3s; -} - -img[loading="lazy"].loaded { - opacity: 1; -} - -/* Improve gallery performance */ -.gallery .img { - background-size: cover; - background-position: center; - background-repeat: no-repeat; - will-change: transform; -} - -/* Responsive images */ -img { - max-width: 100%; - height: auto; - display: block; -} - -/* Placeholder while loading */ -.gallery .img::before { - content: ""; - display: block; - background: linear-gradient(90deg, #f0f0f0 25%, transparent 25%, transparent 50%, #f0f0f0 50%, #f0f0f0 75%, transparent 75%, transparent); - background-size: 20px 20px; - animation: loading 1s linear infinite; -} - -@keyframes loading { - 0% { background-position: 0 0; } - 100% { background-position: 20px 0; } -} - -/* Hide placeholder when image loads */ -.gallery .img img { - position: relative; - z-index: 1; -} -EOF - -log "Создан CSS для оптимизации изображений: $image_optimization_css" - -# Создание файла для проверки работы S3 -test_page="./content/test-s3-images.md" -cat > "$test_page" << EOF -+++ -title = "Тест S3 изображений" -date = $(date -u +"%Y-%m-%dT%H:%M:%SZ") -draft = true -+++ - -# Тест загрузки изображений из S3 - -Эта страница для тестирования работы изображений после миграции на S3. - -## Тестовые изображения - -{{< figure src="$S3_BASE_URL/images/test-image.jpg" alt="Тестовое изображение" >}} - -## Проверка работы галереи - -{{< gallery >}} -{{< figure src="$S3_BASE_URL/images/test-image-1.jpg" >}} -{{< figure src="$S3_BASE_URL/images/test-image-2.jpg" >}} -{{< /gallery >}} - ---- -*Эта страница будет удалена после успешного тестирования* -EOF - -log "Создана тестовая страница: $test_page" - -# Финальная статистика -log "Обновление конфигурации Hugo завершено!" -log "Обновленные файлы:" -log "- layouts/shortcodes/figure.html" -log "- layouts/shortcodes/gallery.html" -log "- config.toml" -log "- layouts/partials/preload-images.html" -log "- static/css/image-optimization.css" -log "- content/test-s3-images.md (для тестирования)" - -log "Скрипт 3 завершен. Теперь запустите: ./4-verify-migration.sh" \ No newline at end of file diff --git a/migration-s3/4-verify-migration.sh b/migration-s3/4-verify-migration.sh deleted file mode 100755 index ef359a4..0000000 --- a/migration-s3/4-verify-migration.sh +++ /dev/null @@ -1,254 +0,0 @@ -#!/bin/bash - -# Скрипт для проверки успешности миграции на S3 -# Проверяет доступность изображений, работу сайта и целостность ссылок - -set -e # Остановка при ошибке - -# Загрузка конфигурации -source "$(dirname "$0")/config.sh" - -log "Начало проверки миграции на S3..." - -# Счетчики для статистики -total_checks=0 -successful_checks=0 -failed_checks=0 -warnings=0 - -# Функция для проверки доступности URL -check_url() { - local url="$1" - local description="$2" - - total_checks=$((total_checks + 1)) - - if curl -s -I "$url" | grep -q "200 OK"; then - successful_checks=$((successful_checks + 1)) - log "✓ $description: $url" - return 0 - else - failed_checks=$((failed_checks + 1)) - error_log "✗ $description: $url" - return 1 - fi -} - -# 1. Проверка доступности S3 bucket -log "1. Проверка доступности S3 bucket..." -if aws s3 ls "s3://$S3_BUCKET/images/" &> /dev/null; then - log "✓ S3 bucket доступен" -else - error_log "✗ S3 bucket недоступен" - exit 1 -fi - -# 2. Проверка случайных изображений из S3 -log "2. Проверка доступности изображений в S3..." - -# Получение списка изображений из S3 -s3_images=$(aws s3 ls "s3://$S3_BUCKET/images/" --recursive | grep -E "\.(jpg|jpeg|png|gif|webp)$" | awk '{print $4}' | head -10) - -if [ -z "$s3_images" ]; then - error_log "Изображения не найдены в S3" - exit 1 -fi - -# Проверка случайных изображений -echo "$s3_images" | while read -r s3_key; do - if [ -n "$s3_key" ]; then - image_url="$S3_BASE_URL/$s3_key" - check_url "$image_url" "Изображение в S3" - fi -done - -# 3. Проверка специальных файлов -log "3. Проверка специальных файлов..." -special_files=( - "images/favicon.ico" - "images/DESKTOP_NEW_1.jpg" - "images/bg-winter.jpg" - "images/bg-spring.jpg" - "images/bg-summer.jpg" - "images/bg-autumn.jpg" -) - -for file in "${special_files[@]}"; do - special_url="$S3_BASE_URL/$file" - check_url "$special_url" "Специальный файл" || warnings=$((warnings + 1)) -done - -# 4. Анализ контента на наличие старых ссылок -log "4. Проверка контента на наличие старых ссылок..." - -old_links_count=0 -if [ -d "$CONTENT_DIR" ]; then - # Поиск ссылок, начинающихся с /images/ (старые локальные ссылки) - old_links=$(grep -r "images/" "$CONTENT_DIR" | grep -v "$S3_BASE_URL" | grep -E "\.(jpg|jpeg|png|gif|webp)" || true) - - if [ -n "$old_links" ]; then - old_links_count=$(echo "$old_links" | wc -l) - warnings=$((warnings + old_links_count)) - error_log "Найдено $old_links_count старых ссылок на изображения:" - echo "$old_links" | head -5 | while read -r link; do - error_log " $link" - done - else - log "✓ Старые ссылки не найдены" - fi -fi - -# 5. Проверка синтаксиса Hugo -log "5. Проверка синтаксиса Hugo..." -if command -v hugo &> /dev/null; then - hugo_output=$(hugo --verbose --printPathWarnings 2>&1 || true) - - if echo "$hugo_output" | grep -qi "error"; then - error_log "Hugo сообщает об ошибках:" - echo "$hugo_output" | grep -i "error" | head -3 - warnings=$((warnings + 1)) - else - log "✓ Hugo синтаксис корректен" - fi -else - log "⚠ Hugo не установлен, пропуск проверки синтаксиса" - warnings=$((warnings + 1)) -fi - -# 6. Проверка размера репозитория -log "6. Проверка размера репозитория..." -repo_size_mb=$(du -sm . | cut -f1) -if [ "$repo_size_mb" -lt 1000 ]; then - log "✓ Размер репозитория: ${repo_size_mb}MB (хорошо)" -else - error_log "⚠ Размер репозитория: ${repo_size_mb}MB (все еще большой)" - warnings=$((warnings + 1)) -fi - -# 7. Проверка отсутствия дубликатов изображений -log "7. Проверка на дубликаты изображений..." -if [ -d "./public/images" ] || [ -d "./static/images" ]; then - error_log "⚠ Локальные изображения все еще присутствуют" - error_log " Рекомендуется удалить после успешной проверки:" - error_log " - ./public/images" - error_log " - ./static/images" - warnings=$((warnings + 1)) -else - log "✓ Локальные изображения удалены" -fi - -# 8. Тестирование с помощью Hugo server (опционально) -log "8. Тестирование локального сервера Hugo..." -if command -v hugo &> /dev/null; then - # Запуск Hugo сервера в фоне на короткое время - hugo server --bind 127.0.0.1 --port 1313 --disableFastRender & - hugo_pid=$! - - # Ждем запуска сервера - sleep 5 - - # Проверка доступности главной страницы - if curl -s "http://localhost:1313/" > /dev/null; then - log "✓ Локальный сервер Hugo работает" - else - error_log "✗ Локальный сервер Hugo недоступен" - warnings=$((warnings + 1)) - fi - - # Остановка сервера - kill $hugo_pid 2>/dev/null || true - wait $hugo_pid 2>/dev/null || true -else - log "⚠ Hugo не установлен, пропуск тестирования сервера" -fi - -# 9. Создание отчета -log "9. Создание отчета..." -report_file="./migration-s3/migration-verification-report.md" - -{ - echo "# Отчет о проверке миграции на S3" - echo "" - echo "**Дата проверки:** $(date)" - echo "**S3 Bucket:** $S3_BUCKET" - echo "**CDN URL:** $S3_BASE_URL" - echo "" - echo "## Результаты проверки" - echo "" - echo "- ✅ **Успешных проверок:** $successful_checks" - echo "- ❌ **Неудачных проверок:** $failed_checks" - echo "- ⚠️ **Предупреждений:** $warnings" - echo "- 📊 **Общее количество проверок:** $total_checks" - echo "" - - if [ $failed_checks -eq 0 ] && [ $warnings -eq 0 ]; then - echo "## ✅ Статус: УСПЕШНО" - echo "" - echo "Миграция прошла успешно! Все изображения доступны через S3." - elif [ $failed_checks -eq 0 ] && [ $warnings -gt 0 ]; then - echo "## ⚠️ Статус: УСПЕШНО С ПРЕДУПРЕЖДЕНИЯМИ" - echo "" - echo "Миграция прошла в целом успешно, но есть предупреждения, которые стоит рассмотреть." - else - echo "## ❌ Статус: ТРЕБУЕТСЯ ВНИМАНИЕ" - echo "" - echo "Обнаружены проблемы, которые требуют исправления." - fi - - echo "" - echo "## Рекомендации" - echo "" - - if [ -d "./public/images" ] || [ -d "./static/images" ]; then - echo "1. **Удалите локальные изображения** после подтверждения работы:" - echo " \`\`\`bash" - echo " rm -rf ./public/images" - echo " rm -rf ./static/images" - echo " \`\`\`" - echo "" - fi - - echo "2. **Настройте CloudFront CDN** для улучшения производительности" - echo "3. **Настройте мониторинг** доступности изображений" - echo "4. **Регулярно проверяйте** работу ссылок" - echo "" - echo "## Следующие шаги" - echo "" - echo "1. Протестируйте сайт в браузере" - echo "2. Проверьте работу галерей и изображений" - echo "3. Убедитесь в корректности отображения на разных устройствах" - echo "4. Используйте \`add-new-photos.sh\` для добавления новых изображений" - echo "" - echo "---" - echo "*Отчет создан автоматически скриптом 4-verify-migration.sh*" - -} > "$report_file" - -# 10. Финальная статистика -log "Проверка миграции завершена!" -log "Отчет создан: $report_file" -log "" -log "=== ИТОГОВАЯ СТАТИСТИКА ===" -log "Успешных проверок: $successful_checks" -log "Неудачных проверок: $failed_checks" -log "Предупреждений: $warnings" -log "Общее количество проверок: $total_checks" -log "" - -if [ $failed_checks -eq 0 ] && [ $warnings -eq 0 ]; then - log "🎉 МИГРАЦИЯ УСПЕШНА! Все изображения работают через S3." - log "Теперь вы можете использовать add-new-photos.sh для добавления новых изображений." -elif [ $failed_checks -eq 0 ]; then - log "✅ МИГРАЦИЯ ЗАВЕРШЕНА с предупреждениями. Проверьте отчет." -else - log "❌ ОБНАРУЖЕНЫ ПРОБЛЕМЫ. Необходимо исправить ошибки." - exit 1 -fi - -# Показать путь к скриптам для дальнейшего использования -log "" -log "Для добавления новых изображений используйте:" -log " ./migration-s3/add-new-photos.sh /path/to/new/photos" -log "" -log "Для отката изменений (если нужно):" -log " ./migration-temp/rollback-content.sh" \ No newline at end of file diff --git a/migration-s3/README.md b/migration-s3/README.md deleted file mode 100644 index ec9279c..0000000 --- a/migration-s3/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Миграция изображений на S3 - -## Что это дает простыми словами - -**До миграции:** -- Каждое новое фото нужно добавлять в GitHub -- Загрузка сайта медленная из-за больших изображений -- Каждое изменение фото требует обновления всего сайта -- Репозиторий раздувается от фотографий (сейчас 2.3GB) - -**После миграции на S3:** -- Фото загружаются прямо в облачное хранилище Amazon S3 -- Сайт загружается быстрее через CDN (сеть доставки контента) -- Новые фото появляются на сайте мгновенно без обновления кода -- Репозиторий уменьшается в 10 раз (с 3GB до 300MB) - -## Как это работает - -1. **Все фото перемещаются в S3** - специальное облачное хранилище для файлов -2. **Настраивается CDN** - сеть серверов по всему миру для быстрой доставки -3. **Сайт ссылается на S3** - вместо локальных файлов используются ссылки на облако -4. **Автоматизация** - скрипты для быстрой загрузки новых фото - -## Преимущества для вас - -✅ **Быстрая загрузка фото**: Загрузил в S3 → фото сразу на сайте -✅ **Быстрый сайт**: Изображения загружаются через CDN -✅ **Меньше проблем с Git**: Репозиторий легкий и быстрый -✅ **Автоматизация**: Скрипты делают всю работу за вас -✅ **Надежность**: S3 - самое надежное хранилище в мире - -## Файлы в этой папке - -- `1-upload-to-s3.sh` - Загружает все изображения в S3 -- `2-update-content.sh` - Обновляет ссылки в постах -- `3-update-config.sh` - Обновляет конфигурацию Hugo -- `4-verify-migration.sh` - Проверяет, что все работает -- `add-new-photos.sh` - Быстрая загрузка новых фото (для постоянного использования) - -## Как использовать - -1. Настройте AWS CLI и S3 bucket -2. Запустите скрипты по порядку (1 → 2 → 3 → 4) -3. В будущем используйте `add-new-photos.sh` для новых изображений - -**Результат**: Сайт работает так же, но быстрее и без больших файлов в Git! \ No newline at end of file diff --git a/migration-s3/add-new-photos.sh b/migration-s3/add-new-photos.sh deleted file mode 100755 index 50c7e51..0000000 --- a/migration-s3/add-new-photos.sh +++ /dev/null @@ -1,247 +0,0 @@ -#!/bin/bash - -# Скрипт для быстрого добавления новых фотографий в S3 -# Использовать ПОСЛЕ миграции для добавления новых изображений - -set -e # Остановка при ошибке - -# Загрузка конфигурации -source "$(dirname "$0")/config.sh" - -# Проверка параметров -if [ $# -eq 0 ]; then - echo "Использование: $0 <путь_к_изображениям> [описание]" - echo "" - echo "Примеры:" - echo " $0 /path/to/new/photos" - echo " $0 /path/to/new/photos \"Фото из поездки в Сочи\"" - echo " $0 ./new-photos" - echo "" - echo "Скрипт загружает изображения в S3 и создает готовый код для вставки в пост." - exit 1 -fi - -photos_path="$1" -description="${2:-Новые фотографии}" - -# Проверка существования директории -if [ ! -d "$photos_path" ]; then - error_log "Директория не найдена: $photos_path" - exit 1 -fi - -log "Начало загрузки новых фотографий..." -log "Путь к фото: $photos_path" -log "Описание: $description" - -# Подсчет изображений -image_files=$(find "$photos_path" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.gif" -o -iname "*.webp" \) | sort) -total_images=$(echo "$image_files" | wc -l) - -if [ $total_images -eq 0 ]; then - error_log "Изображения не найдены в $photos_path" - exit 1 -fi - -log "Найдено $total_images изображений для загрузки" - -# Создание уникального префикса для этой партии фото -batch_prefix="batch_$(date +%Y%m%d_%H%M%S)" -uploaded_files=() -failed_files=() - -# Функция для загрузки одного файла -upload_single_file() { - local file_path="$1" - local filename=$(basename "$file_path") - local extension="${filename##*.}" - - # Создание уникального имени файла - local base_name="${filename%.*}" - local s3_filename="${base_name}_${batch_prefix}.${extension}" - local s3_key="images/$s3_filename" - - # Определение MIME типа - local mime_type="" - case "$extension" in - jpg|jpeg) mime_type="image/jpeg" ;; - png) mime_type="image/png" ;; - gif) mime_type="image/gif" ;; - webp) mime_type="image/webp" ;; - *) mime_type="application/octet-stream" ;; - esac - - # Загрузка файла - if aws s3 cp "$file_path" "s3://$S3_BUCKET/$s3_key" \ - --content-type "$mime_type" \ - --cache-control "max-age=31536000" \ - --metadata-directive REPLACE; then - - uploaded_files+=("$s3_filename") - log "✓ Загружено: $filename → $s3_filename" - return 0 - else - failed_files+=("$filename") - error_log "✗ Ошибка загрузки: $filename" - return 1 - fi -} - -# Загрузка всех файлов -log "Загрузка изображений в S3..." -uploaded_count=0 -failed_count=0 - -echo "$image_files" | while read -r file; do - if [ -n "$file" ]; then - if upload_single_file "$file"; then - uploaded_count=$((uploaded_count + 1)) - else - failed_count=$((failed_count + 1)) - fi - fi -done - -# Пересчет после загрузки -uploaded_count=${#uploaded_files[@]} -failed_count=${#failed_files[@]} - -log "Загрузка завершена: $uploaded_count успешно, $failed_count ошибок" - -# Проверка загруженных файлов -log "Проверка доступности загруженных файлов..." -accessible_files=() -for filename in "${uploaded_files[@]}"; do - file_url="$S3_BASE_URL/images/$filename" - if curl -s -I "$file_url" | grep -q "200 OK"; then - accessible_files+=("$filename") - log "✓ Доступен: $file_url" - else - error_log "✗ Недоступен: $file_url" - fi -done - -# Создание кода для вставки в пост -if [ ${#accessible_files[@]} -gt 0 ]; then - log "Создание кода для вставки в пост..." - - # Файл с готовым кодом - code_file="./migration-s3/ready-code-$(date +%Y%m%d_%H%M%S).md" - - { - echo "# Готовый код для вставки в пост" - echo "" - echo "**Описание:** $description" - echo "**Дата:** $(date)" - echo "**Загружено изображений:** ${#accessible_files[@]}" - echo "" - echo "## Код для галереи" - echo "" - echo "\`\`\`markdown" - echo "{{< gallery >}}" - for filename in "${accessible_files[@]}"; do - echo "{{< figure src=\"$S3_BASE_URL/images/$filename\" >}}" - done - echo "{{< /gallery >}}" - echo "\`\`\`" - echo "" - echo "## Код для отдельных изображений" - echo "" - echo "\`\`\`markdown" - for filename in "${accessible_files[@]}"; do - echo "![Описание]($S3_BASE_URL/images/$filename)" - done - echo "\`\`\`" - echo "" - echo "## Код с figure shortcode" - echo "" - echo "\`\`\`markdown" - for filename in "${accessible_files[@]}"; do - echo "{{< figure src=\"$S3_BASE_URL/images/$filename\" alt=\"Описание\" >}}" - done - echo "\`\`\`" - echo "" - echo "## Прямые ссылки" - echo "" - for filename in "${accessible_files[@]}"; do - echo "- $S3_BASE_URL/images/$filename" - done - echo "" - echo "---" - echo "*Код создан автоматически скриптом add-new-photos.sh*" - - } > "$code_file" - - log "Готовый код создан: $code_file" - - # Показать краткий пример кода - echo "" - echo "=== ГОТОВЫЙ КОД ДЛЯ ВСТАВКИ ===" - echo "" - echo "{{< gallery >}}" - for filename in "${accessible_files[@]}"; do - echo "{{< figure src=\"$S3_BASE_URL/images/$filename\" >}}" - done - echo "{{< /gallery >}}" - echo "" - echo "Полный код сохранен в: $code_file" -fi - -# Создание отчета -report_file="./migration-s3/upload-report-$(date +%Y%m%d_%H%M%S).txt" -{ - echo "Отчет о загрузке фотографий" - echo "==========================" - echo "Дата: $(date)" - echo "Путь к исходным файлам: $photos_path" - echo "Описание: $description" - echo "Batch prefix: $batch_prefix" - echo "" - echo "Статистика:" - echo "- Найдено изображений: $total_images" - echo "- Загружено успешно: $uploaded_count" - echo "- Ошибок загрузки: $failed_count" - echo "- Доступно через CDN: ${#accessible_files[@]}" - echo "" - echo "Загруженные файлы:" - for filename in "${uploaded_files[@]}"; do - echo "- $filename" - done - echo "" - if [ ${#failed_files[@]} -gt 0 ]; then - echo "Файлы с ошибками:" - for filename in "${failed_files[@]}"; do - echo "- $filename" - done - echo "" - fi - echo "Готовый код в: $code_file" -} > "$report_file" - -# Финальная статистика -log "" -log "=== ИТОГОВАЯ СТАТИСТИКА ===" -log "Найдено изображений: $total_images" -log "Загружено в S3: $uploaded_count" -log "Доступно через CDN: ${#accessible_files[@]}" -log "Ошибок: $failed_count" -log "" - -if [ ${#accessible_files[@]} -gt 0 ]; then - log "🎉 ЗАГРУЗКА ЗАВЕРШЕНА! Новые изображения доступны через S3." - log "Готовый код для вставки в пост: $code_file" -else - error_log "❌ НЕ УДАЛОСЬ ЗАГРУЗИТЬ ИЗОБРАЖЕНИЯ. Проверьте настройки S3." - exit 1 -fi - -# Инструкции для использования -echo "" -echo "=== ИНСТРУКЦИИ ===" -echo "1. Скопируйте код из файла: $code_file" -echo "2. Вставьте код в нужный пост в директории content/post/" -echo "3. Измените alt-тексты и описания по необходимости" -echo "4. Сохраните пост и соберите сайт" -echo "" -echo "Для добавления еще фотографий запустите:" -echo " $0 /path/to/more/photos \"Описание новых фото\"" \ No newline at end of file diff --git a/migration-s3/config.sh b/migration-s3/config.sh deleted file mode 100755 index eb39a65..0000000 --- a/migration-s3/config.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash - -# Конфигурация для миграции на S3 -# Отредактируйте эти переменные под ваши настройки - -# AWS S3 настройки -S3_BUCKET="your-ptp-bucket" # Замените на имя вашего S3 bucket -S3_REGION="us-east-1" # Замените на ваш регион -CDN_URL="https://d1234567890.cloudfront.net" # Замените на URL вашего CloudFront CDN (опционально) - -# Если CDN не настроен, используйте прямой S3 URL -# S3_BASE_URL="https://${S3_BUCKET}.s3.${S3_REGION}.amazonaws.com" -S3_BASE_URL="${CDN_URL}" - -# Локальные пути -LOCAL_IMAGES_DIR="./static/images" -LOCAL_PUBLIC_DIR="./public/images" -CONTENT_DIR="./content" -CONFIG_FILE="./config.toml" -CONFIG_PROD_FILE="./config-prod.toml" - -# Временные файлы -TEMP_DIR="./migration-temp" -BACKUP_DIR="./migration-backup" - -# Лог файлы -LOG_FILE="./migration-s3/migration.log" -ERROR_LOG="./migration-s3/errors.log" - -# Функции для логирования -log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" -} - -error_log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$ERROR_LOG" -} - -# Проверка зависимостей -check_dependencies() { - log "Проверка зависимостей..." - - if ! command -v aws &> /dev/null; then - error_log "AWS CLI не установлен. Установите: https://aws.amazon.com/cli/" - exit 1 - fi - - if ! command -v jq &> /dev/null; then - error_log "jq не установлен. Установите: brew install jq" - exit 1 - fi - - # Проверка AWS credentials - if ! aws sts get-caller-identity &> /dev/null; then - error_log "AWS credentials не настроены. Запустите: aws configure" - exit 1 - fi - - log "Все зависимости установлены" -} - -# Создание директорий -create_dirs() { - mkdir -p "$TEMP_DIR" - mkdir -p "$BACKUP_DIR" - mkdir -p "$(dirname "$LOG_FILE")" - mkdir -p "$(dirname "$ERROR_LOG")" -} - -# Инициализация -init_migration() { - log "Инициализация миграции..." - create_dirs - check_dependencies - - # Создание backup - log "Создание backup..." - cp -r "$CONTENT_DIR" "$BACKUP_DIR/content_backup_$(date +%Y%m%d_%H%M%S)" - cp "$CONFIG_FILE" "$BACKUP_DIR/config_backup_$(date +%Y%m%d_%H%M%S).toml" - - log "Backup создан в $BACKUP_DIR" -} \ No newline at end of file diff --git a/migration-s3/setup-aws.sh b/migration-s3/setup-aws.sh deleted file mode 100755 index 6d90dfa..0000000 --- a/migration-s3/setup-aws.sh +++ /dev/null @@ -1,341 +0,0 @@ -#!/bin/bash - -# Скрипт для настройки AWS S3 и CloudFront для хранения изображений -# Запустить ПЕРЕД миграцией для настройки инфраструктуры - -set -e # Остановка при ошибке - -# Загрузка конфигурации -source "$(dirname "$0")/config.sh" - -log "Начало настройки AWS инфраструктуры..." - -# Проверка зависимостей -if ! command -v aws &> /dev/null; then - error_log "AWS CLI не установлен. Установите: https://aws.amazon.com/cli/" - exit 1 -fi - -if ! aws sts get-caller-identity &> /dev/null; then - error_log "AWS credentials не настроены. Запустите: aws configure" - exit 1 -fi - -# Получение информации об аккаунте -aws_account_id=$(aws sts get-caller-identity --query 'Account' --output text) -aws_region=$(aws configure get region || echo "us-east-1") -log "AWS Account ID: $aws_account_id" -log "AWS Region: $aws_region" - -# 1. Создание S3 bucket -log "1. Создание S3 bucket..." - -if aws s3 ls "s3://$S3_BUCKET" &> /dev/null; then - log "S3 bucket '$S3_BUCKET' уже существует" -else - log "Создание S3 bucket: $S3_BUCKET" - - if [ "$aws_region" = "us-east-1" ]; then - aws s3 mb "s3://$S3_BUCKET" - else - aws s3 mb "s3://$S3_BUCKET" --region "$aws_region" - fi - - log "S3 bucket создан" -fi - -# 2. Настройка политики bucket для публичного чтения -log "2. Настройка политики bucket..." - -# Отключение блокировки публичного доступа -aws s3api put-public-access-block \ - --bucket "$S3_BUCKET" \ - --public-access-block-configuration \ - "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false" - -# Политика для публичного чтения изображений -cat > "$TEMP_DIR/bucket-policy.json" << EOF -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::$S3_BUCKET/images/*" - } - ] -} -EOF - -aws s3api put-bucket-policy --bucket "$S3_BUCKET" --policy file://"$TEMP_DIR/bucket-policy.json" -log "Политика bucket настроена" - -# 3. Настройка CORS для браузерного доступа -log "3. Настройка CORS..." - -cat > "$TEMP_DIR/cors-config.json" << EOF -{ - "CORSRules": [ - { - "AllowedOrigins": ["*"], - "AllowedMethods": ["GET", "HEAD"], - "AllowedHeaders": ["*"], - "MaxAgeSeconds": 3000 - } - ] -} -EOF - -aws s3api put-bucket-cors --bucket "$S3_BUCKET" --cors-configuration file://"$TEMP_DIR/cors-config.json" -log "CORS настроен" - -# 4. Настройка CloudFront CDN (опционально) -log "4. Настройка CloudFront CDN..." - -# Проверка существования дистрибуции -existing_distribution=$(aws cloudfront list-distributions --query "DistributionList.Items[?Origins.Items[0].DomainName=='$S3_BUCKET.s3.amazonaws.com'].Id" --output text || echo "") - -if [ -n "$existing_distribution" ]; then - log "CloudFront дистрибуция уже существует: $existing_distribution" - cdn_domain=$(aws cloudfront get-distribution --id "$existing_distribution" --query "Distribution.DomainName" --output text) - log "CDN домен: $cdn_domain" -else - log "Создание CloudFront дистрибуции..." - - # Создание конфигурации CloudFront - cat > "$TEMP_DIR/cloudfront-config.json" << EOF -{ - "CallerReference": "ptp-images-$(date +%s)", - "Comment": "CDN for PTP website images", - "DefaultCacheBehavior": { - "TargetOriginId": "S3-$S3_BUCKET", - "ViewerProtocolPolicy": "redirect-to-https", - "TrustedSigners": { - "Enabled": false, - "Quantity": 0 - }, - "ForwardedValues": { - "QueryString": false, - "Cookies": { - "Forward": "none" - } - }, - "MinTTL": 0, - "DefaultTTL": 86400, - "MaxTTL": 31536000, - "Compress": true - }, - "Origins": { - "Quantity": 1, - "Items": [ - { - "Id": "S3-$S3_BUCKET", - "DomainName": "$S3_BUCKET.s3.amazonaws.com", - "S3OriginConfig": { - "OriginAccessIdentity": "" - } - } - ] - }, - "Enabled": true, - "PriceClass": "PriceClass_100" -} -EOF - - # Создание дистрибуции - distribution_id=$(aws cloudfront create-distribution --distribution-config file://"$TEMP_DIR/cloudfront-config.json" --query "Distribution.Id" --output text) - - log "CloudFront дистрибуция создана: $distribution_id" - log "Ожидание развертывания CloudFront (это может занять несколько минут)..." - - # Ожидание развертывания - aws cloudfront wait distribution-deployed --id "$distribution_id" - - cdn_domain=$(aws cloudfront get-distribution --id "$distribution_id" --query "Distribution.DomainName" --output text) - log "CDN домен: $cdn_domain" - - # Обновление конфигурации с CDN URL - cdn_url="https://$cdn_domain" - sed -i.bak "s|CDN_URL=.*|CDN_URL=\"$cdn_url\"|" "$(dirname "$0")/config.sh" - sed -i.bak "s|S3_BASE_URL=.*|S3_BASE_URL=\"$cdn_url\"|" "$(dirname "$0")/config.sh" - - log "Конфигурация обновлена с CDN URL: $cdn_url" -fi - -# 5. Создание IAM пользователя для загрузки (опционально) -log "5. Создание IAM пользователя для загрузки изображений..." - -iam_user="ptp-images-uploader" -if aws iam get-user --user-name "$iam_user" &> /dev/null; then - log "IAM пользователь '$iam_user' уже существует" -else - log "Создание IAM пользователя: $iam_user" - aws iam create-user --user-name "$iam_user" - - # Политика для загрузки в S3 - cat > "$TEMP_DIR/iam-policy.json" << EOF -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:PutObject", - "s3:PutObjectAcl", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": "arn:aws:s3:::$S3_BUCKET/images/*" - }, - { - "Effect": "Allow", - "Action": [ - "s3:ListBucket" - ], - "Resource": "arn:aws:s3:::$S3_BUCKET" - } - ] -} -EOF - - # Создание политики - policy_arn=$(aws iam create-policy --policy-name "PTPImagesUpload" --policy-document file://"$TEMP_DIR/iam-policy.json" --query "Policy.Arn" --output text) - - # Присвоение политики пользователю - aws iam attach-user-policy --user-name "$iam_user" --policy-arn "$policy_arn" - - log "IAM пользователь создан и политика назначена" -fi - -# 6. Создание тестового изображения -log "6. Создание тестового изображения..." - -# Создание простого тестового изображения (SVG) -cat > "$TEMP_DIR/test-image.svg" << 'EOF' - - - - Test Image for PTP S3 Migration - - - This image confirms S3 setup is working - - -EOF - -# Загрузка тестового изображения -aws s3 cp "$TEMP_DIR/test-image.svg" "s3://$S3_BUCKET/images/test-image.svg" \ - --content-type "image/svg+xml" \ - --cache-control "max-age=3600" - -log "Тестовое изображение загружено" - -# 7. Проверка работы -log "7. Проверка работы настроенной инфраструктуры..." - -# Проверка доступности через S3 -s3_url="https://$S3_BUCKET.s3.amazonaws.com/images/test-image.svg" -if curl -s -I "$s3_url" | grep -q "200 OK"; then - log "✓ S3 доступен: $s3_url" -else - error_log "✗ S3 недоступен: $s3_url" -fi - -# Проверка доступности через CDN (если настроен) -if [ -n "$cdn_domain" ]; then - cdn_url="https://$cdn_domain/images/test-image.svg" - if curl -s -I "$cdn_url" | grep -q "200 OK"; then - log "✓ CDN доступен: $cdn_url" - else - log "⚠ CDN пока недоступен (может потребоваться время): $cdn_url" - fi -fi - -# 8. Создание отчета о настройке -setup_report="./migration-s3/aws-setup-report.md" -{ - echo "# Отчет о настройке AWS для PTP Images" - echo "" - echo "**Дата настройки:** $(date)" - echo "**AWS Account ID:** $aws_account_id" - echo "**AWS Region:** $aws_region" - echo "" - echo "## Созданные ресурсы" - echo "" - echo "### S3 Bucket" - echo "- **Имя:** $S3_BUCKET" - echo "- **Регион:** $aws_region" - echo "- **URL:** https://$S3_BUCKET.s3.amazonaws.com" - echo "- **Публичный доступ:** Только для папки /images/" - echo "" - - if [ -n "$cdn_domain" ]; then - echo "### CloudFront CDN" - echo "- **Домен:** $cdn_domain" - echo "- **URL:** https://$cdn_domain" - echo "- **Статус:** Активен" - echo "" - fi - - echo "### IAM пользователь" - echo "- **Имя:** $iam_user" - echo "- **Права:** Загрузка в /images/" - echo "" - echo "## Тестирование" - echo "" - echo "Тестовое изображение доступно по адресу:" - echo "- S3: $s3_url" - - if [ -n "$cdn_domain" ]; then - echo "- CDN: https://$cdn_domain/images/test-image.svg" - fi - - echo "" - echo "## Следующие шаги" - echo "" - echo "1. Убедитесь, что тестовое изображение доступно" - echo "2. Обновите переменные в config.sh при необходимости" - echo "3. Запустите миграцию: ./1-upload-to-s3.sh" - echo "" - echo "## Конфигурация" - echo "" - echo "Добавьте в config.sh:" - echo "\`\`\`bash" - echo "S3_BUCKET=\"$S3_BUCKET\"" - echo "S3_REGION=\"$aws_region\"" - - if [ -n "$cdn_domain" ]; then - echo "CDN_URL=\"https://$cdn_domain\"" - echo "S3_BASE_URL=\"https://$cdn_domain\"" - else - echo "S3_BASE_URL=\"https://$S3_BUCKET.s3.amazonaws.com\"" - fi - - echo "\`\`\`" - echo "" - echo "---" - echo "*Отчет создан автоматически скриптом setup-aws.sh*" - -} > "$setup_report" - -# Финальная информация -log "" -log "=== НАСТРОЙКА AWS ЗАВЕРШЕНА ===" -log "S3 Bucket: $S3_BUCKET" -log "Region: $aws_region" - -if [ -n "$cdn_domain" ]; then - log "CDN Domain: $cdn_domain" - log "Base URL: https://$cdn_domain" -else - log "Base URL: https://$S3_BUCKET.s3.amazonaws.com" -fi - -log "Отчет создан: $setup_report" -log "" -log "Тестовое изображение:" -log " $s3_url" -log "" -log "Следующий шаг: ./1-upload-to-s3.sh" \ No newline at end of file diff --git a/resources/_gen/images/plan.webpZone.Identifier b/resources/_gen/images/plan.webpZone.Identifier deleted file mode 100644 index c832b8b..0000000 --- a/resources/_gen/images/plan.webpZone.Identifier +++ /dev/null @@ -1,4 +0,0 @@ -[ZoneTransfer] -ZoneId=3 -ReferrerUrl=https://www.pexels.com/ -HostUrl=https://images.pexels.com/photos/54278/pexels-photo-54278.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1 diff --git a/telegram/README_BOT.md b/telegram/README_BOT.md deleted file mode 100644 index d11eb48..0000000 --- a/telegram/README_BOT.md +++ /dev/null @@ -1,162 +0,0 @@ -# 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 deleted file mode 100644 index 208dd42..0000000 --- a/telegram/TODO.md +++ /dev/null @@ -1,106 +0,0 @@ -# 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 deleted file mode 100644 index 09320d4..0000000 --- a/telegram/env_example.txt +++ /dev/null @@ -1,2 +0,0 @@ -# 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 deleted file mode 100644 index 5050803..0000000 --- a/telegram/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -python-telegram-bot==20.7 \ No newline at end of file diff --git a/telegram/start_bot.sh b/telegram/start_bot.sh deleted file mode 100755 index 4337723..0000000 --- a/telegram/start_bot.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/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 deleted file mode 100644 index 0a69944..0000000 --- a/telegram/telegram_bot.py +++ /dev/null @@ -1,1188 +0,0 @@ -#!/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"![Фото поездки]({entry_data['photo']})\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/themes/hugo-theme-massively/layouts/partials/scripts/index.html b/themes/hugo-theme-massively/layouts/partials/scripts/index.html deleted file mode 100644 index 14d356b..0000000 --- a/themes/hugo-theme-massively/layouts/partials/scripts/index.html +++ /dev/null @@ -1,13 +0,0 @@ -{{ $jQuery := resources.Get "js/jquery.min.js" }} -{{ $scrollex := resources.Get "js/jquery.scrollex.min.js" }} -{{ $scrolly := resources.Get "js/jquery.scrolly.min.js" }} -{{ $browser := resources.Get "js/browser.min.js" }} -{{ $breakpoints := resources.Get "js/breakpoints.min.js" }} -{{ $util := resources.Get "js/util.js" }} -{{ $main := resources.Get "js/main.js" }} - -{{ $js := slice $jQuery $scrollex $scrolly $browser $breakpoints $util $main | resources.Concat "assets/js/bundle.js" }} - - - -