From b4ff4bd25b3759fe90dd0e76d06f643fe37a860a Mon Sep 17 00:00:00 2001 From: Manfred Date: Wed, 2 Nov 2022 22:31:33 +0100 Subject: [PATCH 1/3] package initialized many errors, don't know what to do. --- FHEM/98_Matrix.pm | 468 ++------------------------------------ FHEM/lib/Device/Matrix.pm | 437 +++++++++++++++++++++++++++++++++++ hooks/pre-commit | 39 ++++ 3 files changed, 499 insertions(+), 445 deletions(-) create mode 100644 FHEM/lib/Device/Matrix.pm create mode 100644 hooks/pre-commit diff --git a/FHEM/98_Matrix.pm b/FHEM/98_Matrix.pm index a5ecf61..71306ab 100644 --- a/FHEM/98_Matrix.pm +++ b/FHEM/98_Matrix.pm @@ -25,465 +25,43 @@ ########################################################################## # $Id: 98_Matrix.pm 28158 2022-11-02 19:56:00Z Man-fred $ -package main; +package FHEM::Matrix; use strict; use warnings; use HttpUtils; -use vars qw(%data); +use FHEM::Meta; +use GPUtils qw(GP_Export); -my $Module_Version = '0.0.7'; +require FHEM::Device::Matrix; -my $AttrList = "MatrixRoom MatrixSender MatrixQuestion_0 MatrixQuestion_1 " . $readingFnAttributes; +#-- Run before package compilation +BEGIN { -sub Matrix_PerformHttpRequest($$$) -{ - my ($hash, $def, $value) = @_; - my $now = gettimeofday(); - my $name = $hash->{NAME}; - my $param = { - timeout => 10, - hash => $hash, # Muss gesetzt werden, damit die Callback funktion wieder $hash hat - def => $def, # sichern für eventuelle Wiederholung - value => $value, # sichern für eventuelle Wiederholung - method => "POST", # standard, sonst überschreiben - header => "User-Agent: HttpUtils/2.2.3\r\nAccept: application/json", # Den Header gemäß abzufragender Daten setzen - callback => \&Matrix_ParseHttpResponse # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten - }; - $data{MATRIX}{"$name"}{"busy"} = $data{MATRIX}{"$name"}{"busy"} + 1; # queue is busy until response is received - $data{MATRIX}{"$name"}{'LASTSEND'} = $now; # remember when last sent - if ($def eq "sync" && $data{MATRIX}{"$name"}{"next_refresh"} < $now){ - $def = "refresh"; - $data{MATRIX}{"$name"}{"next_refresh"} = $now + 300; - } - - my $device_id = ReadingsVal($name, 'device_id', undef) ? ', "device_id":"'.ReadingsVal($name, 'device_id', undef).'"' : ""; - if ($def eq "register"){ - $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; - $param->{'data'} = '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$hash->{password}.'"}'; - } - if ($def eq"reg2"){ - $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; - $param->{'data'} = '{"username":"'.$hash->{user}.'", "password":"'.$hash->{password}.'", "auth": {"session":"'.$data{MATRIX}{"$name"}{"session"}.'","type":"m.login.dummy"}}'; - } - if ($def eq "login"){ - $param->{'url'} = $hash->{server}."/_matrix/client/v3/login"; - $param->{'data'} = '{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$hash->{password}.'"' - .$device_id.'}'; - } - if ($def eq "refresh"){ - $param->{'url'} = $hash->{server}.'/_matrix/client/v1/refresh'; - $param->{'data'} = '{"refresh_token": "'.$data{MATRIX}{"$name"}{"refresh_token"}.'"}'; - } - if ($def eq "wellknown"){ - $param->{'url'} = $hash->{server}."/.well-known/matrix/client"; - } - if ($def eq "msg"){ - $param->{'url'} = $hash->{server}.'/_matrix/client/r0/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.room.message?access_token='.$data{MATRIX}{"$name"}{"access_token"}; - $param->{'data'} = '{"msgtype":"m.text", "body":"'.$value.'"}'; - } - if ($def eq "question.start"){ - $value = AttrVal($name, "MatrixQuestion_$value",$value); # if ($value =~ /[0-9]/); - my @question = split(':',$value); - my $size = @question; - $value =~ tr/:/
/; - # min. question and one answer - if (int(@question) >= 2){ - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.poll.start?access_token='.$data{MATRIX}{"$name"}{"access_token"}; - $param->{'data'} = '{"org.matrix.msc3381.poll.start": {"max_selections": 1,'. - '"question": {"org.matrix.msc1767.text": "'.$question[0].'"},'. - '"kind": "org.matrix.msc3381.poll.undisclosed",'. - '"answers": [{"id": "'.$question[1].'", "org.matrix.msc1767.text": "'.$question[1].'"},{"id":"'.$question[2].'","org.matrix.msc1767.text": "'.$question[2].'"}],'. - '"org.matrix.msc1767.text": "'.$value.'"}}'; - } else { - Log3 $name, 3, "question.start: $value $size $question[0]"; - return; - } - } - if ($def eq "question.end"){ - $value = ReadingsVal($name, "question_id", "") if (!$value); - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.poll.end?access_token='.$data{MATRIX}{"$name"}{"access_token"}; - # ""'.ReadingsVal($name, 'questionEventId', '!!').' - $param->{'data'} = '{"m.relates_to": {"rel_type": "m.reference","event_id": "'.$value.'"},"org.matrix.msc3381.poll.end": {},'. - '"org.matrix.msc1767.text": "Antort '.ReadingsVal($name, "answer", "").' erhalten von '.ReadingsVal($name, "sender", "").'"}'; - } - if ($def eq "sync"){ - my $since = ReadingsVal($name, "since", undef) ? '&since='.ReadingsVal($name, "since", undef) : ""; - my $full_state = ReadingsVal($name, "poll.fullstate",undef); - if ($full_state){ - $full_state = "&full_state=true"; - readingsSingleUpdate($hash, "poll.fullstate", 0, 1); - } else { - $full_state = ""; - } - $param->{'url'} = $hash->{server}.'/_matrix/client/r0/sync?access_token='.$data{MATRIX}{"$name"}{"access_token"}.$since.$full_state.'&timeout=50000&filter='.ReadingsVal($name, 'filter_id',0); - $param->{'method'} = 'GET'; - $param->{'timeout'} = 60; - } - if ($def eq "filter"){ - if ($value){ # get - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "user_id",0).'/filter/'.$value.'?access_token='.$data{MATRIX}{"$name"}{"access_token"}; - $param->{'method'} = 'GET'; - } else { - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "user_id",0).'/filter?access_token='.$data{MATRIX}{"$name"}{"access_token"}; - $param->{'data'} = '{'; - $param->{'data'} .= '"event_fields": ["type","content","sender"],'; - $param->{'data'} .= '"event_format": "client", '; - $param->{'data'} .= '"presence": { "senders": [ "@xx:example.com"]}'; # no presence - #$param->{'data'} .= '"room": { "ephemeral": {"rooms": ["'.AttrVal($name, 'MatrixRoom', '!!').'"],"types": ["m.receipt"]}, "state": {"types": ["m.room.*"]},"timeline": {"types": ["m.room.message"] } }'; - $param->{'data'} .= '}'; - } - } - - my $test = "$param->{url}, " - . ( $param->{data} ? "\r\ndata: $param->{data}, " : "" ) - . ( $param->{header} ? "\r\nheader: $param->{header}" : "" ); - readingsSingleUpdate($hash, "fullRequest", $test, 1); # Readings erzeugen - $test = "$name: Matrix sends with timeout $param->{timeout} to ".$test; - Log3 $name, 3, $test; - - HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. - return undef; -} - -sub Matrix_ParseHttpResponse($) -{ - my ($param, $err, $data) = @_; - my $hash = $param->{hash}; - my $def = $param->{def}; - my $value = $param->{value}; - my $name = $hash->{NAME}; - my $now = gettimeofday(); - my $nextRequest = ""; - - readingsBeginUpdate($hash); - ###readingsBulkUpdate($hash, "httpHeader", $param->{httpheader}); - readingsBulkUpdate($hash, "httpStatus", $param->{code}); - $hash->{STATE} = $def.' - '.$param->{code}; - if($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist - Log3 $name, 3, "error while requesting ".$param->{url}." - $err"; # Eintrag fürs Log - readingsBulkUpdate($hash, "fullResponse", "ERROR ".$err); # Readings erzeugen - $data{MATRIX}{"$name"}{"FAILS"} = 3; - } - elsif($data ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) - Log3 $name, 3, $def." returned: $data"; # Eintrag fürs Log - my $decoded = eval { decode_json($data) }; - Log3 $name, 2, "$name: json error: $@ in data" if( $@ ); - if ($param->{code} == 200){ - $data{MATRIX}{"$name"}{"FAILS"} = 0; - } else { - $data{MATRIX}{"$name"}{"FAILS"}++; - } - - # default next request - $nextRequest = "sync" ; - # An dieser Stelle die Antwort parsen / verarbeiten mit $data - - # "errcode":"M_UNKNOWN_TOKEN: login or refresh - readingsBulkUpdate($hash, "fullResponse", $data); - my $errcode = $decoded->{'errcode'} ? $decoded->{'errcode'} : ""; - if ($errcode eq "M_UNKNOWN_TOKEN"){ - $data{MATRIX}{"$name"}{"repeat"} = $param if ($def ne "sync"); - if ($decoded->{'error'} eq "Access token has expired"){ - if ($decoded->{'soft_logout'} eq "true"){ - $nextRequest = 'refresh'; - }else{ - $nextRequest = 'login'; - } - } elsif ($decoded->{'error'} eq "refresh token does not exist"){ - $nextRequest = 'login'; - } - } - - if ($def eq "register"){ - $data{MATRIX}{"$name"}{"session"} = $decoded->{'session'}; - $nextRequest = "reg2"; - } - if ($param->{code} == 200 && ($def eq "reg2" || $def eq "login" || $def eq "refresh")){ - readingsBulkUpdate($hash, "user_id", $decoded->{'user_id'}) if ($decoded->{'user_id'}); - readingsBulkUpdate($hash, "home_server", $decoded->{'home_server'}) if ($decoded->{'home_server'}); - readingsBulkUpdate($hash, "device_id", $decoded->{'device_id'}) if ($decoded->{'device_id'}); - readingsBulkUpdate($hash, "last_register", $param->{code}) if $def eq "reg2"; - readingsBulkUpdate($hash, "last_login", $param->{code}) if $def eq "login"; - readingsBulkUpdate($hash, "last_refresh", $param->{code}) if $def eq "refresh"; - - $data{MATRIX}{"$name"}{"expires"} = $decoded->{'expires_in_ms'} if ($decoded->{'expires_in_ms'}); - $data{MATRIX}{"$name"}{"refresh_token"} = $decoded->{'refresh_token'} if ($decoded->{'refresh_token'}); - $data{MATRIX}{"$name"}{"access_token"} = $decoded->{'access_token'} if ($decoded->{'access_token'}); - $data{MATRIX}{"$name"}{"next_refresh"} = $now + $data{MATRIX}{"$name"}{"expires"}/1000 - 60; # refresh one minute before end - } - if ($def eq "wellknown"){ - # https://spec.matrix.org/unstable/client-server-api/ - } - if ($param->{code} == 200 && $def eq "sync"){ - readingsBulkUpdate($hash, "since", $decoded->{'next_batch'}) if ($decoded->{'next_batch'}); - # roomlist - my $list = $decoded->{'rooms'}->{'join'}; - #my @roomlist = (); - my $pos = 0; - foreach my $id ( keys $list->%* ) { - if (ref $list->{$id} eq ref {}) { - my $member = ""; - #my $room = $list->{$id}; - $pos = $pos + 1; - # MatrixRoom ? - readingsBulkUpdate($hash, "room$pos.id", $id); - #foreach my $id ( $decoded->{'rooms'}->{'join'}->{AttrVal($name, 'MatrixRoom', '!!')}->{'timeline'}->{'events'}->@* ) { - foreach my $ev ( $list->{$id}->{'state'}->{'events'}->@* ) { - readingsBulkUpdate($hash, "room$pos.topic", $ev->{'content'}->{'topic'}) if ($ev->{'type'} eq 'm.room.topic'); - readingsBulkUpdate($hash, "room$pos.name", $ev->{'content'}->{'name'}) if ($ev->{'type'} eq 'm.room.name'); - $member .= "$ev->{'sender'} " if ($ev->{'type'} eq 'm.room.member'); - } - readingsBulkUpdate($hash, "room$pos.member", $member); - foreach my $tl ( $list->{$id}->{'timeline'}->{'events'}->@* ) { - readingsBulkUpdate($hash, "room$pos.topic", $tl->{'content'}->{'topic'}) if ($tl->{'type'} eq 'm.room.topic'); - readingsBulkUpdate($hash, "room$pos.name", $tl->{'content'}->{'name'}) if ($tl->{'type'} eq 'm.room.name'); - if ($tl->{'type'} eq 'm.room.message' && $tl->{'content'}->{'msgtype'} eq 'm.text'){ - my $sender = $tl->{'sender'}; - if (AttrVal($name, 'MatrixSender', '') =~ $sender){ - readingsBulkUpdate($hash, "message", $tl->{'content'}->{'body'}); - readingsBulkUpdate($hash, "sender", $sender); - # command - - } - #else { - # readingsBulkUpdate($hash, "message", 'ignoriert, nicht '.AttrVal($name, 'MatrixSender', '')); - # readingsBulkUpdate($hash, "sender", $sender); - #} - } elsif ($tl->{'type'} eq "org.matrix.msc3381.poll.response"){ - my $sender = $tl->{'sender'}; - if (AttrVal($name, 'MatrixSender', '') =~ $sender){ - readingsBulkUpdate($hash, "answer", $tl->{'content'}->{'org.matrix.msc3381.poll.response'}->{'answers'}[0]); - readingsBulkUpdate($hash, "sender", $sender); - # poll.end and - $nextRequest = "question.end" ; - # command - - } - } - } - #push(@roomlist,"$id: "; - } - } - } - if ($def eq "filter"){ - readingsBulkUpdate($hash, "filter_id", $decoded->{'filter_id'}) if ($decoded->{'filter_id'}); - } - if ($def eq "msg" ){ - readingsBulkUpdate($hash, "event_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); - #m.relates_to - } - if ($def eq "question.start"){ - readingsBulkUpdate($hash, "question_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); - #m.relates_to - } - if ($def eq "question.end"){ - readingsBulkUpdate($hash, "event_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); - readingsBulkUpdate($hash, "question_id", "") if ($decoded->{'event_id'}); - #m.relates_to - } - } - readingsEndUpdate($hash, 1); - $data{MATRIX}{"$name"}{"busy"} = $data{MATRIX}{"$name"}{"busy"} - 1; # queue is busy until response is received - $data{MATRIX}{"$name"}{"sync"} = 0 if ($def eq "sync"); # possible next sync - $nextRequest = "" if ($nextRequest eq "sync" && $data{MATRIX}{"$name"}{"sync"} == 1); # only one sync at a time! - - #if ($def eq "sync" && $nextRequest eq "sync" && ReadingsVal($name,'poll',0) == 1 && $data{MATRIX}{"$name"}{"FAILS"} < 3){ - # Matrix_PerformHttpRequest($hash, $nextRequest, ''); - #} els - if ($nextRequest ne "" && ReadingsVal($name,'poll',0) == 1 && $data{MATRIX}{"$name"}{"FAILS"} < 3) { - if ($nextRequest eq "sync" && $data{MATRIX}{"$name"}{"repeat"}){ - $def = $data{MATRIX}{"$name"}{"repeat"}->{"def"}; - $value = $data{MATRIX}{"$name"}{"repeat"}->{"value"}; - $data{MATRIX}{"$name"}{"repeat"} = undef; - Matrix_PerformHttpRequest($hash, $def, $value); - } else { - Matrix_PerformHttpRequest($hash, $nextRequest, ''); - } - } - # Damit ist die Abfrage zuende. + #-- Export to main context with different name + GP_Export( + qw( + Initialize + ) + ); } sub Matrix_Initialize { my ($hash) = @_; - $hash->{DefFn} = \&Matrix_Define; - $hash->{UndefFn} = \&Matrix_Undef; - $hash->{SetFn} = \&Matrix_Set; - $hash->{GetFn} = \&Matrix_Get; - $hash->{AttrFn} = \&Matrix_Attr; - $hash->{ReadFn} = \&Matrix_Read; - $hash->{RenameFn} = \&Matrix_Rename; - $hash->{NotifyFn} = \&Matrix_Notify; + $hash->{DefFn} = \&FHEM::Device::Matrix::Define; + $hash->{UndefFn} = \&FHEM::Device::Matrix::Undef; + $hash->{SetFn} = \&FHEM::Device::Matrix::Set; + $hash->{GetFn} = \&FHEM::Device::Matrix::Get; + $hash->{AttrFn} = \&FHEM::Device::Matrix::Attr; + $hash->{ReadFn} = \&FHEM::Device::Matrix::Read; + $hash->{RenameFn} = \&FHEM::Device::Matrix::Rename; + $hash->{NotifyFn} = \&FHEM::Device::Matrix::Notify; - $hash->{AttrList} = $AttrList." MatrixMessage"; -} + $hash->{AttrList} = "MatrixRoom MatrixSender MatrixQuestion_0 MatrixQuestion_1 MatrixMessage " . $::readingFnAttributes; -sub Matrix_Define { - my ($hash, $def) = @_; - my @param = split('[ \t]+', $def); - - if(int(@param) < 4) { - return "too few parameters: define Matrix "; - } - - $hash->{name} = $param[0]; - $hash->{server} = $param[2]; - $hash->{user} = $param[3]; - $hash->{password} = $param[4]; - - my $name = $param[0]; - #$data{MATRIX}{"$name"}{"FAILS"} = 0; - #$data{MATRIX}{"$name"}{"busy"} = 0; # queue is busy until response is received - #$data{MATRIX}{"$name"}{'LASTSEND'} = 0; # remember when last sent - #$data{MATRIX}{"$name"}{"expires"} = 0; - #$data{MATRIX}{"$name"}{"refresh_token"} = ""; - #$data{MATRIX}{"$name"}{"access_token"} = ""; - #$data{MATRIX}{"$name"}{"session"} = ""; # used for register - #$hash->{STATE} = "paused"; - $hash->{NOTIFYDEV} = "global"; - Matrix_Startproc($hash) if($init_done); - return ; -} + $hash->{parseParams} = 1; -sub Matrix_Undef { - my ($hash, $arg) = @_; - my $name = $hash->{NAME}; - # undef $data - $data{MATRIX}{"$name"} = undef; - return ; -} - -sub Matrix_Startproc { - my ($hash) = @_; - my $name = $hash->{NAME}; - Log3 $name, 1, "$name: Matrix_Startproc V".$hash->{ModuleVersion}." -> V".$Module_Version; - # Update necessary? - $hash->{ModuleVersion} = $Module_Version; -} - -########################## -sub Matrix_Notify($$) -{ - my ($hash, $dev) = @_; - my $name = $hash->{NAME}; - my $devName = $dev->{NAME}; - return "" if(IsDisabled($name)); - Log3 $name, 1, "$name : X_Notify $devName"; - my $events = deviceEvents($dev,1); - return if( !$events ); - - if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) - { - Matrix_Startproc($hash); - } - - foreach my $event (@{$events}) { - $event = "" if(!defined($event)); - ### Writing log entry - Log3 $name, 1, "$name : X_Notify $devName - $event"; - # Examples: - # $event = "readingname: value" - # or - # $event = "INITIALIZED" (for $devName equal "global") - # - # processing $event with further code - } - return undef; -} - -############################################################################################# -# called when the device gets renamed, copy from telegramBot -# in this case we then also need to rename the key in the token store and ensure it is recoded with new name -sub Matrix_Rename($$) { - my ($new,$old) = @_; - $data{MATRIX}{"$new"} = $data{MATRIX}{"$old"}; - $data{MATRIX}{"$old"} = undef; - - my $nhash = $defs{$new}; - - #my $token = Matrix_readToken( $nhash, $old ); - #Matrix_storeToken( $nhash, $token ); - - # remove old token with old name - my $index_old = "Matrix_" . $old . "_token"; - #setKeyValue($index_old, undef); -} - -sub Matrix_Get { - my ( $hash, $name, $opt, @args ) = @_; - - return "\"get $name\" needs at least one argument" unless(defined($opt)); - - my $value = shift @args; - - if ($opt eq "wellknown") { - return Matrix_PerformHttpRequest($hash, $opt, ''); - } - elsif ($opt eq "sync") { - $data{MATRIX}{"$name"}{"FAILS"} = 0; - return Matrix_PerformHttpRequest($hash, $opt, ''); - } - elsif ($opt eq "filter") { - return "\"get Matrix $opt\" needs at least two arguments" if (int(@args) < 1); - return Matrix_PerformHttpRequest($hash, $opt, $value); - } - return "Unknown argument $opt, choose one of filter sync wellknown"; -} - -sub Matrix_Set { - my ($hash, @param) = @_; - - #return '"set Matrix needs at least two arguments' if (int(@param) < 3); - - my $name = shift @param; - my $opt = shift @param; - my $value = join("", @param); - - if ($opt eq "msg") { - return Matrix_PerformHttpRequest($hash, $opt, $value); - } - elsif ($opt eq "poll" || $opt eq "poll.fullstate") { - readingsSingleUpdate($hash, $opt, $value, 1); # Readings erzeugen - } - elsif ($opt eq "filter") { - return Matrix_PerformHttpRequest($hash, $opt, ''); - } - elsif ($opt eq "question.start") { - return Matrix_PerformHttpRequest($hash, $opt, $value); - } - elsif ($opt eq "question.end") { - return Matrix_PerformHttpRequest($hash, $opt, $value); - } - elsif ($opt eq "register") { - return Matrix_PerformHttpRequest($hash, $opt, ''); # 2 steps (ToDo: 3 steps empty -> dummy -> registration_token o.a.) - } - elsif ($opt eq "login") { - return Matrix_PerformHttpRequest($hash, $opt, ''); - } - elsif ($opt eq "refresh") { - return Matrix_PerformHttpRequest($hash, $opt, ''); - } - else { - return "Unknown argument $opt, choose one of filter:noArg question.start question.end poll:0,1 poll.fullstate:0,1 msg register login:noArg refresh:noArg"; - } - - #return "$opt set to $value. Try to get it."; -} - - -sub Matrix_Attr { - my ($cmd,$name,$attr_name,$attr_value) = @_; - if($cmd eq "set") { - if($attr_name eq "xxMatrixRoom") { - $attr_value =~ tr/: /~:/; - addToDevAttrList("mt", "MatrixMessage:".$attr_value); - } elsif($attr_name eq "xxMatrixMessage") { - @_[3] =~ tr/~/:/; - } else { - return ; - } - } - return ; + return FHEM::Meta::InitMod( __FILE__, $hash ); } 1; diff --git a/FHEM/lib/Device/Matrix.pm b/FHEM/lib/Device/Matrix.pm new file mode 100644 index 0000000..ffe56b4 --- /dev/null +++ b/FHEM/lib/Device/Matrix.pm @@ -0,0 +1,437 @@ +my $Module_Version = '0.0.7'; + +sub Matrix_PerformHttpRequest($$$) +{ + my ($hash, $def, $value) = @_; + my $now = gettimeofday(); + my $name = $hash->{NAME}; + my $param = { + timeout => 10, + hash => $hash, # Muss gesetzt werden, damit die Callback funktion wieder $hash hat + def => $def, # sichern für eventuelle Wiederholung + value => $value, # sichern für eventuelle Wiederholung + method => "POST", # standard, sonst überschreiben + header => "User-Agent: HttpUtils/2.2.3\r\nAccept: application/json", # Den Header gemäß abzufragender Daten setzen + callback => \&Matrix_ParseHttpResponse # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten + }; + $data{MATRIX}{"$name"}{"busy"} = $data{MATRIX}{"$name"}{"busy"} + 1; # queue is busy until response is received + $data{MATRIX}{"$name"}{'LASTSEND'} = $now; # remember when last sent + if ($def eq "sync" && $data{MATRIX}{"$name"}{"next_refresh"} < $now){ + $def = "refresh"; + $data{MATRIX}{"$name"}{"next_refresh"} = $now + 300; + } + + my $device_id = ReadingsVal($name, 'device_id', undef) ? ', "device_id":"'.ReadingsVal($name, 'device_id', undef).'"' : ""; + if ($def eq "register"){ + $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; + $param->{'data'} = '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$hash->{password}.'"}'; + } + if ($def eq"reg2"){ + $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; + $param->{'data'} = '{"username":"'.$hash->{user}.'", "password":"'.$hash->{password}.'", "auth": {"session":"'.$data{MATRIX}{"$name"}{"session"}.'","type":"m.login.dummy"}}'; + } + if ($def eq "login"){ + $param->{'url'} = $hash->{server}."/_matrix/client/v3/login"; + $param->{'data'} = '{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$hash->{password}.'"' + .$device_id.'}'; + } + if ($def eq "refresh"){ + $param->{'url'} = $hash->{server}.'/_matrix/client/v1/refresh'; + $param->{'data'} = '{"refresh_token": "'.$data{MATRIX}{"$name"}{"refresh_token"}.'"}'; + } + if ($def eq "wellknown"){ + $param->{'url'} = $hash->{server}."/.well-known/matrix/client"; + } + if ($def eq "msg"){ + $param->{'url'} = $hash->{server}.'/_matrix/client/r0/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.room.message?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'data'} = '{"msgtype":"m.text", "body":"'.$value.'"}'; + } + if ($def eq "question.start"){ + $value = AttrVal($name, "MatrixQuestion_$value",$value); # if ($value =~ /[0-9]/); + my @question = split(':',$value); + my $size = @question; + $value =~ tr/:/
/; + # min. question and one answer + if (int(@question) >= 2){ + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.poll.start?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'data'} = '{"org.matrix.msc3381.poll.start": {"max_selections": 1,'. + '"question": {"org.matrix.msc1767.text": "'.$question[0].'"},'. + '"kind": "org.matrix.msc3381.poll.undisclosed",'. + '"answers": [{"id": "'.$question[1].'", "org.matrix.msc1767.text": "'.$question[1].'"},{"id":"'.$question[2].'","org.matrix.msc1767.text": "'.$question[2].'"}],'. + '"org.matrix.msc1767.text": "'.$value.'"}}'; + } else { + Log3 $name, 3, "question.start: $value $size $question[0]"; + return; + } + } + if ($def eq "question.end"){ + $value = ReadingsVal($name, "question_id", "") if (!$value); + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.poll.end?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + # ""'.ReadingsVal($name, 'questionEventId', '!!').' + $param->{'data'} = '{"m.relates_to": {"rel_type": "m.reference","event_id": "'.$value.'"},"org.matrix.msc3381.poll.end": {},'. + '"org.matrix.msc1767.text": "Antort '.ReadingsVal($name, "answer", "").' erhalten von '.ReadingsVal($name, "sender", "").'"}'; + } + if ($def eq "sync"){ + my $since = ReadingsVal($name, "since", undef) ? '&since='.ReadingsVal($name, "since", undef) : ""; + my $full_state = ReadingsVal($name, "poll.fullstate",undef); + if ($full_state){ + $full_state = "&full_state=true"; + readingsSingleUpdate($hash, "poll.fullstate", 0, 1); + } else { + $full_state = ""; + } + $param->{'url'} = $hash->{server}.'/_matrix/client/r0/sync?access_token='.$data{MATRIX}{"$name"}{"access_token"}.$since.$full_state.'&timeout=50000&filter='.ReadingsVal($name, 'filter_id',0); + $param->{'method'} = 'GET'; + $param->{'timeout'} = 60; + } + if ($def eq "filter"){ + if ($value){ # get + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "user_id",0).'/filter/'.$value.'?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'method'} = 'GET'; + } else { + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "user_id",0).'/filter?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'data'} = '{'; + $param->{'data'} .= '"event_fields": ["type","content","sender"],'; + $param->{'data'} .= '"event_format": "client", '; + $param->{'data'} .= '"presence": { "senders": [ "@xx:example.com"]}'; # no presence + #$param->{'data'} .= '"room": { "ephemeral": {"rooms": ["'.AttrVal($name, 'MatrixRoom', '!!').'"],"types": ["m.receipt"]}, "state": {"types": ["m.room.*"]},"timeline": {"types": ["m.room.message"] } }'; + $param->{'data'} .= '}'; + } + } + + my $test = "$param->{url}, " + . ( $param->{data} ? "\r\ndata: $param->{data}, " : "" ) + . ( $param->{header} ? "\r\nheader: $param->{header}" : "" ); + readingsSingleUpdate($hash, "fullRequest", $test, 1); # Readings erzeugen + $test = "$name: Matrix sends with timeout $param->{timeout} to ".$test; + Log3 $name, 3, $test; + + HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. + return undef; +} + +sub Matrix_ParseHttpResponse($) +{ + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $def = $param->{def}; + my $value = $param->{value}; + my $name = $hash->{NAME}; + my $now = gettimeofday(); + my $nextRequest = ""; + + readingsBeginUpdate($hash); + ###readingsBulkUpdate($hash, "httpHeader", $param->{httpheader}); + readingsBulkUpdate($hash, "httpStatus", $param->{code}); + $hash->{STATE} = $def.' - '.$param->{code}; + if($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + Log3 $name, 3, "error while requesting ".$param->{url}." - $err"; # Eintrag fürs Log + readingsBulkUpdate($hash, "fullResponse", "ERROR ".$err); # Readings erzeugen + $data{MATRIX}{"$name"}{"FAILS"} = 3; + } + elsif($data ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) + Log3 $name, 3, $def." returned: $data"; # Eintrag fürs Log + my $decoded = eval { decode_json($data) }; + Log3 $name, 2, "$name: json error: $@ in data" if( $@ ); + if ($param->{code} == 200){ + $data{MATRIX}{"$name"}{"FAILS"} = 0; + } else { + $data{MATRIX}{"$name"}{"FAILS"}++; + } + + # default next request + $nextRequest = "sync" ; + # An dieser Stelle die Antwort parsen / verarbeiten mit $data + + # "errcode":"M_UNKNOWN_TOKEN: login or refresh + readingsBulkUpdate($hash, "fullResponse", $data); + my $errcode = $decoded->{'errcode'} ? $decoded->{'errcode'} : ""; + if ($errcode eq "M_UNKNOWN_TOKEN"){ + $data{MATRIX}{"$name"}{"repeat"} = $param if ($def ne "sync"); + if ($decoded->{'error'} eq "Access token has expired"){ + if ($decoded->{'soft_logout'} eq "true"){ + $nextRequest = 'refresh'; + }else{ + $nextRequest = 'login'; + } + } elsif ($decoded->{'error'} eq "refresh token does not exist"){ + $nextRequest = 'login'; + } + } + + if ($def eq "register"){ + $data{MATRIX}{"$name"}{"session"} = $decoded->{'session'}; + $nextRequest = "reg2"; + } + if ($param->{code} == 200 && ($def eq "reg2" || $def eq "login" || $def eq "refresh")){ + readingsBulkUpdate($hash, "user_id", $decoded->{'user_id'}) if ($decoded->{'user_id'}); + readingsBulkUpdate($hash, "home_server", $decoded->{'home_server'}) if ($decoded->{'home_server'}); + readingsBulkUpdate($hash, "device_id", $decoded->{'device_id'}) if ($decoded->{'device_id'}); + readingsBulkUpdate($hash, "last_register", $param->{code}) if $def eq "reg2"; + readingsBulkUpdate($hash, "last_login", $param->{code}) if $def eq "login"; + readingsBulkUpdate($hash, "last_refresh", $param->{code}) if $def eq "refresh"; + + $data{MATRIX}{"$name"}{"expires"} = $decoded->{'expires_in_ms'} if ($decoded->{'expires_in_ms'}); + $data{MATRIX}{"$name"}{"refresh_token"} = $decoded->{'refresh_token'} if ($decoded->{'refresh_token'}); + $data{MATRIX}{"$name"}{"access_token"} = $decoded->{'access_token'} if ($decoded->{'access_token'}); + $data{MATRIX}{"$name"}{"next_refresh"} = $now + $data{MATRIX}{"$name"}{"expires"}/1000 - 60; # refresh one minute before end + } + if ($def eq "wellknown"){ + # https://spec.matrix.org/unstable/client-server-api/ + } + if ($param->{code} == 200 && $def eq "sync"){ + readingsBulkUpdate($hash, "since", $decoded->{'next_batch'}) if ($decoded->{'next_batch'}); + # roomlist + my $list = $decoded->{'rooms'}->{'join'}; + #my @roomlist = (); + my $pos = 0; + foreach my $id ( keys $list->%* ) { + if (ref $list->{$id} eq ref {}) { + my $member = ""; + #my $room = $list->{$id}; + $pos = $pos + 1; + # MatrixRoom ? + readingsBulkUpdate($hash, "room$pos.id", $id); + #foreach my $id ( $decoded->{'rooms'}->{'join'}->{AttrVal($name, 'MatrixRoom', '!!')}->{'timeline'}->{'events'}->@* ) { + foreach my $ev ( $list->{$id}->{'state'}->{'events'}->@* ) { + readingsBulkUpdate($hash, "room$pos.topic", $ev->{'content'}->{'topic'}) if ($ev->{'type'} eq 'm.room.topic'); + readingsBulkUpdate($hash, "room$pos.name", $ev->{'content'}->{'name'}) if ($ev->{'type'} eq 'm.room.name'); + $member .= "$ev->{'sender'} " if ($ev->{'type'} eq 'm.room.member'); + } + readingsBulkUpdate($hash, "room$pos.member", $member); + foreach my $tl ( $list->{$id}->{'timeline'}->{'events'}->@* ) { + readingsBulkUpdate($hash, "room$pos.topic", $tl->{'content'}->{'topic'}) if ($tl->{'type'} eq 'm.room.topic'); + readingsBulkUpdate($hash, "room$pos.name", $tl->{'content'}->{'name'}) if ($tl->{'type'} eq 'm.room.name'); + if ($tl->{'type'} eq 'm.room.message' && $tl->{'content'}->{'msgtype'} eq 'm.text'){ + my $sender = $tl->{'sender'}; + if (AttrVal($name, 'MatrixSender', '') =~ $sender){ + readingsBulkUpdate($hash, "message", $tl->{'content'}->{'body'}); + readingsBulkUpdate($hash, "sender", $sender); + # command + + } + #else { + # readingsBulkUpdate($hash, "message", 'ignoriert, nicht '.AttrVal($name, 'MatrixSender', '')); + # readingsBulkUpdate($hash, "sender", $sender); + #} + } elsif ($tl->{'type'} eq "org.matrix.msc3381.poll.response"){ + my $sender = $tl->{'sender'}; + if (AttrVal($name, 'MatrixSender', '') =~ $sender){ + readingsBulkUpdate($hash, "answer", $tl->{'content'}->{'org.matrix.msc3381.poll.response'}->{'answers'}[0]); + readingsBulkUpdate($hash, "sender", $sender); + # poll.end and + $nextRequest = "question.end" ; + # command + + } + } + } + #push(@roomlist,"$id: "; + } + } + } + if ($def eq "filter"){ + readingsBulkUpdate($hash, "filter_id", $decoded->{'filter_id'}) if ($decoded->{'filter_id'}); + } + if ($def eq "msg" ){ + readingsBulkUpdate($hash, "event_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); + #m.relates_to + } + if ($def eq "question.start"){ + readingsBulkUpdate($hash, "question_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); + #m.relates_to + } + if ($def eq "question.end"){ + readingsBulkUpdate($hash, "event_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); + readingsBulkUpdate($hash, "question_id", "") if ($decoded->{'event_id'}); + #m.relates_to + } + } + readingsEndUpdate($hash, 1); + $data{MATRIX}{"$name"}{"busy"} = $data{MATRIX}{"$name"}{"busy"} - 1; # queue is busy until response is received + $data{MATRIX}{"$name"}{"sync"} = 0 if ($def eq "sync"); # possible next sync + $nextRequest = "" if ($nextRequest eq "sync" && $data{MATRIX}{"$name"}{"sync"} == 1); # only one sync at a time! + + #if ($def eq "sync" && $nextRequest eq "sync" && ReadingsVal($name,'poll',0) == 1 && $data{MATRIX}{"$name"}{"FAILS"} < 3){ + # Matrix_PerformHttpRequest($hash, $nextRequest, ''); + #} els + if ($nextRequest ne "" && ReadingsVal($name,'poll',0) == 1 && $data{MATRIX}{"$name"}{"FAILS"} < 3) { + if ($nextRequest eq "sync" && $data{MATRIX}{"$name"}{"repeat"}){ + $def = $data{MATRIX}{"$name"}{"repeat"}->{"def"}; + $value = $data{MATRIX}{"$name"}{"repeat"}->{"value"}; + $data{MATRIX}{"$name"}{"repeat"} = undef; + Matrix_PerformHttpRequest($hash, $def, $value); + } else { + Matrix_PerformHttpRequest($hash, $nextRequest, ''); + } + } + # Damit ist die Abfrage zuende. +} + +sub Matrix_Define { + my ($hash, $def) = @_; + my @param = split('[ \t]+', $def); + + if(int(@param) < 4) { + return "too few parameters: define Matrix "; + } + + $hash->{name} = $param[0]; + $hash->{server} = $param[2]; + $hash->{user} = $param[3]; + $hash->{password} = $param[4]; + + my $name = $param[0]; + #$data{MATRIX}{"$name"}{"FAILS"} = 0; + #$data{MATRIX}{"$name"}{"busy"} = 0; # queue is busy until response is received + #$data{MATRIX}{"$name"}{'LASTSEND'} = 0; # remember when last sent + #$data{MATRIX}{"$name"}{"expires"} = 0; + #$data{MATRIX}{"$name"}{"refresh_token"} = ""; + #$data{MATRIX}{"$name"}{"access_token"} = ""; + #$data{MATRIX}{"$name"}{"session"} = ""; # used for register + #$hash->{STATE} = "paused"; + $hash->{NOTIFYDEV} = "global"; + Matrix_Startproc($hash) if($init_done); + return ; +} + +sub Matrix_Undef { + my ($hash, $arg) = @_; + my $name = $hash->{NAME}; + # undef $data + $data{MATRIX}{"$name"} = undef; + return ; +} + +sub Matrix_Startproc { + my ($hash) = @_; + my $name = $hash->{NAME}; + Log3 $name, 1, "$name: Matrix_Startproc V".$hash->{ModuleVersion}." -> V".$Module_Version; + # Update necessary? + $hash->{ModuleVersion} = $Module_Version; +} + +########################## +sub Matrix_Notify($$) +{ + my ($hash, $dev) = @_; + my $name = $hash->{NAME}; + my $devName = $dev->{NAME}; + return "" if(IsDisabled($name)); + Log3 $name, 1, "$name : X_Notify $devName"; + my $events = deviceEvents($dev,1); + return if( !$events ); + + if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) + { + Matrix_Startproc($hash); + } + + foreach my $event (@{$events}) { + $event = "" if(!defined($event)); + ### Writing log entry + Log3 $name, 1, "$name : X_Notify $devName - $event"; + # Examples: + # $event = "readingname: value" + # or + # $event = "INITIALIZED" (for $devName equal "global") + # + # processing $event with further code + } + return undef; +} + +############################################################################################# +# called when the device gets renamed, copy from telegramBot +# in this case we then also need to rename the key in the token store and ensure it is recoded with new name +sub Matrix_Rename($$) { + my ($new,$old) = @_; + $data{MATRIX}{"$new"} = $data{MATRIX}{"$old"}; + $data{MATRIX}{"$old"} = undef; + + my $nhash = $defs{$new}; + + #my $token = Matrix_readToken( $nhash, $old ); + #Matrix_storeToken( $nhash, $token ); + + # remove old token with old name + my $index_old = "Matrix_" . $old . "_token"; + #setKeyValue($index_old, undef); +} + +sub Matrix_Get { + my ( $hash, $name, $opt, @args ) = @_; + + return "\"get $name\" needs at least one argument" unless(defined($opt)); + + my $value = shift @args; + + if ($opt eq "wellknown") { + return Matrix_PerformHttpRequest($hash, $opt, ''); + } + elsif ($opt eq "sync") { + $data{MATRIX}{"$name"}{"FAILS"} = 0; + return Matrix_PerformHttpRequest($hash, $opt, ''); + } + elsif ($opt eq "filter") { + return "\"get Matrix $opt\" needs at least two arguments" if (int(@args) < 1); + return Matrix_PerformHttpRequest($hash, $opt, $value); + } + return "Unknown argument $opt, choose one of filter sync wellknown"; +} + +sub Matrix_Set { + my ($hash, @param) = @_; + + #return '"set Matrix needs at least two arguments' if (int(@param) < 3); + + my $name = shift @param; + my $opt = shift @param; + my $value = join("", @param); + + if ($opt eq "msg") { + return Matrix_PerformHttpRequest($hash, $opt, $value); + } + elsif ($opt eq "poll" || $opt eq "poll.fullstate") { + readingsSingleUpdate($hash, $opt, $value, 1); # Readings erzeugen + } + elsif ($opt eq "filter") { + return Matrix_PerformHttpRequest($hash, $opt, ''); + } + elsif ($opt eq "question.start") { + return Matrix_PerformHttpRequest($hash, $opt, $value); + } + elsif ($opt eq "question.end") { + return Matrix_PerformHttpRequest($hash, $opt, $value); + } + elsif ($opt eq "register") { + return Matrix_PerformHttpRequest($hash, $opt, ''); # 2 steps (ToDo: 3 steps empty -> dummy -> registration_token o.a.) + } + elsif ($opt eq "login") { + return Matrix_PerformHttpRequest($hash, $opt, ''); + } + elsif ($opt eq "refresh") { + return Matrix_PerformHttpRequest($hash, $opt, ''); + } + else { + return "Unknown argument $opt, choose one of filter:noArg question.start question.end poll:0,1 poll.fullstate:0,1 msg register login:noArg refresh:noArg"; + } + + #return "$opt set to $value. Try to get it."; +} + + +sub Matrix_Attr { + my ($cmd,$name,$attr_name,$attr_value) = @_; + if($cmd eq "set") { + if($attr_name eq "xxMatrixRoom") { + $attr_value =~ tr/: /~:/; + addToDevAttrList("mt", "MatrixMessage:".$attr_value); + } elsif($attr_name eq "xxMatrixMessage") { + @_[3] =~ tr/~/:/; + } else { + return ; + } + } + return ; +} diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100644 index 0000000..7c0320b --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,39 @@ +parseParams#!/usr/bin/perl -w + +use File::Basename; +use POSIX qw(strftime); +use strict; + +my @filenames = ( 'FHEM/98_Matrix.pm', + 'lib/FHEM/Devices/Matrix.pm' + ); + +my $controlsfile = 'controls_Matrix.txt'; + +open(FH, ">$controlsfile") || return("Can't open $controlsfile: $!"); + +for my $filename (@filenames) { + my @statOutput = stat($filename); + + if (scalar @statOutput != 13) { + printf 'error: stat has unexpected return value for ' . $filename . "\n"; + next; + } + + my $mtime = $statOutput[9]; + my $date = POSIX::strftime("%Y-%m-%d", localtime($mtime)); + my $time = POSIX::strftime("%H:%M:%S", localtime($mtime)); + my $filetime = $date."_".$time; + + my $filesize = $statOutput[7]; + + printf FH 'UPD ' . $filetime . ' ' . $filesize . ' ' .$filename . "\n"; +} + +close(FH); + +system("git add $controlsfile"); + +print 'Create controls File succesfully' . "\n"; + +exit 0; From 30a2c13ce04d7a7621b59d2f6b5486758542a9ea Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 2 Nov 2022 21:32:08 +0000 Subject: [PATCH 2/3] Automatic updated controls and CHANGED --- CHANGED | 3 +++ controls_matrix.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGED b/CHANGED index b9ed913..a1a376e 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,6 @@ +2022-11-02 - package initialized + +many errors, don't know what to do. 2022-10-31 - more readings and attributes tested msg from and to fhem, questions from fhem and answers from users with ending the question-task diff --git a/controls_matrix.txt b/controls_matrix.txt index e972e24..97641e1 100644 --- a/controls_matrix.txt +++ b/controls_matrix.txt @@ -1 +1 @@ -UPD 2022-10-31_14:14:16 26582 FHEM/98_Matrix.pm +UPD 2022-11-02_22:31:33 9756 FHEM/98_Matrix.pm From db19349628c72f7849323c7ac73ed7e8c1abbfac Mon Sep 17 00:00:00 2001 From: Manfred Date: Sat, 5 Nov 2022 12:30:17 +0100 Subject: [PATCH 3/3] changes completed --- FHEM/98_Matrix.pm | 126 +++++--- lib/FHEM/Devices/Matrix/Matrix.pm | 483 ++++++++++++++++++++++++++++++ 2 files changed, 565 insertions(+), 44 deletions(-) create mode 100644 lib/FHEM/Devices/Matrix/Matrix.pm diff --git a/FHEM/98_Matrix.pm b/FHEM/98_Matrix.pm index 71306ab..54e0bd8 100644 --- a/FHEM/98_Matrix.pm +++ b/FHEM/98_Matrix.pm @@ -23,64 +23,68 @@ # Usage: # ########################################################################## -# $Id: 98_Matrix.pm 28158 2022-11-02 19:56:00Z Man-fred $ +# $Id: 98_Matrix.pm 11656 2022-11-05 12:26:00Z Man-fred $ -package FHEM::Matrix; +package FHEM::Devices::Matrix; use strict; use warnings; use HttpUtils; use FHEM::Meta; -use GPUtils qw(GP_Export); +use GPUtils qw(GP_Export GP_Import); -require FHEM::Device::Matrix; +use JSON; +use vars qw(%data); +use FHEM::Core::Authentication::Passwords qw(:ALL); +#use FHEM::Core::Authentication::Passwords qw(&setStorePassword); +require FHEM::Devices::Matrix::Matrix; #-- Run before package compilation BEGIN { #-- Export to main context with different name - GP_Export( - qw( - Initialize - ) - ); + GP_Export(qw( + Initialize + )); + GP_Import(qw( + readingFnAttributes + )); } -sub Matrix_Initialize { +sub Initialize { my ($hash) = @_; - $hash->{DefFn} = \&FHEM::Device::Matrix::Define; - $hash->{UndefFn} = \&FHEM::Device::Matrix::Undef; - $hash->{SetFn} = \&FHEM::Device::Matrix::Set; - $hash->{GetFn} = \&FHEM::Device::Matrix::Get; - $hash->{AttrFn} = \&FHEM::Device::Matrix::Attr; - $hash->{ReadFn} = \&FHEM::Device::Matrix::Read; - $hash->{RenameFn} = \&FHEM::Device::Matrix::Rename; - $hash->{NotifyFn} = \&FHEM::Device::Matrix::Notify; - - $hash->{AttrList} = "MatrixRoom MatrixSender MatrixQuestion_0 MatrixQuestion_1 MatrixMessage " . $::readingFnAttributes; + $hash->{DefFn} = \&FHEM::Devices::Matrix::Define; + $hash->{UndefFn} = \&FHEM::Devices::Matrix::Undef; + $hash->{SetFn} = \&FHEM::Devices::Matrix::Set; + $hash->{GetFn} = \&FHEM::Devices::Matrix::Get; + $hash->{AttrFn} = \&FHEM::Devices::Matrix::Attr; + $hash->{ReadFn} = \&FHEM::Devices::Matrix::Read; + $hash->{RenameFn} = \&FHEM::Devices::Matrix::Rename; + $hash->{NotifyFn} = \&FHEM::Devices::Matrix::Notify; + $hash->{AttrList} = "MatrixRoom MatrixSender MatrixMessage MatrixQuestion_0 MatrixQuestion_1 " . $readingFnAttributes; $hash->{parseParams} = 1; - return FHEM::Meta::InitMod( __FILE__, $hash ); } + 1; =pod =item summary Provides a Matrix-Chatbot. =item summary_DE Stellt einen Matrix-Chatbot bereit. =begin html - +

Matrix

    Matrix implements a client to Matrix-Synapse-Servers. It is in a very early development state.

    - - Define + +

    Define

      - define <name> + define <name>

      - Example: define matrix Matrix matrix.com fhem asdf + Example: define matrix Matrix matrix.com fhem

      noch ins Englische: 1. Anmerkung: Zur einfachen Einrichtung habe ich einen Matrix-Element-Client mit "--profile=fhem" gestartet und dort die Registrierung und die Räume vorbereitet. Achtung: alle Räume müssen noch unverschlüsselt sein um FHEM anzubinden. Alle Einladungen in Räume und Annehmen von Einladungen geht hier viel einfacher. Aus dem Element-Client dann die Raum-IDs merken für das Modul.
      @@ -88,8 +92,8 @@ sub Matrix_Initialize {

    - - Set
    + +

    Set

      set <name> <option> <value>

      @@ -97,28 +101,39 @@ sub Matrix_Initialize {

      Options:
        + +
      • password
        + Set the password to login
      • +
      • register
        without function, do not use this
      • +
      • login
        Login to the Matrix-Server and sync endless if poll is set to "1"
      • +
      • refresh
        If logged in or in state "soft-logout" refresh gets a new access_token and syncs endless if poll is set to "1"
      • +
      • filter
        A Filter must be set for syncing in long poll. This filter is in the moment experimentell and must be set manual to get the coresponding filter_id
      • +
      • poll
        Defaults to "0": Set poll to "1" for starting the sync-loop
      • +
      • poll.fullstate
        Defaults to "0": Set poll.fullstate to "1" for getting in the next sync a full state of all rooms
      • +
      • question.start
        Start a question in the room from reading room. The first answer to the question is logged and ends the question.
      • +
      • question.end
        Stop a question also it is not answered.

    - - Get
    + +

    Get

      get <name> <option>

      @@ -126,8 +141,8 @@ sub Matrix_Initialize {

    - - Attributes + +

    Attributes

      attr <name> <attribute> <value>

      @@ -135,43 +150,49 @@ sub Matrix_Initialize {

      Attributes:
        +
      • MatrixMessage
        Set the room-id to wich messagesare sent.
      • +
      • MatrixQuestion_[0..9]
        Prepared questions.
      • +
      • MatrixRoom ...
        Set the room-id's from wich are messages received.
      • +
      • MatrixSender ...
        Set the user's from wich are messages received.

        Example: attr matrix MatrixSender @name:matrix.server @second.name:matrix.server
    + +

    Readings

=end html =begin html_DE - +

Matrix

    Matrix stellt einen Client für Matrix-Synapse-Server bereit. It is in a very early development state.

    - - Define + +

    Define

      - define <name> + define <name>

      - Beispiel: define matrix Matrix matrix.com fhem asdf + Beispiel: define matrix Matrix matrix.com fhem

      1. Anmerkung: Zur einfachen Einrichtung habe ich einen Matrix-Element-Client mit "--profile=fhem" gestartet und dort die Registrierung und die Räume vorbereitet. Achtung: alle Räume müssen noch unverschlüsselt sein um FHEM anzubinden. Alle Einladungen in Räume und Annehmen von Einladungen geht hier viel einfacher. Aus dem Element-Client dann die Raum-IDs merken für das Modul.
      2. Anmerkung: sets, gets, Attribute und Readings müssen noch besser bezeichnet werden.

    - - Set
    + +

    Set

      set <name> <option> <wert>

      @@ -179,30 +200,41 @@ sub Matrix_Initialize {

      Options:
        + +
      • password
        + Setzt das Passwort zum Login
      • +
      • register
        noch ohne Funktion!
      • +
      • login
        Login beim Matrix-Server und horche andauernd auf Nachrichten wenn poll auf "1" gesetzt ist
      • +
      • refresh
        Wenn eingeloggt oder im Zustand "soft-logout" erhält man mit refresh einen neuen access_token. Wenn poll auf "1" gesetzt ist läuft dann wieder der Empfang andauernd.
      • +
      • filter
        Ein Filter muss gesetzt sein um "Longpoll"-Anfragen an den Server schicken zu können. Der Filter muss hier einmalg gesetzt werden um vom Server eine Filter-ID zu erhalten.
      • +
      • poll
        Zunächst "0": Auf "1" startet die Empfangsschleife.
      • +
      • poll.fullstate
        Standard ist "0": Wenn poll.fullstate auf "1" gesetzt wird, werden beider nächsten Synchronisation alle Raumeigenschaften neu eingelesen.
      • +
      • question.start
        Frage in dem Raum des Attributs "MatrixMessage" stellen. Die erste Antwort steht im Reading "answer" und beendet die Frage.
        Als Wert wird entweder die Nummer einer vorbereiteten Frage übergeben oder eine komplette Frage in der Form
        Frage:Antwort 1:Antwort 2:....:Antwort n
      • +
      • question.end
        Die gestartete Frage ohne Antwort beenden. Entweder wird ohne Parameter die aktuelle Frage beendet oder mit einer Nachrichten-ID eine "verwaiste" Frage.

    - - Get
    + +

    Get

      get <name> <option>

      @@ -210,8 +242,8 @@ sub Matrix_Initialize {

    - - Attributes + +

    Attributes

      attr <name> <attribute> <value>

      @@ -219,22 +251,28 @@ sub Matrix_Initialize {

      Attributes:
        +
      • MatrixMessage
        Setzt die Raum-ID in die alle Nachrichten gesendet werden. Zur Zeit ist nur ein Raum möglich.
      • -
      • MatrixQuestion_[0..9].
        + +
      • MatrixQuestion_[0..9]
        Vorbereitete Fragen, die mit set mt question.start 0..9 gestartet werden können.
        Format der Fragen: Frage:Antwort 1:Antwort 2:....:Antwort n
      • +
      • MatrixRoom ...
        Alle Raum-ID's aus denen Nachrichten empfangen werden.
      • +
      • MatrixSender ...
        Alle Personen von denen Nachrichten empfangen werden.
        Beispiel: attr matrix MatrixSender @name:matrix.server @second.name:matrix.server
    + +

    Readings

=end html_DE =cut diff --git a/lib/FHEM/Devices/Matrix/Matrix.pm b/lib/FHEM/Devices/Matrix/Matrix.pm new file mode 100644 index 0000000..35aa31f --- /dev/null +++ b/lib/FHEM/Devices/Matrix/Matrix.pm @@ -0,0 +1,483 @@ +########################################################################## +# Usage: +# +########################################################################## +# $Id: Matrix.pm 20020 2022-11-05 12:26:00Z Man-fred $ + +package FHEM::Devices::Matrix; +use strict; +use warnings; +use HttpUtils; +use JSON; +#use GPUtils qw(GP_Import); +#use vars qw(%data); +use FHEM::Core::Authentication::Passwords qw(:ALL); + +# InternalTimer +# strftime +# RemoveInternalTimer +# readingFnAttributes +# notifyRegexpChanged +# HttpUtils_BlockingGet + +BEGIN { + + GP_Import(qw( + readingsBeginUpdate + readingsBulkUpdate + readingsEndUpdate + readingsSingleUpdate + Log3 + defs + init_done + IsDisabled + deviceEvents + AttrVal + ReadingsVal + HttpUtils_NonblockingGet + data + gettimeofday + )) +}; + +my $Module_Version = '0.0.7'; + +sub Define { + my ($hash, $param) = @_; + # my @param = split('[ \t]+', $def); + my $name = $param->[0]; #$param[0]; + + Log3($name, 1, "$name: Define: $param->[2] ".int(@$param)); + + if(int(@$param) < 1) { + return "too few parameters: define Matrix "; + } + $hash->{name} = $param->[0]; + $hash->{server} = $param->[2]; + $hash->{user} = $param->[3]; + $hash->{password} = $param->[4]; + $hash->{helper}->{passwdobj} = FHEM::Core::Authentication::Passwords->new($hash->{TYPE}); + + $hash->{NOTIFYDEV} = "global"; + Startproc($hash) if($init_done); + return ; +} + +sub Undef { + my ($hash, $arg) = @_; + my $name = $hash->{NAME}; + # undef $data + $data{MATRIX}{"$name"} = undef; + $hash->{helper}->{passwdobj}->setDeletePassword($name); + return ; +} + +sub Startproc { + my ($hash) = @_; + my $name = $hash->{NAME}; + Log3($name, 1, "$name: Startproc V".$hash->{ModuleVersion}." -> V".$Module_Version) if ($hash->{ModuleVersion}); + # Update necessary? + $hash->{ModuleVersion} = $Module_Version; +} + +########################## +sub Notify($$) +{ + my ($hash, $dev) = @_; + my $name = $hash->{NAME}; + my $devName = $dev->{NAME}; + return "" if(IsDisabled($name)); + Log3($name, 1, "$name : X_Notify $devName"); + my $events = deviceEvents($dev,1); + return if( !$events ); + + if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) + { + Startproc($hash); + } + + foreach my $event (@{$events}) { + $event = "" if(!defined($event)); + ### Writing log entry + Log3($name, 4, "$name : X_Notify $devName - $event"); + # Examples: + # $event = "readingname: value" + # or + # $event = "INITIALIZED" (for $devName equal "global") + # + # processing $event with further code + } + return undef; +} + +############################################################################################# +# called when the device gets renamed, copy from telegramBot +# in this case we then also need to rename the key in the token store and ensure it is recoded with new name +sub Rename($$) { + my ($new,$old) = @_; + my $hash = $defs{$new}; + + $data{MATRIX}{"$new"} = $data{MATRIX}{"$old"}; + $data{MATRIX}{"$old"} = undef; + $hash->{helper}->{passwdobj}->setRename($new,$old); + + #my $nhash = $defs{$new}; +} + +sub Get { + my $hash = shift; + my $aArg = shift; + + my $name = shift @$aArg; + my $cmd = shift @$aArg; + my $value = shift @$aArg; + + if ($cmd eq "wellknown") { + return PerformHttpRequest($hash, $cmd, ''); + } + elsif ($cmd eq "sync") { + $data{MATRIX}{"$name"}{"FAILS"} = 0; + return PerformHttpRequest($hash, $cmd, ''); + } + elsif ($cmd eq "filter") { + return qq("get Matrix $cmd" needs a filter_id to request); + return PerformHttpRequest($hash, $cmd, $value); + } + return "Unknown argument $cmd, choose one of filter sync wellknown"; +} + +sub Set { + my $hash = shift; + my $param = shift; + my $hArg = shift; + + my $name = shift @$param; + my $opt = shift @$param; + my $value = join("", @$param); + + #Log3($name, 5, "Set $name - $opt - $value - $hash->{NAME}"); + #return "set $name needs at least one argument" if (int(@$param) < 3); + + if ($opt eq "msg") { + return PerformHttpRequest($hash, $opt, $value); + } + elsif ($opt eq "poll" || $opt eq "poll.fullstate") { + readingsSingleUpdate($hash, $opt, $value, 1); # Readings erzeugen + } + elsif ($opt eq "password") { + my ($erg,$err) = $hash->{helper}->{passwdobj}->setStorePassword($name,$value); + return undef; + } + elsif ($opt eq "filter") { + return PerformHttpRequest($hash, $opt, ''); + } + elsif ($opt eq "question") { + return PerformHttpRequest($hash, $opt, $value); + } + elsif ($opt eq "question.end") { + return PerformHttpRequest($hash, $opt, $value); + } + elsif ($opt eq "register") { + return PerformHttpRequest($hash, $opt, ''); # 2 steps (ToDo: 3 steps empty -> dummy -> registration_token o.a.) + } + elsif ($opt eq "login") { + return PerformHttpRequest($hash, $opt, ''); + } + elsif ($opt eq "refresh") { + return PerformHttpRequest($hash, $opt, ''); + } + else { + return "Unknown argument $opt, choose one of filter:noArg password question question.end poll:0,1 poll.fullstate:0,1 msg register login:noArg refresh:noArg"; + } + + #return "$opt set to $value. Try to get it."; +} + + +sub Attr { + my ($cmd,$name,$attr_name,$attr_value) = @_; + if($cmd eq "set") { + if($attr_name eq "xxMatrixRoom") { + $attr_value =~ tr/: /~:/; + addToDevAttrList("mt", "MatrixMessage:".$attr_value); + } elsif($attr_name eq "xxMatrixMessage") { + $_[3] =~ tr/~/:/; + } else { + return ; + } + } + return ; +} + +sub PerformHttpRequest($$$) +{ + my ($hash, $def, $value) = @_; + my $now = gettimeofday(); + my $name = $hash->{NAME}; + my $passwd = ""; + $passwd = $hash->{helper}->{passwdobj}->getReadPassword($name) if ($def == "login" || $def == "reg2"); + #Log3($name, 5, "PerformHttpRequest - $name - $passwd"); + + + my $param = { + timeout => 10, + hash => $hash, # Muss gesetzt werden, damit die Callback funktion wieder $hash hat + def => $def, # sichern für eventuelle Wiederholung + value => $value, # sichern für eventuelle Wiederholung + method => "POST", # standard, sonst überschreiben + header => "User-Agent: HttpUtils/2.2.3\r\nAccept: application/json", # Den Header gemäß abzufragender Daten setzen + callback => \&ParseHttpResponse # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten + }; + $data{MATRIX}{"$name"}{"busy"} = $data{MATRIX}{"$name"}{"busy"} ? $data{MATRIX}{"$name"}{"busy"} + 1 : 1; # queue is busy until response is received + $data{MATRIX}{"$name"}{'LASTSEND'} = $now; # remember when last sent + if ($def eq "sync" && $data{MATRIX}{"$name"}{"next_refresh"} < $now){ + $def = "refresh"; + $data{MATRIX}{"$name"}{"next_refresh"} = $now + 300; + } + + my $device_id = ReadingsVal($name, 'device_id', undef) ? ', "device_id":"'.ReadingsVal($name, 'device_id', undef).'"' : ""; + if ($def eq "register"){ + $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; + $param->{'data'} = '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$passwd.'"}'; + } + if ($def eq"reg2"){ + $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; + $param->{'data'} = '{"username":"'.$hash->{user}.'", "password":"'.$passwd.'", "auth": {"session":"'.$data{MATRIX}{"$name"}{"session"}.'","type":"m.login.dummy"}}'; + } + if ($def eq "login"){ + $param->{'url'} = $hash->{server}."/_matrix/client/v3/login"; + $param->{'data'} = '{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$passwd.'"' + .$device_id.'}'; + } + if ($def eq "refresh"){ + $param->{'url'} = $hash->{server}.'/_matrix/client/v1/refresh'; + $param->{'data'} = '{"refresh_token": "'.$data{MATRIX}{"$name"}{"refresh_token"}.'"}'; + } + if ($def eq "wellknown"){ + $param->{'url'} = $hash->{server}."/.well-known/matrix/client"; + } + if ($def eq "msg"){ + $param->{'url'} = $hash->{server}.'/_matrix/client/r0/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.room.message?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'data'} = '{"msgtype":"m.text", "body":"'.$value.'"}'; + } + if ($def eq "question"){ + $value = AttrVal($name, "MatrixQuestion_$value",$value); # if ($value =~ /[0-9]/); + my @question = split(':',$value); + my $size = @question; + $value =~ s/:/
/g; + # min. question and one answer + if (int(@question) >= 2){ + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.poll.start?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'data'} = '{"org.matrix.msc3381.poll.start": {"max_selections": 1,'. + '"question": {"org.matrix.msc1767.text": "'.$question[0].'"},'. + '"kind": "org.matrix.msc3381.poll.undisclosed",'. + '"answers": [{"id": "'.$question[1].'", "org.matrix.msc1767.text": "'.$question[1].'"},{"id":"'.$question[2].'","org.matrix.msc1767.text": "'.$question[2].'"}],'. + '"org.matrix.msc1767.text": "'.$value.'"}}'; + } else { + Log3($name, 5, "question: $value $size $question[0]"); + return; + } + } + if ($def eq "question.end"){ + $value = ReadingsVal($name, "question_id", "") if (!$value); + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'MatrixMessage', '!!').'/send/m.poll.end?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + # ""'.ReadingsVal($name, 'questionEventId', '!!').' + $param->{'data'} = '{"m.relates_to": {"rel_type": "m.reference","event_id": "'.$value.'"},"org.matrix.msc3381.poll.end": {},'. + '"org.matrix.msc1767.text": "Antort '.ReadingsVal($name, "answer", "").' erhalten von '.ReadingsVal($name, "sender", "").'"}'; + } + if ($def eq "sync"){ + my $since = ReadingsVal($name, "since", undef) ? '&since='.ReadingsVal($name, "since", undef) : ""; + my $full_state = ReadingsVal($name, "poll.fullstate",undef); + if ($full_state){ + $full_state = "&full_state=true"; + readingsSingleUpdate($hash, "poll.fullstate", 0, 1); + } else { + $full_state = ""; + } + $param->{'url'} = $hash->{server}.'/_matrix/client/r0/sync?access_token='.$data{MATRIX}{"$name"}{"access_token"}.$since.$full_state.'&timeout=50000&filter='.ReadingsVal($name, 'filter_id',0); + $param->{'method'} = 'GET'; + $param->{'timeout'} = 60; + } + if ($def eq "filter"){ + if ($value){ # get + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "user_id",0).'/filter/'.$value.'?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'method'} = 'GET'; + } else { + $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "user_id",0).'/filter?access_token='.$data{MATRIX}{"$name"}{"access_token"}; + $param->{'data'} = '{'; + $param->{'data'} .= '"event_fields": ["type","content","sender"],'; + $param->{'data'} .= '"event_format": "client", '; + $param->{'data'} .= '"presence": { "senders": [ "@xx:example.com"]}'; # no presence + #$param->{'data'} .= '"room": { "ephemeral": {"rooms": ["'.AttrVal($name, 'MatrixRoom', '!!').'"],"types": ["m.receipt"]}, "state": {"types": ["m.room.*"]},"timeline": {"types": ["m.room.message"] } }'; + $param->{'data'} .= '}'; + } + } + + my $test = "$param->{url}, " + . ( $param->{data} ? "\r\ndata: $param->{data}, " : "" ) + . ( $param->{header} ? "\r\nheader: $param->{header}" : "" ); + #readingsSingleUpdate($hash, "fullRequest", $test, 1); # Readings erzeugen + $test = "$name: Matrix sends with timeout $param->{timeout} to ".$test; + Log3($name, 5, $test); + + HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. + return undef; +} + +sub ParseHttpResponse($) +{ + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $def = $param->{def}; + my $value = $param->{value}; + my $name = $hash->{NAME}; + my $now = gettimeofday(); + my $nextRequest = ""; + + readingsBeginUpdate($hash); + ###readingsBulkUpdate($hash, "httpHeader", $param->{httpheader}); + readingsBulkUpdate($hash, "httpStatus", $param->{code}); + $hash->{STATE} = $def.' - '.$param->{code}; + if($err ne "") { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist + Log3($name, 3, "error while requesting ".$param->{url}." - $err"); # Eintrag fürs Log + readingsBulkUpdate($hash, "fullResponse", "ERROR ".$err); # Readings erzeugen + $data{MATRIX}{"$name"}{"FAILS"} = 3; + } + elsif($data ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) + Log3($name, 4, $def." returned: $data"); # Eintrag fürs Log + my $decoded = eval { JSON::decode_json($data) }; + Log3($name, 2, "$name: json error: $@ in data") if( $@ ); + if ($param->{code} == 200){ + $data{MATRIX}{"$name"}{"FAILS"} = 0; + } else { + $data{MATRIX}{"$name"}{"FAILS"}++; + } + # readingsBulkUpdate($hash, "fullResponse", $data); + + # default next request + $nextRequest = "sync" ; + # An dieser Stelle die Antwort parsen / verarbeiten mit $data + + # "errcode":"M_UNKNOWN_TOKEN: login or refresh + my $errcode = $decoded->{'errcode'} ? $decoded->{'errcode'} : ""; + if ($errcode eq "M_UNKNOWN_TOKEN"){ + $data{MATRIX}{"$name"}{"repeat"} = $param if ($def ne "sync"); + if ($decoded->{'error'} eq "Access token has expired"){ + if ($decoded->{'soft_logout'} eq "true"){ + $nextRequest = 'refresh'; + }else{ + $nextRequest = 'login'; + } + } elsif ($decoded->{'error'} eq "refresh token does not exist"){ + $nextRequest = 'login'; + } + } + + if ($def eq "register"){ + $data{MATRIX}{"$name"}{"session"} = $decoded->{'session'}; + $nextRequest = "reg2"; + } + if ($param->{code} == 200 && ($def eq "reg2" || $def eq "login" || $def eq "refresh")){ + readingsBulkUpdate($hash, "user_id", $decoded->{'user_id'}) if ($decoded->{'user_id'}); + readingsBulkUpdate($hash, "home_server", $decoded->{'home_server'}) if ($decoded->{'home_server'}); + readingsBulkUpdate($hash, "device_id", $decoded->{'device_id'}) if ($decoded->{'device_id'}); + readingsBulkUpdate($hash, "last_register", $param->{code}) if $def eq "reg2"; + readingsBulkUpdate($hash, "last_login", $param->{code}) if $def eq "login"; + readingsBulkUpdate($hash, "last_refresh", $param->{code}) if $def eq "refresh"; + + $data{MATRIX}{"$name"}{"expires"} = $decoded->{'expires_in_ms'} if ($decoded->{'expires_in_ms'}); + $data{MATRIX}{"$name"}{"refresh_token"} = $decoded->{'refresh_token'} if ($decoded->{'refresh_token'}); + $data{MATRIX}{"$name"}{"access_token"} = $decoded->{'access_token'} if ($decoded->{'access_token'}); + $data{MATRIX}{"$name"}{"next_refresh"} = $now + $data{MATRIX}{"$name"}{"expires"}/1000 - 60; # refresh one minute before end + } + if ($def eq "wellknown"){ + # https://spec.matrix.org/unstable/client-server-api/ + } + if ($param->{code} == 200 && $def eq "sync"){ + readingsBulkUpdate($hash, "since", $decoded->{'next_batch'}) if ($decoded->{'next_batch'}); + # roomlist + my $list = $decoded->{'rooms'}->{'join'}; + #my @roomlist = (); + my $pos = 0; + foreach my $id ( keys $list->%* ) { + if (ref $list->{$id} eq ref {}) { + my $member = ""; + #my $room = $list->{$id}; + $pos = $pos + 1; + # MatrixRoom ? + readingsBulkUpdate($hash, "room$pos.id", $id); + #foreach my $id ( $decoded->{'rooms'}->{'join'}->{AttrVal($name, 'MatrixRoom', '!!')}->{'timeline'}->{'events'}->@* ) { + foreach my $ev ( $list->{$id}->{'state'}->{'events'}->@* ) { + readingsBulkUpdate($hash, "room$pos.topic", $ev->{'content'}->{'topic'}) if ($ev->{'type'} eq 'm.room.topic'); + readingsBulkUpdate($hash, "room$pos.name", $ev->{'content'}->{'name'}) if ($ev->{'type'} eq 'm.room.name'); + $member .= "$ev->{'sender'} " if ($ev->{'type'} eq 'm.room.member'); + } + readingsBulkUpdate($hash, "room$pos.member", $member); + foreach my $tl ( $list->{$id}->{'timeline'}->{'events'}->@* ) { + readingsBulkUpdate($hash, "room$pos.topic", $tl->{'content'}->{'topic'}) if ($tl->{'type'} eq 'm.room.topic'); + readingsBulkUpdate($hash, "room$pos.name", $tl->{'content'}->{'name'}) if ($tl->{'type'} eq 'm.room.name'); + if ($tl->{'type'} eq 'm.room.message' && $tl->{'content'}->{'msgtype'} eq 'm.text'){ + my $sender = $tl->{'sender'}; + if (AttrVal($name, 'MatrixSender', '') =~ $sender){ + readingsBulkUpdate($hash, "message", $tl->{'content'}->{'body'}); + readingsBulkUpdate($hash, "sender", $sender); + # command + + } + #else { + # readingsBulkUpdate($hash, "message", 'ignoriert, nicht '.AttrVal($name, 'MatrixSender', '')); + # readingsBulkUpdate($hash, "sender", $sender); + #} + } elsif ($tl->{'type'} eq "org.matrix.msc3381.poll.response"){ + my $sender = $tl->{'sender'}; + if (AttrVal($name, 'MatrixSender', '') =~ $sender){ + readingsBulkUpdate($hash, "answer", $tl->{'content'}->{'org.matrix.msc3381.poll.response'}->{'answers'}[0]); + readingsBulkUpdate($hash, "sender", $sender); + # poll.end and + $nextRequest = "question.end" ; + # command + + } + } + } + #push(@roomlist,"$id: "; + } + } + } + if ($def eq "filter"){ + readingsBulkUpdate($hash, "filter_id", $decoded->{'filter_id'}) if ($decoded->{'filter_id'}); + } + if ($def eq "msg" ){ + readingsBulkUpdate($hash, "event_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); + #m.relates_to + } + if ($def eq "question"){ + readingsBulkUpdate($hash, "question_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); + #m.relates_to + } + if ($def eq "question.end"){ + readingsBulkUpdate($hash, "event_id", $decoded->{'event_id'}) if ($decoded->{'event_id'}); + readingsBulkUpdate($hash, "question_id", "") if ($decoded->{'event_id'}); + #m.relates_to + } + } + readingsEndUpdate($hash, 1); + $data{MATRIX}{"$name"}{"busy"} = $data{MATRIX}{"$name"}{"busy"} - 1; # queue is busy until response is received + $data{MATRIX}{"$name"}{"sync"} = 0 if ($def eq "sync" || !$data{MATRIX}{"$name"}{"sync"}); # possible next sync + $nextRequest = "" if ($nextRequest eq "sync" && $data{MATRIX}{"$name"}{"sync"} == 1); # only one sync at a time! + + #if ($def eq "sync" && $nextRequest eq "sync" && ReadingsVal($name,'poll',0) == 1 && $data{MATRIX}{"$name"}{"FAILS"} < 3){ + # PerformHttpRequest($hash, $nextRequest, ''); + #} els + if ($nextRequest ne "" && ReadingsVal($name,'poll',0) == 1 && $data{MATRIX}{"$name"}{"FAILS"} < 3) { + if ($nextRequest eq "sync" && $data{MATRIX}{"$name"}{"repeat"}){ + $def = $data{MATRIX}{"$name"}{"repeat"}->{"def"}; + $value = $data{MATRIX}{"$name"}{"repeat"}->{"value"}; + $data{MATRIX}{"$name"}{"repeat"} = undef; + PerformHttpRequest($hash, $def, $value); + } else { + PerformHttpRequest($hash, $nextRequest, ''); + } + } + # Damit ist die Abfrage zuende. +}