diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | META6.json | 24 | ||||
-rwxr-xr-x | examples/bot.p6 | 98 | ||||
-rw-r--r-- | examples/rooms.p6 | 6 | ||||
-rw-r--r-- | lib/Matrix/Client.pm6 | 138 | ||||
-rw-r--r-- | lib/Matrix/Client/Common.pm6 | 3 | ||||
-rw-r--r-- | lib/Matrix/Client/Requester.pm6 | 52 | ||||
-rw-r--r-- | lib/Matrix/Client/Room.pm6 | 46 | ||||
-rw-r--r-- | t/meta.t | 6 | ||||
-rw-r--r-- | t/use.t | 7 |
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; @@ -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; |