Telegram instant messaging TelegramBot - Empfangen und Senden per FHEM

Begonnen von viegener, 20 Juni 2015, 18:59:41

Vorheriges Thema - Nächstes Thema

viegener

Da es mir das PostMe Modul von pah doch sehr angetan hat, habe ich ein wenig an TelegramBot erweitert, damit man gesendete inlineKeyboards auch nachträglich noch ändern kann. Damit ist soetwas wie ein "inline" Dialog möglich, ähnlich wie es der Botfather macht.

Dazu sind verschiedene Dinge hinzugekommen (erstmal in github):

- Neuer set-Befehl queryEditInline um Nachricht oder Keyboard zu ändern (dazu muss die ursprüngliche msgId angegeben werden)
- Kommandos aus Favoriten und an anderer Stelle werden jetzt erst durch die set-logik umgesetzt, damit man auch Readings in Favorites und auch bei QueryAnswerText einsetzen kann
Achtung: Letzteres ist an sich eine inkompatible Änderung, da nun eckige Klammern in den Befehle unter Umständern vor der Ausführung ersetzt werden - Wenn das Probleme macht gibt es auch eine andere Lösung aber ich finde die set-logik so schön übersichtlich

Ich habe auch mal eine myUtils-Routine eingebaut mit der man eine PostMe-Liste inline editieren kann. Die poste ich hier auch noch.
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

JoeALLb

Sehr toll, vielen Dank :D
Gerade im Vergleich mit Whatsapp ein sehr stabiles und tolles Modul, das nochdazu gut unterstützt wird!
FHEM-Server auf IntelAtom+Debian (8.1 Watt), KNX,
RasPi-2 Sonos-FHEM per FHEM2FHEM,RasPi-3 Versuchs-RasPi für WLAN-Tests
Gateways: DuoFern Stick, CUL866 PCA301, CUL HM, HMLan, JeeLink, LaCrosse,VCO2
Synology. Ardurino UNO für 1-Wire Tests, FB7270

viegener

#1082
Todo- oder Einkaufsliste als inline ChatBot-Dialog:

Wie sieht das aus (leider bin ich zu doof die Bilder hier inline anzuzeigen - also siehe unten)

Aufruf mit /todo - todolist_1

Neuen Eintrag erzeugen durch Click auf "add" - todolist_2

Text Für Eintrag eingeben und abschicken - Neuer Eintrag wird in der liste angezeigt - todolist_3

Clicken auf einen Eintrag - Erzeugt Nachfrage ob gelöscht werden soll - todolist_4

Nach dem Bestätigen erscheint die kürzere Liste - todolist_5

Textliste wird angezeigt nach Click auf OK - todolist_6




Anleitung:

Anmerkung: Ich habe versucht das weitgehend zu verallgemeinern, vielleicht probiert es jemand mal aus - Feedback ist erwünscht!

Folgende Devices sollte es geben:

Ein Telegrambot - sagen wir mal LISTBOT

Ein PostMe Device - sagen wir mal LISTME mit einer Liste im Code ist die Annahme mal, dass es nur eine gibt

Folgende Settings sind wichtig im LISTBOT


attr LISTBOT favorites /todo={(JVTODO_listmanager("[LISTBOT:msgPeerId]","list",undef))};
attr LISTBOT queryAnswerText {(JVTODO_listmanager("[LISTBOT:queryPeerId]","[LISTBOT:queryData]",undef))}


und natürlich die Einrichtung mit polling, defaultPeer und restrictedPeer etc, damit Kommandos ausgeführt werden können.

Ein Dummy device jvtodolistmsgdummy

Zwei Notify devices

define jvtodolistmsgnotify notify LISTBOT:sentMsgId.* IF ( [LISTBOT:&sentMsgText] eq [jvtodolistmsgdummy] ) ( {JVTODO_listmanager(InternalVal("LISTBOT","sentMsgPeerId","0"),"msgid",$EVTPART1)}, set jvtodolistmsgdummy $EVTPART1 )


und


define jvtodolistreplynotify notify LISTBOT:msgReplyMsgId.* IF ( $EVTPART1 != 0 ) ( {JVTODO_listmanager(ReadingsVal("LISTBOT","msgPeerId","0"),"reply",$EVTPART1)})


