From a258fe3391c1d55fa7c0ec807bea969ebbf1c381 Mon Sep 17 00:00:00 2001 From: marvin78 Date: Wed, 5 Feb 2025 11:33:02 +0000 Subject: [PATCH] 98_todoist.pm: delete accidentally uploaded file git-svn-id: https://svn.fhem.de/fhem/trunk@29623 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/98_todoist.pm | 2792 -------------------------------------------- 1 file changed, 2792 deletions(-) delete mode 100644 fhem/98_todoist.pm diff --git a/fhem/98_todoist.pm b/fhem/98_todoist.pm deleted file mode 100644 index e599b3138..000000000 --- a/fhem/98_todoist.pm +++ /dev/null @@ -1,2792 +0,0 @@ -############################################## -# $Id: 98_todoist.pm 26953 2023-01-03 12:58:56Z marvin78 $ - - -package main; - -use strict; -use warnings; - -my $missingModule = ""; - -eval "use Data::Dumper;1" or $missingModule .= "Data::Dumper "; -eval "use JSON;1" or $missingModule .= "JSON "; -eval "use Encode;1" or $missingModule .= "Encode "; -eval "use Date::Parse;1" or $missingModule .= "Date::Parse "; - - -####################### -# Global variables -my $version = "1.3.28"; -my $apiUrl = "https://api.todoist.com/sync/v9/"; - -my $srandUsed; - -my %gets = ( - "version:noArg" => "", -); - -## define variables for multi language -my %todoist_transtable_EN = ( - "check" => "Check", - "delete" => "Delete", - "refreshList" => "Refresh list", - "clearList" => "Delete all elements", - "alreadythere" => "is already on the list", - "error" => "Error", - "clearconfirm" => "Are you sure? This deletes ALL the task in this list permanently.", - "delconfirm" => "Are you sure? This deletes the task permanently.", - "gotodetail" => "Click to show detail page of todoist device", - "newentry" => "Add new task to list", - "nolistdata" => "No data for this list", - "idnotfound" => "Could not find the task", - "today" => "today", - "tomorrow" => "tomorrow", - "dayaftertomorrow" => "the day after tomorrow", - "yesterday" => "yesterday", -); - -my %todoist_transtable_DE = ( - "check" => "Erledigen", - "delete" => "Löschen", - "refreshList" => "Liste aktualisieren", - "clearList" => "Alle Elemente löschen", - "alreadythere" => "befindet sich bereits auf der Liste", - "error" => "Fehler", - "clearconfirm" => "Wirklich alle Elemente löschen?", - "delconfirm" => "Task wirklich löschen?", - "gotodetail" => "Detailseite des todoist-Devices aufrufen", - "newentry" => "Neuen Eintrag zur Liste hinzufügen", - "nolistdata" => "List ist leer", - "idnotfound" => "Task konnte nicht gefundern werden", - "today" => "heute", - "tomorrow" => "morgen", - "dayaftertomorrow" => "übermorgen", - "yesterday" => "gestern", -); - -my $todoist_tt; - -sub todoist_Initialize($) { - my ($hash) = @_; - - $hash->{SetFn} = "todoist_Set"; - $hash->{GetFn} = "todoist_Get"; - $hash->{DefFn} = "todoist_Define"; - $hash->{UndefFn} = "todoist_Undefine"; - $hash->{AttrFn} = "todoist_Attr"; - $hash->{RenameFn} = "todoist_Rename"; - $hash->{CopyFn} = "todoist_Copy"; - $hash->{DeleteFn} = "todoist_Delete"; - $hash->{NotifyFn} = "todoist_Notify"; - - $hash->{FW_detailFn} = "todoist_detailFn"; - - $hash->{AttrList} = "disable:1,0 ". - "pollInterval ". - "do_not_notify ". - "getCompleted:1,0 ". - "showPriority:1,0 ". - "showAssignedBy:1,0 ". - "showResponsible:1,0 ". - "showParent:1,0 ". - "showSection:1,0 ". - "showChecked:1,0 ". - "showDeleted:1,0 ". - "showOrder:1,0 ". - "hideId:1,0 ". - "autoGetUsers:1,0 ". - "avoidDuplicates:1,0 ". - "listDivider ". - "showDetailWidget:1,0 ". - "hideListIfEmpty:1,0 ". - "delDeletedLists:1,0 ". - "language:EN,DE ". - "sslVersion ". - $readingFnAttributes; - - $hash->{NotifyOrderPrefix} = "64-"; - - ## renew version and language in reload - foreach my $d ( sort keys %{ $modules{todoist}{defptr} } ) { - my $hash = $modules{todoist}{defptr}{$d}; - $hash->{VERSION} = $version; - - my $lang = AttrVal($hash->{NAME},"language", AttrVal("global","language","EN")); - if( $lang eq "DE") { - $todoist_tt = \%todoist_transtable_DE; - } - else{ - $todoist_tt = \%todoist_transtable_EN; - } - } - - return undef; - #return FHEM::Meta::InitMod( __FILE__, $hash ); -} - -sub todoist_Define($$) { - my ($hash, $def) = @_; - my $now = time(); - my $name = $hash->{NAME}; - - if( !defined($todoist_tt) ){ - # in any attribute redefinition readjust language - my $lang = AttrVal($name,"language", AttrVal("global","language","EN")); - if( $lang eq "DE") { - $todoist_tt = \%todoist_transtable_DE; - } - else{ - $todoist_tt = \%todoist_transtable_EN; - } - } - - - my @a = split( "[ \t][ \t]*", $def ); - - if ( int(@a) < 2 ) { - my $msg = "Wrong syntax: define todoist "; - Log3 $name, 4, $msg; - return $msg; - } - - return "Cannot define a todoist device. Perl module(s) $missingModule is/are missing." if ( $missingModule ); - - ## set internal variables - $hash->{PID}=$a[2]; - $hash->{INTERVAL}=AttrVal($name,"pollInterval",undef)?AttrVal($name,"pollInterval",undef):1800; - $hash->{VERSION}=$version; - $hash->{MID} = 'todoist_'.$a[2]; # - - $modules{todoist}{defptr}{ $hash->{MID} } = $hash; #MID for internal purposes - - ## check if Access Token is needed - my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; - my ($err, $password) = getKeyValue($index); - - $hash->{helper}{PWD_NEEDED}=1 if ($err || !$password); - - $hash->{NOTIFYDEV}= "global"; - - if ($init_done) { - ## at first, we delete old readings. List could have changed - CommandDeleteReading(undef, "$hash->{NAME} (T|t)ask_.*"); - CommandDeleteReading(undef, "$hash->{NAME} listText"); - ## set state - readingsSingleUpdate($hash,"state","active",1) if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name) ); - readingsSingleUpdate($hash,"state","inactive",1) if ($hash->{helper}{PWD_NEEDED} || ReadingsVal($name,"state","-") eq "-"); - ## remove timers - RemoveInternalTimer($hash,"todoist_GetTasks"); - ## start polling - todoist_GetTasks($hash) if (!IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}); - } - - return undef; -} - -# get token from file -sub todoist_GetPwd($) { - my ($hash) = @_; - - my $name=$hash->{NAME}; - - my $pwd=""; - - my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; - my $key = getUniqueId().$index; - - my ($err, $password) = getKeyValue($index); - - if ($err) { - $hash->{helper}{PWD_NEEDED} = 1; - Log3 $name, 4, "todoist ($name): unable to read password from file: $err"; - return undef; - } - - #some decryption - if ( defined($password) ) { - if ( eval "use Digest::MD5;1" ) { - $key = Digest::MD5::md5_hex(unpack "H*", $key); - $key .= Digest::MD5::md5_hex($key); - } - - for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { - my $decode=chop($key); - $pwd.=chr(ord($char)^ord($decode)); - $key=$decode.$key; - } - } - - return undef if ($pwd eq ""); - - return $pwd; -} - -## set error Readings and log -sub todoist_ErrorReadings($;$$) { - my ($hash,$errorLog,$errorMessage) = @_; - - my $level=2; - - if (!defined($errorLog)) { - $level=3; - $errorLog="no data"; - } - $errorMessage="no data" if (!defined($errorMessage)); - - if (defined($hash->{helper}{errorData}) && $hash->{helper}{errorData} ne "") { - $errorLog=$hash->{helper}{errorData}; - } - - if (defined($hash->{helper}{errorMessage}) && $hash->{helper}{errorMessage} ne "") { - $errorMessage=$hash->{helper}{errorMessage}; - } - - my $name = $hash->{NAME}; - - readingsBeginUpdate( $hash ); - readingsBulkUpdate( $hash,"error",$errorMessage ); - readingsBulkUpdate( $hash,"lastError",$errorMessage ); - readingsEndUpdate( $hash, 1 ); - - Log3 $name,$level, "todoist ($name): Error Message: ".$errorMessage if ($errorMessage ne "no data"); - Log3 $name,4, "todoist ($name): Api-Error Callback-data: ".$errorLog; - - $hash->{helper}{errorData}=""; - $hash->{helper}{errorMessage}=""; - return undef; -} - - -# reorderTasks -sub todoist_ReorderTasks ($$) { - my ($hash,$cmd) = @_; - - my $name=$hash->{NAME}; - - Log3 $name,4, "$name: cmd: ".$cmd; - - my $pwd=""; - - my $param; - - my %commands=(); - - # some random string for UUID - my $uuid = todoist_genUUID(); - - # JSON String start- and endpoint - my $commandsStart="[{"; - my $commandsEnd="}]"; - - my $tType; - - my $argsStart = "{\"items\": ["; - my $argsEnd = "]}"; - - my $args = ""; - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - if ($pwd) { - Log3 $name,5, "$name: hash: ".Dumper($hash); - - # get Task - IDs in order - my $tids = $cmd; - my @taskIds = split(",",$tids); - - $tType = "item_reorder"; - - my $i=0; - - foreach my $taskId (@taskIds) { - $i++; - $args .= "," if ($i>1); - $args .= "{\"id\":".$taskId.",\"child_order\":".$i."}"; - } - - $args = $argsStart.$args.$argsEnd; - - Log3 $name,5, "todoist ($name): Data sent to todoist API: ".$args; - - my $dataArr=$commandsStart.'"type":"'.$tType.'","uuid":"'.$uuid.'","args":'.$args.$commandsEnd; - - Log3 $name,4, "todoist ($name): Data Array sent to todoist API: ".$dataArr; - - my $data= { - token => $pwd, - commands => $dataArr - }; - - Log3 $name,4, "todoist ($name): JSON sent to todoist API: ".Dumper($data); - - my $method="POST"; - - $param = { - url => $apiUrl."sync", - data => $data, - method => $method, - wType => "reorder", - timeout => 7, - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - hash => $hash, - callback => \&todoist_HandleTaskCallback, ## call callback sub to work with the data we get - }; - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+0.1, "HttpUtils_NonblockingGet", $param, 0); - - - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - - } - - - - return undef; -} - -# update Task -sub todoist_UpdateTask($$$) { - my ($hash,$cmd, $type) = @_; - - my($a, $h) = parseParams($cmd); - - my $name=$hash->{NAME}; - - Log3 $name,5, "$name: Type: ".Dumper($type); - - my $param; - - my $pwd=""; - - my %commands=(); - - my $method; - my $taskId=0; - my $title; - my $tid; - - ## get Task-ID - $tid = @$a[0]; - - - ## check if ID is todoist ID (ID:.*) or title (TITLE:.*) - my @temp=split(":",$tid); - - - ## use the todoist ID - if (@temp && $temp[0] =~ /id/i) { - $taskId = int($temp[1]); - $title = $hash->{helper}{"TITLE"}{$temp[1]}; - } - ## use task content - elsif (@temp && $temp[0] =~ /title/i) { - $title = encode_utf8($temp[1]); - my $nTitle = $title; - $nTitle =~ s/ /_/g; - $taskId = $hash->{helper}{"TITLES"}{$nTitle} if ($hash->{helper}{"TITLES"}); - } - elsif (defined($h->{"title"}) || defined($h->{"TITLE"}) || defined($h->{"Title"})) { - Log3 $name, 5, "todoist ($name): Debug: ".Dumper($h); - $title = $h->{"title"} if ($h->{"title"}); - $title = $h->{"TITLE"} if ($h->{"TITLE"}); - $title = $h->{"Title"} if ($h->{"Title"}); - my $nTitle = $title; - $nTitle =~ s/ /_/g; - Log3 $name, 5, "todoist ($name): Debug: ".$nTitle; - $taskId = $hash->{helper}{"TITLES"}{$nTitle} if ($hash->{helper}{"TITLES"}); - } - ## use Task-Number - else { - $tid=int($tid); - $taskId=$hash->{helper}{"IDS"}{"Task_".$tid}; - $title=ReadingsVal($name,"Task_".sprintf('%03d',$tid),"-"); - } - - # error if we did not get a task id - if ($taskId == 0) { - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_ErrorDialog === \"function\") todoist_ErrorDialog('$name','$title ".$todoist_tt->{"idnotfound"}."','".$todoist_tt->{"error"}."')", "")} devspec2array("TYPE=FHEMWEB"); - todoist_ErrorReadings($hash,"Task $title could not be found for $type","task $title not found"); - return undef; - } - - # some random string for UUID - my $uuid = todoist_genUUID(); - - # JSON String start- and endpoint - my $commandsStart="[{"; - - my $commandsEnd="}]"; - - my $tType; - my %args=(); - - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - if ($pwd) { - Log3 $name,5, "$name: hash: ".Dumper($hash); - - ## complete a task - if ($type eq "complete") { - - # variables for the commands parameter - $tType = "item_complete"; - %args = ( - id => $taskId, - ); - Log3 $name,5, "$name: Args: ".Dumper(%args); - $method="POST"; - } - ## close a task - elsif ($type eq "close") { - - # variables for the commands parameter - $tType = "item_close"; - %args = ( - id => $taskId, - ); - Log3 $name,5, "$name: Args: ".Dumper(%args); - $method="POST"; - } - ## uncomplete a task - elsif ($type eq "uncomplete") { - - # variables for the commands parameter - $tType = "item_uncomplete"; - %args = ( - id => $taskId, - ); - Log3 $name,5, "$name: Args: ".Dumper(%args); - $method="POST"; - } - ## move a task - elsif ($type eq "move") { - - - # we can avoid duplicates in FHEM. There may still come duplicates coming from another app - if ($h->{"parent_id"}) { - #if (AttrVal($name,"avoidDuplicates",0) == 1 && todoist_inArray(\@{$hash->{helper}{"TITS"}},$title)) { - # map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_ErrorDialog === \"function\") todoist_ErrorDialog('$name','$title ".$todoist_tt->{"alreadythere"}."','".$todoist_tt->{"error"}."')", "")} devspec2array("TYPE=FHEMWEB"); - # todoist_ErrorReadings($hash,"duplicate detected","duplicate detected"); - # return undef; - #} - } - - $tType = "item_move"; - %args = ( - id => $taskId, - ); - ## parent_id - $args{'parent_id'} = $h->{"parent_id"} if ($h->{"parent_id"}); - $args{'parent_id'} = $h->{"parentID"} if ($h->{"parentID"}); - $args{'parent_id'} = $h->{"parentId"} if ($h->{"parentId"}); - - ## project_id - $args{'project_id'} = $h->{"project_id"} if ($h->{"project_id"}); - $args{'project_id'} = $h->{"projectID"} if ($h->{"projectID"}); - $args{'project_id'} = $h->{"projectId"} if ($h->{"projectId"}); - - ## section_id - $args{'section_id'} = $h->{"section_id"} if ($h->{"section_id"}); - $args{'section_id'} = $h->{"sectionID"} if ($h->{"sectionID"}); - $args{'section_id'} = $h->{"sectionId"} if ($h->{"sectionId"}); - - if ($args{'parent_id'}) { - my $pid=$args{'parent_id'}; - } - } - ## update a task - elsif ($type eq "update") { - $tType = "item_update"; - %args = ( - id => $taskId, - ); - - ## change title - $args{'content'} = $h->{"title"} if($h->{'title'}); - ## change dueDate - $args{'due'}{'string'} = $h->{"dueDate"} if($h->{'dueDate'}); - $args{'due'}{'string'} = "" if ($h->{'dueDate'} && $h->{'dueDate'} =~ /(null|none|nix|leer|del)/); - ## change dueDate (if someone uses due_date in stead of dueDate) - $args{'due'}{'string'} = $h->{"due_date"} if ($h->{'due_date'}); - $args{'due'}{'string'} = "" if ($h->{'due_date'} && $h->{'due_date'} =~ /(null|none|nix|leer|del)/); - ## change priority - $args{'priority'} = int($h->{"priority"}) if ($h->{"priority"}); - ## Who is responsible for the task - $args{'responsible_uid'} = $h->{"responsibleUid"} if ($h->{"responsibleUid"}); - $args{'responsible_uid'} = $h->{"responsible"} if ($h->{"responsible"}); - ## who assigned the task? - $args{'assigned_by_uid'} = $h->{"assignedByUid"} if ($h->{"assignedByUid"}); - $args{'assigned_by_uid'} = $h->{"assignedBy"} if ($h->{"assignedByUid"}); - ## order of the task - $args{'child_order'} = $h->{"order"} if ($h->{"order"}); - ## child order of the task - $args{'child_order'} = $h->{"child_order"} if ($h->{"child_order"}); - - - ## remove attribute - if ($h->{"remove"}) { - my @temp; - my @rem = split(",",$h->{"remove"}); - foreach my $r (@rem) { - $args{'due'} = "" if ($r eq "dueDate" || $r eq "due_date"); - $args{'responsible_uid'} = "" if ($r eq "responsibleUid" || $r eq "responsible"); - $args{'assigned_by_uid'} = 0 if ($r eq "assignedByUid" || $r eq "assignedBy"); - if ($r eq "parent_id" || $r eq "parentID" || $r eq "parentId" || $r eq "child_order") { - $args{'child_order'} = 1; - $args{'parent_id'} = ""; - } - } - ## Debug - #Log3 $name, 1, "wunderlist ($name): Debug: ".Dumper($datas{'remove'}); - } - - ## Debug - #Log3 $name, 1, "todoist ($name): Debug: ".Dumper(%datas); - - $method="POST"; - } - ## delete a task - elsif ($type eq "delete") { - $tType = "item_delete"; - %args = ( - id => $taskId, - ); - $method="POST"; - } - else { - return undef; - } - - Log3 $name,5, "todoist ($name): Data Array sent to todoist API: ".Dumper(%args); - - my $dataArr=$commandsStart.'"type":"'.$tType.'","temp_id":"'.$taskId.'","uuid":"'.$uuid.'","args":'.encode_json(\%args).$commandsEnd; - - Log3 $name,4, "todoist ($name): Data Array sent to todoist API: ".$dataArr; - - my $data= { - token => $pwd, - commands => $dataArr - }; - - Log3 $name,4, "todoist ($name): JSON sent to todoist API: ".Dumper($data); - - $param = { - url => $apiUrl."sync", - data => $data, - tTitle => $title, - method => $method, - wType => $type, - taskId => $taskId, - timeout => 7, - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - hash => $hash, - callback => \&todoist_HandleTaskCallback, ## call callback sub to work with the data we get - }; - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+0.1, "HttpUtils_NonblockingGet", $param, 0); - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - - } - - return undef; -} - -# create Task -sub todoist_CreateTask($$) { - my ($hash,$cmd) = @_; - - my($a, $h) = parseParams($cmd); - - my $name=$hash->{NAME}; - - my $param; - - my $pwd=""; - - my $assigne_id=""; - - ## we try to send a due_date (in developement) - my @tmp = split( ":", join(" ",@$a) ); - - my $titleS=$tmp[0]; - - $titleS = $h->{"title"} if ($h->{"title"}); - - my $title = encode_utf8($titleS); - - my $check=1; - - # we can avoid duplicates in FHEM. There may still come duplicates coming from another app - if (AttrVal($name,"avoidDuplicates",0) == 1 && todoist_inArray(\@{$hash->{helper}{"TITS"}},$title)) { - $check=-1; - } - - if ($check==1) { - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - if ($pwd) { - - # JSON String start- and endpoint - my $commandsStart="[{"; - - my $commandsEnd="}]"; - - # some random string for UUID - my $uuid = todoist_genUUID(); - # some random string for tempID - my $tempId = todoist_genUUID(); - - Log3 $name,5, "$name: hash: ".Dumper($hash); - - my %args=(); - - # data array for API - we could transfer more data - %args = ( - project_id => $hash->{PID}, - content => $titleS, - ); - - - ## check for dueDate as Parameter or part of title - push to hash - if (!$tmp[1] && $h->{"dueDate"}) { ## parameter - $args{'due'}{'string'} = $h->{"dueDate"}; - } - elsif ($tmp[1]) { ## title - $args{'due'}{'string'} = $tmp[1]; - } - else { - - } - - ## if someone uses due_date - no problem - $args{'date_string'} = $h->{"due_date"} if ($h->{"due_date"}); - - $args{'date_string'} = encode_utf8($args{'date_string'}); - - ## Task parent_id - $args{'parent_id'} = int($h->{"parent_id"}) if ($h->{"parent_id"}); - $args{'parent_id'} = int($h->{"parentID"}) if ($h->{"parentID"}); - $args{'parent_id'} = int($h->{"parentId"}) if ($h->{"parentId"}); - - my $parentId = 0; - $parentId = %args{'parent_id'} if (%args{'parent_id'}); - - ## Task priority - $args{'priority'} = $h->{"priority"} if ($h->{"priority"}); - - ## who is responsible for the task? - $args{'responsible_uid'} = $h->{"responsibleUid"} if ($h->{"responsibleUid"}); - $args{'responsible_uid'} = $h->{"responsible"} if ($h->{"responsible"}); - - ## who assigned the task? - $args{'assigned_by_uid'} = $h->{"assignedByUid"} if ($h->{"assignedByUid"}); - $args{'assigned_by_uid'} = $h->{"assignedBy"} if ($h->{"assignedByUid"}); - - ## order of the task - $args{'item_order'} = $h->{"order"} if ($h->{"order"}); - - ## child order of the task - $args{'child_order'} = $h->{"child_order"} if ($h->{"child_order"}); - - - my $dataArr=$commandsStart.'"type":"item_add","temp_id":"'.$tempId.'","uuid":"'.$uuid.'","args":'.encode_json(\%args).$commandsEnd; - - - - Log3 $name,4, "todoist ($name): Data Array sent to todoist API: ".Dumper(%args); - - my $data= { - token => $pwd, - commands => $dataArr - }; - - - $param = { - url => $apiUrl."sync", - data => $data, - tTitle => $title, - method => "POST", - wType => "create", - parentId => $parentId, - timeout => 7, - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - hash => $hash, - callback => \&todoist_HandleTaskCallback, ## call callback sub to work with the data we get - }; - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+0.1, "HttpUtils_NonblockingGet", $param, 0); - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - } - } - else { - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_ErrorDialog === \"function\") todoist_ErrorDialog('$name','$title ".$todoist_tt->{"alreadythere"}."','".$todoist_tt->{"error"}."')", "")} devspec2array("TYPE=FHEMWEB"); - todoist_ErrorReadings($hash,"duplicate detected","duplicate detected"); - } - - - return undef; -} - - -# create Task -sub todoist_CreateTask_old($$) { - my ($hash,$cmd) = @_; - - my($a, $h) = parseParams($cmd); - - my $name=$hash->{NAME}; - - my $param; - - my $pwd=""; - - my $assigne_id=""; - - ## we try to send a due_date (in developement) - my @tmp = split( ":", join(" ",@$a) ); - - my $title=encode_utf8($tmp[0]); - - $title = encode_utf8($h->{"title"}) if ($h->{"title"}); - - my $check=1; - - # we can avoid duplicates in FHEM. There may still come duplicates coming from another app - if (AttrVal($name,"avoidDuplicates",0) == 1 && todoist_inArray(\@{$hash->{helper}{"TITS"}},$title)) { - $check=-1; - } - - if ($check==1) { - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - if ($pwd) { - - Log3 $name,5, "$name: hash: ".Dumper($hash); - - # data array for API - we could transfer more data - my $data = { - project_id => int($hash->{PID}), - content => $title, - token => $pwd, - }; - - - ## check for dueDate as Parameter or part of title - push to hash - if (!$tmp[1] && $h->{"dueDate"}) { ## parameter - $data->{'date_string'} = $h->{"dueDate"}; - } - elsif ($tmp[1]) { ## title - $data->{'date_string'} = $tmp[1]; - } - else { - - } - - ## if someone uses due_date - no problem - $data->{'date_string'} = $h->{"due_date"} if ($h->{"due_date"}); - - $data->{'date_string'} = encode_utf8($data->{'date_string'}); - - ## Task parent_id - $data->{'parent_id'} = int($h->{"parent_id"}) if ($h->{"parent_id"}); - $data->{'parent_id'} = int($h->{"parentID"}) if ($h->{"parentID"}); - $data->{'parent_id'} = int($h->{"parentId"}) if ($h->{"parentId"}); - - my $parentId = 0; - $parentId = $data->{'parent_id'} if ($data->{'parent_id'}); - - ## Task priority - $data->{'priority'} = $h->{"priority"} if ($h->{"priority"}); - - ## who is responsible for the task? - $data->{'responsible_uid'} = $h->{"responsibleUid"} if ($h->{"responsibleUid"}); - $data->{'responsible_uid'} = $h->{"responsible"} if ($h->{"responsible"}); - - ## who assigned the task? - $data->{'assigned_by_uid'} = $h->{"assignedByUid"} if ($h->{"assignedByUid"}); - $data->{'assigned_by_uid'} = $h->{"assignedBy"} if ($h->{"assignedByUid"}); - - ## order of the task - $data->{'item_order'} = $h->{"order"} if ($h->{"order"}); - - ## child order of the task - $data->{'child_order'} = $h->{"child_order"} if ($h->{"child_order"}); - - - - Log3 $name,4, "todoist ($name): Data Array sent to todoist API: ".Dumper($data); - - - $param = { - url => $apiUrl."items/add", - data => $data, - tTitle => $title, - method => "POST", - wType => "create", - parentId => $parentId, - timeout => 7, - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - hash => $hash, - callback => \&todoist_HandleTaskCallback, ## call callback sub to work with the data we get - }; - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+0.1, "HttpUtils_NonblockingGet", $param, 0); - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - } - } - else { - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_ErrorDialog === \"function\") todoist_ErrorDialog('$name','$title ".$todoist_tt->{"alreadythere"}."','".$todoist_tt->{"error"}."')", "")} devspec2array("TYPE=FHEMWEB"); - todoist_ErrorReadings($hash,"duplicate detected","duplicate detected"); - } - - - return undef; -} - -# handle the callback data if task was created or updated -sub todoist_HandleTaskCallback($$$){ - my ($param, $err, $data) = @_; - - my $hash = $param->{hash}; - my $title = $param->{tTitle}; - - my $taskId = 0; - $taskId = $param->{taskId} if ($param->{taskId}); - - my $reading = $title; - - my $name = $hash->{NAME}; - - Log3 $name,4, "todoist ($name): ".$param->{wType}." - Task Callback data: ".Dumper($data); - - my $error; - - ## errors? Log and readings - if ($err ne "") { - todoist_ErrorReadings($hash,$err); - } - else { - - ## if "sync_status" in $data, we were successfull - if((($data =~ /sync_status/ && $data=~/ok/) || $data =~ /sync_id/) && eval {decode_json($data)}) { - - readingsBeginUpdate($hash); - - if ($data ne "") { - my $decoded_json = decode_json($data); - - $taskId = $decoded_json->{id} if ($decoded_json->{id}); - - $reading .= " - ".$taskId; - - ## do some logging - Log3 $name,5, "todoist ($name): Task Callback data (decoded JSON): ".Dumper($decoded_json ); - - Log3 $name,4, "todoist ($name): Callback-ID: $taskId" if ($taskId); - } - Log3 $name,4, "todoist ($name): Task Callback error(s): ".Dumper($err) if ($err); - Log3 $name,5, "todoist ($name): Task Callback param: ".Dumper($param); - - # set information readings - readingsBulkUpdate($hash, "error","none"); - readingsBulkUpdate($hash, "lastCreatedTask",$reading) if ($param->{wType} eq "create"); - readingsBulkUpdate($hash, "lastCompletedTask",$reading) if ($param->{wType} eq "complete" || $param->{wType} eq "close"); - readingsBulkUpdate($hash, "lastUncompletedTask",$reading) if ($param->{wType} eq "uncomplete"); - readingsBulkUpdate($hash, "lastUpdatedTask",$reading) if ($param->{wType} eq "update"); - readingsBulkUpdate($hash, "lastDeletedTask",$reading) if ($param->{wType} eq "delete"); - - ## some Logging - Log3 $name, 4, "todoist ($name): successfully created new task $title" if ($param->{wType} eq "create"); - Log3 $name, 4, "todoist ($name): success: ".$param->{wType}." task $title" if ($title); - - readingsEndUpdate( $hash, 1 ); - - if ($param->{wType} =~ /(complete|delete|close)/) { - # remove line in possible webling widget - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_removeLine === \"function\") todoist_removeLine('$name','$taskId')", "")} devspec2array("TYPE=FHEMWEB"); - } - if ($param->{wType} eq "create") { - if ($param->{parentId}) { - # set parent id with additional updateTask command / API cannot add it in create - CommandSet(undef, "$name moveTask ID:$taskId parent_id=".$param->{parentId}); - Log3 $name, 3, "todoist ($name): startet set parent_id over update after create: Task-ID: ".$taskId." - parent_id: ".$param->{parentId}; - } - # add a line in possible weblink widget - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_addLine === \"function\") todoist_addLine('$name','$taskId','$title')", "")} devspec2array("TYPE=FHEMWEB"); - } - } - ## we got an error from the API - else { - my $error="malformed JSON"; - - # if the error is in the file, log this error - if (eval {decode_json($data)}) { - my $decoded_json = decode_json($data); - $error = $decoded_json->{error} if ($decoded_json->{error}); - } - $error .= " | ".$param->{wType}."Task: ".$taskId; - $error .= "Unknown"; - Log3 $name, 2, "todoist ($name): got error: ".$error; - todoist_ErrorReadings($hash,$error); - } - - } - # restart the timers - todoist_RestartGetTimer($hash); - - return undef; -} - - - -## get all Tasks -sub todoist_GetTasks($;$) { - my ($hash,$completed) = @_; - - my $name=$hash->{NAME}; - - # add loading circle to possivle weblink widget - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_addLoading === \"function\") todoist_addLoading('$name')", "")} devspec2array("TYPE=FHEMWEB"); - - $completed = 0 unless defined($completed); - - my $param; - my $param2; - - my $pwd=""; - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - if ($pwd) { - - Log3 $name,5, "$name: hash: ".Dumper($hash); - - my $data= { - token => $pwd, - project_id => $hash->{PID} - }; - - # set url for API access - my $url = $apiUrl."projects/get_data"; - ## check if we get also the completed Tasks - if ($completed == 1) { - $url = $apiUrl."completed/get_all"; - $data->{'limit'}=50; - } - - Log3 $name,4, "todoist ($name): Curl Data: ".Dumper($data); - - ## get the tasks - $param = { - url => $url, - method => "POST", - data => $data, - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - timeout => 7, - completed => $completed, - hash => $hash, - callback => \&todoist_GetTasksCallback, ## call callback sub to work with the data we get - }; - - - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+0.2, "HttpUtils_NonblockingGet", $param, 0); - - - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - } - - ## one more time, if we want to get completed tasks - if (AttrVal($name,"getCompleted",0)==1 && $completed != 1) { - InternalTimer(gettimeofday()+0.5, "todoist_doGetCompTasks", $hash, 0); - } - InternalTimer(gettimeofday()+0.3, "todoist_GetUsers", $hash, 0) if ($completed != 1 && AttrVal($name,"autoGetUsers",1) == 1); - - return undef; -} - -# helper sub for getting completed tasks -sub todoist_doGetCompTasks($) { - my ($hash) = @_; - todoist_GetTasks($hash,1); -} - -## Callback for the lists tasks -sub todoist_GetTasksCallback($$$){ - my ($param, $err, $data) = @_; - - my $hash=$param->{hash}; - - my $name = $hash->{NAME}; - - Log3 $name,5, "todoist ($name): Task Callback data-raw: ".Dumper($data); - - my $lText=""; - - Log3 $name,5, "todoist ($name): Task Callback param: ".Dumper($param); - - readingsBeginUpdate($hash); - - my $prefix="Task_"; - - ## Log possbile errors in callback - if ($err ne "") { - todoist_ErrorReadings($hash,$err); - } - else { - my $decoded_json=""; - - # check for correct JSON - if (eval{decode_json($data)}) { - - $decoded_json = decode_json($data); - - Log3 $name,5, "todoist ($name): Task Callback data (decoded JSON): ".Dumper($decoded_json ); - } - - # mostly HTML response / todoist is down or locked - if ((ref($decoded_json) eq "HASH" && !$decoded_json->{items}) || $decoded_json eq "") { - $hash->{helper}{errorData} = Dumper($data); - $hash->{helper}{errorMessage} = "GetTasks: Response was damaged or empty. See log for details."; - - InternalTimer(gettimeofday()+0.2, "todoist_ErrorReadings",$hash, 0); - } - # got project - else { - Log3 $name,4, "todoist ($name): getTasks was successful"; - Log3 $name,5, "todoist ($name): Task item data: ".Dumper(@{$decoded_json->{items}}); - ## items data - my @taskseries = @{$decoded_json->{items}}; - - ## project data - my $project = $decoded_json->{project}; - - # set some internals (project data) - if ($project) { - $hash->{PROJECT_NAME}=encode_utf8($project->{name}); - $hash->{PROJECT_COLOR}=$project->{color}; - $hash->{PROJECT_ORDER}=$project->{child_order}; - if ($project->{user_id}) { - $hash->{PROJECT_USER}=$project->{user_id}; - } - else { - delete($hash->{PROJECT_USER}); - } - if ($project->{parent_id}) { - $hash->{PROJECT_PARENT}=$project->{parent_id}; - # hidden reading - readingsBulkUpdate($hash, ".projectParentId",$project->{parent_id}); - } - else { - # delete parent_id if there is none - delete($hash->{PROJECT_PARENT}) if (defined($hash->{PROJECT_PARENT})); - CommandDeleteReading(undef, ".projectParentId"); - } - } - - ## do some logging - Log3 $name,5, "todoist ($name): Task Callback data (taskseries): ".Dumper(@taskseries ); - - my $i=0; - - ## count the results - my $count=@taskseries; - - ## delete Task_* readings for changed list - if ($param->{completed} != 1 || (ReadingsVal($name,"count",0)==0 && $count == 0)) { - CommandDeleteReading(undef, "$hash->{NAME} (T|t)ask_.*"); - delete($hash->{helper}); - } - - #$prefix="cTask_" if ($param->{completed} == 1); - - - - ## no data - if ($count==0 && $param->{completed} != 1) { - InternalTimer(gettimeofday()+0.2, "todoist_ErrorReadings",$hash, 0); - readingsBulkUpdate($hash, "count",0); - } - # got some tasks - else { - $i = ReadingsVal($name,"count",0) if ($param->{completed} == 1); - foreach my $task (@taskseries) { - my $title = encode_utf8($task->{content}); - $title =~ s/^\s+|\s+$//g; - - my $t = sprintf ('%03d',$i); - - ## get todoist-Task-ID - my $taskID = $task->{id}; - $taskID = $task->{task_id} if ($param->{completed} == 1); - - readingsBulkUpdate($hash, $prefix.$t,$title); - readingsBulkUpdate($hash, $prefix.$t."_ID",$taskID) if (AttrVal($name,"hideId",0)!=1); - - # convert title - my $nTitle = $title; - $nTitle=~s/ /_/g; - - ## a few helper for ID and revision - $hash->{helper}{"IDS"}{"Task_".$i}=$taskID; # todoist Task-ID - $hash->{helper}{"TITLE"}{$taskID}=$title; # Task title (content) - $hash->{helper}{"TITLES"}{$nTitle}=$taskID; # Task title (content) - $hash->{helper}{"WID"}{$taskID}=$i; # FHEM Task-ID - $hash->{helper}{"parent_id"}{$taskID}=$task->{parent_id}; # parent_id of item - $hash->{helper}{"section_id"}{$taskID}=$task->{section_id}; # section_id of item - $hash->{helper}{"child_order"}{$taskID}=$task->{child_order}; # order of task under parent - $hash->{helper}{"PRIORITY"}{$taskID}=$task->{priority}; # todoist Task priority - #push @{$hash->{helper}{"PARENTS"}{$task->{parent_id}}},$taskID; # ident for better widget - $hash->{helper}{"ORDER"}{$taskID}=$task->{item_order}; # todoist Task order - if ($param->{completed} != 1) { - push @{$hash->{helper}{"TIDS"}},$taskID; # simple ID list - push @{$hash->{helper}{"TITS"}},$title; # simple ID list - } - - readingsBulkUpdate($hash, $prefix.$t."_parent_id",$task->{parent_id}) if (AttrVal($name,"showParent",0)==1); - readingsBulkUpdate($hash, $prefix.$t."_order",$task->{item_order}) if (AttrVal($name,"showOrder",0)==1); - - ## set parent_id if not null - if (defined($task->{parent_id}) && $task->{parent_id} ne 'null') { - ## if this task has a parent_id we set the reading - readingsBulkUpdate($hash, $prefix.$t."_parentID",$task->{parent_id}) if (AttrVal($name,"showParent",1)==1); - $hash->{helper}{"PARENT_ID"}{$taskID}=$task->{parent_id}; - } - - ## set section_id if not null - if (defined($task->{section_id}) && $task->{section_id} ne 'null') { - ## if this task has a parent_id we set the reading - readingsBulkUpdate($hash, $prefix.$t."_sectionID",$task->{section_id}) if (AttrVal($name,"showSection",1)==1); - $hash->{helper}{"SECTION_ID"}{$taskID}=$task->{section_id}; - } - - ## set completed_date if present - if (defined($task->{checked}) && $task->{checked}!=0) { - readingsBulkUpdate($hash, $prefix.$t."_checked",$task->{checked}) if (AttrVal($name,"showChecked",1)==1); - $hash->{helper}{"CHECKED"}{$taskID}=$task->{checked}; - } - - ## set completed_date if present - if (defined($task->{is_deleted}) && $task->{is_deleted}!=0) { - readingsBulkUpdate($hash, $prefix.$t."_isDeleted",$task->{is_deleted}) if (AttrVal($name,"showDeleted",1)==1); - $hash->{helper}{"ISDELETED"}{$taskID}=$task->{is_deleted}; - } - - ## set completed_date if present - if (defined($task->{completed_date})) { - ## if there is a completed task, we create a new reading - readingsBulkUpdate($hash, $prefix.$t."_completedAt",FmtDateTime(str2time($task->{completed_date}))); - $hash->{helper}{"COMPLETED_AT"}{$taskID}=FmtDateTime(str2time($task->{completed_date})); - readingsBulkUpdate($hash, $prefix.$t."_completedById",$task->{user_id}); - $hash->{helper}{"COMPLETED_BY_ID"}{$taskID}=$task->{user_id}; - } - - ## set due_date if present - if (defined($task->{due}) && $task->{due}{date} ne 'null') { - ## if there is a task with due date, we create a new reading - readingsBulkUpdate($hash, $prefix.$t."_dueDate",FmtDateTime(str2time($task->{due}{date}))); - $hash->{helper}{"DUE_DATE"}{$taskID}=FmtDateTime(str2time($task->{due}{date})); - } - - ## set responsible_uid if present - if (defined($task->{responsible_uid})) { - ## if there is a task with responsible_uid, we create a new reading - readingsBulkUpdate($hash, $prefix.$t."_responsibleUid",$task->{responsible_uid}) if (AttrVal($name,"showResponsible",0)==1); - $hash->{helper}{"RESPONSIBLE_UID"}{$taskID}=$task->{responsible_uid}; - } - - ## set assigned_by_uid if present - if (defined($task->{assigned_by_uid})) { - ## if there is a task with assigned_by_uid, we create a new reading - readingsBulkUpdate($hash, $prefix.$t."_assignedByUid",$task->{assigned_by_uid}) if (AttrVal($name,"showAssignedBy",0)==1); - $hash->{helper}{"ASSIGNEDBY_UID"}{$taskID}=$task->{assigned_by_uid}; - } - - ## set priority if present - if (defined($task->{priority})) { - readingsBulkUpdate($hash, $prefix.$t."_priority",$task->{priority}) if (AttrVal($name,"showPriority",0)==1); - $hash->{helper}{"PRIORITY"}{$taskID}=$task->{priority}; - } - - ## set recurrence_type and count if present - if (defined($task->{date_string})) { - ## if there is a task with recurrence_type, we create new readings - readingsBulkUpdate($hash, $prefix.$t."_recurrenceType",encode_utf8($task->{date_string})); - $hash->{helper}{"RECURRENCE_TYPE"}{$taskID}=encode_utf8($task->{date_string}); - } - - if ($param->{completed} != 1) { - $lText.=AttrVal($name,"listDivider",", ") if ($i != 0); - $lText.=$title; - } - $i++; - } - readingsBulkUpdate($hash, "error","none"); - readingsBulkUpdate($hash, "count",$i); - - - } - } - } - - readingsEndUpdate( $hash, 1 ); - - ## list Text for TTS, Text-Message... - if ($param->{completed} != 1) { - $lText="-" if ($lText eq ""); - readingsSingleUpdate($hash,"listText",$lText,1) if ($lText ne ""); - } - - RemoveInternalTimer($hash,"todoist_GetTasks"); - InternalTimer(gettimeofday()+$hash->{INTERVAL}, "todoist_GetTasks", $hash, 0); ## loop with Interval - - todoist_ReloadTable($name); - - return undef; -} - -## get all Users -sub todoist_GetUsers($) { - my ($hash) = @_; - - my $name=$hash->{NAME}; - - my $param; - - my $pwd=""; - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - my $data= { - token => $pwd, - sync_token => '*', - resource_types => '["collaborators"]' - }; - - if ($pwd) { - - Log3 $name,5, "$name: hash: ".Dumper($hash); - - $param = { - url => $apiUrl."sync", - data => $data, - timeout => 7, - method => "POST", - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - hash => $hash, - callback => \&todoist_GetUsersCallback, ## call callback sub to work with the data we get - }; - - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+1, "HttpUtils_NonblockingGet", $param, 0); - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - } - - return undef; -} - -sub todoist_GetUsersCallback($$$){ - my ($param, $err, $data) = @_; - - my $hash=$param->{hash}; - - my $name = $hash->{NAME}; - - Log3 $name,5, "todoist ($name): User Callback data: ".Dumper($data); - - if ($err ne "") { - todoist_ErrorReadings($hash,$err); - } - else { - my $decoded_json=""; - - if (eval{decode_json($data)}) { - - $decoded_json = decode_json($data); - - Log3 $name,5, "todoist ($name): User Callback data (decoded JSON): ".Dumper($decoded_json ); - } - - readingsBeginUpdate($hash); - if ((ref($decoded_json) eq "HASH" && !$decoded_json->{collaborators}) || $decoded_json eq "") { - $hash->{helper}{errorData} = Dumper($data); - $hash->{helper}{errorMessage} = "Response was damaged or empty. See log for details."; - InternalTimer(gettimeofday()+0.2, "todoist_ErrorReadings",$hash, 0); - } - else { - my @users = @{$decoded_json->{collaborators}}; - my @states = @{$decoded_json->{collaborator_states}}; - ## count the results - my $count=@users; - - ## delete Task_* readings for changed list - CommandDeleteReading(undef, "$hash->{NAME} (U|u)ser_.*"); - delete($hash->{helper}{USER}); - - Log3 $name,5, "todoist ($name): Task States: ".Dumper(@states); - - ## no data - if ($count==0) { - readingsBulkUpdate($hash, "error","none"); - readingsBulkUpdate($hash, "countUsers",0); - } - else { - - my $i=0; - foreach my $user (@users) { - my $do=0; - foreach my $state (@states) { - $do=1 if ($user->{id} == $state->{user_id} && $state->{project_id} == $hash->{PID}); - } - - if ($do==1) { - my $userName = encode_utf8($user->{full_name}); - my $t = sprintf ('%03d',$i); - - ## get todoist-User-ID - my $userID = $user->{id}; - - readingsBulkUpdate($hash, "User_".$t,$userName); - readingsBulkUpdate($hash, "User_".$t."_ID",$userID); - - ## a few helper for ID and revision - $hash->{helper}{USER}{"IDS"}{"User_".$i}=$userID; - $hash->{helper}{USER}{"NAME"}{$userID}=$userName; - $hash->{helper}{USER}{"WID"}{$userID}=$i; - $i++; - } - } - readingsBulkUpdate($hash, "error","none"); - readingsBulkUpdate($hash, "countUsers",$i); - } - } - readingsEndUpdate( $hash, 1 ); - todoist_ReloadTable($name); - } - - -} - -# get all projects -sub todoist_GetProjects($) { - my ($hash) = @_; - - my $name=$hash->{NAME}; - - my $param; - - my $pwd=""; - - ## if no token is needed and device is not disabled, check token and get list vom todoist - if (!$hash->{helper}{PWD_NEEDED} && !IsDisabled($name)) { - - ## get password - $pwd=todoist_GetPwd($hash); - - my $data= { - token => $pwd, - sync_token => '*', - resource_types => '["projects"]' - }; - - if ($pwd) { - - Log3 $name,5, "$name: hash: ".Dumper($hash); - - $param = { - url => $apiUrl."sync", - data => $data, - timeout => 7, - method => "POST", - header => "Content-Type: application/x-www-form-urlencoded\r\n". - "Authorization: Bearer ".$pwd, - hash => $hash, - callback => \&todoist_GetProjectsCallback, ## call callback sub to work with the data we get - }; - - - Log3 $name,5, "todoist ($name): Param: ".Dumper($param); - - ## non-blocking access to todoist API - InternalTimer(gettimeofday()+1, "HttpUtils_NonblockingGet", $param, 0); - } - else { - todoist_ErrorReadings($hash,"access token empty"); - } - } - else { - if (!IsDisabled($name)) { - todoist_ErrorReadings($hash,"no access token set"); - } - else { - todoist_ErrorReadings($hash,"device is disabled"); - } - } - - return undef; - -} - -# get projects callback - -sub todoist_GetProjectsCallback($$$){ - my ($param, $err, $data) = @_; - - my $hash=$param->{hash}; - - my $name = $hash->{NAME}; - - ## some Project-Info - my $pid = $hash->{PID}; - my $room = AttrVal($name,"room",undef); - my $group = AttrVal($name,"group",undef); - - my $return=""; - - my $i=0; - - Log3 $name,5, "todoist ($name): Projects Callback data: ".Dumper($data); - - readingsBeginUpdate($hash); - - if ($err ne "") { - todoist_ErrorReadings($hash,$err); - } - else { - my $decoded_json=""; - - if (eval{decode_json($data)}) { - - $decoded_json = decode_json($data); - - Log3 $name,5, "todoist ($name): User Callback data (decoded JSON): ".Dumper($decoded_json ); - } - if ((ref($decoded_json) eq "HASH" && !$decoded_json->{projects}) || $decoded_json eq "") { - $hash->{helper}{errorData} = Dumper($data); - $hash->{helper}{errorMessage} = "GetTasks: Response was damaged or empty. See log for details."; - InternalTimer(gettimeofday()+0.2, "todoist_ErrorReadings",$hash, 0); - } - else { - Log3 $name,4, "todoist ($name): getProjects was successful"; - Log3 $name,5, "todoist ($name): Task projects data: ".Dumper(@{$decoded_json->{projects}}); - ## items data - my @projects = @{$decoded_json->{projects}}; - - ## count the results - my $count=@projects; - - ## no data - if ($count==0) { - InternalTimer(gettimeofday()+0.2, "todoist_ErrorReadings",$hash, 0); - readingsBulkUpdate($hash, "count",0); - } - else { - # array for possible deletion - my @comDevs; - # walk through projects - foreach my $project (@projects) { - my $project_id = $project->{id}; - my $parent_id = $project->{parent_id}; - my $new_name = encode_utf8($project->{name}); - # new title - my $title = $name."_".$new_name; - $title =~ s!\s!!g; - # is this parent_id equal to id of current project? - if ($pid == $parent_id) { - # push to deletion array - push @comDevs,$project_id; - Log3 $name,4, "todoist ($name): get Projects: Project-ID: $project_id - Title: $new_name - Parent-ID: $parent_id"; - # does this project exist in FHEM? - my $existP = devspec2array("PID=$project_id"); - if (!$existP && (!$project->{is_deleted} || $project->{is_deleted}!=1)) { - # if not, define new todoist device by copying the parent - fhem("copy $name $title $project_id") if (!defined($defs{$title})); - my $new_hash = $defs{$title}; - # set some internals (project data) and set parent_id - if ($new_hash) { - $return.=" $title"; - $i++; - Log3 $name,4, "todoist ($name): new project $title, defined by cChildProjects"; - $new_hash->{PROJECT_NAME}=$project->{name}; - $new_hash->{PROJECT_COLOR}=$project->{color}; - $new_hash->{PROJECT_ORDER}=$project->{child_order}; - if ($project->{user_id}) { - $new_hash->{PROJECT_USER}=$project->{user_id}; - } - else { - delete($new_hash->{PROJECT_USER}); - } - if ($project->{parent_id}) { - $new_hash->{PROJECT_PARENT}=$project->{parent_id}; - # invisible reading - readingsSingleUpdate($new_hash, ".projectParentId",$project->{parent_id},1); - } - else { - delete($new_hash->{PROJECT_PARENT}) if (defined($new_hash->{PROJECT_PARENT})); - CommandDeleteReading(undef, ".projectParentId"); - } - } - } - } - } - if (AttrVal($name,"delDeletedLists",0)==1) { - # get todoist projects for this parent_id - my @tDevs = devspec2array(".projectParentId=$pid"); - foreach my $dev (@tDevs) { - my $tDev = $defs{$dev}; - # delete device if PID is not in array - if (!todoist_inArray(\@comDevs,$tDev->{PID})) { - CommandDelete(undef,$dev); - Log3 $name,3, "todoist ($name): deleted device ".$dev." in cChildProjects"; - } - } - } - } - } - } - Log3 $name,3, "todoist ($name): Got $i new projects: ".$return; - return "Got $i new projects: ".$return; -} - - -################################################# -# delete all Tasks from list -sub todoist_clearList($) { - my ($hash) = @_; - - my $name = $hash->{NAME}; - - my $i=0; - ## iterate through all tasks - foreach my $id (%{$hash->{helper}{IDS}}) { - my $dHash->{hash}=$hash; - if ($id !~ /Task_/) { - $dHash->{id}=$id; - InternalTimer(gettimeofday()+0.4, "todoist_doUpdateTask", $dHash, 0); - $i++; - } - } - if ($i==0) { - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_removeLoading === \"function\") todoist_removeLoading('$name')", "")} devspec2array("TYPE=FHEMWEB"); - } -} - -sub todoist_doUpdateTask($) { - my ($dHash) = @_; - my $hash = $dHash->{hash}; - my $id = $dHash->{id}; - my $name = $hash->{NAME}; - todoist_UpdateTask($hash,"ID:".$id,"delete"); -} - - -sub todoist_Undefine($$) { - my ($hash, $arg) = @_; - - RemoveInternalTimer($hash); - - return undef; -} - -################################################ -# If Device is deleted, delete the password data -sub todoist_Delete($$) { - my ($hash, $name) = @_; - - my $old_index = "todoist_".$name."_passwd"; - - my $old_key =getUniqueId().$old_index; - - my ($err, $old_pwd) = getKeyValue($old_index); - - return undef unless(defined($old_pwd)); - - setKeyValue($old_index, undef); - - - Log3 $name, 3, "todoist: device $name as been deleted. Access-Token has been deleted too."; - - return undef; -} - -################################################ -# If Device is renamed, copy the password data -sub todoist_Rename($$) { - my ($new, $old) = @_; - - my $old_index = "todoist_".$old."_passwd"; - my $new_index = "todoist_".$new."_passwd"; - my $key = getUniqueId().$old_index; - - my $new_hash = $defs{$new}; - my $name = $new_hash->{NAME}; - - my $old_key=""; - - my ($err, $password) = getKeyValue($old_index); - - if ($err) { - $new_hash->{helper}{PWD_NEEDED} = 1; - Log3 $name, 4, "todoist ($name): unable to read password from file: $err"; - return undef; - } - - if ( defined($password) ) { - if ( eval "use Digest::MD5;1" ) { - $key = Digest::MD5::md5_hex(unpack "H*", $key); - $key .= Digest::MD5::md5_hex($key); - } - - for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { - my $decode=chop($key); - $old_key.=chr(ord($char)^ord($decode)); - $key=$decode.$key; - } - } - my $new_key = $old_key; - - - return undef unless(defined($old_key)); - - todoist_setPwd($new_hash, $name, $new_key); - - Log3 $new, 3, "todoist: device has been renamed from $old to $new. Access-Token has been assigned to new name."; - - return undef; -} - -################################################ -# If Device is copied, copy the password data -sub todoist_Copy($$;$) { - my ($old, $new) = @_; - - my $old_index = "todoist_".$old."_passwd"; - my $new_index = "todoist_".$new."_passwd"; - my $key = getUniqueId().$old_index; - - my $new_hash = $defs{$new}; - my $name = $new_hash->{NAME}; - - my $old_key=""; - - my ($err, $password) = getKeyValue($old_index); - - if ($err) { - $new_hash->{helper}{PWD_NEEDED} = 1; - Log3 $name, 4, "todoist ($name): unable to read password from file: $err"; - return undef; - } - - if ( defined($password) ) { - if ( eval "use Digest::MD5;1" ) { - $key = Digest::MD5::md5_hex(unpack "H*", $key); - $key .= Digest::MD5::md5_hex($key); - } - - for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { - my $decode=chop($key); - $old_key.=chr(ord($char)^ord($decode)); - $key=$decode.$key; - } - } - my $new_key = $old_key; - - return undef unless(defined($old_key)); - - todoist_setPwd($new_hash, $name, $new_key); - - delete($new_hash->{helper}{PWD_NEEDED}); - - readingsSingleUpdate($new_hash,"state","inactive",1); - - Log3 $new, 3, "todoist: device has been copied from $old to $new. Access-Token has been assigned to new device."; - - return undef; -} - -## some checks if attribute is set or deleted -sub todoist_Attr($@) { - my ($cmd, $name, $attrName, $attrVal) = @_; - - my $orig = $attrVal; - - my $hash = $defs{$name}; - - if ( $attrName eq "disable" ) { - - if ( $cmd eq "set" && $attrVal == 1 ) { - if ($hash->{READINGS}{state}{VAL} ne "disabled") { - readingsSingleUpdate($hash,"state","disabled",1); - RemoveInternalTimer($hash); - RemoveInternalTimer($hash,"todoist_GetTasks"); - Log3 $name, 4, "todoist ($name): $name is now disabled"; - } - } - elsif ( $cmd eq "del" || $attrVal == 0 ) { - if ($hash->{READINGS}{state}{VAL} ne "active") { - readingsSingleUpdate($hash,"state","active",1); - RemoveInternalTimer($hash); - Log3 $name, 4, "todoist ($name): $name is now ensabled"; - todoist_RestartGetTimer($hash); - } - } - } - - if ( $attrName eq "pollInterval") { - if ( $cmd eq "set" ) { - return "$name: pollInterval has to be a number (seconds)" if ($attrVal!~ /\d+/); - return "$name: pollInterval has to be greater than or equal 20" if ($attrVal < 20); - $hash->{INTERVAL}=$attrVal; - Log3 $name, 4, "todoist ($name): set new pollInterval to $attrVal"; - } - elsif ( $cmd eq "del" ) { - $hash->{INTERVAL}=1800; - Log3 $name, 4, "todoist ($name): set new pollInterval to 1800 (standard)"; - } - todoist_RestartGetTimer($hash); - } - - if ($attrName eq "listDivider") { - todoist_RestartGetTimer($hash); - } - - if ($attrName eq "language") { - # in any attribute redefinition readjust language - if ($cmd eq "set") { - return "$name: language can only be DE or EN" if ($attrVal !~ /(^DE|EN)$/); - if( $attrVal eq "DE") { - $todoist_tt = \%todoist_transtable_DE; - } - else{ - $todoist_tt = \%todoist_transtable_EN; - } - } - else { - $todoist_tt = \%todoist_transtable_EN; - } - } - - if ( $attrName =~ /(show(Priority|AssignedBy|Responsible|Order|DetailWidget|Section|Parent)|getCompleted|hide(Id|ListIfEmpty)|autoGetUsers|avoidDuplicates|delDeletedLists)/) { - if ( $cmd eq "set" ) { - return "$name: $attrName has to be 0 or 1" if ($attrVal !~ /^(0|1)$/); - Log3 $name, 4, "todoist ($name): set attribut $attrName to $attrVal"; - } - elsif ( $cmd eq "del" ) { - Log3 $name, 4, "todoist ($name): deleted attribut $attrName"; - } - todoist_RestartGetTimer($hash); - } - - return; -} - -sub todoist_Set ($@) { - my ($hash, $name, $cmd, @args) = @_; - - my @sets = (); - - push @sets, "active:noArg" if (IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}); - push @sets, "inactive:noArg" if (!IsDisabled($name)); - if (!IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}) { - push @sets, "addTask"; - push @sets, "completeTask"; - push @sets, "closeTask"; - push @sets, "uncompleteTask"; - push @sets, "deleteTask"; - push @sets, "updateTask"; - push @sets, "moveTask"; - push @sets, "clearList:noArg"; - push @sets, "getTasks:noArg"; - push @sets, "cChildProjects:noArg"; - push @sets, "getUsers:noArg"; - push @sets, "reorderTasks"; - } - push @sets, "accessToken" if ($hash->{helper}{PWD_NEEDED}); - push @sets, "newAccessToken" if (!$hash->{helper}{PWD_NEEDED}); - - return join(" ", @sets) if ($cmd eq "?"); - - my $usage = "Unknown argument ".$cmd.", choose one of ".join(" ", @sets) if(scalar @sets > 0); - - if (IsDisabled($name) && $cmd !~ /^(active|inactive|.*ccessToken)?$/) { - Log3 $name, 3, "todoist ($name): Device is disabled at set Device $cmd"; - return "Device is disabled. Enable it on order to use command ".$cmd; - } - - if ( $cmd =~ /^(active|inactive)?$/ ) { - readingsSingleUpdate($hash,"state",$cmd,1); - RemoveInternalTimer($hash,"todoist_GetTasks"); - CommandDeleteAttr(undef,"$name disable") if ($cmd eq "active" && AttrVal($name,"disable",0)==1); - InternalTimer(gettimeofday()+1, "todoist_GetTasks", $hash, 0) if (!IsDisabled($name) && $cmd eq "active"); - Log3 $name, 3, "todoist ($name): set Device $cmd"; - } - elsif ($cmd eq "getTasks") { - RemoveInternalTimer($hash,"todoist_GetTasks"); - Log3 $name, 4, "todoist ($name): set getTasks manually. Timer restartet."; - InternalTimer(gettimeofday()+1, "todoist_GetTasks", $hash, 0) if (!IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}); - } - elsif ($cmd eq "cChildProjects") { - RemoveInternalTimer($hash,"todoist_GetProjects"); - Log3 $name, 4, "todoist ($name): set getProjects manually. Timer restartet."; - InternalTimer(gettimeofday()+1, "todoist_GetProjects", $hash, 0) if (!IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}); - } - elsif ($cmd eq "getUsers") { - RemoveInternalTimer($hash,"todoist_GetUsers"); - Log3 $name, 4, "todoist ($name): set getUsers manually."; - InternalTimer(gettimeofday()+1, "todoist_GetUsers", $hash, 0) if (!IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}); - } - elsif ($cmd eq "accessToken" || $cmd eq "newAccessToken") { - return todoist_setPwd ($hash,$name,@args); - } - elsif ($cmd eq "addTask" || $cmd eq "newTask") { - my $count=@args; - if ($count!=0) { - my $exp=decode_utf8(join(" ",@args)); - todoist_CreateTask ($hash,$exp); - } - return "new Task needs a title" if ($count==0); - } - elsif ($cmd eq "completeTask" || $cmd eq "closeTask") { - my $term=$cmd eq "completeTask"?"complete":"close"; - my $count=@args; - if ($count!=0) { - my $exp=decode_utf8(join(" ",@args)); - Log3 $name,5, "todoist ($name): ".$term."d startet with exp: $exp"; - todoist_UpdateTask ($hash,$exp,$term); - } - return "in order to complete or close a task, we need it's ID" if ($count==0); - } - elsif ($cmd eq "uncompleteTask") { - my $count=@args; - if ($count!=0) { - my $exp=decode_utf8(join(" ",@args)); - Log3 $name,5, "todoist ($name): Uncompleted startet with exp: $exp"; - todoist_UpdateTask ($hash,$exp,"uncomplete"); - } - return "in order to complete a task, we need it's ID" if ($count==0); - } - elsif ($cmd eq "updateTask" || $cmd eq "moveTask") { - my $term=$cmd eq "updateTask"?"update":"move"; - my $count=@args; - if ($count!=0) { - my $exp=decode_utf8(join(" ",@args)); - todoist_UpdateTask ($hash,$exp,$term); - } - return "in order to complete a task, we need it's ID" if ($count==0); - } - elsif ($cmd eq "reorderTasks") { - my $count=@args; - if ($count!=0) { - my $exp=$args[0]; - todoist_ReorderTasks ($hash,$exp); - } - return "in order to delete a task, we need it's ID" if ($count==0); - } - elsif ($cmd eq "deleteTask") { - my $count=@args; - if ($count!=0) { - my $exp=decode_utf8(join(" ",@args)); - todoist_UpdateTask ($hash,$exp,"delete"); - } - return "in order to delete a task, we need it's ID" if ($count==0); - } - elsif ($cmd eq "sortTasks") { - todoist_sort($hash); - } - elsif ($cmd eq "clearList") { - todoist_clearList($hash); - } - else { - return $usage; - } - - return undef; - -} - -sub todoist_Get($@) { - my ($hash, $name, $cmd, @args) = @_; - my $ret = undef; - - if ( $cmd eq "version") { - $hash->{VERSION} = $version; - return "Version: ".$version; - } - else { - $ret ="$name get with unknown argument $cmd, choose one of " . join(" ", sort keys %gets); - } - - return $ret; -} - -##################################### -# sets todoist Access Token -sub todoist_setPwd($$@) { - my ($hash, $name, @pwd) = @_; - - return "todoist: Password can't be empty" if (!@pwd); - - my $pwdString=$pwd[0]; - my $enc_pwd = ""; - - my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; - my $key = getUniqueId().$index; - - if(eval "use Digest::MD5;1") { - $key = Digest::MD5::md5_hex(unpack "H*", $key); - $key .= Digest::MD5::md5_hex($key); - } - - for my $char (split //, $pwdString) { - my $encode=chop($key); - $enc_pwd.=sprintf("%.2x",ord($char)^ord($encode)); - $key=$encode.$key; - } - - Log3 $name,5,"todoist ($name): encoded pwd: $enc_pwd"; - - my $err = setKeyValue($index, $enc_pwd); - - return "error while saving the password - $err" if(defined($err)); - - delete($hash->{helper}{PWD_NEEDED}) if(exists($hash->{helper}{PWD_NEEDED})); - - - if (AttrVal($name,"disable",0) != 1) { - readingsSingleUpdate($hash,"state","active",1); - } - - todoist_RestartGetTimer($hash); - - Log3 $name, 3, "todoist ($name). New Password set."; - - return "password successfully saved"; - -} - -##################################### -# reads the Access Token and checks it -sub todoist_checkPwd ($$) { - my ($hash, $pwd) = @_; - my $name = $hash->{NAME}; - - my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; - my $key = getUniqueId().$index; - - my ($err, $password) = getKeyValue($index); - - if ($err) { - $hash->{helper}{PWD_NEEDED} = 1; - Log3 $name, 3, "todoist ($name): unable to read password from file: $err"; - return undef; - } - - if ( defined($password) ) { - if ( eval "use Digest::MD5;1" ) { - $key = Digest::MD5::md5_hex(unpack "H*", $key); - $key .= Digest::MD5::md5_hex($key); - } - - my $dec_pwd = ''; - - for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { - my $decode=chop($key); - $dec_pwd.=chr(ord($char)^ord($decode)); - $key=$decode.$key; - } - - return 1 if ($dec_pwd eq $pwd); - } - else { - return "no password saved" if (!$password); - } - - return 0; -} - -sub todoist_Notify ($$) { - my ($hash,$dev) = @_; - - my $name = $hash->{NAME}; - - return if($dev->{NAME} ne "global"); - - return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}})); - - todoist_RestartGetTimer($hash); - - return undef; - -} - -# restart timers for getTasks if active -sub todoist_RestartGetTimer($) { - my ($hash) = @_; - - my $name = $hash->{NAME}; - - RemoveInternalTimer($hash, "todoist_GetTasks"); - InternalTimer(gettimeofday()+0.3, "todoist_GetTasks", $hash, 0) if (!IsDisabled($name) && !$hash->{helper}{PWD_NEEDED}); - - return undef; -} - -# placeholder for older widgets -sub todoist_AllHtml(;$) { - my ($regEx) = @_; - return todoist_Html($regEx); -} - -# show widget in detail view of todoist device -sub todoist_detailFn(){ - my ($FW_wname, $devname, $room, $pageHash) = @_; # pageHash is set for summaryFn. - - my $hash = $defs{$devname}; - - $hash->{mayBeVisible} = 1; - - my $name=$hash->{NAME}; - - return undef if (IsDisabled($name) || AttrVal($name,"showDetailWidget",1)!=1); - - my $html=""; - - my $icon = AttrVal($devname, "icon", ""); - $icon = FW_makeImage($icon,$icon,"icon") . " " if($icon); - - #$html = '
'. - # ''. - # ''. - # '
'.$icon.' '.AttrVal($name,"alias",$name).'
'.InternalVal($devname,"STATE",ReadingsVal($devname,"state","-")).'
'; - - $html .= todoist_Html($name,undef,1); - - return $html; -} - -# frontend weblink widget (FHEMWEB) -sub todoist_Html(;$$$) { - my ($regEx,$refreshGet,$detail) = @_; - - $regEx="" if (!defined($regEx)); - $refreshGet=0 if (!defined($refreshGet)); - $detail=0 if (!defined($detail)); - - my $filter=""; - - $filter.=":FILTER=".$regEx; - - my @devs = devspec2array("TYPE=todoist".$filter); - my $ret=""; - my $rot=""; - - my $eo=""; - - my $r=0; - - my $width = 95; - - my $count = @devs; - $width = $width/$count if ($count>=1); - - # refresh request? don't return everything - if (!$refreshGet) { - # define global JS variables - $rot .= " "; - # Javascript and CSS - $rot .= " - - "; - - $ret .= "
\n"; - } - - foreach my $name (@devs) { - - $r++; - - my $hash = $defs{$name}; - my $id = $defs{$name}{NR}; - - my $countList = ReadingsVal($name,"count",0); - my $hLIE = AttrVal($name,"hideListIfEmpty",0); - my $showList = ($hLIE==1 && $countList==0)?0:1; - - # show active lists only - if (!IsDisabled($name) && ($showList || $detail)) { - - # refresh request? don't return everything - if (!$refreshGet) { - - $ret .= "\n"; - - $ret .= "\n". - " \n". - "\n"; - $ret .= "\n"; - - $ret .= "
\n". - " \n". - "
{PID}."\">\n"; - - } - - my $i=1; - - my $cs=3; - - # show data - foreach (@{$hash->{helper}{TIDS}}) { - - if ($i%2==0) { - $eo="even"; - } - else { - $eo="odd"; - } - - my $ind=0; - - - my $dueDate = defined($hash->{helper}{DUE_DATE}{$_})?$hash->{helper}{DUE_DATE}{$_}:""; - my $responsibleUid = defined($hash->{helper}{RESPONSIBLE_UID}{$_})?$hash->{helper}{RESPONSIBLE_UID}{$_}:""; - - $responsibleUid = $hash->{helper}{USER}{NAME}{$responsibleUid} if ($responsibleUid ne "" && defined($hash->{helper}{USER}{NAME}{$responsibleUid})); - - my $dueDateClass = $dueDate ne ""?" todoist_dueDate":""; - my $responsibleUidClass = $responsibleUid ne ""?" todoist_responsibleUid":""; - - $ret .= "{PID}."\" data-line-id=\"".$_."\" class=\"sortit todoist_data ".$eo."\">\n". - " \n". - " \n"; - - $ret .= "\n"; - - $ret .= "\n"; - - $i++; - } - - # refresh request? don't return everything - if (!$refreshGet) { - - my $showPH = 0; - $showPH = 1 if ($i==1); - - $ret .= ""; - $ret .= " "; - $ret .= ""; - - - $ret .= ""; - - - $ret .= ""; - - $ret .= ""; - - $ret .= "
\n". - "
\n". - " {'check'}."\" class=\"todoist_checkbox_".$name."\" type=\"checkbox\" id=\"check_".$_."\" data-id=\"".$_."\" />\n". - "
\n". - " ".$hash->{helper}{TITLE}{$_}."\n". - " {helper}{TITLE}{$_}."\" />\n". - "
". - "
". - "
\n". - " {'delete'}."\" href=\"#\" class=\"todoist_delete_".$name."\" data-id=\"".$_."\">\n". - " x\n". - " \n". - "
". - " ".$todoist_tt->{'nolistdata'}.".\n". - "
". - " \n". - " {'newentry'}."\" type=\"text\" id=\"newEntry_".$name."\" />\n". - "
\n"; - - } - } - } - - # refresh request? don't return everything - if (!$refreshGet) { - - $ret .= "
\n"; - $ret .= "
"; - - } - - return $rot.$ret; -} - -# called if weblink widget table has to be updated -sub todoist_ReloadTable($) { - my ($name) = @_; - - my $ret = todoist_Html($name,1); - $ret =~ s/\"/\'/g; - $ret =~ s/\n//g; - - map {FW_directNotify("#FHEMWEB:$_", "if (typeof todoist_reloadTable === \"function\") todoist_reloadTable('$name',\"$ret\")", "")} devspec2array("TYPE=FHEMWEB"); -} - -# check if element is in array -sub todoist_inArray { - my ($arr,$search_for) = @_; - foreach (@$arr) { - return 1 if ($_ eq $search_for); - } - return 0; -} - -sub todoist_genUUID() { - srand(gettimeofday()) if(!$srandUsed); - $srandUsed = 1; - my $uuid = sprintf("%08x-f33f-%s-%s-%s", time(), substr(getUniqueId(),-4), - join("",map { unpack "H*", chr(rand(256)) } 1..2), - join("",map { unpack "H*", chr(rand(256)) } 1..8)); - return $uuid; -} - -1; - -=pod -=item device -=item summary uses todoist API to add, read, complete and delete tasks in a todoist tasklist -=item summary_DE Taskverwaltung einer todoist Taskliste über die todoist API -=begin html - - -

