rcs 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rcs-0.1.5/PKG-INFO +23 -0
- rcs-0.1.5/README.md +5 -0
- rcs-0.1.5/pyproject.toml +17 -0
- rcs-0.1.5/rcs/__init__.py +13 -0
- rcs-0.1.5/rcs/main.py +143 -0
- rcs-0.1.5/rcs/pilio_types.py +584 -0
rcs-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: rcs
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Ivan Zhang
|
|
6
|
+
Author-email: ivanzhangofficial@gmail.com
|
|
7
|
+
Requires-Python: >=3.8,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: fastapi[standard] (>=0.114.2,<0.115.0)
|
|
15
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
1) pip install pilio
|
|
19
|
+
2) Make sure to have your current API key
|
|
20
|
+
3) Set a webhook url to listen to messages (i.e. using ngrok or https://theboroer.github.io/localtunnel-www/)
|
|
21
|
+
4) pinn.send to send message
|
|
22
|
+
5) pinn.on_message to open webhook to receive messages
|
|
23
|
+
|
rcs-0.1.5/README.md
ADDED
rcs-0.1.5/pyproject.toml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "rcs"
|
|
3
|
+
version = "0.1.5"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["Ivan Zhang <ivanzhangofficial@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.8"
|
|
10
|
+
fastapi = {extras = ["standard"], version = "^0.114.2"}
|
|
11
|
+
requests = "^2.32.3"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["poetry-core"]
|
|
17
|
+
build-backend = "poetry.core.masonry.api"
|
rcs-0.1.5/rcs/main.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from fastapi import FastAPI, Request
|
|
3
|
+
import uvicorn
|
|
4
|
+
from pilio.pilio_types import *
|
|
5
|
+
|
|
6
|
+
PINNACLE_SERVER_URL = "https://www.trypinnacle.dev/api"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
app = FastAPI()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Pinnacle:
|
|
13
|
+
def __init__(self, api_key: str, webhook_url: str):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the Pinnacle object with the API key and webhook URL.
|
|
16
|
+
:param api_key: The API key for authentication.
|
|
17
|
+
:param webhook_url: The URL to receive incoming messages.
|
|
18
|
+
|
|
19
|
+
:raises Exception: If the initialization fails.
|
|
20
|
+
"""
|
|
21
|
+
self.api_key = api_key
|
|
22
|
+
self.webhook_url = webhook_url
|
|
23
|
+
self.headers = {
|
|
24
|
+
"PINNACLE-API-KEY": f"Bearer {self.api_key}",
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
}
|
|
27
|
+
response = requests.post(
|
|
28
|
+
f"{PINNACLE_SERVER_URL}/update_settings",
|
|
29
|
+
json={"webhook_url": self.webhook_url},
|
|
30
|
+
headers=self.headers,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if response.status_code != 200:
|
|
34
|
+
raise Exception(
|
|
35
|
+
f"Failed to send message: {response.status_code}, {response.json().get('error')}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def send(
|
|
39
|
+
self,
|
|
40
|
+
message: Message,
|
|
41
|
+
phone_number: str,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Send a message to the specified phone numbers.
|
|
45
|
+
:param message: The message to send.
|
|
46
|
+
:param phone_numbers: A list of phone numbers to send the message to.
|
|
47
|
+
:raises Exception: If the message sending fails.
|
|
48
|
+
:return: The response from the server.
|
|
49
|
+
"""
|
|
50
|
+
message_type = self.get_message_type(message)
|
|
51
|
+
|
|
52
|
+
response = requests.post(
|
|
53
|
+
f"{PINNACLE_SERVER_URL}/send",
|
|
54
|
+
json={
|
|
55
|
+
"message": (
|
|
56
|
+
{"cards": [message.to_dict()]}
|
|
57
|
+
if message_type == "card"
|
|
58
|
+
else message.to_dict()
|
|
59
|
+
),
|
|
60
|
+
"message_type": message_type,
|
|
61
|
+
"phone_number": phone_number,
|
|
62
|
+
},
|
|
63
|
+
headers=self.headers,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
responseData = response.json()
|
|
67
|
+
|
|
68
|
+
if response.status_code != 200:
|
|
69
|
+
raise Exception(
|
|
70
|
+
f"Failed to send message: {response.status_code}, {responseData.get('error')}"
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
return {
|
|
74
|
+
"message": str(responseData.get("message")),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def check_rcs_status(self, phone_number: str) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Check the status of an RCS message.
|
|
80
|
+
:param message_id: The ID of the message to check.
|
|
81
|
+
:raises Exception: If the status check fails.
|
|
82
|
+
:return: The RCS status of the phone number.
|
|
83
|
+
"""
|
|
84
|
+
response = requests.get(
|
|
85
|
+
f"{PINNACLE_SERVER_URL}/check_rcs",
|
|
86
|
+
headers=self.headers,
|
|
87
|
+
params={"phone_number": phone_number},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
responseData = response.json()
|
|
91
|
+
if response.status_code != 200:
|
|
92
|
+
raise Exception(
|
|
93
|
+
f"Failed to check RCS status: {response.status_code}, {responseData.get('error')}"
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
return responseData.get("rcsEnabled", False)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def get_message_type(
|
|
100
|
+
message: Message,
|
|
101
|
+
) -> Union[
|
|
102
|
+
Literal["basic-rcs"],
|
|
103
|
+
Literal["media"],
|
|
104
|
+
Literal["card"],
|
|
105
|
+
Literal["carousel"],
|
|
106
|
+
Literal["sms"],
|
|
107
|
+
]:
|
|
108
|
+
"""
|
|
109
|
+
Get the type of the message.
|
|
110
|
+
:param message: The message to check.
|
|
111
|
+
:raises ValueError: If the message type is invalid.
|
|
112
|
+
:return: The type of the message as a string.
|
|
113
|
+
"""
|
|
114
|
+
if isinstance(message, RCSBasicMessage):
|
|
115
|
+
return "basic-rcs"
|
|
116
|
+
elif isinstance(message, RCSMediaMessage):
|
|
117
|
+
return "media"
|
|
118
|
+
elif isinstance(message, Card):
|
|
119
|
+
return "card"
|
|
120
|
+
elif isinstance(message, Carousel):
|
|
121
|
+
return "carousel"
|
|
122
|
+
elif isinstance(message, SMSMessage):
|
|
123
|
+
return "sms"
|
|
124
|
+
raise ValueError("Invalid message type")
|
|
125
|
+
|
|
126
|
+
def on_message(
|
|
127
|
+
self,
|
|
128
|
+
callback: OnMessageCallback,
|
|
129
|
+
pathname: str = "/",
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
Set a callback function to be called when a message is received.
|
|
133
|
+
:param pathname: The path to the webhook endpoint.
|
|
134
|
+
:param callback: The callback function to set. Callback function will receive a PayloadData object.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
@app.post(pathname)
|
|
138
|
+
async def inbound(request: Request):
|
|
139
|
+
payload = await request.json()
|
|
140
|
+
payload_data = PayloadData(**payload)
|
|
141
|
+
callback(payload_data)
|
|
142
|
+
|
|
143
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
from typing import Callable, Optional, TypedDict, Union, List, Literal, Tuple
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ButtonPayload(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Represents the payload data related to the button.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
title (str): The title of the button.
|
|
12
|
+
payload (str): The payload associated with the button.
|
|
13
|
+
execute (str): The execute command associated with the action.
|
|
14
|
+
sent (str): The timestamp when the message was sent. Format (yyyy-mm-ddThh:mm:ss) in GMT 0
|
|
15
|
+
fromNum (str): The sender's phone number.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
title: Optional[str] = None
|
|
19
|
+
payload: Optional[str] = None
|
|
20
|
+
execute: Optional[str] = None
|
|
21
|
+
sent: str
|
|
22
|
+
fromNum: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Types related to crafting a message
|
|
26
|
+
MediaType = Union[
|
|
27
|
+
Literal["text"],
|
|
28
|
+
Literal["image"],
|
|
29
|
+
Literal["audio"],
|
|
30
|
+
Literal["video"],
|
|
31
|
+
Literal["file"],
|
|
32
|
+
]
|
|
33
|
+
"""
|
|
34
|
+
MediaType represents the type of media that can be sent in a message.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
text: Represents a text message.
|
|
38
|
+
image: Represents an image message.
|
|
39
|
+
audio: Represents an audio message.
|
|
40
|
+
video: Represents a video message.
|
|
41
|
+
file: Represents a file message.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MessagePayload(BaseModel):
|
|
46
|
+
"""
|
|
47
|
+
Represents the payload data related to the message.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
text (str): The text of the message.
|
|
51
|
+
mediaType (MediaType): The type of media being sent.
|
|
52
|
+
media (str): The URL of the media being sent.
|
|
53
|
+
sent (str): The timestamp when the message was sent. Format (yyyy-mm-ddThh:mm:ss) in GMT 0
|
|
54
|
+
fromNum (str): The sender's phone number.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
text: Optional[str] = None
|
|
58
|
+
mediaType: Optional[MediaType] = None
|
|
59
|
+
media: Optional[str] = None
|
|
60
|
+
sent: str
|
|
61
|
+
fromNum: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PayloadData(BaseModel):
|
|
65
|
+
"""
|
|
66
|
+
Represents the payload data received from a message.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
messageType (str): The type of the message (e.g., "message", "postback").
|
|
70
|
+
buttonPayload (Optional[ButtonPayload]): The payload data related to the button, if applicable.
|
|
71
|
+
messagePayload (Optional[MessagePayload]): The payload data related to the message, if applicable.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
messageType: str
|
|
75
|
+
buttonPayload: Optional[ButtonPayload] = None
|
|
76
|
+
messagePayload: Optional[MessagePayload] = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
OnMessageCallback = Callable[[PayloadData], None]
|
|
80
|
+
|
|
81
|
+
ActionType = Union[
|
|
82
|
+
Literal["weburl"],
|
|
83
|
+
Literal["call"],
|
|
84
|
+
Literal["postback"],
|
|
85
|
+
Literal["shareLocation"],
|
|
86
|
+
Literal["viewLocation"],
|
|
87
|
+
Literal["calendar"],
|
|
88
|
+
]
|
|
89
|
+
"""
|
|
90
|
+
ActionType represents the type of action that can be performed.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
weburl: Represents an action that opens a web URL.
|
|
94
|
+
call: Represents an action that initiates a phone call.
|
|
95
|
+
postback: Represents an action that sends data back to the server.
|
|
96
|
+
shareLocation: Represents an action that requests the user's location.
|
|
97
|
+
viewLocation: Represents an action that displays a location.
|
|
98
|
+
calendar: Represents an action that creates a calendar event.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class LatLng(TypedDict):
|
|
103
|
+
"""
|
|
104
|
+
Latitude and Longitude coordinates.
|
|
105
|
+
|
|
106
|
+
Attributes:
|
|
107
|
+
lat (float): Latitude.
|
|
108
|
+
lng (float): Longitude.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
lat: float
|
|
112
|
+
lng: float
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Action:
|
|
116
|
+
"""
|
|
117
|
+
Represents an action that can be performed in response to a user interaction.
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
title (str): The title of the action (e.g., text for button or quick reply).
|
|
121
|
+
action_type (ActionType): The type of action to be performed.
|
|
122
|
+
payload (Optional[str]): The payload to be sent with the action.
|
|
123
|
+
execute (Optional[str]): Metadata sent when the button/quick reply is clicked.
|
|
124
|
+
query (Optional[str]): The query for location search.
|
|
125
|
+
lat_lng (Optional[LatLng]): The latitude and longitude of a location.
|
|
126
|
+
label (Optional[str]): The label for the location.
|
|
127
|
+
start_time (Optional[str]): The start time of a calendar event.
|
|
128
|
+
end_time (Optional[str]): The end time of a calendar event.
|
|
129
|
+
event_title (Optional[str]): The title of the calendar event.
|
|
130
|
+
event_description (Optional[str]): The description of the calendar event.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
title: str
|
|
134
|
+
action_type: Optional[ActionType] = None
|
|
135
|
+
|
|
136
|
+
# weburl, call, postback
|
|
137
|
+
payload: Optional[str] = None
|
|
138
|
+
|
|
139
|
+
# postback
|
|
140
|
+
execute: Optional[str] = None
|
|
141
|
+
|
|
142
|
+
# viewLocation
|
|
143
|
+
query: Optional[str] = None
|
|
144
|
+
lat_lng: Optional[LatLng] = None
|
|
145
|
+
label: Optional[str] = None
|
|
146
|
+
|
|
147
|
+
# calendar
|
|
148
|
+
start_time: Optional[str] = None
|
|
149
|
+
end_time: Optional[str] = None
|
|
150
|
+
event_title: Optional[str] = None
|
|
151
|
+
event_description: Optional[str] = None
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
title: str,
|
|
156
|
+
):
|
|
157
|
+
"""
|
|
158
|
+
Initialize an Action object.
|
|
159
|
+
:param title: The title of the action (e.g., text for button or quick reply). The max length is 25 characters.
|
|
160
|
+
:raises ValueError: If the title exceeds 25 characters.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
if len(title) > 25:
|
|
164
|
+
raise ValueError("Title cannot exceed 25 characters.")
|
|
165
|
+
|
|
166
|
+
self.title = title[:25]
|
|
167
|
+
|
|
168
|
+
def weburl(self, url: str):
|
|
169
|
+
"""
|
|
170
|
+
Set the action type to weburl and assign the URL to open.
|
|
171
|
+
:param url: The URL to open when the action is triggered.
|
|
172
|
+
:raises ValueError: If the URL is invalid.
|
|
173
|
+
:return: self
|
|
174
|
+
"""
|
|
175
|
+
if not url.startswith("http://") and not url.startswith("https://"):
|
|
176
|
+
raise ValueError(
|
|
177
|
+
"Invalid URL format. It should start with 'http://' or 'https://'."
|
|
178
|
+
)
|
|
179
|
+
self.action_type = "weburl"
|
|
180
|
+
self.payload = url
|
|
181
|
+
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def call(self, phone_number: str):
|
|
185
|
+
"""
|
|
186
|
+
Set the action type to call and assign the phone number to call.
|
|
187
|
+
:param phone_number: The phone number to call (should be in the E.164 format +[country code][number]).
|
|
188
|
+
:raises ValueError: If the phone number is invalid.
|
|
189
|
+
:return: self
|
|
190
|
+
"""
|
|
191
|
+
if (
|
|
192
|
+
not phone_number.startswith("+")
|
|
193
|
+
or not phone_number[1:].isdigit()
|
|
194
|
+
or len(phone_number) < 10
|
|
195
|
+
):
|
|
196
|
+
raise ValueError(
|
|
197
|
+
"Invalid phone number format. It should start with '+' followed by country code and phone number."
|
|
198
|
+
)
|
|
199
|
+
self.action_type = "call"
|
|
200
|
+
self.payload = phone_number
|
|
201
|
+
return self
|
|
202
|
+
|
|
203
|
+
def postback(self, payload: str, execute: Optional[str] = None):
|
|
204
|
+
"""
|
|
205
|
+
Set the action type to postback and assign the payload to send back to the server when the button/quick reply is clicked.
|
|
206
|
+
:param payload: The payload to send back to the server (max 1,000 characters).
|
|
207
|
+
:param execute: Optional. Metadata sent when the button/quick reply is clicked.
|
|
208
|
+
:raises ValueError: If the payload exceeds 1000 characters.
|
|
209
|
+
:return: self
|
|
210
|
+
"""
|
|
211
|
+
if len(payload) > 1000:
|
|
212
|
+
raise ValueError("Payload cannot exceed 1000 characters.")
|
|
213
|
+
|
|
214
|
+
self.action_type = "postback"
|
|
215
|
+
self.payload = payload[:1000] # Truncate payload to 1000 characters
|
|
216
|
+
self.execute = execute
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
def shareLocation(self):
|
|
220
|
+
"""
|
|
221
|
+
Set the action type to shareLocation and request the user's location.
|
|
222
|
+
:return: self
|
|
223
|
+
"""
|
|
224
|
+
self.action_type = "shareLocation"
|
|
225
|
+
return self
|
|
226
|
+
|
|
227
|
+
def viewLocation(
|
|
228
|
+
self, location: Union[Tuple[float, float], str], label: Optional[str] = None
|
|
229
|
+
):
|
|
230
|
+
"""
|
|
231
|
+
Set the action type to viewLocation and send the user a location.
|
|
232
|
+
:param location: A tuple of (latitude, longitude) or a string representing the location.
|
|
233
|
+
:param label: Optional label for the location when using latitude and longitude.
|
|
234
|
+
:return: self
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
raise NotImplementedError("viewLocation method is not implemented yet.")
|
|
238
|
+
|
|
239
|
+
# self.action_type = "viewLocation"
|
|
240
|
+
# if isinstance(location, tuple):
|
|
241
|
+
# self.lat_lng = LatLng(lat=location[0], lng=location[1])
|
|
242
|
+
# self.label = label
|
|
243
|
+
# else:
|
|
244
|
+
# self.query = location
|
|
245
|
+
# return self
|
|
246
|
+
|
|
247
|
+
def calendar(
|
|
248
|
+
self,
|
|
249
|
+
start_time: datetime,
|
|
250
|
+
end_time: datetime,
|
|
251
|
+
title: str,
|
|
252
|
+
description: Optional[str] = None,
|
|
253
|
+
):
|
|
254
|
+
"""
|
|
255
|
+
Set the action type to calendar and assign the event details.
|
|
256
|
+
:param start_time: The start time of the event. Will be converted to ISO format.
|
|
257
|
+
:param end_time: The end time of the event. Will be converted to ISO format.
|
|
258
|
+
:param title: The title of the event.
|
|
259
|
+
:param description: Optional description of the event.
|
|
260
|
+
:return: self
|
|
261
|
+
"""
|
|
262
|
+
self.action_type = "calendar"
|
|
263
|
+
self.start_time = start_time.isoformat()
|
|
264
|
+
self.end_time = end_time.isoformat()
|
|
265
|
+
self.event_title = title
|
|
266
|
+
self.event_description = description
|
|
267
|
+
return self
|
|
268
|
+
|
|
269
|
+
def to_dict(self):
|
|
270
|
+
"""
|
|
271
|
+
Convert the action to a dictionary representation.
|
|
272
|
+
:return: A dictionary representation of the action.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
return self.__dict__
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class Message:
|
|
279
|
+
"""
|
|
280
|
+
Represents a message to be sent.
|
|
281
|
+
|
|
282
|
+
Attributes:
|
|
283
|
+
quick_replies: List[Action]: A list of quick reply actions.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
quick_replies: List[Action]
|
|
287
|
+
|
|
288
|
+
def __init__(self):
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
def with_quick_replies(self, quick_replies: List[Action]):
|
|
292
|
+
"""
|
|
293
|
+
Add quick replies to the message.
|
|
294
|
+
:param quick_replies: A list of quick reply actions.
|
|
295
|
+
:raises ValueError: If the number of quick replies exceeds 11.
|
|
296
|
+
:return: self
|
|
297
|
+
"""
|
|
298
|
+
if len(quick_replies) > 11:
|
|
299
|
+
raise ValueError("A card can have a maximum of 11 buttons.")
|
|
300
|
+
self.quick_replies = quick_replies
|
|
301
|
+
return self
|
|
302
|
+
|
|
303
|
+
def to_dict(self):
|
|
304
|
+
"""
|
|
305
|
+
Convert the message to a dictionary representation.
|
|
306
|
+
:return: A dictionary representation of the message.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def convert_to_dict(obj):
|
|
310
|
+
if isinstance(obj, list):
|
|
311
|
+
return [convert_to_dict(item) for item in obj]
|
|
312
|
+
elif isinstance(obj, dict):
|
|
313
|
+
return {
|
|
314
|
+
key: convert_to_dict(value)
|
|
315
|
+
for key, value in obj.items()
|
|
316
|
+
if value is not None
|
|
317
|
+
}
|
|
318
|
+
elif hasattr(obj, "__dict__"):
|
|
319
|
+
return {
|
|
320
|
+
key: convert_to_dict(value)
|
|
321
|
+
for key, value in obj.__dict__.items()
|
|
322
|
+
if value is not None
|
|
323
|
+
}
|
|
324
|
+
else:
|
|
325
|
+
return obj
|
|
326
|
+
|
|
327
|
+
return convert_to_dict(self)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
CardOrientation = Union[
|
|
331
|
+
Literal["horizontal"],
|
|
332
|
+
Literal["vertical"],
|
|
333
|
+
]
|
|
334
|
+
"""
|
|
335
|
+
CardOrientation represents the orientation of a card in a carousel.
|
|
336
|
+
|
|
337
|
+
Attributes:
|
|
338
|
+
horizontal: Represents a horizontal card orientation.
|
|
339
|
+
vertical: Represents a vertical card orientation.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
CardWidth = Union[
|
|
343
|
+
Literal["small"],
|
|
344
|
+
Literal["medium"],
|
|
345
|
+
]
|
|
346
|
+
"""
|
|
347
|
+
CardWidth represents the width of a card in a carousel.
|
|
348
|
+
|
|
349
|
+
Attributes:
|
|
350
|
+
short: Represents a small card width.
|
|
351
|
+
medium: Represents a medium card width.
|
|
352
|
+
large: Represents a large card width.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
MediaHeight = Union[
|
|
356
|
+
Literal["short"],
|
|
357
|
+
Literal["medium"],
|
|
358
|
+
Literal["tall"],
|
|
359
|
+
]
|
|
360
|
+
"""
|
|
361
|
+
MediaHeight represents the height of media in a rich card.
|
|
362
|
+
|
|
363
|
+
Attributes:
|
|
364
|
+
small: Represents a small media height.
|
|
365
|
+
medium: Represents a medium media height.
|
|
366
|
+
large: Represents a large media height.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
CardImageAlignment = Union[Literal["left"], Literal["right"]]
|
|
370
|
+
"""
|
|
371
|
+
CardImageAlignment represents the alignment of the image in a rich card.
|
|
372
|
+
|
|
373
|
+
Attributes:
|
|
374
|
+
left: Represents left alignment.
|
|
375
|
+
right: Represents right alignment.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class CardStyle(TypedDict):
|
|
380
|
+
"""
|
|
381
|
+
Represents the style of a card in a carousel.
|
|
382
|
+
|
|
383
|
+
Attributes:
|
|
384
|
+
orientation (Optional[CardOrientation]): The orientation of the card.
|
|
385
|
+
width (Optional[CardWidth]): The width of the card.
|
|
386
|
+
thumbnail_url (Optional[str]): The URL of the thumbnail image.
|
|
387
|
+
image_alignment (Optional[CardImageAlignment]): The alignment of the image in the card. Only available for orientation "horizontal".
|
|
388
|
+
media_height (Optional[MediaHeight]): The height of the media. Only available for orientation "vertical".
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
orientation: Optional[CardOrientation]
|
|
392
|
+
width: Optional[CardWidth]
|
|
393
|
+
thumbnail_url: Optional[str]
|
|
394
|
+
image_alignment: Optional[CardImageAlignment]
|
|
395
|
+
media_height: Optional[MediaHeight]
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class Card(Message):
|
|
399
|
+
"""
|
|
400
|
+
Represents a card to be sent.
|
|
401
|
+
|
|
402
|
+
Attributes:
|
|
403
|
+
title (str): The title of the card.
|
|
404
|
+
subtitle (Optional[str]): The subtitle of the card.
|
|
405
|
+
image_url (Optional[str]): The URL of the image to be displayed on the card.
|
|
406
|
+
buttons (List[Action]): A list of actions (buttons) associated with the card.
|
|
407
|
+
card_style (Optional[CardStyle]): The style of the card.
|
|
408
|
+
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
title: str
|
|
412
|
+
subtitle: Optional[str]
|
|
413
|
+
image_url: Optional[str]
|
|
414
|
+
buttons: List[Action]
|
|
415
|
+
card_style: Optional[CardStyle]
|
|
416
|
+
|
|
417
|
+
def __init__(
|
|
418
|
+
self,
|
|
419
|
+
title: str,
|
|
420
|
+
subtitle: Optional[str] = None,
|
|
421
|
+
image_url: Optional[str] = None,
|
|
422
|
+
):
|
|
423
|
+
"""
|
|
424
|
+
Initialize a Card object.
|
|
425
|
+
:param title: The title of the card. The max length is 80 characters.
|
|
426
|
+
:param subtitle: The subtitle of the card. The max length is 80 characters.
|
|
427
|
+
:param image_url: The URL of the image to be displayed on the card.
|
|
428
|
+
:raises ValueError: If the title or subtitle exceeds the maximum length.
|
|
429
|
+
"""
|
|
430
|
+
if len(title) > 80:
|
|
431
|
+
raise ValueError("Title cannot exceed 80 characters.")
|
|
432
|
+
if subtitle and len(subtitle) > 80:
|
|
433
|
+
raise ValueError("Subtitle cannot exceed 80 characters.")
|
|
434
|
+
|
|
435
|
+
self.title = title
|
|
436
|
+
self.subtitle = subtitle
|
|
437
|
+
self.image_url = image_url
|
|
438
|
+
self.buttons = []
|
|
439
|
+
|
|
440
|
+
def with_buttons(self, buttons: List[Action]):
|
|
441
|
+
"""
|
|
442
|
+
Add buttons to the card.
|
|
443
|
+
:param buttons: A list of action buttons.
|
|
444
|
+
:raises ValueError: If the number of buttons exceeds 4.
|
|
445
|
+
:return: self
|
|
446
|
+
"""
|
|
447
|
+
if len(buttons) > 4:
|
|
448
|
+
raise ValueError("A card can have a maximum of 4 buttons.")
|
|
449
|
+
self.buttons = buttons
|
|
450
|
+
return self
|
|
451
|
+
|
|
452
|
+
def with_horizontal_style(
|
|
453
|
+
self,
|
|
454
|
+
width: Optional[CardWidth] = None,
|
|
455
|
+
thumbnail_url: Optional[str] = None,
|
|
456
|
+
image_alignment: Optional[CardImageAlignment] = None,
|
|
457
|
+
):
|
|
458
|
+
"""
|
|
459
|
+
Add horizontal style to the card.
|
|
460
|
+
:param width: The width of the card.
|
|
461
|
+
:param thumbnail_url: The URL of the thumbnail image.
|
|
462
|
+
:param image_alignment: The alignment of the image in the card.
|
|
463
|
+
:return: self
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
self.card_style = {
|
|
467
|
+
"width": width,
|
|
468
|
+
"orientation": "horizontal",
|
|
469
|
+
"thumbnail_url": thumbnail_url,
|
|
470
|
+
"image_alignment": image_alignment,
|
|
471
|
+
"media_height": None,
|
|
472
|
+
}
|
|
473
|
+
return self
|
|
474
|
+
|
|
475
|
+
def with_vertical_style(
|
|
476
|
+
self,
|
|
477
|
+
width: Optional[CardWidth] = None,
|
|
478
|
+
thumbnail_url: Optional[str] = None,
|
|
479
|
+
media_height: Optional[MediaHeight] = None,
|
|
480
|
+
):
|
|
481
|
+
"""
|
|
482
|
+
Add vertical style to the card.
|
|
483
|
+
:param width: The width of the card.
|
|
484
|
+
:param thumbnail_url: The URL of the thumbnail image.
|
|
485
|
+
:param media_height: The height of the media.
|
|
486
|
+
:return: self
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
self.card_style = {
|
|
490
|
+
"width": width,
|
|
491
|
+
"orientation": "vertical",
|
|
492
|
+
"thumbnail_url": thumbnail_url,
|
|
493
|
+
"image_alignment": None,
|
|
494
|
+
"media_height": media_height,
|
|
495
|
+
}
|
|
496
|
+
return self
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class Carousel(Message):
|
|
500
|
+
"""
|
|
501
|
+
Represents carousels to be sent.
|
|
502
|
+
|
|
503
|
+
Attributes:
|
|
504
|
+
cards (List[Card]): A list of cards in the carousel. Maximum of 10 cards.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
cards: List[Card]
|
|
508
|
+
|
|
509
|
+
def __init__(self, cards: List[Card]):
|
|
510
|
+
"""
|
|
511
|
+
Initialize a Carousel object.
|
|
512
|
+
:param cards: A list of Card objects.
|
|
513
|
+
:raises ValueError: If the number of cards exceeds 10
|
|
514
|
+
"""
|
|
515
|
+
super().__init__()
|
|
516
|
+
if len(cards) > 10:
|
|
517
|
+
raise ValueError("A carousel can have a maximum of 10 cards.")
|
|
518
|
+
self.cards = cards
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class RCSBasicMessage(Message):
|
|
522
|
+
"""
|
|
523
|
+
Represents a basic message to be sent.
|
|
524
|
+
|
|
525
|
+
Attributes:
|
|
526
|
+
text (str): The text of the message.
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
text: str
|
|
530
|
+
|
|
531
|
+
def __init__(self, text: str):
|
|
532
|
+
"""
|
|
533
|
+
Initialize an RCSBasicMessage object.
|
|
534
|
+
:param text: The text of the message.
|
|
535
|
+
"""
|
|
536
|
+
super().__init__()
|
|
537
|
+
self.text = text
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class RCSMediaMessage(Message):
|
|
541
|
+
"""
|
|
542
|
+
Represents a media message to be sent.
|
|
543
|
+
|
|
544
|
+
Attributes:
|
|
545
|
+
media_url (str): The URL of the media to be sent.
|
|
546
|
+
media_type (MediaType): The type of media being sent.
|
|
547
|
+
"""
|
|
548
|
+
|
|
549
|
+
media_url: str
|
|
550
|
+
media_type: MediaType
|
|
551
|
+
|
|
552
|
+
def __init__(self, media_url: str, media_type: MediaType):
|
|
553
|
+
"""
|
|
554
|
+
Initialize an RCSMediaMessage object.
|
|
555
|
+
:param media_url: The URL of the media to be sent.
|
|
556
|
+
:param media_type: The type of media being sent.
|
|
557
|
+
"""
|
|
558
|
+
super().__init__()
|
|
559
|
+
self.media_url = media_url
|
|
560
|
+
self.media_type = media_type
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class SMSMessage(Message):
|
|
564
|
+
"""
|
|
565
|
+
Represents an SMS message to be sent.
|
|
566
|
+
|
|
567
|
+
Attributes:
|
|
568
|
+
body (str): The body of the SMS message.
|
|
569
|
+
mediaUrl (Optional[str]): The URL of the media to be sent with the SMS.
|
|
570
|
+
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
body: str
|
|
574
|
+
mediaUrl: Optional[str] = None
|
|
575
|
+
|
|
576
|
+
def __init__(self, body: str, mediaUrl: Optional[str] = None):
|
|
577
|
+
"""
|
|
578
|
+
Initialize an SMSMessage object.
|
|
579
|
+
:param body: The text of the SMS message.
|
|
580
|
+
:param mediaUrl: Optional. The URL of the media to be sent with the SMS.
|
|
581
|
+
"""
|
|
582
|
+
super().__init__()
|
|
583
|
+
self.body = body
|
|
584
|
+
self.mediaUrl = mediaUrl
|