Diese sorgen dafür, dass msgIds und replyMsgIds nach erfolgreichem Senden / Empfangen zugeordnet werden können




Die eigentlich Verarbeitung findet aber in einer zentralen myUtils-Routine statt. Also den folgenden Code in die myUtils einfügen und nach dem anpassen der globalen Variablenwerte im oberen Teil neustarten (bzw. reload)


##############################################
#### Handle Todolist
##############################################

my $JVTODO_bot = "LISTBOT";
my $JVTODO_list = "todolist";
my $JVTODO_postme = "LISTME";
my $JVTODO_msgdummy = "jvtodolistmsgdummy";
my %JVTODO_inlinechats = ();


##############################################
# list or specific entry number
#
sub JVTODO_getList(;$)
{
  my ($entry) = @_;


  # get the list of entries in the list - needs to be changed if the list is not number 1
  my $listCont = ReadingsVal($JVTODO_postme,"postme01Cont","");


  my @entries = split( /,/, $listCont );
 
  if ( defined( $entry ) ) {
    return undef if ( ( $entry < 0 ) || ( $entry > scalar(@entries) ) );
   
    return $entries[$entry];
  }

  return @entries;
}

##############################################
# peer, cmd, arg
#
sub JVTODO_listmanager($$$)
{
  my ($peer, $cmd, $arg) = @_;
 
  Log 3,"JVTODO_listmanager: peer :$peer:   cmd :$cmd:  ".(defined($arg)?"arg :$arg:":"");
 
  ##################### 
  if ( $cmd eq "msgid" ) {
    my $key = $peer."_store";

    if ( defined( $JVTODO_inlinechats{$key}) ) {
      # reply received
      my $replykey = $peer."_reply";
      $JVTODO_inlinechats{$replykey} = $arg;

      $JVTODO_inlinechats{$peer} = $JVTODO_inlinechats{$key};
      delete( $JVTODO_inlinechats{$key} );
    } else {
      $JVTODO_inlinechats{$peer} = $arg;
    }
   
  ##################### 
  } elsif ( $cmd eq "reply" ) {
    # reply with msgId != 0 received
    # check for expected reply and then remove expectations and handle add
   
    my $replykey = $peer."_reply";
    if ( ( defined( $JVTODO_inlinechats{$replykey}) ) && ($JVTODO_inlinechats{$replykey} eq $arg) ) {
      delete( $JVTODO_inlinechats{$replykey} );
     
      my $msgText = ReadingsVal( $JVTODO_bot, "msgText", "" );
     
      JVTODO_listmanager( $peer, "list_add", $msgText );
    }
   
   
  ##################### 
  } elsif ( $cmd eq "list_ok" ) {
    # ok means clean buttons and show only list
   
    # start the inline
    my $inline = "(start:list) ";
    my $liste = "";
   
    # get the list of entries in the list
    my @list = JVTODO_getList();
    foreach my $entry ( @list )  {
      $liste .= "\n".$entry;
    }
   
    my $textmsg = "Liste ".$JVTODO_list;
    $textmsg .= " ist leer " if ( scalar(@list) == 0 );
    $textmsg .= " : $arg " if ( defined($arg) );
    $textmsg .= $liste;
   
    my $msgId = $JVTODO_inlinechats{$peer};
    if ( defined($msgId ) ) {
      # show new list
      fhem( "set $JVTODO_bot queryEditInline $msgId $inline $textmsg" );
    } else {
      Log 1,"JVTODO_listmanager: ERROR no msgId known for peer :$peer:   cmd :$cmd:  ".(defined($arg)?"arg :$arg:":"");
    }
   
  ##################### 
  } elsif ( $cmd eq "list_empty" ) {
    # empty means clean buttons and show only list
   
    my $msgId = $JVTODO_inlinechats{$peer};
    if ( defined($msgId ) ) {
      # show new list
      fhem( "set $JVTODO_bot queryEditInline $msgId DONE" );
    } else {
      Log 1,"JVTODO_listmanager: ERROR no msgId known for peer :$peer:   cmd :$cmd:  ".(defined($arg)?"arg :$arg:":"");
    }
   
  ##################### 
  } elsif ( ( $cmd eq "list" ) || ( $cmd eq "list_edit" ) ) {
    # list means create button table with list entries
   
    # start the inline
    my $inline = "(ok:list_ok|add:list_askadd) ";
   
    # get the list of entries in the list
    my @list = JVTODO_getList();
    my $nre = 0;
    foreach my $entry (  @list ) {
      $inline .= "(".$entry.":list_rem-".$nre.") ";
      $nre++;
    }
   
    my $textmsg = "Liste ".$JVTODO_list;
    $textmsg .= " ist leer " if ( scalar(@list) == 0 );
    $textmsg .= " : $arg " if ( defined($arg) );
   
    if ( $cmd eq "list" ) {
      # store text msg to recognize msg id in dummy
      fhem( "set $JVTODO_msgdummy $textmsg" );

      # send msg and keys
      fhem( "set $JVTODO_bot queryInline $inline $textmsg" );
    } else {
      my $msgId = $JVTODO_inlinechats{$peer};
      if ( defined($msgId ) ) {
        # show new list
      fhem( "set $JVTODO_bot queryEditInline $msgId $inline $textmsg" );
      } else {
        Log 1,"JVTODO_listmanager: ERROR no msgId known for peer :$peer:   cmd :$cmd:  ".(defined($arg)?"arg :$arg:":"");
      }
    }
   
  ##################### 
  } elsif ( $cmd eq "list_add" ) {
    # means add entry to list
    fhem( "set $JVTODO_postme add $JVTODO_list ".$arg );

    my $msgId = $JVTODO_inlinechats{$peer};
   
    if ( defined($msgId ) ) {
      # empty old list now and start a new list message
      JVTODO_listmanager( $peer, "list_empty", undef );
      # show new list -> call recursively
      JVTODO_listmanager( $peer, "list", " Eintrag hinzugefügt" );
    } else {
      Log 1,"JVTODO_listmanager: ERROR no msgId known for peer :$peer:   cmd :$cmd:  ".(defined($arg)?"arg :$arg:":"");
    }
   
  ##################### 
  } elsif ( $cmd =~ /^list_remyes-(\d+)$/ ) {
    # means remove a numbered entry from list - now it is confirmed
    my $no = $1;
   
    my @list = JVTODO_getList();
    if ( ( $no >= 0 ) && ( $no < scalar(@list) ) ) {
   
      fhem( "set $JVTODO_postme remove $JVTODO_list ".$list[$no] );

      # show updated list -> call recursively
      JVTODO_listmanager( $peer, "list_edit", " Eintrag gelöscht" );
   
    }
   
  ##################### 
  } elsif ( $cmd =~ /^list_rem-(\d+)$/ ) {
    # means remove a numbered entry from list - first ask
    my $no = $1;
   
    my @list = JVTODO_getList();
    if ( ( $no >= 0 ) && ( $no < scalar(@list) ) ) {
   
      # post new msg to ask for removal
      my $msgId = $JVTODO_inlinechats{$peer};
      if ( defined($msgId ) ) {
        # show ask for removal
        my $textmsg = "Liste ".$JVTODO_list."\nSoll der Eintrag $no (".$list[$no].") entfernt werden?";
        # show ask msg
        fhem( "set $JVTODO_bot queryEditInline $msgId (Ja:list_remyes-$no) (Nein:list_edit) $textmsg" );
      } else {
        Log 1,"JVTODO_listmanager: ERROR no msgId known for peer :$peer:   cmd :$cmd:  ".(defined($arg)?"arg :$arg:":"");
      }
   
    }
   
  ##################### 
  } elsif ( $cmd eq "list_askadd" ) {
    my $key = $peer."_store";
    $JVTODO_inlinechats{$key} = $JVTODO_inlinechats{$peer};
   
    my $textmsg = "Liste ".$JVTODO_list." Neuen Eintrag eingeben:";
   
    # store text msg to recognize msg id in dummy
    fhem( "set $JVTODO_msgdummy $textmsg" );

    # means ask for an entry to be added to the list
    fhem( "set $JVTODO_bot msgForceReply $textmsg" );

  }

  return 0;
 
}

Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

