forked from GithubBackups/healthchecks
Telegram integration.
This commit is contained in:
parent
eece7c7551
commit
2481aa5a23
@ -35,7 +35,8 @@ CHANNEL_KINDS = (("email", "Email"),
|
|||||||
("pushbullet", "Pushbullet"),
|
("pushbullet", "Pushbullet"),
|
||||||
("opsgenie", "OpsGenie"),
|
("opsgenie", "OpsGenie"),
|
||||||
("victorops", "VictorOps"),
|
("victorops", "VictorOps"),
|
||||||
("discord", "Discord"))
|
("discord", "Discord"),
|
||||||
|
("telegram", "Telegram"))
|
||||||
|
|
||||||
PO_PRIORITIES = {
|
PO_PRIORITIES = {
|
||||||
-2: "lowest",
|
-2: "lowest",
|
||||||
@ -262,6 +263,8 @@ class Channel(models.Model):
|
|||||||
return transports.OpsGenie(self)
|
return transports.OpsGenie(self)
|
||||||
elif self.kind == "discord":
|
elif self.kind == "discord":
|
||||||
return transports.Discord(self)
|
return transports.Discord(self)
|
||||||
|
elif self.kind == "telegram":
|
||||||
|
return transports.Telegram(self)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
|
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
|
||||||
|
|
||||||
@ -348,6 +351,24 @@ class Channel(models.Model):
|
|||||||
doc = json.loads(self.value)
|
doc = json.loads(self.value)
|
||||||
return doc["webhook"]["id"]
|
return doc["webhook"]["id"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def telegram_id(self):
|
||||||
|
assert self.kind == "telegram"
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("id")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def telegram_type(self):
|
||||||
|
assert self.kind == "telegram"
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("type")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def telegram_name(self):
|
||||||
|
assert self.kind == "telegram"
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("name")
|
||||||
|
|
||||||
def latest_notification(self):
|
def latest_notification(self):
|
||||||
return Notification.objects.filter(channel=self).latest()
|
return Notification.objects.filter(channel=self).latest()
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.test import override_settings
|
|
||||||
from hc.api.models import Channel, Check, Notification
|
from hc.api.models import Channel, Check, Notification
|
||||||
from hc.test import BaseTestCase
|
from hc.test import BaseTestCase
|
||||||
from mock import patch
|
from mock import patch
|
||||||
@ -282,3 +281,17 @@ class NotifyTestCase(BaseTestCase):
|
|||||||
_, kwargs = mock_post.call_args
|
_, kwargs = mock_post.call_args
|
||||||
self.assertEqual(kwargs["json"]["type"], "note")
|
self.assertEqual(kwargs["json"]["type"], "note")
|
||||||
self.assertEqual(kwargs["headers"]["Access-Token"], "fake-token")
|
self.assertEqual(kwargs["headers"]["Access-Token"], "fake-token")
|
||||||
|
|
||||||
|
@patch("hc.api.transports.requests.request")
|
||||||
|
def test_telegram(self, mock_post):
|
||||||
|
v = json.dumps({"id": 123})
|
||||||
|
self._setup_data("telegram", v)
|
||||||
|
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["json"]
|
||||||
|
self.assertEqual(payload["chat_id"], 123)
|
||||||
|
self.assertTrue("The check" in payload["text"])
|
||||||
|
@ -61,7 +61,8 @@ class Email(Transport):
|
|||||||
|
|
||||||
class HttpTransport(Transport):
|
class HttpTransport(Transport):
|
||||||
|
|
||||||
def _request(self, method, url, **kwargs):
|
@classmethod
|
||||||
|
def _request(cls, method, url, **kwargs):
|
||||||
try:
|
try:
|
||||||
options = dict(kwargs)
|
options = dict(kwargs)
|
||||||
if "headers" not in options:
|
if "headers" not in options:
|
||||||
@ -79,19 +80,21 @@ class HttpTransport(Transport):
|
|||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
return "Connection failed"
|
return "Connection failed"
|
||||||
|
|
||||||
def get(self, url):
|
@classmethod
|
||||||
|
def get(cls, url):
|
||||||
# Make 3 attempts--
|
# Make 3 attempts--
|
||||||
for x in range(0, 3):
|
for x in range(0, 3):
|
||||||
error = self._request("get", url)
|
error = cls._request("get", url)
|
||||||
if error is None:
|
if error is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
return error
|
return error
|
||||||
|
|
||||||
def post(self, url, **kwargs):
|
@classmethod
|
||||||
|
def post(cls, url, **kwargs):
|
||||||
# Make 3 attempts--
|
# Make 3 attempts--
|
||||||
for x in range(0, 3):
|
for x in range(0, 3):
|
||||||
error = self._request("post", url, **kwargs)
|
error = cls._request("post", url, **kwargs)
|
||||||
if error is None:
|
if error is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -277,3 +280,19 @@ class Discord(HttpTransport):
|
|||||||
payload = json.loads(text)
|
payload = json.loads(text)
|
||||||
url = self.channel.discord_webhook_url + "/slack"
|
url = self.channel.discord_webhook_url + "/slack"
|
||||||
return self.post(url, json=payload)
|
return self.post(url, json=payload)
|
||||||
|
|
||||||
|
|
||||||
|
class Telegram(HttpTransport):
|
||||||
|
SM = "https://api.telegram.org/bot%s/sendMessage" % settings.TELEGRAM_TOKEN
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def send(cls, chat_id, text):
|
||||||
|
return cls.post(cls.SM, json={
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": text,
|
||||||
|
"parse_mode": "html"
|
||||||
|
})
|
||||||
|
|
||||||
|
def notify(self, check):
|
||||||
|
text = tmpl("telegram_message.html", check=check)
|
||||||
|
return self.send(self.channel.telegram_id, text)
|
||||||
|
23
hc/front/schemas.py
Normal file
23
hc/front/schemas.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
telegram_callback = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"chat": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "number"},
|
||||||
|
"type": {"enum": ["group", "private"]},
|
||||||
|
"title": {"type": "string"},
|
||||||
|
"username": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["id", "type"]
|
||||||
|
},
|
||||||
|
"text": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["chat", "text"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["message"]
|
||||||
|
}
|
@ -12,11 +12,6 @@ def hc_duration(td):
|
|||||||
return format_duration(td)
|
return format_duration(td)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
|
||||||
def settings_value(name):
|
|
||||||
return getattr(settings, name, "")
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def site_name():
|
def site_name():
|
||||||
return settings.SITE_NAME
|
return settings.SITE_NAME
|
||||||
|
76
hc/front/tests/test_add_telegram.py
Normal file
76
hc/front/tests/test_add_telegram.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.core import signing
|
||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
class AddTelegramTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_telegram/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "start@HealthchecksBot")
|
||||||
|
|
||||||
|
def test_it_shows_confirmation(self):
|
||||||
|
payload = signing.dumps((123, "group", "My Group"))
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url + "?" + payload)
|
||||||
|
self.assertContains(r, "My Group")
|
||||||
|
|
||||||
|
def test_it_works(self):
|
||||||
|
payload = signing.dumps((123, "group", "My Group"))
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url + "?" + payload, {})
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.kind, "telegram")
|
||||||
|
self.assertEqual(c.telegram_id, 123)
|
||||||
|
self.assertEqual(c.telegram_type, "group")
|
||||||
|
self.assertEqual(c.telegram_name, "My Group")
|
||||||
|
|
||||||
|
@patch("hc.api.transports.requests.request")
|
||||||
|
def test_it_sends_invite(self, mock_get):
|
||||||
|
data = {
|
||||||
|
"message": {
|
||||||
|
"chat": {
|
||||||
|
"id": 123,
|
||||||
|
"title": "My Group",
|
||||||
|
"type": "group"
|
||||||
|
},
|
||||||
|
"text": "/start"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r = self.client.post("/integrations/telegram/bot/", json.dumps(data),
|
||||||
|
content_type="application/json")
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(mock_get.called)
|
||||||
|
|
||||||
|
@patch("hc.api.transports.requests.request")
|
||||||
|
def test_bot_handles_bad_message(self, mock_get):
|
||||||
|
samples = ["", "{}"]
|
||||||
|
|
||||||
|
# text is missing
|
||||||
|
samples.append(json.dumps({
|
||||||
|
"message": {"chat": {"id": 123, "type": "group"}}
|
||||||
|
}))
|
||||||
|
|
||||||
|
# bad type
|
||||||
|
samples.append(json.dumps({
|
||||||
|
"message": {
|
||||||
|
"chat": {"id": 123, "type": "invalid"},
|
||||||
|
"text": "/start"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
for sample in samples:
|
||||||
|
r = self.client.post("/integrations/telegram/bot/", sample,
|
||||||
|
content_type="application/json")
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 400)
|
@ -24,6 +24,8 @@ channel_urls = [
|
|||||||
url(r'^add_pushover/$', views.add_pushover, name="hc-add-pushover"),
|
url(r'^add_pushover/$', views.add_pushover, name="hc-add-pushover"),
|
||||||
url(r'^add_opsgenie/$', views.add_opsgenie, name="hc-add-opsgenie"),
|
url(r'^add_opsgenie/$', views.add_opsgenie, name="hc-add-opsgenie"),
|
||||||
url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"),
|
url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"),
|
||||||
|
url(r'^telegram/bot/$', views.telegram_bot),
|
||||||
|
url(r'^add_telegram/$', views.add_telegram, name="hc-add-telegram"),
|
||||||
url(r'^([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
|
url(r'^([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
|
||||||
url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
|
url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
|
||||||
url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email,
|
url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email,
|
||||||
|
@ -1,29 +1,36 @@
|
|||||||
from collections import Counter
|
from collections import Counter
|
||||||
from croniter import croniter
|
|
||||||
from datetime import datetime, timedelta as td
|
from datetime import datetime, timedelta as td
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
|
import json
|
||||||
|
|
||||||
import requests
|
from croniter import croniter
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core import signing
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.http import Http404, HttpResponseBadRequest, HttpResponseForbidden
|
from django.http import (Http404, HttpResponse, HttpResponseBadRequest,
|
||||||
|
HttpResponseForbidden)
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
|
from django.utils.six.moves.urllib.parse import urlencode
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
from django.utils.six.moves.urllib.parse import urlencode
|
|
||||||
from hc.api.decorators import uuid_or_400
|
from hc.api.decorators import uuid_or_400
|
||||||
from hc.api.models import (DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check,
|
from hc.api.models import (DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check,
|
||||||
Ping, Notification)
|
Ping, Notification)
|
||||||
|
from hc.api.transports import Telegram
|
||||||
from hc.front.forms import (AddWebhookForm, NameTagsForm,
|
from hc.front.forms import (AddWebhookForm, NameTagsForm,
|
||||||
TimeoutForm, AddUrlForm, AddPdForm, AddEmailForm,
|
TimeoutForm, AddUrlForm, AddPdForm, AddEmailForm,
|
||||||
AddOpsGenieForm, CronForm)
|
AddOpsGenieForm, CronForm)
|
||||||
|
from hc.front.schemas import telegram_callback
|
||||||
|
from hc.lib import jsonschema
|
||||||
from pytz import all_timezones
|
from pytz import all_timezones
|
||||||
from pytz.exceptions import UnknownTimeZoneError
|
from pytz.exceptions import UnknownTimeZoneError
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
# from itertools recipes:
|
# from itertools recipes:
|
||||||
@ -341,7 +348,8 @@ def channels(request):
|
|||||||
"num_checks": num_checks,
|
"num_checks": num_checks,
|
||||||
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
|
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
|
||||||
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
|
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
|
||||||
"enable_discord": settings.DISCORD_CLIENT_ID is not None
|
"enable_discord": settings.DISCORD_CLIENT_ID is not None,
|
||||||
|
"enable_telegram": settings.TELEGRAM_TOKEN is not None
|
||||||
}
|
}
|
||||||
return render(request, "front/channels.html", ctx)
|
return render(request, "front/channels.html", ctx)
|
||||||
|
|
||||||
@ -747,6 +755,60 @@ def add_victorops(request):
|
|||||||
return render(request, "integrations/add_victorops.html", ctx)
|
return render(request, "integrations/add_victorops.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@require_POST
|
||||||
|
def telegram_bot(request):
|
||||||
|
try:
|
||||||
|
doc = json.loads(request.body.decode("utf-8"))
|
||||||
|
jsonschema.validate(doc, telegram_callback)
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
except jsonschema.ValidationError:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
if "/start" not in doc["message"]["text"]:
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
chat = doc["message"]["chat"]
|
||||||
|
name = max(chat.get("title", ""), chat.get("username", ""))
|
||||||
|
|
||||||
|
invite = render_to_string("integrations/telegram_invite.html", {
|
||||||
|
"qs": signing.dumps((chat["id"], chat["type"], name))
|
||||||
|
})
|
||||||
|
|
||||||
|
Telegram.send(chat["id"], invite)
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_telegram(request):
|
||||||
|
chat_id, chat_type, chat_name = None, None, None
|
||||||
|
qs = request.META["QUERY_STRING"]
|
||||||
|
if qs:
|
||||||
|
chat_id, chat_type, chat_name = signing.loads(qs, max_age=600)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
channel = Channel(user=request.team.user, kind="telegram")
|
||||||
|
channel.value = json.dumps({
|
||||||
|
"id": chat_id,
|
||||||
|
"type": chat_type,
|
||||||
|
"name": chat_name
|
||||||
|
})
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
channel.assign_all_checks()
|
||||||
|
messages.success(request, "The Telegram integration has been added!")
|
||||||
|
return redirect("hc-channels")
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"chat_type": chat_type,
|
||||||
|
"chat_name": chat_name
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, "integrations/add_telegram.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
def privacy(request):
|
def privacy(request):
|
||||||
return render(request, "front/privacy.html", {})
|
return render(request, "front/privacy.html", {})
|
||||||
|
|
||||||
|
@ -153,6 +153,9 @@ PUSHOVER_EMERGENCY_EXPIRATION = 86400
|
|||||||
PUSHBULLET_CLIENT_ID = None
|
PUSHBULLET_CLIENT_ID = None
|
||||||
PUSHBULLET_CLIENT_SECRET = None
|
PUSHBULLET_CLIENT_SECRET = None
|
||||||
|
|
||||||
|
# Telegram integration -- override in local_settings.py
|
||||||
|
TELEGRAM_TOKEN = None
|
||||||
|
|
||||||
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
|
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
|
||||||
from .local_settings import *
|
from .local_settings import *
|
||||||
else:
|
else:
|
||||||
|
BIN
static/img/integrations/setup_telegram_1.png
Normal file
BIN
static/img/integrations/setup_telegram_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
static/img/integrations/setup_telegram_2.png
Normal file
BIN
static/img/integrations/setup_telegram_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 135 KiB |
BIN
static/img/integrations/setup_telegram_3.png
Normal file
BIN
static/img/integrations/setup_telegram_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
static/img/integrations/telegram.png
Normal file
BIN
static/img/integrations/telegram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 3.9 KiB |
@ -82,6 +82,13 @@
|
|||||||
{{ ch.value }}
|
{{ ch.value }}
|
||||||
{% elif ch.kind == "discord" %}
|
{% elif ch.kind == "discord" %}
|
||||||
{{ ch.discord_webhook_id }}
|
{{ ch.discord_webhook_id }}
|
||||||
|
{% elif ch.kind == "telegram" %}
|
||||||
|
{% if ch.telegram_type == "group" %}
|
||||||
|
<span class="preposition">chat</span>
|
||||||
|
{% elif ch.telegram_type == "private" %}
|
||||||
|
<span class="preposition">user</span>
|
||||||
|
{% endif %}
|
||||||
|
{{ ch.telegram_name }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ ch.value }}
|
{{ ch.value }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -223,6 +230,17 @@
|
|||||||
<a href="{% url 'hc-add-discord' %}" class="btn btn-primary">Add Integration</a>
|
<a href="{% url 'hc-add-discord' %}" class="btn btn-primary">Add Integration</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if enable_telegram %}
|
||||||
|
<li>
|
||||||
|
<img src="{% static 'img/integrations/telegram.png' %}"
|
||||||
|
class="icon" alt="Telegram icon" />
|
||||||
|
|
||||||
|
<h2>Telegram</h2>
|
||||||
|
<p>A messaging app with a focus on speed and security.</p>
|
||||||
|
|
||||||
|
<a href="{% url 'hc-add-telegram' %}" class="btn btn-primary">Add Integration</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="link-to-github">
|
<li class="link-to-github">
|
||||||
<img src="{% static 'img/integrations/missing.png' %}"
|
<img src="{% static 'img/integrations/missing.png' %}"
|
||||||
class="icon" alt="Suggest New Integration" />
|
class="icon" alt="Suggest New Integration" />
|
||||||
|
104
templates/integrations/add_telegram.html
Normal file
104
templates/integrations/add_telegram.html
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load compress humanize staticfiles hc_extras %}
|
||||||
|
|
||||||
|
{% block title %}Notification Channels - {% site_name %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>Telegram</h1>
|
||||||
|
|
||||||
|
{% if chat_id %}
|
||||||
|
<div class="jumbotron">
|
||||||
|
<p>
|
||||||
|
When a check goes <strong>up</strong> or <strong>down</strong>,
|
||||||
|
healthchecks.io will send notifications to
|
||||||
|
{% if chat_type == "private" %}
|
||||||
|
a Telegram user
|
||||||
|
{% else %}
|
||||||
|
a Telegram chat
|
||||||
|
{% endif %}
|
||||||
|
named <strong>{{ chat_name }}</strong>. Sound good?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post" class="text-center">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-default">
|
||||||
|
<img class="ai-icon" src="{% static 'img/integrations/telegram.png' %}" alt="Discord" />
|
||||||
|
Yes, connect Telegram
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>If your team uses <a href="https://telegram.org/">Telegram</a>,
|
||||||
|
you can set up {% site_name %} to post status updates directly to an
|
||||||
|
appropriate Telegram chat or user.</p>
|
||||||
|
|
||||||
|
<h2>Setup Guide</h2>
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no">1</span>
|
||||||
|
<p>
|
||||||
|
From your Telegram client, invite
|
||||||
|
<strong>HealthchecksBot</strong> to a group. It will get added
|
||||||
|
as a member with no access to group messages.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Alternatively, if you want notifications sent to yourself
|
||||||
|
directly, start a conversation with
|
||||||
|
<strong>HealthchecksBot</strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_telegram_1.png' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no">2</span>
|
||||||
|
<p>Type <strong><code>/start</code></strong> command.
|
||||||
|
If there are multiple bots in the group, type
|
||||||
|
<strong><code>/start@HealthchecksBot</code></strong> instead.
|
||||||
|
</p>
|
||||||
|
<p>The bot will respond with a confirmation link.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_telegram_2.png' %}">
|
||||||
|
</div> </div>
|
||||||
|
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no">3</span>
|
||||||
|
<p>Click or tap on the confirmation link, and
|
||||||
|
{% site_name %} will open in a browser window asking you to
|
||||||
|
confirm the new integration.</p>
|
||||||
|
<p>Confirm the integration, and it's done!</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_telegram_3.png' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% compress js %}
|
||||||
|
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
|
{% endcompress %}
|
||||||
|
{% endblock %}
|
5
templates/integrations/telegram_invite.html
Normal file
5
templates/integrations/telegram_invite.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% load hc_extras %}
|
||||||
|
|
||||||
|
Please open this link to complete the {% site_name %} integration:
|
||||||
|
|
||||||
|
{% site_root %}{% url "hc-add-telegram" %}?{{ qs }}
|
6
templates/integrations/telegram_message.html
Normal file
6
templates/integrations/telegram_message.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% load humanize %}
|
||||||
|
{% if check.status == "down" %}
|
||||||
|
The check "{{ check.name_then_code }}" is <b>DOWN</b>. Last ping was {{ check.last_ping|naturaltime }}.
|
||||||
|
{% else %}
|
||||||
|
The check "{{ check.name_then_code }}" received a ping and is now <b>UP</b>.
|
||||||
|
{% endif %}
|
Loading…
x
Reference in New Issue
Block a user