Marko Oldenburg 005ca1b852 remove high complexity score by create functions
first run of outsourced most of condition code in new functions
2022-12-26 15:49:10 +01:00

1387 lines
45 KiB
Perl

##########################################################################
# 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::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
AnalyzeCommandChain
)
);
}
my $VERSION = '0.0.15';
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 <name> Matrix <server> <user>'
if ( scalar( @{$aArg} ) != 4 );
my $name = $aArg->[0];
$hash->{SERVER} = $aArg->[2]; # Internals sollten groß geschrieben werden
$hash->{URL} = 'https://' . $hash->{SERVER} . '/_matrix/client/';
$hash->{USER} = $aArg->[3];
$hash->{VERSION} = $VERSION;
$hash->{helper}->{passwdobj} =
FHEM::Core::Authentication::Passwords->new( $hash->{TYPE} );
$hash->{NOTIFYDEV} = 'global,' . $name;
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;
RemoveInternalTimer($hash);
return;
}
sub Delete {
my $hash = shift;
my $name = shift;
$hash->{helper}->{passwdobj}->setDeletePassword($name);
return;
}
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)
my $hash = shift;
my $name = $hash->{NAME};
Log3( $name, 4,
"$name : Matrix::_Init $hash " . AttrVal( $name, 'matrixPoll', '-1' ) );
# Update necessary?
Log3( $name, 1,
$name . ': Start V' . $hash->{VERSION} . ' -> V' . $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;
return _PerformHttpRequest( $hash, 'login', '' );
}
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 _Init($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 ( $passResp, $passErr );
( $passResp, $passErr ) =
$hash->{helper}->{passwdobj}->setRename( $new, $old )
; #(CoolTux) Es empfiehlt sich ab zu fragen ob der Wechsel geklappt hat
Log3( $new, 1,
"$new : Matrix::Rename - error while change the password hash after rename - $passErr"
)
if (!defined($passResp)
&& defined($passErr) );
Log3( $new, 1,
"$new : Matrix::Rename - change password hash after rename successfully"
)
if ( defined($passResp)
&& !defined($passErr) );
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 = 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 ('wellknown') {
return _PerformHttpRequest( $hash, $cmd, '' );
}
when ('logintypes') {
return _PerformHttpRequest( $hash, $cmd, '' );
}
when ('sync') {
$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 );
}
default {
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;
}
}
}
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=<password>)
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 _Init($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 login:noArg '
if ( exists( $hash->{helper}->{passwdobj} )
&& defined(
$hash->{helper}->{passwdobj}->getReadPassword($name) ) );
$list .=
'filter:noArg question questionEnd pollFullstate:0,1 msg register 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]/x );
$_[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]/x );
$_[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) {
Log3( $name, 3, "$name - $question - $answers[$pos]" );
$answer = $answers[$pos] if ( $message eq $question );
if ($answer) {
Log3( $name, 3, "$name - $pos - $answer" );
AnalyzeCommandChain( undef, $answer );
last;
}
$pos++;
}
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) )
; # nur das eigene Package darf private Funktionen aufrufen (CoolTux)
my $def = shift;
my $paramValue = shift;
my $createParamRefObj = shift;
my $paramref = {
'logintypes' => {
'urlPath' => 'r0/login',
'method' => 'GET',
},
'register' => {
'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} . '"}',
},
};
::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)
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" );
$passwd = $hash->{helper}->{passwdobj}->getReadPassword($name)
if ( $def eq 'login' || $def eq 'reg2' );
$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 $createParamRefObj = {
hash => $hash,
def => $def,
value => $value,
now => $now,
passwd => $passwd,
msgnumber => $msgnumber,
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
};
given ($def) {
when ('logintypes') {
$param->{url} = $hash->{URL}
. _createParamRefForDef( $def, 'urlPath', $createParamRefObj );
$param->{method} =
_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' ) {
$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 ('questionX') {
$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/:/<br>/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} =
'{"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 = '';
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 ('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/:/<br>/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->{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}
. '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}
. 'v3/user/'
. ReadingsVal( $name, "userId", 0 )
. '/filter/'
. $value
. '?access_token='
. $hash->{helper}->{access_token};
$param->{method} = 'GET';
}
else {
$param->{url} =
$hash->{URL}
. '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, 'lastRespErr', $err ); # Reading erzeugen
readingsBulkUpdate( $hash, 'state',
'Error - look lastRespErr 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, '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)
);
}
# 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;
for 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 );
#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 );
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};
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\.(.*)/x ) {
#$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
}
::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 );
$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
}
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + $pauseLogin, \&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.