From ed34e21d4e90accbc8da2561bdc7a342722b81c2 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Mon, 26 Dec 2022 05:51:54 +0100 Subject: [PATCH] next step to rewrite --- lib/FHEM/Devices/Matrix/Client.pm | 722 +++++++++++++++++------------- 1 file changed, 401 insertions(+), 321 deletions(-) diff --git a/lib/FHEM/Devices/Matrix/Client.pm b/lib/FHEM/Devices/Matrix/Client.pm index a0b585e..2a2d90d 100644 --- a/lib/FHEM/Devices/Matrix/Client.pm +++ b/lib/FHEM/Devices/Matrix/Client.pm @@ -8,10 +8,8 @@ # Verwendung von lowerCamelCaps für a) die Bezeichnungen der Behälter für Readings, Fhem und Helper und der Untereintraege, # b) die Bezeichnungen der Readings, # c) die Bezeichnungen der Attribute. - -#package FHEM::Devices::Matrix; -#(Man-Fred) geh ich Recht in der Annahme, dass hier das gleiche package hin gehört -# wie im Modul 98_Matrix? +# +# package FHEM::Devices::Matrix::Client; use strict; @@ -103,12 +101,12 @@ BEGIN { HttpUtils_NonblockingGet InternalTimer gettimeofday - fhem + AnalyzeCommandChain ) ); } -my $Module_Version = '0.0.9'; +my $VERSION = '0.0.15'; sub Attr_List { return @@ -121,21 +119,19 @@ sub Define { my $hash = shift; my $aArg = shift; - return "too few parameters: define Matrix " + return 'too few parameters: define Matrix ' if ( scalar( @{$aArg} ) != 4 ); my $name = $aArg->[0]; $hash->{SERVER} = $aArg->[2]; # Internals sollten groß geschrieben werden - $hash->{URL} = 'https://' . $hash->{SERVER}; + $hash->{URL} = 'https://' . $hash->{SERVER} . '/_matrix/client/'; $hash->{USER} = $aArg->[3]; - $hash->{VERSION} = $Module_Version; + $hash->{VERSION} = $VERSION; $hash->{helper}->{passwdobj} = FHEM::Core::Authentication::Passwords->new( $hash->{TYPE} ); - - #$hash->{helper}->{i18} = Get_I18n(); - $hash->{NOTIFYDEV} = "global"; + $hash->{NOTIFYDEV} = 'global,' . $name; readingsSingleUpdate( $hash, 'state', 'defined', 1 ); @@ -150,11 +146,8 @@ sub Undef { my $hash = shift; my $name = shift; -#my $name = $hash->{NAME}; -# $data{MATRIX}{"$name"} = undef; #(CoolTux) Bin mir gerade nicht sicher woher das $data kommt -# meinst Du das %data aus main? Das ist für User. Wenn Du als Modulentwickler -# etwas zwischenspeichern möchtest dann in $hash->{helper} RemoveInternalTimer($hash); + return; } @@ -162,15 +155,12 @@ sub Delete { my $hash = shift; my $name = shift; - $hash->{helper}->{passwdobj}->setDeletePassword($name) - ; #(CoolTux) das ist nicht nötig, - # du löschst jedesmal den Eintrag wenn FHEM beendet wird. - # Es sollte eine DeleteFn geben da kannst Du das rein machen + $hash->{helper}->{passwdobj}->setDeletePassword($name); return; } -sub _Startproc { # wir machen daraus eine privat function (CoolTux) +sub _Init { # wir machen daraus eine privat function (CoolTux) return 0 unless ( __PACKAGE__ eq caller(0) ) ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) @@ -179,12 +169,11 @@ sub _Startproc { # wir machen daraus eine privat function (CoolTux) my $name = $hash->{NAME}; Log3( $name, 4, - "$name : Matrix::_Startproc $hash " - . AttrVal( $name, 'matrixPoll', '-1' ) ); + "$name : Matrix::_Init $hash " . AttrVal( $name, 'matrixPoll', '-1' ) ); # Update necessary? Log3( $name, 1, - "$name: Start V" . $hash->{VERSION} . " -> V" . $Module_Version ) + $name . ': Start V' . $hash->{VERSION} . ' -> V' . $VERSION ) if ( $hash->{VERSION} ); return ::readingsSingleUpdate( $hash, 'state', 'please set password first', @@ -196,26 +185,11 @@ sub _Startproc { # wir machen daraus eine privat function (CoolTux) ) ); - $hash->{helper}->{"softfail"} = 1; - - Login($hash); - - return; -} - -sub Login { - my $hash = shift; - - Log3( $hash->{NAME}, 4, "$hash->{NAME} : Matrix::Login $hash" ); + $hash->{helper}->{softfail} = 1; return _PerformHttpRequest( $hash, 'login', '' ); } -########################## -# sub Notify($$) -#(CoolTux) Subroutine prototypes used. See page 194 of PBP (Subroutines::ProhibitSubroutinePrototypes) -# Contrary to common belief, subroutine prototypes do not enable -# compile-time checks for proper arguments. Don't use them. sub Notify { my $hash = shift; my $dev = shift; @@ -228,7 +202,7 @@ sub Notify { my $events = ::deviceEvents( $dev, 1 ); return if ( !$events ); - return _Startproc($hash) + return _Init($hash) if ( ( grep { /^INITIALIZED$/x } @{$events} @@ -270,34 +244,25 @@ sub Rename { my $old = shift; my $hash = $defs{$new}; - my $name = $hash->{NAME}; my ( $passResp, $passErr ); - #$data{MATRIX}{"$new"} = $data{MATRIX}{"$old"}; - -#$data{MATRIX}{"$old"} = undef; #(CoolTux) Wenn ein Hash nicht mehr benötigt wird dann delete -# Fehler in der nächsten Zeile: -# delete argument is not a HASH or ARRAY element or slice at lib/FHEM/Devices/Matrix/Matrix.pm line 197. -# delete $data{MATRIX}{"$old"} - ( $passResp, $passErr ) = $hash->{helper}->{passwdobj}->setRename( $new, $old ) ; #(CoolTux) Es empfiehlt sich ab zu fragen ob der Wechsel geklappt hat - Log3( $name, 1, -"$name : Matrix::Rename - error while change the password hash after rename - $passErr" + Log3( $new, 1, +"$new : Matrix::Rename - error while change the password hash after rename - $passErr" ) if (!defined($passResp) && defined($passErr) ); - Log3( $name, 1, -"$name : Matrix::Rename - change password hash after rename successfully" + Log3( $new, 1, + "$new : Matrix::Rename - change password hash after rename successfully" ) if ( defined($passResp) && !defined($passErr) ); - #my $nhash = $defs{$new}; return; } @@ -319,19 +284,23 @@ sub _I18N { # wir machen daraus eine privat function (CoolTux) 'question' => 'Eine neue Frage' }, }; - my $result = $def->{ AttrVal( 'global', 'language', 'EN' ) }->{$value}; - return ( $result ? $result : $value ); + my $result = $def->{ AttrVal( 'global', 'language', 'EN' ) }->{$value}; + + return ( $result ? $result : $value ); } sub Get { - my ( $hash, $name, $cmd, @args ) = @_; - my $value = join( " ", @args ); + my $hash = shift; + my $aArg = shift; + my $hArg = shift; - #$cmd = '?' if (!$cmd); + my $name = shift @$aArg; + my $cmd = shift @$aArg + // carp q[set Matrix Client needs at least one argument] && return; + + my $value = join( ' ', @{$aArg} ); -#(CoolTux) Eine endlos Lange elsif Schlange ist nicht zu empfehlen, besser mit switch arbeiten -# Im Modulkopf use experimental qw /switch/; verwenden given ($cmd) { when ('wellknown') { return _PerformHttpRequest( $hash, $cmd, '' ); @@ -342,22 +311,26 @@ sub Get { } when ('sync') { - $hash->{helper}->{"softfail"} = - 0; #(CoolTux) Bin mir gerade nicht sicher woher das $data kommt - # meinst Du das %data aus main? Das ist für User. Wenn Du als Modulentwickler - # etwas zwischenspeichern möchtest dann in $hash->{helper} - $hash->{helper}->{"hardfail"} = 0; + $hash->{helper}->{softfail} = 0; + $hash->{helper}->{hardfail} = 0; + return _PerformHttpRequest( $hash, $cmd, '' ); } when ('filter') { return qq("get Matrix $cmd" needs a filterId to request); - return _PerformHttpRequest( $hash, $cmd, $value ); + + # return _PerformHttpRequest( $hash, $cmd, $value ); } default { - return -"Unknown argument $cmd, choose one of logintypes filter sync wellknown"; + my $list = ''; + $list .= 'logintypes filter sync wellknown' + if ( exists( $hash->{helper}->{passwdobj} ) + && defined( + $hash->{helper}->{passwdobj}->getReadPassword($name) ) ); + + return 'Unknown argument ' . $cmd . ', choose one of ' . $list; } } } @@ -393,12 +366,13 @@ sub Set { if (!defined($passResp) && defined($passErr) ); - return _Startproc($hash) + return _Init($hash) if ( defined($passResp) && !defined($passErr) ); } when ('removePassword') { - return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); + return 'usage: ' . $cmd + if ( scalar( @{$aArg} ) != 0 ); my ( $passResp, $passErr ); ( $passResp, $passErr ) = @@ -434,7 +408,7 @@ sub Set { } default { - my $list; + my $list = ''; $list .= ( exists( $hash->{helper}->{passwdobj} ) && defined( $hash->{helper}->{passwdobj}->getReadPassword($name) @@ -443,13 +417,13 @@ sub Set { : 'setPassword ' ); - $list .= 'msg ' + $list .= 'msg login:noArg ' if ( exists( $hash->{helper}->{passwdobj} ) && defined( $hash->{helper}->{passwdobj}->getReadPassword($name) ) ); $list .= -'filter:noArg question questionEnd pollFullstate:0,1 msg register login:noArg refresh:noArg' +'filter:noArg question questionEnd pollFullstate:0,1 msg register refresh:noArg' if ( ::AttrVal( $name, 'devel', 0 ) == 1 && exists( $hash->{helper}->{passwdobj} ) && defined( @@ -467,28 +441,35 @@ sub Attr { Log3( $name, 4, "Attr - $cmd - $name - $attr_name - $attr_value" ); - if ( $cmd eq "set" ) { - if ( $attr_name eq "matrixQuestion_" ) { + if ( $cmd eq 'set' ) { + if ( $attr_name eq 'matrixQuestion_' ) { my @erg = split( / /, $attr_value, 2 ); + return qq("attr $name $attr_name" ) . _I18N('require2') if ( !$erg[1] ); + return qq("attr $name $attr_name" ) . _I18N('question') . ' ' . _I18N('beginWithNumber') - if ( $erg[0] !~ /[0-9]/ ); + if ( $erg[0] !~ /[0-9]/x ); + $_[2] = "matrixQuestion_$erg[0]"; $_[3] = $erg[1]; } - if ( $attr_name eq "matrixAnswer_" ) { + + if ( $attr_name eq 'matrixAnswer_' ) { my @erg = split( / /, $attr_value, 2 ); + return qq("attr $name $attr_name" ) . _I18N('require2') if ( !$erg[1] ); + return qq("attr $name $attr_name" ) . _I18N('question') . ' ' . _I18N('beginWithNumber') - if ( $erg[0] !~ /[0-9]/ ); + if ( $erg[0] !~ /[0-9]/x ); + $_[2] = "matrixAnswer_$erg[0]"; $_[3] = $erg[1]; } @@ -506,11 +487,14 @@ sub _Get_Message { # wir machen daraus eine privat function (CoolTux) my $message = shift; Log3( $name, 3, "$name - $def - $message" ); - my $q = AttrVal( $name, "matrixQuestion_$def", "" ); - my $a = AttrVal( $name, "matrixAnswer_$def", "" ); + + my $q = AttrVal( $name, 'matrixQuestion_' . $def, '' ); + my $a = AttrVal( $name, 'matrixAnswer_' . $def, '' ); my @questions = split( ':', $q ); + shift @questions if ( $def ne '99' ); my @answers = split( ':', $a ); + Log3( $name, 3, "$name - $q - $a" ); my $pos = 0; @@ -518,26 +502,45 @@ sub _Get_Message { # wir machen daraus eine privat function (CoolTux) my $answer; # foreach my $question (@questions){ - foreach my $question (@questions) - { #(CoolTux) - Loop iterator is not lexical. See page 108 of PBP (Variables::RequireLexicalLoopIterators)perlcritic - # This policy asks you to use `my'-style lexical loop iterator variables: - - # foreach my $zed (...) { - # ... - # } + foreach my $question (@questions) { Log3( $name, 3, "$name - $question - $answers[$pos]" ); $answer = $answers[$pos] if ( $message eq $question ); + if ($answer) { Log3( $name, 3, "$name - $pos - $answer" ); - fhem($answer); + + AnalyzeCommandChain( undef, $answer ); last; } + $pos++; } return; } +sub _createParamRefForDef { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $def = shift; + my $paramValue = shift; + my $createParamRefObj = shift; + + my $paramref = { + 'logintypes' => { + 'url' => $createParamRefObjhash->{hash}->{URL} . 'r0/login', + 'method' => 'GET', + }, + 'register' => { + 'url' => $createParamRefObjhash->{hash}->{URL}, + 'data' => '', + }, + }; + + return $paramref->{$def}->{$paramValue}; +} + sub _PerformHttpRequest { # wir machen daraus eine privat function (CoolTux) return 0 unless ( __PACKAGE__ eq caller(0) ) @@ -550,101 +553,123 @@ sub _PerformHttpRequest { # wir machen daraus eine privat function (CoolTux) my $def = shift; my $value = shift; - my $now = gettimeofday(); - my $name = $hash->{NAME}; - my $passwd = ""; + my $now = gettimeofday(); + my $name = $hash->{NAME}; + my $passwd; + Log3( $name, 4, "$name : Matrix::_PerformHttpRequest $hash" ); - if ( $def eq "login" || $def eq "reg2" ) { - $passwd = $hash->{helper}->{passwdobj}->getReadPassword($name); - } + $passwd = $hash->{helper}->{passwdobj}->getReadPassword($name) + if ( $def eq 'login' || $def eq 'reg2' ); - $hash->{helper}->{"msgnumber"} = - $hash->{helper}->{"msgnumber"} ? $hash->{helper}->{"msgnumber"} + 1 : 1; + $hash->{helper}->{msgnumber} = + $hash->{helper}->{msgnumber} ? $hash->{helper}->{msgnumber} + 1 : 1; + + my $msgnumber = $hash->{helper}->{msgnumber}; - my $msgnumber = $hash->{helper}->{"msgnumber"}; my $deviceId = ReadingsVal( $name, 'deviceId', undef ) ? ', "device_id":"' . ReadingsVal( $name, 'deviceId', undef ) . '"' - : ""; + : ''; - $hash->{helper}->{"busy"} = - $hash->{helper}->{"busy"} - ? $hash->{helper}->{"busy"} + 1 + $hash->{helper}->{busy} = + $hash->{helper}->{busy} + ? $hash->{helper}->{busy} + 1 : 1; # queue is busy until response is received - $hash->{helper}->{"sync"} = 0 if ( !$hash->{helper}->{"sync"} ); + $hash->{helper}->{sync} = 0 + if ( !$hash->{helper}->{sync} ); - $hash->{helper}->{'LASTSEND'} = $now; # remember when last sent + $hash->{helper}->{LASTSEND} = $now; # remember when last sent if ( $def eq "sync" - && $hash->{helper}->{"next_refresh"} < $now + && $hash->{helper}->{next_refresh} < $now && AttrVal( $name, 'matrixLogin', '' ) eq 'password' ) { - $def = "refresh"; + $def = 'refresh'; + Log3( $name, 5, -qq($name $hash->{helper}->{"access_token"} sync2refresh - $hash->{helper}->{"next_refresh"} < $now) +qq($name $hash->{helper}->{access_token} sync2refresh - $hash->{helper}->{next_refresh} < $now) ); - $hash->{helper}->{"next_refresh"} = $now + 300; + + $hash->{helper}->{next_refresh} = $now + 300; } + my $createParamRefObj = { + hash => $hash, + def => $def, + value => $value, + now => $now, + name => $name, + passwd => $passwd, + msgnumber => $msgnumber, + deviceId => $device_id, + url => undef, + data => undef, + method => undef, + }; + 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" + method => 'POST', # standard, sonst überschreiben + header => 'User-Agent: HttpUtils/2.2.3\r\nAccept: application/json' , # Den Header gemäß abzufragender Daten setzen - callback => \&ParseHttpResponse + msgnumber => $msgnumber, # lfd. Nummer Request + callback => \&ParseHttpResponse , # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten - msgnumber => $msgnumber # lfd. Nummer Request }; given ($def) { when ('logintypes') { - $param->{'url'} = $hash->{URL} . "/_matrix/client/r0/login"; - $param->{'method'} = 'GET'; + $param->{url} = $hash->{URL} . 'r0/login'; + $param->{method} = 'GET'; } + when ('register') { - $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/register"; - $param->{'data'} = + $param->{url} = $hash->{URL} . 'v3/register'; + $param->{data} = '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"' . $hash->{USER} . '" }, "password":"' . $passwd . '"}'; } + when ('reg1') { - $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/register"; - $param->{'data'} = + $param->{url} = $hash->{URL} . 'v3/register'; + $param->{data} = '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"' . $hash->{USER} . '" }, "password":"' . $passwd . '"}'; } + when ('reg2') { - $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/register"; - $param->{'data'} = + $param->{url} = $hash->{URL} . 'v3/register'; + $param->{data} = '{"username":"' . $hash->{USER} . '", "password":"' . $passwd . '", "auth": {"session":"' - . $hash->{helper}->{"session"} + . $hash->{helper}->{session} . '","type":"m.login.dummy"}}'; } + when ('login') { if ( AttrVal( $name, 'matrixLogin', '' ) eq 'token' ) { - $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/login"; - $param->{'data'} = + $param->{url} = $hash->{URL} . 'v3/login'; + $param->{data} = qq({"type":"m.login.token", "token":"$passwd", "user": "$hash->{USER}", "txn_id": "z4567gerww", "session":"1234"}); #$param->{'method'} = 'GET'; } else { - $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/login"; - $param->{'data'} = + $param->{url} = $hash->{URL} . 'v3/login'; + $param->{data} = '{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"' . $hash->{USER} . '" }, "password":"' @@ -652,65 +677,77 @@ qq({"type":"m.login.token", "token":"$passwd", "user": "$hash->{USER}", "txn_id" . $deviceId . '}'; } } + when ('login2') { - $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/login"; + $param->{url} = $hash->{URL} . 'v3/login'; if ( AttrVal( $name, 'matrixLogin', '' ) eq 'token' ) { - $param->{'data'} = + $param->{data} = qq({"type":"m.login.token", "token":"$passwd", "user": "\@$hash->{USER}:matrix.org", "txn_id": "z4567gerww"}); #$param->{'data'} = qq({"type":"m.login.token", "token":"$passwd"}); } } + when ('refresh') { - $param->{'url'} = $hash->{URL} . '/_matrix/client/v1/refresh'; - $param->{'data'} = - '{"refresh_token": "' . $hash->{helper}->{"refresh_token"} . '"}'; + $param->{url} = $hash->{URL} . 'v1/refresh'; + $param->{data} = + '{"refresh_token": "' . $hash->{helper}->{refresh_token} . '"}'; + Log3( $name, 5, -qq($name $hash->{helper}->{"access_token"} refreshBeg $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) +qq($name $hash->{helper}->{access_token} refreshBeg $param->{'msgnumber'}: $hash->{helper}->{next_refresh} > $now) ); } + when ('wellknown') { - $param->{'url'} = $hash->{URL} . "/.well-known/matrix/client"; + $param->{url} = $hash->{URL} . '/.well-known/matrix/client'; } + when ('msg') { - $param->{'url'} = + $param->{url} = $hash->{URL} - . '/_matrix/client/r0/rooms/' + . 'r0/rooms/' . AttrVal( $name, 'matrixMessage', '!!' ) . '/send/m.room.message?access_token=' - . $hash->{helper}->{"access_token"}; - $param->{'data'} = '{"msgtype":"m.text", "body":"' . $value . '"}'; + . $hash->{helper}->{access_token}; + + $param->{data} = '{"msgtype":"m.text", "body":"' . $value . '"}'; } + when ('questionX') { - $hash->{helper}->{"question"} = $value; - $value = AttrVal( $name, "matrixQuestion_$value", "" ) + $hash->{helper}->{question} = $value; + $value = AttrVal( $name, 'matrixQuestion_' . $value, '' ) ; # if ($value =~ /[0-9]/); + my @question = split( ':', $value ); my $size = @question; - my $answer; - my $q = shift @question; - $value =~ s/:/
/g; + my $q = shift @question; + + $value =~ s/:/
/gx; # min. question and one answer if ( int(@question) >= 2 ) { - $param->{'url'} = + $param->{url} = $hash->{URL} - . '/_matrix/client/v3/rooms/' + . 'v3/rooms/' . AttrVal( $name, 'matrixMessage', '!!' ) . '/send/m.poll.start?access_token=' - . $hash->{helper}->{"access_token"}; - $param->{'data'} = + . $hash->{helper}->{access_token}; + + $param->{data} = '{"type":"m.poll.start", "content":{"m.poll": {"max_selections": 1,' . '"question": {"org.matrix.msc1767.text": "' . $q . '"},' . '"kind": "org.matrix.msc3381.poll.undisclosed","answers": ['; + my $comma = ''; - foreach $answer (@question) { - $param->{'data'} .= + + for my $answer (@question) { + $param->{data} .= qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); $comma = ','; } - $param->{'data'} .= + + $param->{data} .= qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}}); } else { @@ -718,36 +755,42 @@ qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain return; } } + when ('question') { - $hash->{helper}->{"question"} = $value; + $hash->{helper}->{question} = $value; $value = AttrVal( $name, "matrixQuestion_$value", "" ) ; # if ($value =~ /[0-9]/); + my @question = split( ':', $value ); my $size = @question; - my $answer; - my $q = shift @question; - $value =~ s/:/
/g; + my $q = shift @question; + + $value =~ s/:/
/gx; # min. question and one answer if ( int(@question) >= 2 ) { - $param->{'url'} = + $param->{url} = $hash->{URL} - . '/_matrix/client/v3/rooms/' + . 'v3/rooms/' . AttrVal( $name, 'matrixMessage', '!!' ) . '/send/m.poll.start?access_token=' - . $hash->{helper}->{"access_token"}; - $param->{'data'} = + . $hash->{helper}->{access_token}; + + $param->{data} = '{"org.matrix.msc3381.poll.start": {"max_selections": 1,' . '"question": {"org.matrix.msc1767.text": "' . $q . '"},' . '"kind": "org.matrix.msc3381.poll.undisclosed","answers": ['; + my $comma = ''; - foreach $answer (@question) { - $param->{'data'} .= + + for my $answer (@question) { + $param->{data} .= qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); $comma = ','; } - $param->{'data'} .= + + $param->{data} .= qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}); } else { @@ -755,95 +798,110 @@ qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain return; } } + when ('questionEnd') { - $value = ReadingsVal( $name, "questionId", "" ) if ( !$value ); - $param->{'url'} = + $value = ReadingsVal( $name, 'questionId', '' ) + if ( !$value ); + + $param->{url} = $hash->{URL} - . '/_matrix/client/v3/rooms/' + . 'v3/rooms/' . AttrVal( $name, 'matrixMessage', '!!' ) . '/send/m.poll.end?access_token=' - . $hash->{helper}->{"access_token"}; - $param->{'data'} = + . $hash->{helper}->{access_token}; + + $param->{data} = '{"m.relates_to": {"rel_type": "m.reference","eventId": "' . $value . '"},"org.matrix.msc3381.poll.end": {},' . '"org.matrix.msc1767.text": "Antort ' - . ReadingsVal( $name, "answer", "" ) + . ReadingsVal( $name, 'answer', '' ) . ' erhalten von ' - . ReadingsVal( $name, "sender", "" ) . '"}'; + . ReadingsVal( $name, 'sender', '' ) . '"}'; } + when ('sync') { my $since = - ReadingsVal( $name, "since", undef ) - ? '&since=' . ReadingsVal( $name, "since", undef ) - : ""; - my $full_state = ReadingsVal( $name, "pollFullstate", undef ); + ReadingsVal( $name, 'since', undef ) + ? '&since=' . ReadingsVal( $name, 'since', undef ) + : ''; + + my $full_state = ReadingsVal( $name, 'pollFullstate', undef ); + if ($full_state) { - $full_state = "&full_state=true"; - readingsSingleUpdate( $hash, "pollFullstate", 0, 1 ); + $full_state = '&full_state=true'; + readingsSingleUpdate( $hash, 'pollFullstate', 0, 1 ); } else { - $full_state = ""; + $full_state = ''; } - $param->{'url'} = + + $param->{url} = $hash->{URL} - . '/_matrix/client/r0/sync?access_token=' - . $hash->{helper}->{"access_token"} + . 'r0/sync?access_token=' + . $hash->{helper}->{access_token} . $since . $full_state . '&timeout=50000&filter=' . ReadingsVal( $name, 'filterId', 0 ); - $param->{'method'} = 'GET'; - $param->{'timeout'} = 60; - $hash->{helper}->{"sync"}++; + + $param->{method} = 'GET'; + $param->{timeout} = 60; + $hash->{helper}->{sync}++; + Log3( $name, 5, -qq($name $hash->{helper}->{"access_token"} syncBeg $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) +qq($name $hash->{helper}->{access_token} syncBeg $param->{'msgnumber'}: $hash->{helper}->{next_refresh} > $now) ); } + when ('filter') { if ($value) { # get - $param->{'url'} = + $param->{url} = $hash->{URL} - . '/_matrix/client/v3/user/' + . 'v3/user/' . ReadingsVal( $name, "userId", 0 ) . '/filter/' . $value . '?access_token=' - . $hash->{helper}->{"access_token"}; - $param->{'method'} = 'GET'; + . $hash->{helper}->{access_token}; + + $param->{method} = 'GET'; } else { - $param->{'url'} = + $param->{url} = $hash->{URL} - . '/_matrix/client/v3/user/' + . 'v3/user/' . ReadingsVal( $name, "userId", 0 ) . '/filter?access_token=' - . $hash->{helper}->{"access_token"}; - $param->{'data'} = '{'; - $param->{'data'} .= + . $hash->{helper}->{access_token}; + + $param->{data} = '{'; + $param->{data} .= '"event_fields": ["type","content","sender"],'; - $param->{'data'} .= '"event_format": "client", '; - $param->{'data'} .= + + $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'} .= '}'; + $param->{data} .= '}'; } } } my $test = - "$param->{url}, " - . ( $param->{data} ? "\r\ndata: $param->{data}, " : "" ) - . ( $param->{header} ? "\r\nheader: $param->{header}" : "" ); + $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 ); Log3( $name, 3, -qq($name $param->{'msgnumber'} $def Request Busy/Sync $hash->{helper}->{"busy"} / $hash->{helper}->{"sync"}) +qq($name $param->{'msgnumber'} $def Request Busy/Sync $hash->{helper}->{busy} / $hash->{helper}->{sync}) ); + HttpUtils_NonblockingGet($param) ; # Starten der HTTP Abfrage. Es gibt keinen Return-Code. @@ -882,93 +940,93 @@ sub ParseHttpResponse { if ( $err ne "" ) { # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist Log3( $name, 2, "error while requesting " . $param->{url} . " - $err" ) ; # Eintrag fürs Log - readingsBulkUpdate( $hash, 'responseError', $err ); # Reading erzeugen + readingsBulkUpdate( $hash, 'lastRespErr', $err ); # Reading erzeugen readingsBulkUpdate( $hash, 'state', - 'Error - look responseError reading' ); - $hash->{helper}->{"softfail"} = 3; - $hash->{helper}->{"hardfail"}++; + 'Error - look lastRespErr reading' ); + $hash->{helper}->{softfail} = 3; + $hash->{helper}->{hardfail}++; } - elsif ( $data ne "" ) + 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 { decode_json($data) }; Log3( $name, 2, "$name: json error: $@ in data" ) if ($@); if ( $param->{code} == 200 ) { - $hash->{helper}->{"softfail"} = 0; - $hash->{helper}->{"hardfail"} = 0; + $hash->{helper}->{softfail} = 0; + $hash->{helper}->{hardfail} = 0; } else { - $hash->{helper}->{"softfail"}++; - $hash->{helper}->{"hardfail"}++ - if ( $hash->{helper}->{"softfail"} > 3 ); - readingsBulkUpdate( $hash, 'responseError', + $hash->{helper}->{softfail}++; + $hash->{helper}->{hardfail}++ + if ( $hash->{helper}->{softfail} > 3 ); + readingsBulkUpdate( $hash, 'lastRespErr', qq(S $hash->{helper}->{'softfail'}: $data) ); Log3( $name, 5, -qq($name $hash->{helper}->{"access_token"} ${def}End $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) +qq($name $hash->{helper}->{access_token} ${def}End $param->{'msgnumber'}: $hash->{helper}->{next_refresh} > $now) ); } # readingsBulkUpdate($hash, "fullResponse", $data); # default next request - $nextRequest = "sync"; + $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" ) { - $hash->{helper}->{"repeat"} = $param if ( $def ne "sync" ); - if ( $decoded->{'error'} eq "Access token has expired" ) { - if ( $decoded->{'soft_logout'} eq "true" ) { + my $errcode = $decoded->{'errcode'} ? $decoded->{'errcode'} : ''; + if ( $errcode eq 'M_UNKNOWN_TOKEN' ) { + $hash->{helper}->{'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" ) { + elsif ( $decoded->{'error'} eq 'refresh token does not exist' ) { $nextRequest = 'login'; } } - if ( $def eq "register" ) { - $hash->{helper}->{"session"} = $decoded->{'session'}; - $nextRequest = ""; #"reg2"; + if ( $def eq 'register' ) { + $hash->{helper}->{session} = $decoded->{session}; + $nextRequest = ''; #'reg2'; } - $hash->{helper}->{"session"} = $decoded->{'session'} + $hash->{helper}->{session} = $decoded->{session} if ( $decoded->{'session'} ); - readingsBulkUpdate( $hash, "session", $decoded->{'session'} ) + readingsBulkUpdate( $hash, 'session', $decoded->{session} ) if ( $decoded->{'session'} ); - if ( $def eq "reg2" || $def eq "login" || $def eq "refresh" ) { - readingsBulkUpdate( $hash, "lastRegister", $param->{code} ) - if $def eq "reg2"; - readingsBulkUpdate( $hash, "lastLogin", $param->{code} ) - if $def eq "login"; - readingsBulkUpdate( $hash, "lastRefresh", $param->{code} ) - if $def eq "refresh"; + if ( $def eq 'reg2' || $def eq 'login' || $def eq 'refresh' ) { + readingsBulkUpdate( $hash, 'lastRegister', $param->{code} ) + if $def eq 'reg2'; + readingsBulkUpdate( $hash, 'lastLogin', $param->{code} ) + if $def eq 'login'; + readingsBulkUpdate( $hash, 'lastRefresh', $param->{code} ) + if $def eq 'refresh'; if ( $param->{code} == 200 ) { - readingsBulkUpdate( $hash, "userId", $decoded->{'user_id'} ) - if ( $decoded->{'user_id'} ); - readingsBulkUpdate( $hash, "homeServer", - $decoded->{'homeServer'} ) - if ( $decoded->{'homeServer'} ); - readingsBulkUpdate( $hash, "deviceId", $decoded->{'device_id'} ) - if ( $decoded->{'device_id'} ); + readingsBulkUpdate( $hash, 'userId', $decoded->{user_id} ) + if ( $decoded->{user_id} ); + readingsBulkUpdate( $hash, 'homeServer', + $decoded->{homeServer} ) + if ( $decoded->{homeServer} ); + readingsBulkUpdate( $hash, 'deviceId', $decoded->{device_id} ) + if ( $decoded->{device_id} ); - $hash->{helper}->{"expires"} = $decoded->{'expires_in_ms'} - if ( $decoded->{'expires_in_ms'} ); - $hash->{helper}->{"refresh_token"} = $decoded->{'refresh_token'} - if ( $decoded->{'refresh_token'} ); - $hash->{helper}->{"access_token"} = $decoded->{'access_token'} - if ( $decoded->{'access_token'} ); - $hash->{helper}->{"next_refresh"} = - $now + $hash->{helper}->{"expires"} / 1000 - + $hash->{helper}->{expires} = $decoded->{expires_in_ms} + if ( $decoded->{expires_in_ms} ); + $hash->{helper}->{refresh_token} = $decoded->{refresh_token} + if ( $decoded->{refresh_token} ); + $hash->{helper}->{access_token} = $decoded->{access_token} + if ( $decoded->{access_token} ); + $hash->{helper}->{next_refresh} = + $now + $hash->{helper}->{expires} / 1000 - 60; # refresh one minute before end } Log3( $name, 5, -qq($name $hash->{helper}->{"access_token"} refreshEnd $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) +qq($name $hash->{helper}->{access_token} refreshEnd $param->{msgnumber}: $hash->{helper}->{'next_refresh'} > $now) ); } if ( $def eq "wellknown" ) { @@ -977,52 +1035,66 @@ qq($name $hash->{helper}->{"access_token"} refreshEnd $param->{'msgnumber'}: $ha } if ( $param->{code} == 200 && $def eq "sync" ) { Log3( $name, 5, -qq($name $hash->{helper}->{"access_token"} syncEnd $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) +qq($name $hash->{helper}->{"access_token"} syncEnd $param->{msgnumber}: $hash->{helper}->{'next_refresh'} > $now) ); - readingsBulkUpdate( $hash, "since", $decoded->{'next_batch'} ) - if ( $decoded->{'next_batch'} ); + readingsBulkUpdate( $hash, 'since', $decoded->{next_batch} ) + if ( $decoded->{next_batch} ); # roomlist - my $list = $decoded->{'rooms'}->{'join'}; + my $list = $decoded->{rooms}->{join}; #my @roomlist = (); my $pos = 0; - foreach my $id ( keys $list->%* ) { + for my $id ( keys $list->%* ) { if ( ref $list->{$id} eq ref {} ) { - my $member = ""; + my $member = ''; #my $room = $list->{$id}; $pos = $pos + 1; # matrixRoom ? - readingsBulkUpdate( $hash, "room$pos.id", $id ); + 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' ); +#for my $id ( $decoded->{'rooms'}->{'join'}->{AttrVal($name, 'matrixRoom', '!!')}->{'timeline'}->{'events'}->@* ) { + for 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' ) + + readingsBulkUpdate( $hash, 'room' . $pos . '.member', + $member ); + + for 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'}; - my $message = $tl->{'content'}->{'body'}; + my $sender = $tl->{sender}; + my $message = $tl->{content}->{body}; + if ( AttrVal( $name, 'matrixSender', '' ) =~ $sender ) { @@ -1039,34 +1111,40 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{'msgnumber'}: $hash- # readingsBulkUpdate($hash, "sender", $sender); #} } - elsif ( $tl->{'type'} eq - "org.matrix.msc3381.poll.response" ) + elsif ( + $tl->{type} eq 'org.matrix.msc3381.poll.response' ) { - my $sender = $tl->{'sender'}; + my $sender = $tl->{sender}; my $message = - $tl->{'content'} + $tl->{content} ->{'org.matrix.msc3381.poll.response'} - ->{'answers'}[0]; - if ( $tl->{'content'}->{'m.relates_to'} ) { - if ( $tl->{'content'}->{'m.relates_to'} - ->{'rel_type'} eq 'm.reference' ) + ->{answers}[0]; + + if ( $tl->{content}->{'m.relates_to'} ) { + if ( + + $tl->{content}->{'m.relates_to'}->{rel_type} + eq 'm.reference' + ) { - readingsBulkUpdate( $hash, "questionId", - $tl->{'content'}->{'m.relates_to'} - ->{'event_id'} ); + readingsBulkUpdate( $hash, 'questionId', + $tl->{content}->{'m.relates_to'} + ->{event_id} ); } } + if ( AttrVal( $name, 'matrixSender', '' ) =~ $sender ) { - readingsBulkUpdate( $hash, "message", + readingsBulkUpdate( $hash, 'message', $message ); - readingsBulkUpdate( $hash, "sender", $sender ); - $nextRequest = "questionEnd"; + + readingsBulkUpdate( $hash, 'sender', $sender ); + $nextRequest = 'questionEnd'; # command _Get_Message( $name, - $hash->{helper}->{"question"}, $message ); + $hash->{helper}->{question}, $message ); } } } @@ -1075,37 +1153,43 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{'msgnumber'}: $hash- } } } - if ( $def eq "logintypes" ) { + + if ( $def eq 'logintypes' ) { my $types = ''; foreach my $flow ( $decoded->{'flows'}->@* ) { - if ( $flow->{'type'} =~ /m\.login\.(.*)/ ) { + if ( $flow->{'type'} =~ /m\.login\.(.*)/x ) { - #$types .= "$flow->{'type'} "; - $types .= "$1 "; # if ($flow->{'type'} ); + #$types .= $flow->{'type'} . ' '; + $types .= $1 . ' '; # if ($flow->{'type'} ); } } - readingsBulkUpdate( $hash, "logintypes", $types ); + + readingsBulkUpdate( $hash, 'logintypes', $types ); } - if ( $def eq "filter" ) { - readingsBulkUpdate( $hash, "filterId", $decoded->{'filter_id'} ) + + if ( $def eq 'filter' ) { + readingsBulkUpdate( $hash, 'filterId', $decoded->{'filter_id'} ) if ( $decoded->{'filter_id'} ); } - if ( $def eq "msg" ) { - readingsBulkUpdate( $hash, "eventId", $decoded->{'event_id'} ) + + if ( $def eq 'msg' ) { + readingsBulkUpdate( $hash, 'eventId', $decoded->{'event_id'} ) if ( $decoded->{'event_id'} ); #m.relates_to } - if ( $def eq "question" ) { - readingsBulkUpdate( $hash, "questionId", $decoded->{'event_id'} ) + + if ( $def eq 'question' ) { + readingsBulkUpdate( $hash, 'questionId', $decoded->{'event_id'} ) if ( $decoded->{'event_id'} ); #m.relates_to } - if ( $def eq "questionEnd" ) { - readingsBulkUpdate( $hash, "eventId", $decoded->{'event_id'} ) + + if ( $def eq 'questionEnd' ) { + readingsBulkUpdate( $hash, 'eventId', $decoded->{'event_id'} ) if ( $decoded->{'event_id'} ); - readingsBulkUpdate( $hash, "questionId", "" ) + readingsBulkUpdate( $hash, 'questionId', '' ) if ( $decoded->{'event_id'} ); #m.relates_to @@ -1114,50 +1198,46 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{'msgnumber'}: $hash- readingsEndUpdate( $hash, 1 ); - $hash->{helper}->{"busy"} - --; # = $hash->{helper}->{"busy"} - 1; # queue is busy until response is received + $hash->{helper}->{busy} + --; # = $hash->{helper}->{busy} - 1; # queue is busy until response is received - $hash->{helper}->{"sync"}-- if ( $def eq "sync" ); # possible next sync - $nextRequest = "" - if ( $nextRequest eq "sync" && $hash->{helper}->{"sync"} > 0 ) + $hash->{helper}->{sync}-- if ( $def eq "sync" ); # possible next sync + $nextRequest = '' + if ( $nextRequest eq 'sync' && $hash->{helper}->{sync} > 0 ) ; # only one sync at a time! # _PerformHttpRequest or InternalTimer if FAIL >= 3 Log3( $name, 4, "$name : Matrix::ParseHttpResponse $hash" ); if ( AttrVal( $name, 'matrixPoll', 0 ) == 1 ) { - - ::Log( 1, '!!!DEBUG!!! - Neu in der Login funktion' ); - - if ( $nextRequest ne "" && $hash->{helper}->{"softfail"} < 3 ) { - if ( $nextRequest eq "sync" && $hash->{helper}->{"repeat"} ) { - $def = $hash->{helper}->{"repeat"}->{"def"}; - $value = $hash->{helper}->{"repeat"}->{"value"}; - $hash->{helper}->{"repeat"} = undef; + if ( $nextRequest ne '' && $hash->{helper}->{softfail} < 3 ) { + if ( $nextRequest eq 'sync' && $hash->{helper}->{repeat} ) { + $def = $hash->{helper}->{repeat}->{def}; + $value = $hash->{helper}->{repeat}->{value}; + $hash->{helper}->{repeat} = undef; _PerformHttpRequest( $hash, $def, $value ); } else { _PerformHttpRequest( $hash, $nextRequest, '' ); } } - elsif ( $hash->{helper}->{"softfail"} == 0 ) { + elsif ( $hash->{helper}->{softfail} == 0 ) { # nichts tun, doppelter sync verhindert } else { my $pauseLogin; - if ( $hash->{helper}->{"hardfail"} >= 3 ) { + if ( $hash->{helper}->{hardfail} >= 3 ) { $pauseLogin = 300; # lange Pause wenn zu viele Fehler } - elsif ( $hash->{helper}->{"softfail"} >= 3 ) { + elsif ( $hash->{helper}->{softfail} >= 3 ) { $pauseLogin = 30; # kurze Pause nach drei Fehlern oder einem 400 } else { $pauseLogin = 10; # nach logischem Fehler ganz kurze Pause } - my $timeToStart = gettimeofday() + $pauseLogin; RemoveInternalTimer($hash); - InternalTimer( $timeToStart, \&Login, $hash ); + InternalTimer( gettimeofday() + $pauseLogin, \&Login, $hash ); } }