changes completed

This commit is contained in:
Manfred 2022-11-05 12:30:17 +01:00
parent 30a2c13ce0
commit db19349628
2 changed files with 565 additions and 44 deletions

View File

@ -23,64 +23,68 @@
# Usage: # Usage:
# #
########################################################################## ##########################################################################
# $Id: 98_Matrix.pm 28158 2022-11-02 19:56:00Z Man-fred $ # $Id: 98_Matrix.pm 11656 2022-11-05 12:26:00Z Man-fred $
package FHEM::Matrix; package FHEM::Devices::Matrix;
use strict; use strict;
use warnings; use warnings;
use HttpUtils; use HttpUtils;
use FHEM::Meta; use FHEM::Meta;
use GPUtils qw(GP_Export); use GPUtils qw(GP_Export GP_Import);
require FHEM::Device::Matrix; use JSON;
use vars qw(%data);
use FHEM::Core::Authentication::Passwords qw(:ALL);
#use FHEM::Core::Authentication::Passwords qw(&setStorePassword);
require FHEM::Devices::Matrix::Matrix;
#-- Run before package compilation #-- Run before package compilation
BEGIN { BEGIN {
#-- Export to main context with different name #-- Export to main context with different name
GP_Export( GP_Export(qw(
qw(
Initialize Initialize
) ));
); GP_Import(qw(
readingFnAttributes
));
} }
sub Matrix_Initialize { sub Initialize {
my ($hash) = @_; my ($hash) = @_;
$hash->{DefFn} = \&FHEM::Device::Matrix::Define; $hash->{DefFn} = \&FHEM::Devices::Matrix::Define;
$hash->{UndefFn} = \&FHEM::Device::Matrix::Undef; $hash->{UndefFn} = \&FHEM::Devices::Matrix::Undef;
$hash->{SetFn} = \&FHEM::Device::Matrix::Set; $hash->{SetFn} = \&FHEM::Devices::Matrix::Set;
$hash->{GetFn} = \&FHEM::Device::Matrix::Get; $hash->{GetFn} = \&FHEM::Devices::Matrix::Get;
$hash->{AttrFn} = \&FHEM::Device::Matrix::Attr; $hash->{AttrFn} = \&FHEM::Devices::Matrix::Attr;
$hash->{ReadFn} = \&FHEM::Device::Matrix::Read; $hash->{ReadFn} = \&FHEM::Devices::Matrix::Read;
$hash->{RenameFn} = \&FHEM::Device::Matrix::Rename; $hash->{RenameFn} = \&FHEM::Devices::Matrix::Rename;
$hash->{NotifyFn} = \&FHEM::Device::Matrix::Notify; $hash->{NotifyFn} = \&FHEM::Devices::Matrix::Notify;
$hash->{AttrList} = "MatrixRoom MatrixSender MatrixQuestion_0 MatrixQuestion_1 MatrixMessage " . $::readingFnAttributes;
$hash->{AttrList} = "MatrixRoom MatrixSender MatrixMessage MatrixQuestion_0 MatrixQuestion_1 " . $readingFnAttributes;
$hash->{parseParams} = 1; $hash->{parseParams} = 1;
return FHEM::Meta::InitMod( __FILE__, $hash ); return FHEM::Meta::InitMod( __FILE__, $hash );
} }
1; 1;
=pod =pod
=item summary Provides a Matrix-Chatbot. =item summary Provides a Matrix-Chatbot.
=item summary_DE Stellt einen Matrix-Chatbot bereit. =item summary_DE Stellt einen Matrix-Chatbot bereit.
=begin html =begin html
<a name="Matrix"></a> <a id="Matrix"></a>
<h3>Matrix</h3> <h3>Matrix</h3>
<ul> <ul>
<i>Matrix</i> implements a client to Matrix-Synapse-Servers. It is in a very early development state. <i>Matrix</i> implements a client to Matrix-Synapse-Servers. It is in a very early development state.
<br><br> <br><br>
<a name="Matrixdefine"></a> <a id="Matrix-define"></a>
<b>Define</b> <h4>Define</h4>
<ul> <ul>
<code>define &lt;name&gt; <server> <user> <password></code> <code>define &lt;name&gt; <server> <user></code>
<br><br> <br><br>
Example: <code>define matrix Matrix matrix.com fhem asdf</code> Example: <code>define matrix Matrix matrix.com fhem</code>
<br><br> <br><br>
noch ins Englische: noch ins Englische:
1. Anmerkung: Zur einfachen Einrichtung habe ich einen Matrix-Element-Client mit "--profile=fhem" gestartet und dort die Registrierung und die Räume vorbereitet. Achtung: alle Räume müssen noch unverschlüsselt sein um FHEM anzubinden. Alle Einladungen in Räume und Annehmen von Einladungen geht hier viel einfacher. Aus dem Element-Client dann die Raum-IDs merken für das Modul.<br/> 1. Anmerkung: Zur einfachen Einrichtung habe ich einen Matrix-Element-Client mit "--profile=fhem" gestartet und dort die Registrierung und die Räume vorbereitet. Achtung: alle Räume müssen noch unverschlüsselt sein um FHEM anzubinden. Alle Einladungen in Räume und Annehmen von Einladungen geht hier viel einfacher. Aus dem Element-Client dann die Raum-IDs merken für das Modul.<br/>
@ -88,8 +92,8 @@ sub Matrix_Initialize {
</ul> </ul>
<br> <br>
<a name="Matrixset"></a> <a id="Matrix-set"></a>
<b>Set</b><br> <h4>Set</h4>
<ul> <ul>
<code>set &lt;name&gt; &lt;option&gt; &lt;value&gt;</code> <code>set &lt;name&gt; &lt;option&gt; &lt;value&gt;</code>
<br><br> <br><br>
@ -97,28 +101,39 @@ sub Matrix_Initialize {
<br><br> <br><br>
Options: Options:
<ul> <ul>
<a id="Matrix-set-password"></a>
<li><i>password</i><br>
Set the password to login</li>
<a id="Matrix-set-register"></a>
<li><i>register</i><br> <li><i>register</i><br>
without function, do not use this</li> without function, do not use this</li>
<a id="Matrix-set-login"></a>
<li><i>login</i><br> <li><i>login</i><br>
Login to the Matrix-Server and sync endless if poll is set to "1"</li> Login to the Matrix-Server and sync endless if poll is set to "1"</li>
<a id="Matrix-set-refresh"></a>
<li><i>refresh</i><br> <li><i>refresh</i><br>
If logged in or in state "soft-logout" refresh gets a new access_token and syncs endless if poll is set to "1"</li> If logged in or in state "soft-logout" refresh gets a new access_token and syncs endless if poll is set to "1"</li>
<a id="Matrix-set-filter"></a>
<li><i>filter</i><br> <li><i>filter</i><br>
A Filter must be set for syncing in long poll. This filter is in the moment experimentell and must be set manual to get the coresponding filter_id</li> A Filter must be set for syncing in long poll. This filter is in the moment experimentell and must be set manual to get the coresponding filter_id</li>
<a id="Matrix-set-poll"></a>
<li><i>poll</i><br> <li><i>poll</i><br>
Defaults to "0": Set poll to "1" for starting the sync-loop</li> Defaults to "0": Set poll to "1" for starting the sync-loop</li>
<a id="Matrix-set-poll.fullstate"></a>
<li><i>poll.fullstate</i><br> <li><i>poll.fullstate</i><br>
Defaults to "0": Set poll.fullstate to "1" for getting in the next sync a full state of all rooms</li> Defaults to "0": Set poll.fullstate to "1" for getting in the next sync a full state of all rooms</li>
<a id="Matrix-set-question.start"></a>
<li><i>question.start</i><br> <li><i>question.start</i><br>
Start a question in the room from reading room. The first answer to the question is logged and ends the question.</li> Start a question in the room from reading room. The first answer to the question is logged and ends the question.</li>
<a id="Matrix-set-question.end"></a>
<li><i>question.end</i><br> <li><i>question.end</i><br>
Stop a question also it is not answered.</li> Stop a question also it is not answered.</li>
</ul> </ul>
</ul> </ul>
<br> <br>
<a name="Matrixget"></a> <a id="Matrix-get"></a>
<b>Get</b><br> <h4>Get</h4>
<ul> <ul>
<code>get &lt;name&gt; &lt;option&gt;</code> <code>get &lt;name&gt; &lt;option&gt;</code>
<br><br> <br><br>
@ -126,8 +141,8 @@ sub Matrix_Initialize {
</ul> </ul>
<br> <br>
<a name="Matrixattr"></a> <a id="Matrix-attr"></a>
<b>Attributes</b> <h4>Attributes</h4>
<ul> <ul>
<code>attr &lt;name&gt; &lt;attribute&gt; &lt;value&gt;</code> <code>attr &lt;name&gt; &lt;attribute&gt; &lt;value&gt;</code>
<br><br> <br><br>
@ -135,43 +150,49 @@ sub Matrix_Initialize {
<br><br> <br><br>
Attributes: Attributes:
<ul> <ul>
<a id="Matrix-attr-MatrixMessage"></a>
<li><i>MatrixMessage</i> <room-id><br> <li><i>MatrixMessage</i> <room-id><br>
Set the room-id to wich messagesare sent. Set the room-id to wich messagesare sent.
</li> </li>
<a id="Matrix-attr-MatrixQuestion_[0..9]"></a>
<li><i>MatrixQuestion_[0..9]</i> <room-id><br> <li><i>MatrixQuestion_[0..9]</i> <room-id><br>
Prepared questions. Prepared questions.
</li> </li>
<a id="Matrix-attr-MatrixRoom"></a>
<li><i>MatrixRoom</i> <room-id 1> <room-id 2> ...<br> <li><i>MatrixRoom</i> <room-id 1> <room-id 2> ...<br>
Set the room-id's from wich are messages received. Set the room-id's from wich are messages received.
</li> </li>
<a id="Matrix-attr-MatrixSender"></a>
<li><i>MatrixSender</i> <code><user 1> <user 2> ...</code><br> <li><i>MatrixSender</i> <code><user 1> <user 2> ...</code><br>
Set the user's from wich are messages received.<br><br> Set the user's from wich are messages received.<br><br>
Example: <code>attr matrix MatrixSender @name:matrix.server @second.name:matrix.server</code><br> Example: <code>attr matrix MatrixSender @name:matrix.server @second.name:matrix.server</code><br>
</li> </li>
</ul> </ul>
</ul> </ul>
<a id="Matrix-readings"></a>
<h4>Readings</h4>
</ul> </ul>
=end html =end html
=begin html_DE =begin html_DE
<a name="Matrix"></a> <a id="Matrix"></a>
<h3>Matrix</h3> <h3>Matrix</h3>
<ul> <ul>
<i>Matrix</i> stellt einen Client für Matrix-Synapse-Server bereit. It is in a very early development state. <i>Matrix</i> stellt einen Client für Matrix-Synapse-Server bereit. It is in a very early development state.
<br><br> <br><br>
<a name="Matrixdefine"></a> <a id="Matrix-define"></a>
<b>Define</b> <h4>Define</h4>
<ul> <ul>
<code>define &lt;name&gt; <server> <user> <passwort></code> <code>define &lt;name&gt; <server> <user></code>
<br><br> <br><br>
Beispiel: <code>define matrix Matrix matrix.com fhem asdf</code> Beispiel: <code>define matrix Matrix matrix.com fhem</code>
<br><br> <br><br>
1. Anmerkung: Zur einfachen Einrichtung habe ich einen Matrix-Element-Client mit "--profile=fhem" gestartet und dort die Registrierung und die Räume vorbereitet. Achtung: alle Räume müssen noch unverschlüsselt sein um FHEM anzubinden. Alle Einladungen in Räume und Annehmen von Einladungen geht hier viel einfacher. Aus dem Element-Client dann die Raum-IDs merken für das Modul.<br/> 1. Anmerkung: Zur einfachen Einrichtung habe ich einen Matrix-Element-Client mit "--profile=fhem" gestartet und dort die Registrierung und die Räume vorbereitet. Achtung: alle Räume müssen noch unverschlüsselt sein um FHEM anzubinden. Alle Einladungen in Räume und Annehmen von Einladungen geht hier viel einfacher. Aus dem Element-Client dann die Raum-IDs merken für das Modul.<br/>
2. Anmerkung: sets, gets, Attribute und Readings müssen noch besser bezeichnet werden. 2. Anmerkung: sets, gets, Attribute und Readings müssen noch besser bezeichnet werden.
</ul> </ul>
<br> <br>
<a name="Matrixset"></a> <a name="Matrix-set"></a>
<b>Set</b><br> <h4>Set</h4>
<ul> <ul>
<code>set &lt;name&gt; &lt;option&gt; &lt;wert&gt;</code> <code>set &lt;name&gt; &lt;option&gt; &lt;wert&gt;</code>
<br><br> <br><br>
@ -179,30 +200,41 @@ sub Matrix_Initialize {
<br><br> <br><br>
Options: Options:
<ul> <ul>
<a id="Matrix-set-password"></a>
<li><i>password</i><br>
Setzt das Passwort zum Login</li>
<a id="Matrix-set-register"></a>
<li><i>register</i><br> <li><i>register</i><br>
noch ohne Funktion!</li> noch ohne Funktion!</li>
<a id="Matrix-set-login"></a>
<li><i>login</i><br> <li><i>login</i><br>
Login beim Matrix-Server und horche andauernd auf Nachrichten wenn poll auf "1" gesetzt ist</li> Login beim Matrix-Server und horche andauernd auf Nachrichten wenn poll auf "1" gesetzt ist</li>
<a id="Matrix-set-refresh"></a>
<li><i>refresh</i><br> <li><i>refresh</i><br>
Wenn eingeloggt oder im Zustand "soft-logout" erhält man mit refresh einen neuen access_token. Wenn poll auf "1" gesetzt ist läuft dann wieder der Empfang andauernd.</li> Wenn eingeloggt oder im Zustand "soft-logout" erhält man mit refresh einen neuen access_token. Wenn poll auf "1" gesetzt ist läuft dann wieder der Empfang andauernd.</li>
<a id="Matrix-set-filter"></a>
<li><i>filter</i><br> <li><i>filter</i><br>
Ein Filter muss gesetzt sein um "Longpoll"-Anfragen an den Server schicken zu können. Der Filter muss hier einmalg gesetzt werden um vom Server eine Filter-ID zu erhalten.</li> Ein Filter muss gesetzt sein um "Longpoll"-Anfragen an den Server schicken zu können. Der Filter muss hier einmalg gesetzt werden um vom Server eine Filter-ID zu erhalten.</li>
<a id="Matrix-set-poll"></a>
<li><i>poll</i><br> <li><i>poll</i><br>
Zunächst "0": Auf "1" startet die Empfangsschleife.</li> Zunächst "0": Auf "1" startet die Empfangsschleife.</li>
<a id="Matrix-set-poll.fullstate"></a>
<li><i>poll.fullstate</i><br> <li><i>poll.fullstate</i><br>
Standard ist "0": Wenn poll.fullstate auf "1" gesetzt wird, werden beider nächsten Synchronisation alle Raumeigenschaften neu eingelesen.</li> Standard ist "0": Wenn poll.fullstate auf "1" gesetzt wird, werden beider nächsten Synchronisation alle Raumeigenschaften neu eingelesen.</li>
<a id="Matrix-set-question.start"></a>
<li><i>question.start</i><br> <li><i>question.start</i><br>
Frage in dem Raum des Attributs "MatrixMessage" stellen. Die erste Antwort steht im Reading "answer" und beendet die Frage.<br> Frage in dem Raum des Attributs "MatrixMessage" stellen. Die erste Antwort steht im Reading "answer" und beendet die Frage.<br>
Als Wert wird entweder die Nummer einer vorbereiteten Frage übergeben oder eine komplette Frage in der Form<br> Als Wert wird entweder die Nummer einer vorbereiteten Frage übergeben oder eine komplette Frage in der Form<br>
<code>Frage:Antwort 1:Antwort 2:....:Antwort n</code></li> <code>Frage:Antwort 1:Antwort 2:....:Antwort n</code></li>
<a id="Matrix-set-question.end"></a>
<li><i>question.end</i><br> <li><i>question.end</i><br>
Die gestartete Frage ohne Antwort beenden. Entweder wird ohne Parameter die aktuelle Frage beendet oder mit einer Nachrichten-ID eine "verwaiste" Frage.</li> Die gestartete Frage ohne Antwort beenden. Entweder wird ohne Parameter die aktuelle Frage beendet oder mit einer Nachrichten-ID eine "verwaiste" Frage.</li>
</ul> </ul>
</ul> </ul>
<br> <br>
<a name="Matrixget"></a> <a id="Matrix-get"></a>
<b>Get</b><br> <h4>Get</h4>
<ul> <ul>
<code>get &lt;name&gt; &lt;option&gt;</code> <code>get &lt;name&gt; &lt;option&gt;</code>
<br><br> <br><br>
@ -210,8 +242,8 @@ sub Matrix_Initialize {
</ul> </ul>
<br> <br>
<a name="Matrixattr"></a> <a id="Matrix-attr"></a>
<b>Attributes</b> <h4>Attributes</h4>
<ul> <ul>
<code>attr &lt;name&gt; &lt;attribute&gt; &lt;value&gt;</code> <code>attr &lt;name&gt; &lt;attribute&gt; &lt;value&gt;</code>
<br><br> <br><br>
@ -219,22 +251,28 @@ sub Matrix_Initialize {
<br><br> <br><br>
Attributes: Attributes:
<ul> <ul>
<a id="Matrix-attr-MatrixMessage"></a>
<li><i>MatrixMessage</i> <room-id><br> <li><i>MatrixMessage</i> <room-id><br>
Setzt die Raum-ID in die alle Nachrichten gesendet werden. Zur Zeit ist nur ein Raum möglich. Setzt die Raum-ID in die alle Nachrichten gesendet werden. Zur Zeit ist nur ein Raum möglich.
</li> </li>
<li><i>MatrixQuestion_[0..9].</i> <room-id><br> <a id="Matrix-attr-MatrixQuestion_[0..9]"></a>
<li><i>MatrixQuestion_[0..9]</i> <room-id><br>
Vorbereitete Fragen, die mit set mt question.start 0..9 gestartet werden können.<br> Vorbereitete Fragen, die mit set mt question.start 0..9 gestartet werden können.<br>
Format der Fragen: <code>Frage:Antwort 1:Antwort 2:....:Antwort n</code> Format der Fragen: <code>Frage:Antwort 1:Antwort 2:....:Antwort n</code>
</li> </li>
<a id="Matrix-attr-MatrixRoom"></a>
<li><i>MatrixRoom</i> <room-id 1> <room-id 2> ...<br> <li><i>MatrixRoom</i> <room-id 1> <room-id 2> ...<br>
Alle Raum-ID's aus denen Nachrichten empfangen werden. Alle Raum-ID's aus denen Nachrichten empfangen werden.
</li> </li>
<a id="Matrix-attr-MatrixSender"></a>
<li><i>MatrixSender</i> <code><user 1> <user 2> ...</code><br> <li><i>MatrixSender</i> <code><user 1> <user 2> ...</code><br>
Alle Personen von denen Nachrichten empfangen werden.<br> Alle Personen von denen Nachrichten empfangen werden.<br>
Beispiel: <code>attr matrix MatrixSender @name:matrix.server @second.name:matrix.server</code><br> Beispiel: <code>attr matrix MatrixSender @name:matrix.server @second.name:matrix.server</code><br>
</li> </li>
</ul> </ul>
</ul> </ul>
<a id="Matrix-readings"></a>
<h4>Readings</h4>
</ul> </ul>
=end html_DE =end html_DE
=cut =cut

View File

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