Files
copykar/services/publisher.py
T

102 lines
4.0 KiB
Python

import os
import asyncio
import logging
from datetime import datetime, timezone
from typing import Optional
from telethon import TelegramClient
from db.repository import Repository
from core.metrics import POSTS_PUBLISHED_TOTAL, QUEUE_POSTS_GAUGE
logger = logging.getLogger(__name__)
SESSION_DIR = os.getenv("SESSION_DIR", "/app/sessions" if os.path.exists("/app") else "/projects/telegram-bots/copykar/sessions")
class PublisherService:
def __init__(
self,
repo: Repository,
api_id: Optional[int] = None,
api_hash: Optional[str] = None,
session_name: Optional[str] = None,
bot_token: Optional[str] = None,
):
self.repo = repo
self.api_id = api_id or int(os.getenv("API_ID", "0"))
self.api_hash = api_hash or os.getenv("API_HASH", "")
self.bot_token = bot_token or os.getenv("BOT_TOKEN")
self.session_name = session_name or os.path.join(SESSION_DIR, "publisher.session")
os.makedirs(os.path.dirname(self.session_name), exist_ok=True)
self.client = TelegramClient(self.session_name, self.api_id, self.api_hash)
self._running = False
self._task: Optional[asyncio.Task] = None
async def start(self):
logger.info("Starting Publisher Service...")
if self.bot_token:
await self.client.start(bot_token=self.bot_token)
else:
await self.client.start()
logger.info("Publisher Service connected successfully.")
self._running = True
self._task = asyncio.create_task(self._publisher_loop())
async def _publisher_loop(self):
while self._running:
try:
await self._process_pending_queues()
except Exception as e:
logger.error(f"Error in publisher loop: {e}", exc_info=True)
await asyncio.sleep(15)
async def _process_pending_queues(self):
targets = await self.repo.get_active_targets()
now = datetime.now(timezone.utc)
pending_count = len(await self.repo.get_posts_by_status("approved", limit=5000))
QUEUE_POSTS_GAUGE.labels(status="approved").set(pending_count)
for target in targets:
if target.last_post_time:
last_post = target.last_post_time
if last_post.tzinfo is None:
last_post = last_post.replace(tzinfo=timezone.utc)
diff_minutes = (now - last_post).total_seconds() / 60.0
if diff_minutes < target.post_interval_min:
continue
post = await self.repo.get_next_approved_post_for_target(target.id)
if not post:
continue
try:
publish_text = post.ai_text or post.raw_text or ""
if post.media_path and os.path.exists(post.media_path):
await self.client.send_file(
target.channel_id,
file=post.media_path,
caption=publish_text,
parse_mode="markdown"
)
else:
await self.client.send_message(
target.channel_id,
publish_text,
parse_mode="markdown"
)
await self.repo.mark_post_published(post.id)
await self.repo.update_target_last_post(target.id)
POSTS_PUBLISHED_TOTAL.labels(target_channel_id=str(target.channel_id)).inc()
logger.info(f"Successfully published post {post.id} to target channel {target.title} ({target.channel_id})")
except Exception as e:
logger.error(f"Failed to publish post {post.id} to target {target.channel_id}: {e}", exc_info=True)
async def stop(self):
self._running = False
if self._task:
self._task.cancel()
if self.client.is_connected():
await self.client.disconnect()
logger.info("Publisher Service disconnected.")