Coverage for custom_components / supernotify / transports / sms.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-06-11 22:18 +0000

1from __future__ import annotations 

2 

3import logging 

4from typing import TYPE_CHECKING, Any 

5 

6from homeassistant.components.notify.const import ATTR_DATA, ATTR_TARGET 

7 

8from custom_components.supernotify.const import ( 

9 ATTR_PHONE, 

10 OPTION_MESSAGE_USAGE, 

11 OPTION_SIMPLIFY_TEXT, 

12 OPTION_STRIP_URLS, 

13 OPTION_TARGET_CATEGORIES, 

14 TRANSPORT_SMS, 

15) 

16from custom_components.supernotify.model import DebugTrace, MessageOnlyPolicy, TransportConfig, TransportFeature 

17from custom_components.supernotify.transport import ( 

18 Transport, 

19) 

20 

21if TYPE_CHECKING: 

22 from custom_components.supernotify.envelope import Envelope 

23 

24RE_VALID_PHONE = r"^(\+\d{1,3})?\s?\(?\d{1,4}\)?[\s.-]?\d{3}[\s.-]?\d{4}$" 

25 

26_LOGGER = logging.getLogger(__name__) 

27 

28 

29class SMSTransport(Transport): 

30 name = TRANSPORT_SMS 

31 MAX_MESSAGE_LENGTH = 158 

32 

33 def __init__(self, *args: Any, **kwargs: Any) -> None: 

34 super().__init__(*args, **kwargs) 

35 

36 @property 

37 def supported_features(self) -> TransportFeature: 

38 return TransportFeature.MESSAGE | TransportFeature.TITLE 

39 

40 @property 

41 def default_config(self) -> TransportConfig: 

42 config = TransportConfig() 

43 config.delivery_defaults.options = { 

44 OPTION_SIMPLIFY_TEXT: True, 

45 OPTION_STRIP_URLS: False, 

46 OPTION_MESSAGE_USAGE: MessageOnlyPolicy.COMBINE_TITLE, 

47 OPTION_TARGET_CATEGORIES: [ATTR_PHONE], 

48 } 

49 return config 

50 

51 def validate_action(self, action: str | None) -> bool: 

52 """Override in subclass if transport has fixed action or doesn't require one""" 

53 return action is not None 

54 

55 async def deliver(self, envelope: Envelope, debug_trace: DebugTrace | None = None) -> bool: # noqa: ARG002 

56 _LOGGER.debug("SUPERNOTIFY notify_sms: %s", envelope.delivery_name) 

57 

58 data: dict[str, Any] = envelope.data or {} 

59 mobile_numbers = envelope.target.phone or [] 

60 

61 if not envelope.message: 

62 _LOGGER.warning("SUPERNOTIFY notify_sms: No message to send") 

63 return False 

64 

65 message: str = envelope.message or "" 

66 if len(message) > self.MAX_MESSAGE_LENGTH: 

67 _LOGGER.debug( 

68 "SUPERNOTIFY notify_sms: Message too long (%d characters), truncating to %d characters", 

69 len(message), 

70 self.MAX_MESSAGE_LENGTH, 

71 ) 

72 

73 action_data = {"message": message[: self.MAX_MESSAGE_LENGTH], ATTR_TARGET: mobile_numbers} 

74 if data and data.get("data"): 

75 action_data[ATTR_DATA] = data.get("data", {}) 

76 

77 return await self.call_action(envelope, action_data=action_data)