todoist

-
    - A module to get a task list as readings from todoist. Tasks can be completed, updated and deleted. - The modul comes with a weblink-widget to manage tasks comfortable. -

    - As preparation to use this module, you need to get your API Symbol (API ID) from the - preferences of your account. -

    - Notes:
    -
      -
    • JSON, Data::Dumper, Date::Parse, Digest::MD5, have to be installed on the FHEM host.
    • -
    -

    - -

    Define

    -
      - define <name> todoist <PROJECT-ID>
      -
      - PROJECT-ID: The ID of your project (=list).
      -

      - - Example: -
        - define Einkaufsliste todoist 257528237
        -
      -

    - -

    Set

    -
      -
    • accessToken - set the API Symbol for your todoist app

    • -
    • active - set the device active (starts the timer for reading task-list periodically)

    • -
    • inactive - set the device inactive (deletes the timer, polling is off)

    • -
    • newAccessToken - replace the saved token with a new one.

    • -
    • getTasks - get the task list immediately, reset timer.

    • -
    • getUsers - get the projects users immediately.

    • -
    • addTask - create a new task. Needs title as parameter.


    • - set <DEVICE> addTask <TASK_TITLE>[:<DUE_DATE>]

      - Additional Parameters are:
      -
        -
      • title="<TITLE>" (string)
      • -
      • dueDate (due_date)=<DUE_DATE> (can be free form text or format: YYYY-MM-DDTHH:MM)
      • -
      • priority=the priority of the task (a number between 1 and 4, 4 for very urgent and 1 for natural).
      • -
      • responsibleUid=the todoist-ID of the user who is responsible for accomplishing the current task
      • -
      • assignedByUid=the todoist-ID of the user who assigned the current task
      • -
      • order=the order of the task inside a project (the smallest value would place the task at the top)
      • -
      • parentID=parent_id of the parent task.
      • -
      • sectionID=section_id of the parent task.
      • - -

      - Examples:

      - set <DEVICE> addTask <TASK_TITLE> dueDate=2017-01-15 priority=2

      - set <DEVICE> addTask <TASK_TITLE> dueDate=morgen

      -
    • updateTask - update a task. Needs Task-ID or todoist-Task-ID as parameter

      - Possible additional parameters are:
      -
        -
      • dueDate (due_date)=<DUE_DATE> (can be free form text or format: YYYY-MM-DDTHH:MM)
      • -
      • priority=(1..4) (string)
      • -
      • title=<TITLE> (string)
      • -
      • responsibleUid=the todoist-ID of the user who is responsible for accomplishing the current task
      • -
      • assignedByUid=the todoist-ID of the user who assigned the current task
      • -
      • order=the order of the task inside a project (the smallest value would place the task at the top)
      • -
      • remove=<TYPE> (comma seperated list of attributes which should be removed from the task)
      • -

      - Examples:

      - set <DEVICE> updateTask ID:12345678 dueDate=2017-01-15 priority=1
      - set <DEVICE> updateTask 1 dueDate=übermorgen
      - set <DEVICE> updateTask TITLE:Brot dueDate=übermorgen

    • -
    • moveTask - move a task to another parent, section or project. - Expects Task-ID or todoist-Task-ID as parameter

      - Possible additional parameters are:
      -
        -
      • parentID=todoist-ID of the new parent task.
      • -
      • projectID=todoist-ID of the receiving project.
      • -
      • sectionID=todoist-ID of the receiving section.
      • -


    • -
    • completeTask - completes a task. Needs number of task (reading 'Task_NUMBER'), the title (TITLE:<TITLE>) or the - todoist-Task-ID (ID:<ID>) as parameter

      - set <DEVICE> completeTask <TASK-ID> - completes a task by number
      - set <DEVICE> completeTask ID:<todoist-TASK-ID> - completes a task by todoist-Task-ID
      - set <DEVICE> completeTask TITLE:<Task title> - completes a task by title (one word)
      - set <DEVICE> completeTask title=<Task title> - completes a task by title (multiple words)
      -
    • -
    • closeTask - closes a task. Needs number of task (reading 'Task_NUMBER')m the title (TITLE:<TITLE>) or the - todoist-Task-ID (ID:) as parameter
      - Difference to complete is: regular task is completed and moved to history, subtask is checked (marked as done, but not moved to history),
      - recurring task is moved forward (due date is updated).

      - set <DEVICE> closeTask <TASK-ID> - completes a task by number
      - set <DEVICE> closeTask ID:<todoist-TASK-ID> - completes a task by todoist-Task-ID
      - set <DEVICE> closeTask TITLE:<Task title> - completes a task by title (one word)
      - set <DEVICE> closeTask title=<Task title> - completes a task by title (multiple words)

    • -
    • uncompleteTask - uncompletes a Task. Use it like complete.

    • -
    • deleteTask - deletes a task. Needs number of task (reading 'Task_NUMBER'), title (TITLE:<TITLE>) or the todoist-Task-ID (ID:<ID>) as parameter

      - set <DEVICE> deleteTask <TASK-ID> - deletes a task by number
      - set <DEVICE> deleteTask ID:<todoist-TASK-ID> - deletes a task by todoist-Task-ID
      - set <DEVICE> deleteTask TITLE:<Task title> - deletes a task by title (one word)
      - set <DEVICE> deleteTask title=<Task title> - completes a task by title (multiple words)

    • -
    • sortTasks - sort Tasks alphabetically

    • -
    • clearList - deletes all Tasks from the list (only FHEM listed Tasks can be deleted)

    • -
    • cChildProjects - searches for children and defines them if possible, deletes lists that are deleted - in todoist (Attribut delDeletedLists)

    • -
    • reorderTasks - expects a comma seperated list of todoist-IDs. Tasks will be reorders in the given order
      - Example:

      - set <DEVICE> reorderTasks 12345678,23456789,34567890

      -
    • -
    -
    - -

    Attributes

    -
      -
    • readingFnAttributes
    • -
    • do_not_notify
    • -
    • disable
    • -
    • pollInterval - get the list every pollInterval seconds. Default is 1800. Smallest possible value is 20.

    • -
      -
    • hideId -
        -
      • 0: show todoist-Task-ID (default)
      • -
      • 1: hide the todoist-Task-ID
      • -
    • -
      -
    • hideListIfEmpty -
        -
      • 0: don't hide list in widget, if empty (default)
      • -
      • 1: hide list in widget, if empty
      • -
    • -
      -
    • showPriority -
        -
      • 0: don't show priority (default)
      • -
      • 1: show priority
      • -
    • -
      -
    • showAssignedBy -
        -
      • 0: don't show assignedByUid (default)
      • -
      • 1: show assignedByUid
      • -
    • -
      -
    • showOrder -
        -
      • 0: don't show order no. of the task (default)
      • -
      • 1: show order number
      • -
    • -
      -
    • showParent -
        -
      • 0: don't show parent_id of the task
      • -
      • 1: show parent_id (default)
      • -
    • -
      -
    • showSection -
        -
      • 0: don't show section_id of the task
      • -
      • 1: show section_id (default)
      • -
    • -
      -
    • showChecked -
        -
      • 0: don't show if a task is checked (for tasks with parent_id)
      • -
      • 1: show if a task is checked (default)
      • -
    • -
      -
    • showDeleted -
        -
      • 0: don't show if a task is deleted (for tasks with parent_id)
      • -
      • 1: show if a task is deleted (default)
      • -
    • -
      -
    • showResponsible -
        -
      • 0: don't show responsibleUid (default)
      • -
      • 1: show responsibleUid
      • -
    • -
      -
    • showDetailWidget -
        -
      • 0: don't show widget in detail view of device
      • -
      • 1: show widget in detail view of device (default)
      • -
    • -
      -
    • getCompleted -
        -
      • 0: don't get completet tasks (default)
      • -
      • 1: get completed tasks
      • -

      - ATTENTION: Only premium users have access to completed tasks!
    • -

      -
    • autoGetUsers -
        -
      • 0: don't get users automatically
      • -
      • 1: get users after every "getTasks" (default)
      • -
    • -
      -
    • avoidDuplicates -
        -
      • 0: don't check for duplicates (default)
      • -
      • 1: check for duplicates, deny new entries if task exists (exactly)
      • -
    • -
      -
    • delDeletedLists -
        -
      • 0: don't delete deleted childrens (default)
      • -
      • 1: delete deleted childrens
      • -
    • -
      -
    • listDivider
      - set the divider for the Reading listText. Default is ", ".
    • -
    - - -

    Readings

    -
      -
    • Task_XXX
      - the tasks are listet as Task_000, Task_001 [...].
    • -
    • Task_XXX_parentID
      - parent ID of task XXX if not null
    • -
    • Task_XXX_sectionID
      - section ID of task XXX if not null
    • -
    • Task_XXX_checked
      - 1 when a task with parent_id is checked
    • -
    • Task_XXX_isDeleted
      - 1 when a task with parent_id is deleted
    • -
    • Task_XXX_dueDate
      - if a task has a due date, this reading should be filled with the date.
    • -
    • Task_XXX_priority
      - the priority of your task.
    • -
    • Task_XXX_ID
      - the todoist ID of Task_X.
    • -
    • Task_XXX_completedAt
      - only for completed Tasks (attribute getCompleted).
    • -
    • Task_XXX_completedById
      - only for completed Tasks (attribute getCompleted).
    • -
    • Task_XXX_assignedByUid
      - the user this task was assigned by.
    • -
    • Task_XXX_responsibleUid
      - the user this task was assigned to.
    • -
    • Task_XXX_order
      - shows the order no. of the task (attribute showOrder).
    • -
    • User_XXX
      - the lists users are listet as User_000, User_001 [...].
    • -
    • User_XXX_ID
      - the users todoist ID.
    • -
    • listText
      - a comma seperated list of tasks in the specified list. This may be used for TTS, Messages etc.
    • -
    • count
      - number of Tasks in list.
    • -
    • error
      - current error. Default is none.
    • -
    • lastCompletedTask
      - title of the last completed task.
    • -
    • lastCreatedTask
      - title of the last created task.
    • -
    • lastDeletedTask
      - title of the last deleted task.
    • -
    • lastError
      - last Error.
    • -
    • state
      - state of the todoist-Device
    • -

    - -

    Weblinks

    - FHEMWEB weblink as frontend for one or more. There are 3 different ways to setup a todoist-weblink:

    -
      -
    • Define a simple weblink for a Task list: -

      - define <NAME> weblink htmlCode {todoist_Html("<TODOIST-DEVCICENAME>")}

    • -
    • Define a simple weblink for all active Task lists: -

      - define <NAME> weblink htmlCode {todoist_Html()}

    • -
    • Define a simple weblink for a group of active Task lists: -

      - define <NAME> weblink htmlCode {todoist_Html('<REGEX-FILTER>')}

    • -
    - Examples:

    - define EinkaufslisteWeb weblink htmlCode {todoist_Html("Einkaufsliste")} - single list
    - define EinkaufslistenWeb weblink htmlCode {todoist_Html()} - all active lists
    - define PapaLists weblink htmlCode {todoist_Html("Papa_list.*")} - all active lists beginning with "Papa_list" -

    -
      -
    • Tasks can be sorted, updated and added inside the widget. You can reload a list and delete all entries of a list.
    • -
    • To check (close) a task, click the checkbox in front of the completed task.
    • -
    • A list can be sorted by by drag & drop. You can also move a task from one list to another. This deletes the task from the "old" list and
      - adds it to the new one.
    • -
    • The input field under the actual list can be used for adding tasks to this list. Leaving the field or pressing enter triggers addTask.
    • -
    • Update a task by clicking it's text. You can edit the task in an input field. Leaving the field or pressing enter triggers updateTask.
    • -
    • You can use parameters like "dueDate" in the input fields like this: -
        - Milch dueDate=morgen -
      -
    • -
    • Clicking the cross behind every task deletes this one for good.
    • -
    • Clicking the trashcan symbol in the title bar of every list deletes all tasks from the list.
    • -
    • A list can be reloaded by clicking the round arrow symbol in the title bar of every list.
    • -
    • A list is refreshed if a getTasks for this is fired (by interval or manually).
    • -
    -
- - -=end html - -=cut \ No newline at end of file