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 "Было: "
- echo "Стало: "
-
-} > "$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 }}
-
-
- {{- with (.Get "dir") -}}
-
-
- {{- $files := readDir (print "/static/" .) }}
- {{- range $files -}}
-
- {{- $thumbext := $.Get "thumb" | default "-thumb" }}
- {{- $isthumb := .Name | findRE ($thumbext | printf "%s\\.") }}
- {{- $isimg := lower .Name | findRE "\\.(gif|jpg|jpeg|tiff|png|bmp|webp|avif|jxl)" }}
- {{- if and $isimg (not $isthumb) }}
- {{- $caption := .Name | replaceRE "\\..*" "" | humanize }}
- {{- $linkURL := print "/images/" .Name }}
- {{- $thumb := .Name | replaceRE "(\\.)" ($thumbext | printf "%s.") }}
- {{- $thumbexists := where $files "Name" $thumb }}
- {{- $thumbURL := print "/images/" $thumb }}
-
-
-
-

-
-
- {{ $caption }}
-
-
-
-
- {{- end }}
- {{- end }}
- {{- else -}}
-
- {{ .Inner }}
- {{- end }}
-
-
-
-{{ 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 ""
- 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'
-
-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.webpZone.Identifier b/resources/_gen/images/plan.webpZone.Identifier
deleted file mode 100644
index c832b8b..0000000
--- a/resources/_gen/images/plan.webpZone.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"\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" }}
-
-
-
-