diff --git a/composer.json b/composer.json index 858e76a..4c3370e 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "require": { "php": "~7.2", "guzzlehttp/guzzle": "^6.3", - "rappasoft/laravel-helpers": "^1.0" + "rappasoft/laravel-helpers": "^1.0", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit" : ">=5.4.3" diff --git a/src/MatrixHttpApi.php b/src/MatrixHttpApi.php index 597406d..f96c0a0 100644 --- a/src/MatrixHttpApi.php +++ b/src/MatrixHttpApi.php @@ -5,6 +5,7 @@ namespace Aryess\PhpMatrixSdk; use Aryess\PhpMatrixSdk\Exceptions\MatrixException; use Aryess\PhpMatrixSdk\Exceptions\MatrixHttpLibException; use Aryess\PhpMatrixSdk\Exceptions\MatrixRequestException; +use Aryess\PhpMatrixSdk\Exceptions\ValidationException; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; @@ -24,6 +25,7 @@ use GuzzleHttp\Exception\GuzzleException; class MatrixHttpApi { const MATRIX_V2_API_PATH = "/_matrix/client/r0"; + const MATRIX_V2_MEDIA_PATH = '/_matrix/media/r0'; const VERSION = '0.0.1-dev'; /** @@ -90,7 +92,7 @@ class MatrixHttpApi { $this->token = $token; $this->identity = $identity; $this->txnId = 0; - $this->vallidateCert = true;//FIXME: use config + $this->vallidateCert = true; $this->client = new Client(); $this->default429WaitMs = $default429WaitMs; $this->useAuthorizationHeader = $useAuthorizationHeader; @@ -108,7 +110,6 @@ class MatrixHttpApi { * @param string|null $setPresence * @return array|string * @throws MatrixException - * @throws GuzzleException */ public function sync(?string $since = null, int $timeoutMs = 30000, $filter = null, bool $fullState = false, ?string $setPresence = null) { @@ -150,6 +151,8 @@ class MatrixHttpApi { * @param string|null $deviceId ID of the client device. * @param string|null $initialDeviceDisplayName Display name to be assigned. * @param bool $inhibitLogin Whether to login after registration. Defaults to false. + * @return array|string + * @throws MatrixException */ public function register(array $authBody = [], string $kind = "user", bool $bindEmail = false, ?string $username = null, ?string $password = null, ?string $deviceId = null, @@ -182,9 +185,703 @@ class MatrixHttpApi { return $this->send('POST', '/register', $content, ['kind' => $kind]); } - public function getDisplayName(string $userId): ?string { - $content = $this->send("GET", "/profile/$userId/displayname"); - return array_get($content, 'displayname', json_encode($content)); + /** + * Perform /login. + * + * @param string $loginType The value for the 'type' key. + * @param array $args Additional key/values to add to the JSON submitted. + * @return array|string + * @throws MatrixException + */ + public function login(string $loginType, array $args) { + $args["type"] = $loginType; + + return $this->send('POST', '/login', $args); + } + + /** + * Perform /logout. + * + * @return array|string + * @throws MatrixException + */ + public function logout() { + return $this->send('POST', '/logout'); + } + + /** + * Perform /logout/all. + * + * @return array|string + * @throws MatrixException + */ + public function logoutAll() { + return $this->send('POST', '/logout/all'); + } + + /** + * Perform /createRoom. + * + * @param string|null $alias Optional. The room alias name to set for this room. + * @param string|null $name Optional. Name for new room. + * @param bool $isPublic Optional. The public/private visibility. + * @param array|null $invitees Optional. The list of user IDs to invite. + * @param bool|null $federate Optional. Сan a room be federated. Default to True. + * @return array|string + * @throws MatrixException + */ + public function createRoom(string $alias = null, string $name = null, bool $isPublic = false, + array $invitees = null, bool $federate = null) { + $content = [ + "visibility" => $isPublic ? "public" : "private" + ]; + if ($alias) { + $content["room_alias_name"] = $alias; + } + if ($invitees) { + $content["invite"] = $invitees; + } + if ($name) { + $content["name"] = $name; + } + if ($federate != null) { + $content["creation_content"] = ['m.federate' => $federate]; + } + return $this->send("POST", "/createRoom", $content); + } + + /** + * Performs /join/$room_id + * + * @param string $roomIdOrAlias The room ID or room alias to join. + * @return array|string + * @throws MatrixException + */ + public function joinRoom(string $roomIdOrAlias) { + $path = sprintf("/join/%s", urlencode($roomIdOrAlias)); + + return $this->send('POST', $path); + } + + /** + * Perform PUT /rooms/$room_id/state/$event_type + * + * @param string $roomId The room ID to send the state event in. + * @param string $eventType The state event type to send. + * @param array $content The JSON content to send. + * @param string $stateKey Optional. The state key for the event. + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + */ + public function sendStateEvent(string $roomId, string $eventType, array $content, + string $stateKey = "", int $timestamp = null) { + $path = sprintf("/rooms/%s/state/%s", urlencode($roomId), urlencode($eventType)); + if ($stateKey) { + $path .= sprintf("/%s", urlencode($stateKey)); + } + $params = []; + if ($timestamp) { + $params["ts"] = $timestamp; + } + + return $this->send('PUT', $path, $content, $params); + } + + /** + * Perform GET /rooms/$room_id/state/$event_type + * + * @param string $roomId The room ID. + * @param string $eventType The type of the event. + * @return array|string + * @throws MatrixRequestException (code=404) if the state event is not found. + * @throws MatrixException + */ + public function getStateEvent(string $roomId, string $eventType) { + $path = sprintf('/rooms/%s/state/%s', urlencode($roomId), urlencode($eventType)); + + return $this->send('GET', $path); + } + + /** + * @param string $roomId The room ID to send the message event in. + * @param string $eventType The event type to send. + * @param array $content The JSON content to send. + * @param int $txnId Optional. The transaction ID to use. + * @param int $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendMessageEvent(string $roomId, string $eventType, array $content, + int $txnId = null, int $timestamp = null) { + if (!$txnId) { + $txnId = $this->makeTxnId(); + } + $path = sprintf('/rooms/%s/send/%s/%s', urlencode($roomId), urlencode($eventType), urlencode($txnId)); + $params = []; + if ($timestamp) { + $params['ts'] = $timestamp; + } + + return $this->send('PUT', $path, $content, $params); + } + + /** + * Perform PUT /rooms/$room_id/redact/$event_id/$txn_id/ + * + * @param string $roomId The room ID to redact the message event in. + * @param string $eventId The event id to redact. + * @param string $reason Optional. The reason the message was redacted. + * @param int|null $txnId Optional. The transaction ID to use. + * @param int|null $timestamp Optional. Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function redactEvent(string $roomId, string $eventId, ?string $reason = null, + int $txnId = null, int $timestamp = null) { + if (!$txnId) { + $txnId = $this->makeTxnId(); + } + $path = sprintf('/rooms/%s/redact/%s/%s', urlencode($roomId), urlencode($eventId), urlencode($txnId)); + $params = []; + $content = []; + if ($reason) { + $content['reason'] = $reason; + } + if ($timestamp) { + $params['ts'] = $timestamp; + } + + return $this->send('PUT', $path, $content, $params); + } + + /** + * $content_type can be a image,audio or video + * extra information should be supplied, see + * https://matrix.org/docs/spec/r0.0.1/client_server.html + * + * @param string $roomId + * @param string $itemUrl + * @param string $itemName + * @param string $msgType + * @param array $extraInformation + * @param int|null $timestamp + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendContent(string $roomId, string $itemUrl, string $itemName, string $msgType, + array $extraInformation = [], int $timestamp = null) { + $contentPack = [ + "url" => $itemUrl, + "msgtype" => $msgType, + "body" => $itemName, + "info" => $extraInformation, + ]; + + return $this->sendMessageEvent($roomId, 'm.room.message', $contentPack, null, $timestamp); + } + + + /** + * Send m.location message event + * http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location + * + * @param string $roomId The room ID to send the event in. + * @param string $geoUri The geo uri representing the location. + * @param string $name Description for the location. + * @param string|null $thumbUrl URL to the thumbnail of the location. + * @param array|null $thumbInfo Metadata about the thumbnail, type ImageInfo. + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendLocation(string $roomId, string $geoUri, string $name, string $thumbUrl = null, + array $thumbInfo = null, int $timestamp = null) { + $contentPack = [ + "geo_uri" => $geoUri, + "msgtype" => "m.location", + "body" => $name, + ]; + if ($thumbUrl) { + $contentPack['thumbnail_url'] = $thumbUrl; + } + if ($thumbInfo) { + $contentPack['thumbnail_info'] = $thumbInfo; + } + + return $this->sendMessageEvent($roomId, 'm.room.message', $contentPack, null, $timestamp); + } + + /** + * Perform PUT /rooms/$room_id/send/m.room.message + * + * @param string $roomId The room ID to send the event in. + * @param string $textContent The m.text body to send. + * @param string $msgType + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendMessage(string $roomId, string $textContent, string $msgType = 'm.text', int $timestamp = null) { + $textBody = $this->getTextBody($textContent, $msgType); + + return $this->sendMessageEvent($roomId, 'm.room.message', $textBody, null, $timestamp); + } + + /** + * Perform PUT /rooms/$room_id/send/m.room.message with m.emote msgtype + * + * @param string $roomId The room ID to send the event in. + * @param string $textContent The m.emote body to send. + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendEmote(string $roomId, string $textContent, int $timestamp = null) { + $body = $this->getEmoteBody($textContent); + + return $this->sendMessageEvent($roomId, 'm.room.message', $body, null, $timestamp); + } + + /** + * Perform PUT /rooms/$room_id/send/m.room.message with m.notice msgtype + * + * @param string $roomId The room ID to send the event in. + * @param string $textContent The m.emote body to send. + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendNotice(string $roomId, string $textContent, int $timestamp = null) { + $body = [ + 'msgtype' => 'm.notice', + 'body' => $textContent, + ]; + + return $this->sendMessageEvent($roomId, 'm.room.message', $body, null, $timestamp); + } + + /** + * @param string $roomId The room's id. + * @param string $token The token to start returning events from. + * @param string $direction The direction to return events from. One of: ["b", "f"]. + * @param int $limit The maximum number of events to return. + * @param string|null $to The token to stop returning events at. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getRoomMessages(string $roomId, string $token, string $direction, int $limit = 10, string $to = null) { + $query = [ + "roomId" => $roomId, + "from" => $token, + 'dir' => $direction, + 'limit' => $limit, + ]; + + if ($to) { + $query['to'] = $to; + } + $path = sprintf('/rooms/%s/messages', urlencode($roomId)); + + return $this->send('GET', $path, null, $query); + } + + /** + * Perform GET /rooms/$room_id/state/m.room.name + * + * @param string $roomId The room ID + * @return array|string + * @throws MatrixException + * @throws MatrixRequestException + */ + public function getRoomName(string $roomId) { + return $this->getStateEvent($roomId, 'm.room.name'); + } + + /** + * Perform PUT /rooms/$room_id/state/m.room.name + * + * @param string $roomId The room ID + * @param string $name The new room name + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + */ + public function setRoomName(string $roomId, string $name, int $timestamp = null) { + $body = ['name' => $name]; + + return $this->sendStateEvent($roomId, 'm.room.name', $body, '', $timestamp); + } + + /** + * Perform GET /rooms/$room_id/state/m.room.topic + * + * @param string $roomId The room ID + * @return array|string + * @throws MatrixException + * @throws MatrixRequestException + */ + public function getRoomTopic(string $roomId) { + return $this->getStateEvent($roomId, 'm.room.topic'); + } + + /** + * Perform PUT /rooms/$room_id/state/m.room.topic + * + * @param string $roomId The room ID + * @param string $topic The new room topic + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + */ + public function setRoomTopic(string $roomId, string $topic, int $timestamp = null) { + $body = ['topic' => $topic]; + + return $this->sendStateEvent($roomId, 'm.room.topic', $body, '', $timestamp); + } + + + /** + * Perform GET /rooms/$room_id/state/m.room.power_levels + * + * + * @param string $roomId The room ID + * @return array|string + * @throws MatrixException + * @throws MatrixRequestException + */ + public function getPowerLevels(string $roomId) { + return $this->getStateEvent($roomId, 'm.room.power_levels'); + } + + /** + * Perform PUT /rooms/$room_id/state/m.room.power_levels + * + * Note that any power levels which are not explicitly specified + * in the content arg are reset to default values. + * + * + * Example: + * $api = new MatrixHttpApi("http://example.com", $token="foobar") + * $api->setPowerLevels("!exampleroom:example.com", + * [ + * "ban" => 50, # defaults to 50 if unspecified + * "events": [ + * "m.room.name" => 100, # must have PL 100 to change room name + * "m.room.power_levels" => 100 # must have PL 100 to change PLs + * ], + * "events_default" => 0, # defaults to 0 + * "invite" => 50, # defaults to 50 + * "kick" => 50, # defaults to 50 + * "redact" => 50, # defaults to 50 + * "state_default" => 50, # defaults to 50 if m.room.power_levels exists + * "users" => [ + * "@someguy:example.com" => 100 # defaults to 0 + * ], + * "users_default" => 0 # defaults to 0 + * ] + * ); + * + * @param string $roomId + * @param array $content + * @return array|string + * @throws MatrixException + */ + public function setPowerLevels(string $roomId, array $content) { + // Synapse returns M_UNKNOWN if body['events'] is omitted, + // as of 2016-10-31 + if (!array_key_exists('events', $content)) { + $content['events'] = []; + } + + return $this->sendStateEvent($roomId, 'm.room.power_levels', $content); + } + + /** + * Perform POST /rooms/$room_id/leave + * + * @param string $roomId The room ID + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function leaveRoom(string $roomId) { + return $this->send('POST', sprintf('/rooms/%s/leave', urlencode($roomId)), []); + } + + /** + * Perform POST /rooms/$room_id/forget + * + * @param string $roomId The room ID + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function forgetRoom(string $roomId) { + return $this->send('POST', sprintf('/rooms/%s/forget', urlencode($roomId)), []); + } + + /** + * Perform POST /rooms/$room_id/invite + * + * @param string $roomId The room ID + * @param string $userId The user ID of the invitee + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function inviteUser(string $roomId, string $userId) { + $body = ['user_id' => $userId]; + + return $this->send('POST', sprintf('.rooms/%s/invite', urlencode($roomId)), $body); + } + + /** + * Calls set_membership with membership="leave" for the user_id provided + * + * @param string $roomId The room ID + * @param string $userId The user ID + * @param string $reason Optional. The reason for kicking them out + * @return mixed + * @throws MatrixException + */ + public function kickUser(string $roomId, string $userId, string $reason = '') { + return $this->setMembership($roomId, $userId, 'leave', $reason); + } + + /** + * Perform GET /rooms/$room_id/state/m.room.member/$user_id + * + * @param string $roomId The room ID + * @param string $userId The user ID + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getMembership(string $roomId, string $userId) { + $path = sprintf('/rooms/%s/state/m.room.member/%s', urlencode($roomId), urlencode($userId)); + + return $this->send('GET', $path); + } + + /** + * Perform PUT /rooms/$room_id/state/m.room.member/$user_id + * + * @param string $roomId The room ID + * @param string $userId The user ID + * @param string $membership New membership value + * @param string $reason The reason + * @param array $profile + * @param int|null $timestamp Set origin_server_ts (For application services only) + * @return array|string + * @throws MatrixException + */ + public function setMembership(string $roomId, string $userId, string $membership, string $reason = '', array $profile = [], int $timestamp = null) { + $body = [ + 'membership' => $membership, + 'reason' => $reason, + ]; + if (array_key_exists('displayname', $profile)) { + $body['displayname'] = $profile['displayname']; + } + if (array_key_exists('avatar_url', $profile)) { + $body['avatar_url'] = $profile['avatar_url']; + } + + return $this->sendStateEvent($roomId, 'm.room.member', $body, $userId, $timestamp); + } + + /** + * Perform POST /rooms/$room_id/ban + * + * @param string $roomId The room ID + * @param string $userId The user ID of the banee(sic) + * @param string $reason The reason for this ban + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function banUser(string $roomId, string $userId, string $reason = '') { + $body = [ + 'user_id' => $userId, + 'reason' => $reason, + ]; + + return $this->send('POST', sprintf('/rooms/%s/ban', urlencode($roomId)), $body); + } + + /** + * Perform POST /rooms/$room_id/unban + * + * @param string $roomId The room ID + * @param string $userId The user ID of the banee(sic) + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function unbanUser(string $roomId, string $userId) { + $body = [ + 'user_id' => $userId, + ]; + + return $this->send('POST', sprintf('/rooms/%s/unban', urlencode($roomId)), $body); + } + + /** + * @param string $userId + * @param string $roomId + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getUserTags(string $userId, string $roomId) { + $path = sprintf('/user/%s/rooms/%s/tags', urlencode($userId), urlencode($roomId)); + + return $this->send('GET', $path); + } + + /** + * @param string $userId + * @param string $roomId + * @param string $tag + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function removeUserTag(string $userId, string $roomId, string $tag) { + $path = sprintf('/user/%s/rooms/%s/tags/%s', urlencode($userId), urlencode($roomId), urlencode($tag)); + + return $this->send('DELETE', $path); + } + + /** + * @param string $userId + * @param string $roomId + * @param string $tag + * @param float|null $order + * @param array $body + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function addUserTag(string $userId, string $roomId, string $tag, ?float $order, array $body = []) { + if ($order) { + $body['order'] = $order; + } + $path = sprintf('/user/%s/rooms/%s/tags/%s', urlencode($userId), urlencode($roomId), urlencode($tag)); + + return $this->send('PUT', $path, $body); + } + + /** + * @param string $userId + * @param string $type + * @param array $accountData + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function setAccountData(string $userId, string $type, array $accountData) { + $path = sprintf("/user/%s/account_data/%s", urlencode($userId), urlencode($type)); + + return $this->send('PUT', $path, $accountData); + } + + /** + * @param string $userId + * @param string $roomId + * @param string $type + * @param array $accountData + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function setRoomAccountData(string $userId, string $roomId, string $type, array $accountData) { + $path = sprintf( + '/user/%s/rooms/%s/account_data/%s', + urlencode($userId), urlencode($roomId), urlencode($type) + ); + + return $this->send('PUT', $path, $accountData); + } + + /** + * Perform GET /rooms/$room_id/state + * + * @param string $roomId The room ID + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getRoomState(string $roomId) { + return $this->send('GET', sprintf('/rooms/%s/state', urlencode($roomId))); + } + + public function getTextBody(string $textContent, string $msgType = 'm.text'): array { + return [ + 'msgtype' => $msgType, + 'body' => $textContent, + ]; + } + + private function getEmoteBody(string $textContent): array { + return $this->getTextBody($textContent, 'm.emote'); + } + + /** + * @param string $userId + * @param string $filterId + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getFilter(string $userId, string $filterId) { + $path = sprintf("/user/%s/filter/%s", urlencode($userId), urlencode($filterId)); + + return $this->send('GET', $path); + } + + /** + * @param string $userId + * @param array $filterParams + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function createFilter(string $userId, array $filterParams) { + $path = sprintf("/user/%s/filter", urlencode($userId)); + + return $this->send('POST', $path, $filterParams); } /** @@ -197,12 +894,14 @@ class MatrixHttpApi { * @param bool $returnJson * @return array|string * @throws MatrixException + * @throws MatrixRequestException + * @throws MatrixHttpLibException */ private function send(string $method, string $path, $content = null, array $queryParams = [], array $headers = [], $apiPath = self::MATRIX_V2_API_PATH, $returnJson = true) { $options = []; if (!in_array('User-Agent', $headers)) { - $headers['User-Agent'] = 'php-matrix-sdk/'.self::VERSION; + $headers['User-Agent'] = 'php-matrix-sdk/' . self::VERSION; } $method = strtoupper($method); @@ -262,4 +961,486 @@ class MatrixHttpApi { return $returnJson ? json_decode($responseBody, true) : $responseBody; } + + /** + * @param $content + * @param string $contentType + * @param string|null $filename + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function mediaUpload($content, string $contentType, string $filename = null) { + $headers = ['Content-Type' => $contentType]; + $apiPath = self::MATRIX_V2_MEDIA_PATH . "/upload"; + $queryParam = []; + if ($filename) { + $queryParam['filename'] = $filename; + } + + return $this->send('POST', '', $content, $queryParam, $headers, $apiPath); + } + + /** + * @param string $userId + * @return string|null + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getDisplayName(string $userId): ?string { + $content = $this->send("GET", "/profile/$userId/displayname"); + + return array_get($content, 'displayname'); + } + + /** + * @param string $userId + * @param string $displayName + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function setDisplayName(string $userId, string $displayName) { + $content = ['displayname' => $displayName]; + $path = sprintf('/profile/%s/displayname', urlencode($userId)); + + return $this->send('PUT', $path, $content); + } + + /** + * @param string $userId + * @return mixed + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getAvatarUrl(string $userId): ?string { + $content = $this->send("GET", "/profile/$userId/avatar_url"); + + return array_get($content, 'avatar_url'); + } + + /** + * @param string $userId + * @param string $avatarUrl + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function setAvatarUrl(string $userId, string $avatarUrl) { + $content = ['avatar_url' => $avatarUrl]; + $path = sprintf('/profile/%s/avatar_url', urlencode($userId)); + + return $this->send('PUT', $path, $content); + } + + /** + * @param string $mxcurl + * @return string + * @throws ValidationException + */ + public function getDownloadUrl(string $mxcurl): string { + Util::checkMxcUrl($mxcurl); + + return $this->baseUrl . self::MATRIX_V2_MEDIA_PATH . "/download/" . substr($mxcurl, 6); + } + + /** + * Download raw media from provided mxc URL. + * + * @param string $mxcurl mxc media URL. + * @param bool $allowRemote Indicates to the server that it should not + * attempt to fetch the media if it is deemed remote. Defaults + * to true if not provided. + * @return string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + * @throws ValidationException + */ + public function mediaDownload(string $mxcurl, bool $allowRemote = true) { + Util::checkMxcUrl($mxcurl); + $queryParam = []; + if (!$allowRemote) { + $queryParam["allow_remote"] = false; + } + $path = substr($mxcurl, 6); + $apiPath = self::MATRIX_V2_MEDIA_PATH . "/download/"; + + return $this->send('GET', $path, null, $queryParam, [], $apiPath, false); + } + + /** + * Download raw media thumbnail from provided mxc URL. + * + * @param string $mxcurl mxc media URL + * @param int $width desired thumbnail width + * @param int $height desired thumbnail height + * @param string $method thumb creation method. Must be + * in ['scale', 'crop']. Default 'scale'. + * @param bool $allowRemote indicates to the server that it should not + * attempt to fetch the media if it is deemed remote. Defaults + * to true if not provided. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + * @throws ValidationException + */ + public function getThumbnail(string $mxcurl, int $width, int $height, + string $method = 'scale', bool $allowRemote = true) { + Util::checkMxcUrl($mxcurl); + if (!in_array($method, ['scale', 'crop'])) { + throw new ValidationException('Unsupported thumb method ' . $method); + } + $queryParams = [ + "width" => $width, + "height" => $height, + "method" => $method, + ]; + if (!$allowRemote) { + $queryParams["allow_remote"] = false; + } + $path = substr($mxcurl, 6); + $apiPath = self::MATRIX_V2_MEDIA_PATH . "/thumbnail/"; + + + return $this->send('GET', $path, null, $queryParams, [], $apiPath, false); + } + + /** + * Get preview for URL. + * + * @param string $url URL to get a preview + * @param float|null $ts The preferred point in time to return + * a preview for. The server may return a newer + * version if it does not have the requested + * version available. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getUrlPreview(string $url, float $ts = null) { + $params = ['url' => $url]; + if ($ts) { + $params['ts'] = $ts; + } + $apiPath = self::MATRIX_V2_MEDIA_PATH . '/preview_url'; + + return $this->send('GET', '', null, $params, [], $apiPath); + } + + /** + * Get room id from its alias. + * + * @param string $roomAlias The room alias name. + * @return null|string Wanted room's id. + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getRoomId(string $roomAlias): ?string { + $content = $this->send('GET', sprintf("/directory/room/%s", urlencode($roomAlias))); + + return array_get($content, 'room_id'); + } + + /** + * Set alias to room id + * + * @param string $roomId The room id. + * @param string $roomAlias The room wanted alias name. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function setRoomAlias(string $roomId, string $roomAlias) { + $content = ['room_id' => $roomId]; + + return $this->send('PUT', sprintf("/directory/room/%s", urlencode($roomAlias)), $content); + } + + /** + * Remove mapping of an alias + * + * @param string $roomAlias The alias to be removed. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function removeRoomAlias(string $roomAlias) { + return $this->send('DELETE', sprintf("/directory/room/%s", urlencode($roomAlias))); + } + + /** + * Get the list of members for this room. + * + * @param string $roomId The room to get the member events for. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getRoomMembers(string $roomId) { + return $this->send('GET', sprintf("/rooms/%s/members", urlencode($roomId))); + } + + /** + * Set the rule for users wishing to join the room. + * + * @param string $roomId The room to set the rules for. + * @param string $joinRule The chosen rule. One of: ["public", "knock", "invite", "private"] + * @return array|string + * @throws MatrixException + */ + public function setJoinRule(string $roomId, string $joinRule) { + $content = ['join_rule' => $joinRule]; + + return $this->sendStateEvent($roomId, 'm.room.join_rule', $content); + } + + /** + * Set the guest access policy of the room. + * + * @param string $roomId The room to set the rules for. + * @param string $guestAccess Wether guests can join. One of: ["can_join", "forbidden"] + * @return array|string + * @throws MatrixException + */ + public function setGuestAccess(string $roomId, string $guestAccess) { + $content = ['guest_access' => $guestAccess]; + + return $this->sendStateEvent($roomId, 'm.room.guest_access', $content); + } + + /** + * Gets information about all devices for the current user. + * + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getDevices() { + return $this->send('GET', '/devices'); + } + + /** + * Gets information on a single device, by device id. + * + * @param string $deviceId + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function getDevice(string $deviceId) { + return $this->send('GET', sprintf('/devices/%s', urlencode($deviceId))); + } + + /** + * Update the display name of a device. + * + * @param string $deviceId The device ID of the device to update. + * @param string $displayName New display name for the device. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function updateDeviceInfo(string $deviceId, string $displayName) { + $content = ['display_name' => $displayName]; + + return $this->send('PUT', sprintf('/devices/%s', urlencode($deviceId)), $content); + } + + /** + * Deletes the given device, and invalidates any access token associated with it. + * + * NOTE: This endpoint uses the User-Interactive Authentication API. + * + * @param array $authBody Authentication params. + * @param string $deviceId The device ID of the device to delete. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function deleteDevice(array $authBody, string $deviceId) { + $content = ['auth' => $authBody]; + + return $this->send('DELETE', sprintf('/devices/%s', urlencode($deviceId)), $content); + } + + /** + * Bulk deletion of devices. + * + * NOTE: This endpoint uses the User-Interactive Authentication API. + * + * @param array $authBody Authentication params. + * @param array $devices List of device ID"s to delete. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function deleteDevices($authBody, $devices) { + $content = [ + 'auth' => $authBody, + 'devices' => $devices + ]; + + return $this->send('POST', '/delete_devices', $content); + } + + /** + * Publishes end-to-end encryption keys for the device. + * Said device must be the one used when logging in. + * + * @param array $deviceKeys Optional. Identity keys for the device. The required keys are: + * | user_id (str): The ID of the user the device belongs to. Must match the user ID used when logging in. + * | device_id (str): The ID of the device these keys belong to. Must match the device ID used when logging in. + * | algorithms (list): The encryption algorithms supported by this device. + * | keys (dict): Public identity keys. Should be formatted as : . + * | signatures (dict): Signatures for the device key object. Should be formatted as : {: } + * @param array $oneTimeKeys Optional. One-time public keys. Should be + * formatted as : , the key format being + * determined by the algorithm. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function uploadKeys(array $deviceKeys = [], array $oneTimeKeys = []) { + $content = []; + if ($deviceKeys) { + $content['device_keys'] = $deviceKeys; + } + if ($oneTimeKeys) { + $content['one_time_keys'] = $oneTimeKeys; + } + + return $this->send('POST', '/keys/upload', $content); + } + + /** + * Query HS for public keys by user and optionally device. + * + * @param array $userDevices The devices whose keys to download. Should be + * formatted as : []. No device_ids indicates + * all devices for the corresponding user. + * @param int $timeout Optional. The time (in milliseconds) to wait when + * downloading keys from remote servers. + * @param string $token Optional. If the client is fetching keys as a result of + * a device update received in a sync request, this should be the + * 'since' token of that sync request, or any later sync token. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function queryKeys(array $userDevices, int $timeout = null, string $token = null) { + $content = ['device_keys' => $userDevices]; + if ($timeout) { + $content['timeout'] = $timeout; + } + if ($token) { + $content['token'] = $token; + } + + return $this->send('POST', "/keys/query", $content); + } + + /** + * Claims one-time keys for use in pre-key messages. + * + * @param array $keyRequest The keys to be claimed. Format should be : { : }. + * @param int $timeout Optional. The time (in ms) to wait when downloading keys from remote servers. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function claimKeys(array $keyRequest, int $timeout) { + $content = ['one_time_keys' => $keyRequest]; + if ($timeout) { + $content['timeout'] = $timeout; + } + + return $this->send('POST', "/keys/claim", $content); + } + + /** + * Gets a list of users who have updated their device identity keys. + * + * @param string $fromToken The desired start point of the list. Should be the + * next_batch field from a response to an earlier call to /sync. + * @param string $toToken The desired end point of the list. Should be the next_batch + * field from a recent call to /sync - typically the most recent such call. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function keyChanges(string $fromToken, string $toToken) { + $params = [ + 'from' => $fromToken, + 'to' => $toToken, + ]; + + return $this->send("GET", "/keys/changes", null, $params); + } + + /** + * Sends send-to-device events to a set of client devices. + * + * @param string $eventType The type of event to send. + * @param array $messages The messages to send. Format should be + * : {: }. + * The device ID may also be '*', meaning all known devices for the user. + * @param string|null $txnId Optional. The transaction ID for this event, will be generated automatically otherwise. + * @return array|string + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function sendToDevice(string $eventType, array $messages, string $txnId = null) { + $txnId = $txnId ?: $this->makeTxnId(); + $content = ['messages' => $messages]; + $path = sprintf("/sendToDevice/%s/%s", urlencode($eventType), urlencode($txnId)); + + return $this->send('PUT', $path, $content); + } + + private function makeTxnId(): int { + $txnId = $this->txnId . (int)(microtime(true) * 1000); + $this->txnId++; + + return $txnId; + } + + /** + * Determine user_id for authenticated user. + * + * @return array + * @throws MatrixException + * @throws MatrixHttpLibException + * @throws MatrixRequestException + */ + public function whoami(): array { + if (!$this->token) { + throw new MatrixException('Authentication required.'); + } + + return $this->send('GET', '/account/whoami'); + } + + } \ No newline at end of file diff --git a/src/Util.php b/src/Util.php index 1ae3982..f5f728e 100644 --- a/src/Util.php +++ b/src/Util.php @@ -37,4 +37,10 @@ class Util { throw new ValidationException("UserIDs must have a domain component, seperated by a :"); } } + + public static function checkMxcUrl(string $mxcUrl) { + if (substr($mxcUrl, 0, 6) != 'mxc://') { + throw new ValidationException('MXC URL did not begin with \'mxc://\''); + } + } } \ No newline at end of file