param ( [Parameter(Mandatory=$true)] [string] $HomeServer, [Parameter(Mandatory=$true)] [string] $User, [Parameter(Mandatory=$true)] [securestring] $AccessToken ) function Send-MatrixEvent { param ( [Parameter(Mandatory=$true)] [string] $RoomId, [Parameter(Mandatory=$true)] [string] $Event, [Parameter(Mandatory=$true)] [string] $EventType ) #txn_id should be unique per client, so we use timestamp+random $txn_id = '{0}{1}' -f (Get-Date -UFormat '%s'),(Get-Random) $uri = '{0}/_matrix/client/r0/rooms/{1}/send/{2}/{3}' -f $HomeServer,$RoomId,$EventType,$txn_id $header_splat = @{ Authentication = 'Bearer' Token = $AccessToken ContentType = 'application/json' } $http_splat = @{ Uri = $uri Method = 'Put' Body = $Event } $response = Invoke-RestMethod @header_splat @http_splat if($response.event_id) { Write-Host ('Event {0} sent to room {1}' -f $response.event_id,$RoomId) } } function Send-Pong { param ( [Parameter(Mandatory=$true)] [string] $RoomId, [Parameter(Mandatory=$true)] [string] $Body, [Parameter(Mandatory=$false)] [string] $FormattedBody, [Parameter(Mandatory=$true)] [string] $OriginHomeServer, [Parameter(Mandatory=$true)] [int] $Duration, [Parameter(Mandatory=$true)] [string] $PingEventId ) $event = @{ msgtype = 'm.notice' body = $Body pong = @{ from = $OriginHomeServer ms = $Duration ping = $PingEventId } } if($FormattedBody) { $event += @{ format = 'org.matrix.custom.html' formatted_body = $FormattedBody } } $event_json = $event | ConvertTo-Json -Compress Send-MatrixEvent -RoomId $RoomId -Event $event_json -EventType 'm.room.message' } function Compare-Timestamps { param ( [Parameter(Mandatory=$true)] [Int64] $OriginServerTs ) $current_time = (Get-Date).ToUniversalTime() #$OriginServerTs is milliseconds since 1970-01-01 (epoch time in milliseconds) $original_time = Get-Date -Date ((Get-Date -Date '1970-01-01') + [timespan]::FromMilliseconds($OriginServerTs)) #return the difference Write-Output ($current_time - $original_time) } function ConvertTo-HumanReadableTimespan { param ( [parameter(Mandatory=$true)] [timespan] $TimeSpan ) switch($TimeSpan) { Default { $output = '{0} days' -f [System.Math]::Round($TimeSpan.TotalDays, 2) } {$PSItem.TotalHours -lt 24} { $output = '{0} h' -f [System.Math]::Round($TimeSpan.TotalHours, 2) } {$PSItem.TotalMinutes -lt 120} { $output = '{0} min' -f [System.Math]::Round($TimeSpan.TotalMinutes, 2) } {$PSItem.TotalSeconds -lt 120} { $output = '{0} s' -f [System.Math]::Round($TimeSpan.TotalSeconds, 2) } {$PSItem.TotalMilliseconds -lt 10000} { $output = '{0} ms' -f [System.Math]::Round($TimeSpan.TotalMilliseconds, 0) } } Write-Output $output } function Join-Pong { param ( [Parameter(Mandatory=$true)] [string] $RoomId, [Parameter(Mandatory=$true)] [string] $PingEventId, [Parameter(Mandatory=$true)] [string] $SenderMxId, [Parameter(Mandatory=$true)] [string] $ReadableTimespan, [Parameter(Mandatory=$false)] [string] $Ball ) if($Ball) { $padded_ball = $Ball+' ' } $body = '{0}: Pong! (ping {1}took {2} to arrive)' -f $SenderMxId,$padded_ball,$ReadableTimespan $formatted_body = '{0}: Pong! ' -f $SenderMxId $formatted_body += '(ping {2}took {3} to arrive)' -f $RoomId,$PingEventId,$padded_ball,$ReadableTimespan return @{ Body = $body FormattedBody = $formatted_body } } function Open-MatrixEvent { param ( [Parameter(Mandatory=$true)] $Event, [Parameter(Mandatory=$true)] $RoomId ) #the "ball" is a string returned by the bot if($Event.content.msgtype -eq 'm.text' -and $Event.content.body -match '!ping( (?.*))?') { $difference = Compare-Timestamps -OriginServerTs $Event.origin_server_ts $readable_timespan = ConvertTo-HumanReadableTimespan -TimeSpan $difference #$bodies contains a hashtable with keys Body and FormattedBody $bodies = Join-Pong -RoomId $RoomId -PingEventId $Event.event_id -SenderMxId $Event.sender -ReadableTimespan $readable_timespan -Ball $Matches.ball $origin_homeserver = $Event.sender.Split(':')[1] Send-Pong -RoomId $RoomId @bodies -OriginHomeServer $origin_homeserver -Duration $difference.TotalMilliseconds -PingEventId $Event.event_id } } function Start-MatrixSync { param ( [string] $Token, [int] $Timeout = 30000 ) $uri = '{0}/_matrix/client/r0/sync?timeout={1}' -f $HomeServer,$Timeout if($Token) { $uri += '&since={0}' -f $Token } $header_splat = @{ Authentication = 'Bearer' Token = $AccessToken ContentType = 'application/json' } $http_splat = @{ Uri = $uri } $response = Invoke-RestMethod @header_splat @http_splat #.PSObject.Properties because the rooms under .join are [NoteProperty] $room_ids = $response.rooms.join.PSObject.Properties.Name foreach($room_id in $room_ids) { $events = $response.rooms.join.$room_id.timeline.events foreach($event in $events) { Open-MatrixEvent -Event $event -RoomId $room_id } } Write-Output $response.next_batch } #start sync loop while($true) { #use the token of the last sync #initial token is $null $token = Start-MatrixSync -Token $token }