From af9d97e6864522d5108edd36c25715f950128ebd Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Sun, 25 Dec 2022 17:56:52 +0100 Subject: [PATCH] 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