Skip to content

Commit

Permalink
added whatsapp transport
Browse files Browse the repository at this point in the history
  • Loading branch information
djamg committed Jan 31, 2023
1 parent 992f6b7 commit aa05f7a
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 3 deletions.
2 changes: 1 addition & 1 deletion funnel/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@ def main_notification_preferences(self) -> NotificationPreferences:
by_sms=True,
by_webpush=False,
by_telegram=False,
by_whatsapp=False,
by_whatsapp=True,
)
db.session.add(main)
return main
Expand Down
2 changes: 2 additions & 0 deletions funnel/transports/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ def init():
platform_transports['email'] = True
if sms_init():
platform_transports['sms'] = True
if app.config.get('WHATSAPP_TOKEN'):

This comment has been minimized.

Copy link
@jace

jace Jan 31, 2023

Member

This needs both WHATSAPP_PHONE_ID and WHATSAPP_TOKEN.

platform_transports['whatsapp'] = True

# Other transports are not supported yet
110 changes: 109 additions & 1 deletion funnel/transports/whatsapp.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,111 @@
"""Support functions for sending a WhatsApp message. Forthcoming."""
"""Support functions for sending an Whatsapp messages."""

from __future__ import annotations

from typing import Union

from models import PhoneNumber, PhoneNumberBlockedError
import phonenumbers
import requests

from baseframe import _

from .. import app
from .exc import (
TransportConnectionError,
TransportRecipientError,
TransportTransactionError,
)

__all__ = ['send_wa_via_meta', 'send_wa_via_on_premise']


def get_phone_number(
phone: Union[str, phonenumbers.PhoneNumber, PhoneNumber]
) -> PhoneNumber:
if isinstance(phone, PhoneNumber):
if not phone.number:
raise TransportRecipientError(_("This phone number is not available"))
return phone
try:
phone_number = PhoneNumber.add(phone)
except PhoneNumberBlockedError as exc:
raise TransportRecipientError(_("This phone number has been blocked")) from exc
if not phone_number.allow_whatsapp:
raise TransportRecipientError(_("Whatsapp is disabled for this phone number"))
if not phone_number.number:
# This should never happen as :meth:`PhoneNumber.add` will restore the number
raise TransportRecipientError(_("This phone number is not available"))
return phone_number


def send_wa_via_meta(phone: str, message, callback: bool = True) -> str:
"""
Send the Whatsapp message using Meta Cloud API.
:param phone: Phone number
:param message: Message to deliver to phone number
:param callback: Whether to request a status callback
:return: Transaction id
"""
phone_number = get_phone_number(phone)
sid = app.config['WHATSAPP_PHONE_ID']
token = app.config['WHATSAPP_TOKEN']
payload = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
'to': phone_number.number,
"type": "template",
'body': str(message),
'DltEntityId': message.registered_entityid,
}
try:
r = requests.post(
f'https://graph.facebook.com/v15.0/{sid}/messages',
timeout=30,
auth=(token),
data=payload,
)
if r.status_code == 200:
jsonresponse = r.json()
transactionid = jsonresponse['messages'].get('id')
return transactionid
raise TransportTransactionError(_("Whatsapp API error"), r.status_code, r.text)
except requests.ConnectionError as exc:
raise TransportConnectionError(_("Whatsapp not reachable")) from exc


def send_wa_via_on_premise(phone: str, message, callback: bool = True) -> str:
"""
Send the Whatsapp message using Meta Cloud API.
:param phone: Phone number
:param message: Message to deliver to phone number
:param callback: Whether to request a status callback
:return: Transaction id
"""
phone_number = get_phone_number(phone)
sid = app.config['WHATSAPP_PHONE_ID']
token = app.config['WHATSAPP_TOKEN']
payload = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
'to': phone_number.number,
"type": "template",
'body': str(message),
'DltEntityId': message.registered_entityid,
}
try:
r = requests.post(
f'https://graph.facebook.com/v15.0/{sid}/messages',
timeout=30,
auth=(token),
data=payload,
)
if r.status_code == 200:
jsonresponse = r.json()
transactionid = jsonresponse['messages'].get('id')
return transactionid
raise TransportTransactionError(_("Whatsapp API error"), r.status_code, r.text)
except requests.ConnectionError as exc:
raise TransportConnectionError(_("Whatsapp not reachable")) from exc
28 changes: 27 additions & 1 deletion funnel/views/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,34 @@ def dispatch_transport_sms(user_notification, view):
)


@rqjob
@transport_worker_wrapper
def dispatch_transport_whatsapp(user_notification, view):
if not user_notification.user.main_notification_preferences.by_transport(
'whatsapp'
):
# Cancel delivery if user's main switch is off. This was already checked, but
# the worker may be delayed and the user may have changed their preference.
user_notification.messageid_whatsapp = 'cancelled'
return
user_notification.messageid_whatsapp = sms.send(
str(view.transport_for('sms')), view.sms_with_unsubscribe()
)
statsd.incr(
'notification.transport',
tags={
'notification_type': user_notification.notification_type,
'transport': 'whatsapp',
},
)


# Add transport workers here as their worker methods are written
transport_workers = {'email': dispatch_transport_email, 'sms': dispatch_transport_sms}
transport_workers = {
'email': dispatch_transport_email,
'sms': dispatch_transport_sms,
'whatsapp': dispatch_transport_whatsapp,
}

# --- Notification background workers --------------------------------------------------

Expand Down

0 comments on commit aa05f7a

Please sign in to comment.