########################################################################################################################
# $Id: $
#########################################################################################################################
# 57_SSCal.pm
#
# (c) 2019 - 2020 by Heiko Maaz
# e-mail: Heiko dot Maaz at t-online dot de
#
# This Module integrate the Synology Calendar into FHEM
#
# 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 SSCal [ServerPort] [Protocol]
#
# Example: define SynCal SSCal 192.168.2.20 [5000] [HTTP(S)]
#
package main;
use strict;
use warnings;
eval "use JSON;1;" or my $SSCalMM = "JSON"; # Debian: apt-get install libjson-perl
use Data::Dumper; # Perl Core module
use MIME::Base64;
use Time::HiRes;
use HttpUtils;
use Encode;
use Blocking;
no if $] >= 5.017011, warnings => 'experimental::smartmatch';
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1;
# no if $] >= 5.017011, warnings => 'experimental';
# Versions History intern
my %SSCal_vNotesIntern = (
"1.9.0" => "11.02.2020 new reading Weekday with localization, more field selection for overview table ",
"1.8.0" => "09.02.2020 evaluate icons for DaysLeft, Map and State in sub SSCal_evalTableSpecs , fix no table is shown after FHEM restart ",
"1.7.0" => "09.02.2020 respect global language setting for some presentation, new attributes tableSpecs & tableColumnMap, days left in overview ".
"formatting overview table, feature smallScreen for tableSpecs, rename attributes to tableFields, ".
"tableInDetail, tableInRoom, correct enddate/time if is_all_day incl. bugfix API, function SSCal_boolean ".
"to avoid fhem crash if an older JSON module is installed ",
"1.6.1" => "03.02.2020 rename attributes to \"calOverviewInDetail\",\"calOverviewInRoom\", bugfix of gps extraction ",
"1.6.0" => "03.02.2020 new attribute \"tableFields\" to show specified fields in calendar overview in detail/room view, ".
"Model Diary/Tasks defined, periodic call of ToDo-Liists now possible ",
"1.5.0" => "02.02.2020 new attribute \"calOverviewInDetail\",\"calOverviewInRoom\" to control calendar overview in room or detail view ",
"1.4.0" => "02.02.2020 get calAsHtml command or use sub SSCal_calAsHtml(\$name) ",
"1.3.1" => "01.02.2020 add SSCal_errauthlist hash for login/logout API error codes ",
"1.3.0" => "01.02.2020 new command \"cleanCompleteTasks\" to delete completed tasks, \"deleteEventId\" to delete an event id, ".
"new get command \"apiInfo\" - detect and show API info, avoid empty readings ",
"1.2.0" => "29.01.2020 get tasks from calendar with set command 'calToDoList' ",
"1.1.14" => "29.01.2020 ignore calendars of type ne 'Event' for set calEventList ",
"1.1.13" => "20.01.2020 change save and read credentials routine ",
"1.1.12" => "19.01.2020 add attribute interval, automatic event fetch ",
"1.1.11" => "18.01.2020 status information added: upcoming, alarmed, started, ended ",
"1.1.10" => "17.01.2020 attribute asyncMode for parsing events in BlockingCall, some fixes ",
"1.1.9" => "14.01.2020 preparation of asynchronous calendar event extraction, some fixes ",
"1.1.8" => "13.01.2020 can proces WEEKLY general recurring events, use \$data{SSCal}{\$name}{eventlist} as Hash of Events ",
"1.1.7" => "12.01.2020 can proces WEEKLY recurring events BYDAY ",
"1.1.6" => "11.01.2020 can proces DAILY recurring events ",
"1.1.5" => "10.01.2020 can proces MONTHLY recurring events BYDAY ",
"1.1.4" => "07.01.2020 can proces MONTHLY recurring events BYMONTHDAY ",
"1.1.3" => "06.01.2020 can proces YEARLY recurring events ",
"1.1.2" => "04.01.2020 logout if new credentials are set ",
"1.1.1" => "03.01.2020 add array of 'evt_notify_setting' ",
"1.1.0" => "01.01.2020 logout command ",
"1.0.0" => "18.12.2019 initial "
);
# Versions History extern
my %SSCal_vNotesExtern = (
"1.0.0" => "18.12.2019 initial "
);
# Aufbau Errorcode-Hashes
my %SSCal_errauthlist = (
400 => "No such account or the password is incorrect",
401 => "Account disabled",
402 => "Permission denied",
403 => "2-step verification code required",
404 => "Failed to authenticate 2-step verification code",
);
my %SSCal_errlist = (
100 => "Unknown error",
101 => "No parameter of API, method or version",
102 => "The requested API does not exist - may be the Synology Calendar package is stopped",
103 => "The requested method does not exist",
104 => "The requested version does not support the functionality",
105 => "The logged in session does not have permission",
106 => "Session timeout",
107 => "Session interrupted by duplicate login",
114 => "Missing required parameters",
117 => "Unknown internal error",
119 => "session id not valid",
120 => "Invalid parameter",
160 => "Insufficient application privilege",
400 => "Invalid parameter of file operation",
401 => "Unknown error of file operation",
402 => "System is too busy",
403 => "The user does not have permission to execute this operation",
404 => "The group does not have permission to execute this operation",
405 => "The user/group does not have permission to execute this operation",
406 => "Cannot obtain user/group information from the account server",
407 => "Operation not permitted",
408 => "No such file or directory",
409 => "File system not supported",
410 => "Failed to connect internet-based file system (ex: CIFS)",
411 => "Read-only file system",
412 => "Filename too long in the non-encrypted file system",
413 => "Filename too long in the encrypted file system",
414 => "File already exists",
415 => "Disk quota exceeded",
416 => "No space left on device",
417 => "Input/output error",
418 => "Illegal name or path",
419 => "Illegal file name",
420 => "Illegal file name on FAT file system",
421 => "Device or resource busy",
599 => "No such task of the file operation",
800 => "malformed or unsupported URL",
805 => "empty API data received - may be the Synology cal Server package is stopped",
806 => "couldn't get Synology cal API information",
810 => "The credentials couldn't be retrieved",
900 => "malformed JSON string received from Synology Calendar Server",
910 => "Wrong timestamp definition. Check attributes \"cutOlderDays\", \"cutLaterDays\". ",
);
# Standardvariablen und Forward-Deklaration
use vars qw(%SSCal_vHintsExt_en);
use vars qw(%SSCal_vHintsExt_de);
our %SSCal_api;
################################################################
sub SSCal_Initialize($) {
my ($hash) = @_;
$hash->{DefFn} = "SSCal_Define";
$hash->{UndefFn} = "SSCal_Undef";
$hash->{DeleteFn} = "SSCal_Delete";
$hash->{SetFn} = "SSCal_Set";
$hash->{GetFn} = "SSCal_Get";
$hash->{AttrFn} = "SSCal_Attr";
$hash->{DelayedShutdownFn} = "SSCal_DelayedShutdown";
# Darstellung FHEMWEB
# $hash->{FW_summaryFn} = "SSCal_FWsummaryFn";
$hash->{FW_addDetailToSummary} = 1 ; # zusaetzlich zu der Device-Summary auch eine Neue mit dem Inhalt von DetailFn angezeigt
$hash->{FW_detailFn} = "SSCal_FWdetailFn";
$hash->{FW_deviceOverview} = 1;
$hash->{AttrList} = "asyncMode:1,0 ".
"cutOlderDays ".
"cutLaterDays ".
"disable:1,0 ".
"tableSpecs:textField-long ".
"filterCompleteTask:1,2,3 ".
"filterDueTask:1,2,3 ".
"interval ".
"loginRetries:1,2,3,4,5,6,7,8,9,10 ".
"tableColumnMap:icon,data,text ".
"showRepeatEvent:true,false ".
"showPassInLog:1,0 ".
"tableInDetail:0,1 ".
"tableInRoom:0,1 ".
"tableFields:multiple-strict,Begin,End,DaysLeft,DaysLeftLong,Weekday,Timezone,Summary,Description,Status,Completion,Location,Map,Calendar,EventId ".
"timeout ".
"usedCalendars:--wait#for#Calendar#list-- ".
$readingFnAttributes;
eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
return;
}
################################################################
# define SyncalBot SSCal 192.168.2.10 [5000] [HTTP(S)]
# [1] [2] [3] [4]
#
################################################################
sub SSCal_Define($@) {
my ($hash, $def) = @_;
my $name = $hash->{NAME};
return "Error: Perl module ".$SSCalMM." is missing. Install it on Debian with: sudo apt-get install libjson-perl" if($SSCalMM);
my @a = split("[ \t][ \t]*", $def);
if(int(@a) < 2) {
return "You need to specify more parameters.\n". "Format: define SSCal [Port] [HTTP(S)] [Tasks]";
}
shift @a; shift @a;
my $addr = $a[0] if($a[0] ne "Tasks");
my $port = ($a[1] && $a[1] ne "Tasks") ? $a[1] : 5000;
my $prot = ($a[2] && $a[2] ne "Tasks") ? lc($a[2]) : "http";
my $model = "Diary";
$model = "Tasks" if( grep {$_ eq "Tasks"} @a );
$hash->{ADDR} = $addr;
$hash->{PORT} = $port;
$hash->{MODEL} = "Calendar";
$hash->{PROT} = $prot;
$hash->{MODEL} = $model;
$hash->{RESEND} = "next planned SendQueue start: immediately by next entry";
$hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden
$hash->{HELPER}{CALFETCHED} = 0; # vorhandene Kalender sind noch nicht abgerufen
$hash->{HELPER}{APIPARSET} = 0; # es sind keine API Informationen gesetzt -> neu abrufen
CommandAttr(undef,"$name room SSCal");
CommandAttr(undef,"$name event-on-update-reading .*Summary.*,state");
%SSCal_api = (
"APIINFO" => { "NAME" => "SYNO.API.Info" }, # Info-Seite für alle API's, einzige statische Seite !
"APIAUTH" => { "NAME" => "SYNO.API.Auth" }, # API used to perform session login and logout
"CALCAL" => { "NAME" => "SYNO.Cal.Cal" }, # API to manipulate calendar
"CALEVENT" => { "NAME" => "SYNO.Cal.Event" }, # Provide methods to manipulate events in the specific calendar
"CALSHARE" => { "NAME" => "SYNO.Cal.Sharing" }, # Get/set sharing setting of calendar
"CALTODO" => { "NAME" => "SYNO.Cal.Todo" }, # Provide methods to manipulate events in the specific calendar
);
# Versionsinformationen setzen
SSCal_setVersionInfo($hash);
# Credentials lesen
SSCal_getcredentials($hash,1,"credentials");
# Index der Sendequeue initialisieren
$data{SSCal}{$name}{sendqueue}{index} = 0;
readingsBeginUpdate ($hash);
readingsBulkUpdateIfChanged ($hash, "Errorcode", "none");
readingsBulkUpdateIfChanged ($hash, "Error", "none");
readingsBulkUpdateIfChanged ($hash, "QueueLenth", 0); # Länge Sendqueue initialisieren
readingsBulkUpdate ($hash, "nextUpdate", "Manual"); # Abrufmode initial auf "Manual" setzen
readingsBulkUpdate ($hash, "state", "Initialized"); # Init state
readingsEndUpdate ($hash,1);
# initiale Routinen nach Start ausführen , verzögerter zufälliger Start
SSCal_initonboot($name);
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 SSCal_Undef($$) {
my ($hash, $arg) = @_;
my $name = $hash->{NAME};
BlockingKill($hash->{HELPER}{RUNNING_PID}) if($hash->{HELPER}{RUNNING_PID});
delete $data{SSCal}{$name};
RemoveInternalTimer($name);
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 SSCal_DelayedShutdown($) {
my ($hash) = @_;
my $name = $hash->{NAME};
if($hash->{HELPER}{SID}) {
SSCal_logout($hash); # Session alter User beenden falls vorhanden
return 1;
}
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 SSCal_Delete($$) {
my ($hash, $arg) = @_;
my $name = $hash->{NAME};
my $index = $hash->{TYPE}."_".$hash->{NAME}."_credentials";
# gespeicherte Credentials löschen
setKeyValue($index, undef);
return undef;
}
################################################################
sub SSCal_Attr($$$$) {
my ($cmd,$name,$aName,$aVal) = @_;
my $hash = $defs{$name};
my $model = $hash->{MODEL};
my ($do,$val,$cache);
# $cmd can be "del" or "set"
# $name is device name
# aName and aVal are Attribute name and value
if ($cmd eq "set") {
my $attrVal = $aVal;
if ($aName =~ /filterCompleteTask|filterDueTask/ && $model ne "Tasks") {
return " The attribute \"$aName\" is only valid for devices of MODEL \"Tasks\"! Please set this attribute in a device of this model.";
}
if ($aName =~ /showRepeatEvent/ && $model ne "Diary") {
return " The attribute \"$aName\" is only valid for devices of MODEL \"Diary\"! Please set this attribute in a device of this model.";
}
if ($attrVal =~ m/^\{.*\}$/s && $attrVal =~ m/=>/) {
$attrVal =~ s/\@/\\\@/g;
$attrVal =~ s/\$/\\\$/g;
my $av = eval $attrVal;
if($@) {
Log3($name, 2, "$name - Error while evaluate: ".$@);
return $@;
} else {
$attrVal = $av if(ref($av) eq "HASH");
}
}
$hash->{HELPER}{$aName} = $attrVal;
} else {
delete $hash->{HELPER}{$aName};
}
if ($aName eq "disable") {
if($cmd eq "set") {
$do = $aVal?1:0;
}
$do = 0 if($cmd eq "del");
$val = ($do == 1 ? "disabled" : "initialized");
if ($do == 1) {
RemoveInternalTimer($name);
} else {
InternalTimer(gettimeofday()+2, "SSCal_initonboot", $name, 0) if($init_done);
}
readingsBeginUpdate($hash);
readingsBulkUpdate ($hash, "state", $val);
readingsEndUpdate ($hash,1);
}
if ($cmd eq "set") {
if ($aName =~ m/timeout|cutLaterDays|cutOlderDays|interval/) {
unless ($aVal =~ /^\d+$/) { return "The Value for $aName is not valid. Use only figures 1-9 !";}
}
if($aName =~ m/interval/) {
RemoveInternalTimer($name,"SSCal_periodicCall");
if($aVal > 0) {
InternalTimer(gettimeofday()+1.0, "SSCal_periodicCall", $name, 0);
}
}
}
return undef;
}
################################################################
sub SSCal_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 $model = $hash->{MODEL};
my ($success,$setlist);
return if(IsDisabled($name));
my $idxlist = join(",", SSCal_sortVersion("asc",keys %{$data{SSCal}{$name}{sendqueue}{entries}}));
# alle aktuell angezeigten Event Id's ermitteln
my (@idarray,$evids);
foreach my $key (keys %{$defs{$name}{READINGS}}) {
next if $key !~ /^.*_EventId$/;
push (@idarray, $defs{$name}{READINGS}{$key}{VAL});
}
if(@idarray) {
my %seen;
my @unique = sort{$a<=>$b} grep { !$seen{$_}++ } @idarray; # distinct / unique the keys
$evids = join(",", @unique);
}
if(!$hash->{CREDENTIALS}) {
# initiale setlist für neue Devices
$setlist = "Unknown argument $opt, choose one of ".
"credentials "
;
} elsif ($model eq "Diary") {
$setlist = "Unknown argument $opt, choose one of ".
"calEventList ".
"credentials ".
($evids?"deleteEventId:$evids ":"deleteEventId:noArg ").
"eraseReadings:noArg ".
"listSendqueue:noArg ".
"logout:noArg ".
($idxlist?"purgeSendqueue:-all-,-permError-,$idxlist ":"purgeSendqueue:-all-,-permError- ").
"restartSendqueue:noArg "
;
} else { # Model ist "Tasks"
$setlist = "Unknown argument $opt, choose one of ".
"calToDoList ".
"cleanCompleteTasks:noArg ".
"credentials ".
($evids?"deleteEventId:$evids ":"deleteEventId:noArg ").
"eraseReadings:noArg ".
"listSendqueue:noArg ".
"logout:noArg ".
($idxlist?"purgeSendqueue:-all-,-permError-,$idxlist ":"purgeSendqueue:-all-,-permError- ").
"restartSendqueue:noArg "
;
}
if ($opt eq "credentials") {
return "The command \"$opt\" needs an argument." if (!$prop);
SSCal_logout($hash) if($hash->{HELPER}{SID}); # Session alter User beenden falls vorhanden
($success) = SSCal_setcredentials($hash,$prop,$prop1);
if($success) {
SSCal_addQueue($name,"listcal","CALCAL","list","&is_todo=true&is_evt=true");
SSCal_getapisites($name);
return "credentials saved successfully";
} else {
return "Error while saving credentials - see logfile for details";
}
} elsif ($opt eq "listSendqueue") {
my $sub = sub ($) {
my ($idx) = @_;
my $ret;
foreach my $key (reverse sort keys %{$data{SSCal}{$name}{sendqueue}{entries}{$idx}}) {
$ret .= ", " if($ret);
$ret .= $key."=>".$data{SSCal}{$name}{sendqueue}{entries}{$idx}{$key};
}
return $ret;
};
if (!keys %{$data{SSCal}{$name}{sendqueue}{entries}}) {
return "SendQueue is empty.";
}
my $sq;
foreach my $idx (sort{$a<=>$b} keys %{$data{SSCal}{$name}{sendqueue}{entries}}) {
$sq .= $idx." => ".$sub->($idx)."\n";
}
return $sq;
} elsif ($opt eq "purgeSendqueue") {
if($prop eq "-all-") {
delete $hash->{OPIDX};
delete $data{SSCal}{$name}{sendqueue}{entries};
$data{SSCal}{$name}{sendqueue}{index} = 0;
return "All entries of SendQueue are deleted";
} elsif($prop eq "-permError-") {
foreach my $idx (keys %{$data{SSCal}{$name}{sendqueue}{entries}}) {
delete $data{SSCal}{$name}{sendqueue}{entries}{$idx}
if($data{SSCal}{$name}{sendqueue}{entries}{$idx}{forbidSend});
}
return "All entries with state \"permanent send error\" are deleted";
} else {
delete $data{SSCal}{$name}{sendqueue}{entries}{$prop};
return "SendQueue entry with index \"$prop\" deleted";
}
} elsif ($opt eq "calEventList") { # Termine einer Cal_id (Liste) in Zeitgrenzen abrufen
return "Obtain the Calendar list first with \"get $name getCalendars\" command." if(!$hash->{HELPER}{CALFETCHED});
my ($err,$tstart,$tend) = SSCal_timeEdge ($name);
if($err) {
Log3($name, 2, "$name - ERROR in timestamp: $err");
my $errorcode = "910";
readingsBeginUpdate ($hash);
readingsBulkUpdateIfChanged ($hash, "Error", $err);
readingsBulkUpdateIfChanged ($hash, "Errorcode", $errorcode);
readingsBulkUpdate ($hash, "state", "Error");
readingsEndUpdate ($hash,1);
return "ERROR in timestamp: $err";
}
my $cals = AttrVal($name,"usedCalendars", "");
shift @a; shift @a;
my $c = join(" ", @a);
$cals = $c?$c:$cals;
return "Please set attribute \"usedCalendars\" or specify the Calendar(s) you want read in \"$opt\" command." if(!$cals);
# Kalender aufsplitten und zu jedem die ID ermitteln
my @ca = split(",", $cals);
my $oids;
foreach (@ca) {
my $oid = $hash->{HELPER}{CALENDARS}{"$_"}{id};
next if(!$oid);
if ($hash->{HELPER}{CALENDARS}{"$_"}{type} ne "Event") {
Log3($name, 3, "$name - The Calendar \"$_\" is not of type \"Event\" and will be ignored.");
next;
}
$oids .= "," if($oids);
$oids .= '"'.$oid.'"';
Log3($name, 2, "$name - WARNING - The Calendar \"$_\" seems to be unknown because its ID couldn't be found.") if(!$oid);
}
return "No Calendar of type \"Event\" was selected or its ID(s) couldn't be found." if(!$oids);
Log3($name, 5, "$name - Calendar selection for add queue: $cals");
my $lr = AttrVal($name,"showRepeatEvent", "true");
SSCal_addQueue($name,"eventlist","CALEVENT","list","&cal_id_list=[$oids]&start=$tstart&end=$tend&list_repeat=$lr");
SSCal_getapisites($name);
} elsif ($opt eq "calToDoList") { # Aufgaben einer Cal_id (Liste) abrufen
return "Obtain the Calendar list first with \"get $name getCalendars\" command." if(!$hash->{HELPER}{CALFETCHED});
my $cals = AttrVal($name,"usedCalendars", "");
shift @a; shift @a;
my $c = join(" ", @a);
$cals = $c?$c:$cals;
return "Please set attribute \"usedCalendars\" or specify the Calendar(s) you want read in \"$opt\" command." if(!$cals);
# Kalender aufsplitten und zu jedem die ID ermitteln
my @ca = split(",", $cals);
my $oids;
foreach (@ca) {
my $oid = $hash->{HELPER}{CALENDARS}{"$_"}{id};
next if(!$oid);
if ($hash->{HELPER}{CALENDARS}{"$_"}{type} ne "ToDo") {
Log3($name, 3, "$name - The Calendar \"$_\" is not of type \"ToDo\" and will be ignored.");
next;
}
$oids .= "," if($oids);
$oids .= '"'.$oid.'"';
Log3($name, 2, "$name - WARNING - The Calendar \"$_\" seems to be unknown because its ID couldn't be found.") if(!$oid);
}
return "No Calendar of type \"ToDo\" was selected or its ID(s) couldn't be found." if(!$oids);
Log3($name, 5, "$name - Calendar selection for add queue: $cals");
my $limit = ""; # Limit of matched tasks
my $offset = 0; # offset of mnatched tasks
my $filterdue = AttrVal($name,"filterDueTask", 3); # show tasks with and without due time
my $filtercomplete = AttrVal($name,"filterCompleteTask", 3); # show completed and not completed tasks
SSCal_addQueue($name,"todolist","CALTODO","list","&cal_id_list=[$oids]&limit=$limit&offset=$offset&filter_due=$filterdue&filter_complete=$filtercomplete");
SSCal_getapisites($name);
} elsif ($opt eq "cleanCompleteTasks") { # erledigte Aufgaben einer Cal_id (Liste) löschen
return "Obtain the Calendar list first with \"get $name getCalendars\" command." if(!$hash->{HELPER}{CALFETCHED});
my $cals = AttrVal($name,"usedCalendars", "");
shift @a; shift @a;
my $c = join(" ", @a);
$cals = $c?$c:$cals;
return "Please set attribute \"usedCalendars\" or specify the Calendar(s) you want read in \"$opt\" command." if(!$cals);
# Kalender aufsplitten und zu jedem die ID ermitteln
my @ca = split(",", $cals);
my $oids;
foreach (@ca) {
my $oid = $hash->{HELPER}{CALENDARS}{"$_"}{id};
next if(!$oid);
if ($hash->{HELPER}{CALENDARS}{"$_"}{type} ne "ToDo") {
Log3($name, 3, "$name - The Calendar \"$_\" is not of type \"ToDo\" and will be ignored.");
next;
}
$oids .= "," if($oids);
$oids .= '"'.$oid.'"';
Log3($name, 2, "$name - WARNING - The Calendar \"$_\" seems to be unknown because its ID couldn't be found.") if(!$oid);
}
return "No Calendar of type \"ToDo\" was selected or its ID(s) couldn't be found." if(!$oids);
Log3($name, 5, "$name - Calendar selection for add queue: $cals");
#
SSCal_addQueue($name,"cleanCompleteTasks","CALTODO","clean_complete","&cal_id_list=[$oids]");
SSCal_getapisites($name);
} elsif ($opt eq "deleteEventId") {
return "You must specify an event id (Reading EventId) what is to be deleted." if(!$prop);
my $eventid = $prop;
# Blocknummer ermitteln
my $bnr;
my @allrds = keys%{$defs{$name}{READINGS}};
foreach my $key(@allrds) {
next if $key !~ /^.*_EventId$/;
$bnr = (split("_", $key))[0] if($defs{$name}{READINGS}{$key}{VAL} == $eventid); # Blocknummer ermittelt
}
return "The blocknumber of specified event id could not be identified. Make sure you have specified a valid event id." if(!$bnr);
# die Summary zur Event Id ermitteln
my $sum = ReadingsVal($name, $bnr."_01_Summary", "");
# Kalendername und dessen id und Typ ermitteln
my $calname = ReadingsVal($name, $bnr."_90_calName", "");
my $calid = $hash->{HELPER}{CALENDARS}{"$calname"}{id};
my $caltype = $hash->{HELPER}{CALENDARS}{"$calname"}{type};
# Kalender-API in Abhängigkeit des Kalendertyps wählen
my $api = ($caltype eq "Event")?"CALEVENT":"CALTODO";
Log3($name, 3, "$name - The event \"$sum\" with id \"$eventid\" will be deleted in calendar \"$calname\".");
#
SSCal_addQueue($name,"deleteEventId",$api,"delete","&evt_id=$eventid");
SSCal_getapisites($name);
} elsif ($opt eq "restartSendqueue") {
my $ret = SSCal_getapisites($name);
if($ret) {
return $ret;
} else {
return "The SendQueue has been restarted.";
}
} elsif ($opt eq 'eraseReadings') {
SSCal_delReadings($name,0); # Readings löschen
} elsif ($opt eq 'logout') {
SSCal_logout($hash);
} else {
return "$setlist";
}
return;
}
################################################################
sub SSCal_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->{CREDENTIALS}) {
return;
} else {
$getlist = "Unknown argument $opt, choose one of ".
"apiInfo:noArg ".
"calAsHtml:noArg ".
"getCalendars:noArg ".
"storedCredentials:noArg ".
"versionNotes "
;
}
return if(IsDisabled($name));
if ($opt eq "storedCredentials") {
if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials \"";}
# Credentials abrufen
my ($success, $username, $passwd) = SSCal_getcredentials($hash,0,"credentials");
unless ($success) {return "Credentials couldn't be retrieved successfully - see logfile"};
return "Stored Credentials:\n".
"===================\n".
"Username: $username, Password: $passwd \n"
;
} elsif ($opt eq "apiInfo") { # Liste aller Kalender abrufen
# übergebenen CL-Hash (FHEMWEB) in Helper eintragen
SSCal_getclhash($hash,1);
$hash->{HELPER}{APIPARSET} = 0; # Abruf API Infos erzwingen
#
SSCal_addQueue($name,"apiInfo","","","");
SSCal_getapisites($name);
} elsif ($opt eq "getCalendars") { # Liste aller Kalender abrufen
# übergebenen CL-Hash (FHEMWEB) in Helper eintragen
SSCal_getclhash($hash,1);
SSCal_addQueue($name,"listcal","CALCAL","list","&is_todo=true&is_evt=true");
SSCal_getapisites($name);
} elsif ($opt eq "calAsHtml") {
my $out = SSCal_calAsHtml($name);
return $out;
} 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("