280 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| class MatrixClient
 | |
| {
 | |
|     private $matrix_server;
 | |
|     private $rooms;
 | |
|     private $transaction_id;
 | |
|     private $mxid;
 | |
|     private $access_token;
 | |
| 
 | |
|     function __construct($matrix_server)
 | |
|     {
 | |
|         $this->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;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| };
 |