From 7be9fa68bef82a7576344a6e8cc1e51154c6b3bf Mon Sep 17 00:00:00 2001 From: Matias Linares Date: Mon, 11 Jan 2021 22:20:09 -0300 Subject: Response & Exception refactor --- lib/Matrix/Client/Common.pm6 | 3 - lib/Matrix/Client/Common.rakumod | 3 + lib/Matrix/Client/Exception.pm6 | 16 --- lib/Matrix/Client/Exception.rakumod | 17 ++++ lib/Matrix/Client/MediaStore.rakumod | 6 +- lib/Matrix/Client/Requester.pm6 | 115 --------------------- lib/Matrix/Client/Requester.rakumod | 115 +++++++++++++++++++++ lib/Matrix/Client/Response.rakumod | 156 ++++++++++++++++++++++++++++ lib/Matrix/Client/Room.pm6 | 190 ----------------------------------- lib/Matrix/Client/Room.rakumod | 190 +++++++++++++++++++++++++++++++++++ 10 files changed, 484 insertions(+), 327 deletions(-) delete mode 100644 lib/Matrix/Client/Common.pm6 create mode 100644 lib/Matrix/Client/Common.rakumod delete mode 100644 lib/Matrix/Client/Exception.pm6 create mode 100644 lib/Matrix/Client/Exception.rakumod delete mode 100644 lib/Matrix/Client/Requester.pm6 create mode 100644 lib/Matrix/Client/Requester.rakumod create mode 100644 lib/Matrix/Client/Response.rakumod delete mode 100644 lib/Matrix/Client/Room.pm6 create mode 100644 lib/Matrix/Client/Room.rakumod (limited to 'lib/Matrix/Client') diff --git a/lib/Matrix/Client/Common.pm6 b/lib/Matrix/Client/Common.pm6 deleted file mode 100644 index 83cf65d..0000000 --- a/lib/Matrix/Client/Common.pm6 +++ /dev/null @@ -1,3 +0,0 @@ -unit module Matrix::Client::Common; - -our $TXN-ID; diff --git a/lib/Matrix/Client/Common.rakumod b/lib/Matrix/Client/Common.rakumod new file mode 100644 index 0000000..83cf65d --- /dev/null +++ b/lib/Matrix/Client/Common.rakumod @@ -0,0 +1,3 @@ +unit module Matrix::Client::Common; + +our $TXN-ID; diff --git a/lib/Matrix/Client/Exception.pm6 b/lib/Matrix/Client/Exception.pm6 deleted file mode 100644 index e02f572..0000000 --- a/lib/Matrix/Client/Exception.pm6 +++ /dev/null @@ -1,16 +0,0 @@ -package X::Matrix { - class Response is Exception { - has $.code; - has $.error; - - method message { - "$!code: $!error" - } - } - - class MXCParse is Exception { - has $.uri; - - method message { "Cannot parse '$!uri'" } - } -} diff --git a/lib/Matrix/Client/Exception.rakumod b/lib/Matrix/Client/Exception.rakumod new file mode 100644 index 0000000..6fb4c75 --- /dev/null +++ b/lib/Matrix/Client/Exception.rakumod @@ -0,0 +1,17 @@ +module Matrix::Client::Exception { + + class X::Matrix::Response is Exception { + has $.code; + has $.error; + + method message(--> Str) { + "$!code: $!error" + } + } + + class X::Matrix::MXCParse is Exception { + has $.uri; + + method message { "Cannot parse '$!uri'" } + } +} diff --git a/lib/Matrix/Client/MediaStore.rakumod b/lib/Matrix/Client/MediaStore.rakumod index 9779db2..afea9e5 100644 --- a/lib/Matrix/Client/MediaStore.rakumod +++ b/lib/Matrix/Client/MediaStore.rakumod @@ -3,7 +3,7 @@ use URI::Escape; use Matrix::Client::Requester; use Matrix::Client::Exception; -use Matrix::Response; +use Matrix::Client::Response; unit class Matrix::Client::MediaStore does Matrix::Client::Requester; @@ -110,7 +110,7 @@ multi method thumbnail(Str $server-name, Str $media-id, } #| GET - /_matrix/media/r0/config -method config(--> Matrix::Response::MediaStore::Config) { +method config(--> Matrix::Client::Response::MediaStore::Config) { my $response = $.get("/config"); - Matrix::Response::MediaStore::Config.new(from-json($response.content)) + Matrix::Client::Response::MediaStore::Config.new(from-json($response.content)) } diff --git a/lib/Matrix/Client/Requester.pm6 b/lib/Matrix/Client/Requester.pm6 deleted file mode 100644 index 36a9f69..0000000 --- a/lib/Matrix/Client/Requester.pm6 +++ /dev/null @@ -1,115 +0,0 @@ -use HTTP::UserAgent; -use HTTP::Request::Common; -use URI::Encode; -use JSON::Fast; -use Matrix::Client::Exception; - -unit role Matrix::Client::Requester; - -has $.home-server is required; -has $.access-token = ""; - -has $!ua = HTTP::UserAgent.new; -has $!client-endpoint = "/_matrix/client/r0"; -has $!url-prefix = ""; -has $!sync-since = ""; - -method !handle-error($response) is hidden-from-backtrace { - unless $response.is-success { - my $data = from-json($response.content); - X::Matrix::Response.new(:code($data), :error($data)).throw; - } - $response -} - -method !access-token-arg { - $!access-token ?? "access_token=$!access-token" !! '' -} - -method get(Str $path, :$media = False, *%data) { - my $query = "?"; - for %data.kv -> $k,$v { - $query ~= "&$k=$v" if $v.so; - } - my $encoded-path = $path.subst('#', '%23'); - my $uri = $.base-url(:$media) ~ $encoded-path ~ uri_encode($query); - - my $req = HTTP::Request.new(GET => $uri); - - if $!access-token.so { - $req.header.field(Authorization => "Bearer {$!access-token}"); - } - - return self!handle-error( - $!ua.request($req) - ); -} - -method base-url(Bool :$media? = False --> Str) { - if !$media { - "$.home-server$!client-endpoint$!url-prefix" - } else { - "$.home-server/_matrix/media/r0" - } -} - -multi method post(Str $path, Str $json, :$media = False) { - my $encoded-path = $path.subst('#', '%23'); - my $url = $.base-url(:$media) ~ $encoded-path; - my $req = HTTP::Request.new(POST => $url, - Content-Type => 'application/json'); - if $!access-token.so { - $req.header.field(Authorization => "Bearer {$!access-token}"); - } - $req.add-content($json); - return self!handle-error($!ua.request($req)); -} - -multi method post(Str $path, :$media = False, *%params) { - my $json = to-json(%params); - $.post($path, $json, :$media) -} - -method post-bin(Str $path, Buf $buf, :$content-type) { - my $encoded-path = $path.subst('#', '%23'); - my $req = POST( - $.base-url() ~ $encoded-path, - content => $buf, - Content-Type => $content-type - ); - - if $!access-token.so { - $req.header.field(Authorization => "Bearer {$!access-token}"); - } - - return self!handle-error($!ua.request($req)); -} - -multi method put(Str $path, Str $json) { - my $encoded-path = $path.subst('#', '%23'); - my $req = HTTP::Request.new(PUT => $.base-url() ~ $encoded-path, - Content-Type => 'application/json'); - if $!access-token.so { - $req.header.field(Authorization => "Bearer {$!access-token}"); - } - - $req.add-content($json); - return self!handle-error($!ua.request($req)) -} - -multi method put(Str $path, *%params) { - self.put($path, to-json(%params)) -} - -method delete(Str $path) { - my $encoded-path = $path.subst('#', '%23'); - my $req = HTTP::Request.new( - DELETE => $.base-url ~ $encoded-path, - Content-Type => 'application/json'); - if $!access-token.so { - $req.header.field( - Authorization => "Bearer $!access-token" - ); - } - return self!handle-error($!ua.request($req)) -} diff --git a/lib/Matrix/Client/Requester.rakumod b/lib/Matrix/Client/Requester.rakumod new file mode 100644 index 0000000..36a9f69 --- /dev/null +++ b/lib/Matrix/Client/Requester.rakumod @@ -0,0 +1,115 @@ +use HTTP::UserAgent; +use HTTP::Request::Common; +use URI::Encode; +use JSON::Fast; +use Matrix::Client::Exception; + +unit role Matrix::Client::Requester; + +has $.home-server is required; +has $.access-token = ""; + +has $!ua = HTTP::UserAgent.new; +has $!client-endpoint = "/_matrix/client/r0"; +has $!url-prefix = ""; +has $!sync-since = ""; + +method !handle-error($response) is hidden-from-backtrace { + unless $response.is-success { + my $data = from-json($response.content); + X::Matrix::Response.new(:code($data), :error($data)).throw; + } + $response +} + +method !access-token-arg { + $!access-token ?? "access_token=$!access-token" !! '' +} + +method get(Str $path, :$media = False, *%data) { + my $query = "?"; + for %data.kv -> $k,$v { + $query ~= "&$k=$v" if $v.so; + } + my $encoded-path = $path.subst('#', '%23'); + my $uri = $.base-url(:$media) ~ $encoded-path ~ uri_encode($query); + + my $req = HTTP::Request.new(GET => $uri); + + if $!access-token.so { + $req.header.field(Authorization => "Bearer {$!access-token}"); + } + + return self!handle-error( + $!ua.request($req) + ); +} + +method base-url(Bool :$media? = False --> Str) { + if !$media { + "$.home-server$!client-endpoint$!url-prefix" + } else { + "$.home-server/_matrix/media/r0" + } +} + +multi method post(Str $path, Str $json, :$media = False) { + my $encoded-path = $path.subst('#', '%23'); + my $url = $.base-url(:$media) ~ $encoded-path; + my $req = HTTP::Request.new(POST => $url, + Content-Type => 'application/json'); + if $!access-token.so { + $req.header.field(Authorization => "Bearer {$!access-token}"); + } + $req.add-content($json); + return self!handle-error($!ua.request($req)); +} + +multi method post(Str $path, :$media = False, *%params) { + my $json = to-json(%params); + $.post($path, $json, :$media) +} + +method post-bin(Str $path, Buf $buf, :$content-type) { + my $encoded-path = $path.subst('#', '%23'); + my $req = POST( + $.base-url() ~ $encoded-path, + content => $buf, + Content-Type => $content-type + ); + + if $!access-token.so { + $req.header.field(Authorization => "Bearer {$!access-token}"); + } + + return self!handle-error($!ua.request($req)); +} + +multi method put(Str $path, Str $json) { + my $encoded-path = $path.subst('#', '%23'); + my $req = HTTP::Request.new(PUT => $.base-url() ~ $encoded-path, + Content-Type => 'application/json'); + if $!access-token.so { + $req.header.field(Authorization => "Bearer {$!access-token}"); + } + + $req.add-content($json); + return self!handle-error($!ua.request($req)) +} + +multi method put(Str $path, *%params) { + self.put($path, to-json(%params)) +} + +method delete(Str $path) { + my $encoded-path = $path.subst('#', '%23'); + my $req = HTTP::Request.new( + DELETE => $.base-url ~ $encoded-path, + Content-Type => 'application/json'); + if $!access-token.so { + $req.header.field( + Authorization => "Bearer $!access-token" + ); + } + return self!handle-error($!ua.request($req)) +} diff --git a/lib/Matrix/Client/Response.rakumod b/lib/Matrix/Client/Response.rakumod new file mode 100644 index 0000000..3836a20 --- /dev/null +++ b/lib/Matrix/Client/Response.rakumod @@ -0,0 +1,156 @@ +use JSON::Fast; + +unit module Matrix::Client::Response; + +class Event { + has %.content; + has $.type is required; +} + +class RoomEvent is Event { + has Str $.sender; + has Int $.origin_server_ts; + has $.event_id; + has Str $.room_id; + + method id { $.event_id } + method timestamp { $!origin_server_ts } + method room-id { $.room_id } +} + +class StateEvent is RoomEvent { + has $.prev_content; + has $.state_key; +} + +class MemberEvent is StateEvent { + has $.type is required where 'm.room.member'; +} + +class Timeline { + has Event @.events; + has Bool $limited; + has Str $prev-batch; +} + +class RoomInfo { + has $.room-id is required; + has Event @.state; + has Timeline $.timeline; + + method gist(--> Str) { + "" + } +} + +class InviteInfo { + has $.room-id is required; + has Event @.events; + + method gist(--> Str) { + "" + } +} + +sub gather-events($room-id, $from) { + gather for $from.List -> $ev { + take StateEvent.new(:room_id($room-id), |$ev); + } +} + +class Messages { + has $.start; + has $.end; + has RoomEvent @.messages; +} + +class Sync { + has Str $.next-batch; + has Event @.presence; + has RoomInfo @.joined-rooms; + has InviteInfo @.invited-rooms; + + multi method new(Str $json) { + return self.new(from-json($json)); + } + + multi method new(Hash $json) { + my $next-batch = $json; + my Event @presence; + my RoomInfo @joined-rooms; + my InviteInfo @invited-rooms; + + for $json.List -> $ev { + @presence.push(Event.new(|$ev)); + } + + for $json.kv -> $room-id, $data { + my @state = gather-events($room-id, $data); + + my $timeline = Timeline.new( + limited => $data, + prev-batch => $data, + events => gather-events($room-id, $data) + ); + + @joined-rooms.push(RoomInfo.new( + :$room-id, :$timeline, :@state + )); + } + + for $json.kv -> $room-id, $data { + my @events = gather-events($room-id, $data); + @invited-rooms.push(InviteInfo.new( + :$room-id, :@events + )); + } + + return self.bless(:$next-batch, :@presence, + :@joined-rooms, :@invited-rooms); + } +} + +class Presence { + has Str $.presence is required; + has Int $.last-active-ago; + has Str $.status-message; + has Bool $.currently-active; + + submethod BUILD( + Str :$!presence, + :last_active_ago(:$!last-active-ago) = 0, + :status_message(:$!status-message) = "", + :currently_active(:$!currently-active) = False + ) { } +} + +class Tag { + has @.tags; + + method new(%json) { + my @tags = %json.keys; + self.bless(:@tags) + } +} + +class Device { + has Str $.device-id; + has $.display-name; + has $.last-seen-ip; + has $.last-seen-ts; + + submethod BUILD( + Str :device_id(:$!device-id), + :display_name(:$!display-name)?, + :last_seen_ip(:$!last-seen-ip)?, + :last_seen_ts(:$!last-seen-ts)? + ) { } +} + +class MediaStore::Config { + has Int $.upload-size; + + method new(%config) { + self.bless(:upload-size(%config // Int)); + } +} diff --git a/lib/Matrix/Client/Room.pm6 b/lib/Matrix/Client/Room.pm6 deleted file mode 100644 index 3d6ecaa..0000000 --- a/lib/Matrix/Client/Room.pm6 +++ /dev/null @@ -1,190 +0,0 @@ -use JSON::Fast; -use Matrix::Client::Common; -use Matrix::Client::Requester; -use Matrix::Response; - -unit class Matrix::Client::Room does Matrix::Client::Requester; - -has $!name; -has $.id; - -submethod TWEAK { - $!url-prefix = "/rooms/$!id"; -} - -method !get-name() { - CATCH { - when X::Matrix::Response { - .code ~~ /M_NOT_FOUND/ - ?? ($!name = '') - !! fail - } - default { fail } - } - my $data = $.state('m.room.name'); - $!name = $data; -} - -method name(--> Str) { - self!get-name; - $!name -} - -method fallback-name(--> Str) { - my @members = $.joined-members.kv.map( - -> $k, %v { - %v // $k - } - ); - - $!name = do given @members.elems { - when 1 { @members.first } - when 2 { @members[0] ~ " and " ~ @members[1] } - when * > 2 { @members.first ~ " and {@members.elems - 1} others" } - default { "Empty room" } - }; -} - -#| GET - /_matrix/client/r0/rooms/{roomId}/aliases -method aliases(--> List) { - my %data = from-json($.get('/aliases').content); - %data.List -} - -# Events - -## Getting events for a room - -#| GET - /_matrix/client/r0/rooms/{roomId}/event/{eventId} -method event(Str $event-id --> Matrix::Response::RoomEvent) { - my %data = from-json($.get("/event/$event-id").content); - Matrix::Response::RoomEvent.new(|%data) -} - -#| GET - /_matrix/client/r0/rooms/{roomId}/state -multi method state(--> Seq) { - my $data = from-json($.get('/state').content); - - gather for $data.List -> $event { - take Matrix::Response::StateEvent.new(:room-id($.id), |$event) - } -} - -#| GET - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey} -multi method state(Str $event-type, Str $state-key = "") { - from-json($.get("/state/$event-type/$state-key").content) -} - -#| GET - /_matrix/client/r0/rooms/{roomId}/joined_members -method joined-members { - my %data = from-json($.get("/joined_members").content); - %data -} - -#| GET - /_matrix/client/r0/rooms/{roomId}/messages -method messages( - Str:D :$from!, Str :$to, - Str :$dir where * eq 'f'|'b' = 'f', - Int :$limit = 10, :%filter, - --> Matrix::Response::Messages -) { - my $res = $.get( - "/messages", :$from, :$to, :$dir, :$limit, :%filter - ); - my $data = from-json($res.content); - - - my @messages = $data.map(-> $ev { - Matrix::Response::RoomEvent.new(|$ev) - }); - - Matrix::Response::Messages.new( - start => $data, - end => $data, - messages => @messages - ) -} - -#| GET - /_matrix/client/r0/rooms/{roomId}/members -method members(:$at, Str :$membership, Str :$not-membership --> Seq) { - my %query; - - %query = $at with $at; - %query = $membership with $membership; - %query = $not-membership with $not-membership; - - my %data = from-json($.get('/members', |%query).content); - - gather for %data.List -> $ev { - take Matrix::Response::MemberEvent.new(|$ev) - } -} - -## Sending events to a room - -#| PUT - /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId} -method send(Str $body!, Str :$type? = "m.text") { - $Matrix::Client::Common::TXN-ID++; - my $res = $.put( - "/send/m.room.message/{$Matrix::Client::Common::TXN-ID}", - msgtype => $type, body => $body - ); - - from-json($res.content) -} - -#| PUT - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey} -method send-state(Str:D $event-type, :$state-key = "", *%args --> Str) { - my $res = $.put( - "/state/$event-type/$state-key", - |%args - ); - from-json($res.content) -} - -# Room membership! - -## Joining rooms - -#| POST - /_matrix/client/r0/rooms/{roomId}/invite -method invite(Str $user-id) { - $.post('/invite', :$user-id) -} - -#| POST - /_matrix/client/r0/rooms/{roomId}/join -method join { - $.post('/join') -} - -## Leaving rooms - -#| POST - /_matrix/client/r0/rooms/{roomId}/leave -method leave { - $.post('/leave') -} - -#| POST - /_matrix/client/r0/rooms/{roomId}/forget -method forget { - $.post('/forget') -} - -#| POST - /_matrix/client/r0/rooms/{roomId}/kick -method kick(Str $user-id, Str $reason = "") { - $.post('/kick', :$user-id, :$reason) -} - -## Banning users - -#| POST - /_matrix/client/r0/rooms/{roomId}/ban -method ban(Str $user-id, $reason = "") { - $.post('/ban', :$user-id, :$reason) -} - -#| POST - /_matrix/client/r0/rooms/{roomId}/unban -method unban(Str $user-id) { - $.post('/unban', :$user-id) -} - -method Str(--> Str) { - "Room" -} diff --git a/lib/Matrix/Client/Room.rakumod b/lib/Matrix/Client/Room.rakumod new file mode 100644 index 0000000..1e8c582 --- /dev/null +++ b/lib/Matrix/Client/Room.rakumod @@ -0,0 +1,190 @@ +use JSON::Fast; +use Matrix::Client::Common; +use Matrix::Client::Requester; +use Matrix::Client::Response; + +unit class Matrix::Client::Room does Matrix::Client::Requester; + +has $!name; +has $.id; + +submethod TWEAK { + $!url-prefix = "/rooms/$!id"; +} + +method !get-name() { + CATCH { + when X::Matrix::Response { + .code ~~ /M_NOT_FOUND/ + ?? ($!name = '') + !! fail + } + default { fail } + } + my $data = $.state('m.room.name'); + $!name = $data; +} + +method name(--> Str) { + self!get-name; + $!name +} + +method fallback-name(--> Str) { + my @members = $.joined-members.kv.map( + -> $k, %v { + %v // $k + } + ); + + $!name = do given @members.elems { + when 1 { @members.first } + when 2 { @members[0] ~ " and " ~ @members[1] } + when * > 2 { @members.first ~ " and {@members.elems - 1} others" } + default { "Empty room" } + }; +} + +#| GET - /_matrix/client/r0/rooms/{roomId}/aliases +method aliases(--> List) { + my %data = from-json($.get('/aliases').content); + %data.List +} + +# Events + +## Getting events for a room + +#| GET - /_matrix/client/r0/rooms/{roomId}/event/{eventId} +method event(Str $event-id --> Matrix::Client::Response::RoomEvent) { + my %data = from-json($.get("/event/$event-id").content); + Matrix::Client::Response::RoomEvent.new(|%data) +} + +#| GET - /_matrix/client/r0/rooms/{roomId}/state +multi method state(--> Seq) { + my $data = from-json($.get('/state').content); + + gather for $data.List -> $event { + take Matrix::Client::Response::StateEvent.new(:room-id($.id), |$event) + } +} + +#| GET - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey} +multi method state(Str $event-type, Str $state-key = "") { + from-json($.get("/state/$event-type/$state-key").content) +} + +#| GET - /_matrix/client/r0/rooms/{roomId}/joined_members +method joined-members { + my %data = from-json($.get("/joined_members").content); + %data +} + +#| GET - /_matrix/client/r0/rooms/{roomId}/messages +method messages( + Str:D :$from!, Str :$to, + Str :$dir where * eq 'f'|'b' = 'f', + Int :$limit = 10, :%filter, + --> Matrix::Client::Response::Messages +) { + my $res = $.get( + "/messages", :$from, :$to, :$dir, :$limit, :%filter + ); + my $data = from-json($res.content); + + + my @messages = $data.map(-> $ev { + Matrix::Client::Response::RoomEvent.new(|$ev) + }); + + Matrix::Client::Response::Messages.new( + start => $data, + end => $data, + messages => @messages + ) +} + +#| GET - /_matrix/client/r0/rooms/{roomId}/members +method members(:$at, Str :$membership, Str :$not-membership --> Seq) { + my %query; + + %query = $at with $at; + %query = $membership with $membership; + %query = $not-membership with $not-membership; + + my %data = from-json($.get('/members', |%query).content); + + gather for %data.List -> $ev { + take Matrix::Client::Response::MemberEvent.new(|$ev) + } +} + +## Sending events to a room + +#| PUT - /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId} +method send(Str $body!, Str :$type? = "m.text") { + $Matrix::Client::Common::TXN-ID++; + my $res = $.put( + "/send/m.room.message/{$Matrix::Client::Common::TXN-ID}", + msgtype => $type, body => $body + ); + + from-json($res.content) +} + +#| PUT - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey} +method send-state(Str:D $event-type, :$state-key = "", *%args --> Str) { + my $res = $.put( + "/state/$event-type/$state-key", + |%args + ); + from-json($res.content) +} + +# Room membership! + +## Joining rooms + +#| POST - /_matrix/client/r0/rooms/{roomId}/invite +method invite(Str $user-id) { + $.post('/invite', :$user-id) +} + +#| POST - /_matrix/client/r0/rooms/{roomId}/join +method join { + $.post('/join') +} + +## Leaving rooms + +#| POST - /_matrix/client/r0/rooms/{roomId}/leave +method leave { + $.post('/leave') +} + +#| POST - /_matrix/client/r0/rooms/{roomId}/forget +method forget { + $.post('/forget') +} + +#| POST - /_matrix/client/r0/rooms/{roomId}/kick +method kick(Str $user-id, Str $reason = "") { + $.post('/kick', :$user-id, :$reason) +} + +## Banning users + +#| POST - /_matrix/client/r0/rooms/{roomId}/ban +method ban(Str $user-id, $reason = "") { + $.post('/ban', :$user-id, :$reason) +} + +#| POST - /_matrix/client/r0/rooms/{roomId}/unban +method unban(Str $user-id) { + $.post('/unban', :$user-id) +} + +method Str(--> Str) { + "Room" +} -- cgit v1.2.3-70-g09d2