phpmatrix/phpmatrix.php
2023-10-22 14:09:08 +02:00

259 lines
6.7 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));
}
}
}
}
}