|
@@ -6,16 +6,16 @@ import phonenumbers
|
|
|
from django import forms
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
|
|
-from sentry import http
|
|
|
from sentry.plugins.bases.notify import NotificationPlugin
|
|
|
|
|
|
+from .client import TwilioApiClient
|
|
|
+from sentry_plugins.base import CorePluginMixin
|
|
|
+
|
|
|
import sentry
|
|
|
|
|
|
DEFAULT_REGION = "US"
|
|
|
MAX_SMS_LENGTH = 160
|
|
|
|
|
|
-twilio_sms_endpoint = "https://api.twilio.com/2010-04-01/Accounts/{0}/SMS/Messages.json"
|
|
|
-
|
|
|
|
|
|
def validate_phone(phone):
|
|
|
try:
|
|
@@ -36,10 +36,6 @@ def clean_phone(phone):
|
|
|
)
|
|
|
|
|
|
|
|
|
-def basic_auth(user, password):
|
|
|
- return "Basic " + (user + ":" + password).encode("base64").replace("\n", "")
|
|
|
-
|
|
|
-
|
|
|
# XXX: can likely remove the dedupe here after notify_users has test coverage;
|
|
|
# in theory only cleaned data would make it to the plugin via the form,
|
|
|
# and cleaned numbers are deduped already.
|
|
@@ -92,7 +88,7 @@ class TwilioConfigurationForm(forms.Form):
|
|
|
return self.cleaned_data
|
|
|
|
|
|
|
|
|
-class TwilioPlugin(NotificationPlugin):
|
|
|
+class TwilioPlugin(CorePluginMixin, NotificationPlugin):
|
|
|
author = "Matt Robenolt"
|
|
|
author_url = "https://github.com/mattrobenolt"
|
|
|
version = sentry.VERSION
|
|
@@ -129,7 +125,18 @@ class TwilioPlugin(NotificationPlugin):
|
|
|
# This doesn't depend on email permission... stuff.
|
|
|
return True
|
|
|
|
|
|
+ def error_message_from_json(self, data):
|
|
|
+ code = data.get("code")
|
|
|
+ message = data.get("message")
|
|
|
+ more_info = data.get("more_info")
|
|
|
+ error_message = "%s - %s %s" % (code, message, more_info)
|
|
|
+ if message:
|
|
|
+ return error_message
|
|
|
+ return None
|
|
|
+
|
|
|
def notify_users(self, group, event, **kwargs):
|
|
|
+ if not self.is_configured(group.project):
|
|
|
+ return
|
|
|
project = group.project
|
|
|
|
|
|
body = "Sentry [{0}] {1}: {2}".format(
|
|
@@ -139,38 +146,31 @@ class TwilioPlugin(NotificationPlugin):
|
|
|
)
|
|
|
body = body[:MAX_SMS_LENGTH]
|
|
|
|
|
|
- account_sid = self.get_option("account_sid", project)
|
|
|
- auth_token = self.get_option("auth_token", project)
|
|
|
- sms_from = clean_phone(self.get_option("sms_from", project))
|
|
|
- endpoint = twilio_sms_endpoint.format(account_sid)
|
|
|
-
|
|
|
- sms_to = self.get_option("sms_to", project)
|
|
|
- if not sms_to:
|
|
|
- return
|
|
|
- sms_to = split_sms_to(sms_to)
|
|
|
+ client = self.get_client(group.project)
|
|
|
|
|
|
- headers = {"Authorization": basic_auth(account_sid, auth_token)}
|
|
|
+ payload = {"From": client.sms_from, "Body": body}
|
|
|
|
|
|
errors = []
|
|
|
|
|
|
- for phone in sms_to:
|
|
|
+ for phone in client.sms_to:
|
|
|
if not phone:
|
|
|
continue
|
|
|
try:
|
|
|
# TODO: Use API client with raise_error
|
|
|
phone = clean_phone(phone)
|
|
|
- http.safe_urlopen(
|
|
|
- endpoint,
|
|
|
- method="POST",
|
|
|
- headers=headers,
|
|
|
- data={"From": sms_from, "To": phone, "Body": body},
|
|
|
- ).raise_for_status()
|
|
|
+ payload = payload.copy()
|
|
|
+ payload["To"] = phone
|
|
|
+ client.request(payload)
|
|
|
except Exception as e:
|
|
|
errors.append(e)
|
|
|
|
|
|
if errors:
|
|
|
- if len(errors) == 1:
|
|
|
- raise errors[0]
|
|
|
+ self.raise_error(errors[0])
|
|
|
|
|
|
- # TODO: multi-exception
|
|
|
- raise Exception(errors)
|
|
|
+ def get_client(self, project):
|
|
|
+ account_sid = self.get_option("account_sid", project)
|
|
|
+ auth_token = self.get_option("auth_token", project)
|
|
|
+ sms_from = clean_phone(self.get_option("sms_from", project))
|
|
|
+ sms_to = self.get_option("sms_to", project)
|
|
|
+ sms_to = split_sms_to(sms_to)
|
|
|
+ return TwilioApiClient(account_sid, auth_token, sms_from, sms_to)
|