Upload files to "/"
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Директория со скриптами
|
||||
SCRIPTS_DIR="/opt/testbot"
|
||||
# Активация виртуального окружения
|
||||
VENV_ACTIVATE="$SCRIPTS_DIR/venv/bin/activate"
|
||||
|
||||
# Список скриптов для запуска в порядке выполнения
|
||||
SCRIPTS=("vk_load.py" "zk_load.py" "db_update_shortname.py" "evt_prefetch.py" "tg_publish.py" "evtg_publish.py")
|
||||
|
||||
# Интервалы между скриптами (в секундах)
|
||||
DELAYS=(2 2 2 2 30)
|
||||
|
||||
# Лог-файл
|
||||
LOG_FILE="/opt/testbot/runner.log"
|
||||
|
||||
# Функция для логирования
|
||||
log_message() {
|
||||
local message="$1"
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Проверка и настройка лог-файла
|
||||
setup_logfile() {
|
||||
# Проверяем, существует ли файл и можно ли в него писать
|
||||
if [ ! -f "$LOG_FILE" ]; then
|
||||
# Пытаемся создать файл
|
||||
if ! touch "$LOG_FILE" 2>/dev/null; then
|
||||
# Если не получается, пробуем с sudo
|
||||
if sudo touch "$LOG_FILE" 2>/dev/null; then
|
||||
sudo chown $(whoami) "$LOG_FILE"
|
||||
sudo chmod 644 "$LOG_FILE"
|
||||
log_message "Создан лог-файл с помощью sudo: $LOG_FILE"
|
||||
else
|
||||
echo "ОШИБКА: Не удалось создать лог-файл $LOG_FILE!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Проверяем права на запись
|
||||
if [ ! -w "$LOG_FILE" ]; then
|
||||
# Пытаемся изменить права
|
||||
if sudo chown $(whoami) "$LOG_FILE" 2>/dev/null || chmod 644 "$LOG_FILE" 2>/dev/null; then
|
||||
log_message "Изменены права на лог-файл: $LOG_FILE"
|
||||
else
|
||||
echo "ОШИБКА: Нет прав на запись в $LOG_FILE!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Настройка лог-файла
|
||||
setup_logfile
|
||||
|
||||
log_message "=== ЗАПУСК СКРИПТА ==="
|
||||
log_message "Пользователь: $(whoami)"
|
||||
log_message "Директория со скриптами: $SCRIPTS_DIR"
|
||||
|
||||
# Проверка существования директории
|
||||
if [ ! -d "$SCRIPTS_DIR" ]; then
|
||||
log_message "ОШИБКА: Директория $SCRIPTS_DIR не существует!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Активация виртуального окружения
|
||||
if [ -f "$VENV_ACTIVATE" ]; then
|
||||
source "$VENV_ACTIVATE"
|
||||
log_message "Активировано виртуальное окружение: $VENV_ACTIVATE"
|
||||
else
|
||||
log_message "ОШИБКА: Файл активации venv $VENV_ACTIVATE не найден!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Переход в директорию со скриптами
|
||||
cd "$SCRIPTS_DIR" || {
|
||||
log_message "ОШИБКА: Не удалось перейти в $SCRIPTS_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Запуск скриптов
|
||||
for i in "${!SCRIPTS[@]}"; do
|
||||
script="${SCRIPTS[i]}"
|
||||
|
||||
# Проверка существования скрипта
|
||||
if [ ! -f "$script" ]; then
|
||||
log_message "ПРЕДУПРЕЖДЕНИЕ: Скрипт $script не найден, пропускаем."
|
||||
continue
|
||||
fi
|
||||
|
||||
log_message "Запуск скрипта: $script"
|
||||
|
||||
# Запуск Python-скрипта с записью вывода в лог
|
||||
if python3 "$script" >> "$LOG_FILE" 2>&1; then
|
||||
log_message "УСПЕХ: Скрипт $script завершился успешно."
|
||||
else
|
||||
exit_code=$?
|
||||
log_message "ОШИБКА: Скрипт $script завершился с ошибкой (код: $exit_code)!"
|
||||
log_message "=== ЗАВЕРШЕНИЕ С ОШИБКОЙ ==="
|
||||
exit $exit_code
|
||||
fi
|
||||
|
||||
# Пауза после скрипта (кроме последнего)
|
||||
if [ $i -lt $((${#SCRIPTS[@]} - 1)) ]; then
|
||||
delay="${DELAYS[i]}"
|
||||
log_message "Ожидание $delay секунд перед следующим скриптом..."
|
||||
sleep $delay
|
||||
fi
|
||||
done
|
||||
|
||||
log_message "ВСЕ СКРИПТЫ ВЫПОЛНЕНЫ УСПЕШНО!"
|
||||
log_message "=== ЗАВЕРШЕНИЕ ==="
|
||||
|
||||
+1206
File diff suppressed because it is too large
Load Diff
+324
@@ -0,0 +1,324 @@
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
import pymysql
|
||||
import time
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from telegram import Bot, PollOption
|
||||
from telegram.error import TelegramError, RetryAfter
|
||||
from dotenv import load_dotenv
|
||||
from formatter import get_event_text
|
||||
|
||||
# Загрузка переменных окружения
|
||||
load_dotenv()
|
||||
|
||||
# Настройки из переменных окружения
|
||||
BOT_TOKEN = os.getenv('POSTER_BOT_TOKEN')
|
||||
RESPONDER_BOT_NAME = os.getenv('RESPONDER_BOT_NAME')
|
||||
CHANNEL_ID = os.getenv('CHANNEL_ID')
|
||||
MDB_HOST = os.getenv('MDB_HOST')
|
||||
MDB_USER = os.getenv('MDB_USER')
|
||||
MDB_PW = os.getenv('MDB_PW')
|
||||
MDBASE = os.getenv('MDBASE')
|
||||
LOG_FILE = os.getenv('LOG_FILE', 'post_publisher.log')
|
||||
ORG_MESSAGE_TAG = os.getenv('ORG_MESSAGE_TAG', '') # Хештег для определения организационных сообщений
|
||||
|
||||
# Параметры длины сообщений
|
||||
MAX_CAPTION_LENGTH = int(os.getenv('MAX_CAPTION_LENGTH', 1000))
|
||||
MAX_TEXT_LENGTH = int(os.getenv('MAX_TEXT_LENGTH', 4000))
|
||||
|
||||
# Настройка логирования
|
||||
logger = logging.getLogger('TG_Poster')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
formatter = logging.Formatter('[%(asctime)s] [%(name)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
class RetryFileHandler(logging.FileHandler):
|
||||
def emit(self, record):
|
||||
for _ in range(5):
|
||||
try:
|
||||
super().emit(record)
|
||||
return
|
||||
except (IOError, PermissionError):
|
||||
time.sleep(0.5)
|
||||
print(f"Failed to write to log file after 5 attempts: {record.msg}")
|
||||
|
||||
file_handler = RetryFileHandler(LOG_FILE, encoding='utf-8')
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
def prepare_text(text):
|
||||
"""Подготовка текстовых полей с экранированием HTML-сущностей"""
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
# Экранируем специальные символы HTML
|
||||
text = html.escape(str(text))
|
||||
|
||||
return text
|
||||
|
||||
def has_org_tag(text):
|
||||
"""Проверяет, содержит ли текст организационный хештег"""
|
||||
if not text or not ORG_MESSAGE_TAG:
|
||||
return False
|
||||
|
||||
# Ищем хештег (с учетом возможных пробелов и других символов)
|
||||
pattern = r'#?' + re.escape(ORG_MESSAGE_TAG) + r'\b'
|
||||
return bool(re.search(pattern, text, re.IGNORECASE))
|
||||
|
||||
async def publish_to_tg(vk_post_id):
|
||||
"""Публикация одной записи по VK post ID"""
|
||||
logger.info(f"Запуск публикации для записи VK ID {vk_post_id}")
|
||||
conn = None
|
||||
try:
|
||||
conn = pymysql.connect(
|
||||
host=MDB_HOST,
|
||||
user=MDB_USER,
|
||||
password=MDB_PW,
|
||||
database=MDBASE,
|
||||
charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT id, vk_post_id, text, image_url, vk_post_url, is_poll, poll_question,
|
||||
poll_options, poll_multiple, poll_end_date, is_event
|
||||
FROM posts
|
||||
WHERE vk_post_id = %s
|
||||
AND marked_for_publication = True
|
||||
AND published_in_tg = False
|
||||
""", (vk_post_id,))
|
||||
post = cursor.fetchone()
|
||||
|
||||
if not post:
|
||||
logger.info(f"Запись VK ID {vk_post_id} не найдена или уже опубликована")
|
||||
return False
|
||||
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
|
||||
# Проверяем наличие организационного хештега
|
||||
is_org_message = has_org_tag(post['text'])
|
||||
|
||||
# Предварительный расчет строки ссылок (с запасом 20 символов для message_id)
|
||||
placeholder_message_id = '0' * 20 # Заполнитель для message_id
|
||||
|
||||
# Формируем ссылки
|
||||
links = []
|
||||
|
||||
# Ссылка на оригинал в ВК
|
||||
if post['vk_post_url'] and (post['vk_post_url'].startswith('http://') or post['vk_post_url'].startswith('https://')):
|
||||
links.append(f'<a href="{post["vk_post_url"]}">Оригинал в ВК</a>')
|
||||
|
||||
# Ссылка на подписку (только для событий)
|
||||
if post['is_event']:
|
||||
links.append(f'<a href="https://t.me/{RESPONDER_BOT_NAME}?start=post_{placeholder_message_id}">🔜 Подписка</a>')
|
||||
|
||||
# Формируем строку ссылок
|
||||
links_line = " | ".join(links) if links else ""
|
||||
links_line_length = len(links_line)
|
||||
|
||||
# Определяем максимальную длину в зависимости от типа сообщения
|
||||
# Если это организационное сообщение, используем текстовый лимит
|
||||
image_url = post['image_url']
|
||||
has_image = image_url and image_url.strip() and not is_org_message
|
||||
max_length = MAX_CAPTION_LENGTH if has_image else MAX_TEXT_LENGTH
|
||||
|
||||
# Вычисляем доступную длину для текста
|
||||
available_length = max_length - links_line_length - 2 # -2 для символов переноса строки
|
||||
if available_length < 0:
|
||||
available_length = 0
|
||||
|
||||
# Получаем обработанный текст с учетом доступной длины
|
||||
text = get_event_text(post['text'], available_length) if post['text'] else ""
|
||||
|
||||
# Публикация первоначального сообщения без ссылок
|
||||
if has_image and not is_org_message:
|
||||
try:
|
||||
message = await bot.send_photo(
|
||||
chat_id=CHANNEL_ID,
|
||||
photo=image_url,
|
||||
caption=text,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True
|
||||
)
|
||||
except TelegramError as e:
|
||||
logger.warning(f"Не удалось отправить изображение для записи VK ID {vk_post_id}: {e}. Отправляем текстовое сообщение.")
|
||||
message = await bot.send_message(
|
||||
chat_id=CHANNEL_ID,
|
||||
text=text,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
else:
|
||||
# Если это организационное сообщение или нет изображения, отправляем текстовое сообщение
|
||||
message = await bot.send_message(
|
||||
chat_id=CHANNEL_ID,
|
||||
text=text,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
|
||||
# Получаем ID сообщения для использования в ссылке подписки
|
||||
message_id = message.message_id
|
||||
|
||||
# Формируем финальный текст с ссылками
|
||||
if links:
|
||||
# Обновляем ссылку подписки с реальным message_id
|
||||
updated_links = []
|
||||
if post['vk_post_url'] and (post['vk_post_url'].startswith('http://') or post['vk_post_url'].startswith('https://')):
|
||||
updated_links.append(f'<a href="{post["vk_post_url"]}">Оригинал в ВК</a>')
|
||||
|
||||
if post['is_event']:
|
||||
updated_links.append(f'<a href="https://t.me/{RESPONDER_BOT_NAME}?start=post_{message_id}">🔜 Подписка</a>')
|
||||
|
||||
links_line = " | ".join(updated_links)
|
||||
final_text = f"{text}\n\n{links_line}"
|
||||
|
||||
await asyncio.sleep(2) # задержка между обращениями к телеграм
|
||||
|
||||
# Редактируем сообщение, добавляя ссылки
|
||||
if has_image and not is_org_message:
|
||||
try:
|
||||
await bot.edit_message_caption(
|
||||
chat_id=CHANNEL_ID,
|
||||
message_id=message_id,
|
||||
caption=final_text,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
except TelegramError as e:
|
||||
logger.warning(f"Не удалось отредактировать подпись для записи VK ID {vk_post_id}: {e}. Пробуем отредактировать текстовое сообщение.")
|
||||
await bot.edit_message_text(
|
||||
chat_id=CHANNEL_ID,
|
||||
message_id=message_id,
|
||||
text=final_text,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
else:
|
||||
await bot.edit_message_text(
|
||||
chat_id=CHANNEL_ID,
|
||||
message_id=message_id,
|
||||
text=final_text,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True
|
||||
)
|
||||
|
||||
await asyncio.sleep(2) # задержка между обращениями к телеграм
|
||||
|
||||
# Публикация опроса, если требуется
|
||||
poll_message_id = None
|
||||
if post['is_poll'] and post['poll_question'] and post['poll_options']:
|
||||
try:
|
||||
# Парсим варианты ответа
|
||||
options = json.loads(post['poll_options'])
|
||||
poll_options = [PollOption(option, 0) for option in options]
|
||||
|
||||
# Отправляем опрос (в каналах можно отправлять только анонимные опросы)
|
||||
poll_message = await bot.send_poll(
|
||||
chat_id=CHANNEL_ID,
|
||||
question=post['poll_question'],
|
||||
options=poll_options,
|
||||
is_anonymous=True, # В каналах можно отправлять только анонимные опросы
|
||||
allows_multiple_answers=post['poll_multiple'],
|
||||
close_date=post['poll_end_date'] if post['poll_end_date'] else None,
|
||||
disable_notification=True
|
||||
)
|
||||
|
||||
poll_message_id = poll_message.message_id
|
||||
logger.info(f"Опубликован опрос для записи VK ID {vk_post_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при публикации опроса для записи VK ID {vk_post_id}: {e}")
|
||||
|
||||
# Обновляем запись в базе данных
|
||||
current_time_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
cursor.execute("""
|
||||
UPDATE posts
|
||||
SET published_in_tg = True,
|
||||
tg_publication_date = %s,
|
||||
tg_message_id = %s,
|
||||
tg_poll_id = %s
|
||||
WHERE vk_post_id = %s
|
||||
""", (current_time_utc, message_id, poll_message_id, vk_post_id))
|
||||
conn.commit()
|
||||
|
||||
logger.info(f"Запись VK ID {vk_post_id} успешно опубликована")
|
||||
return True
|
||||
|
||||
except RetryAfter as e:
|
||||
logger.error(f"Получена ошибка FloodWait (429) при публикации записи VK ID {vk_post_id}: {e}")
|
||||
logger.error(f"Необходимо подождать {e.retry_after} секунд перед следующей попыткой")
|
||||
raise
|
||||
except TelegramError as e:
|
||||
logger.error(f"Ошибка Telegram при публикации записи VK ID {vk_post_id}: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Неожиданная ошибка при публикации записи VK ID {vk_post_id}: {e}")
|
||||
return False
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
async def publish_to_tg_all():
|
||||
"""Публикация всех неопубликованных записей (максимум 10 за один вызов)"""
|
||||
logger.info("Запуск скрипта публикации всех записей")
|
||||
conn = None
|
||||
try:
|
||||
conn = pymysql.connect(
|
||||
host=MDB_HOST,
|
||||
user=MDB_USER,
|
||||
password=MDB_PW,
|
||||
database=MDBASE,
|
||||
charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT vk_post_id
|
||||
FROM posts
|
||||
WHERE marked_for_publication = True
|
||||
AND published_in_tg = False
|
||||
LIMIT 5
|
||||
""")
|
||||
posts = cursor.fetchall()
|
||||
|
||||
post_count = 0
|
||||
for post in posts:
|
||||
try:
|
||||
success = await publish_to_tg(post['vk_post_id'])
|
||||
if success:
|
||||
post_count += 1
|
||||
|
||||
# Задержка между публикациями разных записей
|
||||
if post_count < len(posts):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except RetryAfter as e:
|
||||
logger.error(f"Прерываем публикацию из-за ошибки FloodWait. Ожидание: {e.retry_after} секунд")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при публикации записи VK ID {post['vk_post_id']}: {e}")
|
||||
# Продолжаем публикацию следующих записей, несмотря на ошибку
|
||||
|
||||
logger.info(f"Опубликовано записей в этом запуске: {post_count}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при публикации: {e}")
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
logger.info("Завершение работы скрипта")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(publish_to_tg_all())
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
import mysql.connector
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime
|
||||
from mysql.connector import errorcode
|
||||
from dotenv import load_dotenv
|
||||
from ensure_db import ensure_database_structure
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
# Получение конфигурации из переменных окружения
|
||||
VK_GROUP_ID = int(os.getenv('VK_GROUP_ID', -3342146))
|
||||
VK_ACCESS_TOKEN = os.getenv('VK_ACCESS_TOKEN', '')
|
||||
VK_API_VERSION = os.getenv('VK_API_VERSION', '5.131')
|
||||
IS_EVENT = os.getenv('IS_EVENT', 'false').lower() in ('true', '1', 'yes', 'on')
|
||||
AUTO_PUBLISH = os.getenv('AUTO_PUBLISH', 'false').lower() in ('true', '1', 'yes', 'on')
|
||||
LOG_FILE = os.getenv('LOG_FILE', 'vk_loader.log') # Путь к лог-файлу
|
||||
LOG_PREFIX = "VK_loader" # Уникальный префикс для идентификации скрипта
|
||||
|
||||
# Конфигурация MariaDB из .env
|
||||
DB_CONFIG = {
|
||||
'host': os.getenv('MDB_HOST', 'localhost'),
|
||||
'user': os.getenv('MDB_USER', 'root'),
|
||||
'password': os.getenv('MDB_PW', ''),
|
||||
'database': os.getenv('MDBASE', 'vk_posts')
|
||||
}
|
||||
|
||||
def log_message(message, max_retries=5, retry_delay=0.1):
|
||||
"""
|
||||
Записывает сообщение в лог-файл с обработкой блокировок
|
||||
и идентификатором скрипта
|
||||
"""
|
||||
if not LOG_FILE:
|
||||
return
|
||||
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
# Добавляем идентификатор скрипта к сообщению
|
||||
log_line = f"[{timestamp}] [{LOG_PREFIX}] {message}\n"
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
with open(LOG_FILE, 'a', encoding='utf-8') as log:
|
||||
log.write(log_line)
|
||||
return True
|
||||
except (IOError, OSError) as e:
|
||||
if "locked" in str(e).lower() and attempt < max_retries - 1:
|
||||
# Случайная задержка для уменьшения коллизий
|
||||
sleep_time = retry_delay * (1 + random.random() * 0.5)
|
||||
time.sleep(sleep_time)
|
||||
else:
|
||||
# Если не удалось записать после всех попыток
|
||||
print(f"Ошибка записи в лог: {e}")
|
||||
print(f"Сообщение для лога: {log_line.strip()}")
|
||||
return False
|
||||
|
||||
def get_vk_posts():
|
||||
"""Получает последние 10 постов из VK группы"""
|
||||
url = 'https://api.vk.ru/method/wall.get'
|
||||
params = {
|
||||
'owner_id': VK_GROUP_ID,
|
||||
'count': 10,
|
||||
'access_token': VK_ACCESS_TOKEN,
|
||||
'v': VK_API_VERSION,
|
||||
'extended': 1
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if 'error' in data:
|
||||
error_msg = f"VK API Error: {data['error']['error_msg']}"
|
||||
log_message(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
return data['response']['items']
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_msg = f"Ошибка подключения к VK API: {e}"
|
||||
log_message(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
def process_post(post):
|
||||
"""Извлекает необходимые данные из поста VK"""
|
||||
post_id = post['id']
|
||||
vk_post_url = f"https://vk.ru/wall{VK_GROUP_ID}_{post_id}"
|
||||
published_at = datetime.utcfromtimestamp(post['date']).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Инициализация полей опроса
|
||||
is_poll = 0
|
||||
poll_question = None
|
||||
poll_options = None
|
||||
poll_multiple = False
|
||||
poll_end_date = None
|
||||
|
||||
# Инициализация поля изображения
|
||||
image_url = None
|
||||
|
||||
# Обработка вложений
|
||||
for attachment in post.get('attachments', []):
|
||||
if attachment['type'] == 'poll':
|
||||
is_poll = 1
|
||||
poll = attachment['poll']
|
||||
poll_question = poll['question']
|
||||
poll_options = json.dumps([answer['text'] for answer in poll['answers']])
|
||||
poll_multiple = bool(poll.get('multiple', False))
|
||||
|
||||
if poll.get('end_date', 0) > 0:
|
||||
poll_end_date = datetime.utcfromtimestamp(poll['end_date']).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
elif attachment['type'] == 'photo' and not image_url:
|
||||
# Берем только первое изображение
|
||||
photo = attachment['photo']
|
||||
sizes = photo.get('sizes', [])
|
||||
if sizes:
|
||||
# Выбираем изображение максимального качества
|
||||
max_size = max(sizes, key=lambda s: s.get('width', 0) * s.get('height', 0))
|
||||
image_url = max_size['url']
|
||||
|
||||
return {
|
||||
'vk_post_id': post_id,
|
||||
'text': post['text'],
|
||||
'image_url': image_url,
|
||||
'vk_post_url': vk_post_url,
|
||||
'published_at': published_at,
|
||||
'is_poll': is_poll,
|
||||
'poll_question': poll_question,
|
||||
'poll_options': poll_options,
|
||||
'poll_multiple': poll_multiple,
|
||||
'poll_end_date': poll_end_date
|
||||
}
|
||||
|
||||
def save_to_database(posts):
|
||||
"""Сохраняет новые посты в MariaDB с учетом настроек публикации"""
|
||||
try:
|
||||
conn = mysql.connector.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
new_posts_count = 0
|
||||
for post in posts:
|
||||
try:
|
||||
# Добавляем поля is_event, marked_for_publication, shortname и action_number
|
||||
cursor.execute('''
|
||||
INSERT INTO posts (
|
||||
vk_post_id, text, image_url, vk_post_url, published_at,
|
||||
is_poll, poll_question, poll_options, poll_multiple, poll_end_date,
|
||||
is_event, marked_for_publication, shortname, action_number
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE vk_post_id = VALUES(vk_post_id)
|
||||
''', (
|
||||
post['vk_post_id'],
|
||||
post['text'],
|
||||
post['image_url'],
|
||||
post['vk_post_url'],
|
||||
post['published_at'],
|
||||
post['is_poll'],
|
||||
post['poll_question'],
|
||||
post['poll_options'],
|
||||
int(post['poll_multiple']),
|
||||
post['poll_end_date'],
|
||||
IS_EVENT,
|
||||
int(AUTO_PUBLISH), # Значение из .env
|
||||
None, # shortname - пока не используется, устанавливаем NULL
|
||||
None # action_number - пока не используется, устанавливаем NULL
|
||||
))
|
||||
if cursor.rowcount > 0:
|
||||
new_posts_count += 1
|
||||
# Логируем добавление нового поста
|
||||
log_message(f"Добавлен новый пост: ID {post['vk_post_id']}, Дата: {post['published_at']}, Автопубликация: {'Да' if AUTO_PUBLISH else 'Нет'}")
|
||||
except mysql.connector.Error as err:
|
||||
error_msg = f"Ошибка при вставке поста {post['vk_post_id']}: {err}"
|
||||
log_message(error_msg)
|
||||
continue
|
||||
|
||||
conn.commit()
|
||||
return new_posts_count
|
||||
|
||||
except mysql.connector.Error as err:
|
||||
error_msg = f"Ошибка подключения к базе данных: {err}"
|
||||
log_message(error_msg)
|
||||
return 0
|
||||
finally:
|
||||
if 'conn' in locals() and conn.is_connected():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
def vk_load_10():
|
||||
"""
|
||||
Экспортируемая функция для загрузки 10 последних постов из VK
|
||||
Возвращает количество добавленных постов
|
||||
"""
|
||||
# Стартовая информация
|
||||
start_msg = f"Старт скрипта | База: {DB_CONFIG['database']}@{DB_CONFIG['host']} | IS_EVENT: {IS_EVENT} | AUTO_PUBLISH: {AUTO_PUBLISH}"
|
||||
print(start_msg)
|
||||
log_message(start_msg)
|
||||
|
||||
# Обеспечиваем структуру БД
|
||||
try:
|
||||
ensure_database_structure()
|
||||
except Exception as e:
|
||||
error_msg = f"Не удалось создать структуру базы данных: {e}"
|
||||
print(error_msg)
|
||||
log_message(error_msg)
|
||||
return 0
|
||||
|
||||
# Получаем посты из VK
|
||||
try:
|
||||
vk_posts = get_vk_posts()
|
||||
status_msg = f"Получено {len(vk_posts)} постов из VK"
|
||||
print(status_msg)
|
||||
log_message(status_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"Ошибка при получении данных из VK: {e}"
|
||||
print(error_msg)
|
||||
log_message(error_msg)
|
||||
return 0
|
||||
|
||||
# Обрабатываем и сохраняем посты
|
||||
processed_posts = [process_post(post) for post in vk_posts]
|
||||
new_count = save_to_database(processed_posts)
|
||||
|
||||
# Фиксируем результат
|
||||
if new_count > 0:
|
||||
result_msg = f"Добавлено {new_count} новых постов в базу данных"
|
||||
else:
|
||||
result_msg = "Новые посты не обнаружены"
|
||||
|
||||
print(result_msg)
|
||||
log_message(result_msg)
|
||||
|
||||
# Завершение работы
|
||||
end_msg = "Работа скрипта завершена"
|
||||
print(end_msg)
|
||||
log_message(end_msg)
|
||||
|
||||
return new_count
|
||||
|
||||
def main():
|
||||
"""Основная функция для запуска скрипта из командной строки"""
|
||||
vk_load_10()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user