โ† Back
โ˜†
"""
AlphaPulse Bot โ€” Telegram Poster (v7.0 โ€” card-style templates)
"""

import asyncio
import logging
from datetime import timedelta

from telegram import Bot
from telegram.error import TelegramError, TimedOut

from config import CONFIG, QUICK_ACTIONS_KEYBOARD
from utils import now_van, build_hashtags
from templates import (
    card_news, card_price_snapshot, card_trending,
    card_top_movers, card_weekly_digest, card_fear_greed,
    card_funding_rates, card_watchlist, card_long_short,
    card_history, card_whale_alerts,
)

logger = logging.getLogger(__name__)


class TelegramPoster:
    FORMAT_EMOJIS = {
        'hot':       '๐Ÿ”ฅ',
        'analysis':  '๐Ÿง ',
        'sarcasm':   '๐Ÿ˜',
        'facts':     '๐Ÿ“Š',
        'signal':    '๐Ÿ“ˆ',
        'fear_greed':'๐ŸŽš๏ธ',
        'thread':    '๐Ÿงต',
        'hot_take':  'โšก',
        'deep_dive': '๐Ÿ”ฌ',
    }

    FORMAT_TITLES = {
        'hot':       'BREAKING',
        'analysis':  'MARKET ANALYSIS',
        'sarcasm':   'MARKET VIBES',
        'facts':     'KEY FACTS',
        'signal':    'TRADE SIGNAL',
        'thread':    'KEY POINTS',
        'hot_take':  'HOT TAKE',
        'deep_dive': 'DEEP DIVE',
    }

    def __init__(self, token: str, channel_id: str):
        self.bot        = Bot(token=token)
        self.channel_id = channel_id
        self.post_counter = 0

    async def _send(self, chat_id=None, **kwargs) -> bool:
        """Send with 3-attempt retry. TimedOut is NOT retried."""
        target = chat_id or self.channel_id
        for attempt in range(1, 4):
            try:
                await self.bot.send_message(chat_id=target, **kwargs)
                self.post_counter += 1
                return True
            except TimedOut:
                logger.warning("Telegram TimedOut โ€” message likely delivered, skipping retry")
                self.post_counter += 1
                return True
            except TelegramError as e:
                logger.warning(f"Telegram attempt {attempt}/3: {e}")
                if attempt < 3:
                    await asyncio.sleep(5)
        logger.error("Telegram send failed after 3 retries")
        return False

    async def post(self, text: str, url: str, source: str,
                   fmt: str = 'regular', add_affiliate: bool = False,
                   chat_id: str = None, title: str = '') -> bool:
        """Post a news item with card-style formatting."""
        emoji = self.FORMAT_EMOJIS.get(fmt, '๐Ÿ“ฐ')

        if fmt == 'fear_greed':
            from ai import AICommentary
            message = card_fear_greed(0, '', text)
        else:
            hashtags = build_hashtags(title, source) if title else ''
            message = card_news(emoji, text, source, url, hashtags,
                                add_affiliate=add_affiliate)

        keyboard = QUICK_ACTIONS_KEYBOARD if fmt not in ('fear_greed',) else None

        success = await self._send(
            chat_id=chat_id, text=message,
            parse_mode='HTML', disable_web_page_preview=True,
            reply_markup=keyboard,
        )
        if success:
            logger.info(f"Posted [{fmt}]: {source}")
        return success

    async def post_price_snapshot(self, prices: dict, fg: dict = None,
                                  dominance: dict = None,
                                  sparklines: dict = None) -> bool:
        """Daily price card with sparklines and progress bar."""
        message = card_price_snapshot(prices, fg, dominance, sparklines)
        logger.info("Posted price snapshot")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_trending(self, coins: list) -> bool:
        """Morning trending coins card."""
        if not coins:
            return False
        date_str = now_van().strftime('%b %d, %Y')
        message = card_trending(coins, date_str)
        logger.info("Posted trending coins")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_top_movers(self, gainers: list, losers: list) -> bool:
        """Top movers card."""
        if not gainers and not losers:
            return False
        message = card_top_movers(gainers, losers)
        logger.info("Posted top movers")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_weekly_digest(self, prices: dict, fg: dict = None,
                                 gainers: list = None) -> bool:
        """Sunday weekly summary card."""
        now        = now_van()
        week_start = (now - timedelta(days=6)).strftime('%b %d')
        week_end   = now.strftime('%b %d')
        week_range = f"{week_start} โ€“ {week_end}"

        message = card_weekly_digest(prices, fg, gainers, week_range)
        logger.info("Posted weekly digest")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_fear_greed(self, value: int, classification: str,
                              comment: str) -> bool:
        """Standalone Fear & Greed card."""
        message = card_fear_greed(value, classification, comment)
        logger.info("Posted fear & greed")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_funding_rates(self, rates: list) -> bool:
        """Funding rates card."""
        if not rates:
            return False
        message = card_funding_rates(rates)
        logger.info("Posted funding rates")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_watchlist(self, coins: list, ai_commentary: str,
                             date_str: str) -> bool:
        """Monday watchlist card."""
        if not coins:
            return False
        message = card_watchlist(coins, ai_commentary, date_str)
        logger.info("Posted watchlist")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_history(self, events: list, date_str: str,
                           ai_reflection: str = '') -> bool:
        """This Day in Crypto card."""
        if not events:
            return False
        message = card_history(events, date_str, ai_reflection)
        logger.info("Posted 'This Day in Crypto'")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_long_short(self, positions: list) -> bool:
        """Market positioning card."""
        if not positions:
            return False
        message = card_long_short(positions)
        logger.info("Posted long/short positioning")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_whale_alerts(self, alerts: list) -> bool:
        """Whale watch card."""
        if not alerts:
            return False
        message = card_whale_alerts(alerts)
        logger.info("Posted whale alerts")
        return await self._send(
            text=message, parse_mode='HTML', disable_web_page_preview=True
        )

    async def post_poll(self, question: str, options: list) -> bool:
        try:
            await self.bot.send_poll(
                chat_id=self.channel_id,
                question=question,
                options=options,
                is_anonymous=True,
                allows_multiple_answers=False,
            )
            logger.info("Posted poll")
            self.post_counter += 1
            return True
        except TelegramError as e:
            logger.error(f"Poll error: {e}")
            return False

๐Ÿ“œ Git History

a09f02fchore: initial commit โ€” version control setup5 weeks ago
Show last diff
Loading...