Telegram API

Neben Teams oder Skype for Business in Firmen und dem aktuell (leider) unvermeidlichen WhatsApp im privaten Umfeld gibt es durchaus andere Messenger, die sich für das ein oder andere private Projekt eignen. Diese Seite beschreibt die Kommunikation per Skript als Bot mit meinem Telegram-Konto.

Ich habe erst etwas gezögert, einen Telegram-Bot zu nutzen, denn das Business-Modell und damit auch die Vertraulichkeit des Backends ist mir nicht klar. Der Inhaber ist wohl russischer Abstammung, der früher mit einem russischen Facebook-Clon zu Geld gekommen ist, dann aber über verschiedene Orte nun wohl in Dubai wohnt und das Telegram Backend aus seinem Privatvermögen betreibt. Die Firma hat wohl keine Post-Adresse oder Ansprechpartner für Ermittlungsbehörden in Deutschland und wird gerne als Plattform von Menschen genutzt, deren Ansichten und Taten ich nicht teile.

Ich muss mich aber an diesen Kommunikationen nicht beteiligen und kann Telegram einfach als "Transportdienst" für Daten und Informationen nutzen, und ggfls. selbst verschlüsseln. Ansonsten könnte ich immer noch zu einem eigenen Chat-Service, z.B. Matrix.org oder andere Messenger wie Signal ().

Anlass

Es gibt immer mal wieder den Bedarf, auch unterwegs über Vorgänge im Haus informiert zu werden, z.B. Ladevorgänge an der Tesla Wallbox Gen 3 u.a. Ich möchte dazu aber nicht meine IoT-Server aus dem Internet erreichbar machen und mein Smartphone soll auch gar nicht permanent "pollen". Ich möchte aber auch nicht jedes Mal eine Mail versenden. Im privaten Umfeld ist (leider) WhatsApp der Quasi-Standard, auch wenn es starke Bedenken bezüglich des Datenschutz gibt. Aber WhatsApp hat keine einfache API, mit der ich mir eine Chat-Message senden könnte.

Es gibt aber andere Messenger neben WhatsApp wie z.B.: Signal, Telegram oder eigene Server wie "Matrix.org". Da bietet es sich doch an, einfach diese Schnittstelle zu nutzen, wenn Sie denn angeboten wird. Mit Telegram ist das recht schnell und einfach möglich.

Bot einrichten

Zuerst starte ich Telegram auf meinem Smartphone und beginne einen Chat mit der Gegenstelle "BotFather". Das ist selbst ein Bot, der mich durch die Verwaltung meiner Bots per Chat führt. Die Schritte werden durch den Botfather geführt:

Schritt Beschreibung

Einrichtung

  1. Chat mit BotFather beginnen
    Klar, dass ich dazu natürlich Telegram selbst nutzen muss. Das geht auch per Browser unter https://t.me/Botfather mit dem Telegram Desktop Client.
  2. Befehlt "/Newbot" absenden
    Damit starte ich den Prozess zur Anlage eines Bot.
  3. Namen für den Bot eingeben
  4. Benutzername für die spätere Anmeldung durch den Bot festlegen
  5. API-Key notieren

Schon habe ich meinen ersten Bot bereitgestellt.

Wenn ich den API-Key vergesse, kan ich den immer wieder über Telegram abfragen.

  1. Botfather anchatten
  2. "/mybots" eingeben
  3. gewünschten bereits angewählten Bot auswählen
  4. API Token auswählen
  5. Token abschreiben

Absicherung

Achtung: Der Bot ist fast wie ein normaler Benutzer und kann per Default von allen Telegram-Benutzern angesprochen und in Gruppen aufgenommen werden.

Für eigene Tests sollten Sie daher mit den folgenden Optionen den Privacy-Status einschränken.

/setprivacy
/setjoingroups 

Der einfachste Weg ist über den Assistenten

  1. Eingeben von "/mybots"
  2. Auswahl des Bots
  3. Auswahl der Einstellung "Bot Settings

Hier kann ich dann "Allow Groups" und "Group Privacy" setzen.

Test des API-Keys

Damit Anwender (oder ich) mit meinem Bot interagieren können, muss natürlich etwas Code geschrieben werden. Zuerst sollte ich mal testen, dass mein Bot und der API-Key stimmen.

