matrix_server = $matrix_server; $this->ch = curl_init(); // Supposed to be unique for each transaction of an access token // A bit rough, but use microtime... $this->transaction_id = microtime(); } private function get_new_transaction_id() { $this->transaction_id = microtime(); return $this->transaction_id; } public function get_access_token() { return $this->access_token; } public function get_mxid() { return $this->mxid; } function login_with_password($mxid, $password) { $this->mxid = $mxid; $body = [ "identifier" => [ "type" => "m.id.user", "user" => $mxid, ], "initial_device_display_name" => "MatrixPhpClient", "password" => $password, "type" => "m.login.password", ]; $res = $this->query("/_matrix/client/v3/login", "POST", $body, null, false); $this->access_token = $res["access_token"]; } /** * @var string $url_path: path after the TLD * @var string $method: GET, PUT, POST * @var string[] $body: array of key/values for POST/PUT * @var string[] $params: URL params * @var bool $require_token: if true, access_token needed */ function query($url_path, $method="GET", $body=null, $params=null, $require_token=true) { if (!$this->access_token && $require_token) { throw new MatrixRequestException("Missing access token", 0); } if (!$params) { $params = []; } $params["access_token"] = $this->access_token; curl_reset($this->ch); $url = $this->matrix_server . $url_path . "?" . http_build_query($params); curl_setopt($this->ch, CURLOPT_URL, $url); switch ($method) { case "POST": curl_setopt($this->ch, CURLOPT_POST, 1); if ($body) { curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($body)); } break; case "PUT": curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "PUT"); if ($body) { curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($body)); } break; default: break; } curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($this->ch); if ($res === false) { throw new MatrixRequestException("Matrix request failed: " . curl_error($this->ch), curl_errno($this->ch)); } $res = json_decode($res, true); if (isset($res['errcode'])) { $error = "[{$res['errcode']}] {$res['error']}"; throw new MatrixRequestException("Matrix request failed: $error", 0); } return $res; } function send_room_event($room_id, $event_type, $body) { $txn_id = $this->get_new_transaction_id(); $room_id = curl_escape($this->ch, $room_id); $path = "/_matrix/client/v3/rooms/{$room_id}/send/{$event_type}/{$txn_id}"; return $this->query($path, "PUT", $body); } /** * @var string $mxid: mxid (@xxxx:yyyy.zz) to identify the user * @var string $access_token: the access token returned by a login */ function login_with_token($mxid, $access_token) { $this->mxid = $mxid; $this->access_token = $access_token; } /** * @var string $room_id room id or alias (#xxxx:yyy.zz) * @return MatrixRoom */ function join_room($room_id) { $room = new MatrixRoom($this, $room_id); $this->rooms[$room_id] = $room; return $room; } function run() { while (true) { foreach ($this->rooms as $room) { $room->get_events(); } usleep(500000); } } } class MatrixRequestException extends Exception { function __construct($message, $code = 0, $previous = null) { parent::__construct($message, $code, $previous); } } class MatrixEvent { public $room; public $event_type; public $sender; public $content; public $event_id; public $origin_server_ts; public $user_id; public $age; function __construct($room, $contents) { $this->room = $room; $this->event_type = $contents["type"]; $this->sender = $contents["sender"]; $this->content = $contents["content"]; $this->event_id = $contents["event_id"]; $this->origin_server_ts = $contents["origin_server_ts"]; $this->user_id = $contents["user_id"]; $this->age = isset($contents["age"]) ? $contents["age"] : null; } } class MatrixRoom { private $client; private $room_id; private $listeners = []; private $last_end_token = null; function __construct($client, $room_id) { $this->client = $client; $this->room_id = $room_id; } /** * @var $callback function(room, event) */ function add_listener($callback) { $this->listeners[] = $callback; } function send_text($text) { $body = [ "body" => $text, "msgtype" => "m.text" ]; return $this->client->send_room_event($this->room_id, "m.room.message", $body); } function send_html($html) { $body = [ "body" => $text, "msgtype" => "m.html" ]; return $this->client->send_room_event($this->room_id, "m.room.message", $body); } function get_events() { $params = [ "limit" => 10, "dir" => "b", ]; if ($this->last_end_token) { $params["to"] = $this->last_end_token; } $room_id = curl_escape($this->client->ch, $this->room_id); $res = $this->client->query("/_matrix/client/v3/rooms/{$room_id}/messages", "GET", null, $params); $dispatch_events = $this->last_end_token !== null; $this->last_end_token = $res["start"]; // First time we get events: do nothing if ($dispatch_events) { foreach ($res["chunk"] as $event) { foreach ($this->listeners as $listener) { // Ignore our events if ($event["sender"] === $this->client->get_mxid()) { continue; } $listener(new MatrixEvent($this, $event)); } } } } } $cb = function($event) { if ($event->event_type == "m.room.message") { var_dump($event); if ($event->content["body"] && $event->content["body"][0] === "!") { $command_parts = explode(" ", $event->content["body"], 2); $command = $command_parts[0]; $arg = isset($command_parts[1]) ? $command_parts[1] : ""; switch($command) { case "!echo": $event->room->send_text($arg); break; case "!help": $event->room->send_text("Usage: !COMMAND ARG1 ARG2...\n!echo TEXT => repeat TEXT"); break; } } } };