From af9d97e6864522d5108edd36c25715f950128ebd Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Sun, 25 Dec 2022 17:56:52 +0100 Subject: [PATCH 1/6] first ready version after rewrite --- .gitignore | 4 +- FHEM/{98_Matrix.pm => 70_Matrix.pm} | 35 +- lib/FHEM/Devices/Matrix/Client.pm | 1169 +++++++++++++++++++++++++++ lib/FHEM/Devices/Matrix/Matrix.pm | 781 ------------------ 4 files changed, 1189 insertions(+), 800 deletions(-) rename FHEM/{98_Matrix.pm => 70_Matrix.pm} (94%) create mode 100644 lib/FHEM/Devices/Matrix/Client.pm delete mode 100644 lib/FHEM/Devices/Matrix/Matrix.pm diff --git a/.gitignore b/.gitignore index ecf66f8..25f63f6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ *.o *.pm.tdy *.bs +.vscode/ +.gitignore # Devel::Cover cover_db/ @@ -32,4 +34,4 @@ inc/ /Makefile.old /MANIFEST.bak /pm_to_blib -/*.zip +/*.zip \ No newline at end of file diff --git a/FHEM/98_Matrix.pm b/FHEM/70_Matrix.pm similarity index 94% rename from FHEM/98_Matrix.pm rename to FHEM/70_Matrix.pm index bcda4de..5565e79 100644 --- a/FHEM/98_Matrix.pm +++ b/FHEM/70_Matrix.pm @@ -33,35 +33,34 @@ use FHEM::Meta; use GPUtils qw(GP_Export); use JSON; -require FHEM::Devices::Matrix::Matrix; +require FHEM::Devices::Matrix::Client; #-- Run before package compilation BEGIN { - - #-- Export to main context with different name - GP_Export(qw( - Initialize - )); + sub ::Matrix_Initialize { goto &Initialize } } sub Initialize { my ($hash) = @_; - - $hash->{DefFn} = \&FHEM::Devices::Matrix::Define; - $hash->{UndefFn} = \&FHEM::Devices::Matrix::Undef; - $hash->{Delete} = \&FHEM::Devices::Matrix::Delete; - $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} = FHEM::Devices::Matrix::Attr_List(); + $hash->{DefFn} = \&FHEM::Devices::Matrix::Client::Define; + $hash->{UndefFn} = \&FHEM::Devices::Matrix::Client::Undef; + $hash->{Delete} = \&FHEM::Devices::Matrix::Client::Delete; + $hash->{SetFn} = \&FHEM::Devices::Matrix::Client::Set; + $hash->{GetFn} = \&FHEM::Devices::Matrix::Client::Get; + $hash->{AttrFn} = \&FHEM::Devices::Matrix::Client::Attr; + $hash->{ReadFn} = \&FHEM::Devices::Matrix::Client::Read; + $hash->{RenameFn} = \&FHEM::Devices::Matrix::Client::Rename; + $hash->{NotifyFn} = \&FHEM::Devices::Matrix::Client::Notify; + + $hash->{AttrList} = FHEM::Devices::Matrix::Client::Attr_List(); + + $hash->{parseParams} = + 1; # wir verwenden parseParams für Set und Get (CoolTux) + return FHEM::Meta::InitMod( __FILE__, $hash ); } - 1; =pod diff --git a/lib/FHEM/Devices/Matrix/Client.pm b/lib/FHEM/Devices/Matrix/Client.pm new file mode 100644 index 0000000..a0b585e --- /dev/null +++ b/lib/FHEM/Devices/Matrix/Client.pm @@ -0,0 +1,1169 @@ +########################################################################## +# Usage: +# +########################################################################## +# $Id: Matrix.pm 22821 2022-11-12 12:52:00Z Man-fred $ +# +# from the developerpages: +# 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; +use warnings; + +use HttpUtils; +use JSON; +use GPUtils qw(GP_Import); +use FHEM::Core::Authentication::Passwords qw(:ALL); + +use experimental qw /switch/ + ; #(CoolTux) - als Ersatz für endlos lange elsif Abfragen +use Carp qw( carp ) + ; # wir verwenden Carp für eine bessere Fehlerrückgabe (CoolTux) + +use Data::Dumper; # Debugging + +# try to use JSON::MaybeXS wrapper +# for chance of better performance + open code +eval { + require JSON::MaybeXS; + import JSON::MaybeXS qw( decode_json encode_json ); + 1; +} or do { + + # try to use JSON wrapper + # for chance of better performance + eval { + # JSON preference order + local $ENV{PERL_JSON_BACKEND} = + 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' + unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); + + require JSON; + import JSON qw( decode_json encode_json ); + 1; + } or do { + + # In rare cases, Cpanel::JSON::XS may + # be installed but JSON|JSON::MaybeXS not ... + eval { + require Cpanel::JSON::XS; + import Cpanel::JSON::XS qw(decode_json encode_json); + 1; + } or do { + + # In rare cases, JSON::XS may + # be installed but JSON not ... + eval { + require JSON::XS; + import JSON::XS qw(decode_json encode_json); + 1; + } or do { + + # Fallback to built-in JSON which SHOULD + # be available since 5.014 ... + eval { + require JSON::PP; + import JSON::PP qw(decode_json encode_json); + 1; + } or do { + + # Fallback to JSON::backportPP in really rare cases + require JSON::backportPP; + import JSON::backportPP qw(decode_json encode_json); + 1; + }; + }; + }; + }; +}; + +BEGIN { + + GP_Import( + qw( + readingFnAttributes + readingsBeginUpdate + readingsBulkUpdate + readingsEndUpdate + readingsSingleUpdate + Log3 + defs + init_done + IsDisabled + deviceEvents + AttrVal + ReadingsVal + HttpUtils_NonblockingGet + InternalTimer + gettimeofday + fhem + ) + ); +} + +my $Module_Version = '0.0.9'; + +sub Attr_List { + return +"matrixLogin:password matrixRoom matrixPoll:0,1 matrixSender matrixMessage matrixQuestion_ matrixQuestion_[0-9]+ matrixAnswer_ matrixAnswer_[0-9]+ $readingFnAttributes"; +} + +sub Define { + + #(CoolTux) bei einfachen übergaben nimmt man die Daten mit shift auf + my $hash = shift; + my $aArg = shift; + + 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->{USER} = $aArg->[3]; + $hash->{VERSION} = $Module_Version; + + $hash->{helper}->{passwdobj} = + FHEM::Core::Authentication::Passwords->new( $hash->{TYPE} ); + + #$hash->{helper}->{i18} = Get_I18n(); + $hash->{NOTIFYDEV} = "global"; + + readingsSingleUpdate( $hash, 'state', 'defined', 1 ); + + Log3( $name, 1, +"$name: Define: $name with Server URL: $hash->{URL} and User: $hash->{USER}" + ); + + return; +} + +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; +} + +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 + + return; +} + +sub _Startproc { # wir machen daraus eine privat function (CoolTux) + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $hash = shift; + + my $name = $hash->{NAME}; + + Log3( $name, 4, + "$name : Matrix::_Startproc $hash " + . AttrVal( $name, 'matrixPoll', '-1' ) ); + + # Update necessary? + Log3( $name, 1, + "$name: Start V" . $hash->{VERSION} . " -> V" . $Module_Version ) + if ( $hash->{VERSION} ); + + return ::readingsSingleUpdate( $hash, 'state', 'please set password first', + 1 ) + if ( + !exists( $hash->{helper}->{passwdobj} ) + || ( exists( $hash->{helper}->{passwdobj} ) + && !defined( $hash->{helper}->{passwdobj}->getReadPassword($name) ) + ) + ); + + $hash->{helper}->{"softfail"} = 1; + + Login($hash); + + return; +} + +sub Login { + my $hash = shift; + + Log3( $hash->{NAME}, 4, "$hash->{NAME} : Matrix::Login $hash" ); + + 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; + + my $name = $hash->{NAME}; + return if ( ::IsDisabled($name) ); + + my $devname = $dev->{NAME}; + my $devtype = $dev->{TYPE}; + my $events = ::deviceEvents( $dev, 1 ); + return if ( !$events ); + + return _Startproc($hash) + if ( + ( + grep { /^INITIALIZED$/x } @{$events} + or grep { /^ATTR.$name.matrixPoll.1/x } @{$events} + or grep { /^DELETEATTR.$name.disable$/x } @{$events} + or grep { /^DEFINED.$name$/x } @{$events} + ) + && $init_done + ); + +#(CoolTux) bin mir nicht sicher wieso die Schleife. Nötig ist sie aber egal wofür gedacht nicht. +#(Man-Fred) die Schleife ist vom Debugging, ich wollte wissen was im Notify ankommt. +# kann raus in einer späteren Version +# foreach my $event ( @{$events} ) { +# $event = "" if ( !defined($event) ); +# ### Writing log entry +# Log3( $name, 3, "$name : Matrix::Notify $devname - $event" ); +# $language = AttrVal( 'global', 'language', 'EN' ) +# if ( $event =~ /ATTR global language.*/ ); + + # # Examples: + # # $event = "ATTR global language DE" + # # $event = "readingname: value" + # # or + # # $event = "INITIALIZED" (for $devName equal "global") + # # + # # processing $event with further code + # } + + return + ; #(CoolTux) es reicht nur return. Wichtig jede sub muss immer mit return enden +} + +############################################################################################# +# 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 = shift; + 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" + ) + if (!defined($passResp) + && defined($passErr) ); + + Log3( $name, 1, +"$name : Matrix::Rename - change password hash after rename successfully" + ) + if ( defined($passResp) + && !defined($passErr) ); + + #my $nhash = $defs{$new}; + return; +} + +sub _I18N { # wir machen daraus eine privat function (CoolTux) + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $value = shift; + + my $def = { + 'EN' => { + 'require2' => 'requires 2 arguments', + 'beginWithNumber' => 'must begin with a number', + 'question' => 'a new question' + }, + 'DE' => { + 'require2' => 'benötigt 2 Argumente', + 'beginWithNumber' => 'muss mit einer Ziffer beginnen', + 'question' => 'Eine neue Frage' + }, + }; + my $result = $def->{ AttrVal( 'global', 'language', 'EN' ) }->{$value}; + return ( $result ? $result : $value ); + +} + +sub Get { + my ( $hash, $name, $cmd, @args ) = @_; + my $value = join( " ", @args ); + + #$cmd = '?' if (!$cmd); + +#(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, '' ); + } + + when ('logintypes') { + return _PerformHttpRequest( $hash, $cmd, '' ); + } + + 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; + return _PerformHttpRequest( $hash, $cmd, '' ); + } + + when ('filter') { + return qq("get Matrix $cmd" needs a filterId to request); + return _PerformHttpRequest( $hash, $cmd, $value ); + } + + default { + return +"Unknown argument $cmd, choose one of logintypes filter sync wellknown"; + } + } +} + +sub Set { + my $hash = shift; + my $aArg = shift; + my $hArg = shift; + + my $name = shift @$aArg; + my $cmd = shift @$aArg + // carp q[set Matrix Client needs at least one argument] && return; + + my $value = join( ' ', @{$aArg} ); + + given ($cmd) { + when ('msg') { + return _PerformHttpRequest( $hash, $cmd, $value ); + } + when ('pollFullstate') { + readingsSingleUpdate( $hash, $cmd, $value, 1 ); # Readings erzeugen + } + when ('setPassword') { + return qq(usage: $cmd pass=) + if ( scalar( @{$aArg} ) != 0 + || scalar( keys %{$hArg} ) != 1 ); + my ( $passResp, $passErr ); + + ( $passResp, $passErr ) = $hash->{helper}->{passwdobj} + ->setStorePassword( $name, $hArg->{'pass'} ); + + return qq{error while saving the password - $passErr} + if (!defined($passResp) + && defined($passErr) ); + + return _Startproc($hash) + if ( defined($passResp) + && !defined($passErr) ); + } + when ('removePassword') { + return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); + + my ( $passResp, $passErr ); + ( $passResp, $passErr ) = + $hash->{helper}->{passwdobj}->setDeletePassword($name); + + return qq{error while saving the password - $passErr} + if (!defined($passResp) + && defined($passErr) ); + + return q{password successfully removed} + if ( defined($passResp) + && !defined($passErr) ); + return; + } + when ('filter') { + return _PerformHttpRequest( $hash, $cmd, '' ); + } + when ('question') { + return _PerformHttpRequest( $hash, $cmd, $value ); + } + when ('questionEnd') { + return _PerformHttpRequest( $hash, $cmd, $value ); + } + when ('register') { + return _PerformHttpRequest( $hash, $cmd, '' ) + ; # 2 steps (ToDo: 3 steps empty -> dummy -> registration_token o.a.) + } + when ('login') { + return _PerformHttpRequest( $hash, $cmd, '' ); + } + when ('refresh') { + return _PerformHttpRequest( $hash, $cmd, '' ); + } + + default { + my $list; + $list .= ( + exists( $hash->{helper}->{passwdobj} ) && defined( + $hash->{helper}->{passwdobj}->getReadPassword($name) + ) + ? 'removePassword:noArg ' + : 'setPassword ' + ); + + $list .= 'msg ' + 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' + if ( ::AttrVal( $name, 'devel', 0 ) == 1 + && exists( $hash->{helper}->{passwdobj} ) + && defined( + $hash->{helper}->{passwdobj}->getReadPassword($name) ) ); + + return 'Unknown argument ' . $cmd . ', choose one of ' . $list; + } + } + + return; +} + +sub Attr { + my ( $cmd, $name, $attr_name, $attr_value ) = @_; + + Log3( $name, 4, "Attr - $cmd - $name - $attr_name - $attr_value" ); + + 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]/ ); + $_[2] = "matrixQuestion_$erg[0]"; + $_[3] = $erg[1]; + } + 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]/ ); + $_[2] = "matrixAnswer_$erg[0]"; + $_[3] = $erg[1]; + } + } + + return; +} + +sub _Get_Message { # wir machen daraus eine privat function (CoolTux) + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $name = shift; + my $def = shift; + my $message = shift; + + Log3( $name, 3, "$name - $def - $message" ); + 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; + + #my ($question, $answer); + 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 (...) { + # ... + # } + Log3( $name, 3, "$name - $question - $answers[$pos]" ); + $answer = $answers[$pos] if ( $message eq $question ); + if ($answer) { + Log3( $name, 3, "$name - $pos - $answer" ); + fhem($answer); + last; + } + $pos++; + } + + return; +} + +sub _PerformHttpRequest { # wir machen daraus eine privat function (CoolTux) + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + +#(CoolTux) hier solltest Du überlegen das Du die einzelnen Anweisung nach der Bedingung in einzelne Funktionen auslagerst +# Subroutine "_PerformHttpRequest" with high complexity score +#(Man-Fred) da ich noch nicht wusste wie ähnlich die Ergebnisse sind habe ich erst mal alles zusammen ausgewertet + my $hash = shift; + my $def = shift; + my $value = shift; + + 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); + } + + $hash->{helper}->{"msgnumber"} = + $hash->{helper}->{"msgnumber"} ? $hash->{helper}->{"msgnumber"} + 1 : 1; + + 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 + : 1; # queue is busy until response is received + + $hash->{helper}->{"sync"} = 0 if ( !$hash->{helper}->{"sync"} ); + + $hash->{helper}->{'LASTSEND'} = $now; # remember when last sent + + if ( $def eq "sync" + && $hash->{helper}->{"next_refresh"} < $now + && AttrVal( $name, 'matrixLogin', '' ) eq 'password' ) + { + $def = "refresh"; + Log3( $name, 5, +qq($name $hash->{helper}->{"access_token"} sync2refresh - $hash->{helper}->{"next_refresh"} < $now) + ); + $hash->{helper}->{"next_refresh"} = $now + 300; + } + + 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 + msgnumber => $msgnumber # lfd. Nummer Request + }; + + given ($def) { + when ('logintypes') { + $param->{'url'} = $hash->{URL} . "/_matrix/client/r0/login"; + $param->{'method'} = 'GET'; + } + when ('register') { + $param->{'url'} = $hash->{URL} . "/_matrix/client/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'} = +'{"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'} = + '{"username":"' + . $hash->{USER} + . '", "password":"' + . $passwd + . '", "auth": {"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'} = +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'} = +'{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"' + . $hash->{USER} + . '" }, "password":"' + . $passwd . '"' + . $deviceId . '}'; + } + } + when ('login2') { + $param->{'url'} = $hash->{URL} . "/_matrix/client/v3/login"; + if ( AttrVal( $name, 'matrixLogin', '' ) eq 'token' ) { + $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"} . '"}'; + Log3( $name, 5, +qq($name $hash->{helper}->{"access_token"} refreshBeg $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) + ); + } + when ('wellknown') { + $param->{'url'} = $hash->{URL} . "/.well-known/matrix/client"; + } + when ('msg') { + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/r0/rooms/' + . AttrVal( $name, 'matrixMessage', '!!' ) + . '/send/m.room.message?access_token=' + . $hash->{helper}->{"access_token"}; + $param->{'data'} = '{"msgtype":"m.text", "body":"' . $value . '"}'; + } + when ('questionX') { + $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; + + # min. question and one answer + if ( int(@question) >= 2 ) { + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/v3/rooms/' + . AttrVal( $name, 'matrixMessage', '!!' ) + . '/send/m.poll.start?access_token=' + . $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'} .= +qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); + $comma = ','; + } + $param->{'data'} .= +qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}}); + } + else { + Log3( $name, 5, "question: $value $size $question[0]" ); + return; + } + } + when ('question') { + $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; + + # min. question and one answer + if ( int(@question) >= 2 ) { + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/v3/rooms/' + . AttrVal( $name, 'matrixMessage', '!!' ) + . '/send/m.poll.start?access_token=' + . $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'} .= +qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); + $comma = ','; + } + $param->{'data'} .= +qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}); + } + else { + Log3( $name, 5, "question: $value $size $question[0]" ); + return; + } + } + when ('questionEnd') { + $value = ReadingsVal( $name, "questionId", "" ) if ( !$value ); + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/v3/rooms/' + . AttrVal( $name, 'matrixMessage', '!!' ) + . '/send/m.poll.end?access_token=' + . $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", "" ) + . ' erhalten von ' + . ReadingsVal( $name, "sender", "" ) . '"}'; + } + when ('sync') { + my $since = + 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 ); + } + else { + $full_state = ""; + } + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/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"}++; + Log3( $name, 5, +qq($name $hash->{helper}->{"access_token"} syncBeg $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) + ); + } + when ('filter') { + if ($value) { # get + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/v3/user/' + . ReadingsVal( $name, "userId", 0 ) + . '/filter/' + . $value + . '?access_token=' + . $hash->{helper}->{"access_token"}; + $param->{'method'} = 'GET'; + } + else { + $param->{'url'} = + $hash->{URL} + . '/_matrix/client/v3/user/' + . ReadingsVal( $name, "userId", 0 ) + . '/filter?access_token=' + . $hash->{helper}->{"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 ); + + Log3( $name, 3, +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. + + return; +} + +sub ParseHttpResponse { + +#(CoolTux) hier solltest Du überlegen das Du die einzelnen Anweisung nach der Bedingung in einzelne Funktionen auslagerst +# Subroutine "_PerformHttpRequest" with high complexity score +#(Man-Fred) da ich noch nicht wusste wie ähnlich die Ergebnisse sind habe ich erst mal alles zusammen ausgewertet + + my $param = shift; + my $err = shift; + my $data = shift; + + #::Log( 1, '!!!DEBUG!!! - Error: ' . $err ); + #::Log( 1, '!!!DEBUG!!! - Param: ' . Dumper $param); + #::Log( 1, '!!!DEBUG!!! - Data: ' . Dumper $data); + + #return; + + my $hash = $param->{hash}; + my $def = $param->{def}; + my $value = $param->{value}; + my $name = $hash->{NAME}; + my $now = gettimeofday(); + my $nextRequest = ""; + + Log3( $name, 3, + qq($name $param->{'msgnumber'} $def Result $param->{code}) ); + readingsBeginUpdate($hash); + ###readingsBulkUpdate($hash, "httpHeader", $param->{httpheader}); + readingsBulkUpdate( $hash, 'httpStatus', $param->{code} ); + readingsBulkUpdate( $hash, 'state', $def . ' - ' . $param->{code} ); + 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, 'state', + 'Error - look responseError reading' ); + $hash->{helper}->{"softfail"} = 3; + $hash->{helper}->{"hardfail"}++; + } + 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; + } + else { + $hash->{helper}->{"softfail"}++; + $hash->{helper}->{"hardfail"}++ + if ( $hash->{helper}->{"softfail"} > 3 ); + readingsBulkUpdate( $hash, 'responseError', + qq(S $hash->{helper}->{'softfail'}: $data) ); + Log3( $name, 5, +qq($name $hash->{helper}->{"access_token"} ${def}End $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) + ); + } + + # 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" ) { + $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" ) { + $nextRequest = 'login'; + } + } + + if ( $def eq "register" ) { + $hash->{helper}->{"session"} = $decoded->{'session'}; + $nextRequest = ""; #"reg2"; + } + $hash->{helper}->{"session"} = $decoded->{'session'} + if ( $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 ( $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'} ); + + $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) + ); + } + if ( $def eq "wellknown" ) { + + # https://spec.matrix.org/unstable/client-server-api/ + } + if ( $param->{code} == 200 && $def eq "sync" ) { + Log3( $name, 5, +qq($name $hash->{helper}->{"access_token"} syncEnd $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) + ); + 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'}; + my $message = $tl->{'content'}->{'body'}; + if ( AttrVal( $name, 'matrixSender', '' ) =~ + $sender ) + { + readingsBulkUpdate( $hash, "message", + $message ); + readingsBulkUpdate( $hash, "sender", $sender ); + + # command + _Get_Message( $name, '99', $message ); + } + +#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'}; + my $message = + $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' ) + { + readingsBulkUpdate( $hash, "questionId", + $tl->{'content'}->{'m.relates_to'} + ->{'event_id'} ); + } + } + if ( AttrVal( $name, 'matrixSender', '' ) =~ + $sender ) + { + readingsBulkUpdate( $hash, "message", + $message ); + readingsBulkUpdate( $hash, "sender", $sender ); + $nextRequest = "questionEnd"; + + # command + _Get_Message( $name, + $hash->{helper}->{"question"}, $message ); + } + } + } + + #push(@roomlist,"$id: "; + } + } + } + if ( $def eq "logintypes" ) { + my $types = ''; + foreach my $flow ( $decoded->{'flows'}->@* ) { + if ( $flow->{'type'} =~ /m\.login\.(.*)/ ) { + + #$types .= "$flow->{'type'} "; + $types .= "$1 "; # if ($flow->{'type'} ); + } + } + readingsBulkUpdate( $hash, "logintypes", $types ); + } + 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 ( $decoded->{'event_id'} ); + + #m.relates_to + } + 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 ( $decoded->{'event_id'} ); + readingsBulkUpdate( $hash, "questionId", "" ) + if ( $decoded->{'event_id'} ); + + #m.relates_to + } + } + + readingsEndUpdate( $hash, 1 ); + + $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 ) + ; # 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; + _PerformHttpRequest( $hash, $def, $value ); + } + else { + _PerformHttpRequest( $hash, $nextRequest, '' ); + } + } + elsif ( $hash->{helper}->{"softfail"} == 0 ) { + + # nichts tun, doppelter sync verhindert + } + else { + my $pauseLogin; + if ( $hash->{helper}->{"hardfail"} >= 3 ) { + $pauseLogin = 300; # lange Pause wenn zu viele Fehler + } + 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 ); + } + } + + return; +} + +1; #(CoolTux) ein Modul endet immer mit 1; + +__END__ #(CoolTux) Markiert im File das Ende des Programms. Danach darf beliebiger Text stehen. Dieser wird vom Perlinterpreter nicht berücksichtigt. diff --git a/lib/FHEM/Devices/Matrix/Matrix.pm b/lib/FHEM/Devices/Matrix/Matrix.pm deleted file mode 100644 index e5d657f..0000000 --- a/lib/FHEM/Devices/Matrix/Matrix.pm +++ /dev/null @@ -1,781 +0,0 @@ -########################################################################## -# Usage: -# -########################################################################## -# $Id: Matrix.pm 22821 2022-11-12 12:52:00Z Man-fred $ -# -# from the developerpages: -# 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; - -use strict; -use warnings; -use HttpUtils; -use JSON; -use GPUtils qw(GP_Import); -use FHEM::Core::Authentication::Passwords qw(:ALL); - -use experimental qw /switch/; #(CoolTux) - als Ersatz für endlos lange elsif Abfragen - -BEGIN { - - GP_Import(qw( - readingFnAttributes - readingsBeginUpdate - readingsBulkUpdate - readingsEndUpdate - readingsSingleUpdate - Log3 - defs - init_done - IsDisabled - deviceEvents - AttrVal - ReadingsVal - HttpUtils_NonblockingGet - InternalTimer - gettimeofday - fhem - )) -}; - -my $Module_Version = '0.0.9'; -my $language = 'EN'; - -sub Attr_List{ - return "matrixLogin:password matrixRoom matrixPoll:0,1 matrixSender matrixMessage matrixQuestion_ matrixQuestion_[0-9]+ matrixAnswer_ matrixAnswer_[0-9]+ $readingFnAttributes"; -} - -sub Define { - #(CoolTux) bei einfachen übergaben nimmt man die Daten mit shift auf - my $hash = shift; - my $def = shift; - - 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->{helper}->{i18} = Get_I18n(); - $hash->{NOTIFYDEV} = "global"; - - Startproc($hash) if($init_done); #(CoolTux) Wie startet Startproc() wenn $init_done 0 ist. Dann bleibt das Modul stehen und macht nichts mehr - # es empfiehlt sich hier in der NotifyFn das globale Event INITIALIZED abzufangen. - # Ok gerade gesehen hast Du gemacht!!! - - return ; -} - -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->{myTimer}) if($hash->{myTimer}); - $hash->{myTimer} = undef; - return ; -} - -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 - - return ; -} - -sub Startproc { - my $hash = shift; - - my $name = $hash->{NAME}; - - Log3($name, 4, "$name : Matrix::Startproc $hash ".AttrVal($name,'matrixPoll','-1')); - # Update necessary? - Log3($name, 1, "$name: Start V".$hash->{ModuleVersion}." -> V".$Module_Version) if ($hash->{ModuleVersion}); - - $hash->{ModuleVersion} = $Module_Version; - $language = AttrVal('global','language','EN'); - $hash->{helper}->{"softfail"} = 1; - - Login($hash) if (AttrVal($name,'matrixPoll',0) == 1); - - return; -} - -sub Login { - my $hash = shift; - - Log3($hash->{NAME}, 4, "$hash->{NAME} : Matrix::Login $hash"); - - 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; - - my $name = $hash->{NAME}; - my $devName = $dev->{NAME}; - - return "" if(IsDisabled($name)); - - my $events = deviceEvents($dev,1); - - return if( !$events ); - - #if(($devName eq "global") && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) - #(CoolTux) unnötige Klammern, und vielleicht bisschen übersichtlicher versuchen :-) - if ( $devName eq "global" - && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) - { - Log3($name, 4, "$name : Matrix::Notify $hash"); - Startproc($hash); - } - - - #(CoolTux) bin mir nicht sicher wieso die Schleife. Nötig ist sie aber egal wofür gedacht nicht. - #(Man-Fred) die Schleife ist vom Debugging, ich wollte wissen was im Notify ankommt. - # kann raus in einer späteren Version - foreach my $event (@{$events}) { - $event = "" if(!defined($event)); - ### Writing log entry - Log3($name, 4, "$name : Matrix::Notify $devName - $event"); - $language = AttrVal('global','language','EN') if ($event =~ /ATTR global language.*/); - # Examples: - # $event = "ATTR global language DE" - # $event = "readingname: value" - # or - # $event = "INITIALIZED" (for $devName equal "global") - # - # processing $event with further code - } - - return; #(CoolTux) es reicht nur return. Wichtig jede sub muss immer mit return enden -} - -############################################################################################# -# 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 = shift; - 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") - if ( !defined($passResp) - && defined($passErr) ); - - Log3($name, 1, "$name : Matrix::Rename - change password hash after rename successfully") - if ( defined($passResp) - && !defined($passErr) ); - - #my $nhash = $defs{$new}; - return; -} - -sub I18N { - my $value = shift; - - my $def = { - 'EN' => { - 'require2' => 'requires 2 arguments', - 'beginWithNumber' => 'must begin with a number', - 'question' => 'a new question' - }, - 'DE' => { - 'require2' => 'benötigt 2 Argumente', - 'beginWithNumber' => 'muss mit einer Ziffer beginnen', - 'question' => 'Eine neue Frage' - }, - }; - my $result = $def->{$language}->{$value}; - return ($result ? $result : $value); - -} - -sub Get { - my ( $hash, $name, $cmd, @args ) = @_; - my $value = join(" ", @args); - #$cmd = '?' if (!$cmd); - - #(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, ''); - } - - when ('logintypes') { - return PerformHttpRequest($hash, $cmd, ''); - } - - 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; - return PerformHttpRequest($hash, $cmd, ''); - } - - when ('filter') { - return qq("get Matrix $cmd" needs a filterId to request); - return PerformHttpRequest($hash, $cmd, $value); - } - - default { return "Unknown argument $cmd, choose one of logintypes filter sync wellknown"; } - } -} - -sub Set { - my ( $hash, $name, $cmd, @args ) = @_; - my $value = join(" ", @args); - - given ($cmd) { - when ('msg') { - return PerformHttpRequest($hash, $cmd, $value); - } - when ('pollFullstate') { - readingsSingleUpdate($hash, $cmd, $value, 1); # Readings erzeugen - } - when ('password') { - my ($erg,$err) = $hash->{helper}->{passwdobj}->setStorePassword($name,$value); - return; - } - when ('filter') { - return PerformHttpRequest($hash, $cmd, ''); - } - when ('question') { - return PerformHttpRequest($hash, $cmd, $value); - } - when ('questionEnd') { - return PerformHttpRequest($hash, $cmd, $value); - } - when ('register') { - return PerformHttpRequest($hash, $cmd, ''); # 2 steps (ToDo: 3 steps empty -> dummy -> registration_token o.a.) - } - when ('login') { - return PerformHttpRequest($hash, $cmd, ''); - } - when ('refresh') { - return PerformHttpRequest($hash, $cmd, ''); - } - - default { - return "Unknown argument $cmd, choose one of filter:noArg password question questionEnd pollFullstate:0,1 msg register login:noArg refresh:noArg"; - } - } - - return; -} - - -sub Attr { - my ($cmd,$name,$attr_name,$attr_value) = @_; - - Log3($name, 4, "Attr - $cmd - $name - $attr_name - $attr_value"); - - 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]/); - $_[2] = "matrixQuestion_$erg[0]"; - $_[3] = $erg[1]; - } - 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]/); - $_[2] = "matrixAnswer_$erg[0]"; - $_[3] = $erg[1]; - } - } - - return ; -} - -sub Get_Message { - my $name = shift; - my $def = shift; - my $message = shift; - - Log3($name, 3, "$name - $def - $message"); - 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; - #my ($question, $answer); - 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 (...) { - # ... - # } - Log3($name, 3, "$name - $question - $answers[$pos]"); - $answer = $answers[$pos] if ($message eq $question); - if ($answer){ - Log3($name, 3, "$name - $pos - $answer"); - fhem($answer); - last; - } - $pos++; - } - - return; -} - -sub PerformHttpRequest -{ - #(CoolTux) hier solltest Du überlegen das Du die einzelnen Anweisung nach der Bedingung in einzelne Funktionen auslagerst - # Subroutine "PerformHttpRequest" with high complexity score - #(Man-Fred) da ich noch nicht wusste wie ähnlich die Ergebnisse sind habe ich erst mal alles zusammen ausgewertet - my $hash = shift; - my $def = shift; - my $value = shift; - - 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) ; - } - $hash->{helper}->{"msgnumber"} = $hash->{helper}->{"msgnumber"} ? $hash->{helper}->{"msgnumber"} + 1 : 1; - 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 : 1; # queue is busy until response is received - $hash->{helper}->{"sync"} = 0 if (!$hash->{helper}->{"sync"}); - - $hash->{helper}->{'LASTSEND'} = $now; # remember when last sent - if ($def eq "sync" && $hash->{helper}->{"next_refresh"} < $now && AttrVal($name,'matrixLogin','') eq 'password'){ - $def = "refresh"; - Log3($name, 5, qq($name $hash->{helper}->{"access_token"} sync2refresh - $hash->{helper}->{"next_refresh"} < $now) ); - $hash->{helper}->{"next_refresh"} = $now + 300; - } - - 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 - msgnumber => $msgnumber # lfd. Nummer Request - }; - - given ($def) { - when ('logintypes') { - $param->{'url'} = $hash->{server}."/_matrix/client/r0/login"; - $param->{'method'} = 'GET'; - } - when ('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.'"}'; - } - when ('reg1'){ - $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; - $param->{'data'} = '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$passwd.'"}'; - } - when ('reg2'){ - $param->{'url'} = $hash->{server}."/_matrix/client/v3/register"; - $param->{'data'} = '{"username":"'.$hash->{user}.'", "password":"'.$passwd.'", "auth": {"session":"'.$hash->{helper}->{"session"}.'","type":"m.login.dummy"}}'; - } - when ('login'){ - if (AttrVal($name,'matrixLogin','') eq 'token'){ - $param->{'url'} = $hash->{server}."/_matrix/client/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->{server}."/_matrix/client/v3/login"; - $param->{'data'} = '{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"'.$hash->{user}.'" }, "password":"'.$passwd.'"'.$deviceId.'}'; - } - } - when ('login2'){ - $param->{'url'} = $hash->{server}."/_matrix/client/v3/login"; - if (AttrVal($name,'matrixLogin','') eq 'token'){ - $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->{server}.'/_matrix/client/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) ); - } - when ('wellknown'){ - $param->{'url'} = $hash->{server}."/.well-known/matrix/client"; - } - when ('msg'){ - $param->{'url'} = $hash->{server}.'/_matrix/client/r0/rooms/'.AttrVal($name, 'matrixMessage', '!!').'/send/m.room.message?access_token='.$hash->{helper}->{"access_token"}; - $param->{'data'} = '{"msgtype":"m.text", "body":"'.$value.'"}'; - } - when ('questionX'){ - $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; - # 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='.$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'} .= qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); - $comma = ','; - } - $param->{'data'} .= qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}}); - } else { - Log3($name, 5, "question: $value $size $question[0]"); - return; - } - } - when ('question'){ - $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; - # 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='.$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'} .= qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); - $comma = ','; - } - $param->{'data'} .= qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}); - } else { - Log3($name, 5, "question: $value $size $question[0]"); - return; - } - } - when ('questionEnd'){ - $value = ReadingsVal($name, "questionId", "") if (!$value); - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/rooms/'.AttrVal($name, 'matrixMessage', '!!').'/send/m.poll.end?access_token='.$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", "").' erhalten von '.ReadingsVal($name, "sender", "").'"}'; - } - when ('sync'){ - my $since = 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); - } else { - $full_state = ""; - } - $param->{'url'} = $hash->{server}.'/_matrix/client/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"}++; - Log3($name, 5, qq($name $hash->{helper}->{"access_token"} syncBeg $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) ); - } - when ('filter'){ - if ($value){ # get - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "userId",0).'/filter/'.$value.'?access_token='.$hash->{helper}->{"access_token"}; - $param->{'method'} = 'GET'; - } else { - $param->{'url'} = $hash->{server}.'/_matrix/client/v3/user/'.ReadingsVal($name, "userId",0).'/filter?access_token='.$hash->{helper}->{"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); - - Log3($name, 3, 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. - - return; -} - -sub ParseHttpResponse -{ - - #(CoolTux) hier solltest Du überlegen das Du die einzelnen Anweisung nach der Bedingung in einzelne Funktionen auslagerst - # Subroutine "PerformHttpRequest" with high complexity score - #(Man-Fred) da ich noch nicht wusste wie ähnlich die Ergebnisse sind habe ich erst mal alles zusammen ausgewertet - - my $param = shift; - my $err = shift; - my $data = shift; - - - my $hash = $param->{hash}; - my $def = $param->{def}; - my $value = $param->{value}; - my $name = $hash->{NAME}; - my $now = gettimeofday(); - my $nextRequest = ""; - - Log3($name, 3, qq($name $param->{'msgnumber'} $def Result $param->{code}) ); - 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, 2, "error while requesting ".$param->{url}." - $err"); # Eintrag fürs Log - readingsBulkUpdate($hash, "responseError", $err); # Reading erzeugen - $hash->{helper}->{"softfail"} = 3; - $hash->{helper}->{"hardfail"}++; - } - 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){ - $hash->{helper}->{"softfail"} = 0; - $hash->{helper}->{"hardfail"} = 0; - } else { - $hash->{helper}->{"softfail"}++; - $hash->{helper}->{"hardfail"}++ if ($hash->{helper}->{"softfail"} > 3); - readingsBulkUpdate($hash, "responseError", qq(S $hash->{helper}->{'softfail'}: $data) ); - Log3($name, 5, qq($name $hash->{helper}->{"access_token"} ${def}End $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) ); - } - # 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"){ - $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"){ - $nextRequest = 'login'; - } - } - - if ($def eq "register"){ - $hash->{helper}->{"session"} = $decoded->{'session'}; - $nextRequest = "";#"reg2"; - } - $hash->{helper}->{"session"} = $decoded->{'session'} if ($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 ($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'}); - - $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) ); - } - if ($def eq "wellknown"){ - # https://spec.matrix.org/unstable/client-server-api/ - } - if ($param->{code} == 200 && $def eq "sync"){ - Log3($name, 5, qq($name $hash->{helper}->{"access_token"} syncEnd $param->{'msgnumber'}: $hash->{helper}->{"next_refresh"} > $now) ); - 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'}; - my $message = $tl->{'content'}->{'body'}; - if (AttrVal($name, 'matrixSender', '') =~ $sender){ - readingsBulkUpdate($hash, "message", $message); - readingsBulkUpdate($hash, "sender", $sender); - # command - Get_Message($name, '99', $message); - } - #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'}; - my $message = $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'){ - readingsBulkUpdate($hash, "questionId", $tl->{'content'}->{'m.relates_to'}->{'event_id'}) - } - } - if (AttrVal($name, 'matrixSender', '') =~ $sender){ - readingsBulkUpdate($hash, "message", $message); - readingsBulkUpdate($hash, "sender", $sender); - $nextRequest = "questionEnd" ; - # command - Get_Message($name, $hash->{helper}->{"question"}, $message); - } - } - } - #push(@roomlist,"$id: "; - } - } - } - if ($def eq "logintypes"){ - my $types = ''; - foreach my $flow ( $decoded->{'flows'}->@* ) { - if ($flow->{'type'} =~ /m\.login\.(.*)/) { - #$types .= "$flow->{'type'} "; - $types .= "$1 ";# if ($flow->{'type'} ); - } - } - readingsBulkUpdate($hash, "logintypes", $types); - } - 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 ($decoded->{'event_id'}); - #m.relates_to - } - 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 ($decoded->{'event_id'}); - readingsBulkUpdate($hash, "questionId", "") if ($decoded->{'event_id'}); - #m.relates_to - } - } - - readingsEndUpdate($hash, 1); - $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); # 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){ - 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){ - # nichts tun, doppelter sync verhindert - } else { - my $pauseLogin; - if ($hash->{helper}->{"hardfail"} >= 3){ - $pauseLogin = 300; # lange Pause wenn zu viele Fehler - } 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->{myTimer}) if($hash->{myTimer}); - $hash->{myTimer} = { hash=>$hash }; - InternalTimer($timeToStart, \&FHEM::Devices::Matrix::Login, $hash->{myTimer}); - } - } - - return; -} - - -1; #(CoolTux) ein Modul endet immer mit 1; - -__END__ #(CoolTux) Markiert im File das Ende des Programms. Danach darf beliebiger Text stehen. Dieser wird vom Perlinterpreter nicht berücksichtigt. \ No newline at end of file -- 2.45.2 From ed34e21d4e90accbc8da2561bdc7a342722b81c2 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Mon, 26 Dec 2022 05:51:54 +0100 Subject: [PATCH 2/6] 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 ); } } -- 2.45.2 From 005ca1b8527c2277150f0f48b0aa752a5709bc62 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Mon, 26 Dec 2022 15:49:10 +0100 Subject: [PATCH 3/6] remove high complexity score by create functions first run of outsourced most of condition code in new functions --- lib/FHEM/Devices/Matrix/Client.pm | 295 ++++++++++++++++++++++-------- 1 file changed, 216 insertions(+), 79 deletions(-) diff --git a/lib/FHEM/Devices/Matrix/Client.pm b/lib/FHEM/Devices/Matrix/Client.pm index 2a2d90d..f52dfe4 100644 --- a/lib/FHEM/Devices/Matrix/Client.pm +++ b/lib/FHEM/Devices/Matrix/Client.pm @@ -519,6 +519,137 @@ sub _Get_Message { # wir machen daraus eine privat function (CoolTux) return; } +sub _createParamRefForDataLogin { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + return +qq({"type":"m.login.token", "token":"$createParamRefObj->{passwd}", "user": "$createParamRefObj->{hash}->{USER}", "txn_id": "z4567gerww", "session":"1234"}) + if ( + AttrVal( $createParamRefObj->{hash}->{NAME}, 'matrixLogin', '' ) eq + 'token' ); + + return +'{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"' + . $createParamRefObj->{hash}->{USER} + . '" }, "password":"' + . ( defined( $createParamRefObj->{passwd} ) + && $createParamRefObj->{passwd} ? $createParamRefObj->{passwd} : '' ) + . '"' + . ( + defined( $createParamRefObj->{deviceId} ) + && $createParamRefObj->{deviceId} + ? $createParamRefObj->{deviceId} + : '' + ) . '}'; +} + +sub _createParamRefForDataRegister { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + return + '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"' + . ( + defined( $createParamRefObj->{hash}->{USER} ) + && $createParamRefObj->{hash}->{USER} + ? $createParamRefObj->{hash}->{USER} + : '' + ) + . '" }, "password":"' + . ( + defined( $createParamRefObj->{passwd} ) + && $createParamRefObj->{passwd} ? $createParamRefObj->{passwd} + : '' + ) . '"}'; +} + +sub _createParamRefForDataReg1 { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + return + '{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"' + . ( + defined( $createParamRefObj->{hash}->{USER} ) + && $createParamRefObj->{hash}->{USER} + ? $createParamRefObj->{hash}->{USER} + : '' + ) + . '" }, "password":"' + . ( + defined( $createParamRefObj->{passwd} ) + && $createParamRefObj->{passwd} ? $createParamRefObj->{passwd} + : '' + ) . '"}'; +} + +sub _createParamRefForDataReg2 { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + return '{"username":"' + . ( + defined( $createParamRefObj->{hash}->{USER} ) + && $createParamRefObj->{hash}->{USER} + ? $createParamRefObj->{hash}->{USER} + : '' + ) + . '", "password":"' + . ( + defined( $createParamRefObj->{passwd} ) + && $createParamRefObj->{passwd} ? $createParamRefObj->{passwd} + : '' + ) + . '", "auth": {"session":"' + . ( + defined( $createParamRefObj->{hash}->{helper}->{session} ) + && $createParamRefObj->{hash}->{helper}->{session} + ? $createParamRefObj->{hash}->{helper}->{session} + : '' + ) . '","type":"m.login.dummy"}}'; +} + +sub _createParamRefForDataRefresh { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + return '{"refresh_token": "' + . ( + defined( $createParamRefObj->{hash}->{helper}->{refresh_token} ) + && $createParamRefObj->{hash}->{helper}->{refresh_token} + ? $createParamRefObj->{hash}->{helper}->{refresh_token} + : '' + ) . '"}'; +} + +sub _createParamRefForUrlMsg { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + return + 'r0/rooms/' + . AttrVal( $createParamRefObj->{hash}->{NAME}, 'matrixMessage', '!!' ) + . '/send/m.room.message?access_token=' + . ( + $createParamRefObj->{hash}->{helper}->{access_token} + ? $createParamRefObj->{hash}->{helper}->{access_token} + : '' + ); +} + sub _createParamRefForDef { return 0 unless ( __PACKAGE__ eq caller(0) ) @@ -529,16 +660,64 @@ sub _createParamRefForDef { my $paramref = { 'logintypes' => { - 'url' => $createParamRefObjhash->{hash}->{URL} . 'r0/login', - 'method' => 'GET', + 'urlPath' => 'r0/login', + 'method' => 'GET', }, + 'register' => { - 'url' => $createParamRefObjhash->{hash}->{URL}, - 'data' => '', + 'urlPath' => 'v3/register', + 'data' => _createParamRefForDataRegister($createParamRefObj), }, + + 'reg1' => { + 'urlPath' => 'v3/register', + 'data' => _createParamRefForDataReg1($createParamRefObj), + }, + + 'reg2' => { + 'urlPath' => 'v3/register', + 'data' => _createParamRefForDataReg2($createParamRefObj), + }, + + 'login' => { + 'urlPath' => 'v3/login', + 'data' => _createParamRefForDataLogin($createParamRefObj), + }, + + 'refresh' => { + 'urlPath' => 'v1/refresh', + 'data' => _createParamRefForDataRefresh($createParamRefObj), + }, + + 'wellknown' => { + 'urlPath' => '/.well-known/matrix/client', + 'data' => undef, + }, + + 'msg' => { + 'urlPath' => _createParamRefForUrlMsg($createParamRefObj), + 'data' => '{"msgtype":"m.text", "body":"' + . $createParamRefObj->{value} . '"}', + }, + }; - return $paramref->{$def}->{$paramValue}; + ::Log( 1, + '!!!DEBUG - MsgcreateParamRef: ' . $paramref->{$def}->{$paramValue} ); + + ::Log( 1, + '!!!DEBUG - Def: ' + . $def + . ' ParamValue: ' + . $paramValue + . ' Resp: ' + . $paramref->{$def}->{$paramValue} ); + return ( + defined( $paramref->{$def}->{$paramValue} ) + && $paramref->{$def}->{$paramValue} + ? $paramref->{$def}->{$paramValue} + : '' + ); } sub _PerformHttpRequest { # wir machen daraus eine privat function (CoolTux) @@ -600,13 +779,9 @@ qq($name $hash->{helper}->{access_token} sync2refresh - $hash->{helper}->{next_r def => $def, value => $value, now => $now, - name => $name, passwd => $passwd, msgnumber => $msgnumber, - deviceId => $device_id, - url => undef, - data => undef, - method => undef, + deviceId => $deviceId, }; my $param = { @@ -625,58 +800,39 @@ qq($name $hash->{helper}->{access_token} sync2refresh - $hash->{helper}->{next_r given ($def) { when ('logintypes') { - $param->{url} = $hash->{URL} . 'r0/login'; - $param->{method} = 'GET'; + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); + $param->{method} = + _createParamRefForDef( $def, 'method', $createParamRefObj ); } - when ('register') { - $param->{url} = $hash->{URL} . 'v3/register'; + when (/^register|reg[1-2]|login|refresh|wellknown$/x) { + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); $param->{data} = -'{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"' - . $hash->{USER} - . '" }, "password":"' - . $passwd . '"}'; + _createParamRefForDef( $def, 'data', $createParamRefObj ); + + Log3( $name, 5, +qq($name $hash->{helper}->{access_token} refreshBeg $param->{'msgnumber'}: $hash->{helper}->{next_refresh} > $now) + ) if ( $def eq 'refresh' ); } - when ('reg1') { - $param->{url} = $hash->{URL} . 'v3/register'; + when ('msg') { + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); $param->{data} = -'{"type":"m.login.password", "identifier":{ "type":"m.id.user", "user":"' - . $hash->{USER} - . '" }, "password":"' - . $passwd . '"}'; + _createParamRefForDef( $def, 'data', $createParamRefObj ); } - when ('reg2') { - $param->{url} = $hash->{URL} . 'v3/register'; - $param->{data} = - '{"username":"' - . $hash->{USER} - . '", "password":"' - . $passwd - . '", "auth": {"session":"' - . $hash->{helper}->{session} - . '","type":"m.login.dummy"}}'; - } - - when ('login') { - if ( AttrVal( $name, 'matrixLogin', '' ) eq 'token' ) { - $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} . 'v3/login'; - $param->{data} = -'{"type":"m.login.password", "refresh_token": true, "identifier":{ "type":"m.id.user", "user":"' - . $hash->{USER} - . '" }, "password":"' - . $passwd . '"' - . $deviceId . '}'; - } - } + # when ('msg') { + # $param->{'url'} = + # $hash->{URL} + # . '/_matrix/client/r0/rooms/' + # . AttrVal( $name, 'matrixMessage', '!!' ) + # . '/send/m.room.message?access_token=' + # . $hash->{helper}->{access_token}; + # $param->{data} = '{"msgtype":"m.text", "body":"' . $value . '"}'; + # } when ('login2') { $param->{url} = $hash->{URL} . 'v3/login'; @@ -688,31 +844,6 @@ qq({"type":"m.login.token", "token":"$passwd", "user": "\@$hash->{USER}:matrix.o } } - when ('refresh') { - $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) - ); - } - - when ('wellknown') { - $param->{url} = $hash->{URL} . '/.well-known/matrix/client'; - } - - when ('msg') { - $param->{url} = - $hash->{URL} - . 'r0/rooms/' - . AttrVal( $name, 'matrixMessage', '!!' ) - . '/send/m.room.message?access_token=' - . $hash->{helper}->{access_token}; - - $param->{data} = '{"msgtype":"m.text", "body":"' . $value . '"}'; - } - when ('questionX') { $hash->{helper}->{question} = $value; $value = AttrVal( $name, 'matrixQuestion_' . $value, '' ) @@ -951,6 +1082,7 @@ sub ParseHttpResponse { 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; @@ -1194,6 +1326,11 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{msgnumber}: $hash->{ #m.relates_to } + + ::Log( 1, + '!!!DEBUG - TOKEN aus Hash: ' . $hash->{helper}->{access_token} ); + ::Log( 1, '!!!DEBUG - TOKEN aus Decode: ' . $decoded->{access_token} ) + if ( $decoded->{access_token} ); } readingsEndUpdate( $hash, 1 ); -- 2.45.2 From fa1cf5d5bd1f6a4b6ed6fd6c1fd7c1db70fc20b1 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Mon, 26 Dec 2022 19:40:51 +0100 Subject: [PATCH 4/6] fix specialcharacrter in messages --- lib/FHEM/Devices/Matrix/Client.pm | 65 +++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/lib/FHEM/Devices/Matrix/Client.pm b/lib/FHEM/Devices/Matrix/Client.pm index f52dfe4..6ae13ea 100644 --- a/lib/FHEM/Devices/Matrix/Client.pm +++ b/lib/FHEM/Devices/Matrix/Client.pm @@ -26,6 +26,7 @@ use Carp qw( carp ) ; # wir verwenden Carp für eine bessere Fehlerrückgabe (CoolTux) use Data::Dumper; # Debugging +use Encode; # try to use JSON::MaybeXS wrapper # for chance of better performance + open code @@ -100,6 +101,7 @@ BEGIN { ReadingsVal HttpUtils_NonblockingGet InternalTimer + RemoveInternalTimer gettimeofday AnalyzeCommandChain ) @@ -439,7 +441,9 @@ sub Set { sub Attr { my ( $cmd, $name, $attr_name, $attr_value ) = @_; - Log3( $name, 4, "Attr - $cmd - $name - $attr_name - $attr_value" ); + Log3( $name, 4, + "Attr - $cmd - $name - $attr_name - " + . ( defined($attr_value) && $attr_value ? $attr_value : '' ) ); if ( $cmd eq 'set' ) { if ( $attr_name eq 'matrixQuestion_' ) { @@ -478,6 +482,12 @@ sub Attr { return; } +sub Login { + my $hash = shift; + + return _PerformHttpRequest( $hash, 'login', '' ); +} + sub _Get_Message { # wir machen daraus eine privat function (CoolTux) return 0 unless ( __PACKAGE__ eq caller(0) ) @@ -720,6 +730,30 @@ sub _createParamRefForDef { ); } +sub _createPerformHttpRequestHousekeepingParamObj { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + my $param = { + timeout => 10, + hash => $createParamRefObj->{hash} + , # Muss gesetzt werden, damit die Callback funktion wieder $hash hat + def => $createParamRefObj->{def}, # sichern für eventuelle Wiederholung + value => $createParamRefObj->{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 + msgnumber => $createParamRefObj->{msgnumber}, # lfd. Nummer Request + callback => \&ParseHttpResponse + , # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten + }; + + return $param; +} + sub _PerformHttpRequest { # wir machen daraus eine privat function (CoolTux) return 0 unless ( __PACKAGE__ eq caller(0) ) @@ -738,7 +772,8 @@ sub _PerformHttpRequest { # wir machen daraus eine privat function (CoolTux) Log3( $name, 4, "$name : Matrix::_PerformHttpRequest $hash" ); - $passwd = $hash->{helper}->{passwdobj}->getReadPassword($name) + $passwd = + encode_utf8( $hash->{helper}->{passwdobj}->getReadPassword($name) ) if ( $def eq 'login' || $def eq 'reg2' ); $hash->{helper}->{msgnumber} = @@ -784,19 +819,8 @@ qq($name $hash->{helper}->{access_token} sync2refresh - $hash->{helper}->{next_r deviceId => $deviceId, }; - 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 - msgnumber => $msgnumber, # lfd. Nummer Request - callback => \&ParseHttpResponse - , # Diese Funktion soll das Ergebnis dieser HTTP Anfrage bearbeiten - }; + my $param = + _createPerformHttpRequestHousekeepingParamObj($createParamRefObj); given ($def) { when ('logintypes') { @@ -1225,7 +1249,7 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{msgnumber}: $hash->{ && $tl->{content}->{msgtype} eq 'm.text' ) { my $sender = $tl->{sender}; - my $message = $tl->{content}->{body}; + my $message = encode_utf8( $tl->{content}->{body} ); if ( AttrVal( $name, 'matrixSender', '' ) =~ $sender ) @@ -1248,9 +1272,9 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{msgnumber}: $hash->{ { my $sender = $tl->{sender}; my $message = - $tl->{content} - ->{'org.matrix.msc3381.poll.response'} - ->{answers}[0]; + encode_utf8( $tl->{content} + ->{'org.matrix.msc3381.poll.response'} + ->{answers}[0] ); if ( $tl->{content}->{'m.relates_to'} ) { if ( @@ -1374,7 +1398,8 @@ qq($name $hash->{helper}->{"access_token"} syncEnd $param->{msgnumber}: $hash->{ } RemoveInternalTimer($hash); - InternalTimer( gettimeofday() + $pauseLogin, \&Login, $hash ); + InternalTimer( gettimeofday() + $pauseLogin, + \&FHEM::Devices::Matrix::Client::Login, $hash ); } } -- 2.45.2 From 87ae5607ae60b338f18e0950075a9caef3fc80ed Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Thu, 29 Dec 2022 10:51:19 +0100 Subject: [PATCH 5/6] Consider refactoring because high complexity score --- lib/FHEM/Devices/Matrix/Client.pm | 363 ++++++++++++++++++------------ 1 file changed, 225 insertions(+), 138 deletions(-) diff --git a/lib/FHEM/Devices/Matrix/Client.pm b/lib/FHEM/Devices/Matrix/Client.pm index 6ae13ea..32c0764 100644 --- a/lib/FHEM/Devices/Matrix/Client.pm +++ b/lib/FHEM/Devices/Matrix/Client.pm @@ -352,9 +352,11 @@ sub Set { when ('msg') { return _PerformHttpRequest( $hash, $cmd, $value ); } + when ('pollFullstate') { readingsSingleUpdate( $hash, $cmd, $value, 1 ); # Readings erzeugen } + when ('setPassword') { return qq(usage: $cmd pass=) if ( scalar( @{$aArg} ) != 0 @@ -372,6 +374,7 @@ sub Set { if ( defined($passResp) && !defined($passErr) ); } + when ('removePassword') { return 'usage: ' . $cmd if ( scalar( @{$aArg} ) != 0 ); @@ -389,22 +392,28 @@ sub Set { && !defined($passErr) ); return; } + when ('filter') { return _PerformHttpRequest( $hash, $cmd, '' ); } + when ('question') { return _PerformHttpRequest( $hash, $cmd, $value ); } + when ('questionEnd') { return _PerformHttpRequest( $hash, $cmd, $value ); } + when ('register') { return _PerformHttpRequest( $hash, $cmd, '' ) ; # 2 steps (ToDo: 3 steps empty -> dummy -> registration_token o.a.) } + when ('login') { return _PerformHttpRequest( $hash, $cmd, '' ); } + when ('refresh') { return _PerformHttpRequest( $hash, $cmd, '' ); } @@ -660,6 +669,168 @@ sub _createParamRefForUrlMsg { ); } +sub _createParamRefForQuestionEnd { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + my $paramValue = shift; + + my $value; + $value = ( + exists( $createParamRefObj->{value} ) + && $createParamRefObj->{value} + ? $createParamRefObj->{value} + : ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'questionId', '' ) + ); + + return + 'v3/rooms/' + . AttrVal( $createParamRefObj->{hash}->{NAME}, 'matrixMessage', '!!' ) + . '/send/m.poll.end?access_token=' + . ( + defined( + $createParamRefObj->{hash}->{helper}->{access_token} + && $createParamRefObj->{hash}->{helper}->{access_token} + ? $createParamRefObj->{hash}->{helper}->{access_token} + : '' + ) + ) if ( $paramValue eq 'urlPath' ); + + return + '{"m.relates_to": {"rel_type": "m.reference","eventId": "' + . $value + . '"},"org.matrix.msc3381.poll.end": {},' + . '"org.matrix.msc1767.text": "Antort ' + . ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'answer', '' ) + . ' erhalten von ' + . ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'sender', '' ) . '"}' + if ( $paramValue eq 'data' ); + + return; +} + +sub _createParamRefForFilter { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + my $paramValue = shift; + + return + 'v3/user/' + . ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'userId', 0 ) + . '/filter' + . ( + exists( $createParamRefObj->{value} ) + && $createParamRefObj->{value} ? '/' . $createParamRefObj->{value} + : '' + ) + . '?access_token=' + . ( + defined( $createParamRefObj->{hash}->{helper}->{access_token} ) + && $createParamRefObj->{hash}->{helper}->{access_token} + ? $createParamRefObj->{hash}->{helper}->{access_token} + : '' + ) if ( $paramValue eq 'urlPath' ); + + return +'{"event_fields": ["type","content","sender"],"event_format": "client", "presence": { "senders": [ "@xx:example.com"]}}' + if ( $paramValue eq 'data' + && exists( $createParamRefObj->{value} ) + && $createParamRefObj->{value} ); + + return; +} + +sub _createParamRefForQuestion { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + my $paramValue = shift; + + my $value = AttrVal( $createParamRefObj->{hash}->{NAME}, + 'matrixQuestion_' . $createParamRefObj->{value}, '' ) + ; # if ($value =~ /[0-9]/); + + my @question = split( ':', $value ); + my $size = @question; + my $q = shift @question; + + $value =~ s/:/
/gx; + + # min. question and one answer + if ( int(@question) >= 2 ) { + return + 'v3/rooms/' + . AttrVal( $createParamRefObj->{hash}->{NAME}, 'matrixMessage', '!!' ) + . '/send/m.poll.start?access_token=' + . $createParamRefObj->{hash}->{helper}->{access_token} + if ( $paramValue eq 'urlPath' ); + + my $data = + '{"org.matrix.msc3381.poll.start": {"max_selections": 1,' + . '"question": {"org.matrix.msc1767.text": "' + . $q . '"},' + . '"kind": "org.matrix.msc3381.poll.undisclosed","answers": ['; + + my $comma = ''; + + for my $answer (@question) { + $data .= +qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); + $comma = ','; + } + + $data .= +qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}); + + return $data + if ( $paramValue eq 'data' ); + } + else { + Log3( $createParamRefObj->{hash}->{NAME}, + 5, 'question: ' . $value . $size . $question[0] ); + return; + } + + return; +} + +sub _createParamRefForUrlSync { + return 0 + unless ( __PACKAGE__ eq caller(0) ) + ; # nur das eigene Package darf private Funktionen aufrufen (CoolTux) + my $createParamRefObj = shift; + + my $since = + ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'since', undef ) + ? '&since=' + . ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'since', undef ) + : ''; + + my $full_state = + ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'pollFullstate', undef ); + + if ($full_state) { + $full_state = '&full_state=true'; + readingsSingleUpdate( $createParamRefObj->{hash}, + 'pollFullstate', 0, 1 ); + } + else { + $full_state = ''; + } + + return + 'r0/sync?access_token=' + . $createParamRefObj->{hash}->{helper}->{access_token} + . $since + . $full_state + . '&timeout=50000&filter=' + . ReadingsVal( $createParamRefObj->{hash}->{NAME}, 'filterId', 0 ); +} + sub _createParamRefForDef { return 0 unless ( __PACKAGE__ eq caller(0) ) @@ -710,6 +881,28 @@ sub _createParamRefForDef { . $createParamRefObj->{value} . '"}', }, + # 'questionEnd' => { + # 'urlPath' => + # _createParamRefForQuestionEnd( $createParamRefObj, 'urlPath' ), + # 'data' => + # _createParamRefForQuestionEnd( $createParamRefObj, 'data' ), + # }, + + # 'filter' => { + # 'urlPath' => + # _createParamRefForFilter( $createParamRefObj, 'urlPath' ), + # 'data' => _createParamRefForFilter( $createParamRefObj, 'data' ), + # }, + + # 'question' => { + # 'urlPath' => + # _createParamRefForQuestion( $createParamRefObj, 'urlPath' ), + # 'data' => _createParamRefForQuestion( $createParamRefObj, 'data' ), + # }, + + # 'sync' => { + # 'urlPath' => _createParamRefForUrlSync($createParamRefObj), + # }, }; ::Log( 1, @@ -723,7 +916,7 @@ sub _createParamRefForDef { . ' Resp: ' . $paramref->{$def}->{$paramValue} ); return ( - defined( $paramref->{$def}->{$paramValue} ) + exists( $paramref->{$def}->{$paramValue} ) && $paramref->{$def}->{$paramValue} ? $paramref->{$def}->{$paramValue} : '' @@ -830,34 +1023,6 @@ qq($name $hash->{helper}->{access_token} sync2refresh - $hash->{helper}->{next_r _createParamRefForDef( $def, 'method', $createParamRefObj ); } - when (/^register|reg[1-2]|login|refresh|wellknown$/x) { - $param->{url} = $hash->{URL} - . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); - $param->{data} = - _createParamRefForDef( $def, 'data', $createParamRefObj ); - - Log3( $name, 5, -qq($name $hash->{helper}->{access_token} refreshBeg $param->{'msgnumber'}: $hash->{helper}->{next_refresh} > $now) - ) if ( $def eq 'refresh' ); - } - - when ('msg') { - $param->{url} = $hash->{URL} - . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); - $param->{data} = - _createParamRefForDef( $def, 'data', $createParamRefObj ); - } - - # when ('msg') { - # $param->{'url'} = - # $hash->{URL} - # . '/_matrix/client/r0/rooms/' - # . AttrVal( $name, 'matrixMessage', '!!' ) - # . '/send/m.room.message?access_token=' - # . $hash->{helper}->{access_token}; - # $param->{data} = '{"msgtype":"m.text", "body":"' . $value . '"}'; - # } - when ('login2') { $param->{url} = $hash->{URL} . 'v3/login'; if ( AttrVal( $name, 'matrixLogin', '' ) eq 'token' ) { @@ -913,92 +1078,16 @@ qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain when ('question') { $hash->{helper}->{question} = $value; - $value = AttrVal( $name, "matrixQuestion_$value", "" ) - ; # if ($value =~ /[0-9]/); - - my @question = split( ':', $value ); - my $size = @question; - my $q = shift @question; - - $value =~ s/:/
/gx; - - # min. question and one answer - if ( int(@question) >= 2 ) { - $param->{url} = - $hash->{URL} - . 'v3/rooms/' - . AttrVal( $name, 'matrixMessage', '!!' ) - . '/send/m.poll.start?access_token=' - . $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 = ''; - - for my $answer (@question) { - $param->{data} .= -qq($comma {"id": "$answer", "org.matrix.msc1767.text": "$answer"}); - $comma = ','; - } - - $param->{data} .= -qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain", "body": "$value"}]}); - } - else { - Log3( $name, 5, "question: $value $size $question[0]" ); - return; - } - } - - when ('questionEnd') { - $value = ReadingsVal( $name, 'questionId', '' ) - if ( !$value ); - - $param->{url} = - $hash->{URL} - . 'v3/rooms/' - . AttrVal( $name, 'matrixMessage', '!!' ) - . '/send/m.poll.end?access_token=' - . $hash->{helper}->{access_token}; + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); $param->{data} = - '{"m.relates_to": {"rel_type": "m.reference","eventId": "' - . $value - . '"},"org.matrix.msc3381.poll.end": {},' - . '"org.matrix.msc1767.text": "Antort ' - . ReadingsVal( $name, 'answer', '' ) - . ' erhalten von ' - . ReadingsVal( $name, 'sender', '' ) . '"}'; + _createParamRefForDef( $def, 'data', $createParamRefObj ); } when ('sync') { - my $since = - 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 ); - } - else { - $full_state = ''; - } - - $param->{url} = - $hash->{URL} - . 'r0/sync?access_token=' - . $hash->{helper}->{access_token} - . $since - . $full_state - . '&timeout=50000&filter=' - . ReadingsVal( $name, 'filterId', 0 ); + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); $param->{method} = 'GET'; $param->{timeout} = 60; @@ -1010,37 +1099,35 @@ qq($name $hash->{helper}->{access_token} syncBeg $param->{'msgnumber'}: $hash->{ } when ('filter') { - if ($value) { # get - $param->{url} = - $hash->{URL} - . 'v3/user/' - . ReadingsVal( $name, "userId", 0 ) - . '/filter/' - . $value - . '?access_token=' - . $hash->{helper}->{access_token}; + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); + $param->{data} = + _createParamRefForDef( $def, 'data', $createParamRefObj ); - $param->{method} = 'GET'; - } - else { - $param->{url} = - $hash->{URL} - . 'v3/user/' - . ReadingsVal( $name, "userId", 0 ) - . '/filter?access_token=' - . $hash->{helper}->{access_token}; + $param->{method} = 'GET' + if ($value); + } - $param->{data} = '{'; - $param->{data} .= - '"event_fields": ["type","content","sender"],'; + when ('refresh') { + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); + $param->{data} = + _createParamRefForDef( $def, 'data', $createParamRefObj ); + + Log3( $name, 5, +qq($name $hash->{helper}->{access_token} refreshBeg $param->{'msgnumber'}: $hash->{helper}->{next_refresh} > $now) + ); + } + + default { + $param->{url} = $hash->{URL} + . _createParamRefForDef( $def, 'urlPath', $createParamRefObj ); + $param->{data} = + _createParamRefForDef( $def, 'data', $createParamRefObj ); + + $param->{method} = 'GET' + if ( $def eq 'filter' && $value ); - $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} .= '}'; - } } } -- 2.45.2 From 396d69c3f6463bf2355863aa03660c3722af8284 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Mon, 2 Jan 2023 13:58:03 +0100 Subject: [PATCH 6/6] redesign all condition and switches of _PerformHttpRequest fn --- lib/FHEM/Devices/Matrix/Client.pm | 76 +++++++++++++++++-------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/lib/FHEM/Devices/Matrix/Client.pm b/lib/FHEM/Devices/Matrix/Client.pm index 32c0764..7cbc228 100644 --- a/lib/FHEM/Devices/Matrix/Client.pm +++ b/lib/FHEM/Devices/Matrix/Client.pm @@ -734,13 +734,13 @@ sub _createParamRefForFilter { : '' ) if ( $paramValue eq 'urlPath' ); - return -'{"event_fields": ["type","content","sender"],"event_format": "client", "presence": { "senders": [ "@xx:example.com"]}}' - if ( $paramValue eq 'data' - && exists( $createParamRefObj->{value} ) - && $createParamRefObj->{value} ); - - return; + return ( + $paramValue eq 'data' + && exists( $createParamRefObj->{value} ) + && $createParamRefObj->{value} + ? '{"event_fields": ["type","content","sender"],"event_format": "client", "presence": { "senders": [ "@xx:example.com"]}}' + : '' + ); } sub _createParamRefForQuestion { @@ -766,8 +766,11 @@ sub _createParamRefForQuestion { 'v3/rooms/' . AttrVal( $createParamRefObj->{hash}->{NAME}, 'matrixMessage', '!!' ) . '/send/m.poll.start?access_token=' - . $createParamRefObj->{hash}->{helper}->{access_token} - if ( $paramValue eq 'urlPath' ); + . ( + $createParamRefObj->{hash}->{helper}->{access_token} + ? $createParamRefObj->{hash}->{helper}->{access_token} + : '' + ) if ( $paramValue eq 'urlPath' ); my $data = '{"org.matrix.msc3381.poll.start": {"max_selections": 1,' @@ -790,8 +793,12 @@ qq(],"org.matrix.msc1767.text": "$value"}, "m.markup": [{"mimetype": "text/plain if ( $paramValue eq 'data' ); } else { - Log3( $createParamRefObj->{hash}->{NAME}, - 5, 'question: ' . $value . $size . $question[0] ); + Log3( $createParamRefObj->{hash}->{NAME}, 5, + 'question: ' + . $value + . $size + . ( defined( $question[0] ) && $question[0] ? $question[0] : '' ) + ); return; } @@ -822,9 +829,12 @@ sub _createParamRefForUrlSync { $full_state = ''; } - return - 'r0/sync?access_token=' - . $createParamRefObj->{hash}->{helper}->{access_token} + return 'r0/sync?access_token=' + . ( + $createParamRefObj->{hash}->{helper}->{access_token} + ? $createParamRefObj->{hash}->{helper}->{access_token} + : '' + ) . $since . $full_state . '&timeout=50000&filter=' @@ -881,28 +891,28 @@ sub _createParamRefForDef { . $createParamRefObj->{value} . '"}', }, - # 'questionEnd' => { - # 'urlPath' => - # _createParamRefForQuestionEnd( $createParamRefObj, 'urlPath' ), - # 'data' => - # _createParamRefForQuestionEnd( $createParamRefObj, 'data' ), - # }, + 'questionEnd' => { + 'urlPath' => + _createParamRefForQuestionEnd( $createParamRefObj, 'urlPath' ), + 'data' => + _createParamRefForQuestionEnd( $createParamRefObj, 'data' ), + }, - # 'filter' => { - # 'urlPath' => - # _createParamRefForFilter( $createParamRefObj, 'urlPath' ), - # 'data' => _createParamRefForFilter( $createParamRefObj, 'data' ), - # }, + 'filter' => { + 'urlPath' => + _createParamRefForFilter( $createParamRefObj, 'urlPath' ), + 'data' => _createParamRefForFilter( $createParamRefObj, 'data' ), + }, - # 'question' => { - # 'urlPath' => - # _createParamRefForQuestion( $createParamRefObj, 'urlPath' ), - # 'data' => _createParamRefForQuestion( $createParamRefObj, 'data' ), - # }, + 'question' => { + 'urlPath' => + _createParamRefForQuestion( $createParamRefObj, 'urlPath' ), + 'data' => _createParamRefForQuestion( $createParamRefObj, 'data' ), + }, - # 'sync' => { - # 'urlPath' => _createParamRefForUrlSync($createParamRefObj), - # }, + 'sync' => { + 'urlPath' => _createParamRefForUrlSync($createParamRefObj), + }, }; ::Log( 1, -- 2.45.2