param (
   [string]$apikey="xxxxxxx:xxxxxxx"
)

Write-Host"GetMe"
$getme= Invoke-Restmethod  -Uri "https://api.telegram.org/bot$($apikey)/getMe"

 $getme | Convertto-Json
{
  "ok": true,
  "result": {
    "id": xxxxxxx,
    "is_bot": true,
    "first_name": "Msxfaq",
    "username": "Msxfaq_bot",
    "can_join_groups": false,
    "can_read_all_group_messages": false,
    "supports_inline_queries": false
  }
}

API-Key und Zugriff funktioniert schon mal. Wenn etwas nicht stimmt dann liefert der HTTPS-Aufruf ein 200 OK aber die JSON-Payload enthält dann den Fehler:

{"ok":false,"error_code":404,"description":"Not Found"}

Daher sollten wir immer erst den Wert von "ok" abfragen, z.B. mit.

if ($getme.ok -ne "true") {
   write-error "Fehler beim REST Aufruf"
}

Die Fehlerbehandlung sollte natürlich etwas ausgefeilter sein

Bot bekommt Nachricht

Die einfachste Übung ist ein "Fire and forget", d.h. ich sende einfach eine Nachricht an mich als Telegram-User. Aber das geht nicht so einfach, denn ich kann ja nicht alle Empfänger der Welt einfach mit Spam-Nachrichten beaufschlagen. Zuerst muss der mögliche Empfänger eine Verbindung mit dem Bot herstellen und so quasi die Tür öffnen. Ich habe daher in Telegram den Bot gesucht und angeschrieben

Im Telegram Client werden hier sehr schnell zwei "Haken" vergeben, selbst wenn mein Bot noch gar nichts abgerufen hat. Diese eingehende Verbindung kann der BOT wie folgt ermitteln:

PS C:\> $update = (Invoke-Restmethod  -Uri "https://api.telegram.org/bot$($apikey)/GetUpdates").result

PS C:\> $update

update_id message
--------- -------
289419211 @{message_id=4; from=; chat=; date=1639124715; text=Test1}
289419212 @{message_id=5; from=; chat=; date=1639124717; text=TEst2}
289419213 @{message_id=6; from=; chat=; date=1639124721; text=Test3}

Sie sehen hier aber auch, dass z.B. die Nachricht von vor zwei Tagen nicht mehr angezeigt wird.

Telegram hält Nachrichten maximal 24h.  Ein Bot sollte also nicht allzu lange inaktiv sein.

Dass der Aufruf funktioniert hat, sehen Sie am Status "OK = True" ist". Die JSON-Datei sieht wie folgt aus:

PS C:\> $update[1] | ConvertTo-Json
{
  "update_id": 289419211,
  "message": {
    "message_id": 4,
    "from": {
      "id": 730975735,
      "is_bot": false,
      "first_name": "Frank",
      "last_name": "Carius",
      "language_code": "de"
    },
    "chat": {
      "id": 730975735,
      "first_name": "Frank",
      "last_name": "Carius",
      "type": "private"
    },
    "date": 1639124715,
    "text": "Test1"
  }
}

Zu den Inhalten

  • Über die API bekomme ich die Meldung immer wieder, bis sie "veralten"
  • Eine Meldung kann mehrere Messages von unterschiedlichen Absendern enthalten
  • Die Update_ID ist positiv und wird inkrementiert
    Nach ca. 1 Woche ohne Meldungen fängt die Zählung neu an.
  • Das "Date"-Feld enthält die Sekunden seit 1.1.1970
    Per PowerShell kann ich das wie folgt konvertieren
(([System.DateTimeOffset]::FromUnixTimeSeconds($date)).DateTime).ToString()
  • Update_ID als Filter
    Wenn ich nur "neue" Meldungen bekommen möchte, dann kann ich mir die höchste Update_ID merken und mit dem nächsten Request als Parameter mitsenden.
PS C:\> (Invoke-Restmethod  -Uri "https://api.telegram.org/bot$($apikey)/GetUpdates?offset=289419212").result