Prof. Dr. Peter Henning

Schöne Sache. Funktioniert im Schnelltest aber leider noch nicht, weil mein Update des Moduls nicht klappt. Teste ich später noch mal.

Die beiden notifies haben noch denselben Namen.

LG

pah


viegener

Zitat von: Prof. Dr. Peter Henning am 22 Dezember 2016, 16:06:25
Schöne Sache. Funktioniert im Schnelltest aber leider noch nicht, weil mein Update des Moduls nicht klappt. Teste ich später noch mal.

Die beiden notifies haben noch denselben Namen.

LG

pah

Danke, Namen sind angepasst
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

Prof. Dr. Peter Henning

#1085
Na, dann wollen wir mal sehen, ob man das aus PostMe nicht noch etwas besser unterstützen kann.

Die angehängte Version des PostMe-Moduls (2.0beta) bietet eine Routine an
ZitatPostMe_tgi(<devicename>,<listenname>)
mit der eine beliebige Liste in einem Format für Inline-Keyboards zurückgegeben wird. Und zwar in der Form

(Gegenstand 1:item00) (Gegenstand 2:item01) .... Listenname

Das kann man also unmittelbar als Inline-Keyboad versenden, und die Rückgabewerte item00 ... dann wieder in einer eigenen Routine auswerten.

