from typing import List, Optional from sqlalchemy.orm import Session from sqlalchemy import and_, or_ from ..models.post import Post from ..schemas.post import PostCreate, PostUpdate from datetime import datetime from ..integrations.telegram_bot import TelegramBot from ..integrations.twitter_api import TwitterAPI import os class PostService: def __init__(self, db_session: Session): self.db = db_session def get_post(self, post_id: int, owner_id: int) -> Optional[Post]: """Получить пост по ID и владельцу""" return self.db.query(Post).filter( and_(Post.id == post_id, Post.owner_id == owner_id) ).first() def get_posts(self, owner_id: int, skip: int = 0, limit: int = 100) -> List[Post]: """Получить список постов пользователя""" return self.db.query(Post).filter(Post.owner_id == owner_id)\ .order_by(Post.created_at.desc())\ .offset(skip)\ .limit(limit)\ .all() def create_post(self, post_data: PostCreate, owner_id: int) -> Post: """Создать новый пост""" db_post = Post( title=post_data.title, content=post_data.content, scheduled_time=post_data.scheduled_time, status=post_data.status, owner_id=owner_id ) self.db.add(db_post) self.db.commit() self.db.refresh(db_post) return db_post def update_post(self, post_id: int, post_data: PostUpdate, owner_id: int) -> Optional[Post]: """Обновить пост""" db_post = self.get_post(post_id, owner_id) if not db_post: return None # Обновляем только те поля, которые предоставлены update_data = post_data.dict(exclude_unset=True) for field, value in update_data.items(): setattr(db_post, field, value) # Обновляем время последнего изменения db_post.updated_at = datetime.utcnow() # Если статус меняется на published и published_at еще не установлен, устанавливаем его if post_data.status == "published" and not db_post.published_at: db_post.published_at = datetime.utcnow() self.db.commit() self.db.refresh(db_post) return db_post def delete_post(self, post_id: int, owner_id: int) -> bool: """Удалить пост""" db_post = self.get_post(post_id, owner_id) if not db_post: return False self.db.delete(db_post) self.db.commit() return True def get_scheduled_posts(self) -> List[Post]: """Получить посты, готовые к публикации (статус scheduled и время наступило)""" current_time = datetime.utcnow() return self.db.query(Post).filter( and_( Post.status == "scheduled", Post.scheduled_time <= current_time ) ).all() def get_posts_by_status(self, owner_id: int, status: str) -> List[Post]: """Получить посты по статусу""" return self.db.query(Post).filter( and_(Post.owner_id == owner_id, Post.status == status) ).all() def publish_post_to_telegram(self, post_id: int, chat_id: str) -> bool: """ Опубликовать пост в Telegram Args: post_id: ID поста в базе данных chat_id: ID чата или канала в Telegram Returns: Успешность публикации """ # Получаем пост из базы данных post = self.db.query(Post).filter(Post.id == post_id).first() if not post: return False # Получаем токен бота из переменных окружения bot_token = os.getenv("TELEGRAM_BOT_TOKEN") if not bot_token: raise ValueError("TELEGRAM_BOT_TOKEN environment variable is not set") # Создаем экземпляр TelegramBot bot = TelegramBot(bot_token) try: # Формируем сообщение из заголовка и содержимого поста message = f"{post.title}\n\n{post.content}" # Отправляем сообщение в Telegram result = bot.send_message(chat_id=chat_id, text=message) # Если публикация успешна, обновляем статус поста и время публикации if result.get("ok"): post.status = "published" post.published_at = datetime.utcnow() self.db.commit() return True else: return False except Exception as e: print(f"Error publishing post to Telegram: {str(e)}") return False def publish_scheduled_posts(self, chat_id: str) -> int: """ Опубликовать все запланированные посты, время которых наступило Args: chat_id: ID чата или канала в Telegram Returns: Количество опубликованных постов """ scheduled_posts = self.get_scheduled_posts() published_count = 0 for post in scheduled_posts: try: # Пытаемся опубликовать пост в Telegram success = self.publish_post_to_telegram(post.id, chat_id) if success: published_count += 1 # Обновляем статус поста на published post.status = "published" post.published_at = datetime.utcnow() self.db.commit() else: # Если публикация не удалась, помечаем пост как cancelled post.status = "cancelled" self.db.commit() except Exception as e: print(f"Error publishing scheduled post {post.id}: {str(e)}") # Если возникла ошибка, помечаем пост как cancelled post.status = "cancelled" self.db.commit() return published_count def publish_post_to_twitter(self, post_id: int) -> bool: """ Опубликовать пост в Twitter Args: post_id: ID поста в базе данных Returns: Успешность публикации """ # Получаем пост из базы данных post = self.db.query(Post).filter(Post.id == post_id).first() if not post: return False # Получаем параметры для Twitter API из переменных окружения twitter_consumer_key = os.getenv("TWITTER_CONSUMER_KEY") twitter_consumer_secret = os.getenv("TWITTER_CONSUMER_SECRET") twitter_access_token = os.getenv("TWITTER_ACCESS_TOKEN") twitter_access_token_secret = os.getenv("TWITTER_ACCESS_TOKEN_SECRET") if not all([twitter_consumer_key, twitter_consumer_secret, twitter_access_token, twitter_access_token_secret]): raise ValueError("Twitter API credentials are not fully configured in environment variables") # Создаем экземпляр TwitterAPI twitter_api = TwitterAPI( consumer_key=twitter_consumer_key, consumer_secret=twitter_consumer_secret, access_token=twitter_access_token, access_token_secret=twitter_access_token_secret ) try: # Формируем текст твита из заголовка и содержимого поста # Учитываем ограничение на 280 символов combined_text = f"{post.title}\n\n{post.content}" if len(combined_text) > 280: # Обрезаем текст до 280 символов, возможно сокращая содержимое truncated_title = post.title[:int(280*0.3)] # используем 30% на заголовок remaining_chars = 280 - len(truncated_title) - 3 # -3 для "..." if len(post.content) > remaining_chars: combined_text = f"{truncated_title}...\n\n{post.content[:remaining_chars-4]}..." else: combined_text = f"{truncated_title}...\n\n{post.content}" # Отправляем твит result = twitter_api.post_tweet(combined_text) # Если публикация успешна, обновляем статус поста и время публикации if result.get("success"): post.status = "published" post.published_at = datetime.utcnow() self.db.commit() return True else: return False except Exception as e: print(f"Error publishing post to Twitter: {str(e)}") return False