update_id message
--------- -------
289419212 @{message_id=5; from=; chat=; date=1639124717; text=TEst2}
289419213 @{message_id=6; from=; chat=; date=1639124721; text=Test3}
  • Long Running Request statt Polling mit Parameter "timeout"
    Klassisch würde ich nun z.B. jede Sekunde per HTTPS einen Anfrage stellen. Das kostet aber Bandbreite und Rechenleistung. Interessant ist hier der Parameter "timeout", der in Sekunden angegeben wird. Über den kann ich einen HTTP-Request absetzen und Telegram antwortet mir erst, wenn eine neue Meldung ankommt oder der Timeout abgelaufen ist.
    Eine andere Option wäre noch die Bereitstellung eines Webhook, den Telegram bei einer neuen Message aufruft.

Nachricht beantworten

Der Empfang einer Nachricht ist natürlich nur die halbe Miete, denn ich möchte dem Absender ja auch eine Rückmeldung geben oder ihm sogar selbst eine neue Nachricht senden. Über die erste eingehende Kommunikation habe ich aber schon einmal eine Chat-ID und über die kann ich eine Meldung übermitteln. Auch das geht einfach per HTTPS unter Nutzung des APIKeys. Eine einfache Textnachricht kann ich direkt per URL-Parameter übermitteln.

Invoke-Restmethod `
   -Uri "https://api.telegram.org/bot$($apikey)/sendMessage?chat_id=$($chatid)&text="$($antwort)"

Eine Antwort ist wieder ein Einzeiler.

$sendmessageresult= Invoke-Restmethod -Uri "https://api.telegram.org/bot$($apikey)/sendMessage?chat_id=730975735&text=""angekommen"""

Auch hier liefert Telegram eine JSON-Struktur als Antwort.

PS C:\> $sendmessageresult | ConvertTo-Json
{
  "ok": true,
  "result": {
    "message_id": 13,
    "from": {
      "id": 2064795591,
      "is_bot": true,
      "first_name": "Msxfaq",
      "username": "Msxfaq_bot"
    },
    "chat": {
      "id": 730975735,
      "first_name": "Frank",
      "last_name": "Carius",
      "type": "private"
    },
    "date": 1639134680,
    "text": "\"angekommen\""
  }
}

Hinweis:
Die Chat_ID ist mit dem Bot verbunden. Wenn Sie also mit einem eigenen BOT und eigenem Key eine Nachricht an "730975735" senden, kommt sie nicht an. Es ist also nicht eine "geheime" ID meines Telegram Users.

Natürlich geht auch noch viel mehr, wenn ich strukturierte JSON-Daten per FORM POST übermitteln. Hier erst mal für eine Message

Invoke-Restmethod `
   -Uri "https://api.telegram.org/bot$($apikey)/sendMessage" `
   -Body @{chat_id=730975735; text="Angekommen2"}

Wenn ich umfangreichere Informationen, z.B. Bilder u.a. senden möchte, dann komme ich an die Grenzen des HTTP-GET mit URL-Parametern. Dann ist ein POST mit den Daten besser. Hier sende ich exemplarisch ein Bild.

$result= Invoke-RestMethod `
   -URI "https://api.telegram.org/bot$($apikey)/sendphoto" `
   -Method POST `
   -Form @{
        chat_id              = 730975735
        photo                = (Get-Item "C:\temp\fctest1a.jpg")
        caption              = "Musterbild"
    }

 

Die Antwort ist dann natürlich auch etwas umfangreicher und es hat den Eindruck, dass Telegram das Bild in zwei weitere Auflösungen umrechnet. 506x600 war das originale Bild

