From ee9ac0ffefca6131192baf0b14ed2d497432755f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Wed, 29 Jul 2020 18:30:50 +0300 Subject: [PATCH] New integration: phone calls. Fixes #403 --- CHANGELOG.md | 1 + hc/api/models.py | 5 +- hc/api/tests/test_notify.py | 38 ++++++++++ hc/api/transports.py | 25 +++++++ hc/front/tests/test_add_call.py | 59 ++++++++++++++++ hc/front/urls.py | 1 + hc/front/views.py | 28 ++++++++ static/css/icomoon.css | 14 ++-- static/fonts/icomoon.eot | Bin 12428 -> 12844 bytes static/fonts/icomoon.svg | 1 + static/fonts/icomoon.ttf | Bin 12264 -> 12680 bytes static/fonts/icomoon.woff | Bin 12340 -> 12756 bytes static/img/integrations/call.png | Bin 0 -> 3499 bytes templates/emails/sms-limit-subject.html | 2 +- templates/front/channels.html | 16 ++++- templates/front/event_summary.html | 5 ++ templates/front/welcome.html | 14 +++- templates/integrations/add_call.html | 84 +++++++++++++++++++++++ templates/integrations/call_message.html | 1 + templates/payments/pricing.html | 14 ++-- 20 files changed, 292 insertions(+), 16 deletions(-) create mode 100644 hc/front/tests/test_add_call.py create mode 100644 static/img/integrations/call.png create mode 100644 templates/integrations/add_call.html create mode 100644 templates/integrations/call_message.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5f2da3..0d2f1ea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. - Updated Discord integration to use discord.com instead of discordapp.com - Add "Failure Keyword" filtering for inbound emails (#396) - Add support for multiple, comma-separated keywords (#396) +- New integration: phone calls (#403) ### Bug Fixes - Removing Pager Team integration, project appears to be discontinued diff --git a/hc/api/models.py b/hc/api/models.py index 8e7b3040..37e0771b 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -50,6 +50,7 @@ CHANNEL_KINDS = ( ("shell", "Shell Command"), ("zulip", "Zulip"), ("spike", "Spike"), + ("call", "Phone Call"), ) PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"} @@ -460,6 +461,8 @@ class Channel(models.Model): return transports.Zulip(self) elif self.kind == "spike": return transports.Spike(self) + elif self.kind == "call": + return transports.Call(self) else: raise NotImplementedError("Unknown channel kind: %s" % self.kind) @@ -640,7 +643,7 @@ class Channel(models.Model): @property def sms_number(self): - assert self.kind in ("sms", "whatsapp") + assert self.kind in ("call", "sms", "whatsapp") if self.value.startswith("{"): doc = json.loads(self.value) return doc["value"] diff --git a/hc/api/tests/test_notify.py b/hc/api/tests/test_notify.py index b7ec1c8b..a64da31f 100644 --- a/hc/api/tests/test_notify.py +++ b/hc/api/tests/test_notify.py @@ -754,6 +754,44 @@ class NotifyTestCase(BaseTestCase): self.assertEqual(email.to[0], "alice@example.org") self.assertEqual(email.subject, "Monthly WhatsApp Limit Reached") + @patch("hc.api.transports.requests.request") + def test_call(self, mock_post): + value = {"label": "foo", "value": "+1234567890"} + self._setup_data("call", json.dumps(value)) + self.check.last_ping = now() - td(hours=2) + + mock_post.return_value.status_code = 200 + + self.channel.notify(self.check) + assert Notification.objects.count() == 1 + + args, kwargs = mock_post.call_args + payload = kwargs["data"] + self.assertEqual(payload["To"], "+1234567890") + + @patch("hc.api.transports.requests.request") + def test_call_limit(self, mock_post): + # At limit already: + self.profile.last_sms_date = now() + self.profile.sms_sent = 50 + self.profile.save() + + definition = {"value": "+1234567890"} + self._setup_data("call", json.dumps(definition)) + + self.channel.notify(self.check) + self.assertFalse(mock_post.called) + + n = Notification.objects.get() + self.assertTrue("Monthly phone call limit exceeded" in n.error) + + # And email should have been sent + self.assertEqual(len(mail.outbox), 1) + + email = mail.outbox[0] + self.assertEqual(email.to[0], "alice@example.org") + self.assertEqual(email.subject, "Monthly Phone Call Limit Reached") + @patch("apprise.Apprise") @override_settings(APPRISE_ENABLED=True) def test_apprise_enabled(self, mock_apprise): diff --git a/hc/api/transports.py b/hc/api/transports.py index 1333a350..3ed517e5 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -478,6 +478,31 @@ class Sms(HttpTransport): return self.post(url, data=data, auth=auth) +class Call(HttpTransport): + URL = "https://api.twilio.com/2010-04-01/Accounts/%s/Calls.json" + + def is_noop(self, check): + return check.status != "down" + + def notify(self, check): + profile = Profile.objects.for_user(self.channel.project.owner) + if not profile.authorize_sms(): + profile.send_sms_limit_notice("phone call") + return "Monthly phone call limit exceeded" + + url = self.URL % settings.TWILIO_ACCOUNT + auth = (settings.TWILIO_ACCOUNT, settings.TWILIO_AUTH) + twiml = tmpl("call_message.html", check=check, site_name=settings.SITE_NAME) + + data = { + "From": settings.TWILIO_FROM, + "To": self.channel.sms_number, + "Twiml": twiml, + } + + return self.post(url, data=data, auth=auth) + + class WhatsApp(HttpTransport): URL = "https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json" diff --git a/hc/front/tests/test_add_call.py b/hc/front/tests/test_add_call.py new file mode 100644 index 00000000..ecf14ce9 --- /dev/null +++ b/hc/front/tests/test_add_call.py @@ -0,0 +1,59 @@ +from django.test.utils import override_settings +from hc.api.models import Channel +from hc.test import BaseTestCase + + +@override_settings(TWILIO_ACCOUNT="foo", TWILIO_AUTH="foo", TWILIO_FROM="123") +class AddCallTestCase(BaseTestCase): + def setUp(self): + super(AddCallTestCase, self).setUp() + self.url = "/projects/%s/add_call/" % self.project.code + + def test_instructions_work(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertContains(r, "Get a phone call") + + @override_settings(USE_PAYMENTS=True) + def test_it_warns_about_limits(self): + self.profile.sms_limit = 0 + self.profile.save() + + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertContains(r, "upgrade to a") + + def test_it_creates_channel(self): + form = {"label": "My Phone", "value": "+1234567890"} + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, form) + self.assertRedirects(r, self.channels_url) + + c = Channel.objects.get() + self.assertEqual(c.kind, "call") + self.assertEqual(c.sms_number, "+1234567890") + self.assertEqual(c.name, "My Phone") + self.assertEqual(c.project, self.project) + + def test_it_rejects_bad_number(self): + form = {"value": "not a phone number"} + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, form) + self.assertContains(r, "Invalid phone number format.") + + def test_it_trims_whitespace(self): + form = {"value": " +1234567890 "} + + self.client.login(username="alice@example.org", password="password") + self.client.post(self.url, form) + + c = Channel.objects.get() + self.assertEqual(c.sms_number, "+1234567890") + + @override_settings(TWILIO_AUTH=None) + def test_it_requires_credentials(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertEqual(r.status_code, 404) diff --git a/hc/front/urls.py b/hc/front/urls.py index 016918cb..bce15731 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -55,6 +55,7 @@ channel_urls = [ project_urls = [ path("add_apprise/", views.add_apprise, name="hc-add-apprise"), + path("add_call/", views.add_call, name="hc-add-call"), path("add_discord/", views.add_discord, name="hc-add-discord"), path("add_email/", views.add_email, name="hc-add-email"), path("add_matrix/", views.add_matrix, name="hc-add-matrix"), diff --git a/hc/front/views.py b/hc/front/views.py index 2df1777a..e892781c 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -269,6 +269,7 @@ def index(request): "enable_shell": settings.SHELL_ENABLED is True, "enable_slack_btn": settings.SLACK_CLIENT_ID is not None, "enable_sms": settings.TWILIO_AUTH is not None, + "enable_call": settings.TWILIO_AUTH is not None, "enable_telegram": settings.TELEGRAM_TOKEN is not None, "enable_trello": settings.TRELLO_APP_KEY is not None, "enable_whatsapp": settings.TWILIO_USE_WHATSAPP, @@ -702,6 +703,7 @@ def channels(request, code): "enable_shell": settings.SHELL_ENABLED is True, "enable_slack_btn": settings.SLACK_CLIENT_ID is not None, "enable_sms": settings.TWILIO_AUTH is not None, + "enable_call": settings.TWILIO_AUTH is not None, "enable_telegram": settings.TELEGRAM_TOKEN is not None, "enable_trello": settings.TRELLO_APP_KEY is not None, "enable_whatsapp": settings.TWILIO_USE_WHATSAPP, @@ -1515,6 +1517,32 @@ def add_sms(request, code): return render(request, "integrations/add_sms.html", ctx) +@require_setting("TWILIO_AUTH") +@login_required +def add_call(request, code): + project = _get_project_for_user(request, code) + if request.method == "POST": + form = forms.AddSmsForm(request.POST) + if form.is_valid(): + channel = Channel(project=project, kind="call") + channel.name = form.cleaned_data["label"] + channel.value = json.dumps({"value": form.cleaned_data["value"]}) + channel.save() + + channel.assign_all_checks() + return redirect("hc-p-channels", project.code) + else: + form = forms.AddSmsForm() + + ctx = { + "page": "channels", + "project": project, + "form": form, + "profile": project.owner_profile, + } + return render(request, "integrations/add_call.html", ctx) + + @require_setting("TWILIO_USE_WHATSAPP") @login_required def add_whatsapp(request, code): diff --git a/static/css/icomoon.css b/static/css/icomoon.css index 0cb69798..d0897aa5 100644 --- a/static/css/icomoon.css +++ b/static/css/icomoon.css @@ -1,10 +1,10 @@ @font-face { font-family: 'icomoon'; - src: url('../fonts/icomoon.eot?37tb6f'); - src: url('../fonts/icomoon.eot?37tb6f#iefix') format('embedded-opentype'), - url('../fonts/icomoon.ttf?37tb6f') format('truetype'), - url('../fonts/icomoon.woff?37tb6f') format('woff'), - url('../fonts/icomoon.svg?37tb6f#icomoon') format('svg'); + src: url('../fonts/icomoon.eot?e4bee3'); + src: url('../fonts/icomoon.eot?e4bee3#iefix') format('embedded-opentype'), + url('../fonts/icomoon.ttf?e4bee3') format('truetype'), + url('../fonts/icomoon.woff?e4bee3') format('woff'), + url('../fonts/icomoon.svg?e4bee3#icomoon') format('svg'); font-weight: normal; font-style: normal; } @@ -24,6 +24,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-call:before { + content: "\e91a"; + color: #e81a34; +} .icon-spike:before { content: "\e919"; color: #007bff; diff --git a/static/fonts/icomoon.eot b/static/fonts/icomoon.eot index 93b4cadcafd5e64aef7b44546d088d70213f1779..8eec81efefe9be3fdbab20f89d90fd1688716466 100644 GIT binary patch delta 620 zcmZuu&1(}u6n}4bv%A^P#3Z|E5@SefLfbUSY}#F0)0$dA6hZ32da1f3g@B0(SZV1c z2TSY0OW0l%@f5*>h)}#}4nfd^_#b%j5AdLeo;>KR)tkecH~il3V_@cy`?A3oEP#ik z&)eME3fb@1w)8UqFr@o-z3p~)p7%edd+*j?eFMM|Kw;3_a2sPsAG!d=236TjIuK=U zf#RnWPc++`+t2c%PBGQPi&m%Z4&T1s11L2qzTS4XyReAgDfaoyUAMjQ_;T~xO<*Yl#MftlWxYDqFyRew9TSHP)0ysZ0KY~Yup>AH7oY+EQ#`tESVgaQ1u7%sr!}6Qa}on_+0D_jmTIhB1s8R6$P1*VybpJ7#J82 zFfeHIWTYmhNFI*xV_?uZ15{;}0TkfiX0c>o&}9MgRWfo*DrTv3@B{f8K>m`P{N%)C z85`Pwe31H(+{B6k1{=l?3=Db-K)ym=Vs7e8FV5dUz5~#Xwu1cP5(Z|V41@j&kURr3 z^Wnt|cA9_MEmJy}QJoKbUf WivE(xJO&|b3?MC + \ No newline at end of file diff --git a/static/fonts/icomoon.ttf b/static/fonts/icomoon.ttf index 65a8141f6010609d61b19ec174260dae28e37c24..f1890ce83af31281fc62a2fc426843312aad1141 100644 GIT binary patch delta 675 zcmZuv&ubG=5T4m=b~n45-K4|TB8+;A`}(WOHxBx8){+#ty+7D zV5ve+8ZSZ-gckIm6pBzZhg<~z2QMByh}1(*9(2|!cyO4U3#cuQn>J)$Kd405D8=bD^@-qUs{NM^#v;-@Ea?^yVw!9zb%d zHeZ?RJNQ7F)Ex1-8Z`uo9Vfg=c(B%3UU?oDoPE28C}!WJQ7K%s6K@oh{oa?k-u|nG-qhM zu9~a^wL@7ZXXrWVnAVOzLXx-1I%^t^<6pxF{>Lz@F|b%FZwygvluL{M?j=Mfb1oE) zyHK`q661`j>O@iyWudDMvb;mY)Ao}t_k~bekX9wp&$2+M6mrG`AEQDJdP}5lX)(dQ@njV%7P%H*J2IKWZVVy zl-;gbFES|0b%*naz*)Sug09~U88`im<&0HvyAmPBoENx2RF+&U&D&)2ST4z6L;BaSXWUKRW#&6&;Rlsy!pm%pFwT-_3 D0)BWc delta 281 zcmeB3ei2{Kz{tSBz|GLWz|3IaAFOZ0FT`dJ6xjpB3CX#M1yxJRRx&U!$^iLu(i4jd zfV2RRzXM2fq~}zoRh1=#0r>|Q7_@mZQWH}o4@dYhFzB2C%9~{X1vt1_EEyPdS%7?% zjNFonS?V18K)wc$za%F=*)nli#)dYa07yefZem3NgAL;c1_nI^AYUObF*kLl7w2yv z-vMYzTS0zt3DBWHpnqcG4tGY$NsO_KlAGfg^R<;h{s00yp5trd`E9;3aD%L5V7RTv r`58t}?$9%5)SP@kZ^`5g{SX1L*EJd6F$jTGGEH8{D7^Wx{!&H&Bh5jM diff --git a/static/fonts/icomoon.woff b/static/fonts/icomoon.woff index 68106e2a6452ccde3b8ca2df3a37c058c9ceac66..8a3cfe19483be7e1fda0466bec15cf97b6f01e2a 100644 GIT binary patch delta 716 zcmZuv&rcIU6rQ);?rwLtzvy;b+FD7$w$Rdcx3Co~71UrfB#j=_OVOqx4YsxkRgg;$ z8Z_ZxuwG1zCrJE}7!vA5IrQQmV8YFRz=JVz;G_w@DHtxk%$J$@-uJ$l$-MTh-IB7D zpPnW_FtlAFb?2w8Jy4!A?0VeQJYA|5>x4iVGp{ICHXpp5FD@d-u(%hF;FzyGyn~!f zNPL6hbK%2}@?7yYA&D*27!t3-shg=*lxk~Z!vgLQZw%EW2wb%dT!PlCB0{FJy zK`qwAD<5=NV(O5SnhFKKs%JBjErs|L#=H`LX;1pb($KE!#~3y{Kqh@wZ6uD zO$mTWUGfJa4v;OJ#MncsIuRE{S=dnrSl%YWl=Y0mIpa?X(uyQ{Sk`B?KN<_%Pp5Nc z(VXPM?vH5Er}w!0B7jxkB}Q~xZKnd%!_c9mjmQ0fUynZ zhrGnxRG?T7(2;E*JkyKwcR_wJ&@cm$iGTbVB`5nZ#xhE7Ud5QNtqk@k&{&@1YvcKC zzA|uw{J_9)TaWWIjGlZ&&zwz2&|F`7&gM2^$nIX0s!>W BMalpG diff --git a/static/img/integrations/call.png b/static/img/integrations/call.png new file mode 100644 index 0000000000000000000000000000000000000000..94a30dcdba00447d2336b917ed76df761be0165c GIT binary patch literal 3499 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 ch.sms_number }} + {% elif ch.kind == "call" %} + Phone call to {{ ch.sms_number }} {% elif ch.kind == "trello" %} Trello board {{ ch.trello_board_list|first }}, @@ -122,7 +124,7 @@ {% else %} Never {% endif %} - {% if ch.kind == "sms" or ch.kind == "whatsapp" %} + {% if ch.kind == "sms" or ch.kind == "whatsapp" or ch.kind == "call" %}