Ich werde vielleicht heute im Laufe des vormittags dazu kommen, auch noch eine auf die Zusammenarbeit mit dem TelegramBot optimierte Hinzufüge- und Löschfunktion einbauen kann.

LG

pah

viegener

Zitat von: Prof. Dr. Peter Henning am 23 Dezember 2016, 08:06:58
Na, dann wollen wir mal sehen, ob man das aus PostMe nicht noch etwas besser unterstützen kann.

Die angehängte Version des PostMe-Moduls (2.0beta) bietet eine Routine anmit der eine beliebige Liste in einem Format für Inline-Keyboards zurückgegeben wird. Und zwar in der Form

(Gegenstand 1:item00) (Gegenstand 2:item01) .... Listenname

Das kann man also unmittelbar als Inline-Keyboad versenden, und die Rückgabewerte item00 ... dann wieder in einer eigenen Routine auswerten.

Ich werde vielleicht heute im Laufe des vormittags dazu kommen, auch noch eine auf die Zusammenarbeit mit dem TelegramBot optimierte Hinzufüge- und Löschfunktion einbauen kann.

LG

pah

Klingt gut, wäre es auch möglich eine ähnliche Funktion bereitzustellen, die nur die Listeneinträge zurückliefert (als array)?
Hintergrund: Wenn man jemals zwei Listen mischen will oder in anderer Form das Keyboard anpassen will, ist das mit dem generischen Array immer möglich.

Achso und: Ich muss zugeben, ich habe jetzt nicht im PostMe-Thread geschaut, aber es wäre schön, wenn man Kommata in einem Eintrag verwenden könnte, da bin ich sofort auf die Nase gefallen beim Testen. Vielleicht durch Ersetzung intern im PostMe-Modul?



Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

Prof. Dr. Peter Henning

Mit den Kommata wollte ich eigentlich nicht aufräume, die sollten tatsächlich die Trenner der Items bleiben. Wozu willst Du sie denn verwenden ?

Eine Array-Funktion kann ich einbauen. Derzeit bastele ich noch daran, dass man auch Listen einer entfernten FHEM-Instanz aufrufen kann.

LG

pah

viegener

Zitat von: Prof. Dr. Peter Henning am 23 Dezember 2016, 11:26:56
Mit den Kommata wollte ich eigentlich nicht aufräume, die sollten tatsächlich die Trenner der Items bleiben. Wozu willst Du sie denn verwenden ?

Eine Array-Funktion kann ich einbauen. Derzeit bastele ich noch daran, dass man auch Listen einer entfernten FHEM-Instanz aufrufen kann.

LG

pah

Ein Anwendungsfall bei mir ist eine Todo-Liste, die anders als eine Einkaufsliste auch kleine Texte enthält, also so etwas wie "Weihnachtskarten - Mutter, Vatter, Onkel abgeben". Da ich nicht ausschliessen kann, dass solche Einträge hier gemacht werden, dachte ich es wäre einfacher das programmatisch auszuschliessen und im Modul zu kapseln. Bisher sind ja die Befehle zum Hinzufügen eher für Einzeleinträge, also wird da eigentlich kein Komma als Trennzeichen benötigt.



