mirror of https://github.com/dani/patrix.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
11 KiB
337 lines
11 KiB
#!/usr/bin/perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
use LWP::UserAgent;
|
|
use HTTP::Request;
|
|
use JSON qw(from_json to_json);
|
|
use Getopt::Long;
|
|
use Config::Simple;
|
|
use File::HomeDir;
|
|
use File::MimeInfo;
|
|
use File::Basename;
|
|
use URI::Escape;
|
|
use Path::Tiny;
|
|
|
|
our $opt;
|
|
|
|
GetOptions(
|
|
"user=s" => \$opt->{user},
|
|
"password=s" => \$opt->{password},
|
|
"access_token=s" => \$opt->{access_token},
|
|
"server=s" => \$opt->{server},
|
|
"room=s" => \$opt->{room},
|
|
"message=s" => \$opt->{message},
|
|
"files=s" => \$opt->{file},
|
|
"debug" => \$opt->{debug},
|
|
"action=s" => \$opt->{action},
|
|
"conf=s" => \$opt->{conf},
|
|
"invite=s@" => \$opt->{invite},
|
|
"name=s" => \$opt->{name},
|
|
"alias=s" => \$opt->{alias},
|
|
"topic=s" => \$opt->{topic},
|
|
"preset=s" => \$opt->{preset},
|
|
"visibility=s" => \$opt->{visibility},
|
|
"federation!" => \$opt->{federation}
|
|
);
|
|
|
|
if (-e File::HomeDir->my_home . "/.patrixrc" && !$opt->{conf}){
|
|
$opt->{conf} = File::HomeDir->my_home . "/.patrixrc";
|
|
if ($opt->{debug}){
|
|
print "Using default config file $opt->{conf}\n\n";
|
|
}
|
|
}
|
|
if ($opt->{conf} && -e $opt->{conf}){
|
|
read_conf();
|
|
}
|
|
|
|
my $stdin = 0;
|
|
if (!-t STDIN){
|
|
if ($opt->{debug}){
|
|
print "Reading data from stdin\n\n";
|
|
}
|
|
$stdin = 1;
|
|
}
|
|
|
|
# Set defaults
|
|
$opt->{server} //= 'matrix.org';
|
|
$opt->{action} //= 'send-msg';
|
|
$opt->{federation} //= 1;
|
|
|
|
# Check we have all the options we need
|
|
if ($opt->{action} eq 'get-access-token' && (!$opt->{user} || !$opt->{password})){
|
|
die "You need to provide a valid user and password to get an access token\n\n";
|
|
}
|
|
elsif (!$opt->{access_token} && (!$opt->{user} || !$opt->{password})){
|
|
die "Test: You need to provide either an access token or a valid user and password\n\n";
|
|
}
|
|
if ($opt->{action} eq 'send-msg' && (!$opt->{room} || (!$opt->{message} && !$stdin))){
|
|
die "You need to provide a room ID and a message\n\n";
|
|
}
|
|
if ($opt->{action} eq 'send-file' && (!$opt->{room} || !$opt->{file})){
|
|
die "You need to provide a room ID and a file to send\n\n";
|
|
}
|
|
if ($opt->{action} eq 'send-file' && $opt->{file} && !-e $opt->{file}){
|
|
die "File $opt->{file} not found\n\n";
|
|
}
|
|
|
|
$opt->{server} = 'https://' . $opt->{server} unless ($opt->{server} =~ m|https?://|);
|
|
|
|
my $lwp = LWP::UserAgent->new;
|
|
|
|
# Load values from the config file if it exists
|
|
sub read_conf {
|
|
my $cfg = Config::Simple->new;
|
|
$cfg->read($opt->{conf});
|
|
foreach my $param(keys %{$opt}){
|
|
if ($cfg->param('default.' . $param) && !$opt->{$param}){
|
|
$opt->{$param} = $cfg->param('default.' . $param)
|
|
}
|
|
}
|
|
}
|
|
|
|
# Submit user and password the the HS and obtain an access_token
|
|
sub login {
|
|
if ( $opt->{debug} ){
|
|
print "Trying to login on $opt->{server} as $opt->{user}\n";
|
|
}
|
|
my $uri = $opt->{server} . '/_matrix/client/r0/login';
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
my $json = {
|
|
type => 'm.login.password',
|
|
user => $opt->{user},
|
|
password => $opt->{password}
|
|
};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
my $resp = $lwp->request( $req );
|
|
if ( $opt->{debug} ){
|
|
print "Login response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error login in, please check your credentials\n";
|
|
}
|
|
# Set the access token
|
|
$opt->{access_token} = from_json($resp->decoded_content)->{access_token};
|
|
}
|
|
|
|
# Invalidate the access_token
|
|
sub logout {
|
|
if ( $opt->{debug} ){
|
|
print "Trying to logout\n";
|
|
}
|
|
my $uri = $opt->{server} . '/_matrix/client/r0/logout?access_token=' . $opt->{access_token};
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
my $json = {};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
my $resp = $lwp->request( $req );
|
|
if ($opt->{debug}){
|
|
print "Logout response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error login out\n";
|
|
}
|
|
}
|
|
|
|
# Join the specified room, before we can send anything
|
|
sub join_room {
|
|
if ($opt->{debug}){
|
|
print "Trying to join room $opt->{room}\n";
|
|
}
|
|
# Room must be escaped. if not and room is an alias, it'll start with # so the access_token won't be sent
|
|
my $uri = $opt->{server} . '/_matrix/client/r0/join/' . uri_escape( $opt->{room} ) . '?access_token=' . $opt->{access_token};
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
my $json = {};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
my $resp = $lwp->request( $req );
|
|
if ($opt->{debug}){
|
|
print "Joining room response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error joining room $opt->{room}\n";
|
|
}
|
|
# Resolve room -> room_id if joined by alias
|
|
my $room_id = from_json($resp->decoded_content)->{room_id};
|
|
$opt->{room} = $room_id if $room_id;
|
|
}
|
|
|
|
# Send a text message (either message or notice as both are similar)
|
|
sub send_msg {
|
|
my $uri = $opt->{server} . '/_matrix/client/r0/rooms/' . $opt->{room} . '/send/m.room.message?access_token=' . $opt->{access_token};
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
# Ignore --message if reading from stdin
|
|
if ($stdin){
|
|
$opt->{message} = '';
|
|
while (<STDIN>){
|
|
$opt->{message} .= $_;
|
|
}
|
|
}
|
|
my $json = {
|
|
msgtype => ($opt->{action} eq 'send-notice') ? 'm.notice' : 'm.text',
|
|
body => $opt->{message}
|
|
};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
my $resp = $lwp->request( $req );
|
|
if ($opt->{debug}){
|
|
print "Sending message response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error sending message to $opt->{room}\n";
|
|
}
|
|
}
|
|
|
|
# Send a file to the room
|
|
sub send_file {
|
|
# Sending a file is a 2 steps operation. First we need to upload the file to the media store
|
|
# And then we post the uri on the room
|
|
if ($opt->{debug}){
|
|
print "Uploading file $opt->{file} to the media store\n\n";
|
|
}
|
|
my $uri = $opt->{server} . '/_matrix/media/v1/upload?access_token=' . $opt->{access_token} . '&filename=' . basename($opt->{file});
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
$req->header( 'Content-Type' => mimetype $opt->{file} );
|
|
# Not ideal as it won't work for huge files but...
|
|
$req->content( path( $opt->{file} )->slurp_raw );
|
|
my $resp = $lwp->request( $req );
|
|
if ($opt->{debug}){
|
|
print "File upload response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error uploading file\n";
|
|
}
|
|
# If everything went well, the server replied with the URI of our file, which we can
|
|
# now post on the room
|
|
my $file_uri = from_json($resp->decoded_content)->{content_uri};
|
|
unless ($file_uri){
|
|
die "Server did not sent the file URI\n";
|
|
}
|
|
if ($opt->{debug}){
|
|
print "File uploaded, with the URI $file_uri\n";
|
|
print "Now Sending the file link to the room $opt->{room}\n\n";
|
|
}
|
|
# Now lets post a new message with the URI of the file
|
|
$uri = $opt->{server} . '/_matrix/client/r0/rooms/' . $opt->{room} . '/send/m.room.message?access_token=' . $opt->{access_token};
|
|
$req = HTTP::Request->new( 'POST', $uri );
|
|
my $json = {
|
|
msgtype => 'm.file',
|
|
body => basename($opt->{file}),
|
|
filename => basename($opt->{file}),
|
|
info => {
|
|
mimetype => mimetype $opt->{file},
|
|
size => (stat $opt->{file})[7]
|
|
},
|
|
url => $file_uri
|
|
};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
$resp = $lwp->request( $req );
|
|
if ($opt->{debug}){
|
|
print "Posting file link to the room reseponse is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error posting file link on room $opt->{room}\n";
|
|
}
|
|
}
|
|
|
|
# List public rooms
|
|
# Note that there's no pagination handling yet, so you might not have all the results
|
|
sub list_room {
|
|
if ($opt->{debug}){
|
|
print "Fetching list of public rooms on $opt->{server}\n";
|
|
}
|
|
my $uri = $opt->{server} . '/_matrix/client/r0/publicRooms?access_token=' . $opt->{access_token};
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
my $json = {};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
my $resp = $lwp->request( $req );
|
|
unless ( $resp->is_success ){
|
|
die "Error joining room $opt->{room}\n";
|
|
}
|
|
if ($opt->{debug}){
|
|
print "List rooms response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
print "Existing Rooms:\n";
|
|
foreach (@{from_json($resp->decoded_content)->{chunk}}){
|
|
print " * " . $_->{room_id};
|
|
print ' (' . $_->{canonical_alias} . ')' if (defined $_->{canonical_alias});
|
|
print "\n";
|
|
}
|
|
}
|
|
|
|
# Create a new room
|
|
sub create_room {
|
|
if ($opt->{debug}){
|
|
print "Creating a new room on $opt->{server}\n";
|
|
}
|
|
my $uri = $opt->{server} . '/_matrix/client/r0/createRoom?access_token=' . $opt->{access_token};
|
|
my $req = HTTP::Request->new( 'POST', $uri );
|
|
my $json = {};
|
|
$json->{room_alias_name} = $opt->{alias} if $opt->{alias};
|
|
$json->{topic} = $opt->{topic} if $opt->{topic};
|
|
$json->{name} = $opt->{name} if $opt->{name};
|
|
$json->{invite} = $opt->{invite} if $opt->{invite};
|
|
$json->{preset} = $opt->{preset} if $opt->{preset};
|
|
$json->{visibility} = $opt->{visibility} if $opt->{visbility};
|
|
$json->{creation_content}->{'m.federate'} = $opt->{federation};
|
|
$req->header( 'Content-Type' => 'application/json' );
|
|
$req->content( to_json($json) );
|
|
my $resp = $lwp->request( $req );
|
|
if ($opt->{debug}){
|
|
print "Room creation response is\n" .
|
|
to_json(from_json($resp->decoded_content), { pretty => 1 }) .
|
|
"\n\n";
|
|
}
|
|
unless ( $resp->is_success ){
|
|
die "Error creating room on $opt->{server}\n";
|
|
}
|
|
my $room_id = from_json($resp->decoded_content)->{room_id};
|
|
print "$room_id\n";
|
|
}
|
|
|
|
# Should we logout at the end ? Only if we used login and pass
|
|
# If we used an access_token, we don't want it to be invalidated
|
|
my $must_logout = ($opt->{access_token}) ? 0 : 1;
|
|
|
|
# If we don't have an access token, we must get one now
|
|
if (!$opt->{access_token}){
|
|
login();
|
|
}
|
|
if ($opt->{action} eq 'get-access-token'){
|
|
print $opt->{access_token} . "\n";
|
|
}
|
|
elsif ($opt->{action} eq 'get-room-list'){
|
|
list_room();
|
|
}
|
|
elsif ($opt->{action} =~ m/send\-(msg|message|notice)/){
|
|
join_room();
|
|
send_msg();
|
|
}
|
|
elsif ($opt->{action} eq 'send-file'){
|
|
join_room();
|
|
send_file();
|
|
}
|
|
elsif ($opt->{action} eq 'create-room'){
|
|
create_room();
|
|
}
|
|
|
|
logout() if $must_logout;
|
|
|
|
exit(0);
|
|
|