diff --git a/fhem/contrib/DS_Starter/50_SSChatBot.pm b/fhem/contrib/DS_Starter/50_SSChatBot.pm
new file mode 100644
index 000000000..1efcdde6a
--- /dev/null
+++ b/fhem/contrib/DS_Starter/50_SSChatBot.pm
@@ -0,0 +1,1379 @@
+########################################################################################################################
+# $Id: $
+#########################################################################################################################
+# 50_SSChatBot.pm
+#
+# (c) 2019 by Heiko Maaz
+# e-mail: Heiko dot Maaz at t-online dot de
+#
+# This Module can be used to operate as Bot for Synology Chat.
+# It's based on and uses Synology Chat Web Hook.
+#
+# This script is part of fhem.
+#
+# Fhem is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Fhem is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with fhem. If not, see .
+#
+#########################################################################################################################
+#
+# Definition: define SSChatBot [ServerPort] [Protocol]
+#
+# Example of defining a Bot: define SynChatBot SSChatBot 192.168.2.20 [5000] [HTTP(S)]
+#
+
+package main;
+
+use strict;
+use warnings;
+eval "use JSON;1;" or my $SSChatBotMM = "JSON"; # Debian: apt-get install libjson-perl
+use Data::Dumper; # Perl Core module
+use MIME::Base64;
+use Time::HiRes;
+use HttpUtils;
+use Encode;
+eval "use FHEM::Meta;1" or my $modMetaAbsent = 1;
+
+# no if $] >= 5.017011, warnings => 'experimental';
+
+# Versions History intern
+our %SSChatBot_vNotesIntern = (
+ "1.0.0" => "20.11.2019 initial "
+);
+
+# Versions History extern
+our %SSChatBot_vNotesExtern = (
+ "1.0.0" => "12.12.2015 initial "
+);
+
+my %SSChatBot_errlist = (
+ 100 => "Unknown error",
+ 102 => "API does not exist- may be the Chat server package is stopped",
+ 120 => "payload has wrong format",
+ 407 => "record is not valid",
+);
+
+# Standardvariablen und Forward-Deklaration
+use vars qw(%SSChatBot_vHintsExt_en);
+use vars qw(%SSChatBot_vHintsExt_de);
+
+################################################################
+sub SSChatBot_Initialize($) {
+ my ($hash) = @_;
+ $hash->{DefFn} = "SSChatBot_Define";
+ $hash->{UndefFn} = "SSChatBot_Undef";
+ $hash->{DeleteFn} = "SSChatBot_Delete";
+ $hash->{SetFn} = "SSChatBot_Set";
+ $hash->{GetFn} = "SSChatBot_Get";
+ $hash->{AttrFn} = "SSChatBot_Attr";
+ $hash->{DelayedShutdownFn} = "SSChatBot_DelayedShutdown";
+ $hash->{FW_deviceOverview} = 1;
+
+ $hash->{AttrList} = "disable:1,0 ".
+ "recepUser:--wait#for#userlist-- ".
+ "showTokenInLog:1,0 ".
+ "httptimeout ".
+ $readingFnAttributes;
+
+ eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
+
+return;
+}
+
+################################################################
+# define SynChatBot SSChatBot 192.168.2.10 [5000] [HTTP(S)]
+# ($hash) [1] [2] [3] [4]
+#
+################################################################
+sub SSChatBot_Define($@) {
+ my ($hash, $def) = @_;
+ my $name = $hash->{NAME};
+
+ return "Error: Perl module ".$SSChatBotMM." is missing. Install it on Debian with: sudo apt-get install libjson-perl" if($SSChatBotMM);
+
+ my @a = split("[ \t][ \t]*", $def);
+
+ if(int(@a) < 2) {
+ return "You need to specify more parameters.\n". "Format: define SSChatBot [Port] [HTTP(S)]";
+ }
+
+ my $serveraddr = $a[2];
+ my $serverport = $a[3] ? $a[3] : 5000;
+ my $proto = $a[4] ? lc($a[4]) : "http";
+
+ $hash->{SERVERADDR} = $serveraddr;
+ $hash->{SERVERPORT} = $serverport;
+ $hash->{MODEL} = "ChatBot";
+ $hash->{PROTOCOL} = $proto;
+ $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden
+
+ # benötigte API's in $hash einfügen
+ $hash->{HELPER}{APIINFO} = "SYNO.API.Info"; # Info-Seite für alle API's, einzige statische Seite !
+ $hash->{HELPER}{CHATEXTERNAL} = "SYNO.Chat.External";
+
+ # Versionsinformationen setzen
+ SSChatBot_setVersionInfo($hash);
+
+ # Token lesen
+ SSChatBot_getToken($hash,1,"botToken");
+
+ # Index der Sendequeue initialisieren
+ $data{SSChatBot}{$name}{sendqueue}{index} = 0;
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"state", "Initialized"); # Init state
+ readingsEndUpdate($hash,1);
+
+ # initiale Routinen nach Start ausführen , verzögerter zufälliger Start
+ RemoveInternalTimer($hash, "SSChatBot_initonboot");
+ InternalTimer(gettimeofday()+int(rand(15)), "SSChatBot_initonboot", $hash, 0);
+
+return undef;
+}
+
+################################################################
+# Die Undef-Funktion wird aufgerufen wenn ein Gerät mit delete
+# gelöscht wird oder bei der Abarbeitung des Befehls rereadcfg,
+# der ebenfalls alle Geräte löscht und danach das
+# Konfigurationsfile neu einliest.
+# Funktion: typische Aufräumarbeiten wie das
+# saubere Schließen von Verbindungen oder das Entfernen von
+# internen Timern, sofern diese im Modul zum Pollen verwendet
+# wurden.
+################################################################
+sub SSChatBot_Undef($$) {
+ my ($hash, $arg) = @_;
+ my $name = $hash->{NAME};
+
+ delete $data{SSChatBot}{$name};
+ RemoveInternalTimer($hash);
+
+return undef;
+}
+
+#######################################################################################################
+# Mit der X_DelayedShutdown Funktion kann eine Definition das Stoppen von FHEM verzögern um asynchron
+# hinter sich aufzuräumen.
+# Je nach Rückgabewert $delay_needed wird der Stopp von FHEM verzögert (0|1).
+# Sobald alle nötigen Maßnahmen erledigt sind, muss der Abschluss mit CancelDelayedShutdown($name) an
+# FHEM zurückgemeldet werden.
+#######################################################################################################
+sub SSChatBot_DelayedShutdown($) {
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+return 0;
+}
+
+#################################################################
+# Wenn ein Gerät in FHEM gelöscht wird, wird zuerst die Funktion
+# X_Undef aufgerufen um offene Verbindungen zu schließen,
+# anschließend wird die Funktion X_Delete aufgerufen.
+# Funktion: Aufräumen von dauerhaften Daten, welche durch das
+# Modul evtl. für dieses Gerät spezifisch erstellt worden sind.
+# Es geht hier also eher darum, alle Spuren sowohl im laufenden
+# FHEM-Prozess, als auch dauerhafte Daten bspw. im physikalischen
+# Gerät zu löschen die mit dieser Gerätedefinition zu tun haben.
+#################################################################
+sub SSChatBot_Delete($$) {
+ my ($hash, $arg) = @_;
+ my $name = $hash->{NAME};
+ my $index = $hash->{TYPE}."_".$hash->{NAME}."_botToken";
+
+ # gespeicherte Credentials löschen
+ setKeyValue($index, undef);
+
+return undef;
+}
+
+################################################################
+sub SSChatBot_Attr($$$$) {
+ my ($cmd,$name,$aName,$aVal) = @_;
+ my $hash = $defs{$name};
+ my ($do,$val,$cache);
+
+ # $cmd can be "del" or "set"
+ # $name is device name
+ # aName and aVal are Attribute name and value
+
+ if ($aName eq "disable") {
+ if($cmd eq "set") {
+ $do = ($aVal) ? 1 : 0;
+ }
+ $do = 0 if($cmd eq "del");
+ if(SSChatBot_IsModelCam($hash)) {
+ $val = ($do == 1 ? "inactive" : "off");
+ } else {
+ $val = ($do == 1 ? "disabled" : "initialized");
+ }
+
+ if ($do == 1) {
+ RemoveInternalTimer($hash);
+ } else {
+ InternalTimer(gettimeofday()+int(rand(30)), "SSChatBot_initonboot", $hash, 0);
+ }
+
+ readingsSingleUpdate($hash, "state", $val, 1);
+ }
+
+ if ($cmd eq "set") {
+ if ($aName =~ m/httptimeout/) {
+ unless ($aVal =~ /^\d+$/) { return " The Value for $aName is not valid. Use only figures 1-9 !";}
+ }
+ }
+
+return undef;
+}
+
+################################################################
+sub SSChatBot_Set($@) {
+ my ($hash, @a) = @_;
+ return "\"set X\" needs at least an argument" if ( @a < 2 );
+ my $name = $a[0];
+ my $opt = $a[1];
+ my $prop = $a[2];
+ my $prop1 = $a[3];
+ my $prop2 = $a[4];
+ my $prop3 = $a[5];
+ my $success;
+ my $setlist;
+
+ return if(IsDisabled($name));
+
+ if(!$hash->{TOKEN}) {
+ # initiale setlist für neue Devices
+ $setlist = "Unknown argument $opt, choose one of ".
+ "botToken "
+ ;
+ } else {
+ $setlist = "Unknown argument $opt, choose one of ".
+ "botToken ".
+ "listSendqueue:noArg ".
+ "sendItem:textField-long "
+ ;
+ }
+
+ if ($opt eq "botToken") {
+ $prop =~ /^(%22)(.*)(%22)$/ if($prop);
+ return "The token you entered was incomplete ! \n".
+ "Take the complete string after \"&token=\" from the Synology Chat \"Integration->Bots->incoming URL\" menu. \n".
+ "The token has the form like \"%22U6FOMH9IgT2WECJceaIW0fNwEiVVfqWQFP7gJQUJ6vpaGo8Z1SJkOGP7zlVIscCp%22\" " if (!$1 || !$3);
+ ($success) = SSChatBot_setToken($hash,$2,"botToken");
+
+ if($success) {
+ return "botToken saved successfully";
+ } else {
+ return "Error while saving botToken - see logfile for details";
+ }
+
+ } elsif ($opt eq "listSendqueue") {
+ my $sub = sub ($) {
+ my ($idx) = @_;
+ my $ret;
+ foreach my $key (reverse sort keys %{$data{SSChatBot}{$name}{sendqueue}{entries}{$idx}}) {
+ $ret .= ", " if($ret);
+ $ret .= $key."=>".$data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{$key};
+ }
+ return $ret;
+ };
+
+ my $sq;
+ foreach my $idx (sort{$a<=>$b}keys %{$data{SSChatBot}{$name}{sendqueue}{entries}}) {
+ $sq .= $idx." => ".$sub->($idx)."\n";
+ }
+ return $sq;
+
+ } elsif ($opt eq "sendItem") {
+ # text="First line of message to post.\nAlso you can have a second line of message." users="user1"
+ # text="" users="user1"
+ # text="Check this!! for details!" users="user1,user2"
+ # text="a fun image" fileUrl="http://imgur.com/xxxxx" users="user1,user2"
+ my $cmd = join(" ", @a);
+ my ($text,$users,$fileUrl);
+ my($a, $h) = parseParams($cmd);
+ if($h) {
+ $text = $h->{text} if(defined $h->{text});
+ $users = $h->{users} if(defined $h->{users});
+ $fileUrl = $h->{fileUrl} if(defined $h->{fileUrl});
+ }
+
+ return "Your sendstring is incorrect. It must contain at least the \"text\" tag like 'text=\"...\" '." if(!$text);
+
+ $users = AttrVal($name,"recepUser", "") if(!$users);
+ return "You haven't defined any receptor for send the message to. ".
+ "You have to use the \"users\" tag or define default receptors with attribute \"recepUser\"." if(!$users);
+
+ # User aufsplitten und zu jedem die ID ermitteln
+ my @ua = split(/,/, $users);
+ foreach (@ua) {
+ next if(!$_);
+ my $uid = $hash->{HELPER}{USERS}{$_}{id};
+ return "The receptor \"$_\" seems to be unknown because its ID coulnd't be found." if(!$uid);
+
+ # Eintrag zur SendQueue hinzufügen
+ # Werte: (name,opmode,method,userid,text,fileUrl,channel,attachment)
+ SSChatBot_addQueue($name, "sendItem", "chatbot", $uid, $text, $fileUrl, "", "");
+ }
+
+ SSChatBot_getapisites($name);
+
+ } else {
+ return "$setlist";
+ }
+
+return;
+}
+
+################################################################
+sub SSChatBot_Get($@) {
+ my ($hash, @a) = @_;
+ return "\"get X\" needs at least an argument" if ( @a < 2 );
+ my $name = shift @a;
+ my $opt = shift @a;
+ my $arg = shift @a;
+ my $arg1 = shift @a;
+ my $arg2 = shift @a;
+ my $ret = "";
+ my $getlist;
+
+ if(!$hash->{TOKEN}) {
+ return;
+
+ } else {
+ $getlist = "Unknown argument $opt, choose one of ".
+ "storedToken:noArg ".
+ "chatUserlist:noArg ".
+ "chatChannellist:noArg ".
+ "versionNotes "
+ ;
+ }
+
+ return if(IsDisabled($name));
+
+ if ($opt eq "storedToken") {
+ if (!$hash->{TOKEN}) {return "Token of $name is not set - make sure you've set it with \"set $name botToken \"";}
+ # Token abrufen
+ my ($success, $token) = SSChatBot_getToken($hash,0,"botToken");
+ unless ($success) {return "Token couldn't be retrieved successfully - see logfile"};
+
+ return "Stored Token to act as Synology Chat Bot:\n".
+ "=========================================\n".
+ "$token \n"
+ ;
+
+ } elsif ($opt eq "chatUserlist") {
+ # übergebenen CL-Hash (FHEMWEB) in Helper eintragen
+ SSChatBot_getclhash($hash,1);
+
+ # Eintrag zur SendQueue hinzufügen
+ # Werte: (name,opmode,method,userid,text,fileUrl,channel,attachment)
+ SSChatBot_addQueue($name, "chatUserlist", "user_list", "", "", "", "", "");
+
+ SSChatBot_getapisites($name);
+
+ } elsif ($opt eq "chatChannellist") {
+ # übergebenen CL-Hash (FHEMWEB) in Helper eintragen
+ SSChatBot_getclhash($hash,1);
+
+ # Eintrag zur SendQueue hinzufügen
+ # Werte: (name,opmode,method,userid,text,fileUrl,channel,attachment)
+ SSChatBot_addQueue($name, "chatChannellist", "channel_list", "", "", "", "", "");
+
+ SSChatBot_getapisites($name);
+
+ } elsif ($opt =~ /versionNotes/) {
+ my $header = "Module release information
";
+ my $header1 = "Helpful hints
";
+ my %hs;
+
+ # Ausgabetabelle erstellen
+ my ($ret,$val0,$val1);
+ my $i = 0;
+
+ $ret = "";
+
+ # Hints
+ if(!$arg || $arg =~ /hints/ || $arg =~ /[\d]+/) {
+ $ret .= sprintf("$header1
");
+ $ret .= "
";
+ $ret .= "";
+ $ret .= "";
+ if($arg && $arg =~ /[\d]+/) {
+ my @hints = split(",",$arg);
+ foreach (@hints) {
+ if(AttrVal("global","language","EN") eq "DE") {
+ $hs{$_} = $SSChatBot_vHintsExt_de{$_};
+ } else {
+ $hs{$_} = $SSChatBot_vHintsExt_en{$_};
+ }
+ }
+ } else {
+ if(AttrVal("global","language","EN") eq "DE") {
+ %hs = %SSChatBot_vHintsExt_de;
+ } else {
+ %hs = %SSChatBot_vHintsExt_en;
+ }
+ }
+ $i = 0;
+ foreach my $key (SSChatBot_sortVersion("desc",keys %hs)) {
+ $val0 = $hs{$key};
+ $ret .= sprintf("$key | $val0 | " );
+ $ret .= "
";
+ $i++;
+ if ($i & 1) {
+ # $i ist ungerade
+ $ret .= "";
+ } else {
+ $ret .= "
";
+ }
+ }
+ $ret .= "
";
+ $ret .= "";
+ $ret .= "
";
+ $ret .= "
";
+ }
+
+ # Notes
+ if(!$arg || $arg =~ /rel/) {
+ $ret .= sprintf("$header
");
+ $ret .= "
";
+ $ret .= "";
+ $ret .= "";
+ $i = 0;
+ foreach my $key (SSChatBot_sortVersion("desc",keys %SSChatBot_vNotesExtern)) {
+ ($val0,$val1) = split(/\s/,$SSChatBot_vNotesExtern{$key},2);
+ $ret .= sprintf("$key | $val0 | $val1 | " );
+ $ret .= "
";
+ $i++;
+ if ($i & 1) {
+ # $i ist ungerade
+ $ret .= "";
+ } else {
+ $ret .= "
";
+ }
+ }
+ $ret .= "
";
+ $ret .= "";
+ $ret .= "
";
+ $ret .= "
";
+ }
+
+ $ret .= "";
+
+ return $ret;
+
+ } else {
+ return "$getlist";
+ }
+
+return $ret; # not generate trigger out of command
+}
+
+######################################################################################
+# initiale Startroutinen nach Restart FHEM
+######################################################################################
+sub SSChatBot_initonboot ($) {
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ RemoveInternalTimer($hash, "SSChatBot_initonboot");
+
+ if ($init_done == 1) {
+ RemoveInternalTimer($hash); # alle Timer löschen
+
+ CommandGet(undef, "$name chatUserlist");
+
+ } else {
+ InternalTimer(gettimeofday()+3, "SSChatBot_initonboot", $hash, 0);
+ }
+
+return;
+}
+
+######################################################################################
+# Eintrag zur SendQueue hinzufügen
+######################################################################################
+sub SSChatBot_addQueue ($$$$$$$$) {
+ my ($name,$opmode,$method,$userid,$text,$fileUrl,$channel,$attachment) = @_;
+ my $hash = $defs{$name};
+
+ $data{SSChatBot}{$name}{sendqueue}{index}++;
+ my $index = $data{SSChatBot}{$name}{sendqueue}{index};
+
+ my $pars = {'opmode' => $opmode,
+ 'method' => $method,
+ 'userid' => $userid,
+ 'channel' => $channel,
+ 'text' => $text,
+ 'attachment' => $attachment,
+ 'fileUrl' => $fileUrl,
+ 'retryCount' => 0,
+ };
+
+ $data{SSChatBot}{$name}{sendqueue}{entries}{$index} = $pars;
+
+return;
+}
+
+
+#############################################################################################
+# Erfolg einer Rückkehrroutine checken und ggf. Send-Retry ausführen
+# bzw. den SendQueue-Eintrag bei Erfolg löschen
+# $name = Name des Chatbot-Devices
+# $retry = 0 -> Opmode erfolgreich (DS löschen),
+# 1 -> Opmode nicht erfolgreich (Abarbeitung verzögert wiederholen)
+#############################################################################################
+sub SSChatBot_checkretry ($$) {
+ my ($name,$retry) = @_;
+ my $hash = $defs{$name};
+ my $idx = $hash->{OPIDX};
+
+ if(!$retry) {
+ # Befehl erfolgreich, Senden nur neu starten wenn weitere Einträge in SendQueue
+ delete $hash->{OPIDX};
+ delete $data{SSChatBot}{$name}{sendqueue}{entries}{$idx};
+ Log3($name, 4, "$name - Opmode \"$hash->{OPMODE}\" finished successfully, Sendqueue index \"$idx\" deleted.");
+ return SSChatBot_chatop($name) if((sort{$a<=>$b}keys %{$data{SSChatBot}{$name}{sendqueue}{entries}})[0]); # nächsten Eintrag abarbeiten wenn SendQueue nicht leer
+
+ } else {
+ # Befehl nicht erfolgreich, (verzögertes) Senden einplanen
+ $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{retryCount}++;
+ my $rc = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{retryCount};
+
+ my $rs = 0;
+ if($rc <= 5) {
+ $rs = 5;
+ } elsif ($rc < 10) {
+ $rs = 20;
+ } elsif ($rc < 15) {
+ $rs = 60;
+ } elsif ($rc < 20) {
+ $rs = 1800;
+ } elsif ($rc < 25) {
+ $rs = 3600;
+ } else {
+ $rs = 86400;
+ }
+
+ Log3($name, 2, "$name - ERROR - \"$hash->{OPMODE}\" index \"$idx\" finished faulty. Restart SendQueue in $rs seconds (retryCount $rc).");
+
+ RemoveInternalTimer($hash, "SSChatBot_chatop");
+ InternalTimer(gettimeofday()+$rs, "SSChatBot_chatop", "$name", 0);
+ }
+
+return;
+}
+
+#############################################################################################################################
+####### Begin Kameraoperationen mit NonblockingGet (nicht blockierender HTTP-Call) #######
+#############################################################################################################################
+sub SSChatBot_getapisites($) {
+ my ($name) = @_;
+ my $hash = $defs{$name};
+ my $serveraddr = $hash->{SERVERADDR};
+ my $serverport = $hash->{SERVERPORT};
+ my $proto = $hash->{PROTOCOL};
+ my $apiinfo = $hash->{HELPER}{APIINFO}; # Info-Seite für alle API's, einzige statische Seite !
+ my $chatexternal = $hash->{HELPER}{CHATEXTERNAL};
+ my $url;
+ my $param;
+
+ # API-Pfade und MaxVersions ermitteln
+ Log3($name, 4, "$name - ####################################################");
+ Log3($name, 4, "$name - ### start Chat operation ");
+ Log3($name, 4, "$name - ####################################################");
+
+ if ($hash->{HELPER}{APIPARSET}) {
+ # API-Hashwerte sind bereits gesetzt -> Abruf überspringen
+ Log3($name, 4, "$name - API hashvalues already set - ignore get apisites");
+ return SSChatBot_chatop($name);
+ }
+
+ my $httptimeout = AttrVal($name,"httptimeout",4);
+ Log3($name, 5, "$name - HTTP-Call will be done with httptimeout: $httptimeout s");
+
+ # URL zur Abfrage der Eigenschaften der API's
+ $url = "$proto://$serveraddr:$serverport/webapi/query.cgi?api=$apiinfo&method=Query&version=1&query=$chatexternal";
+
+ Log3($name, 4, "$name - Call-Out: $url");
+
+ $param = {
+ url => $url,
+ timeout => $httptimeout,
+ hash => $hash,
+ method => "GET",
+ header => "Accept: application/json",
+ callback => \&SSChatBot_getapisites_parse
+ };
+ HttpUtils_NonblockingGet ($param);
+}
+
+####################################################################################
+# Auswertung Abruf apisites
+####################################################################################
+sub SSChatBot_getapisites_parse ($) {
+ my ($param, $err, $myjson) = @_;
+ my $hash = $param->{hash};
+ my $name = $hash->{NAME};
+ my $serveraddr = $hash->{SERVERADDR};
+ my $serverport = $hash->{SERVERPORT};
+ my $chatexternal = $hash->{HELPER}{CHATEXTERNAL};
+
+ my ($chatexternalmaxver,$chatexternalpath);
+
+ if ($err ne "") {
+ # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
+ Log3($name, 2, "$name - error while requesting ".$param->{url}." - $err");
+
+ readingsSingleUpdate($hash, "Error", $err, 1);
+
+ SSChatBot_checkretry($name,1);
+ return;
+
+ } elsif ($myjson ne "") {
+ # Evaluiere ob Daten im JSON-Format empfangen wurden
+ ($hash, my $success) = SSChatBot_evaljson($hash,$myjson);
+
+ unless ($success) {return;}
+
+ my $data = decode_json($myjson);
+
+ # Logausgabe decodierte JSON Daten
+ Log3($name, 5, "$name - JSON returned: ". Dumper $data);
+
+ $success = $data->{'success'};
+
+ if ($success) {
+ my $logstr;
+
+ # Pfad und Maxversion von "SYNO.Chat.External" ermitteln
+ my $chatexternalpath = $data->{'data'}->{$chatexternal}->{'path'};
+ $chatexternalpath =~ tr/_//d if (defined($chatexternalpath));
+ my $chatexternalmaxver = $data->{'data'}->{$chatexternal}->{'maxVersion'};
+
+ $logstr = defined($chatexternalpath) ? "Path of $chatexternal selected: $chatexternalpath" : "Path of $chatexternal undefined - Surveillance Station may be stopped";
+ Log3($name, 4, "$name - $logstr");
+ $logstr = defined($chatexternalmaxver) ? "MaxVersion of $chatexternal selected: $chatexternalmaxver" : "MaxVersion of $chatexternal undefined - Surveillance Station may be stopped";
+ Log3($name, 4, "$name - $logstr");
+
+ # ermittelte Werte in $hash einfügen
+ $hash->{HELPER}{CHATEXTERNALPATH} = $chatexternalpath;
+ $hash->{HELPER}{CHATEXTERNALMAXVER} = $chatexternalmaxver;
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error","none");
+ readingsEndUpdate($hash,1);
+
+ # Webhook Hash values sind gesetzt
+ $hash->{HELPER}{APIPARSET} = 1;
+
+ } else {
+ my $error = "couldn't get Synology Chat API informations";
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error",$error);
+ readingsEndUpdate($hash, 1);
+
+ Log3($name, 2, "$name - ERROR - the API-Query couldn't be executed successfully");
+
+ SSChatBot_checkretry($name,1);
+ return;
+ }
+ }
+
+return SSChatBot_chatop($name);
+}
+
+#############################################################################################
+# Ausführung Operation
+#############################################################################################
+sub SSChatBot_chatop ($) {
+ my ($name) = @_;
+ my $hash = $defs{$name};
+ my $proto = $hash->{PROTOCOL};
+ my $serveraddr = $hash->{SERVERADDR};
+ my $serverport = $hash->{SERVERPORT};
+ # my $opmode = $hash->{OPMODE};
+ my $chatexternal = $hash->{HELPER}{CHATEXTERNAL};
+ my $chatexternalpath = $hash->{HELPER}{CHATEXTERNALPATH};
+ my $chatexternalmaxver = $hash->{HELPER}{CHATEXTERNALMAXVER};
+ my ($url,$httptimeout,$param,$error);
+
+ # Token abrufen
+ my ($success, $token) = SSChatBot_getToken($hash,0,"botToken");
+ unless ($success) {
+ $error = "The botToken couldn't be retrieved";
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error",$error);
+ readingsEndUpdate($hash, 1);
+
+ Log3($name, 2, "$name - ERROR - $error");
+
+ SSChatBot_checkretry($name,1);
+ return;
+ }
+
+ # den nächsten Eintrag aus "SendQueue" verarbeiten
+ my $idx = (sort{$a<=>$b}keys %{$data{SSChatBot}{$name}{sendqueue}{entries}})[0];
+ if(!$idx) {
+ Log3($name, 4, "$name - SendQueue is empty. Nothing to do ...");
+ return;
+ }
+ $hash->{OPMODE} = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{opmode};
+ $hash->{OPIDX} = $idx;
+ my $opmode = $hash->{OPMODE};
+ my $method = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{method};
+ my $userid = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{userid};
+ my $channel = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{channel};
+ my $text = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{text};
+ my $attachment = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{attachment};
+ my $fileUrl = $data{SSChatBot}{$name}{sendqueue}{entries}{$idx}{fileUrl};
+ Log3($name, 4, "$name - start SendQueue entry index \"$idx\" ($hash->{OPMODE}) for operation.");
+
+ $httptimeout = AttrVal($name, "httptimeout", 4);
+
+ Log3($name, 5, "$name - HTTP-Call will be done with httptimeout: $httptimeout s");
+
+ if ($opmode =~ /^chatUserlist$|^chatChannellist$/) {
+ $url = "$proto://$serveraddr:$serverport/webapi/$chatexternalpath?api=$chatexternal&version=$chatexternalmaxver&method=$method&token=\"$token\"";
+ }
+
+ if ($opmode eq "sendItem") {
+ # Form: payload={"text": "a fun image", "file_url": "http://imgur.com/xxxxx" "user_ids": [5]}
+ # payload={"text": "First line of message to post in the channel" "user_ids": [5]}
+ # payload={"text": "Check this!! for details!" "user_ids": [5]}
+
+ $url = "$proto://$serveraddr:$serverport/webapi/$chatexternalpath?api=$chatexternal&version=$chatexternalmaxver&method=$method&token=\"$token\"";
+ $url .= "&payload={";
+ $url .= "\"text\": \"$text\"," if($text);
+ $url .= "\"file_url\": \"$fileUrl\"," if($fileUrl);
+ $url .= "\"user_ids\": [$userid]" if($userid);
+ $url .= "}";
+ }
+
+ Log3($name, 4, "$name - Call-Out: $url");
+
+ $param = {
+ url => $url,
+ timeout => $httptimeout,
+ hash => $hash,
+ method => "GET",
+ header => "Accept: application/json",
+ callback => \&SSChatBot_chatop_parse
+ };
+
+ HttpUtils_NonblockingGet ($param);
+}
+
+#############################################################################################
+# Callback from SSChatBot_chatop
+#############################################################################################
+sub SSChatBot_chatop_parse ($) {
+ my ($param, $err, $myjson) = @_;
+ my $hash = $param->{hash};
+ my $name = $hash->{NAME};
+ my $proto = $hash->{PROTOCOL};
+ my $serveraddr = $hash->{SERVERADDR};
+ my $serverport = $hash->{SERVERPORT};
+ my $opmode = $hash->{OPMODE};
+ my ($rectime,$data,$success);
+ my ($error,$errorcode);
+
+ my $lang = AttrVal("global","language","EN");
+
+ if ($err ne "") {
+ # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
+ Log3($name, 2, "$name - error while requesting ".$param->{url}." - $err");
+
+ readingsSingleUpdate($hash, "Error", $err, 1);
+
+ SSChatBot_checkretry($name,1);
+ return;
+
+ } elsif ($myjson ne "") {
+ # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes)
+ # Evaluiere ob Daten im JSON-Format empfangen wurden
+ ($hash,$success,$myjson) = SSChatBot_evaljson($hash,$myjson);
+ unless ($success) {
+ Log3($name, 4, "$name - Data returned: ".$myjson);
+ return;
+ }
+
+ $data = decode_json($myjson);
+
+ # Logausgabe decodierte JSON Daten
+ Log3($name, 5, "$name - JSON returned: ". Dumper $data);
+
+ $success = $data->{'success'};
+
+ if ($success) {
+
+ if ($opmode eq "chatUserlist") {
+ my %users = ();
+ my ($un,$ui,$st,$nn,$em,$uids);
+ my $i = 0;
+
+ my $out = "";
+ $out .= "Synology Chat Server visible Users
";
+ $out .= "";
+ $out .= " Username | ID | state | Nickname | Email | |
";
+ $out .= " | | | | | |
";
+
+ while ($data->{'data'}->{'users'}->[$i]) {
+ my $deleted = SSChatBot_jboolmap($data->{'data'}->{'users'}->[$i]->{'deleted'});
+ my $isdis = SSChatBot_jboolmap($data->{'data'}->{'users'}->[$i]->{'is_disabled'});
+ if($deleted ne "true" && $isdis ne "true") {
+ $un = $data->{'data'}->{'users'}->[$i]->{'username'};
+ $ui = $data->{'data'}->{'users'}->[$i]->{'user_id'};
+ $st = $data->{'data'}->{'users'}->[$i]->{'status'};
+ $nn = $data->{'data'}->{'users'}->[$i]->{'nickname'};
+ $em = $data->{'data'}->{'users'}->[$i]->{'user_props'}->{'email'};
+ $users{$un}{id} = $ui;
+ $users{$un}{status} = $st;
+ $users{$un}{nickname} = $nn;
+ $users{$un}{email} = $em;
+ $uids .= "," if($uids);
+ $uids .= $un;
+ $out .= " $un | $ui | $st | $nn | $em | |
";
+ }
+ $i++;
+ }
+ $hash->{HELPER}{USERS} = \%users if(%users);
+
+ my @newa;
+ my @deva = split(" ", $hash->{".AttrList"});
+ foreach (@deva) {
+ push @newa, $_ if($_ !~ /recepUser/);
+ }
+ push @newa, ($uids?"recepUser:multiple-strict,$uids ":"recepUser:--no#userlist#selectable-- ");
+ $hash->{".AttrList"} = join(" ", @newa);
+
+ $out .= "
";
+ $out .= "";
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error","none");
+ readingsEndUpdate($hash, 1);
+
+ # Ausgabe Popup der User-Daten (nach readingsEndUpdate positionieren sonst
+ # "Connection lost, trying reconnect every 5 seconds" wenn > 102400 Zeichen)
+ asyncOutput($hash->{HELPER}{CL}{1},"$out");
+ delete($hash->{HELPER}{CL});
+
+ } elsif ($opmode eq "chatChannellist") {
+ my %channels = ();
+ my ($cn,$ci,$cr,$mb,$ty,$cids);
+ my $i = 0;
+
+ my $out = "";
+ $out .= "Synology Chat Server visible Channels
";
+ $out .= "";
+ $out .= " Channelname | ID | Creator | Members | Type | |
";
+ $out .= " | | | | | |
";
+
+ while ($data->{'data'}->{'channels'}->[$i]) {
+ my $cn = SSChatBot_jboolmap($data->{'data'}->{'channels'}->[$i]->{'name'});
+ if($cn) {
+ $ci = $data->{'data'}->{'channels'}->[$i]->{'channel_id'};
+ $cr = $data->{'data'}->{'channels'}->[$i]->{'creator_id'};
+ $mb = $data->{'data'}->{'channels'}->[$i]->{'members'};
+ $ty = $data->{'data'}->{'channels'}->[$i]->{'type'};
+ $channels{$cn}{id} = $ci;
+ $channels{$cn}{creator} = $cr;
+ $channels{$cn}{members} = $mb;
+ $channels{$cn}{type} = $ty;
+ $cids .= "," if($cids);
+ $cids .= $cn;
+ $out .= " $cn | $ci | $cr | $mb | $ty | |
";
+ }
+ $i++;
+ }
+ $hash->{HELPER}{CHANNELS} = \%channels if(%channels);
+
+ $out .= "
";
+ $out .= "";
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error","none");
+ readingsEndUpdate($hash, 1);
+
+ # Ausgabe Popup der User-Daten (nach readingsEndUpdate positionieren sonst
+ # "Connection lost, trying reconnect every 5 seconds" wenn > 102400 Zeichen)
+ asyncOutput($hash->{HELPER}{CL}{1},"$out");
+ delete($hash->{HELPER}{CL});
+
+ } elsif ($opmode eq "sendItem") {
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error","none");
+ readingsEndUpdate($hash, 1);
+
+ }
+
+ SSChatBot_checkretry($name,0);
+ readingsSingleUpdate($hash,"state", "connected", 1);
+
+ } else {
+ # die API-Operation war fehlerhaft
+ # Errorcode aus JSON ermitteln
+ $errorcode = $data->{'error'}->{'code'};
+
+ # Fehlertext zum Errorcode ermitteln
+ $error = SSChatBot_experror($hash,$errorcode);
+
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode", $errorcode);
+ readingsBulkUpdate($hash,"Error", $error);
+ readingsBulkUpdate($hash,"state", "disconnected") if($errorcode =~ /102/);
+ readingsEndUpdate($hash, 1);
+
+ Log3($name, 2, "$name - ERROR - Operation $opmode was not successful. Errorcode: $errorcode - $error");
+
+ SSChatBot_checkretry($name,1);
+ }
+
+ undef $data;
+ undef $myjson;
+ }
+
+return;
+}
+
+###############################################################################
+# Test ob JSON-String empfangen wurde
+###############################################################################
+sub SSChatBot_evaljson($$) {
+ my ($hash,$myjson) = @_;
+ my $OpMode = $hash->{OPMODE};
+ my $name = $hash->{NAME};
+ my $success = 1;
+
+ eval {decode_json($myjson)} or do {
+ $success = 0;
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash,"Errorcode","none");
+ readingsBulkUpdate($hash,"Error","malformed JSON string received");
+ readingsEndUpdate($hash, 1);
+ };
+
+return($hash,$success,$myjson);
+}
+
+###############################################################################
+# JSON Boolean Test und Mapping
+###############################################################################
+sub SSChatBot_jboolmap($){
+ my ($bool)= @_;
+
+ if(JSON::is_bool($bool)) {
+ $bool = $bool?"true":"false";
+ }
+
+return $bool;
+}
+
+
+##############################################################################
+# Auflösung Errorcodes SVS API
+# Übernahmewerte sind $hash, $errorcode
+##############################################################################
+sub SSChatBot_experror ($$) {
+ my ($hash,$errorcode) = @_;
+ my $device = $hash->{NAME};
+ my $error;
+
+ unless (exists($SSChatBot_errlist{"$errorcode"})) {$error = "Message of errorcode \"$errorcode\" not found. Please turn to Synology Web API-Guide."; return ($error);}
+
+ # Fehlertext aus Hash-Tabelle %errorlist ermitteln
+ $error = $SSChatBot_errlist{"$errorcode"};
+
+return ($error);
+}
+
+################################################################
+# sortiert eine Liste von Versionsnummern x.x.x
+# Schwartzian Transform and the GRT transform
+# Übergabe: "asc | desc",
+################################################################
+sub SSChatBot_sortVersion (@){
+ my ($sseq,@versions) = @_;
+
+ my @sorted = map {$_->[0]}
+ sort {$a->[1] cmp $b->[1]}
+ map {[$_, pack "C*", split /\./]} @versions;
+
+ @sorted = map {join ".", unpack "C*", $_}
+ sort
+ map {pack "C*", split /\./} @versions;
+
+ if($sseq eq "desc") {
+ @sorted = reverse @sorted;
+ }
+
+return @sorted;
+}
+
+######################################################################################
+# botToken speichern
+######################################################################################
+sub SSChatBot_setToken ($$@) {
+ my ($hash, $token, $ao) = @_;
+ my $name = $hash->{NAME};
+ my ($success, $credstr, $index, $retcode);
+ my (@key,$len,$i);
+
+ $credstr = encode_base64($token);
+
+ # Beginn Scramble-Routine
+ @key = qw(1 3 4 5 6 3 2 1 9);
+ $len = scalar @key;
+ $i = 0;
+ $credstr = join "",
+ map { $i = ($i + 1) % $len;
+ chr((ord($_) + $key[$i]) % 256) } split //, $credstr;
+ # End Scramble-Routine
+
+ $index = $hash->{TYPE}."_".$hash->{NAME}."_".$ao;
+ $retcode = setKeyValue($index, $credstr);
+
+ if ($retcode) {
+ Log3($name, 2, "$name - Error while saving Token - $retcode");
+ $success = 0;
+ } else {
+ ($success, $token) = SSChatBot_getToken($hash,1,$ao); # Credentials nach Speicherung lesen und in RAM laden ($boot=1)
+ }
+
+return ($success);
+}
+
+######################################################################################
+# botToken lesen
+######################################################################################
+sub SSChatBot_getToken ($$$) {
+ my ($hash,$boot, $ao) = @_;
+ my $name = $hash->{NAME};
+ my ($success, $token, $index, $retcode, $credstr);
+ my (@key,$len,$i);
+
+ if ($boot) {
+ # mit $boot=1 botToken von Platte lesen und als scrambled-String in RAM legen
+ $index = $hash->{TYPE}."_".$hash->{NAME}."_".$ao;
+ ($retcode, $credstr) = getKeyValue($index);
+
+ if ($retcode) {
+ Log3($name, 2, "$name - Unable to read botToken from file: $retcode");
+ $success = 0;
+ }
+
+ if ($credstr) {
+ # beim Boot scrambled botToken in den RAM laden
+ $hash->{HELPER}{TOKEN} = $credstr;
+
+ # "TOKEN" wird als Statusbit ausgewertet. Wenn nicht gesetzt -> Warnmeldung und keine weitere Verarbeitung
+ $hash->{TOKEN} = "Set";
+ $success = 1;
+ }
+
+ } else {
+ # boot = 0 -> botToken aus RAM lesen, decoden und zurückgeben
+ $credstr = $hash->{HELPER}{TOKEN};
+
+ if($credstr) {
+ # Beginn Descramble-Routine
+ @key = qw(1 3 4 5 6 3 2 1 9);
+ $len = scalar @key;
+ $i = 0;
+ $credstr = join "",
+ map { $i = ($i + 1) % $len;
+ chr((ord($_) - $key[$i] + 256) % 256) }
+ split //, $credstr;
+ # Ende Descramble-Routine
+
+ $token = decode_base64($credstr);
+
+ my $logtok = AttrVal($name, "showTokenInLog", "0") == 1 ? $token : "********";
+
+ Log3($name, 4, "$name - botToken read from RAM: $logtok");
+
+ } else {
+ Log3($name, 2, "$name - botToken not set in RAM !");
+ }
+
+ $success = (defined($token)) ? 1 : 0;
+ }
+
+return ($success, $token);
+}
+
+#############################################################################################
+# Leerzeichen am Anfang / Ende eines strings entfernen
+#############################################################################################
+sub SSChatBot_trim ($) {
+ my $str = shift;
+ $str =~ s/^\s+|\s+$//g;
+
+return ($str);
+}
+
+#############################################################################################
+# Clienthash übernehmen oder zusammenstellen
+# Identifikation ob über FHEMWEB ausgelöst oder nicht -> erstellen $hash->CL
+#############################################################################################
+sub SSChatBot_getclhash($;$$) {
+ my ($hash,$nobgd)= @_;
+ my $name = $hash->{NAME};
+ my $ret;
+
+ if($nobgd) {
+ # nur übergebenen CL-Hash speichern,
+ # keine Hintergrundverarbeitung bzw. synthetische Erstellung CL-Hash
+ $hash->{HELPER}{CL}{1} = $hash->{CL};
+ return undef;
+ }
+
+ if (!defined($hash->{CL})) {
+ # Clienthash wurde nicht übergeben und wird erstellt (FHEMWEB Instanzen mit canAsyncOutput=1 analysiert)
+ my $outdev;
+ my @webdvs = devspec2array("TYPE=FHEMWEB:FILTER=canAsyncOutput=1:FILTER=STATE=Connected");
+ my $i = 1;
+ foreach (@webdvs) {
+ $outdev = $_;
+ next if(!$defs{$outdev});
+ $hash->{HELPER}{CL}{$i}->{NAME} = $defs{$outdev}{NAME};
+ $hash->{HELPER}{CL}{$i}->{NR} = $defs{$outdev}{NR};
+ $hash->{HELPER}{CL}{$i}->{COMP} = 1;
+ $i++;
+ }
+ } else {
+ # übergebenen CL-Hash in Helper eintragen
+ $hash->{HELPER}{CL}{1} = $hash->{CL};
+ }
+
+ # Clienthash auflösen zur Fehlersuche (aufrufende FHEMWEB Instanz
+ if (defined($hash->{HELPER}{CL}{1})) {
+ for (my $k=1; (defined($hash->{HELPER}{CL}{$k})); $k++ ) {
+ Log3($name, 4, "$name - Clienthash number: $k");
+ while (my ($key,$val) = each(%{$hash->{HELPER}{CL}{$k}})) {
+ $val = $val?$val:" ";
+ Log3($name, 4, "$name - Clienthash: $key -> $val");
+ }
+ }
+ } else {
+ Log3($name, 2, "$name - Clienthash was neither delivered nor created !");
+ $ret = "Clienthash was neither delivered nor created. Can't use asynchronous output for function.";
+ }
+
+return ($ret);
+}
+
+#############################################################################################
+# Versionierungen des Moduls setzen
+# Die Verwendung von Meta.pm und Packages wird berücksichtigt
+#############################################################################################
+sub SSChatBot_setVersionInfo($) {
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $v = (SSChatBot_sortVersion("desc",keys %SSChatBot_vNotesIntern))[0];
+ my $type = $hash->{TYPE};
+ $hash->{HELPER}{PACKAGE} = __PACKAGE__;
+ $hash->{HELPER}{VERSION} = $v;
+
+ if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) {
+ # META-Daten sind vorhanden
+ $modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SSChatBot}{META}}
+ if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id: 50_SSChatBot.pm 20534 2019-11-18 17:50:17Z DS_Starter $ im Kopf komplett! vorhanden )
+ $modules{$type}{META}{x_version} =~ s/1.1.1/$v/g;
+ } else {
+ $modules{$type}{META}{x_version} = $v;
+ }
+ return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 50_SSChatBot.pm 20534 2019-11-18 17:50:17Z DS_Starter $ im Kopf komplett! vorhanden )
+ if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) {
+ # es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
+ # mit {->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
+ use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
+ }
+ } else {
+ # herkömmliche Modulstruktur
+ $hash->{VERSION} = $v;
+ }
+
+return;
+}
+
+#############################################################################################
+# Hint Hash EN
+#############################################################################################
+%SSChatBot_vHintsExt_en = (
+);
+
+#############################################################################################
+# Hint Hash DE
+#############################################################################################
+%SSChatBot_vHintsExt_de = (
+
+);
+
+1;
+
+=pod
+=item summary module to use a Synology Chat Bot
+=item summary_DE Modul zur Installation eines Synology Chat Bot
+=begin html
+
+
+SSChatBot
+
+
+
+ Define
+
+
+
+
+
+ Set
+
+
+
+
+
+
+Get
+
+
+
+
+
+Attributes
+
+
+
+
+
+
+
+=end html
+=begin html_DE
+
+
+SSChatBot
+
+
+
+
+Definition
+
+
+
+
+
+Set
+
+
+
+
+
+Get
+
+
+
+
+
+Attribute
+
+
+
+
+
+
+=end html_DE
+
+=for :application/json;q=META.json 50_SSChatBot.pm
+{
+ "abstract": "Integration of Synology Chat server into FHEM.",
+ "x_lang": {
+ "de": {
+ "abstract": "Integration des Synology Chat Servers in FHEM."
+ }
+ },
+ "keywords": [
+ "synology",
+ "synologychat",
+ "chatbot",
+ "chat"
+ ],
+ "version": "v1.1.1",
+ "release_status": "stable",
+ "author": [
+ "Heiko Maaz "
+ ],
+ "x_fhem_maintainer": [
+ "DS_Starter"
+ ],
+ "x_fhem_maintainer_github": [
+ "nasseeder1"
+ ],
+ "prereqs": {
+ "runtime": {
+ "requires": {
+ "FHEM": 5.00918799,
+ "perl": 5.014,
+ "JSON": 0,
+ "Data::Dumper": 0,
+ "MIME::Base64": 0,
+ "Time::HiRes": 0,
+ "HttpUtils": 0,
+ "Encode": 0
+ },
+ "recommends": {
+ "FHEM::Meta": 0
+ },
+ "suggests": {
+ }
+ }
+ },
+ "resources": {
+ "x_wiki": {
+ "web": "https://wiki.fhem.de/wiki/SSChatBot_-_Integration des Synology Chat Servers in FHEM",
+ "title": "SSChatBot - Integration des Synology Chat Servers in FHEM"
+ },
+ "repository": {
+ "x_dev": {
+ "type": "svn",
+ "url": "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter",
+ "web": "https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter/50_SSChatBot.pm",
+ "x_branch": "dev",
+ "x_filepath": "fhem/contrib/",
+ "x_raw": "https://svn.fhem.de/fhem/trunk/fhem/contrib/DS_Starter/50_SSChatBot.pm"
+ }
+ }
+ }
+}
+=end :application/json;q=META.json
+
+=cut