python-termii 0.1.0__py3-none-any.whl
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.
- python_termii-0.1.0.dist-info/METADATA +388 -0
- python_termii-0.1.0.dist-info/RECORD +23 -0
- python_termii-0.1.0.dist-info/WHEEL +5 -0
- python_termii-0.1.0.dist-info/licenses/LICENSE +21 -0
- python_termii-0.1.0.dist-info/top_level.txt +1 -0
- termii_py/__init__.py +10 -0
- termii_py/client.py +64 -0
- termii_py/config.py +12 -0
- termii_py/http/__init__.py +8 -0
- termii_py/http/request_handler.py +141 -0
- termii_py/http/request_response.py +64 -0
- termii_py/services/__init__.py +14 -0
- termii_py/services/campaign.py +175 -0
- termii_py/services/contact.py +167 -0
- termii_py/services/message.py +191 -0
- termii_py/services/number.py +75 -0
- termii_py/services/phonebook.py +137 -0
- termii_py/services/sender_id.py +78 -0
- termii_py/services/template.py +99 -0
- termii_py/utils/__init__.py +0 -0
- termii_py/utils/exception.py +11 -0
- termii_py/value_object/__init__.py +5 -0
- termii_py/value_object/phone_number.py +74 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RequestResponse class for handling API responses.
|
|
3
|
+
Classes:
|
|
4
|
+
RequestResponse
|
|
5
|
+
Methods:
|
|
6
|
+
handle_response: Processes an HTTP response and returns a RequestResponse instance.
|
|
7
|
+
"""
|
|
8
|
+
from hmac import compare_digest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestResponse:
|
|
12
|
+
"""
|
|
13
|
+
Represents a standardized API response.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
status_code (int): The HTTP status code of the response.
|
|
17
|
+
status (str): A string indicating the status of the response (e.g., "ok" or "error").
|
|
18
|
+
message (str | dict): The response message, which can be a string or a dictionary.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, status_code: int, status, message: str | dict):
|
|
22
|
+
"""
|
|
23
|
+
Initializes a RequestResponse instance.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
status_code (int): The HTTP status code of the response.
|
|
27
|
+
status (str): A string indicating the status of the response.
|
|
28
|
+
message (str | dict): The response message.
|
|
29
|
+
"""
|
|
30
|
+
self.status_code = status_code
|
|
31
|
+
self.status = status
|
|
32
|
+
self.message = message
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def handle_response(response):
|
|
36
|
+
"""
|
|
37
|
+
Processes an HTTP response and returns a RequestResponse instance.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
response: The HTTP response object.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
RequestResponse: An instance representing the API response.
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
This method checks the HTTP status code of the response and returns a RequestResponse instance with a
|
|
47
|
+
standardized status and message.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if compare_digest(str(response.status_code), "200") or compare_digest(str(response.status_code), "201"):
|
|
51
|
+
return RequestResponse(
|
|
52
|
+
status_code=response.status_code,
|
|
53
|
+
status="ok",
|
|
54
|
+
message=response.text
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if not compare_digest(str(response.status_code), "200"):
|
|
58
|
+
return RequestResponse(
|
|
59
|
+
status_code=response.status_code,
|
|
60
|
+
status="error",
|
|
61
|
+
message=response.text
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return None
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the service classes for interacting with the Termii API. Each service class corresponds to a
|
|
3
|
+
specific aspect of the Termii platform, such as managing campaigns, contacts, messages, numbers, phonebooks, sender IDs,
|
|
4
|
+
and templates. These classes provide methods to perform various operations related to their respective domains,
|
|
5
|
+
allowing users to easily integrate Termii's functionality into their applications.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .campaign import CampaignService
|
|
9
|
+
from .contact import ContactService
|
|
10
|
+
from .message import MessageService
|
|
11
|
+
from .number import NumberService
|
|
12
|
+
from .phonebook import PhonebookService
|
|
13
|
+
from .sender_id import SenderIDService
|
|
14
|
+
from .template import TemplateService
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides the CampaignService class, which allows you to send SMS campaigns, fetch campaign details, and
|
|
3
|
+
retry failed campaigns using the Termii API. The service includes methods to send campaigns with various options such
|
|
4
|
+
as scheduling, link tracking, and channel selection. It also provides functionality to retrieve campaign history and
|
|
5
|
+
retry campaigns that may have failed.
|
|
6
|
+
|
|
7
|
+
References:
|
|
8
|
+
- https://developer.termii.com/campaign
|
|
9
|
+
|
|
10
|
+
Classes:
|
|
11
|
+
CampaignService: A service class that provides methods to manage SMS campaigns via Termii's API
|
|
12
|
+
"""
|
|
13
|
+
from termii_py.http import RequestHandler
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CampaignService:
|
|
17
|
+
"""
|
|
18
|
+
Provides methods to manage SMS campaigns through Termii's API. The class includes methods to send SMS campaigns
|
|
19
|
+
with various options, fetch campaign details, and retry failed campaigns. Each method constructs the appropriate
|
|
20
|
+
API request and handles the response.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
http (object): An HTTP client instance responsible for making API requests to Termii.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, http: RequestHandler):
|
|
27
|
+
"""
|
|
28
|
+
Initializes the CampaignService instance. This constructor takes an HTTP client instance as an argument, which
|
|
29
|
+
is used to perform network requests to the Termii API.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
http (object): An HTTP client instance (e.g., `requests.Session`) used to perform network requests to the Termii API.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
self.http = http
|
|
36
|
+
|
|
37
|
+
def send_campaign(self, country_code: str, sender_id: str, message: str, message_type: str, phonebook_id: str,
|
|
38
|
+
enable_link_tracking: bool, campaign_type: str, schedule_sms_status: str,
|
|
39
|
+
schedule_time: str = None, channel: str = "dnd"):
|
|
40
|
+
"""
|
|
41
|
+
Sends an SMS campaign using the Termii API. This method constructs a payload with the provided parameters
|
|
42
|
+
and sends a POST request to the `/api/sms/campaigns/send` endpoint. The method includes validation for the
|
|
43
|
+
input parameters to ensure they meet the required criteria before making the API call.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
country_code (str): The country code for the recipients of the campaign (e.g., "234" for Nigeria).
|
|
47
|
+
sender_id (str): The sender ID to be displayed on the recipient's device (must be between 3 and 11 characters).
|
|
48
|
+
message (str): The content of the SMS message to be sent in the campaign.
|
|
49
|
+
message_type (str): The type of message, either "plain" for standard text or "unicode" for messages containing special characters.
|
|
50
|
+
phonebook_id (str): The unique identifier of the phonebook containing the recipients of the campaign.
|
|
51
|
+
enable_link_tracking (bool): A flag indicating whether link tracking should be enabled for the campaign.
|
|
52
|
+
campaign_type (str): The type of campaign, which can be "promotional" or "transactional".
|
|
53
|
+
schedule_sms_status (str): The scheduling status of the campaign, either "scheduled" for campaigns that should be sent at a later time or "regular" for immediate sending.
|
|
54
|
+
schedule_time (str, optional): The scheduled time for the campaign to be sent, required if `schedule_sms_status` is "scheduled". The format should be in ISO 8601 (e.g., "2024-12-31T23:59:00Z").
|
|
55
|
+
channel (str, optional): The channel through which the campaign should be sent, either "dnd" for Do Not Disturb compliant messages or "generic" for standard messages. Default is "dnd".
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: If any of the input parameters do not meet the required criteria (e.g., invalid country code,
|
|
59
|
+
sender ID length, message type, campaign type, scheduling status, or missing schedule time when required).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
A response object containing the result of the campaign send operation, including status and any relevant metadata
|
|
63
|
+
|
|
64
|
+
References:
|
|
65
|
+
- https://developer.termii.com/campaign#:~:text=a%20specified%20phonebook.-,Send%20a%20campaign,-This%20endpoint%20allows
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
if country_code.startswith("+"):
|
|
69
|
+
raise ValueError("country code should not start with '+' sign.")
|
|
70
|
+
|
|
71
|
+
if len(sender_id) < 3 or len(sender_id) > 11:
|
|
72
|
+
raise ValueError("sender id should be between 3 and 11 characters.")
|
|
73
|
+
|
|
74
|
+
if message_type not in ["plain", "unicode"]:
|
|
75
|
+
raise ValueError("message type should be either 'plain' or 'unicode'.")
|
|
76
|
+
|
|
77
|
+
if channel not in ["dnd", "generic"]:
|
|
78
|
+
raise ValueError("channel should be either 'dnd' or 'generic'.")
|
|
79
|
+
|
|
80
|
+
if schedule_sms_status not in ["scheduled", "regular"]:
|
|
81
|
+
raise ValueError("schedule sms status should be either 'scheduled' or 'regular'.")
|
|
82
|
+
|
|
83
|
+
if schedule_sms_status == "scheduled" and not schedule_time:
|
|
84
|
+
raise ValueError("schedule time is required when schedule sms status is 'scheduled'.")
|
|
85
|
+
|
|
86
|
+
payload = {
|
|
87
|
+
"country_code": country_code,
|
|
88
|
+
"sender_id": sender_id,
|
|
89
|
+
"message": message,
|
|
90
|
+
"message_type": message_type,
|
|
91
|
+
"phonebook_id": phonebook_id,
|
|
92
|
+
"enable_link_tracking": enable_link_tracking,
|
|
93
|
+
"campaign_type": campaign_type,
|
|
94
|
+
"schedule_sms_status": schedule_sms_status,
|
|
95
|
+
"schedule_time": schedule_time,
|
|
96
|
+
"channel": channel,
|
|
97
|
+
"remove_duplicate": "yes",
|
|
98
|
+
"delimiter": ","
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return self.http.post("/api/sms/campaigns/send", payload)
|
|
102
|
+
|
|
103
|
+
def fetch_campaigns(self):
|
|
104
|
+
"""
|
|
105
|
+
Fetches a list of all SMS campaigns that have been sent or are scheduled to be sent using the Termii
|
|
106
|
+
API. This method sends a GET request to the `/api/sms/campaigns` endpoint and retrieves the campaign
|
|
107
|
+
history, including details such as campaign ID, status, message content, recipient information, and
|
|
108
|
+
scheduling details. The response will contain an array of campaign objects, each representing a
|
|
109
|
+
specific campaign with its associated metadata.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
A response object containing a list of SMS campaigns, including details such as campaign ID, status,
|
|
113
|
+
message content, recipient information, and scheduling details.
|
|
114
|
+
|
|
115
|
+
References:
|
|
116
|
+
- https://developer.termii.com/campaign#fetch-campaigns:~:text=C714360330258%22%2C%0A%20%20%20%20%22status%22%3A%20%22success%22%0A%20%20%7D-,Fetch%20campaigns,-This%20endpoint%20retrieves
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
return self.http.fetch("/api/sms/campaigns")
|
|
120
|
+
|
|
121
|
+
def fetch_campaign_history(self, campaign_id: str):
|
|
122
|
+
"""
|
|
123
|
+
Fetches the details of a specific SMS campaign using its unique identifier (campaign ID). This method sends
|
|
124
|
+
a GET request to the `/api/sms/campaigns/{campaign_id}` endpoint, where `{campaign_id}` is the unique
|
|
125
|
+
identifier of the campaign whose details you want to retrieve. The response will contain comprehensive
|
|
126
|
+
information about the specified campaign, including its status, message content, recipient
|
|
127
|
+
information, scheduling details, and any relevant metadata.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
campaign_id (str): The unique identifier of the SMS campaign for which to fetch details. This ID is
|
|
131
|
+
typically returned when a campaign is created or can be obtained from the list of campaigns.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValueError: If the `campaign_id` is not provided or is invalid. The campaign ID is required to fetch
|
|
135
|
+
the details of a specific campaign, and it must be a valid identifier that exists in the system.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
A response object containing the details of the specified SMS campaign, including its status, message
|
|
139
|
+
content, recipient information, scheduling details, and any relevant metadata.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
if not campaign_id:
|
|
143
|
+
raise ValueError("campaign id is required")
|
|
144
|
+
|
|
145
|
+
return self.http.fetch(f"/api/sms/campaigns/{campaign_id}")
|
|
146
|
+
|
|
147
|
+
def retry_campaign(self, campaign_id: str):
|
|
148
|
+
"""
|
|
149
|
+
Retries a specific SMS campaign that may have failed or encountered issues during its initial sending attempt.
|
|
150
|
+
This method sends a PATCH request to the `/api/sms/campaigns/{campaign_id}` endpoint, where `{campaign_id}`
|
|
151
|
+
is the unique identifier of the campaign you wish to retry. The response will indicate whether the retry
|
|
152
|
+
attempt was successful and may include updated campaign details or status information. This functionality
|
|
153
|
+
is useful for campaigns that may have failed due to temporary issues such as network errors or recipient
|
|
154
|
+
device problems, allowing you to attempt sending the campaign again without having to create a new one.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
campaign_id (str): The unique identifier of the SMS campaign that you want to retry. This ID is typically
|
|
158
|
+
returned when a campaign is created or can be obtained from the list of campaigns. It must be a valid
|
|
159
|
+
identifier corresponding to an existing campaign in the system.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: If the `campaign_id` is not provided or is invalid. The campaign ID is required to identify
|
|
163
|
+
which campaign to retry, and it must be a valid identifier that exists in the system.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
A response object indicating the result of the retry attempt, including whether it was successful and
|
|
167
|
+
any relevant details about the campaign's updated status or information.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
if not campaign_id:
|
|
171
|
+
raise ValueError("campaign id is required")
|
|
172
|
+
|
|
173
|
+
payload = {}
|
|
174
|
+
|
|
175
|
+
return self.http.patch(f"/api/sms/campaigns/{campaign_id}", json=payload)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides the ContactService class, which allows you to manage contacts within a phonebook. You can fetch
|
|
3
|
+
existing contacts, create new contacts (individually or in bulk), and delete contacts from a specified phonebook.
|
|
4
|
+
The service interacts with the Termii API to perform these operations.
|
|
5
|
+
|
|
6
|
+
References:
|
|
7
|
+
- https://developer.termii.com/contacts
|
|
8
|
+
|
|
9
|
+
Classes:
|
|
10
|
+
ContactService: A service class that provides methods to manage contacts within a phonebook via Termii's API.
|
|
11
|
+
"""
|
|
12
|
+
from termii_py.http import RequestHandler
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContactService:
|
|
16
|
+
"""
|
|
17
|
+
Provides methods to manage contacts within a phonebook through Termii's API.
|
|
18
|
+
The class includes methods to fetch contacts, create new contacts (individually or in bulk), and delete contacts from a specified phonebook. Each method constructs the appropriate API request and handles the response.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
http (object): An HTTP client instance responsible for making API requests to Termii.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, http: RequestHandler):
|
|
25
|
+
"""
|
|
26
|
+
Initializes the ContactService instance.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
http (object): An HTTP client instance (e.g., `requests.Session`) used to perform network requests to the Termii API.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
self.http = http
|
|
33
|
+
|
|
34
|
+
def fetch_contacts(self, phonebook_id: str):
|
|
35
|
+
"""
|
|
36
|
+
Fetches all contacts associated with a specific phonebook. This method sends a GET request to
|
|
37
|
+
the `/api/phonebooks/{phonebook_id}/contacts` endpoint, where `{phonebook_id}` is the unique identifier of
|
|
38
|
+
the phonebook whose contacts you want to retrieve. The response will contain a list of contacts along with
|
|
39
|
+
their details.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
phonebook_id (str): The unique identifier of the phonebook for which to fetch contacts.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If the `phonebook_id` is not provided or is invalid.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A response object containing the list of contacts and associated metadata for the specified phonebook.
|
|
49
|
+
|
|
50
|
+
References:
|
|
51
|
+
- https://developer.termii.com/contacts#:~:text=within%20your%20phonebooks.-,Fetch%20contacts%20by%20phonebook%20ID,-This%20endpoint%20retrieves
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
if not phonebook_id:
|
|
55
|
+
raise ValueError("phonebook_id is required")
|
|
56
|
+
|
|
57
|
+
return self.http.fetch(f"/api/phonebooks/{phonebook_id}/contacts")
|
|
58
|
+
|
|
59
|
+
def create_contact(self, phonebook_id, phone_number: str, country_code: str, email_address: str = None,
|
|
60
|
+
first_name: str = None, last_name: str = None, company: str = None):
|
|
61
|
+
"""
|
|
62
|
+
Creates a new contact within a specified phonebook. This method sends a POST request to
|
|
63
|
+
the `/api/phonebooks/{phonebook_id}/contacts` endpoint, where `{phonebook_id}` is the unique identifier of
|
|
64
|
+
the phonebook to which the contact will be added. The request body should include the contact's
|
|
65
|
+
phone number, country code, and optionally their email address, first name, last name, and company. The
|
|
66
|
+
response will contain the details of the newly created contact.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
phonebook_id (str): The unique identifier of the phonebook to which the contact will be added.
|
|
70
|
+
phone_number (str): The contact's phone number (without the country code).
|
|
71
|
+
country_code (str): The country code for the contact's phone number (without the '+' sign).
|
|
72
|
+
email_address (str, optional): The contact's email address. This field is optional.
|
|
73
|
+
first_name (str, optional): The contact's first name. This field is optional.
|
|
74
|
+
last_name (str, optional): The contact's last name. This field is optional.
|
|
75
|
+
company (str, optional): The contact's company name. This field is optional.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If the `phonebook_id` is not provided or is invalid.
|
|
79
|
+
ValueError: If the `country_code` starts with a '+' sign. The country code should be provided without the '+' sign.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A response object containing the details of the newly created contact, including its unique identifier and any associated metadata.
|
|
83
|
+
|
|
84
|
+
References:
|
|
85
|
+
- https://developer.termii.com/contacts#add-single-contacts-to-phonebook:~:text=12%2C%0A%20%20%20%20%22empty%22%3A%20false%0A%20%20%7D%0A%7D-,Add%20single%20contacts%20to%20phonebook,-This%20endpoint%20allows
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
if not phonebook_id:
|
|
89
|
+
raise ValueError("phonebook_id is required")
|
|
90
|
+
|
|
91
|
+
if country_code.startswith("+"):
|
|
92
|
+
raise ValueError("country code should not start with '+' sign.")
|
|
93
|
+
|
|
94
|
+
payload = {
|
|
95
|
+
"phone_number": phone_number,
|
|
96
|
+
"country_code": country_code,
|
|
97
|
+
"email_address": email_address,
|
|
98
|
+
"first_name": first_name,
|
|
99
|
+
"last_name": last_name,
|
|
100
|
+
"company": company,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return self.http.post(f"/api/phonebooks/{phonebook_id}/contacts", json=payload)
|
|
104
|
+
|
|
105
|
+
def create_multiple_contacts(self, phonebook_id, country_code: str, file_path: str, ):
|
|
106
|
+
"""
|
|
107
|
+
Creates multiple contacts in bulk by uploading a CSV file containing the contact details. This method sends
|
|
108
|
+
a POST request to the `/api/phonebooks/contacts/upload` endpoint with the specified phonebook ID, country
|
|
109
|
+
code, and file path. The CSV file should be formatted according to Termii's requirements, with columns for
|
|
110
|
+
phone number, email address, first name, last name, and company. The response will indicate the success or
|
|
111
|
+
failure of the bulk upload operation.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
phonebook_id (str): The unique identifier of the phonebook to which the contacts will be added.
|
|
115
|
+
country_code (str): The country code for the contacts' phone numbers (without the '+' sign).
|
|
116
|
+
file_path (str): The file path to the CSV file containing the contact details to be uploaded.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If the `phonebook_id` is not provided or is invalid.
|
|
120
|
+
ValueError: If the `country_code` starts with a '+' sign. The country code should be provided without the '+' sign.
|
|
121
|
+
ValueError: If the `file_path` is not provided or is invalid. The file path must point to a valid CSV file containing the contact details.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
A response object indicating the success or failure of the bulk contact upload operation, along with any relevant metadata or error messages.
|
|
125
|
+
|
|
126
|
+
References:
|
|
127
|
+
- https://developer.termii.com/contacts#add-single-contacts-to-phonebook:~:text=Promise%22%2C%0A%20%20%20%20%22last_name%22%3A%20%22John%22%0A%20%20%7D%0A%7D-,Add%20multiple%20contacts%20to%20phonebook,-This%20endpoint%20allows
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
if not phonebook_id:
|
|
131
|
+
raise ValueError("phonebook_id is required")
|
|
132
|
+
|
|
133
|
+
if country_code.startswith("+"):
|
|
134
|
+
raise ValueError("country code should not start with '+' sign.")
|
|
135
|
+
|
|
136
|
+
data = {
|
|
137
|
+
"phonebook_id": phonebook_id,
|
|
138
|
+
"country_code": country_code,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return self.http.post_file(f"/api/phonebooks/contacts/upload", data=data, file_path=file_path)
|
|
142
|
+
|
|
143
|
+
def delete_contact(self, phonebook_id):
|
|
144
|
+
"""
|
|
145
|
+
Deletes all contacts associated with a specific phonebook. This method sends a DELETE request to the
|
|
146
|
+
`/api/phonebooks/{phonebook_id}/contacts` endpoint, where `{phonebook_id}` is the unique identifier of the
|
|
147
|
+
phonebook from which to delete contacts. The response will indicate the success or failure of the delete
|
|
148
|
+
operation. Note that this action will remove all contacts from the specified phonebook, so use it with
|
|
149
|
+
caution.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
phonebook_id (str): The unique identifier of the phonebook from which to delete contacts.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
ValueError: If the `phonebook_id` is not provided or is invalid. The `phonebook_id` is required to identify which phonebook's contacts should be deleted.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
A response object indicating the success or failure of the delete operation, along with any relevant metadata or error messages. The response may include information about the number of contacts deleted or any issues encountered during the process.
|
|
159
|
+
|
|
160
|
+
References:
|
|
161
|
+
- https://developer.termii.com/contacts#add-single-contacts-to-phonebook:~:text=get%20it%20done.%22%0A%20%20%7D-,Delete%20Contact%20in%20a%20Phonebook,-This%20endpoint%20allows
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
if not phonebook_id:
|
|
165
|
+
raise ValueError("phonebook_id is required to delete contacts.")
|
|
166
|
+
|
|
167
|
+
return self.http.delete(f"/api/phonebooks/{phonebook_id}/contacts")
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the `MessageService` class, which provides an interface for sending messages
|
|
3
|
+
through Termii's Messaging API.
|
|
4
|
+
|
|
5
|
+
It supports sending single SMS messages, WhatsApp messages, and bulk messages while ensuring
|
|
6
|
+
parameter validation and correct channel-type combinations before making HTTP requests to the API.
|
|
7
|
+
|
|
8
|
+
References:
|
|
9
|
+
- https://developer.termii.com/messaging-api
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
MessageService: Handles message dispatch operations via Termii's Messaging API.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from hmac import compare_digest
|
|
16
|
+
|
|
17
|
+
from termii_py.http import RequestHandler
|
|
18
|
+
from termii_py.value_object import PhoneNumber
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MessageService:
|
|
22
|
+
"""
|
|
23
|
+
Provides methods for sending SMS, WhatsApp, and bulk messages via Termii's Messaging API.
|
|
24
|
+
|
|
25
|
+
The class ensures data validation and enforces correct message channel and type usage.
|
|
26
|
+
Each method constructs the appropriate request payload and sends it using the injected
|
|
27
|
+
HTTP client.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
http (object): An HTTP client instance responsible for making API requests.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, http: RequestHandler):
|
|
34
|
+
"""
|
|
35
|
+
Initializes the MessageService instance.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
http (object): An HTTP client instance (e.g., `requests.Session`) used to perform
|
|
39
|
+
network requests to the Termii API.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
self.http = http
|
|
43
|
+
|
|
44
|
+
def send_message(self, sent_to: str, sent_from: str, message: str, channel: str, type: str):
|
|
45
|
+
"""
|
|
46
|
+
Sends a single SMS message through Termii's Messaging API.
|
|
47
|
+
|
|
48
|
+
This method supports the following channels:
|
|
49
|
+
- "generic" (for standard messaging)
|
|
50
|
+
- "dnd" (for messages that bypass Do-Not-Disturb filters)
|
|
51
|
+
- "voice" (for voice call messages)
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
- WhatsApp messages must be sent using `send_whatsapp_message()`.
|
|
55
|
+
- For voice messages, the `type` parameter must explicitly be set to "voice".
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
sent_to (str): Recipient’s phone number in international format.
|
|
59
|
+
sent_from (str): The sender ID registered on Termii.
|
|
60
|
+
message (str): The content of the message to be sent.
|
|
61
|
+
channel (str): The communication channel. Must be one of ["generic", "dnd", "voice"].
|
|
62
|
+
type (str): The message type (e.g., "plain", "voice").
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If an invalid channel-type combination is provided.
|
|
66
|
+
ValueError: If attempting to send WhatsApp messages via this method.
|
|
67
|
+
ValueError: If `channel` or `type` parameters are invalid.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
dict: The JSON response returned by the Termii API.
|
|
71
|
+
|
|
72
|
+
References:
|
|
73
|
+
https://developer.termii.com/messaging-api#:~:text=Send%20message
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
PhoneNumber(sent_to)
|
|
77
|
+
|
|
78
|
+
if compare_digest("whatsapp", str(channel).strip().lower()):
|
|
79
|
+
raise ValueError("For WhatsApp messages, please use the 'send_whatsapp_message' method.")
|
|
80
|
+
|
|
81
|
+
if compare_digest("voice", str(channel).strip().lower()) and not compare_digest("voice",
|
|
82
|
+
str(type).strip().lower()):
|
|
83
|
+
raise ValueError("For voice channel, the 'type' parameter must be set to 'voice'.")
|
|
84
|
+
|
|
85
|
+
if not compare_digest("generic", str(channel).strip().lower()) and not compare_digest("dnd",
|
|
86
|
+
str(channel).strip().lower() or not compare_digest(
|
|
87
|
+
"voice",
|
|
88
|
+
str(channel).strip().lower())):
|
|
89
|
+
raise ValueError("The 'channel' parameter must be either 'generic' or 'dnd' or voice.")
|
|
90
|
+
|
|
91
|
+
payload = {
|
|
92
|
+
"to": sent_to,
|
|
93
|
+
"from": sent_from,
|
|
94
|
+
"sms": message,
|
|
95
|
+
"channel": channel,
|
|
96
|
+
"type": type
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return self.http.post("/api/sms/send", json=payload)
|
|
100
|
+
|
|
101
|
+
def send_whatsapp_message(self, sent_to: str, sent_from: str, message: str, url: str = None, caption: str = None):
|
|
102
|
+
"""
|
|
103
|
+
Sends a WhatsApp message through Termii's Messaging API.
|
|
104
|
+
|
|
105
|
+
This endpoint supports text-only and media messages (e.g., image or document links).
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
sent_to (str): Recipient’s phone number in international format.
|
|
109
|
+
sent_from (str): The sender ID or WhatsApp business number.
|
|
110
|
+
message (str): Text message to be sent.
|
|
111
|
+
url (str, optional): Media URL for attachments (image, document, etc.).
|
|
112
|
+
caption (str, optional): Caption for the media file (if applicable).
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If the recipient phone number is invalid.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
dict: The JSON response from the Termii API.
|
|
119
|
+
|
|
120
|
+
References:
|
|
121
|
+
https://developer.termii.com/messaging-api#:~:text=Send%20WhatsApp%20Message%20(Conversational)
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
PhoneNumber(sent_to)
|
|
125
|
+
|
|
126
|
+
payload = {
|
|
127
|
+
"to": sent_to,
|
|
128
|
+
"from": sent_from,
|
|
129
|
+
"sms": message,
|
|
130
|
+
"channel": "whatsapp",
|
|
131
|
+
"type": "plain",
|
|
132
|
+
"media": {
|
|
133
|
+
"url": url,
|
|
134
|
+
"caption": caption,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return self.http.post("/api/sms/send", json=payload)
|
|
139
|
+
|
|
140
|
+
def send_bulk_message(self, sent_to: list, sent_from: str, message: str, channel: str, type: str):
|
|
141
|
+
"""
|
|
142
|
+
Sends bulk SMS messages to multiple recipients using Termii's Messaging API.
|
|
143
|
+
|
|
144
|
+
This method supports sending to multiple phone numbers in one request. Only the "generic"
|
|
145
|
+
and "dnd" channels are supported for bulk operations.
|
|
146
|
+
|
|
147
|
+
Notes:
|
|
148
|
+
- Voice and WhatsApp messages cannot be sent in bulk.
|
|
149
|
+
- Each recipient number is validated before sending.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
sent_to (list): A list of recipient phone numbers in international format.
|
|
153
|
+
sent_from (str): The sender ID registered on Termii.
|
|
154
|
+
message (str): The content of the message to be sent.
|
|
155
|
+
channel (str): The message channel. Must be either "generic" or "dnd".
|
|
156
|
+
type (str): The message type (e.g., "plain").
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError: If any recipient number is invalid.
|
|
160
|
+
ValueError: If attempting to send WhatsApp or voice messages in bulk.
|
|
161
|
+
ValueError: If an invalid channel is specified.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
dict: The JSON response returned by the Termii API.
|
|
165
|
+
|
|
166
|
+
References:
|
|
167
|
+
https://developer.termii.com/messaging-api#:~:text=Send%20Bulk%20message
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
for x in sent_to:
|
|
171
|
+
PhoneNumber(x)
|
|
172
|
+
|
|
173
|
+
if compare_digest("whatsapp", str(channel).strip().lower()):
|
|
174
|
+
raise ValueError("For WhatsApp messages, please use the 'send_whatsapp_message' method.")
|
|
175
|
+
|
|
176
|
+
if compare_digest("voice", str(channel).strip().lower()) or compare_digest("voice", str(type).strip().lower()):
|
|
177
|
+
raise ValueError("Voice messages are not supported in bulk messaging.")
|
|
178
|
+
|
|
179
|
+
if not compare_digest("generic", str(channel).strip().lower()) and not compare_digest("dnd",
|
|
180
|
+
str(channel).strip().lower()):
|
|
181
|
+
raise ValueError("The 'channel' parameter must be either 'generic' or 'dnd' or voice.")
|
|
182
|
+
|
|
183
|
+
payload = {
|
|
184
|
+
"to": sent_to,
|
|
185
|
+
"from": sent_from,
|
|
186
|
+
"sms": message,
|
|
187
|
+
"channel": channel,
|
|
188
|
+
"type": type
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return self.http.post("/api/sms/send/bulk", json=payload)
|