diff --git a/leggen/background/scheduler.py b/leggen/background/scheduler.py index 302f5e7..bfd380a 100644 --- a/leggen/background/scheduler.py +++ b/leggen/background/scheduler.py @@ -112,7 +112,7 @@ class BackgroundScheduler: # Send notification about the failure try: - await self.notification_service.send_expiry_notification( + await self.notification_service.send_sync_failure_notification( { "type": "sync_failure", "error": str(e), @@ -145,7 +145,7 @@ class BackgroundScheduler: logger.error("Maximum retries exceeded for sync job") # Send final failure notification try: - await self.notification_service.send_expiry_notification( + await self.notification_service.send_sync_failure_notification( { "type": "sync_final_failure", "error": str(e), diff --git a/leggen/notifications/discord.py b/leggen/notifications/discord.py index 0420357..a0ffec5 100644 --- a/leggen/notifications/discord.py +++ b/leggen/notifications/discord.py @@ -55,3 +55,44 @@ def send_transactions_message(ctx: click.Context, transactions: list): response.raise_for_status() except Exception as e: raise Exception(f"Discord notification failed: {e}\n{response.text}") from e + + +def send_sync_failure_notification(ctx: click.Context, notification: dict): + info("Sending sync failure notification to Discord") + webhook = DiscordWebhook(url=ctx.obj["notifications"]["discord"]["webhook"]) + + # Determine color and title based on failure type + if notification.get("type") == "sync_final_failure": + color = "ff0000" # Red for final failure + title = "🚨 Sync Final Failure" + description = ( + f"Sync failed permanently after {notification['retry_count']} attempts" + ) + else: + color = "ffaa00" # Orange for retry + title = "⚠️ Sync Failure" + description = f"Sync failed (attempt {notification['retry_count']}/{notification['max_retries']}). Will retry automatically..." + + embed = DiscordEmbed( + title=title, + description=description, + color=color, + ) + embed.set_author( + name="Leggen", + url="https://github.com/elisiariocouto/leggen", + ) + embed.add_embed_field( + name="Error", + value=notification["error"][:1024], # Discord has field value limits + inline=False, + ) + embed.set_footer(text="Sync failure notification") + embed.set_timestamp() + + webhook.add_embed(embed) + response = webhook.execute() + try: + response.raise_for_status() + except Exception as e: + raise Exception(f"Discord notification failed: {e}\n{response.text}") from e diff --git a/leggen/notifications/telegram.py b/leggen/notifications/telegram.py index 7e51ae6..9a3301b 100644 --- a/leggen/notifications/telegram.py +++ b/leggen/notifications/telegram.py @@ -79,3 +79,38 @@ def send_transaction_message(ctx: click.Context, transactions: list): res.raise_for_status() except Exception as e: raise Exception(f"Telegram notification failed: {e}\n{res.text}") from e + + +def send_sync_failure_notification(ctx: click.Context, notification: dict): + token = ctx.obj["notifications"]["telegram"]["token"] + chat_id = ctx.obj["notifications"]["telegram"]["chat_id"] + bot_url = f"https://api.telegram.org/bot{token}/sendMessage" + info("Sending sync failure notification to Telegram") + + message = "*🚨 [Leggen](https://github.com/elisiariocouto/leggen)*\n" + message += "*Sync Failed*\n\n" + message += escape_markdown(f"Error: {notification['error']}\n") + + if notification.get("type") == "sync_final_failure": + message += escape_markdown( + f"❌ Final failure after {notification['retry_count']} attempts\n" + ) + else: + message += escape_markdown( + f"🔄 Attempt {notification['retry_count']}/{notification['max_retries']}\n" + ) + message += escape_markdown("Will retry automatically...\n") + + res = requests.post( + bot_url, + json={ + "chat_id": chat_id, + "text": message, + "parse_mode": "MarkdownV2", + }, + ) + + try: + res.raise_for_status() + except Exception as e: + raise Exception(f"Telegram notification failed: {e}\n{res.text}") from e diff --git a/leggen/services/notification_service.py b/leggen/services/notification_service.py index 3d1858b..8014530 100644 --- a/leggen/services/notification_service.py +++ b/leggen/services/notification_service.py @@ -289,3 +289,69 @@ class NotificationService: except Exception as e: logger.error(f"Failed to send Telegram expiry notification: {e}") raise + + async def send_sync_failure_notification( + self, notification_data: Dict[str, Any] + ) -> None: + """Send notification about sync failure""" + if self._is_discord_enabled(): + await self._send_discord_sync_failure(notification_data) + + if self._is_telegram_enabled(): + await self._send_telegram_sync_failure(notification_data) + + async def _send_discord_sync_failure( + self, notification_data: Dict[str, Any] + ) -> None: + """Send Discord sync failure notification""" + try: + import click + + from leggen.notifications.discord import send_sync_failure_notification + + # Create a mock context with the webhook + ctx = click.Context(click.Command("sync_failure")) + ctx.obj = { + "notifications": { + "discord": { + "webhook": self.notifications_config.get("discord", {}).get( + "webhook" + ) + } + } + } + + # Send sync failure notification using the actual implementation + send_sync_failure_notification(ctx, notification_data) + logger.info(f"Sent Discord sync failure notification: {notification_data}") + except Exception as e: + logger.error(f"Failed to send Discord sync failure notification: {e}") + raise + + async def _send_telegram_sync_failure( + self, notification_data: Dict[str, Any] + ) -> None: + """Send Telegram sync failure notification""" + try: + import click + + from leggen.notifications.telegram import send_sync_failure_notification + + # Create a mock context with the telegram config + ctx = click.Context(click.Command("sync_failure")) + telegram_config = self.notifications_config.get("telegram", {}) + ctx.obj = { + "notifications": { + "telegram": { + "token": telegram_config.get("token"), + "chat_id": telegram_config.get("chat_id"), + } + } + } + + # Send sync failure notification using the actual implementation + send_sync_failure_notification(ctx, notification_data) + logger.info(f"Sent Telegram sync failure notification: {notification_data}") + except Exception as e: + logger.error(f"Failed to send Telegram sync failure notification: {e}") + raise