Used {{ profile.sms_sent_this_month }} of {{ profile.sms_limit }} sends this month.

{% endif %} @@ -191,6 +193,17 @@ Add Integration + {% if enable_call %} +
  • + Phone icon + +

    Phone Call

    +

    Get a phone call when a check goes down.

    + Add Integration +
  • + {% endif %} +
  • Webhook icon @@ -199,6 +212,7 @@

    Receive a HTTP callback when a check goes down.

    Add Integration
  • + {% if enable_apprise %}
  • + {% if enable_call %} +
    +
    + +

    + {% trans "Phone Call" %}
    +   +

    +
    +
    + {% endif %} + {% endif %} -
    +
    Spike.sh icon

    diff --git a/templates/integrations/add_call.html b/templates/integrations/add_call.html new file mode 100644 index 00000000..7f2b838f --- /dev/null +++ b/templates/integrations/add_call.html @@ -0,0 +1,84 @@ +{% extends "base.html" %} +{% load humanize static hc_extras %} + +{% block title %}Add Phone Call Integration - {{ site_name }}{% endblock %} + + +{% block content %} +
    +
    +

    Phone Call

    +

    + Get a phone call when a check goes down. When you pick up the call, + a text-to-speech engine will read out a message and then hang up. +

    + + {% if show_pricing and profile.sms_limit == 0 %} +

    + Paid plan required. + Phone call notifications are not available on the free plan–they + cost too much! Please upgrade to a + paid plan + to enable phone call notifications. +

    + {% endif %} + +

    Integration Settings

    + +
    + {% csrf_token %} +
    + +
    + + + {% if form.label.errors %} +
    + {{ form.label.errors|join:"" }} +
    + {% else %} + + Optional. If you add multiple phone numbers, + the labels will help you tell them apart. + + {% endif %} +
    +
    +
    + +
    + + + {% if form.value.errors %} +
    + {{ form.value.errors|join:"" }} +
    + {% else %} + + Make sure the phone number starts with "+" and has the + country code. + + {% endif %} +
    +
    +
    +
    + +
    +
    +
    +
    +
    +{% endblock %} diff --git a/templates/integrations/call_message.html b/templates/integrations/call_message.html new file mode 100644 index 00000000..0a0664b4 --- /dev/null +++ b/templates/integrations/call_message.html @@ -0,0 +1 @@ +Hello! A message from {{ site_name }}: The check "{{ check.name_then_code }}" is down. \ No newline at end of file diff --git a/templates/payments/pricing.html b/templates/payments/pricing.html index 47cba7a6..692405d5 100644 --- a/templates/payments/pricing.html +++ b/templates/payments/pricing.html @@ -87,7 +87,7 @@

  • API access
  • - 5 SMS & WhatsApp credits + 5 SMS, WhatsApp and call credits
  •  
  • @@ -120,7 +120,7 @@
  • API access
  • - 5 SMS & WhatsApp credits + 5 SMS, WhatsApp and call credits
  • Email support
  • @@ -156,7 +156,7 @@
  • API access
  • - 50 SMS & WhatsApp credits + 50 SMS, WhatsApp and call credits
  • Email support
  • @@ -192,7 +192,7 @@
  • API access
  • - 500 SMS & WhatsApp credits + 500 SMS, WhatsApp and call credits
  • Priority email support
  • @@ -303,10 +303,10 @@ {% if not request.user.is_authenticated %}