Nachrichten von FHEM zu Matrix Synapse senden

Begonnen von DecaTec, 03 Mai 2021, 12:42:21

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Das bekannte Problem, wenn der Longpoll noch aktiv ist und man dann eine Nachricht senden möchte, ist nun gelöst - man braucht also nicht zwei Devices einrichten.

Es wird das Attribut set01IExpr ein wenig ungewöhnlich genutzt um vor dem Senden ein wenig Perlcode auszuführen. So wird dann der longpoll-Aufruf vorzeitig beendet und das set01 kann seine Arbeit verrichten ohne extra warten zu müssen:
attr MatrixBot set01IExpr #cancel an active longpoll\
if ($hash->{BUSY}\
    && $hash->{HttpUtils}\
    && $hash->{HttpUtils}->{url} =~ m|^https://[^/]+/_matrix/client/v3/sync\?timeout=\d+&filter=| ) {\
    Log(3, "$name: longpoll active, cutting it off now");;\
    HttpUtils_Close($hash->{HttpUtils});;\
}\
\
#just return $val unteraltered\
$val

Warum das Probleme machte kann ich nur eingrenzen auf den Abschnitt aus der 98_HTTPMOD.pm:
    if ($hash->{BUSY}) {            # still waiting for reply to last request
        if ($now > $last + max(15, AttrVal($name, "timeout", 2) *2)) {
            Log3 $name, 5, "$name: HandleSendQueue - still waiting for reply, timeout is over twice - this should never happen. Stop waiting";
            $hash->{BUSY} = 0;      # waited long enough, clear busy flag and continue
        }
Obwohl das Timeout auf 65 Sekunden steht und auch .LASTSEND auf sinnvollen Werten steht, springt HTTPMOD in das Fehlerhandling, das einfach nur busy auf 0 setzt, ohne den ggf. noch aktiven longpoll darüber zu informieren. Als Reaktion darauf kann es passieren, dass die ReadCallback-Funktion dann zweimal aufgerufen wird, die Ergebnisse aber bei dem falschem Reading landen. Wie auch immer, so wie oben umschiffe ich das Problem.

Hier das ganze Device zum einfachen Einrichten:
defmod MatrixBot HTTPMOD none 0
attr MatrixBot userattr MatrixRoomID MatrixServer MatrixUser
attr MatrixBot MatrixRoomID !12345678901234:nope.chat
attr MatrixBot MatrixServer nope.chat
attr MatrixBot MatrixUser DeinUsername
attr MatrixBot bodyDecode utf-8
attr MatrixBot bodyEncode utf-8
attr MatrixBot comment "\
Create a room for FHEM.\
\
The room must not use encryption, a room that has encryption\
enabled, cannot be converted to a non-encrypted room anymore \
\
The room-id can be found in Element-Web at:\
Room Settings --> Advanced --> Internal room ID\
\
To store the password in FHEM in obfuscated way:\
set MatrixBot storeKeyValue MatrixPassword yourPassword123\
\
###\
To send a text:\
set MatrixBot sendText Bla Bla Bla\
\
\
###\
To longpoll for messages once:\
# 1. send special filter to Matrix:\
set MatrixBot sendFilter\
\
# 2. start one longPoll (waits up to 60 seconds\
#                        or until data is available)\
get MatrixBot longpoll\
\
#alternatively, to keep on longPolling (this also sets filter):\
set MatrixBot longpollCmd startTimer\
#to stop the timers:\
set MatrixBot longpollCmd stopTimer\
\
#############################################################\
https://spec.matrix.org/v1.14/client-server-api/#syncing\
"
attr MatrixBot get02AlwaysNum 0
attr MatrixBot get02HeaderAuthorization Authorization: Bearer $sid
attr MatrixBot get02Name longpoll
attr MatrixBot get02Regex \"next_batch\":\s*\"(?<next_batch>[^\"]+)\"(?:.*?\"timeline\":\s*{\s*\"events\":\s*(?<messages>\[.*?\])\s*)?
attr MatrixBot get02TextArg 0
attr MatrixBot get02URL https://[$name:MatrixServer]/_matrix/client/v3/sync?timeout=60000&filter=[$name:filter_id]%%next_batch_param%%
attr MatrixBot icon message_info
attr MatrixBot parseFunction1 handleAuthErrors
attr MatrixBot reAuthAlways 0
attr MatrixBot reAuthRegex M_UNKNOWN_TOKEN
attr MatrixBot replacement01Mode expression
attr MatrixBot replacement01Regex \[([^:\s\[\"\']+):([^\]\s]+)\]
attr MatrixBot replacement01Value my $device = $name if ($1 eq "\$name") // $1;;\
ReadingsVal($device, $2, undef) or AttrVal($device, $2, "???");;
attr MatrixBot replacement02Mode expression
attr MatrixBot replacement02Regex %%uuid%%
attr MatrixBot replacement02Value join("-", unpack("A8 A4 A4 A4 A12", unpack("H*", join("", map { chr(int rand 256) } 0..15))))
attr MatrixBot replacement03Mode key
attr MatrixBot replacement03Regex %%MatrixPassword%%
attr MatrixBot replacement03Value MatrixPassword
attr MatrixBot replacement04Mode expression
attr MatrixBot replacement04Regex %%next_batch_param%%
attr MatrixBot replacement04Value #is there a reading 'next_batch'?\
my $val = ReadingsVal($name, 'next_batch', '???');;\
\
#return the GET parameter 'sync=value' for /sync Endpoint\
return "&since=$val" if ($val ne '???');;\
\
#return neither since-key nor value for it:\
return "";;
attr MatrixBot set01Data {\
  "msgtype": "m.text",\
  "body": "$val"\
}
attr MatrixBot set01HeaderAuthorization Authorization: Bearer $sid
attr MatrixBot set01HeaderContent-Type application/json
attr MatrixBot set01IExpr #cancel an active longpoll\
if ($hash->{BUSY}\
    && $hash->{HttpUtils}\
    && $hash->{HttpUtils}->{url} =~ m|^https://[^/]+/_matrix/client/v3/sync\?timeout=\d+&filter=| ) {\
    Log(3, "$name: longpoll active, cutting it off now");;\
    HttpUtils_Close($hash->{HttpUtils});;\
}\
\
#just return $val unteraltered\
$val
attr MatrixBot set01Method POST
attr MatrixBot set01Name sendText
attr MatrixBot set01ParseResponse 1
attr MatrixBot set01Regex {\"event_id\":\"(.*)\"}
attr MatrixBot set01TextArg 1
attr MatrixBot set01URL https://[$name:MatrixServer]/_matrix/client/v3/rooms/[$name:MatrixRoomID]/send/m.room.message?txnId=%%uuid%%
attr MatrixBot set02Data {\
  "room": {\
    "rooms": ["[$name:MatrixRoomID]"],\
    "timeline": {\
      "limit": 10,\
      "types": ["m.room.message"]\
    },\
    "include_leave": false,\
    "include_join": false,\
    "include_account_data": false,\
    "include_state": false,\
    "state": {\
      "types": []\
    },\
    "ephemeral": {\
      "types": []\
    },\
    "account_data": {\
      "types": []\
    }\
  },\
  "event_fields": [\
    "content.body",\
    "sender",\
    "origin_server_ts"\
  ],\
  "event_format": "client",\
  "presence": {\
    "types": [],\
    "not_types": ["*"]\
  },\
  "account_data": {\
    "types": [],\
    "not_types": ["*"]\
  }\
}
attr MatrixBot set02HeaderAuthorization Authorization: Bearer $sid
attr MatrixBot set02HeaderContent-Type application/json
attr MatrixBot set02Method POST
attr MatrixBot set02Name sendFilter
attr MatrixBot set02NoArg 1
attr MatrixBot set02ParseResponse 1
attr MatrixBot set02Regex \"filter_id\":\s*\"(?<filter_id>\d+)\"
attr MatrixBot set02URL https://[$name:MatrixServer]/_matrix/client/v3/user/@[$name:MatrixUser]:[$name:MatrixServer]/filter
attr MatrixBot set03Local 1
attr MatrixBot set03Name longpollCmd
attr MatrixBot set03TextArg 1
attr MatrixBot showError 1
attr MatrixBot sid01Data {\
  "type": "m.login.password",\
  "identifier": {\
    "type": "m.id.user",\
    "user": "[$name:MatrixUser]"\
  },\
  "password": "%%MatrixPassword%%"\
}
attr MatrixBot sid01HeaderContent-Type application/json
attr MatrixBot sid01IdRegex "access_token"\s*:\s*"([^"]+)"
attr MatrixBot sid01URL https://[$name:MatrixServer]/_matrix/client/v3/login
attr MatrixBot timeout 65
attr MatrixBot userReadings longpollTimer:(next_batch|longpollCmd|LAST_ERROR|sendText):.* {\
  my $longpollCmd = ReadingsVal($name, 'longpollCmd', '???');;\
  my $delay = 1;;\
  my $timeout = AttrVal($name, 'timeout', 61) + $delay + 5;;\
  \
  # stop our timers:\
  if ($longpollCmd eq "stopTimer") {\
    fhem("cancel ${name}_longpollTimer quiet");;\
    fhem("cancel ${name}_longpollTimer2 quiet");;\
    return "stopped";;\
  }\
  \
  if ($longpollCmd ne "startTimer") {\
    return "no timer set, longpollCmd is not set to 'startTimer'";;\
  }\
  \
  #if startTimer cmd was given now, set filter as well:\
  if (ReadingsAge($name, 'longpollCmd', 0) <= 1) {\
    $delay = 5;; #delay to allow for sendFilter to be answered\
    #Log(1, "🪲 $name: >>". InternalVal($name, 'httpbody', '???') ."<<");;\
    fhem("sleep 0.1 quiet;; set $name sendFilter");;\
  }\
  \
  #we handle an error reported by http-utils:\
  if (ReadingsAge($name, 'LAST_ERROR', 0) <= 1) {\
    my $last_error = ReadingsVal($name, 'LAST_ERROR', '???');;\
    $delay = 10;; #delay to allow for error reasons to improve\
    #Log(1, "🪲 $name: Dealing with error: >>$last_error<<");;\
  }\
  \
  # for testing this: { $defs{MatrixBot}{sid} = 'bla' }\
  if (!defined &HTTPMOD::handleAuthErrors) {\
    *HTTPMOD::handleAuthErrors = sub {\
      my ($hash, $header, $body, $request) = @_;;\
      my $name = $hash->{NAME};;\
      my $status;;\
      \
      if ($header =~ m{^HTTP/\d\.\d\s+(\d+)}m) {\
        $status = $1;;\
      }\
      \
      Log3($name, 4, "$name: HTTP status code is $status");;\
      \
      if ( $status == 401 || $status == 403 || $status == 500 ) {\
        Log3($name, 3, "$name: auth-error or servererror ($status), calling doAuth()");;\
        HTTPMOD::DoAuth($hash);;\
      }\
    };;\
  }\
  \
  #set timers, one regular and one fallback:\
  fhem("sleep $delay ${name}_longpollTimer quiet;; get $name longpoll");;\
  fhem("sleep $timeout ${name}_longpollTimer2 quiet;; set $name longpollCmd startTimer");;\
  \
  return strftime("next longpoll at %H:%M:%S", localtime( time()+$delay ));;\
},\
messages_list:messages:.* {\
  my $this = ReadingsVal($name, $reading, '');;\
  my @timestampArray = split("\n", $this);;\
  my $messages_ref = decode_json(ReadingsVal($name, 'messages', ''));;\
  my $length = 20;;\
  \
  my %seen_messages = map { $_ => 1 } @timestampArray;;\
  \
  foreach my $msg (@$messages_ref) {\
    my $val = $msg->{content}{body};;\
    $val = Encode::decode('utf-8', $val) unless Encode::is_utf8($val);;\
    $val =~ s/\n/ /g;; #replace newlines with spaces\
    \
    my $sender = $msg->{sender};;\
    my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime($msg->{origin_server_ts} / 1000));;\
    my $new_entry = "$ts: $sender: $val";;\
    \
    next if $seen_messages{$new_entry};;\
    \
    my $inserted = 0;;\
    for (my $i = 0;; $i < @timestampArray;; $i++) {\
      my ($existing_ts) = $timestampArray[$i] =~ /^([^:]+):/;;\
      if ($ts lt $existing_ts) {\
        splice(@timestampArray, $i, 0, $new_entry);;\
        $inserted = 1;;\
        last;;\
      }\
    }\
    push(@timestampArray, encode('utf-8', $new_entry)) unless $inserted;;\
    shift(@timestampArray) while @timestampArray > $length;;\
  }\
  \
  return join("\n", @timestampArray);;\
},\
process_messages:messages:.* {\
  my $val = ReadingsVal($name, 'messages_list', '');;\
  my $this = ReadingsVal($name, $reading, '');;\
  my $latest_ts = $this;;\
  \
  foreach my $line (split(/\n/, $val)) {\
    if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}):\s*(.*)$/) {\
      my ($ts, $msg) = ($1, $2);;\
      \
      if ($ts gt $this) {\
        fhem("trigger $name msg: $line");;\
        $latest_ts = $ts if ($ts gt $latest_ts);;\
      }\
    }\
  }\
  return $latest_ts;;\
}
attr MatrixBot verbose 3
attr MatrixBot widgetOverride longpollCmd:uzsuSelectRadio,startTimer,stopTimer