patrix is a simple perl based command line client for Matrix
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.

256 lines
7.6 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 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},
"notice" => \$opt->{notice},
"files=s" => \$opt->{file},
"debug" => \$opt->{debug},
"action=s" => \$opt->{action},
"conf=s" => \$opt->{conf}
);
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();
}
if (!-t STDIN){
$opt->{message} = '';
if ($opt->{debug}){
print "Reading message from stdin. --message will be ignored\n\n";
}
while (<STDIN>){
$opt->{message} .= $_;
}
}
# Set defaults
$opt->{server} //= 'matrix.org';
$opt->{action} //= 'send-msg';
# 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;
# 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 "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})){
die "You need to provide a room ID and a message";
}
if ($opt->{action} eq 'send-file' && (!$opt->{room} || !$opt->{file})){
die "You need to provide a room ID and a file to send\n";
}
$opt->{server} = 'https://' . $opt->{server} unless ($opt->{server} =~ m|https?://|);
my $lwp = LWP::UserAgent->new;
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)
}
}
}
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};
}
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";
}
}
sub join_room(){
if ($opt->{debug}){
print "Trying to join room $opt->{room}\n";
}
my $uri = $opt->{server} . '/_matrix/client/r0/rooms/' . $opt->{room} . '/join?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->{roomt} = $room_id if $room_id;
}
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 );
my $json = {
msgtype => ($opt->{notice}) ? 'm.notice' : 'm.text',
body => $opt->{message}
};
$req->header( 'Content-Type' => 'application/json' );
$req->content( to_json($json) );
my $resp = $lwp->request( $req );
}
sub send_file(){
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} );
$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";
}
my $file_uri = from_json($resp->decoded_content)->{content_uri};
unless ($file_uri){
die "Error occurred during file upload\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";
}
$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 );
unless ( $resp->is_success ){
die "Error posting file link on room $opt->{room}\n";
}
}
sub list_room(){
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";
}
}
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} eq 'send-msg'){
join_room();
send_msg();
}
elsif ($opt->{action} eq 'send-file'){
join_room();
send_file();
}
logout() if $must_logout;
exit(0);