Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
synapse
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Monitor
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Maunium
synapse
Commits
84a43676
Commit
84a43676
authored
10 years ago
by
Matthew Hodgson
Browse files
Options
Downloads
Patches
Plain Diff
WIP vertobridge AS
parent
3c8bd780
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
contrib/vertobot/bridge.pl
+489
-0
489 additions, 0 deletions
contrib/vertobot/bridge.pl
with
489 additions
and
0 deletions
contrib/vertobot/bridge.pl
0 → 100755
+
489
−
0
View file @
84a43676
#!/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": ["\@\\\\+.*"] }
}
EOT
),
$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
});
}
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment