vobiz-python 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.
- vobiz/__init__.py +4 -0
- vobiz/base.py +237 -0
- vobiz/exceptions.py +34 -0
- vobiz/resources/__init__.py +12 -0
- vobiz/resources/accounts.py +59 -0
- vobiz/resources/applications.py +138 -0
- vobiz/resources/calls_vobiz.py +206 -0
- vobiz/resources/cdrs.py +46 -0
- vobiz/resources/credentials.py +104 -0
- vobiz/resources/endpoints.py +101 -0
- vobiz/resources/ip_access_control_lists.py +100 -0
- vobiz/resources/numbers.py +134 -0
- vobiz/resources/origination_uris.py +109 -0
- vobiz/resources/recordings.py +91 -0
- vobiz/resources/sip_trunks.py +99 -0
- vobiz/resources/subaccounts.py +101 -0
- vobiz/rest/__init__.py +2 -0
- vobiz/rest/client.py +277 -0
- vobiz/utils/__init__.py +72 -0
- vobiz/utils/interactive.py +50 -0
- vobiz/utils/jwt.py +97 -0
- vobiz/utils/location.py +6 -0
- vobiz/utils/signature_v3.py +111 -0
- vobiz/utils/template.py +50 -0
- vobiz/utils/validators.py +280 -0
- vobiz/version.py +2 -0
- vobiz/xml/ConferenceElement.py +485 -0
- vobiz/xml/DTMFElement.py +41 -0
- vobiz/xml/DialElement.py +371 -0
- vobiz/xml/MultiPartyCallElement.py +711 -0
- vobiz/xml/ResponseElement.py +414 -0
- vobiz/xml/VobizXMLElement.py +48 -0
- vobiz/xml/__init__.py +31 -0
- vobiz/xml/breakElement.py +62 -0
- vobiz/xml/contElement.py +190 -0
- vobiz/xml/emphasisElement.py +174 -0
- vobiz/xml/getDigitsElement.py +294 -0
- vobiz/xml/getInputElement.py +369 -0
- vobiz/xml/hangupElement.py +57 -0
- vobiz/xml/langElement.py +197 -0
- vobiz/xml/messageElement.py +115 -0
- vobiz/xml/numberElement.py +77 -0
- vobiz/xml/pElement.py +164 -0
- vobiz/xml/phonemeElement.py +62 -0
- vobiz/xml/playElement.py +41 -0
- vobiz/xml/preAnswerElement.py +148 -0
- vobiz/xml/prosodyElement.py +227 -0
- vobiz/xml/recordElement.py +337 -0
- vobiz/xml/redirectElement.py +42 -0
- vobiz/xml/sElement.py +154 -0
- vobiz/xml/sayAsElement.py +61 -0
- vobiz/xml/speakElement.py +249 -0
- vobiz/xml/streamElement.py +123 -0
- vobiz/xml/subElement.py +43 -0
- vobiz/xml/userElement.py +79 -0
- vobiz/xml/wElement.py +144 -0
- vobiz/xml/waitElement.py +93 -0
- vobiz/xml/xmlUtils.py +6 -0
- vobiz_python-0.1.0.dist-info/METADATA +640 -0
- vobiz_python-0.1.0.dist-info/RECORD +63 -0
- vobiz_python-0.1.0.dist-info/WHEEL +5 -0
- vobiz_python-0.1.0.dist-info/licenses/LICENSE.txt +19 -0
- vobiz_python-0.1.0.dist-info/top_level.txt +1 -0
vobiz/__init__.py
ADDED
vobiz/base.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Base classes, used to deal with every Vobiz resource.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pprint
|
|
7
|
+
|
|
8
|
+
from vobiz.exceptions import InvalidRequestError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.limit = None
|
|
14
|
+
self.next = None
|
|
15
|
+
self.offset = None
|
|
16
|
+
self.previous = None
|
|
17
|
+
self.total_count = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VobizGenericResponse(object):
|
|
21
|
+
"""A generic response to cover it all!
|
|
22
|
+
|
|
23
|
+
This provides a generic blanket response based on what is received from
|
|
24
|
+
Vobiz servers.
|
|
25
|
+
|
|
26
|
+
This will be used only during POST and DELETE requests.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, params_dict, id_string=None):
|
|
30
|
+
for i in params_dict:
|
|
31
|
+
self.__dict__[i] = params_dict[i]
|
|
32
|
+
|
|
33
|
+
if id_string and id_string in params_dict:
|
|
34
|
+
self.__dict__['id'] = params_dict[id_string]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ResponseObject(object):
|
|
38
|
+
def __init__(self, dct):
|
|
39
|
+
self.__dict__.update(dct)
|
|
40
|
+
|
|
41
|
+
def __contains__(self, item):
|
|
42
|
+
return item in self.__dict__
|
|
43
|
+
|
|
44
|
+
def __getitem__(self, item):
|
|
45
|
+
try:
|
|
46
|
+
return self.__dict__.__getitem__(item)
|
|
47
|
+
except KeyError:
|
|
48
|
+
return self.objects.__getitem__(item)
|
|
49
|
+
|
|
50
|
+
def __setitem__(self, key, value):
|
|
51
|
+
self.__dict__.__setitem__(key, value)
|
|
52
|
+
|
|
53
|
+
def __delitem__(self, key):
|
|
54
|
+
del self.__dict__[key]
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
return pprint.pformat(self.__dict__)
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
return pprint.pformat(self.__dict__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ListResponseObject(ResponseObject):
|
|
64
|
+
def __init__(self, client, dct):
|
|
65
|
+
super(ListResponseObject, self).__init__(dct)
|
|
66
|
+
|
|
67
|
+
def __iter__(self):
|
|
68
|
+
return self.objects.__iter__()
|
|
69
|
+
|
|
70
|
+
def __len__(self):
|
|
71
|
+
return len(self.objects)
|
|
72
|
+
|
|
73
|
+
def __str__(self):
|
|
74
|
+
return pprint.pformat(self.objects)
|
|
75
|
+
|
|
76
|
+
def __repr__(self):
|
|
77
|
+
return str([object for object in self.objects])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class VobizResource(ResponseObject):
|
|
81
|
+
"""The Vobiz resource object
|
|
82
|
+
|
|
83
|
+
This provides an interface to deal with all Vobiz resources and
|
|
84
|
+
sub-resources.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
_identifier_string = None
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def id(self):
|
|
91
|
+
value = self.__dict__.get(self._identifier_string, None)
|
|
92
|
+
if not value:
|
|
93
|
+
raise ValueError('{} must be set'.format(self._identifier_string))
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
def __init__(self, client, data):
|
|
97
|
+
"""Sets up the resource URI along with a hack for Account resource"""
|
|
98
|
+
|
|
99
|
+
super(VobizResource, self).__init__(data)
|
|
100
|
+
self._name = self._name or self.__class__.__name__
|
|
101
|
+
self.client = client
|
|
102
|
+
|
|
103
|
+
def __str__(self):
|
|
104
|
+
return pprint.pformat(self.__dict__)
|
|
105
|
+
|
|
106
|
+
def __repr__(self):
|
|
107
|
+
return self.__str__()
|
|
108
|
+
|
|
109
|
+
def update(self, params, path, **kwargs):
|
|
110
|
+
self.client.request('POST', params, path, **kwargs)
|
|
111
|
+
|
|
112
|
+
def _update(self, params):
|
|
113
|
+
if not self.id:
|
|
114
|
+
raise InvalidRequestError(
|
|
115
|
+
'Cannot update a {resource_type} resource without an '
|
|
116
|
+
'identifier'.format(resource_type=self._name))
|
|
117
|
+
|
|
118
|
+
response_json = self.client.send_request(
|
|
119
|
+
self.__resource_uri, method='POST', data=params)
|
|
120
|
+
|
|
121
|
+
for key in params:
|
|
122
|
+
self.__dict__[key] = params[key]
|
|
123
|
+
|
|
124
|
+
for key in response_json:
|
|
125
|
+
self.__dict__[key] = response_json[key]
|
|
126
|
+
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def _execute_action(self,
|
|
130
|
+
action=None,
|
|
131
|
+
method='GET',
|
|
132
|
+
params=None,
|
|
133
|
+
parse=False):
|
|
134
|
+
if not action:
|
|
135
|
+
response = self.client.send_request(
|
|
136
|
+
self.__resource_uri, method=method, data=params)
|
|
137
|
+
else:
|
|
138
|
+
response = self.client.send_request(
|
|
139
|
+
self.__resource_uri + action + '/', method=method, data=params)
|
|
140
|
+
|
|
141
|
+
if not parse:
|
|
142
|
+
return response
|
|
143
|
+
|
|
144
|
+
self.__resource_json = response
|
|
145
|
+
self.__parse_json()
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
self.id = response[self._identifier_string]
|
|
149
|
+
except AttributeError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
if method == 'POST':
|
|
153
|
+
self.__resource_json = params
|
|
154
|
+
self.__parse_json()
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
def delete(self):
|
|
158
|
+
if not self.id:
|
|
159
|
+
raise InvalidRequestError(
|
|
160
|
+
'Cannot delete a {resource_type} resource without an '
|
|
161
|
+
'identifier'.format(resource_type=self._name))
|
|
162
|
+
|
|
163
|
+
return VobizGenericResponse(
|
|
164
|
+
self.client.send_request(self.__resource_uri, method='DELETE'))
|
|
165
|
+
|
|
166
|
+
def get(self):
|
|
167
|
+
if not self.id:
|
|
168
|
+
raise InvalidRequestError(
|
|
169
|
+
'Cannot get a {resource_type} resource without an '
|
|
170
|
+
'identifier'.format(resource_type=self._name))
|
|
171
|
+
|
|
172
|
+
self.__resource_json = self.client.send_request(self.__resource_uri)
|
|
173
|
+
self.__parse_json()
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
def create(self, params):
|
|
177
|
+
if self.id:
|
|
178
|
+
raise InvalidRequestError(
|
|
179
|
+
'Cannot create a {resource_type} resource because another'
|
|
180
|
+
' {resource_type} resource exists with the same '
|
|
181
|
+
'identifier: {identifier}.'.format(
|
|
182
|
+
resource_type=self._name, identifier=self.id))
|
|
183
|
+
|
|
184
|
+
id_string = None
|
|
185
|
+
if self._identifier_string:
|
|
186
|
+
id_string = self._identifier_string
|
|
187
|
+
|
|
188
|
+
return VobizGenericResponse(
|
|
189
|
+
self.client.send_request(
|
|
190
|
+
self.__resource_uri, data=params, method='POST'), id_string)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class SecondaryVobizResource(VobizResource):
|
|
194
|
+
"""
|
|
195
|
+
SecondaryVobizResource resource object.
|
|
196
|
+
Provides an interface to deal with resources where the identifier
|
|
197
|
+
has a mid-level parent.
|
|
198
|
+
"""
|
|
199
|
+
_secondary_identifier_string = None
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def secondary_id(self):
|
|
203
|
+
value = self.__dict__.get(self._secondary_identifier_string, None)
|
|
204
|
+
if not value:
|
|
205
|
+
raise ValueError('{} must be set'.format(self._secondary_identifier_string))
|
|
206
|
+
return value
|
|
207
|
+
|
|
208
|
+
def __init__(self, client, data):
|
|
209
|
+
super(VobizResource, self).__init__(client, data)
|
|
210
|
+
self._name = self._name or self.__class__.__name__
|
|
211
|
+
self.client = client
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class VobizResourceInterface(object):
|
|
215
|
+
_iterable = True
|
|
216
|
+
|
|
217
|
+
def __init__(self, client, **kwargs):
|
|
218
|
+
self.client = client
|
|
219
|
+
|
|
220
|
+
def __iter__(self):
|
|
221
|
+
if not getattr(self, 'list') or not self.__class__._iterable:
|
|
222
|
+
raise NotImplementedError(
|
|
223
|
+
'list is not supported for this resource')
|
|
224
|
+
|
|
225
|
+
def gen():
|
|
226
|
+
limit = 20
|
|
227
|
+
offset = 0
|
|
228
|
+
while True:
|
|
229
|
+
response = self.list(limit=limit, offset=offset)
|
|
230
|
+
if not response.objects:
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
for item in response:
|
|
234
|
+
yield item
|
|
235
|
+
offset += limit
|
|
236
|
+
|
|
237
|
+
return gen()
|
vobiz/exceptions.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
class VobizRestError(Exception):
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AuthenticationError(VobizRestError):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InvalidRequestError(VobizRestError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VobizServerError(VobizRestError):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VobizXMLError(VobizRestError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ResourceNotFoundError(VobizRestError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ValidationError(VobizRestError):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ForbiddenError(VobizRestError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
class GeoPermissionError(VobizRestError):
|
|
34
|
+
pass
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .accounts import Accounts
|
|
2
|
+
from .subaccounts import Subaccounts
|
|
3
|
+
from .calls_vobiz import Calls
|
|
4
|
+
from .applications import Applications
|
|
5
|
+
from .recordings import Recordings
|
|
6
|
+
from .cdrs import CDRs
|
|
7
|
+
from .numbers import PhoneNumbers
|
|
8
|
+
from .endpoints import Endpoints
|
|
9
|
+
from .sip_trunks import SipTrunks
|
|
10
|
+
from .credentials import Credentials
|
|
11
|
+
from .ip_access_control_lists import IpAccessControlLists
|
|
12
|
+
from .origination_uris import OriginationUris
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Vobiz Accounts resource.
|
|
4
|
+
|
|
5
|
+
Implements only the endpoints defined in the Vobiz accounts documentation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
VOBIZ_API_V1 = "https://api.vobiz.ai/api/v1"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Accounts:
|
|
12
|
+
def __init__(self, client):
|
|
13
|
+
self.client = client
|
|
14
|
+
|
|
15
|
+
def get(self):
|
|
16
|
+
"""
|
|
17
|
+
GET https://api.vobiz.ai/api/v1/auth/me
|
|
18
|
+
"""
|
|
19
|
+
url = f"{VOBIZ_API_V1}/auth/me"
|
|
20
|
+
resp = self.client.session.get(
|
|
21
|
+
url, timeout=self.client.timeout, proxies=self.client.proxies
|
|
22
|
+
)
|
|
23
|
+
return self.client.process_response("GET", resp)
|
|
24
|
+
|
|
25
|
+
def get_transactions(self, account_id, limit=None, offset=None):
|
|
26
|
+
"""
|
|
27
|
+
GET https://api.vobiz.ai/api/v1/account/{account_id}/transactions
|
|
28
|
+
"""
|
|
29
|
+
url = f"{VOBIZ_API_V1}/account/{account_id}/transactions"
|
|
30
|
+
params = {}
|
|
31
|
+
if limit is not None:
|
|
32
|
+
params["limit"] = limit
|
|
33
|
+
if offset is not None:
|
|
34
|
+
params["offset"] = offset
|
|
35
|
+
resp = self.client.session.get(
|
|
36
|
+
url, params=params, timeout=self.client.timeout, proxies=self.client.proxies
|
|
37
|
+
)
|
|
38
|
+
return self.client.process_response("GET", resp)
|
|
39
|
+
|
|
40
|
+
def get_balance(self, account_id, currency):
|
|
41
|
+
"""
|
|
42
|
+
GET https://api.vobiz.ai/api/v1/account/{account_id}/balance/{currency}
|
|
43
|
+
"""
|
|
44
|
+
url = f"{VOBIZ_API_V1}/account/{account_id}/balance/{currency}"
|
|
45
|
+
resp = self.client.session.get(
|
|
46
|
+
url, timeout=self.client.timeout, proxies=self.client.proxies
|
|
47
|
+
)
|
|
48
|
+
return self.client.process_response("GET", resp)
|
|
49
|
+
|
|
50
|
+
def get_concurrency(self, account_id):
|
|
51
|
+
"""
|
|
52
|
+
GET https://api.vobiz.ai/api/v1/account/{account_id}/concurrency
|
|
53
|
+
"""
|
|
54
|
+
url = f"{VOBIZ_API_V1}/account/{account_id}/concurrency"
|
|
55
|
+
resp = self.client.session.get(
|
|
56
|
+
url, timeout=self.client.timeout, proxies=self.client.proxies
|
|
57
|
+
)
|
|
58
|
+
return self.client.process_response("GET", resp)
|
|
59
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
VOBIZ_API_V1 = "https://api.vobiz.ai/api/v1"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Applications:
|
|
7
|
+
"""
|
|
8
|
+
Vobiz Applications resource.
|
|
9
|
+
|
|
10
|
+
All endpoints are scoped to the authenticated account.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, client):
|
|
14
|
+
self.client = client
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def _account_id(self) -> str:
|
|
18
|
+
# For Vobiz, we treat the RestClient auth_id as the account_id
|
|
19
|
+
return self.client.auth_id
|
|
20
|
+
|
|
21
|
+
def create(
|
|
22
|
+
self,
|
|
23
|
+
name: str,
|
|
24
|
+
answer_url: str,
|
|
25
|
+
answer_method: str = "POST",
|
|
26
|
+
hangup_url: Optional[str] = None,
|
|
27
|
+
hangup_method: str = "POST",
|
|
28
|
+
fallback_answer_url: Optional[str] = None,
|
|
29
|
+
fallback_method: str = "POST",
|
|
30
|
+
application_type: Optional[str] = None,
|
|
31
|
+
**extra: Any,
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
POST /api/v1/Account/{account_id}/Application/
|
|
35
|
+
"""
|
|
36
|
+
url = f"{VOBIZ_API_V1}/Account/{self._account_id}/Application/"
|
|
37
|
+
body: Dict[str, Any] = {
|
|
38
|
+
"name": name,
|
|
39
|
+
"answer_url": answer_url,
|
|
40
|
+
"answer_method": answer_method,
|
|
41
|
+
"hangup_method": hangup_method,
|
|
42
|
+
}
|
|
43
|
+
if hangup_url is not None:
|
|
44
|
+
body["hangup_url"] = hangup_url
|
|
45
|
+
if fallback_answer_url is not None:
|
|
46
|
+
body["fallback_answer_url"] = fallback_answer_url
|
|
47
|
+
if fallback_method is not None:
|
|
48
|
+
body["fallback_method"] = fallback_method
|
|
49
|
+
if application_type is not None:
|
|
50
|
+
body["application_type"] = application_type
|
|
51
|
+
# allow forward-compatible extra keys
|
|
52
|
+
body.update(extra)
|
|
53
|
+
|
|
54
|
+
resp = self.client.session.post(
|
|
55
|
+
url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
|
|
56
|
+
)
|
|
57
|
+
return self.client.process_response("POST", resp)
|
|
58
|
+
|
|
59
|
+
def list(
|
|
60
|
+
self,
|
|
61
|
+
page: Optional[int] = None,
|
|
62
|
+
size: Optional[int] = None,
|
|
63
|
+
application_type: Optional[str] = None,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
GET /api/v1/Account/{account_id}/Application/
|
|
67
|
+
"""
|
|
68
|
+
url = f"{VOBIZ_API_V1}/Account/{self._account_id}/Application/"
|
|
69
|
+
params: Dict[str, Any] = {}
|
|
70
|
+
if page is not None:
|
|
71
|
+
params["page"] = page
|
|
72
|
+
if size is not None:
|
|
73
|
+
params["size"] = size
|
|
74
|
+
if application_type is not None:
|
|
75
|
+
params["application_type"] = application_type
|
|
76
|
+
|
|
77
|
+
resp = self.client.session.get(
|
|
78
|
+
url, params=params, timeout=self.client.timeout, proxies=self.client.proxies
|
|
79
|
+
)
|
|
80
|
+
return self.client.process_response("GET", resp)
|
|
81
|
+
|
|
82
|
+
def get(self, application_id: str):
|
|
83
|
+
"""
|
|
84
|
+
GET /api/v1/Account/{account_id}/Application/{application_id}
|
|
85
|
+
"""
|
|
86
|
+
url = f"{VOBIZ_API_V1}/Account/{self._account_id}/Application/{application_id}"
|
|
87
|
+
resp = self.client.session.get(
|
|
88
|
+
url, timeout=self.client.timeout, proxies=self.client.proxies
|
|
89
|
+
)
|
|
90
|
+
return self.client.process_response("GET", resp)
|
|
91
|
+
|
|
92
|
+
def update(self, application_id: str, **params: Any):
|
|
93
|
+
"""
|
|
94
|
+
POST /api/v1/Account/{account_id}/Application/{application_id}/
|
|
95
|
+
"""
|
|
96
|
+
url = f"{VOBIZ_API_V1}/Account/{self._account_id}/Application/{application_id}/"
|
|
97
|
+
body: Dict[str, Any] = dict(params)
|
|
98
|
+
resp = self.client.session.post(
|
|
99
|
+
url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
|
|
100
|
+
)
|
|
101
|
+
return self.client.process_response("POST", resp)
|
|
102
|
+
|
|
103
|
+
def delete(self, application_id: str):
|
|
104
|
+
"""
|
|
105
|
+
DELETE /api/v1/Account/{account_id}/Application/{application_id}
|
|
106
|
+
"""
|
|
107
|
+
url = f"{VOBIZ_API_V1}/Account/{self._account_id}/Application/{application_id}"
|
|
108
|
+
resp = self.client.session.delete(
|
|
109
|
+
url, timeout=self.client.timeout, proxies=self.client.proxies
|
|
110
|
+
)
|
|
111
|
+
return self.client.process_response("DELETE", resp)
|
|
112
|
+
|
|
113
|
+
def attach_number(self, number: str, application_id: str):
|
|
114
|
+
"""
|
|
115
|
+
POST /api/v1/account/{account_id}/numbers/{number}/application
|
|
116
|
+
|
|
117
|
+
Attach a phone number to a voice application.
|
|
118
|
+
number: E.164 format, e.g. "+911234567890"
|
|
119
|
+
"""
|
|
120
|
+
url = f"{VOBIZ_API_V1}/account/{self._account_id}/numbers/{number}/application"
|
|
121
|
+
body = {"application_id": application_id}
|
|
122
|
+
resp = self.client.session.post(
|
|
123
|
+
url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
|
|
124
|
+
)
|
|
125
|
+
return self.client.process_response("POST", resp)
|
|
126
|
+
|
|
127
|
+
def detach_number(self, number: str):
|
|
128
|
+
"""
|
|
129
|
+
DELETE /api/v1/account/{account_id}/numbers/{number}/application
|
|
130
|
+
|
|
131
|
+
Detach a phone number from its voice application.
|
|
132
|
+
number: E.164 format, e.g. "+911234567890"
|
|
133
|
+
"""
|
|
134
|
+
url = f"{VOBIZ_API_V1}/account/{self._account_id}/numbers/{number}/application"
|
|
135
|
+
resp = self.client.session.delete(
|
|
136
|
+
url, timeout=self.client.timeout, proxies=self.client.proxies
|
|
137
|
+
)
|
|
138
|
+
return self.client.process_response("DELETE", resp)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Calls:
|
|
5
|
+
"""
|
|
6
|
+
Vobiz Calls resource.
|
|
7
|
+
|
|
8
|
+
Uses the `/api/v1/account/{auth_id}/call/` endpoints for call control and
|
|
9
|
+
live/queued status listing, plus DTMF.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, client):
|
|
13
|
+
self.client = client
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def _account_id(self) -> str:
|
|
17
|
+
return self.client.auth_id
|
|
18
|
+
|
|
19
|
+
def create(self, from_: str, to_: str, answer_url: str, **params: Any):
|
|
20
|
+
"""
|
|
21
|
+
POST /api/v1/Account/{auth_id}/Call/
|
|
22
|
+
"""
|
|
23
|
+
body: Dict[str, Any] = {
|
|
24
|
+
"from": from_,
|
|
25
|
+
"to": to_,
|
|
26
|
+
"answer_url": answer_url,
|
|
27
|
+
}
|
|
28
|
+
body.update(params)
|
|
29
|
+
return self.client.request("POST", ("Call",), data=body)
|
|
30
|
+
|
|
31
|
+
def transfer(self, call_uuid: str, **params: Any):
|
|
32
|
+
"""
|
|
33
|
+
POST /api/v1/Account/{auth_id}/Call/{call_uuid}/
|
|
34
|
+
"""
|
|
35
|
+
return self.client.request("POST", ("Call", call_uuid), data=dict(params))
|
|
36
|
+
|
|
37
|
+
def hangup(self, call_uuid: str):
|
|
38
|
+
"""
|
|
39
|
+
DELETE /api/v1/Account/{auth_id}/Call/{call_uuid}/
|
|
40
|
+
"""
|
|
41
|
+
return self.client.request("DELETE", ("Call", call_uuid))
|
|
42
|
+
|
|
43
|
+
def list(self, status: Optional[str] = None, **filters: Any):
|
|
44
|
+
"""
|
|
45
|
+
GET /api/v1/Account/{auth_id}/Call/
|
|
46
|
+
"""
|
|
47
|
+
params: Dict[str, Any] = dict(filters)
|
|
48
|
+
if status is not None:
|
|
49
|
+
params["status"] = status
|
|
50
|
+
return self.client.request(
|
|
51
|
+
"GET",
|
|
52
|
+
("Call",),
|
|
53
|
+
data=params,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def get(self, call_uuid: str, status: Optional[str] = None):
|
|
57
|
+
"""
|
|
58
|
+
GET /api/v1/Account/{auth_id}/Call/{call_uuid}/
|
|
59
|
+
"""
|
|
60
|
+
params: Dict[str, Any] = {}
|
|
61
|
+
if status is not None:
|
|
62
|
+
params["status"] = status
|
|
63
|
+
return self.client.request("GET", ("Call", call_uuid), data=params)
|
|
64
|
+
|
|
65
|
+
def list_live(self):
|
|
66
|
+
return self.list(status="live")
|
|
67
|
+
|
|
68
|
+
def list_queued(self):
|
|
69
|
+
return self.list(status="queued")
|
|
70
|
+
|
|
71
|
+
def get_live(self, call_uuid: str):
|
|
72
|
+
return self.get(call_uuid, status="live")
|
|
73
|
+
|
|
74
|
+
def get_queued(self, call_uuid: str):
|
|
75
|
+
return self.get(call_uuid, status="queued")
|
|
76
|
+
|
|
77
|
+
def send_digits(self, call_uuid: str, digits: str, leg: str):
|
|
78
|
+
"""
|
|
79
|
+
POST /api/v1/Account/{auth_id}/Call/{call_uuid}/DTMF/
|
|
80
|
+
"""
|
|
81
|
+
body: Dict[str, Any] = {"digits": digits, "leg": leg}
|
|
82
|
+
return self.client.request("POST", ("Call", call_uuid, "DTMF"), data=body)
|
|
83
|
+
|
|
84
|
+
# ── Recording ──────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
def start_recording(
|
|
87
|
+
self,
|
|
88
|
+
call_uuid: str,
|
|
89
|
+
time_limit: Optional[int] = None,
|
|
90
|
+
file_format: Optional[str] = None,
|
|
91
|
+
callback_url: Optional[str] = None,
|
|
92
|
+
callback_method: Optional[str] = None,
|
|
93
|
+
**params: Any,
|
|
94
|
+
):
|
|
95
|
+
"""
|
|
96
|
+
POST /api/v1/Account/{auth_id}/Call/{call_uuid}/Record/
|
|
97
|
+
|
|
98
|
+
Start recording an active call.
|
|
99
|
+
"""
|
|
100
|
+
body: Dict[str, Any] = {}
|
|
101
|
+
if time_limit is not None:
|
|
102
|
+
body["time_limit"] = time_limit
|
|
103
|
+
if file_format is not None:
|
|
104
|
+
body["file_format"] = file_format
|
|
105
|
+
if callback_url is not None:
|
|
106
|
+
body["callback_url"] = callback_url
|
|
107
|
+
if callback_method is not None:
|
|
108
|
+
body["callback_method"] = callback_method
|
|
109
|
+
body.update(params)
|
|
110
|
+
return self.client.request("POST", ("Call", call_uuid, "Record"), data=body)
|
|
111
|
+
|
|
112
|
+
def stop_recording(self, call_uuid: str, url: Optional[str] = None):
|
|
113
|
+
"""
|
|
114
|
+
DELETE /api/v1/Account/{auth_id}/Call/{call_uuid}/Record/
|
|
115
|
+
|
|
116
|
+
Stop recording on an active call.
|
|
117
|
+
If url is provided, stops only that specific recording.
|
|
118
|
+
If omitted, stops all recordings on the call.
|
|
119
|
+
"""
|
|
120
|
+
body: Dict[str, Any] = {}
|
|
121
|
+
if url is not None:
|
|
122
|
+
body["URL"] = url
|
|
123
|
+
return self.client.request("DELETE", ("Call", call_uuid, "Record"), data=body)
|
|
124
|
+
|
|
125
|
+
# ── Play Audio ─────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
def play_audio(
|
|
128
|
+
self,
|
|
129
|
+
call_uuid: str,
|
|
130
|
+
urls: list,
|
|
131
|
+
length: Optional[int] = None,
|
|
132
|
+
legs: Optional[str] = None,
|
|
133
|
+
loop: Optional[bool] = None,
|
|
134
|
+
mix: Optional[bool] = None,
|
|
135
|
+
**params: Any,
|
|
136
|
+
):
|
|
137
|
+
"""
|
|
138
|
+
POST /api/v1/Account/{auth_id}/Call/{call_uuid}/Play/
|
|
139
|
+
|
|
140
|
+
Play one or more audio files during an active call.
|
|
141
|
+
urls: list of HTTPS URLs to audio files (mp3/wav).
|
|
142
|
+
legs: "aleg", "bleg", or "both" (default "aleg").
|
|
143
|
+
"""
|
|
144
|
+
body: Dict[str, Any] = {"urls": urls}
|
|
145
|
+
if length is not None:
|
|
146
|
+
body["length"] = length
|
|
147
|
+
if legs is not None:
|
|
148
|
+
body["legs"] = legs
|
|
149
|
+
if loop is not None:
|
|
150
|
+
body["loop"] = loop
|
|
151
|
+
if mix is not None:
|
|
152
|
+
body["mix"] = mix
|
|
153
|
+
body.update(params)
|
|
154
|
+
return self.client.request("POST", ("Call", call_uuid, "Play"), data=body)
|
|
155
|
+
|
|
156
|
+
def stop_audio(self, call_uuid: str):
|
|
157
|
+
"""
|
|
158
|
+
DELETE /api/v1/Account/{auth_id}/Call/{call_uuid}/Play/
|
|
159
|
+
|
|
160
|
+
Stop audio playback on an active call.
|
|
161
|
+
"""
|
|
162
|
+
return self.client.request("DELETE", ("Call", call_uuid, "Play"), data={})
|
|
163
|
+
|
|
164
|
+
# ── Speak Text (TTS) ───────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
def speak_text(
|
|
167
|
+
self,
|
|
168
|
+
call_uuid: str,
|
|
169
|
+
text: str,
|
|
170
|
+
voice: Optional[str] = None,
|
|
171
|
+
language: Optional[str] = None,
|
|
172
|
+
legs: Optional[str] = None,
|
|
173
|
+
loop: Optional[bool] = None,
|
|
174
|
+
mix: Optional[bool] = None,
|
|
175
|
+
**params: Any,
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
POST /api/v1/Account/{auth_id}/Call/{call_uuid}/Speak/
|
|
179
|
+
|
|
180
|
+
Convert text to speech and play it during an active call.
|
|
181
|
+
voice: "WOMAN" or "MAN" (default "WOMAN").
|
|
182
|
+
language: BCP-47 language code, e.g. "en-US" (default).
|
|
183
|
+
legs: "aleg", "bleg", or "both".
|
|
184
|
+
"""
|
|
185
|
+
body: Dict[str, Any] = {"text": text}
|
|
186
|
+
if voice is not None:
|
|
187
|
+
body["voice"] = voice
|
|
188
|
+
if language is not None:
|
|
189
|
+
body["language"] = language
|
|
190
|
+
if legs is not None:
|
|
191
|
+
body["legs"] = legs
|
|
192
|
+
if loop is not None:
|
|
193
|
+
body["loop"] = loop
|
|
194
|
+
if mix is not None:
|
|
195
|
+
body["mix"] = mix
|
|
196
|
+
body.update(params)
|
|
197
|
+
return self.client.request("POST", ("Call", call_uuid, "Speak"), data=body)
|
|
198
|
+
|
|
199
|
+
def stop_speak(self, call_uuid: str):
|
|
200
|
+
"""
|
|
201
|
+
DELETE /api/v1/Account/{auth_id}/Call/{call_uuid}/Speak/
|
|
202
|
+
|
|
203
|
+
Stop text-to-speech currently playing on an active call.
|
|
204
|
+
"""
|
|
205
|
+
return self.client.request("DELETE", ("Call", call_uuid, "Speak"), data={})
|
|
206
|
+
|