aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--META6.json24
-rwxr-xr-xexamples/bot.p698
-rw-r--r--examples/rooms.p66
-rw-r--r--lib/Matrix/Client.pm6138
-rw-r--r--lib/Matrix/Client/Common.pm63
-rw-r--r--lib/Matrix/Client/Requester.pm652
-rw-r--r--lib/Matrix/Client/Room.pm646
-rw-r--r--t/meta.t6
-rw-r--r--t/use.t7
10 files changed, 383 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e1848c4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+state
+file.json
+**/*/.precomp
diff --git a/META6.json b/META6.json
new file mode 100644
index 0000000..887916e
--- /dev/null
+++ b/META6.json
@@ -0,0 +1,24 @@
+{
+ "perl" : "6.c",
+ "name" : "Matrix::Client",
+ "version" : "0.1.0",
+ "description" : "Simple matrix.org client",
+ "tags" : [ "Net", "Matrix" ],
+ "depends" : [
+ "JSON::Tiny",
+ "HTTP::UserAgent",
+ "URI::Encode"
+ ],
+ "test-depends" : [
+ "Test",
+ "Test::META"
+ ],
+ "provides" : {
+ "Matrix::Client" : "lib/Matrix/Client.pm6",
+ "Matrix::Client::Room" : "lib/Matrix/Client/Room.pm6",
+ "Matrix::Client::Requester" : "lib/Matrix/Client/Requester.pm6",
+ "Matrix::Client::Common" : "lib/Matrix/Client/Common.pm6"
+ },
+ "authors" : ["Matias Linares"],
+ "support" : {"source" : ""}
+}
diff --git a/examples/bot.p6 b/examples/bot.p6
new file mode 100755
index 0000000..cfc1247
--- /dev/null
+++ b/examples/bot.p6
@@ -0,0 +1,98 @@
+#!/usr/bin/env perl6
+use v6;
+use lib "lib";
+use JSON::Tiny;
+use Matrix::Client;
+
+class Bot {
+ has $!name = "deprecated";
+ has $!username is required;
+ has Bool $!register = False;
+ has @!room-ids;
+
+ has $!on-event;
+
+ has Matrix::Client $!client;
+
+ submethod BUILD(:$username!, :$password!, :$home-server!, :@room-ids!, :$on-event!) {
+ $!client = Matrix::Client.new(:home-server($home-server));
+ $!username = $username;
+ @!room-ids = @room-ids;
+ $!on-event = $on-event;
+
+ $!client.login($!username, $password);
+ }
+
+ method join-rooms() {
+ @!room-ids.map: { $!client.join-room($_) }
+ }
+
+ method shutdown() {
+ $!client.finish;
+ }
+
+ method listen() {
+ say "Listening";
+ my $since = "";
+
+ loop {
+ my $sync = { room => timeline => limit => 1 };
+ my $data = from-json($!client.sync(sync-filter => $sync, since => $since).content);
+ $since = $data<next_batch>;
+
+ for $data<rooms><join>.kv -> $room-id, $d {
+ for @($d<timeline><events>) -> $ev {
+ if $ev<type> eq "m.room.message" {
+ if $ev<content><body>.match($!name) {
+ my $bot-msg = $!on-event($ev);
+ if so $bot-msg {
+ say "Sending message $bot-msg";
+ my $res = $!client.send($room-id, ~$bot-msg);
+ if $res.is-success {
+ say $res.content;
+ } else {
+ warn $res.content;
+ die $res.status-line;
+ }
+ }
+ }
+ }
+ }
+ }
+ sleep(10);
+ }
+ }
+}
+
+sub MAIN(Str:D $username, Str:D $password, :$home-server = "https://matrix.deprecated.org") {
+ my @rooms = "!bpHGYOiCGlvCZarfMH:matrix.deprecated.org";
+ my $bot = Bot.new:
+ username => $username,
+ password => $password,
+ home-server => $home-server,
+ room-ids => @rooms,
+ on-event => -> $ev {
+ given $ev<content><body> {
+ when /"say hi"/ {
+ say "Someone is saying hi!";
+ "Hello @ {DateTime.now}"
+ }
+ default { say "Dunno what's telling me"; Str }
+ }
+ };
+
+ signal(SIGINT).tap({
+ $bot.shutdown;
+ exit 0;
+ });
+
+ my $ress = $bot.join-rooms;
+ for @($ress) -> $res {
+ if !$res.is-success {
+ warn $res.status-line;
+ warn $res.content;
+ }
+ }
+
+ $bot.listen;
+}
diff --git a/examples/rooms.p6 b/examples/rooms.p6
new file mode 100644
index 0000000..7f62233
--- /dev/null
+++ b/examples/rooms.p6
@@ -0,0 +1,6 @@
+use Matrix::Client;
+
+my $c = Matrix::Client.new: :home-server<https://matrix.deprecated.org>;
+$c.login: @*ARGS[0], @*ARGS[1];
+
+say $c.rooms;
diff --git a/lib/Matrix/Client.pm6 b/lib/Matrix/Client.pm6
new file mode 100644
index 0000000..6849fe4
--- /dev/null
+++ b/lib/Matrix/Client.pm6
@@ -0,0 +1,138 @@
+use HTTP::Request::Common;
+use URI::Encode;
+use JSON::Tiny;
+use Matrix::Client::Common;
+use Matrix::Client::Room;
+use Matrix::Client::Requester;
+
+unit class Matrix::Client does Matrix::Client::Requester;
+
+has Str $!user-id;
+has Str $!device-id;
+has Str $.state-file = 'state';
+has @!rooms;
+has @!users;
+
+method user-id() {
+ $!user-id
+}
+
+method device-id() {
+ $!device-id
+}
+
+method login(Str $username, Str $pass) returns Bool {
+ if $.state-file.IO.e {
+ my $data = from-json(slurp $.state-file);
+ $!access-token = $data<access_token>;
+ $!user-id = $data<user_id>;
+ $!device-id = $data<device_id>;
+ $Matrix::Client::Common::TXN-ID = $data<txn_id> // 0;
+ return True
+ }
+
+ # Handle POST
+ my $data = to-json {
+ type => "m.login.password",
+ user => $username,
+ password => $pass
+ };
+
+ my $res = $.post("/login", $data);
+ if $res.is-success {
+ spurt $.state-file, $res.content;
+ my $data = from-json($res.content);
+ $!access-token = $data<access_token>;
+ $!user-id = $data<user_id>;
+ $!device-id = $data<device_id>;
+ True
+ } else {
+ False
+ }
+}
+
+method finish() {
+ my %data =
+ access_token => $!access-token,
+ user_id => $!user-id,
+ device_id => $!device-id,
+ txn_id => $Matrix::Client::Common::TXN-ID;
+
+ spurt $.state-file, to-json(%data);
+}
+
+method logout() {
+ unlink $.state-file;
+ $.post("/logout")
+}
+
+method register($username, $password, Bool :$bind-email? = False) {
+ my $res = $.post("/register",
+ username => $username, password => $password,
+ bind_email => $bind-email,
+ auth => {
+ type => "m.login.dummy"
+ });
+ if $res.is-success {
+ my $data = from-json $res.content;
+ $!access-token = $data<access_token>;
+ $.user-id = $data<user_id>;
+ } else {
+ die "Error with the homeserver: " ~ $res.content;
+ }
+}
+
+method check-res($res) {
+ if $res.is-success {
+ True
+ } else {
+ warn $res.status-line;
+ warn $res.content;
+ False
+ }
+}
+
+multi method sync() {
+ my $res = $.get("/sync",
+ timeout => 30000
+ );
+
+ $.check-res($res);
+ $res
+}
+
+multi method sync(Str :$sync-filter, Str :$since = "") {
+ my $res = $.get("/sync",
+ timeout => 30000,
+ filter => $sync-filter,
+ since => $since
+ );
+
+ $.check-res($res);
+ $res
+}
+
+multi method sync(:$sync-filter is copy, :$since = "") {
+ $.sync(sync-filter => to-json($sync-filter), since => $since)
+}
+
+method join-room($room-id!) {
+ $.post("/join/" ~ $room-id)
+}
+
+method rooms() {
+ my $res = $.get("/sync", timeout => "30000");
+
+ return () unless $res.is-success;
+ my $data = from-json($res.content);
+ for $data<rooms><join>.kv -> $id, $json {
+ @!rooms.push(Matrix::Client::Room.new(id => $id, json => $json, home-server => $!home-server));
+ }
+
+ @!rooms
+}
+
+method send(Str $room-id, Str $body, :$type? = "m.text") {
+ $Matrix::Client::Common::TXN-ID++;
+ $.put("/rooms/$room-id/send/m.room.message/{$Matrix::Client::Common::TXN-ID}", msgtype => $type, body => $body)
+}
diff --git a/lib/Matrix/Client/Common.pm6 b/lib/Matrix/Client/Common.pm6
new file mode 100644
index 0000000..670f8e3
--- /dev/null
+++ b/lib/Matrix/Client/Common.pm6
@@ -0,0 +1,3 @@
+unit module Matrix::Client::Common;
+
+our $TXN-ID = 0;
diff --git a/lib/Matrix/Client/Requester.pm6 b/lib/Matrix/Client/Requester.pm6
new file mode 100644
index 0000000..c2ba865
--- /dev/null
+++ b/lib/Matrix/Client/Requester.pm6
@@ -0,0 +1,52 @@
+use HTTP::UserAgent;
+use HTTP::Request::Common;
+use URI::Encode;
+use JSON::Tiny;
+
+unit role Matrix::Client::Requester;
+
+has $!ua = HTTP::UserAgent.new;
+has $.home-server is required;
+has $!client-endpoint = "/_matrix/client/r0";
+has $!url-prefix = "";
+has $!access-token = "";
+has $!sync-since = "";
+
+method get(Str $path, *%data) {
+ my $q = "$path?access_token=$!access-token";
+ for %data.kv -> $k,$v {
+ $q ~= "&$k=$v" unless $v eq "";
+ }
+ my $uri = uri_encode($.base-url ~ $q);
+
+ $!ua.history = [];
+ $!ua.get($uri)
+}
+
+method base-url(--> Str) {
+ "$.home-server$!client-endpoint$!url-prefix"
+}
+
+multi method post(Str $path, Str $json) {
+ my $req = HTTP::Request.new(POST => $.base-url() ~ $path ~ "?access_token=$!access-token",
+ Content-Type => 'application/json');
+ $req.add-content($json);
+ $!ua.history = [];
+ $!ua.request($req)
+}
+
+multi method post(Str $path, *%params) {
+ self.post($path, to-json(%params))
+}
+
+multi method put(Str $path,Str $json) {
+ my $req = HTTP::Request.new(PUT => $.base-url() ~ $path ~ "?access_token=$!access-token",
+ Content-Type => 'application/json');
+ $req.add-content($json);
+ $!ua.history = [];
+ $!ua.request($req)
+}
+
+multi method put(Str $path, *%params) {
+ self.put($path, to-json(%params))
+}
diff --git a/lib/Matrix/Client/Room.pm6 b/lib/Matrix/Client/Room.pm6
new file mode 100644
index 0000000..5601f33
--- /dev/null
+++ b/lib/Matrix/Client/Room.pm6
@@ -0,0 +1,46 @@
+use JSON::Tiny;
+use Matrix::Client::Common;
+use Matrix::Client::Requester;
+
+unit class Matrix::Client::Room does Matrix::Client::Requester;
+
+has $.name is rw;
+has $.id is rw;
+has $!prev-batch;
+
+submethod BUILD(Str :$id!, :$json, :$home-server!) {
+ $!home-server = $home-server;
+ $!id = $id;
+ $!url-prefix = "/rooms/$!id";
+ $!prev-batch = $json<timeline><prev_batch>;
+
+ if so $json {
+ my @events = $json<state><events>.clone;
+ for @events -> $ev {
+ if $ev<type> eq "m.room.name" {
+ $!name = $ev<content><name>;
+ }
+ }
+ }
+
+ # FIXME: Should be a 1:1 conversation
+ unless $!name {
+ $!name = "Unknown";
+ }
+}
+
+method messages() {
+ my $res = $.get("/messages");
+ my $data = from-json($res.content);
+
+ return $data<chunk>.clone;
+}
+
+method send($room-id, Str $body!, Str :$type? = "m.text") {
+ $Matrix::Client::Common::TXN-ID++;
+ $.put("/send/m.room.message/{$Matrix::Client::Common::TXN-ID}", msgtype => $type, body => $body)
+}
+
+method gist(--> Str) {
+ "Room<name: $.name, id: $.id>"
+}
diff --git a/t/meta.t b/t/meta.t
new file mode 100644
index 0000000..98788f2
--- /dev/null
+++ b/t/meta.t
@@ -0,0 +1,6 @@
+use lib 'lib';
+use Test;
+use Test::META;
+
+meta-ok;
+done-testing;
diff --git a/t/use.t b/t/use.t
new file mode 100644
index 0000000..88b4caa
--- /dev/null
+++ b/t/use.t
@@ -0,0 +1,7 @@
+use lib 'lib';
+use Test;
+
+use-ok 'Matrix::Client';
+use-ok 'Matrix::Client::Room';
+use-ok 'Matrix::Client::Requester';
+done-testing;