Skip to content

Commit 7858d52

Browse files
authored
Add rcs channel (#64)
* Added RCS channel class and send_rcs model * Changed send_rcs model * Added send RCS message endpoint * Added send_bulk_rcs_message * Added integrations tests * Added RcsMessageBody model tests * Added tests for send_bulk_rcs_message * Changed model and tests naming * Added tests for smsFailover * Updated sdk version and README.md * Updated RCS tests, corrected naming * Corrected naming in content_description test * Refactored RCS test * Run pre-commit hooks * Corrected integration test naming
1 parent f625ea6 commit 7858d52

File tree

18 files changed

+2062
-5
lines changed

18 files changed

+2062
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Python client for Infobip's API channels.
55
- Whatsapp -> [Docs](https://www.infobip.com/docs/api#channels/whatsapp)
66
- WebRTC -> [Docs](https://www.infobip.com/docs/api#channels/webrtc/)
77
- MMS -> [Docs](https://www.infobip.com/docs/api#channels/mms)
8+
- RCS -> [Docs](https://www.infobip.com/docs/api#channels/rcs)
89

910
#### Table of contents:
1011

infobip_channels/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .mms.channel import MMSChannel
2+
from .rcs.channel import RCSChannel
23
from .web_rtc.channel import WebRtcChannel
34
from .whatsapp.channel import WhatsAppChannel
45

5-
__all__ = ["WhatsAppChannel", "WebRtcChannel", "MMSChannel"]
6+
__all__ = ["WhatsAppChannel", "WebRtcChannel", "MMSChannel", "RCSChannel"]

infobip_channels/core/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ def validate_scheme(cls, value: str) -> str:
3535

3636

3737
class UrlLengthValidatorMixin:
38-
MAX_URL_LENGTH = 2048
38+
_MAX_URL_LENGTH = 2048
3939

4040
@classmethod
4141
def validate_url_length(cls, value: str) -> str:
4242
if not isinstance(value, str):
4343
return value
4444

45-
if len(value) > cls.MAX_URL_LENGTH:
46-
raise ValueError(f"Url length must be less than {cls.MAX_URL_LENGTH}")
45+
if len(value) > cls._MAX_URL_LENGTH:
46+
raise ValueError(f"Url length must be less than {cls._MAX_URL_LENGTH}")
4747

4848
return value
4949

infobip_channels/rcs/Models/__init__.py

Whitespace-only changes.

infobip_channels/rcs/Models/body/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from typing import List
2+
3+
from infobip_channels.core.models import MessageBodyBase
4+
from infobip_channels.rcs.Models.body.send_rcs_message import RCSMessageBody
5+
6+
7+
class RCSMessageBodyList(MessageBodyBase):
8+
messages: List[RCSMessageBody]
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
from enum import Enum
2+
from typing import List, Optional, Union
3+
4+
try:
5+
from typing import Literal
6+
except ImportError:
7+
from typing_extensions import Literal
8+
9+
from pydantic import AnyHttpUrl, Field, confloat, constr, validator
10+
11+
from infobip_channels.core.models import (
12+
CamelCaseModel,
13+
MessageBodyBase,
14+
UrlLengthValidatorMixin,
15+
)
16+
17+
18+
class ValidityPeriodTimeUnitEnum(str, Enum):
19+
SECONDS = "SECONDS"
20+
MINUTES = "MINUTES"
21+
HOURS = "HOURS"
22+
DAYS = "DAYS"
23+
24+
25+
class CardWidth(str, Enum):
26+
SMALL = "SMALL"
27+
MEDIUM = "MEDIUM"
28+
29+
30+
class HeightEnum(str, Enum):
31+
SHORT = "SHORT"
32+
MEDIUM = "MEDIUM"
33+
TALL = "TALL"
34+
35+
36+
class AlignmentEnum(str, Enum):
37+
LEFT = "LEFT"
38+
RIGHT = "RIGHT"
39+
40+
41+
class OrientationEnum(str, Enum):
42+
HORIZONTAL = "HORIZONTAL"
43+
VERTICAL = "VERTICAL"
44+
45+
46+
class FileProperties(CamelCaseModel, UrlLengthValidatorMixin):
47+
url: AnyHttpUrl
48+
49+
_MAX_URL_LENGTH = 1000
50+
51+
@validator("url", pre=True)
52+
def validate_url_length(cls, value: str) -> str:
53+
return super().validate_url_length(value)
54+
55+
56+
class ThumbnailProperties(CamelCaseModel, UrlLengthValidatorMixin):
57+
url: AnyHttpUrl
58+
59+
_MAX_URL_LENGTH = 1000
60+
61+
@validator("url", pre=True)
62+
def validate_url_length(cls, value: str) -> str:
63+
return super().validate_url_length(value)
64+
65+
66+
class Suggestion(CamelCaseModel):
67+
text: constr(min_length=1, max_length=25)
68+
postback_data: constr(min_length=1, max_length=2048)
69+
70+
71+
class SuggestionReply(Suggestion):
72+
type: Literal["REPLY"]
73+
74+
75+
class SuggestionOpenUrl(Suggestion, UrlLengthValidatorMixin):
76+
type: Literal["OPEN_URL"]
77+
url: AnyHttpUrl
78+
79+
_MAX_URL_LENGTH = 1000
80+
81+
@validator("url", pre=True)
82+
def validate_url_length(cls, value: str) -> str:
83+
return super().validate_url_length(value)
84+
85+
86+
class SuggestionDialPhone(Suggestion):
87+
type: Literal["DIAL_PHONE"]
88+
phone_number: Optional[constr(regex=r"\+?\d{5,15}")] = None # noqa: F722
89+
90+
91+
class SuggestionShowLocation(Suggestion):
92+
type: Literal["SHOW_LOCATION"]
93+
latitude: confloat(ge=-90, le=90)
94+
longitude: confloat(ge=-180, le=180)
95+
label: Optional[constr(min_length=1, max_length=100)] = None
96+
97+
98+
class SuggestionRequestLocation(Suggestion):
99+
type: Literal["REQUEST_LOCATION"]
100+
101+
102+
class CardMedia(CamelCaseModel):
103+
file: FileProperties
104+
thumbnail: Optional[ThumbnailProperties] = None
105+
height: HeightEnum
106+
107+
108+
class CardContent(CamelCaseModel):
109+
title: Optional[constr(min_length=1, max_length=200)] = None
110+
description: Optional[constr(min_length=1, max_length=2000)] = None
111+
media: Optional[CardMedia] = None
112+
suggestions: Optional[
113+
List[
114+
Union[
115+
SuggestionShowLocation,
116+
SuggestionDialPhone,
117+
SuggestionOpenUrl,
118+
SuggestionReply,
119+
SuggestionRequestLocation,
120+
]
121+
]
122+
] = None
123+
124+
@validator("suggestions")
125+
def validate_suggestions(cls, suggestions: List[Suggestion]) -> List[Suggestion]:
126+
if len(suggestions) > 4:
127+
raise ValueError("There can be only four suggestions in a card")
128+
return suggestions
129+
130+
131+
class Contents(CardContent):
132+
pass
133+
134+
135+
class ContentCarousel(CamelCaseModel):
136+
type: Literal["CAROUSEL"]
137+
card_width: CardWidth
138+
contents: List[Contents]
139+
suggestions: Optional[
140+
List[
141+
Union[
142+
SuggestionShowLocation,
143+
SuggestionDialPhone,
144+
SuggestionOpenUrl,
145+
SuggestionReply,
146+
SuggestionRequestLocation,
147+
]
148+
]
149+
] = None
150+
151+
@validator("contents")
152+
def validate_contents(cls, contents: List[Contents]) -> List[Contents]:
153+
if len(contents) < 2 or len(contents) > 10:
154+
raise ValueError("There can be only 2 - 10 content objects in a Carousel")
155+
156+
return contents
157+
158+
159+
class ContentCard(CamelCaseModel):
160+
type: Literal["CARD"]
161+
orientation: OrientationEnum
162+
alignment: AlignmentEnum
163+
content: CardContent
164+
suggestions: Optional[
165+
List[
166+
Union[
167+
SuggestionShowLocation,
168+
SuggestionDialPhone,
169+
SuggestionOpenUrl,
170+
SuggestionReply,
171+
SuggestionRequestLocation,
172+
]
173+
]
174+
] = None
175+
176+
177+
class ContentFile(CamelCaseModel):
178+
type: Literal["FILE"]
179+
file: FileProperties
180+
thumbnail: Optional[ThumbnailProperties] = None
181+
182+
183+
class ContentText(CamelCaseModel):
184+
type: Literal["TEXT"]
185+
text: constr(min_length=1, max_length=1000)
186+
suggestions: Optional[
187+
List[
188+
Union[
189+
SuggestionShowLocation,
190+
SuggestionDialPhone,
191+
SuggestionOpenUrl,
192+
SuggestionReply,
193+
SuggestionRequestLocation,
194+
]
195+
]
196+
] = []
197+
198+
199+
class SmsFailover(CamelCaseModel):
200+
from_number: constr(min_length=1) = Field(alias="from")
201+
text: constr(min_length=1)
202+
validity_period: Optional[int] = None
203+
validity_period_time_unit: Optional[ValidityPeriodTimeUnitEnum] = None
204+
205+
206+
class RCSMessageBody(MessageBodyBase):
207+
from_number: Optional[str] = Field(alias="from")
208+
to: str
209+
validity_period: Optional[int] = None
210+
validity_period_time_unit: Optional[ValidityPeriodTimeUnitEnum] = None
211+
content: Union[ContentCarousel, ContentCard, ContentFile, ContentText]
212+
sms_failover: Optional[SmsFailover] = None
213+
notify_url: Optional[str] = None
214+
callback_data: Optional[str] = None
215+
message_id: Optional[str] = None

infobip_channels/rcs/Models/response/__init__.py

Whitespace-only changes.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from typing import Dict, List, Optional
2+
3+
from infobip_channels.core.models import CamelCaseModel, ResponseBase, ResponseStatus
4+
5+
6+
class ServiceException(CamelCaseModel):
7+
message_id: str
8+
text: str
9+
validation_errors: Optional[Dict[str, List[str]]] = None
10+
11+
12+
class RequestError(CamelCaseModel):
13+
service_exception: ServiceException
14+
15+
16+
class RCSResponseError(ResponseBase):
17+
request_error: RequestError
18+
19+
20+
class SendRcsResponseMessage(CamelCaseModel):
21+
to: Optional[str] = None
22+
message_count: Optional[int] = None
23+
message_id: Optional[str] = None
24+
status: ResponseStatus
25+
26+
27+
class RCSResponseOK(ResponseBase):
28+
messages: List[SendRcsResponseMessage]
29+
30+
31+
class Message(CamelCaseModel):
32+
messages: List[SendRcsResponseMessage]
33+
34+
35+
class RCSResponseOKList(ResponseBase):
36+
list: List[Message]

infobip_channels/rcs/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)