PS C:\> $result | ConvertTo-Json -Depth 4
{
  "ok": true,
  "result": {
    "message_id": 11,
    "from": {
      "id": 2064795591,
      "is_bot": true,
      "first_name": "Msxfaq",
      "username": "Msxfaq_bot"
    },
    "chat": {
      "id": 730975735,
      "first_name": "Frank",
      "last_name": "Carius",
      "type": "private"
    },
    "date": 1639133086,
    "photo": [
      {
        "file_id": "AgACAgIAAxkDAAMLYbMvnvut5eA-XoZhCW8kodDSHmsAAvG1MRuCdphJ6YGnh94wPXMBAAMCAANzAAMjBA",
        "file_unique_id": "AQAD8bUxG4J2mEl4",
        "file_size": 1627,
        "width": 76,
        "height": 90
      },
      {
        "file_id": "AgACAgIAAxkDAAMLYbMvnvut5eA-XoZhCW8kodDSHmsAAvG1MRuCdphJ6YGnh94wPXMBAAMCAANtAAMjBA",
        "file_unique_id": "AQAD8bUxG4J2mEly",
        "file_size": 11399,
        "width": 270,
        "height": 320
      },
      {
        "file_id": "AgACAgIAAxkDAAMLYbMvnvut5eA-XoZhCW8kodDSHmsAAvG1MRuCdphJ6YGnh94wPXMBAAMCAAN4AAMjBA",
        "file_unique_id": "AQAD8bUxG4J2mEl9",
        "file_size": 13733,
        "width": 506,
        "height": 600
      }
    ],
    "caption": "Musterbild"
  }
}

Analog gibt es ganz viele weitere Optionen zum Senden von Dokumenten, Locations, URLs, Sticker, Umfragen, Kontakte u.a.

Rückmeldung und Fehler

Wenn der REST-Aufruf von Telegram korrekt verarbeitet werden konnte, dann kommt als Payload eine JSON-Struktur zusammen mit einem HTTP-200-Code zurück. Die beim Aufruf angegeben Variable enthält diese Information. Hier am Beispiel von $r2

 

Der absichtlich fehlerhafte Aufruf zeigt, dass "$r3" leer ist. Dafür ist "$error" gefüllt und an die JSON-Antwort komme ich über den Abruf der Errordetails. Interessant ist dabei aber auch, dass der Statuscode "leer" ist. Ein recht eleganter Weg ist die Angabe einer ErrorVariable beim Aufruf, die nur im Fehlerfall ungleich "$null" ist.

PS C:\> $r3= Invoke-Restmethod -Uri "https://api.telegram.org/bot$($apikey)/sendMessage?chat_id=7305735&text=""angekommen""" -ErrorVariable r3error
Invoke-RestMethod: {"ok":false,"error_code":400,"description":"Bad Request: chat not found"}

PS C:\> $r3error.Message
{"ok":false,"error_code":400,"description":"Bad Request: chat not found"}

Hier habe ich mal eine Liste der Fehler, die ich bei meinen Tests alle so bekommen hatte.

Invoke-RestMethod: {"ok":false,"error_code":400,"description":"Bad Request: chat_id is empty"}
Invoke-RestMethod: Response status code does not indicate success: 400 (Bad Request)
Invoke-RestMethod: {"ok":false,"error_code":400,"description":"Bad Request: there is no document in the request"}
Invoke-RestMethod: {"ok":false,"error_code":400,"description":"Bad Request: message text is empty"}
Invoke-RestMethod: {"ok":false,"error_code":400,"description":"Bad Request: chat not found"}
Invoke-RestMethod: {"ok":false,"error_code":404,"description":"Not Found"} 

Die Liste ist sicher nicht vollständig aber zeigt gut, dass Telegram schon hilfreiche Meldungen liefert.

Chat_ID, Kanal und Gruppen

Ich habe verstanden, dass ein BOT keine direkte Verbindung zu einem Telegram-Benutzer aufbauen kann. Ein Bot muss sich von einer eingehenden Kommunikation die Chat_ID holen und darauf dann reagieren.

Ich habe noch nicht rausgefunden, wie lange so eine Chat_ID gültig ist. Sie ist ja direkt mit der Gegenstelle und dem Bot verbunden, so dass Sie solange beständig sein könnte, solange es einen der beiden Teilnehmer gibt.

Ein normaler Bot reagiert in der Regel schnell auf Anfragen eines Anwenders. Aber es kann ja auch einen Grund geben, dass ein Bot seinerseits die Konversation beginnt, z.B. wenn etwas passiert ist. Das geht problemlos mit einer 1:1: Kommunikation aber hat auch den Nachteil, dass der Teilnehmer den Bot einmalig ansprechen muss und der Bot sich die Chat_ID irgendwo merkt. Als Bot-Entwickler müssen Sie sich also einen Speicherort überlegen.

