summary refs log tree commit diff
path: root/contrib/vertobot/bridge.pl
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xcontrib/vertobot/bridge.pl493
1 files changed, 0 insertions, 493 deletions
diff --git a/contrib/vertobot/bridge.pl b/contrib/vertobot/bridge.pl
deleted file mode 100755

index a551850f40..0000000000 --- a/contrib/vertobot/bridge.pl +++ /dev/null
@@ -1,493 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; -use 5.010; # // -use IO::Socket::SSL qw(SSL_VERIFY_NONE); -use IO::Async::Loop; -use Net::Async::WebSocket::Client; -use Net::Async::HTTP; -use Net::Async::HTTP::Server; -use JSON; -use YAML; -use Data::UUID; -use Getopt::Long; -use Data::Dumper; -use URI::Encode qw(uri_encode uri_decode); - -binmode STDOUT, ":encoding(UTF-8)"; -binmode STDERR, ":encoding(UTF-8)"; - -my $msisdn_to_matrix = { - '447417892400' => '@matthew:matrix.org', -}; - -my $matrix_to_msisdn = {}; -foreach (keys %$msisdn_to_matrix) { - $matrix_to_msisdn->{$msisdn_to_matrix->{$_}} = $_; -} - - -my $loop = IO::Async::Loop->new; -# Net::Async::HTTP + SSL + IO::Poll doesn't play well. See -# https://rt.cpan.org/Ticket/Display.html?id=93107 -# ref $loop eq "IO::Async::Loop::Poll" and -# warn "Using SSL with IO::Poll causes known memory-leaks!!\n"; - -GetOptions( - 'C|config=s' => \my $CONFIG, - 'eval-from=s' => \my $EVAL_FROM, -) or exit 1; - -if( defined $EVAL_FROM ) { - # An emergency 'eval() this file' hack - $SIG{HUP} = sub { - my $code = do { - open my $fh, "<", $EVAL_FROM or warn( "Cannot read - $!" ), return; - local $/; <$fh> - }; - - eval $code or warn "Cannot eval() - $@"; - }; -} - -defined $CONFIG or die "Must supply --config\n"; - -my %CONFIG = %{ YAML::LoadFile( $CONFIG ) }; - -my %MATRIX_CONFIG = %{ $CONFIG{matrix} }; -# No harm in always applying this -$MATRIX_CONFIG{SSL_verify_mode} = SSL_VERIFY_NONE; - -my $bridgestate = {}; -my $roomid_by_callid = {}; - -my $sessid = lc new Data::UUID->create_str(); -my $as_token = $CONFIG{"matrix-bot"}->{as_token}; -my $hs_domain = $CONFIG{"matrix-bot"}->{domain}; - -my $http = Net::Async::HTTP->new(); -$loop->add( $http ); - -sub create_virtual_user -{ - my ($localpart) = @_; - my ( $response ) = $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/register?". - "access_token=$as_token&user_id=$localpart" - ), - content_type => "application/json", - content => <<EOT -{ - "type": "m.login.application_service", - "user": "$localpart" -} -EOT - )->get; - warn $response->as_string if ($response->code != 200); -} - -my $http_server = Net::Async::HTTP::Server->new( - on_request => sub { - my $self = shift; - my ( $req ) = @_; - - my $response; - my $path = uri_decode($req->path); - warn("request: $path"); - if ($path =~ m#/users/\@(\+.*)#) { - # when queried about virtual users, auto-create them in the HS - my $localpart = $1; - create_virtual_user($localpart); - $response = HTTP::Response->new( 200 ); - $response->add_content('{}'); - $response->content_type( "application/json" ); - } - elsif ($path =~ m#/transactions/(.*)#) { - my $event = JSON->new->decode($req->body); - print Dumper($event); - - my $room_id = $event->{room_id}; - my %dp = %{$CONFIG{'verto-dialog-params'}}; - $dp{callID} = $bridgestate->{$room_id}->{callid}; - - if ($event->{type} eq 'm.room.membership') { - my $membership = $event->{content}->{membership}; - my $state_key = $event->{state_key}; - my $room_id = $event->{state_id}; - - if ($membership eq 'invite') { - # autojoin invites - my ( $response ) = $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/rooms/$room_id/join?". - "access_token=$as_token&user_id=$state_key" - ), - content_type => "application/json", - content => "{}", - )->get; - warn $response->as_string if ($response->code != 200); - } - } - elsif ($event->{type} eq 'm.call.invite') { - my $room_id = $event->{room_id}; - $bridgestate->{$room_id}->{matrix_callid} = $event->{content}->{call_id}; - $bridgestate->{$room_id}->{callid} = lc new Data::UUID->create_str(); - $bridgestate->{$room_id}->{sessid} = $sessid; - # $bridgestate->{$room_id}->{offer} = $event->{content}->{offer}->{sdp}; - my $offer = $event->{content}->{offer}->{sdp}; - # $bridgestate->{$room_id}->{gathered_candidates} = 0; - $roomid_by_callid->{ $bridgestate->{$room_id}->{callid} } = $room_id; - # no trickle ICE in verto apparently - - my $f = send_verto_json_request("verto.invite", { - "sdp" => $offer, - "dialogParams" => \%dp, - "sessid" => $bridgestate->{$room_id}->{sessid}, - }); - $self->adopt_future($f); - } - # elsif ($event->{type} eq 'm.call.candidates') { - # # XXX: this could fire for both matrix->verto and verto->matrix calls - # # and races as it collects candidates. much better to just turn off - # # candidate gathering in the webclient entirely for now - # - # my $room_id = $event->{room_id}; - # # XXX: compare call IDs - # if (!$bridgestate->{$room_id}->{gathered_candidates}) { - # $bridgestate->{$room_id}->{gathered_candidates} = 1; - # my $offer = $bridgestate->{$room_id}->{offer}; - # my $candidate_block = ""; - # foreach (@{$event->{content}->{candidates}}) { - # $candidate_block .= "a=" . $_->{candidate} . "\r\n"; - # } - # # XXX: collate using the right m= line - for now assume audio call - # $offer =~ s/(a=rtcp.*[\r\n]+)/$1$candidate_block/; - # - # my $f = send_verto_json_request("verto.invite", { - # "sdp" => $offer, - # "dialogParams" => \%dp, - # "sessid" => $bridgestate->{$room_id}->{sessid}, - # }); - # $self->adopt_future($f); - # } - # else { - # # ignore them, as no trickle ICE, although we might as well - # # batch them up - # # foreach (@{$event->{content}->{candidates}}) { - # # push @{$bridgestate->{$room_id}->{candidates}}, $_; - # # } - # } - # } - elsif ($event->{type} eq 'm.call.answer') { - # grab the answer and relay it to verto as a verto.answer - my $room_id = $event->{room_id}; - - my $answer = $event->{content}->{answer}->{sdp}; - my $f = send_verto_json_request("verto.answer", { - "sdp" => $answer, - "dialogParams" => \%dp, - "sessid" => $bridgestate->{$room_id}->{sessid}, - }); - $self->adopt_future($f); - } - elsif ($event->{type} eq 'm.call.hangup') { - my $room_id = $event->{room_id}; - if ($bridgestate->{$room_id}->{matrix_callid} eq $event->{content}->{call_id}) { - my $f = send_verto_json_request("verto.bye", { - "dialogParams" => \%dp, - "sessid" => $bridgestate->{$room_id}->{sessid}, - }); - $self->adopt_future($f); - } - else { - warn "Ignoring unrecognised callid: ".$event->{content}->{call_id}; - } - } - else { - warn "Unhandled event: $event->{type}"; - } - - $response = HTTP::Response->new( 200 ); - $response->add_content('{}'); - $response->content_type( "application/json" ); - } - else { - warn "Unhandled path: $path"; - $response = HTTP::Response->new( 404 ); - } - - $req->respond( $response ); - }, -); -$loop->add( $http_server ); - -$http_server->listen( - addr => { family => "inet", socktype => "stream", port => 8009 }, - on_listen_error => sub { die "Cannot listen - $_[-1]\n" }, -); - -my $bot_verto = Net::Async::WebSocket::Client->new( - on_frame => sub { - my ( $self, $frame ) = @_; - warn "[Verto] receiving $frame"; - on_verto_json($frame); - }, -); -$loop->add( $bot_verto ); - -my $verto_connecting = $loop->new_future; -$bot_verto->connect( - %{ $CONFIG{"verto-bot"} }, - on_connected => sub { - warn("[Verto] connected to websocket"); - if (not $verto_connecting->is_done) { - $verto_connecting->done($bot_verto); - - send_verto_json_request("login", { - 'login' => $CONFIG{'verto-dialog-params'}{'login'}, - 'passwd' => $CONFIG{'verto-config'}{'passwd'}, - 'sessid' => $sessid, - }); - } - }, - on_connect_error => sub { die "Cannot connect to verto - $_[-1]" }, - on_resolve_error => sub { die "Cannot resolve to verto - $_[-1]" }, -); - -# die Dumper($verto_connecting); - -my $as_url = $CONFIG{"matrix-bot"}->{as_url}; - -Future->needs_all( - $http->do_request( - method => "POST", - uri => URI->new( $CONFIG{"matrix"}->{server}."/_matrix/appservice/v1/register" ), - content_type => "application/json", - content => <<EOT -{ - "as_token": "$as_token", - "url": "$as_url", - "namespaces": { "users": [ { "regex": "\@\\\\+.*", "exclusive": false } ] } -} -EOT - )->then( sub{ - my ($response) = (@_); - warn $response->as_string if ($response->code != 200); - return Future->done; - }), - $verto_connecting, -)->get; - -$loop->attach_signal( - PIPE => sub { warn "pipe\n" } -); -$loop->attach_signal( - INT => sub { $loop->stop }, -); -$loop->attach_signal( - TERM => sub { $loop->stop }, -); - -eval { - $loop->run; -} or my $e = $@; - -die $e if $e; - -exit 0; - -{ - my $json_id; - my $requests; - - sub send_verto_json_request - { - $json_id ||= 1; - - my ($method, $params) = @_; - my $json = { - jsonrpc => "2.0", - method => $method, - params => $params, - id => $json_id, - }; - my $text = JSON->new->encode( $json ); - warn "[Verto] sending $text"; - $bot_verto->send_frame ( $text ); - my $request = $loop->new_future; - $requests->{$json_id} = $request; - $json_id++; - return $request; - } - - sub send_verto_json_response - { - my ($result, $id) = @_; - my $json = { - jsonrpc => "2.0", - result => $result, - id => $id, - }; - my $text = JSON->new->encode( $json ); - warn "[Verto] sending $text"; - $bot_verto->send_frame ( $text ); - } - - sub on_verto_json - { - my $json = JSON->new->decode( $_[0] ); - if ($json->{method}) { - if (($json->{method} eq 'verto.answer' && $json->{params}->{sdp}) || - $json->{method} eq 'verto.media') { - - my $caller = $json->{dialogParams}->{caller_id_number}; - my $callee = $json->{dialogParams}->{destination_number}; - my $caller_user = '@+' . $caller . ':' . $hs_domain; - my $callee_user = $msisdn_to_matrix->{$callee} || warn "unrecogised callee: $callee"; - my $room_id = $roomid_by_callid->{$json->{params}->{callID}}; - - if ($json->{params}->{sdp}) { - $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/send/m.call.answer?". - "access_token=$as_token&user_id=$caller_user" - ), - content_type => "application/json", - content => JSON->new->encode({ - call_id => $bridgestate->{$room_id}->{matrix_callid}, - version => 0, - answer => { - sdp => $json->{params}->{sdp}, - type => "answer", - }, - }), - )->then( sub { - send_verto_json_response( { - method => $json->{method}, - }, $json->{id}); - })->get; - } - } - elsif ($json->{method} eq 'verto.invite') { - my $caller = $json->{dialogParams}->{caller_id_number}; - my $callee = $json->{dialogParams}->{destination_number}; - my $caller_user = '@+' . $caller . ':' . $hs_domain; - my $callee_user = $msisdn_to_matrix->{$callee} || warn "unrecogised callee: $callee"; - - my $alias = ($caller lt $callee) ? ($caller.'-'.$callee) : ($callee.'-'.$caller); - my $room_id; - - # create a virtual user for the caller if needed. - create_virtual_user($caller); - - # create a room of form #peer-peer and invite the callee - $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/createRoom?". - "access_token=$as_token&user_id=$caller_user" - ), - content_type => "application/json", - content => JSON->new->encode({ - room_alias_name => $alias, - invite => [ $callee_user ], - }), - )->then( sub { - my ( $response ) = @_; - my $resp = JSON->new->decode($response->content); - $room_id = $resp->{room_id}; - $roomid_by_callid->{$json->{params}->{callID}} = $room_id; - })->get; - - # join it - my ($response) = $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/join/$room_id?". - "access_token=$as_token&user_id=$caller_user" - ), - content_type => "application/json", - content => '{}', - )->get; - - $bridgestate->{$room_id}->{matrix_callid} = lc new Data::UUID->create_str(); - $bridgestate->{$room_id}->{callid} = $json->{dialogParams}->{callID}; - $bridgestate->{$room_id}->{sessid} = $sessid; - - # put the m.call.invite in there - $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/send/m.call.invite?". - "access_token=$as_token&user_id=$caller_user" - ), - content_type => "application/json", - content => JSON->new->encode({ - call_id => $bridgestate->{$room_id}->{matrix_callid}, - version => 0, - answer => { - sdp => $json->{params}->{sdp}, - type => "offer", - }, - }), - )->then( sub { - # acknowledge the verto - send_verto_json_response( { - method => $json->{method}, - }, $json->{id}); - })->get; - } - elsif ($json->{method} eq 'verto.bye') { - my $caller = $json->{dialogParams}->{caller_id_number}; - my $callee = $json->{dialogParams}->{destination_number}; - my $caller_user = '@+' . $caller . ':' . $hs_domain; - my $callee_user = $msisdn_to_matrix->{$callee} || warn "unrecogised callee: $callee"; - my $room_id = $roomid_by_callid->{$json->{params}->{callID}}; - - # put the m.call.hangup into the room - $http->do_request( - method => "POST", - uri => URI->new( - $CONFIG{"matrix"}->{server}. - "/_matrix/client/api/v1/send/m.call.hangup?". - "access_token=$as_token&user_id=$caller_user" - ), - content_type => "application/json", - content => JSON->new->encode({ - call_id => $bridgestate->{$room_id}->{matrix_callid}, - version => 0, - }), - )->then( sub { - # acknowledge the verto - send_verto_json_response( { - method => $json->{method}, - }, $json->{id}); - })->get; - } - else { - warn ("[Verto] unhandled method: " . $json->{method}); - send_verto_json_response( { - method => $json->{method}, - }, $json->{id}); - } - } - elsif ($json->{result}) { - $requests->{$json->{id}}->done($json->{result}); - } - elsif ($json->{error}) { - $requests->{$json->{id}}->fail($json->{error}->{message}, $json->{error}); - } - } -} -