Ich hatte geadcht an eine Art der Maskierung intern, ich (also z.B. ,, oder \, wird nicht als Trenner gewertet
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

JoeALLb

Zitat von: viegener am 23 Dezember 2016, 11:43:03
"Weihnachtskarten - Mutter, Vatter, Onkel abgeben".

Ah.. danke! Jetzt verstehe ich einen Fehler de rmir vorhin aufgefallen ist :D
FHEM-Server auf IntelAtom+Debian (8.1 Watt), KNX,
RasPi-2 Sonos-FHEM per FHEM2FHEM,RasPi-3 Versuchs-RasPi für WLAN-Tests
Gateways: DuoFern Stick, CUL866 PCA301, CUL HM, HMLan, JeeLink, LaCrosse,VCO2
Synology. Ardurino UNO für 1-Wire Tests, FB7270

Prof. Dr. Peter Henning

Aber darin siehst du das Problem: Mutter,Vatter, Onkel ist eine Liste - und der natürlichste Listentrenner ist und bleibt eben das Komma. Ist also eine Frage der Usability.

Gerade bei der "add"-Funktion kann man somit eine Vielzahl von Listen-"Items" in einem Befehl eintragen.

Ich schlage vor, statt dem eher un-ergonomischen \, entweder ein "+" oder ein ";" einzusetzen.

LG

pah

Vampy20

Hallo zusammen,

besteht schon die Möglichkeit das eigene Keyboard nach Benutzung nicht verschwinden zu lassen, Stichwort "ReplyKeyboardRemove".
Aktuell habe ich es damit gelöst erneut eine Message mit dem Keyboardinhalt zu senden. Aber zwischendurch schaltet sich immer das Default-Keyboard ein.

Besinnliche Grüße
FHEM 5.7 auf RPi 2 (Raspbian 4.4.13+, Perl v5.20.2), HM-CFG-USB, HM-SEC-RHS, HM-ES-PMSw1-Pl, HM-LC-Bl1PBU-FM, HM-SEC-SD-2, co2mini, Philips Hue, MySensors, DashButton

Vampy20

Sorry, ich hab die API Dokumentation falsch verstanden. Was ich brauche ist wahrscheinlich Zugriff auf dem Flag "one_time_keyboard" für ReplyKeyboardMarkup.
FHEM 5.7 auf RPi 2 (Raspbian 4.4.13+, Perl v5.20.2), HM-CFG-USB, HM-SEC-RHS, HM-ES-PMSw1-Pl, HM-LC-Bl1PBU-FM, HM-SEC-SD-2, co2mini, Philips Hue, MySensors, DashButton

viegener

Zitat von: Vampy20 am 23 Dezember 2016, 17:58:42
Sorry, ich hab die API Dokumentation falsch verstanden. Was ich brauche ist wahrscheinlich Zugriff auf dem Flag "one_time_keyboard" für ReplyKeyboardMarkup.

Ich nehme das mal auf die TODOliste für das Modul, wobei ich jetzt noch keine Idee habe, wie sich das sinnvoll in den set-Befehl einbinden lässt

Was ist denn der Anwendungsfall?
Kein Support über PM - Anfragen gerne im Forum - Damit auch andere profitieren und helfen können

Prof. Dr. Peter Henning

#1094
Anbei eine neue Version des PostMe-Moduls, mit zwei neuen subroutinen:

PostMe_tgi(<postmedevice>,<listenname>) - liefert eine Liste in Telegram-Syntax
PostMe_tgk(<postmedevice>,<listenname>)- liefert eine Liste einfach als Array (muss im Array-Kontext aufgerufen werden.

Kann man auch remote aufrufen, z.B. mit einem Shell-Skript

#!/bin/bash
postme=$1
liste=$2
FHEMIP=192.168.0.XXX

echo "{PostMe_tgi('$postme','$liste')}" | socat -t50 - TCP:$FHEMIP:7072


Da meine Frau die bereits im WIki dokumentierte Fassung der Listensteuerung mit Telegram bevorzugt (also nicht-klickbare Items ...), wird meine Auswertungsroutine etwas komplizierter. Für mich selbst habe ich das inzwischen so realisiert, wie unten auf dem Bild dargestellt:
Klicken auf den Item löscht diesen, fürs Hinzufügen gibt es einen Extrabutton.

LG

pah