Nur für einfache Entwicklungen ist es ein brauchbarer Weg, als Entwickler die Chat_ID per HTTP-Request auf "/GetUpdates" zu holen und statisch im Code zu hinterlegen. Nun gibt es aber in Telegram auch noch Gruppen und Kanäle, die jeder Teilnehmer anlegen kann.

Eine normale Gruppe kann bis zu 200.000 Teilnehmer haben. Kanäle können "öffentlich" oder "Privat" sein und auch die Gruppen können zu "Supergruppen" umgewandelt werden. Darauf gehe ich hier nicht weiter ein.

Type

Kanal

Gruppe

Max Teilnehmer

unlimited

200.000

Aufindbar

Öffentlich oder Privat

Öffentlich oder Privat

früherer Chatverlauf für neue Mitgieder

Nein

Sichtbar/versteckt

Berechtigungen

Nur Administrator

Pro Gruppe einstellbar

Wer kann lesen?

Mitglieder

Mitglieder

Administratoren

max. 50

max. 50

Anzahl Bots

Bot müssen Admin sein

max. 20

Einsatzbereich

 Ein Kanal ist primär ein "Mitteilungsmedium", in dem die Abonnementen nur "lesen" und die Administratoren die einzigen Personen sind, die dort etwas "posten" könne

üIn einer Gruppe können alle Mitglieder miteinander chatten.

Bot Einsatz

Ein Bot kann die Verwaltung eines Kanals mit so vielen Teilnehmern automatisieren und auch Nachrichten automatisch absetzen.

Für ein Team ist eine Gruppe natürlich interessant und ein Bot kann dort z.B. Meldungen addieren. Die Verwaltung der "Empfänger" erfolgt alleine in der Grupe

Wenn eine Firma daher z.B. eine "Meldungs-Bot" betreiben will oder eine Heimautomatisierung alle Familienmitglieder erreichen soll, dann wäre eine private Gruppe mit dem BOT durchaus sinnvoll. Jeder bekommt die Information und die Benutzerverwaltung läuft komplett in Telegram.

Hinweis: Sie können beim Bot einstellen, ob er auch in Gruppen addiert werden darf.

Wenn ein Bot addiert wird, dann bekommt er folgende Update-Meldung

PS C:\> (Invoke-Restmethod -Uri "https://api.telegram.org/bot$($apikey)/Getupdates").result.my_chat_member

chat            : @{id=-554885677; title=MSXFAQGruppe; type=group;
                  all_members_are_administrators=True}
from            : @{id=730975736; is_bot=False; first_name=Frank; last_name=Carius; username=msxfaq;
                  language_code=de}
date            : 1639148402
old_chat_member : @{user=; status=left}
new_chat_member : @{user=; status=member}

PS C:\> (Invoke-Restmethod -Uri "https://api.telegram.org/bot$($apikey)/Getupdates").result.my_chat_member.chat.id
-554885676

Hinweis:
Als Bot-Entwickler können Sie einmal eine Gruppe anlegen, den Bot addieren und dann über genau diesen Weg die Chat_ID ermitteln und im Code fest hinterlegen. Solange Sie die Gruppe nicht löschen, funktioniert dies dauerhaft

Wenn ein Bot zu einem Kanal addiert wird, komm folgendes Updates beim BOT an

# Meldung, wenn Bot in einen Kanal addiert wird
PS C:\> (Invoke-Restmethod -Uri "https://api.telegram.org/bot$($apikey)/Getupdates").result.my_chat_member

chat            : @{id=-1001532267226; title=MSXKanal; type=channel}
from            : @{id=730975736; is_bot=False; first_name=Frank; last_name=Carius; username=msxfaq;
                  language_code=de}
date            : 1639146681
old_chat_member : @{user=; status=left}
new_chat_member : @{user=; status=administrator; can_be_edited=False; can_manage_chat=True;
                  can_change_info=True; can_post_messages=True; can_edit_messages=True;
                  can_delete_messages=True; can_invite_users=True; can_restrict_members=True;
                  can_promote_members=False; can_manage_voice_chats=True; is_anonymous=False}

Throttling

