Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
![Unstable](https://img.shields.io/badge/status-unstable-red.svg)

# python-voipms
*Added support for sending MMS messages

Python client for v1 of voip.ms REST API using requests >=
2.7.0.
Expand Down Expand Up @@ -279,6 +280,7 @@ individual methods available after.
#### Send

client.dids.send.sms(did, dst, message)
client.dids.send.mms(did, dst, message)

#### Set

Expand Down
78 changes: 78 additions & 0 deletions voipms/entities/didssend.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,23 @@
Documentation: https://voip.ms/m/apidocs.php
"""
from voipms.baseapi import BaseApi
import validators
from validators import ValidationFailure
import base64

def isBase64(s):
try:
return base64.b64encode(base64.b64decode(s)) == s
except Exception:
return False

def valid_url(url_string: str) -> bool:
result = validators.url(url_string)

if isinstance(result, ValidationFailure):
return False

return result

class DidsSend(BaseApi):
"""
Expand Down Expand Up @@ -52,3 +68,65 @@ def sms(self, did, dst, message):
}

return self._voipms_client._get(method, parameters)

def mms(self, did, dst, message, media1=None, media2=None):
"""
Send a MMS message to a Destination Number

:param did: [Required] DID Numbers which is sending the message (Example: 5551234567)
:type did: :py:class:`int`
:param dst: [Required] Destination Number (Example: 5551234568)
:type dst: :py:class:`int`
:param message: [Required] Message to be sent (Example: 'hello John Smith' max chars: 1600)
:type message: :py:class:`str`
:param media1: [Optional] Url to media file (Example: 'https://voip.ms/themes/voipms/assets/img/talent.jpg?v=2'
:type media1: :py:class:`str`
:param media2: [Optional] Base 64 image encode (Example: data:image/png;base64,iVBORw0KGgoAAAANSUh...)
:type media2: :py:class:`str`

:returns: :py:class:`dict`
"""
method = "sendMMS"

if not isinstance(did, int):
raise ValueError("DID Numbers which is sending the message needs to be an int (Example: 5551234567)")

if not isinstance(dst, int):
raise ValueError("Destination Number needs to be an int (Example: 5551234568) ")

if not isinstance(message, str):
raise ValueError("Message to be sent needs to be a str (Example: 'hello John Smith' max chars: 1600)")

if media1:
if not valid_url(media1):
raise ValueError("Media1 to be sent needs to be a valid url to media file (Example: 'https://voip.ms/themes/voipms/assets/img/talent.jpg?v=2' ")

if media2:
if not isBase64(media2):
raise ValueError("Media2 to be sent needs to be a base 64 image encode (Example: data:image/png;base64,iVBORw0KGgoAAAANSUh...)")
else:
if len(message) > 1600:
raise ValueError("Message to be sent can only have 1600 chars")

if media1:
parameters = {
"did": did,
"dst": dst,
"message": message,
"media1": media1,
}
elif media2:
parameters = {
"did": did,
"dst": dst,
"message": message,
"media2": media2,
}
else:
parameters = {
"did": did,
"dst": dst,
"message": message,
}

return self._voipms_client._get(method, parameters)
39 changes: 37 additions & 2 deletions voipms/voipmsclient.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import requests
import requests, json

# Handle library reorganisation Python 2 > Python 3.
try:
Expand All @@ -24,6 +24,9 @@ def __init__(self, voip_user, voip_api_password):
"""
super(VoipMsClient, self).__init__()
self.base_url = 'https://voip.ms/api/v1/rest.php?api_username={}&api_password={}&'.format(voip_user, voip_api_password)
self.post_url = 'https://voip.ms/api/v1/rest.php'
self.voip_user = voip_user
self.voip_api_password = voip_api_password

def _error_code(self, status):
"""
Expand Down Expand Up @@ -52,7 +55,7 @@ def _get(self, method, parameters=None):
}
if parameters:
query_set.update(parameters)
url = self.base_url + urlencode(query_set)
url = self.base_url + urlencode(query_set).replace('%2F', '/').replace('%3A', ':').replace('%0D%0A', '+').replace('%0A', '+').replace('%21', '+')

try:
r = requests.get(url)
Expand All @@ -67,3 +70,35 @@ def _get(self, method, parameters=None):
if status != "success":
self._error_code(status)
return r_json

def _post(self, method, parameters=None):
"""
Handle authenticated POST requests

:param method: The method call for the API
:type method: :py:class:`str`
:param parameters: The POST parameters
:type parameters: :py:class:`str`
:returns: The JSON output from the API
"""
url = self.post_url
headers = {
'api_username' : self.voip_user,
'api_password' : self.voip_api_password,
"method" : method
}
parameters.update(headers)

try:
r = requests.post(url, data=parameters)
except requests.exceptions.RequestException as e:
raise e
else:
r.raise_for_status()
if r.status_code == 204:
return None
r_json = r.json()
status = r_json["status"]
if status != "success":
self._error_code(status)
return r_json