diff --git a/README.md b/README.md index 27d9ca4..68843b4 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ It requires the following perl modules * URI::Escape * JSON * Term::ReadKey + * Hash::Merge::Simple + * Scalar::Util Here're the vailable options: @@ -29,6 +31,10 @@ Here're the vailable options: * --topic: set the topic of a room. Valid for create-room and modify-room * --alias: set an alias for a room. Valid for create-room and modify-room * --join_rules: change joining rules. Can be either public (anyone can join the room) or invite (you must be invited to join the room) + * --perm: set power levels on the room. Can be specified several times. See examples + * --perm_user: set user levels on the room. Can be specified several times. See examples + * --perm_event: set power levels requires to send specific state events. Can be specified several times. See examples + * --perm_reset: the default behavior of the various --perm args is to add or override specific permissions without changing the others already existing permission. If this flag is set, the previous permissions will be removed, and the one specified with the --perm arg will be applied. The only exception is for user power levels which are at least as high as the operator (including the operator). These user power levels will be kept even is --perm-reset is set * --federation: Enable the federation when creating a room. Default is enabled. Can be turned of with --no-federation * --action: what to do. Valid actions are * send-msg (default): send the text message @@ -53,18 +59,35 @@ Options given on the command line take precedence over the config file Examples: + * Send the content of /var/log/boot.log to a room (as text) ``` cat /var/log/boot.log | patrix --room='#bootlogs:matrix.domain.com' --action=send-notice ``` + * Send a file (here, the room name must be specified in the config file) ``` patrix --action=send-file --file=/home/dani/archive.tgz --user=dani --password=secret --server=matrix.domain.com ``` + * Send a simple text message, and enable debuging ``` patrix --debug --message="Hello World" ``` + * Create a new room, set its name and invite a Matrix user ``` patrix --action=create-room --name="Human readable room name" --invite="@dani:matrix.example.com" ``` + * Configure an existing room ``` patrix --action=modify-room --join_rules=public --topic='New topic' --room='!uXfknaWNcAnvthnIms:matrix.example.com' --invite='@admin:matrix.example.com' ``` + * Change power level needed for the ban action. Set the default power levels of new users to 10. Set power level for @dani:matrix.example.com to 90 +``` +patrix --action=modify-room --perm "ban=70" --perm "users_default=10" --perm_user "@dani:matrix.example.com=90" +``` + * Set the required power level to send the m.room.name event to 80 (you can change the room name if you have a power level of at least 80) +``` +patrix --action=modify-room --perm_event "m.room.name=80" +``` + * Reset permissions. Only keep user power levels which are at least the same as yours (including yours) +``` +patrix --action=modify-room --perm_reset +``` diff --git a/TODO b/TODO index 2441c14..fb478cb 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* Set power levels * Purge history of a room * Purge history of all empty rooms (with no members) * Modify federation setting of a room diff --git a/patrix.spec b/patrix.spec index 887fc5c..ccf1d09 100644 --- a/patrix.spec +++ b/patrix.spec @@ -20,6 +20,8 @@ Requires: perl(File::MimeInfo) Requires: perl(Path::Tiny) Requires: perl(URI::Escape) Requires: perl(Term::ReadKey) +Requires: perl(Hash::Merge::Simple) +Requires: perl(Scalar::Util) %description Patrix is a simple (and quite limited) client for the Matrix communication network diff --git a/scripts/patrix b/scripts/patrix index 3c5f43f..27bcf69 100644 --- a/scripts/patrix +++ b/scripts/patrix @@ -13,7 +13,8 @@ use File::Basename; use URI::Escape; use Path::Tiny; use Term::ReadKey; -use Data::Dumper; +use Hash::Merge::Simple qw(merge); +use Scalar::Util qw(looks_like_number); our $opt; @@ -33,7 +34,11 @@ GetOptions( "alias=s" => \$opt->{alias}, "topic=s" => \$opt->{topic}, "join_rules=s" => \$opt->{join_rules}, - "federation!" => \$opt->{federation} + "federation!" => \$opt->{federation}, + "perm=s@" => \$opt->{perm}, + "perm_user=s@" => \$opt->{perm_user}, + "perm_event=s@" => \$opt->{perm_user}, + "perm_reset" => \$opt->{perm_reset} ); if (-e File::HomeDir->my_home . "/.patrixrc" && !$opt->{conf}){ @@ -58,7 +63,9 @@ $opt->{action} //= 'send-msg'; $opt->{federation} //= 1; $opt->{server} = 'https://' . $opt->{server} unless ($opt->{server} =~ m|https?://|); -# Prompt to enter the password +# No password on the command line or the conf file +# And no access token +# Prompt to type the password if (!$opt->{access_token} && $opt->{user} && !$opt->{password}){ ReadMode('noecho'); print "Password: "; @@ -68,6 +75,8 @@ if (!$opt->{access_token} && $opt->{user} && !$opt->{password}){ print "\n"; } +# If the given room starts with #, then it's an alias +# Lets resolve this to the room ID if ($opt->{room} && $opt->{room} =~ m/^#/){ $opt->{room} = room_alias_to_id($opt->{room}); debug('Room ID is ' . $opt->{room}); @@ -178,6 +187,27 @@ sub join_room { die "Error joining room $opt->{room}\n" unless ($resp->is_success); } +# Retrieve the actual permissions for a room +sub get_room_permissions { + debug('Getting actual room state'); + my $uri = $opt->{server} . '/_matrix/client/r0/rooms/' . $opt->{room} . '/state/m.room.power_levels?access_token=' . $opt->{access_token}; + my $resp = send_request({ + method => 'GET', + uri => $uri + }); + die "Error joining room $opt->{room}\n" unless ($resp->is_success); + return from_json($resp->decoded_content); +} + +# Return the user ID of the operator +sub who_am_i { + # We could get user_id if we login with user/pass but what if we use an access token ? + # Lets just build it manually for now + my $server = $opt->{server}; + $server =~ s|^https?://||; + return '@' . $opt->{user} .':' . $server; +} + # 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}; @@ -336,6 +366,67 @@ sub modify_room { die "Error changing joining rules of room $opt->{room}\n" unless ($resp->is_success); } + # Permissions modification + if ($opt->{perm} || $opt->{perm_user} || $opt->{perm_event} || $opt->{perm_reset}){ + debug('Changing permissions for the room'); + my $current_perm = get_room_permissions(); + # If we asked to reset the permission + if ($opt->{perm_reset}){ + my $operator = who_am_i(); + my $reset_perm = { + events => { + "m.room.avatar" => 50, + "m.room.canonical_alias" => 50, + "m.room.name" => 50, + "m.room.power_levels" => 100, + "m.room.history_visibility" => 100 + }, + }; + # Ensure we keep at least the permission of the operating user + # Note that we must also keep the permission of anyone who has at least the same level + # of privilege, or the operation will be forbidden + foreach my $user (keys %{$current_perm->{users}}){ + if (looks_like_number($current_perm->{users}->{$user}) && + $current_perm->{users}->{$user} >= $current_perm->{users}->{$operator}){ + debug("Keeping permission of $user because it has at least the same privileges " . + "($current_perm->{users}->{$user} vs $current_perm->{users}->{$operator})"); + $reset_perm->{users}->{$user} = $current_perm->{users}->{$user}; + } + } + $current_perm = $reset_perm; + } + my $new_perm = {}; + if ($opt->{perm}){ + foreach my $perm (@{$opt->{perm}}){ + my ($key,$val) = split (/\s*=\s*/, $perm); + $new_perm->{$key} = $val; + } + } + if ($opt->{perm_user}){ + foreach my $perm (@{$opt->{perm_user}}){ + my ($key,$val) = split (/\s*=\s*/, $perm); + # Prevent the operating user to downgrade its own permissions + next if ($key eq $opt->{user}); + $new_perm->{users}->{$key} = $val; + } + } + if ($opt->{perm_event}){ + foreach my $perm (@{$opt->{perm_event}}){ + my ($key,$val) = split (/\s*=\s*/, $perm); + $new_perm->{events}->{$key} = $val; + } + } + my $perm = merge($current_perm, $new_perm); + print to_json($perm, { pretty => 1 }); + $uri = $opt->{server} . '/_matrix/client/r0/rooms/' . $opt->{room} . '/state/m.room.power_levels?access_token=' . $opt->{access_token}; + $resp = send_request({ + method => 'PUT', + uri => $uri, + content => to_json($perm) + }); + die "Error changing permissions for room $opt->{room}\n" + unless ($resp->is_success); + } # New invitees should be added if ($opt->{invite}){ debug('Inviting ' . join(',', @{$opt->{invite}}) . ' to join the room');