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;
|
|
}
|
|
}
|
|
}
|
|
};
|