#!/usr/bin/env run.sh """Telegram notification module for Kidcam.""" # : out kidcam-notifier # : dep python-telegram-bot # : dep pytest # : dep pytest-asyncio import asyncio import datetime as dt import logging import Omni.Test as Test import os import pytest import typing import unittest.mock as mock class TelegramNotifier: def __init__(self, bot_token: str, chat_id: str) -> None: self.bot_token = bot_token self.chat_id = chat_id self.logger = logging.getLogger(__name__) async def send_notification( self, stream_url: str, message: str = "Kids are playing!" ) -> bool: try: import telegram as tg # type: ignore[import-not-found,unused-ignore] bot = tg.Bot(token=self.bot_token) timestamp = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S") full_message = ( f"{message}\n\nšŸŽ„ Watch: {stream_url}\nšŸ“… {timestamp}" ) await bot.send_message(chat_id=self.chat_id, text=full_message) self.logger.info("Notification sent: %s", message) return True except Exception as e: self.logger.exception("Failed to send notification: %s", e) return False async def send_stream_ended(self) -> bool: timestamp = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S") message = f"Stream ended\nšŸ“… {timestamp}" try: import telegram as tg # type: ignore[import-not-found,unused-ignore] bot = tg.Bot(token=self.bot_token) await bot.send_message(chat_id=self.chat_id, text=message) self.logger.info("Stream ended notification sent") return True except Exception as e: self.logger.exception("Failed to send stream ended notification: %s", e) return False def from_env() -> TelegramNotifier: bot_token = os.getenv("TELEGRAM_BOT_TOKEN") chat_id = os.getenv("TELEGRAM_CHAT_ID") if not bot_token: msg = "TELEGRAM_BOT_TOKEN environment variable not set" raise ValueError(msg) if not chat_id: msg = "TELEGRAM_CHAT_ID environment variable not set" raise ValueError(msg) return TelegramNotifier(bot_token=bot_token, chat_id=chat_id) async def main() -> None: notifier = from_env() stream_url = "https://example.com/stream/123" await notifier.send_notification(stream_url) class TestTelegramNotifier(Test.TestCase): @mock.patch.dict( os.environ, {"TELEGRAM_BOT_TOKEN": "test_token", "TELEGRAM_CHAT_ID": "test_chat"}, ) def test_from_env(self) -> None: notifier = from_env() self.assertEqual(notifier.bot_token, "test_token") self.assertEqual(notifier.chat_id, "test_chat") def test_from_env_missing_token(self) -> None: with pytest.raises(ValueError): from_env() @mock.patch("telegram.Bot") async def test_send_notification(self, mock_bot_class: typing.Any) -> None: mock_bot = mock.AsyncMock() mock_bot_class.return_value = mock_bot notifier = TelegramNotifier(bot_token="test_token", chat_id="test_chat") result = await notifier.send_notification("https://example.com/stream") self.assertTrue(result) mock_bot.send_message.assert_called_once() call_args = mock_bot.send_message.call_args self.assertEqual(call_args.kwargs["chat_id"], "test_chat") self.assertIn("Kids are playing!", call_args.kwargs["text"]) self.assertIn("https://example.com/stream", call_args.kwargs["text"]) @mock.patch("telegram.Bot") async def test_send_stream_ended(self, mock_bot_class: typing.Any) -> None: mock_bot = mock.AsyncMock() mock_bot_class.return_value = mock_bot notifier = TelegramNotifier(bot_token="test_token", chat_id="test_chat") result = await notifier.send_stream_ended() self.assertTrue(result) mock_bot.send_message.assert_called_once() call_args = mock_bot.send_message.call_args self.assertIn("Stream ended", call_args.kwargs["text"]) @mock.patch("telegram.Bot") async def test_send_notification_error( self, mock_bot_class: typing.Any ) -> None: mock_bot = mock.AsyncMock() mock_bot.send_message.side_effect = Exception("Network error") mock_bot_class.return_value = mock_bot notifier = TelegramNotifier(bot_token="test_token", chat_id="test_chat") result = await notifier.send_notification("https://example.com/stream") self.assertFalse(result) def test() -> None: import Omni.App as App Test.run(App.Area.Test, [TestTelegramNotifier]) if __name__ == "__main__": asyncio.run(main())