aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatias Linares <matiaslina@gmail.com>2020-12-27 12:19:17 -0300
committerMatias Linares <matiaslina@gmail.com>2020-12-27 12:19:17 -0300
commit6883348dafc0e6c58a5478074221f8cca7859716 (patch)
tree8dd8db05b0f31f2bf3aef81426758b2b658e9f7f
parente1abad7a57fd34ffb55c1d57d0b89ee91e55296b (diff)
parentf1d1a526cc4950578e8769fe3c94147777466531 (diff)
downloadperl6-matrix-client-6883348dafc0e6c58a5478074221f8cca7859716.tar.gz
Merge branch 'master' into documentation
-rw-r--r--.github/workflows/main.yml35
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml9
-rw-r--r--META6.json3
-rw-r--r--docs/basics.pod63
-rw-r--r--docs/room.pod6113
-rw-r--r--endpoints.md39
-rw-r--r--lib/Matrix/Client.pm620
-rw-r--r--lib/Matrix/Client/Exception.pm66
-rw-r--r--lib/Matrix/Client/MediaStore.rakumod116
-rw-r--r--lib/Matrix/Client/Requester.pm62
-rw-r--r--lib/Matrix/Client/Room.pm6138
-rw-r--r--lib/Matrix/Response.pm619
-rwxr-xr-xscripts/load-docs.p617
-rw-r--r--t/10-use.t2
-rw-r--r--t/40-response.t44
-rw-r--r--t/60-media.t76
17 files changed, 558 insertions, 87 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..b568bda
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,35 @@
+# This is a basic workflow to help you get started with Actions
+
+name: Tests
+
+# Controls when the action will run. Triggers the workflow on push or pull request
+# events but only for the master branch
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "build"
+ test:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Run tests
+ id: tests
+ uses: Raku/setup-raku@v1
+ with:
+ raku-version: 'latest'
+ - name: Install Dependencies
+ run: zef install --deps-only --/test --test-depends .
+ - name: Install App::Prove6
+ run: zef install --/test App::Prove6
+ - name: Run Tests
+ run: prove6 -l t
diff --git a/.gitignore b/.gitignore
index 5bf717a..206dd0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ file.json
.precomp
**/*/.precomp
todo.org
-*~ \ No newline at end of file
+*~
+*.tar.gz \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 22775c4..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: perl6
-perl6:
- - latest
-install:
- - rakudobrew build zef
- - zef --debug --depsonly install .
-script:
- - prove -v -e 'perl6 -Ilib' t/
-sudo: false
diff --git a/META6.json b/META6.json
index bfac3e3..1ce78d4 100644
--- a/META6.json
+++ b/META6.json
@@ -18,6 +18,7 @@
"Matrix::Client::Common" : "lib/Matrix/Client/Common.pm6",
"Matrix::Client::Exception" : "lib/Matrix/Client/Exception.pm6",
"Matrix::Client::Requester" : "lib/Matrix/Client/Requester.pm6",
+ "Matrix::Client::MediaStore": "lib/Matrix/Client/MediaStore.rakumod",
"Matrix::Client::Room" : "lib/Matrix/Client/Room.pm6",
"Matrix::Response" : "lib/Matrix/Response.pm6"
},
@@ -34,5 +35,5 @@
"Test",
"Test::META"
],
- "version" : "0.5.1"
+ "version" : "0.6.1"
}
diff --git a/docs/basics.pod6 b/docs/basics.pod6
index 694983f..5770f4f 100644
--- a/docs/basics.pod6
+++ b/docs/basics.pod6
@@ -50,8 +50,7 @@ repeated data.
my $new-response = $client.sync(:$since);
There's a C<filter> argument that you could use to filter the response. To
-see available parameters to the filter you can go
-L<here|https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter>.
+see available parameters to the filter you can go L<here|https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter>.
# limit the messages per room to 1
diff --git a/docs/room.pod6 b/docs/room.pod6
index 9724793..f186583 100644
--- a/docs/room.pod6
+++ b/docs/room.pod6
@@ -34,16 +34,37 @@ without the need for a L<Matrix::Client>.
Returns the name of the room. If no C<m.room.name> was emmited (i.e.: no name
was set for this room), then an empty string is returned.
-=head2 send
+=head2 fallback-name
- method send(Str $body!, Str :$type? = "m.text")
+ method fallback-name(--> Str)
-Sends a message to the room. It will return the C<event_id> for this message.
+Return a name for the room with the members of the room. Use this if
+the room doesn't have a name set or in 1-1 chats.
+
+Example:
+
+ my $room = Matrix::Client::Room $room .= new(
+ :$access-token, :$home-server, :id('#34df12:matrix.org')
+ );
+ say 'Room name: ' ~ $room.name # OUTPUT: «Room name: »
+ say 'Room name: ' ~ $room.fallback-name # OUTPUT: «Room name: Alice and Bob»
+
+=head2 aliases
+
+ method aliases(--> List)
+
+Get a list of aliases maintained by the local server for the given room.
+
+=head2 event
+
+ method event(Str $event-id --> Matrix::Response::RoomEvent)
+
+Get a single event based on the C<$event-id>. Returns a L<Matrix::Response::RoomEvent>.
=head2 state
multi method state(--> Seq)
- multi method state(Str $event-type)
+ multi method state(Str $event-type, Str $state-key = "")
Get the state events for the current state of a room. it will return a C<Seq>
for every event on that room.
@@ -51,6 +72,52 @@ for every event on that room.
If an C<$event-type> is passed, the return value will be the value of that
single event.
+C<$state-key> is the key of the state to look up.
+
+=head2 joined-members
+
+ method joined-members()
+
+Returns the data for the members of the room.
+
+Example:
+
+ my $room = Matrix::Client::Room $room .= new(
+ :$access-token, :$home-server, :id('#34df12:matrix.org')
+ );
+ my $member = $room.joined-members.head;
+ # the key is the matrix id
+ say $member.key # OUTPUT: «@meeple:matrix.deprecated.org»
+ say $member.values<avatar_url> # OUTPUT: «mxc://matrix.deprecated.org/NdSvF..»
+ say $member.values<display_name> # OUTPUT: «meeple»
+
+=head2 messages
+
+ method messages(
+ Str:D :$from!, Str :$to,
+ Str :$dir where * eq 'f'|'b' = 'f',
+ Int :$limit = 10, :%filter
+ --> Matrix::Response::Messages
+ )
+
+Return a L<Matrix::Response::Messages> with the messages from
+C<$from>. This token can be obtained from a C<prev_batch> token
+returned for each room by the sync API, or from a C<start> or C<end>
+attributes from L<Matrix::Response::Messages>.
+
+=head2 members
+
+ method members(:$at, Str :$membership, Str :$not-membership --> Seq)
+
+Get the list of members for this room. This returns a C<Seq> of
+L<Matrix::Response::MemberEvent>.
+
+=head2 send
+
+ method send(Str $body!, Str :$type? = "m.text")
+
+Sends a message to the room. It will return the C<event_id> for this message.
+
=head2 send-state
method send-state(Str:D $event-type, :$state-key = "", *%args --> Str)
@@ -61,10 +128,48 @@ server.
It will return the C<event_id> for this state change.
+=head2 invite
+
+ method invite(Str $user-id)
+
+Invite a user to the room.
+
+=head2 join
+
+ method join()
+
+Join the room. As the creation of the C<Matrix::Client::Room> expects
+a room id (C<#032mf90f:matrix.org> for example), you need to use the
+L<join-room> method of C<Matrix::Client> to join by an alias.
+
=head2 leave
method leave()
Leaves the room.
+=head2 forget
+
+ method forget()
+
+Stop a user of remembering this particular room.
+
+=head2 kick
+
+ method kick(Str $user-id, Str $reason = "")
+
+Kick an user of this room.
+
+=head2 ban
+
+ method ban(Str $user-id, $reason = "")
+
+Ban an user of this room.
+
+=head2 unban
+
+ method unban(Str $user-id)
+
+Unban a user of this room.
+
=end pod
diff --git a/endpoints.md b/endpoints.md
index 2ddf2c3..e8bc9f8 100644
--- a/endpoints.md
+++ b/endpoints.md
@@ -29,11 +29,11 @@ from matrix.org. This will give you an overview about what's implemented in the
## Media
-- [ ] GET - /_matrix/media/r0/config
-- [ ] GET - /_matrix/media/r0/download/{serverName}/{mediaId}
-- [ ] GET - /_matrix/media/r0/download/{serverName}/{mediaId}/{fileName}
+- [X] GET - /_matrix/media/r0/config
+- [X] GET - /_matrix/media/r0/download/{serverName}/{mediaId}
+- [X] GET - /_matrix/media/r0/download/{serverName}/{mediaId}/{fileName}
- [ ] GET - /_matrix/media/r0/preview_url
-- [ ] GET - /_matrix/media/r0/thumbnail/{serverName}/{mediaId}
+- [X] GET - /_matrix/media/r0/thumbnail/{serverName}/{mediaId}
- [X] POST - /_matrix/media/r0/upload
## OpenID
@@ -52,6 +52,8 @@ from matrix.org. This will give you an overview about what's implemented in the
- [ ] GET - /_matrix/client/r0/pushers
- [ ] GET - /_matrix/client/r0/pushrules/
- [ ] GET - /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}
+- [ ] GET - /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions
+- [ ] GET - /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled
- [ ] POST - /_matrix/client/r0/pushers/set
- [ ] PUT - /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}
- [ ] PUT - /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions
@@ -73,6 +75,7 @@ from matrix.org. This will give you an overview about what's implemented in the
- [X] DELETE - /_matrix/client/r0/directory/room/{roomAlias}
- [X] GET - /_matrix/client/r0/directory/room/{roomAlias}
+- [X] GET - /_matrix/client/r0/rooms/{roomId}/aliases
- [X] PUT - /_matrix/client/r0/directory/room/{roomAlias}
## Room discovery
@@ -88,7 +91,7 @@ from matrix.org. This will give you an overview about what's implemented in the
- [X] POST - /_matrix/client/r0/rooms/{roomId}/forget
- [X] POST - /_matrix/client/r0/rooms/{roomId}/invite
- [ ] POST - /_matrix/client/r0/rooms/{roomId}/invite
-- [ ] POST - /_matrix/client/r0/rooms/{roomId}/join
+- [X] POST - /_matrix/client/r0/rooms/{roomId}/join
- [X] POST - /_matrix/client/r0/rooms/{roomId}/kick
- [X] POST - /_matrix/client/r0/rooms/{roomId}/leave
- [X] POST - /_matrix/client/r0/rooms/{roomId}/unban
@@ -99,14 +102,14 @@ from matrix.org. This will give you an overview about what's implemented in the
- [ ] GET - /_matrix/client/r0/events/{eventId}
- [ ] GET - /_matrix/client/r0/initialSync
- [ ] GET - /_matrix/client/r0/rooms/{roomId}/context/{eventId}
-- [ ] GET - /_matrix/client/r0/rooms/{roomId}/event/{eventId}
+- [X] GET - /_matrix/client/r0/rooms/{roomId}/event/{eventId}
- [ ] GET - /_matrix/client/r0/rooms/{roomId}/initialSync
- [X] GET - /_matrix/client/r0/rooms/{roomId}/joined_members
-- [ ] GET - /_matrix/client/r0/rooms/{roomId}/members
+- [X] GET - /_matrix/client/r0/rooms/{roomId}/members
- [X] GET - /_matrix/client/r0/rooms/{roomId}/messages
-- [ ] GET - /_matrix/client/r0/rooms/{roomId}/state
-- [ ] GET - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}
-- [ ] GET - /_matrix/client/r0/sync
+- [X] GET - /_matrix/client/r0/rooms/{roomId}/state
+- [X] GET - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}
+- [X] GET - /_matrix/client/r0/sync
- [ ] GET - /_matrix/client/r0/user/{userId}/filter/{filterId}
- [ ] POST - /_matrix/client/r0/rooms/{roomId}/receipt/{receiptType}/{eventId}
- [ ] POST - /_matrix/client/r0/user/{userId}/filter
@@ -115,7 +118,7 @@ from matrix.org. This will give you an overview about what's implemented in the
- [X] PUT - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}
- [ ] PUT - /_matrix/client/r0/rooms/{roomId}/typing/{userId}
-## Room ugprades
+## Room upgrades
- [ ] POST - /_matrix/client/r0/rooms/{roomId}/upgrade
@@ -136,7 +139,7 @@ from matrix.org. This will give you an overview about what's implemented in the
## Session management
- [ ] GET - /_matrix/client/r0/login
-- [ ] POST - /_matrix/client/r0/login
+- [X] POST - /_matrix/client/r0/login
- [X] POST - /_matrix/client/r0/logout
- [ ] POST - /_matrix/client/r0/logout/all
@@ -151,20 +154,26 @@ from matrix.org. This will give you an overview about what's implemented in the
- [ ] GET - /_matrix/client/r0/register/available
- [ ] GET - /_matrix/client/r0/user/{userId}/account_data/{type}
- [ ] GET - /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}
-- [ ] GET - /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags
+- [X] GET - /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags
- [ ] POST - /_matrix/client/r0/account/3pid
+- [ ] POST - /_matrix/client/r0/account/3pid/bind
- [ ] POST - /_matrix/client/r0/account/3pid/delete
+- [ ] POST - /_matrix/client/r0/account/3pid/unbind
- [ ] POST - /_matrix/client/r0/account/deactivate
- [ ] POST - /_matrix/client/r0/account/password
- [X] POST - /_matrix/client/r0/register
- [ ] POST - /_matrix/client/r0/user_directory/search
-- [ ] PUT - /_matrix/client/r0/profile/{userId}/avatar_url
+- [X] PUT - /_matrix/client/r0/profile/{userId}/avatar_url
- [X] PUT - /_matrix/client/r0/profile/{userId}/displayname
- [ ] PUT - /_matrix/client/r0/user/{userId}/account_data/{type}
- [ ] PUT - /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}
-- [ ] PUT - /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}
+- [X] PUT - /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}
## VOIP
- [ ] GET - /_matrix/client/r0/voip/turnServer
+
+# Endpoint completion
+
+0.446602% - (46/103)
diff --git a/lib/Matrix/Client.pm6 b/lib/Matrix/Client.pm6
index 9022c65..2e26ee7 100644
--- a/lib/Matrix/Client.pm6
+++ b/lib/Matrix/Client.pm6
@@ -5,6 +5,7 @@ use Matrix::Response;
use Matrix::Client::Common;
use Matrix::Client::Room;
use Matrix::Client::Requester;
+use Matrix::Client::MediaStore;
unit class Matrix::Client does Matrix::Client::Requester;
@@ -331,16 +332,15 @@ method remove-room-alias($room-alias) {
# Media
-#| POST - /_matrix/media/r0/upload
-method upload(IO::Path $path, Str $filename?) {
- my $buf = slurp $path, :bin;
- my $fn = $filename ?? $filename !! $path.basename;
- my $res = $.post-bin("/upload", $buf,
- content-type => "image/png",
- filename => $fn,
- );
- my $data = from-json($res.content);
- $data<content_uri> // "";
+method media(--> Matrix::Client::MediaStore) {
+ return Matrix::Client::MediaStore.new(
+ home-server => $!home-server,
+ access-token => $!access-token
+ )
+}
+
+method upload(IO::Path $path, Str $filename?) is DEPRECATED('media.upload') {
+ self.media.upload($path, $filename)
}
# Misc
diff --git a/lib/Matrix/Client/Exception.pm6 b/lib/Matrix/Client/Exception.pm6
index 924eece..e02f572 100644
--- a/lib/Matrix/Client/Exception.pm6
+++ b/lib/Matrix/Client/Exception.pm6
@@ -7,4 +7,10 @@ package X::Matrix {
"$!code: $!error"
}
}
+
+ class MXCParse is Exception {
+ has $.uri;
+
+ method message { "Cannot parse '$!uri'" }
+ }
}
diff --git a/lib/Matrix/Client/MediaStore.rakumod b/lib/Matrix/Client/MediaStore.rakumod
new file mode 100644
index 0000000..9779db2
--- /dev/null
+++ b/lib/Matrix/Client/MediaStore.rakumod
@@ -0,0 +1,116 @@
+use JSON::Fast;
+use URI::Escape;
+
+use Matrix::Client::Requester;
+use Matrix::Client::Exception;
+use Matrix::Response;
+
+unit class Matrix::Client::MediaStore does Matrix::Client::Requester;
+
+class Matrix::Client::MediaStore::File {
+ has Str $.content-type;
+ has Str $.content-disposition;
+ has Buf $.content;
+}
+
+submethod TWEAK {
+ # Different client endpoint for media
+ $!client-endpoint = "/_matrix/media/r0";
+}
+
+method parse-mxc(Str $uri) {
+ if $uri ~~ m/"mxc://" $<server-name> = [.*] "/" $<media-id> = [ .* ]/ {
+ return {
+ server-name => $<server-name>,
+ media-id => $<media-id>
+ }
+ }
+
+ X::Matrix::MXCParse.new(:$uri).throw;
+}
+
+#| POST - /_matrix/media/r0/upload
+method upload(IO::Path $path, Str $filename?, Str :$content-type is copy = "image/png" --> Str) {
+ my $buf = slurp $path, :bin;
+ my $fn = $filename ?? $filename !! $path.basename;
+
+ # The filename is passed on a query param.
+ my $endpoint = "/upload?filename=" ~ uri-escape($fn);
+
+
+ my $res = $.post-bin(
+ $endpoint, $buf,
+ :$content-type,
+ );
+
+ my $data = from-json($res.content);
+ $data<content_uri> // "";
+}
+
+#| GET - /_matrix/media/r0/download/{serverName}/{mediaId}
+multi method download(Str $mxc-uri, :$allow-remote = True, Str :$filename?) {
+ my $mxc = self.parse-mxc($mxc-uri);
+
+ samewith($mxc<server-name>, $mxc<media-id>, :$allow-remote, :$filename)
+}
+
+#| GET - /_matrix/media/r0/download/{serverName}/{mediaId}/{fileName}
+multi method download(Str $server-name, Str $media-id,
+ Bool :$allow-remote = True, Str :$filename?) {
+ my $endpoint = "/download/{$server-name}/{$media-id}";
+
+ $endpoint ~= "/{$filename}" if $filename.defined;
+ my $response = $.get(
+ $endpoint,
+ allow_remote => $allow-remote.Str.lc
+ );
+
+ my %headers = $response.header.hash();
+
+ Matrix::Client::MediaStore::File.new(
+ content-type => %headers<Content-Type>.head,
+ content-disposition => %headers<Content-Disposition>.head,
+ content => $response.content
+ )
+}
+
+#| GET - /_matrix/media/r0/thumbnail/{serverName}/{mediaId}
+multi method thumbnail(Str $mxc-uri, Int $width, Int $height,
+ Str :$method where * eq 'crop'|'scale',
+ Bool :$allow-remote = True) {
+ my $mxc = self.parse-mxc($mxc-uri);
+ samewith(
+ $mxc<server-name>, $mxc<media-id>,
+ $width, $height,
+ :$method, :$allow-remote
+ )
+}
+
+#| GET - /_matrix/media/r0/thumbnail/{serverName}/{mediaId}
+multi method thumbnail(Str $server-name, Str $media-id,
+ Int $width, Int $height,
+ Str :$method where * eq 'crop'|'scale',
+ Bool :$allow-remote = True) {
+ my $endpoint = "/thumbnail/{$server-name}/{$media-id}";
+
+ my $response = $.get(
+ $endpoint,
+ :$height,
+ :$width,
+ :$method,
+ allow_remote => $allow-remote.Str.lc
+ );
+
+ my %headers = $response.header.hash();
+
+ Matrix::Client::MediaStore::File.new(
+ content-type => %headers<Content-Type>.head,
+ content => $response.content
+ )
+}
+
+#| GET - /_matrix/media/r0/config
+method config(--> Matrix::Response::MediaStore::Config) {
+ my $response = $.get("/config");
+ Matrix::Response::MediaStore::Config.new(from-json($response.content))
+}
diff --git a/lib/Matrix/Client/Requester.pm6 b/lib/Matrix/Client/Requester.pm6
index ff543c9..36a9f69 100644
--- a/lib/Matrix/Client/Requester.pm6
+++ b/lib/Matrix/Client/Requester.pm6
@@ -73,7 +73,7 @@ multi method post(Str $path, :$media = False, *%params) {
method post-bin(Str $path, Buf $buf, :$content-type) {
my $encoded-path = $path.subst('#', '%23');
my $req = POST(
- $.base-url(:media) ~ $encoded-path,
+ $.base-url() ~ $encoded-path,
content => $buf,
Content-Type => $content-type
);
diff --git a/lib/Matrix/Client/Room.pm6 b/lib/Matrix/Client/Room.pm6
index ca97d2f..3d6ecaa 100644
--- a/lib/Matrix/Client/Room.pm6
+++ b/lib/Matrix/Client/Room.pm6
@@ -25,15 +25,8 @@ method !get-name() {
$!name = $data<name>;
}
-#| GET - /_matrix/client/r0/rooms/{roomId}/joined_members
-method joined-members {
- my %data = from-json($.get("/joined_members").content);
- %data<joined>
-}
-
-method name {
+method name(--> Str) {
self!get-name;
-
$!name
}
@@ -52,23 +45,20 @@ method fallback-name(--> Str) {
};
}
-#| GET - /_matrix/client/r0/rooms/{roomId}/messages
-method messages() {
- my $res = $.get("/messages");
- my $data = from-json($res.content);
-
- return $data<chunk>.clone;
+#| GET - /_matrix/client/r0/rooms/{roomId}/aliases
+method aliases(--> List) {
+ my %data = from-json($.get('/aliases').content);
+ %data<aliases>.List
}
-#| 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
- );
+# Events
- from-json($res.content)<event_id>
+## 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
@@ -80,9 +70,67 @@ multi method state(--> Seq) {
}
}
-#| GET - /_matrix/client/r0/rooms/{roomId}/state/{eventType}
-multi method state(Str $event-type) {
- from-json($.get("/state/$event-type").content)
+#| 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<joined>
+}
+
+#| 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<chunk>.map(-> $ev {
+ Matrix::Response::RoomEvent.new(|$ev)
+ });
+
+ Matrix::Response::Messages.new(
+ start => $data<start>,
+ end => $data<end>,
+ messages => @messages
+ )
+}
+
+#| GET - /_matrix/client/r0/rooms/{roomId}/members
+method members(:$at, Str :$membership, Str :$not-membership --> Seq) {
+ my %query;
+
+ %query<at> = $at with $at;
+ %query<membership> = $membership with $membership;
+ %query<not_membership> = $not-membership with $not-membership;
+
+ my %data = from-json($.get('/members', |%query).content);
+
+ gather for %data<chunk>.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)<event_id>
}
#| PUT - /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}
@@ -94,11 +142,49 @@ method send-state(Str:D $event-type, :$state-key = "", *%args --> Str) {
from-json($res.content)<event_id>
}
+# 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() {
+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<id: {self.id}>"
}
diff --git a/lib/Matrix/Response.pm6 b/lib/Matrix/Response.pm6
index 268be84..a0b2896 100644
--- a/lib/Matrix/Response.pm6
+++ b/lib/Matrix/Response.pm6
@@ -23,6 +23,10 @@ class Matrix::Response::StateEvent is Matrix::Response::RoomEvent {
has $.state_key;
}
+class Matrix::Response::MemberEvent is Matrix::Response::StateEvent {
+ has $.type is required where 'm.room.member';
+}
+
class Matrix::Response::Timeline {
has Matrix::Response::Event @.events;
has Bool $limited;
@@ -54,6 +58,12 @@ sub gather-events($room-id, $from) {
}
}
+class Matrix::Response::Messages {
+ has $.start;
+ has $.end;
+ has Matrix::Response::RoomEvent @.messages;
+}
+
class Matrix::Response::Sync {
has Str $.next-batch;
has Matrix::Response::Event @.presence;
@@ -123,7 +133,6 @@ class Tag {
}
}
-
class Matrix::Response::Device {
has Str $.device-id;
has $.display-name;
@@ -137,3 +146,11 @@ class Matrix::Response::Device {
:last_seen_ts(:$!last-seen-ts)?
) { }
}
+
+class Matrix::Response::MediaStore::Config {
+ has Int $.upload-size;
+
+ method new(%config) {
+ self.bless(:upload-size(%config<m.upload.size> // Int));
+ }
+}
diff --git a/scripts/load-docs.p6 b/scripts/load-docs.p6
index b20dd86..2392a3a 100755
--- a/scripts/load-docs.p6
+++ b/scripts/load-docs.p6
@@ -34,8 +34,11 @@ sub get-api-docs {
sub MAIN(:$spec?) {
my %tags = get-api-docs;
- my $implemented-methods = (Matrix::Client, Matrix::Client::Room).map(*.^methods)
- .flat.map(
+ my $total = 0;
+ my $completed = 0;
+ my $implemented-methods = (
+ Matrix::Client, Matrix::Client::Room, Matrix::Client::MediaStore
+ ).map(*.^methods».candidates).flat.map(
{
quietly try { .WHY.Str } or ""
}).grep(/_matrix/).SetHash;
@@ -57,6 +60,9 @@ sub MAIN(:$spec?) {
for $methods.Seq.sort -> $m {
my Str $method = $m.trim;
my $checked = $implemented-methods{$m} ?? "X" !! " ";
+ $completed++ if $implemented-methods{$m};
+ $total++;
+
if $spec {
$method = $m.subst(/unstable/, $spec)
}
@@ -64,4 +70,11 @@ sub MAIN(:$spec?) {
}
say "";
}
+
+ say qq:to/EOF/;
+
+ # Endpoint completion
+
+ {$completed/$total}% - ($completed/$total)
+ EOF
}
diff --git a/t/10-use.t b/t/10-use.t
index df4130b..1c6f97e 100644
--- a/t/10-use.t
+++ b/t/10-use.t
@@ -6,4 +6,6 @@ use-ok 'Matrix::Client::Room';
use-ok 'Matrix::Client::Requester';
use-ok 'Matrix::Response';
use-ok 'Matrix::Client::Exception';
+use-ok 'Matrix::Client::MediaStore';
+
done-testing;
diff --git a/t/40-response.t b/t/40-response.t
index cc219b2..c401378 100644
--- a/t/40-response.t
+++ b/t/40-response.t
@@ -2,24 +2,38 @@ use lib 'lib';
use Test;
use JSON::Fast;
use Matrix::Response;
-plan 7;
+plan 2;
-my $test-file = 'sync.json';
+subtest 'Sync', {
+ plan 7;
+ my $test-file = 'sync.json';
+ my $data;
-unless $test-file.IO.f {
- skip-rest 'Missing sync.json to test';
- exit;
-}
+ if $test-file.IO.f {
+ $data = from-json($test-file.IO.slurp);
+ lives-ok { Matrix::Response::Sync.new($test-file.IO.slurp) }, 'Sync.new accepts Str';
+ lives-ok { Matrix::Response::Sync.new($data) }, 'Sync.new accepts Associative';
-my $data = from-json($test-file.IO.slurp);
+ my $res = Matrix::Response::Sync.new($data);
+ can-ok $res, 'joined-rooms', 'can .joined-rooms';
+ can-ok $res, 'presence', 'can .presence';
-ok $data;
-lives-ok { Matrix::Response::Sync.new($test-file.IO.slurp) };
-lives-ok { Matrix::Response::Sync.new($data) };
+ isa-ok $res.joined-rooms, List, '.joined-rooms returns a List';
+ isa-ok $res.presence, List, '.presence returns a List';
-my $res = Matrix::Response::Sync.new($data);
-can-ok $res, 'joined-rooms';
-can-ok $res, 'presence';
+ } else {
+ skip 'Missing sync.json to test', 7;
+ }
-isa-ok $res.joined-rooms, List;
-isa-ok $res.presence, List;
+};
+
+subtest 'MediaStore', {
+ plan 4;
+ my %config = 'm.upload.size' => 5000;
+ my $config-response = Matrix::Response::MediaStore::Config.new(%config);
+
+ isa-ok $config-response, Matrix::Response::MediaStore::Config;
+ can-ok $config-response, 'upload-size';
+ isa-ok $config-response.upload-size, Int;
+ is $config-response.upload-size, %config<m.upload.size>, 'correct upload size';
+};
diff --git a/t/60-media.t b/t/60-media.t
new file mode 100644
index 0000000..2417cb1
--- /dev/null
+++ b/t/60-media.t
@@ -0,0 +1,76 @@
+use lib 'lib';
+use Test;
+use Matrix::Client::MediaStore;
+use Matrix::Client::Exception;
+
+my $path = $*TMPDIR.add('matrix-client-test');
+LEAVE { unlink $path; }
+$path.spurt("") unless $path.f;
+
+plan 3;
+
+subtest 'Upload', {
+ plan 5;
+ # Mock the post-bin method of Matrix::Client::Requester.
+ my $media = Matrix::Client::MediaStore.new(:home-server("1234")) but role {
+ has %.called-with;
+ method post-bin(Str $path, Buf $buf, :$content-type) {
+ %.called-with<path> = $path;
+ %.called-with<buf> = $buf;
+ %.called-with<content-type> = $content-type;
+
+ class { method content { '{"content_uri": "bla" }' } }
+ }
+ }
+
+
+ $media.upload($path, "something");
+ is $media.called-with<path>, '/upload?filename=something', "Send filename pass by parameter";
+
+ $media.upload($path, "something with spaces");
+ is $media.called-with<path>, '/upload?filename=something%20with%20spaces', "Escaped filename";
+
+ $media.upload($path);
+ is $media.called-with<path>, "/upload?filename={$path.basename}", "Send file basename if not set";
+ is $media.called-with<content-type>, 'image/png', "By default use `image/png` MIME";
+
+ $media.upload($path, content-type => 'application/pdf');
+ is $media.called-with<content-type>, 'application/pdf', "Can change the content type";
+}
+
+subtest 'mxc parsing', {
+ plan 2;
+ my $media = Matrix::Client::MediaStore.new(:home-server("1234"));
+ is $media.parse-mxc("mxc://matrix.org/123456.pdf"), {
+ server-name => "matrix.org", media-id => "123456.pdf"
+ }, 'Simple parsing';
+
+ throws-like {
+ $media.parse-mxc("http://matrix.org/123456.pdf")
+ }, X::Matrix::MXCParse, 'Dies with HTTP URI';
+
+};
+
+subtest 'config', {
+ # Mock the post-bin method of Matrix::Client::Requester.
+ plan 5;
+ my $media = Matrix::Client::MediaStore.new(:home-server("1234")) but role {
+ method get(Str $path) {
+ class { method content { '{"m.upload.size": 5000 }' } }
+ }
+ }
+
+ isa-ok $media.config, Matrix::Response::MediaStore::Config, 'Can load Associative';
+ is $media.config.upload-size, 5000, 'correct upload size';
+
+ my $empty-media = Matrix::Client::MediaStore.new(:home-server("1234")) but role {
+ method get(Str $path) {
+ class { method content { '{}' } }
+ }
+ }
+
+ isa-ok $empty-media.config, Matrix::Response::MediaStore::Config, 'Can load empty configuration';
+ is $empty-media.config.upload-size, Int, 'Unknown upload-size';
+ nok $empty-media.config.upload-size.defined, 'upload-size not defined';
+
+}