Wenn Sie nun glauben, sie können per BOT auch die bereits verbundenen Endpunkte mit Nachrichten überfluten, dann haben Sie sich getäuscht. Wer allzu viele Nachrichten in zu kurzer Zeit sendet, bekommt eine Fehlermeldung beim "sendMessage":

{
   "ok":false,
   "error_code":429,
   "description":"Too Many Requests: retry after 35",
   "parameters":{
      "retry_after":35
   }
}

Mein Skript wird hier für 35 Sekunden auf die Strafbank gesetzt. Bei der Suche nach einer entsprechenden Beschreibung habe ich unterschiedliche Hinweise gefunden. In der API-Beschreibung steht:

If you're sending bulk notifications to multiple users, the API will not allow more than 30 messages per second or so. Consider spreading out notifications over large intervals of 8—12 hours for best results.
...your bot will not be able to send more than 20 messages per minute to the same group.
Quelle: My bot is hitting limits, how do I avoid this? https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this

Eine Message an eine Gruppe unterliegt deutlich engeren Grenzen als eine Nachricht direkt an eine Person

Libraries

Die einfache Programmierung von Telegram per Bot macht natürlich ganz viele Szenarien möglich. Einige Produkte unterstützen eine Telegram-Anbindung schon von Hause aus. Andere lassen sich per Skript oder über entsprechende Libraries erweitern. Genau genommen hätte ich mir auch das Leben einfacher machen können und einfach eine fertige Library nutzen können. Mir war aber auch das Verständnis wichtig. Insbesondere wie ein Bot mit dem Anwender kommuniziert.

Soweit ich gesehen habe, nutzen alle APIs und Libraries immer die Chat_ID und antworten auf eingehende Nachrichten. Eine Lösung muss also darauf warten, dass ein Telegram Benutzer einen Chat zum Bot startet oder vorab eine Gruppe oder Kanal angelegt und der Bot addiert wurde.

Entsprechend erwarten die Libraries quasi alle eine Chat_ID zum Versand von Nachrichten

Platform Beschreibung und LInks

HTTP

Das ist keine Library im eigentlichen Sinne aber die REST-Aufrufe sind so einfach, dass ich sie hier mit verlinke.

PowerShell

PoshGram - a PowerShell module for sending Telegram messages
https://www.youtube.com/watch?v=OfyRVl7YThw

Arduino/ESP

Python

Python Telegram Bot
https://python-telegram-bot.org/
https://GitHub.com/python-telegram-bot/python-telegram-bot

Home Automation

Eine Meldung per Telegram ist ein interessanter Weg für die Heimautomatisierung. Ich muss keinen eigenen Server im Internet erreichbar machen und brauche keine weitere App auf einem Smartphone um informiert zu werden oder sogar reagieren zu können. Daher ist es nicht verwunderlich, dass die meisten Plattformen eine Anbindung haben.

C#/Net

Einschätzung

Eigentlich habe ich erst mal nach einem Weg gesucht, wie meine Tesla Wallbox Gen 3 mit melden kann, was sich verändert. Die Box kann ich per REST abfragen und habe sie auch in PRTG drin aber eine Mail senden ist altmodisch. Telegram habe ich eh schon genutzt und die API wollte ich einfach mal verstehen. Klar könnte ich per Teams Bot und Microsoft Graph auch eine Teams-Message an mein Konto senden aber das wäre ja nicht massentauglich. Geplant ist ja z.B. ein ESP8266 als "Wallbox<->Telegram" Gateway. Mit den Vorarbeiten von hier sollte das auch einfach möglich sein.

Aber mit dem Gesamtverständnis eröffnen sich natürlich auch noch ganz andere Optionen, Telegram mit eigenen Prozessen und Lösungen anzureichern. Die Arbeit mit dem Bot und die umfangreiche Funktionen der BOT-Nutzung z.B.: in Telegram selbst, sind zudem ein elegantes Beispiel die Arbeitsweise und möglichen Einsatzbereiche von Bots besser zu verstehen. Das bringt einem schon auf die ein oder andere Idee bei der Teams Entwicklung.

Wenn ich sicher wäre, dass das Backend keine "Mitleser" hat, könnte man per Nachricht an den Bot z.B.: auch IoT-Aktionen auslösen.

Weitere Links