mantis-matrix-integration – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 <?php
2  
3 namespace MatrixPhp;
4  
5 use MatrixPhp\Exceptions\MatrixException;
6 use MatrixPhp\Exceptions\MatrixHttpLibException;
7 use MatrixPhp\Exceptions\MatrixRequestException;
8 use MatrixPhp\Exceptions\MatrixUnexpectedResponse;
9 use MatrixPhp\Exceptions\ValidationException;
10 use GuzzleHttp\Client;
11 use GuzzleHttp\Exception\GuzzleException;
12 use GuzzleHttp\RequestOptions;
13  
14 /**
15 * Contains all raw Matrix HTTP Client-Server API calls.
16 * For room and sync handling, consider using MatrixClient.
17 *
18 * Examples:
19 * Create a client and send a message::
20 *
21 * $matrix = new MatrixHttpApi("https://matrix.org", $token="foobar");
22 * $response = $matrix.sync();
23 * $response = $matrix->sendMessage("!roomid:matrix.org", "Hello!");
24 *
25 * @see https://matrix.org/docs/spec/client_server/latest
26 *
27 * @package MatrixPhp
28 */
29 class MatrixHttpApi {
30  
31 const MATRIX_V2_API_PATH = '/_matrix/client/r0';
32 const MATRIX_V2_MEDIA_PATH = '/_matrix/media/r0';
33 const VERSION = '0.0.1-dev';
34  
35 /**
36 * @var string
37 */
38 private $baseUrl;
39  
40 /**
41 * @var string|null
42 */
43 private $token;
44  
45 /**
46 * @var string|null
47 */
48 private $identity;
49  
50 /**
51 * @var int
52 */
53 private $default429WaitMs;
54  
55 /**
56 * @var bool
57 */
58 private $useAuthorizationHeader;
59  
60 /**
61 * @var int
62 */
63 private $txnId;
64  
65 /**
66 * @var bool
67 */
68 private $validateCert;
69  
70 /**
71 * @var Client
72 */
73 private $client;
74  
75 /**
76 * MatrixHttpApi constructor.
77 *
78 * @param string $baseUrl The home server URL e.g. 'http://localhost:8008'
79 * @param string|null $token Optional. The client's access token.
80 * @param string|null $identity Optional. The mxid to act as (For application services only).
81 * @param int $default429WaitMs Optional. Time in milliseconds to wait before retrying a request
82 * when server returns a HTTP 429 response without a 'retry_after_ms' key.
83 * @param bool $useAuthorizationHeader Optional. Use Authorization header instead of access_token query parameter.
84 * @throws MatrixException
85 */
86 public function __construct(string $baseUrl, ?string $token = null, ?string $identity = null,
87 int $default429WaitMs = 5000, bool $useAuthorizationHeader = true) {
88 if (!filter_var($baseUrl, FILTER_VALIDATE_URL)) {
89 throw new MatrixException("Invalid homeserver url $baseUrl");
90 }
91  
92 if (!array_get(parse_url($baseUrl), 'scheme')) {
93 throw new MatrixException("No scheme in homeserver url $baseUrl");
94 }
95 $this->baseUrl = $baseUrl;
96 $this->token = $token;
97 $this->identity = $identity;
98 $this->txnId = 0;
99 $this->validateCert = true;
100 $this->client = new Client();
101 $this->default429WaitMs = $default429WaitMs;
102 $this->useAuthorizationHeader = $useAuthorizationHeader;
103 }
104  
105 public function setClient(Client $client) {
106 $this->client = $client;
107 }
108  
109 /**
110 * @param string|null $since Optional. A token which specifies where to continue a sync from.
111 * @param int $timeoutMs
112 * @param null $filter
113 * @param bool $fullState
114 * @param string|null $setPresence
115 * @return array|string
116 * @throws MatrixException
117 */
118 public function sync(?string $since = null, int $timeoutMs = 30000, $filter = null,
119 bool $fullState = false, ?string $setPresence = null) {
120 $request = [
121 'timeout' => (int)$timeoutMs,
122 ];
123  
124 if ($since) {
125 $request['since'] = $since;
126 }
127  
128 if ($filter) {
129 $request['filter'] = $filter;
130 }
131  
132 if ($fullState) {
133 $request['full_state'] = json_encode($fullState);
134 }
135  
136 if ($setPresence) {
137 $request['set_presence'] = $setPresence;
138 }
139  
140 return $this->send('GET', "/sync", null, $request);
141 }
142  
143 public function validateCertificate(bool $validity) {
144 $this->validateCert = $validity;
145 }
146  
147 /**
148 * Performs /register.
149 *
150 * @param array $authBody Authentication Params.
151 * @param string $kind Specify kind of account to register. Can be 'guest' or 'user'.
152 * @param bool $bindEmail Whether to use email in registration and authentication.
153 * @param string|null $username The localpart of a Matrix ID.
154 * @param string|null $password The desired password of the account.
155 * @param string|null $deviceId ID of the client device.
156 * @param string|null $initialDeviceDisplayName Display name to be assigned.
157 * @param bool $inhibitLogin Whether to login after registration. Defaults to false.
158 * @return array|string
159 * @throws MatrixException
160 */
161 public function register(array $authBody = [], string $kind = "user", bool $bindEmail = false,
162 ?string $username = null, ?string $password = null, ?string $deviceId = null,
163 ?string $initialDeviceDisplayName = null, bool $inhibitLogin = false) {
164 $content = [
165 'kind' => $kind
166 ];
167 if ($authBody) {
168 $content['auth'] = $authBody;
169 }
170 if ($username) {
171 $content['username'] = $username;
172 }
173 if ($password) {
174 $content['password'] = $password;
175 }
176 if ($deviceId) {
177 $content['device_id'] = $deviceId;
178 }
179 if ($initialDeviceDisplayName) {
180 $content['initial_device_display_name'] = $initialDeviceDisplayName;
181 }
182 if ($bindEmail) {
183 $content['bind_email'] = $bindEmail;
184 }
185 if ($inhibitLogin) {
186 $content['inhibit_login'] = $inhibitLogin;
187 }
188  
189 return $this->send('POST', '/register', $content, ['kind' => $kind]);
190 }
191  
192 /**
193 * Perform /login.
194 *
195 * @param string $loginType The value for the 'type' key.
196 * @param array $args Additional key/values to add to the JSON submitted.
197 * @return array|string
198 * @throws MatrixException
199 */
200 public function login(string $loginType, array $args) {
201 $args["type"] = $loginType;
202  
203 return $this->send('POST', '/login', $args);
204 }
205  
206 /**
207 * Perform /logout.
208 *
209 * @return array|string
210 * @throws MatrixException
211 */
212 public function logout() {
213 return $this->send('POST', '/logout');
214 }
215  
216 /**
217 * Perform /logout/all.
218 *
219 * @return array|string
220 * @throws MatrixException
221 */
222 public function logoutAll() {
223 return $this->send('POST', '/logout/all');
224 }
225  
226 /**
227 * Perform /createRoom.
228 *
229 * @param string|null $alias Optional. The room alias name to set for this room.
230 * @param string|null $name Optional. Name for new room.
231 * @param bool $isPublic Optional. The public/private visibility.
232 * @param array|null $invitees Optional. The list of user IDs to invite.
233 * @param bool|null $federate Optional. Сan a room be federated. Default to True.
234 * @return array|string
235 * @throws MatrixException
236 */
237 public function createRoom(string $alias = null, string $name = null, bool $isPublic = false,
238 array $invitees = null, bool $federate = null) {
239 $content = [
240 "visibility" => $isPublic ? "public" : "private"
241 ];
242 if ($alias) {
243 $content["room_alias_name"] = $alias;
244 }
245 if ($invitees) {
246 $content["invite"] = $invitees;
247 }
248 if ($name) {
249 $content["name"] = $name;
250 }
251 if ($federate != null) {
252 $content["creation_content"] = ['m.federate' => $federate];
253 }
254 return $this->send("POST", "/createRoom", $content);
255 }
256  
257 /**
258 * Performs /join/$room_id
259 *
260 * @param string $roomIdOrAlias The room ID or room alias to join.
261 * @return array|string
262 * @throws MatrixException
263 */
264 public function joinRoom(string $roomIdOrAlias) {
265 $path = sprintf("/join/%s", urlencode($roomIdOrAlias));
266  
267 return $this->send('POST', $path);
268 }
269  
270 /**
271 * Perform PUT /rooms/$room_id/state/$event_type
272 *
273 * @param string $roomId The room ID to send the state event in.
274 * @param string $eventType The state event type to send.
275 * @param array $content The JSON content to send.
276 * @param string $stateKey Optional. The state key for the event.
277 * @param int|null $timestamp Set origin_server_ts (For application services only)
278 * @return array|string
279 * @throws MatrixException
280 */
281 public function sendStateEvent(string $roomId, string $eventType, array $content,
282 string $stateKey = "", int $timestamp = null) {
283 $path = sprintf("/rooms/%s/state/%s", urlencode($roomId), urlencode($eventType));
284 if ($stateKey) {
285 $path .= sprintf("/%s", urlencode($stateKey));
286 }
287 $params = [];
288 if ($timestamp) {
289 $params["ts"] = $timestamp;
290 }
291  
292 return $this->send('PUT', $path, $content, $params);
293 }
294  
295 /**
296 * Perform GET /rooms/$room_id/state/$event_type
297 *
298 * @param string $roomId The room ID.
299 * @param string $eventType The type of the event.
300 * @return array|string
301 * @throws MatrixRequestException (code=404) if the state event is not found.
302 * @throws MatrixException
303 */
304 public function getStateEvent(string $roomId, string $eventType) {
305 $path = sprintf('/rooms/%s/state/%s', urlencode($roomId), urlencode($eventType));
306  
307 return $this->send('GET', $path);
308 }
309  
310 /**
311 * @param string $roomId The room ID to send the message event in.
312 * @param string $eventType The event type to send.
313 * @param array $content The JSON content to send.
314 * @param int $txnId Optional. The transaction ID to use.
315 * @param int $timestamp Set origin_server_ts (For application services only)
316 * @return array|string
317 * @throws MatrixException
318 * @throws MatrixHttpLibException
319 * @throws MatrixRequestException
320 */
321 public function sendMessageEvent(string $roomId, string $eventType, array $content,
322 int $txnId = null, int $timestamp = null) {
323 if (!$txnId) {
324 $txnId = $this->makeTxnId();
325 }
326 $path = sprintf('/rooms/%s/send/%s/%u', $roomId, urlencode($eventType), $txnId);
327 $params = [];
328 if ($timestamp) {
329 $params['ts'] = $timestamp;
330 }
331  
332 return $this->send('PUT', $path, $content, $params);
333 }
334  
335 /**
336 * Perform PUT /rooms/$room_id/redact/$event_id/$txn_id/
337 *
338 * @param string $roomId The room ID to redact the message event in.
339 * @param string $eventId The event id to redact.
340 * @param string $reason Optional. The reason the message was redacted.
341 * @param int|null $txnId Optional. The transaction ID to use.
342 * @param int|null $timestamp Optional. Set origin_server_ts (For application services only)
343 * @return array|string
344 * @throws MatrixException
345 * @throws MatrixHttpLibException
346 * @throws MatrixRequestException
347 */
348 public function redactEvent(string $roomId, string $eventId, ?string $reason = null,
349 int $txnId = null, int $timestamp = null) {
350 if (!$txnId) {
351 $txnId = $this->makeTxnId();
352 }
353 $path = sprintf('/rooms/%s/redact/%s/%u', $roomId, urlencode($eventId), $txnId);
354 $params = [];
355 $content = [];
356 if ($reason) {
357 $content['reason'] = $reason;
358 }
359 if ($timestamp) {
360 $params['ts'] = $timestamp;
361 }
362  
363 return $this->send('PUT', $path, $content, $params);
364 }
365  
366 /**
367 * $content_type can be a image,audio or video
368 * extra information should be supplied, see
369 * https://matrix.org/docs/spec/r0.0.1/client_server.html
370 *
371 * @param string $roomId
372 * @param string $itemUrl
373 * @param string $itemName
374 * @param string $msgType
375 * @param array $extraInformation
376 * @param int|null $timestamp
377 * @return array|string
378 * @throws MatrixException
379 * @throws MatrixHttpLibException
380 * @throws MatrixRequestException
381 */
382 public function sendContent(string $roomId, string $itemUrl, string $itemName, string $msgType,
383 array $extraInformation = [], int $timestamp = null) {
384 $contentPack = [
385 "url" => $itemUrl,
386 "msgtype" => $msgType,
387 "body" => $itemName,
388 "info" => $extraInformation,
389 ];
390  
391 return $this->sendMessageEvent($roomId, 'm.room.message', $contentPack, null, $timestamp);
392 }
393  
394  
395 /**
396 * Send m.location message event
397 * http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location
398 *
399 * @param string $roomId The room ID to send the event in.
400 * @param string $geoUri The geo uri representing the location.
401 * @param string $name Description for the location.
402 * @param string|null $thumbUrl URL to the thumbnail of the location.
403 * @param array|null $thumbInfo Metadata about the thumbnail, type ImageInfo.
404 * @param int|null $timestamp Set origin_server_ts (For application services only)
405 * @return array|string
406 * @throws MatrixException
407 * @throws MatrixHttpLibException
408 * @throws MatrixRequestException
409 */
410 public function sendLocation(string $roomId, string $geoUri, string $name, string $thumbUrl = null,
411 array $thumbInfo = null, int $timestamp = null) {
412 $contentPack = [
413 "geo_uri" => $geoUri,
414 "msgtype" => "m.location",
415 "body" => $name,
416 ];
417 if ($thumbUrl) {
418 $contentPack['thumbnail_url'] = $thumbUrl;
419 }
420 if ($thumbInfo) {
421 $contentPack['thumbnail_info'] = $thumbInfo;
422 }
423  
424 return $this->sendMessageEvent($roomId, 'm.room.message', $contentPack, null, $timestamp);
425 }
426  
427 /**
428 * Perform PUT /rooms/$room_id/send/m.room.message
429 *
430 * @param string $roomId The room ID to send the event in.
431 * @param string $textContent The m.text body to send.
432 * @param string $msgType
433 * @param int|null $timestamp Set origin_server_ts (For application services only)
434 * @return array|string
435 * @throws MatrixException
436 * @throws MatrixHttpLibException
437 * @throws MatrixRequestException
438 */
439 public function sendMessage(string $roomId, string $textContent, string $msgType = 'm.text', int $timestamp = null) {
440 $textBody = $this->getTextBody($textContent, $msgType);
441  
442 return $this->sendMessageEvent($roomId, 'm.room.message', $textBody, null, $timestamp);
443 }
444  
445 /**
446 * Perform PUT /rooms/$room_id/send/m.room.message with m.emote msgtype
447 *
448 * @param string $roomId The room ID to send the event in.
449 * @param string $textContent The m.emote body to send.
450 * @param int|null $timestamp Set origin_server_ts (For application services only)
451 * @return array|string
452 * @throws MatrixException
453 * @throws MatrixHttpLibException
454 * @throws MatrixRequestException
455 */
456 public function sendEmote(string $roomId, string $textContent, int $timestamp = null) {
457 $body = $this->getEmoteBody($textContent);
458  
459 return $this->sendMessageEvent($roomId, 'm.room.message', $body, null, $timestamp);
460 }
461  
462 /**
463 * Perform PUT /rooms/$room_id/send/m.room.message with m.notice msgtype
464 *
465 * @param string $roomId The room ID to send the event in.
466 * @param string $textContent The m.emote body to send.
467 * @param int|null $timestamp Set origin_server_ts (For application services only)
468 * @return array|string
469 * @throws MatrixException
470 * @throws MatrixHttpLibException
471 * @throws MatrixRequestException
472 */
473 public function sendNotice(string $roomId, string $textContent, int $timestamp = null) {
474 $body = [
475 'msgtype' => 'm.notice',
476 'body' => $textContent,
477 ];
478  
479 return $this->sendMessageEvent($roomId, 'm.room.message', $body, null, $timestamp);
480 }
481  
482 /**
483 * @param string $roomId The room's id.
484 * @param string $token The token to start returning events from.
485 * @param string $direction The direction to return events from. One of: ["b", "f"].
486 * @param int $limit The maximum number of events to return.
487 * @param string|null $to The token to stop returning events at.
488 * @return array|string
489 * @throws MatrixException
490 * @throws MatrixHttpLibException
491 * @throws MatrixRequestException
492 */
493 public function getRoomMessages(string $roomId, string $token, string $direction, int $limit = 10, string $to = null) {
494 $query = [
495 "roomId" => $roomId,
496 "from" => $token,
497 'dir' => $direction,
498 'limit' => $limit,
499 ];
500  
501 if ($to) {
502 $query['to'] = $to;
503 }
504 $path = sprintf('/rooms/%s/messages', urlencode($roomId));
505  
506 return $this->send('GET', $path, null, $query);
507 }
508  
509 /**
510 * Perform GET /rooms/$room_id/state/m.room.name
511 *
512 * @param string $roomId The room ID
513 * @return array|string
514 * @throws MatrixException
515 * @throws MatrixRequestException
516 */
517 public function getRoomName(string $roomId) {
518 return $this->getStateEvent($roomId, 'm.room.name');
519 }
520  
521 /**
522 * Perform PUT /rooms/$room_id/state/m.room.name
523 *
524 * @param string $roomId The room ID
525 * @param string $name The new room name
526 * @param int|null $timestamp Set origin_server_ts (For application services only)
527 * @return array|string
528 * @throws MatrixException
529 */
530 public function setRoomName(string $roomId, string $name, int $timestamp = null) {
531 $body = ['name' => $name];
532  
533 return $this->sendStateEvent($roomId, 'm.room.name', $body, '', $timestamp);
534 }
535  
536 /**
537 * Perform GET /rooms/$room_id/state/m.room.topic
538 *
539 * @param string $roomId The room ID
540 * @return array|string
541 * @throws MatrixException
542 * @throws MatrixRequestException
543 */
544 public function getRoomTopic(string $roomId) {
545 return $this->getStateEvent($roomId, 'm.room.topic');
546 }
547  
548 /**
549 * Perform PUT /rooms/$room_id/state/m.room.topic
550 *
551 * @param string $roomId The room ID
552 * @param string $topic The new room topic
553 * @param int|null $timestamp Set origin_server_ts (For application services only)
554 * @return array|string
555 * @throws MatrixException
556 */
557 public function setRoomTopic(string $roomId, string $topic, int $timestamp = null) {
558 $body = ['topic' => $topic];
559  
560 return $this->sendStateEvent($roomId, 'm.room.topic', $body, '', $timestamp);
561 }
562  
563  
564 /**
565 * Perform GET /rooms/$room_id/state/m.room.power_levels
566 *
567 *
568 * @param string $roomId The room ID
569 * @return array|string
570 * @throws MatrixException
571 * @throws MatrixRequestException
572 */
573 public function getPowerLevels(string $roomId) {
574 return $this->getStateEvent($roomId, 'm.room.power_levels');
575 }
576  
577 /**
578 * Perform PUT /rooms/$room_id/state/m.room.power_levels
579 *
580 * Note that any power levels which are not explicitly specified
581 * in the content arg are reset to default values.
582 *
583 *
584 * Example:
585 * $api = new MatrixHttpApi("http://example.com", $token="foobar")
586 * $api->setPowerLevels("!exampleroom:example.com",
587 * [
588 * "ban" => 50, # defaults to 50 if unspecified
589 * "events": [
590 * "m.room.name" => 100, # must have PL 100 to change room name
591 * "m.room.power_levels" => 100 # must have PL 100 to change PLs
592 * ],
593 * "events_default" => 0, # defaults to 0
594 * "invite" => 50, # defaults to 50
595 * "kick" => 50, # defaults to 50
596 * "redact" => 50, # defaults to 50
597 * "state_default" => 50, # defaults to 50 if m.room.power_levels exists
598 * "users" => [
599 * "@someguy:example.com" => 100 # defaults to 0
600 * ],
601 * "users_default" => 0 # defaults to 0
602 * ]
603 * );
604 *
605 * @param string $roomId
606 * @param array $content
607 * @return array|string
608 * @throws MatrixException
609 */
610 public function setPowerLevels(string $roomId, array $content) {
611 // Synapse returns M_UNKNOWN if body['events'] is omitted,
612 // as of 2016-10-31
613 if (!array_key_exists('events', $content)) {
614 $content['events'] = [];
615 }
616  
617 return $this->sendStateEvent($roomId, 'm.room.power_levels', $content);
618 }
619  
620 /**
621 * Perform POST /rooms/$room_id/leave
622 *
623 * @param string $roomId The room ID
624 * @return array|string
625 * @throws MatrixException
626 * @throws MatrixHttpLibException
627 * @throws MatrixRequestException
628 */
629 public function leaveRoom(string $roomId) {
630 return $this->send('POST', sprintf('/rooms/%s/leave', urlencode($roomId)));
631 }
632  
633 /**
634 * Perform POST /rooms/$room_id/forget
635 *
636 * @param string $roomId The room ID
637 * @return array|string
638 * @throws MatrixException
639 * @throws MatrixHttpLibException
640 * @throws MatrixRequestException
641 */
642 public function forgetRoom(string $roomId) {
643 return $this->send('POST', sprintf('/rooms/%s/forget', urlencode($roomId)), []);
644 }
645  
646 /**
647 * Perform POST /rooms/$room_id/invite
648 *
649 * @param string $roomId The room ID
650 * @param string $userId The user ID of the invitee
651 * @return array|string
652 * @throws MatrixException
653 * @throws MatrixHttpLibException
654 * @throws MatrixRequestException
655 */
656 public function inviteUser(string $roomId, string $userId) {
657 $body = ['user_id' => $userId];
658  
659 return $this->send('POST', sprintf('/rooms/%s/invite', urlencode($roomId)), $body);
660 }
661  
662 /**
663 * Calls set_membership with membership="leave" for the user_id provided
664 *
665 * @param string $roomId The room ID
666 * @param string $userId The user ID
667 * @param string $reason Optional. The reason for kicking them out
668 * @return mixed
669 * @throws MatrixException
670 */
671 public function kickUser(string $roomId, string $userId, string $reason = '') {
672 return $this->setMembership($roomId, $userId, 'leave', $reason);
673 }
674  
675 /**
676 * Perform GET /rooms/$room_id/state/m.room.member/$user_id
677 *
678 * @param string $roomId The room ID
679 * @param string $userId The user ID
680 * @return array|string
681 * @throws MatrixException
682 * @throws MatrixHttpLibException
683 * @throws MatrixRequestException
684 */
685 public function getMembership(string $roomId, string $userId) {
686 $path = sprintf('/rooms/%s/state/m.room.member/%s', urlencode($roomId), urlencode($userId));
687  
688 return $this->send('GET', $path);
689 }
690  
691 /**
692 * Perform PUT /rooms/$room_id/state/m.room.member/$user_id
693 *
694 * @param string $roomId The room ID
695 * @param string $userId The user ID
696 * @param string $membership New membership value
697 * @param string $reason The reason
698 * @param array $profile
699 * @param int|null $timestamp Set origin_server_ts (For application services only)
700 * @return array|string
701 * @throws MatrixException
702 */
703 public function setMembership(string $roomId, string $userId, string $membership, string $reason = '', array $profile = [], int $timestamp = null) {
704 $body = [
705 'membership' => $membership,
706 'reason' => $reason,
707 ];
708 if (array_key_exists('displayname', $profile)) {
709 $body['displayname'] = $profile['displayname'];
710 }
711 if (array_key_exists('avatar_url', $profile)) {
712 $body['avatar_url'] = $profile['avatar_url'];
713 }
714  
715 return $this->sendStateEvent($roomId, 'm.room.member', $body, $userId, $timestamp);
716 }
717  
718 /**
719 * Perform POST /rooms/$room_id/ban
720 *
721 * @param string $roomId The room ID
722 * @param string $userId The user ID of the banee(sic)
723 * @param string $reason The reason for this ban
724 * @return array|string
725 * @throws MatrixException
726 * @throws MatrixHttpLibException
727 * @throws MatrixRequestException
728 */
729 public function banUser(string $roomId, string $userId, string $reason = '') {
730 $body = [
731 'user_id' => $userId,
732 'reason' => $reason,
733 ];
734  
735 return $this->send('POST', sprintf('/rooms/%s/ban', urlencode($roomId)), $body);
736 }
737  
738 /**
739 * Perform POST /rooms/$room_id/unban
740 *
741 * @param string $roomId The room ID
742 * @param string $userId The user ID of the banee(sic)
743 * @return array|string
744 * @throws MatrixException
745 * @throws MatrixHttpLibException
746 * @throws MatrixRequestException
747 */
748 public function unbanUser(string $roomId, string $userId) {
749 $body = [
750 'user_id' => $userId,
751 ];
752  
753 return $this->send('POST', sprintf('/rooms/%s/unban', urlencode($roomId)), $body);
754 }
755  
756 /**
757 * @param string $userId
758 * @param string $roomId
759 * @return array|string
760 * @throws MatrixException
761 * @throws MatrixHttpLibException
762 * @throws MatrixRequestException
763 */
764 public function getUserTags(string $userId, string $roomId) {
765 $path = sprintf('/user/%s/rooms/%s/tags', urlencode($userId), urlencode($roomId));
766  
767 return $this->send('GET', $path);
768 }
769  
770 /**
771 * @param string $userId
772 * @param string $roomId
773 * @param string $tag
774 * @return array|string
775 * @throws MatrixException
776 * @throws MatrixHttpLibException
777 * @throws MatrixRequestException
778 */
779 public function removeUserTag(string $userId, string $roomId, string $tag) {
780 $path = sprintf('/user/%s/rooms/%s/tags/%s', urlencode($userId), urlencode($roomId), urlencode($tag));
781  
782 return $this->send('DELETE', $path);
783 }
784  
785 /**
786 * @param string $userId
787 * @param string $roomId
788 * @param string $tag
789 * @param float|null $order
790 * @param array $body
791 * @return array|string
792 * @throws MatrixException
793 * @throws MatrixHttpLibException
794 * @throws MatrixRequestException
795 */
796 public function addUserTag(string $userId, string $roomId, string $tag, ?float $order = null, array $body = []) {
797 if ($order) {
798 $body['order'] = $order;
799 }
800 $path = sprintf('/user/%s/rooms/%s/tags/%s', urlencode($userId), urlencode($roomId), urlencode($tag));
801  
802 return $this->send('PUT', $path, $body);
803 }
804  
805 /**
806 * @param string $userId
807 * @param string $type
808 * @param array $accountData
809 * @return array|string
810 * @throws MatrixException
811 * @throws MatrixHttpLibException
812 * @throws MatrixRequestException
813 */
814 public function setAccountData(string $userId, string $type, array $accountData) {
815 $path = sprintf("/user/%s/account_data/%s", urlencode($userId), urlencode($type));
816  
817 return $this->send('PUT', $path, $accountData);
818 }
819  
820 /**
821 * @param string $userId
822 * @param string $roomId
823 * @param string $type
824 * @param array $accountData
825 * @return array|string
826 * @throws MatrixException
827 * @throws MatrixHttpLibException
828 * @throws MatrixRequestException
829 */
830 public function setRoomAccountData(string $userId, string $roomId, string $type, array $accountData) {
831 $path = sprintf(
832 '/user/%s/rooms/%s/account_data/%s',
833 urlencode($userId), urlencode($roomId), urlencode($type)
834 );
835  
836 return $this->send('PUT', $path, $accountData);
837 }
838  
839 /**
840 * Perform GET /rooms/$room_id/state
841 *
842 * @param string $roomId The room ID
843 * @return array|string
844 * @throws MatrixException
845 * @throws MatrixHttpLibException
846 * @throws MatrixRequestException
847 */
848 public function getRoomState(string $roomId) {
849 return $this->send('GET', sprintf('/rooms/%s/state', urlencode($roomId)));
850 }
851  
852 public function getTextBody(string $textContent, string $msgType = 'm.text'): array {
853 return [
854 'msgtype' => $msgType,
855 'body' => $textContent,
856 ];
857 }
858  
859 private function getEmoteBody(string $textContent): array {
860 return $this->getTextBody($textContent, 'm.emote');
861 }
862  
863 /**
864 * @param string $userId
865 * @param string $filterId
866 * @return array|string
867 * @throws MatrixException
868 * @throws MatrixHttpLibException
869 * @throws MatrixRequestException
870 */
871 public function getFilter(string $userId, string $filterId) {
872 $path = sprintf("/user/%s/filter/%s", urlencode($userId), urlencode($filterId));
873  
874 return $this->send('GET', $path);
875 }
876  
877 /**
878 * @param string $userId
879 * @param array $filterParams
880 * @return array|string
881 * @throws MatrixException
882 * @throws MatrixHttpLibException
883 * @throws MatrixRequestException
884 */
885 public function createFilter(string $userId, array $filterParams) {
886 $path = sprintf("/user/%s/filter", urlencode($userId));
887  
888 return $this->send('POST', $path, $filterParams);
889 }
890  
891 /**
892 * @param string $method
893 * @param string $path
894 * @param mixed $content
895 * @param array $queryParams
896 * @param array $headers
897 * @param string $apiPath
898 * @param bool $returnJson
899 * @return array|string
900 * @throws MatrixException
901 * @throws MatrixRequestException
902 * @throws MatrixHttpLibException
903 */
904 private function send(string $method, string $path, $content = null, array $queryParams = [], array $headers = [],
905 $apiPath = self::MATRIX_V2_API_PATH, $returnJson = true) {
906 $options = [];
907 if (!in_array('User-Agent', $headers)) {
908 $headers['User-Agent'] = 'php-matrix-sdk/' . self::VERSION;
909 }
910  
911 $method = strtoupper($method);
912 if (!in_array($method, ['GET', 'POST', 'PUT', 'DELETE'])) {
913 throw new MatrixException("Unsupported HTTP method: $method");
914 }
915  
916 if (!in_array('Content-Type', $headers)) {
917 $headers['Content-Type'] = 'application/json';
918 }
919  
920 if ($this->useAuthorizationHeader) {
921 $headers['Authorization'] = sprintf('Bearer %s', $this->token);
922 } else {
923 $queryParams['access_token'] = $this->token;
924 }
925  
926 if ($this->identity) {
927 $queryParams['user_id'] = $this->identity;
928 }
929  
930 $options = array_merge($options, [
931 RequestOptions::HEADERS => $headers,
932 RequestOptions::QUERY => $queryParams,
933 RequestOptions::VERIFY => $this->validateCert,
934 RequestOptions::HTTP_ERRORS => FALSE,
935 ]);
936  
937 $endpoint = $this->baseUrl . $apiPath . $path;
938 if ($headers['Content-Type'] == "application/json" && $content !== null) {
939 $options[RequestOptions::JSON] = $content;
940 }
941 else {
942 $options[RequestOptions::FORM_PARAMS] = $content;
943 }
944  
945 $responseBody = '';
946 while (true) {
947 try {
948 $response = $this->client->request($method, $endpoint, $options);
949 } catch (GuzzleException $e) {
950 throw new MatrixHttpLibException($e, $method, $endpoint);
951 }
952  
953 $responseBody = $response->getBody()->getContents();
954  
955 if ($response->getStatusCode() >= 500) {
956 throw new MatrixUnexpectedResponse($responseBody);
957 }
958  
959 if ($response->getStatusCode() != 429) {
960 break;
961 }
962  
963 $jsonResponse = json_decode($responseBody, true);
964 $waitTime = array_get($jsonResponse, 'retry_after_ms');
965 $waitTime = $waitTime ?: array_get($jsonResponse, 'error.retry_after_ms', $this->default429WaitMs);
966 $waitTime /= 1000;
967 sleep($waitTime);
968 }
969  
970 if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
971 throw new MatrixRequestException($response->getStatusCode(), $responseBody);
972 }
973  
974 return $returnJson ? json_decode($responseBody, true) : $responseBody;
975 }
976  
977 /**
978 * @param $content
979 * @param string $contentType
980 * @param string|null $filename
981 * @return array|string
982 * @throws MatrixException
983 * @throws MatrixHttpLibException
984 * @throws MatrixRequestException
985 */
986 public function mediaUpload($content, string $contentType, string $filename = null) {
987 $headers = ['Content-Type' => $contentType];
988 $apiPath = self::MATRIX_V2_MEDIA_PATH . "/upload";
989 $queryParam = [];
990 if ($filename) {
991 $queryParam['filename'] = $filename;
992 }
993  
994 return $this->send('POST', '', $content, $queryParam, $headers, $apiPath);
995 }
996  
997 /**
998 * @param string $userId
999 * @return string|null
1000 * @throws MatrixException
1001 * @throws MatrixHttpLibException
1002 * @throws MatrixRequestException
1003 */
1004 public function getDisplayName(string $userId): ?string {
1005 $content = $this->send("GET", "/profile/$userId/displayname");
1006  
1007 return array_get($content, 'displayname');
1008 }
1009  
1010 /**
1011 * @param string $userId
1012 * @param string $displayName
1013 * @return array|string
1014 * @throws MatrixException
1015 * @throws MatrixHttpLibException
1016 * @throws MatrixRequestException
1017 */
1018 public function setDisplayName(string $userId, string $displayName) {
1019 $content = ['displayname' => $displayName];
1020 $path = sprintf('/profile/%s/displayname', urlencode($userId));
1021  
1022 return $this->send('PUT', $path, $content);
1023 }
1024  
1025 /**
1026 * @param string $userId
1027 * @return ?string
1028 * @throws MatrixException
1029 * @throws MatrixHttpLibException
1030 * @throws MatrixRequestException
1031 */
1032 public function getAvatarUrl(string $userId): ?string {
1033 $content = $this->send("GET", "/profile/$userId/avatar_url");
1034  
1035 return array_get($content, 'avatar_url');
1036 }
1037  
1038 /**
1039 * @param string $userId
1040 * @param string $avatarUrl
1041 * @return array|string
1042 * @throws MatrixException
1043 * @throws MatrixHttpLibException
1044 * @throws MatrixRequestException
1045 */
1046 public function setAvatarUrl(string $userId, string $avatarUrl) {
1047 $content = ['avatar_url' => $avatarUrl];
1048 $path = sprintf('/profile/%s/avatar_url', urlencode($userId));
1049  
1050 return $this->send('PUT', $path, $content);
1051 }
1052  
1053 /**
1054 * @param string $mxcurl
1055 * @return string
1056 * @throws ValidationException
1057 */
1058 public function getDownloadUrl(string $mxcurl): string {
1059 Util::checkMxcUrl($mxcurl);
1060  
1061 return $this->baseUrl . self::MATRIX_V2_MEDIA_PATH . "/download/" . substr($mxcurl, 6);
1062 }
1063  
1064 /**
1065 * Download raw media from provided mxc URL.
1066 *
1067 * @param string $mxcurl mxc media URL.
1068 * @param bool $allowRemote Indicates to the server that it should not
1069 * attempt to fetch the media if it is deemed remote. Defaults
1070 * to true if not provided.
1071 * @return string
1072 * @throws MatrixException
1073 * @throws MatrixHttpLibException
1074 * @throws MatrixRequestException
1075 * @throws ValidationException
1076 */
1077 public function mediaDownload(string $mxcurl, bool $allowRemote = true) {
1078 Util::checkMxcUrl($mxcurl);
1079 $queryParam = [];
1080 if (!$allowRemote) {
1081 $queryParam["allow_remote"] = false;
1082 }
1083 $path = substr($mxcurl, 6);
1084 $apiPath = self::MATRIX_V2_MEDIA_PATH . "/download/";
1085  
1086 return $this->send('GET', $path, null, $queryParam, [], $apiPath, false);
1087 }
1088  
1089 /**
1090 * Download raw media thumbnail from provided mxc URL.
1091 *
1092 * @param string $mxcurl mxc media URL
1093 * @param int $width desired thumbnail width
1094 * @param int $height desired thumbnail height
1095 * @param string $method thumb creation method. Must be
1096 * in ['scale', 'crop']. Default 'scale'.
1097 * @param bool $allowRemote indicates to the server that it should not
1098 * attempt to fetch the media if it is deemed remote. Defaults
1099 * to true if not provided.
1100 * @return array|string
1101 * @throws MatrixException
1102 * @throws MatrixHttpLibException
1103 * @throws MatrixRequestException
1104 * @throws ValidationException
1105 */
1106 public function getThumbnail(string $mxcurl, int $width, int $height,
1107 string $method = 'scale', bool $allowRemote = true) {
1108 Util::checkMxcUrl($mxcurl);
1109 if (!in_array($method, ['scale', 'crop'])) {
1110 throw new ValidationException('Unsupported thumb method ' . $method);
1111 }
1112 $queryParams = [
1113 "width" => $width,
1114 "height" => $height,
1115 "method" => $method,
1116 ];
1117 if (!$allowRemote) {
1118 $queryParams["allow_remote"] = false;
1119 }
1120 $path = substr($mxcurl, 6);
1121 $apiPath = self::MATRIX_V2_MEDIA_PATH . "/thumbnail/";
1122  
1123  
1124 return $this->send('GET', $path, null, $queryParams, [], $apiPath, false);
1125 }
1126  
1127 /**
1128 * Get preview for URL.
1129 *
1130 * @param string $url URL to get a preview
1131 * @param float|null $ts The preferred point in time to return
1132 * a preview for. The server may return a newer
1133 * version if it does not have the requested
1134 * version available.
1135 * @return array|string
1136 * @throws MatrixException
1137 * @throws MatrixHttpLibException
1138 * @throws MatrixRequestException
1139 */
1140 public function getUrlPreview(string $url, float $ts = null) {
1141 $params = ['url' => $url];
1142 if ($ts) {
1143 $params['ts'] = $ts;
1144 }
1145 $apiPath = self::MATRIX_V2_MEDIA_PATH . '/preview_url';
1146  
1147 return $this->send('GET', '', null, $params, [], $apiPath);
1148 }
1149  
1150 /**
1151 * Get room id from its alias.
1152 *
1153 * @param string $roomAlias The room alias name.
1154 * @return null|string Wanted room's id.
1155 * @throws MatrixException
1156 * @throws MatrixHttpLibException
1157 * @throws MatrixRequestException
1158 */
1159 public function getRoomId(string $roomAlias): ?string {
1160 $content = $this->send('GET', sprintf("/directory/room/%s", urlencode($roomAlias)));
1161  
1162 return array_get($content, 'room_id');
1163 }
1164  
1165 /**
1166 * Set alias to room id
1167 *
1168 * @param string $roomId The room id.
1169 * @param string $roomAlias The room wanted alias name.
1170 * @return array|string
1171 * @throws MatrixException
1172 * @throws MatrixHttpLibException
1173 * @throws MatrixRequestException
1174 */
1175 public function setRoomAlias(string $roomId, string $roomAlias) {
1176 $content = ['room_id' => $roomId];
1177  
1178 return $this->send('PUT', sprintf("/directory/room/%s", urlencode($roomAlias)), $content);
1179 }
1180  
1181 /**
1182 * Remove mapping of an alias
1183 *
1184 * @param string $roomAlias The alias to be removed.
1185 * @return array|string
1186 * @throws MatrixException
1187 * @throws MatrixHttpLibException
1188 * @throws MatrixRequestException
1189 */
1190 public function removeRoomAlias(string $roomAlias) {
1191 return $this->send('DELETE', sprintf("/directory/room/%s", urlencode($roomAlias)));
1192 }
1193  
1194 /**
1195 * Get the list of members for this room.
1196 *
1197 * @param string $roomId The room to get the member events for.
1198 * @return array|string
1199 * @throws MatrixException
1200 * @throws MatrixHttpLibException
1201 * @throws MatrixRequestException
1202 */
1203 public function getRoomMembers(string $roomId) {
1204 return $this->send('GET', sprintf("/rooms/%s/members", urlencode($roomId)));
1205 }
1206  
1207 /**
1208 * Set the rule for users wishing to join the room.
1209 *
1210 * @param string $roomId The room to set the rules for.
1211 * @param string $joinRule The chosen rule. One of: ["public", "knock", "invite", "private"]
1212 * @return array|string
1213 * @throws MatrixException
1214 */
1215 public function setJoinRule(string $roomId, string $joinRule) {
1216 $content = ['join_rule' => $joinRule];
1217  
1218 return $this->sendStateEvent($roomId, 'm.room.join_rule', $content);
1219 }
1220  
1221 /**
1222 * Set the guest access policy of the room.
1223 *
1224 * @param string $roomId The room to set the rules for.
1225 * @param string $guestAccess Wether guests can join. One of: ["can_join", "forbidden"]
1226 * @return array|string
1227 * @throws MatrixException
1228 */
1229 public function setGuestAccess(string $roomId, string $guestAccess) {
1230 $content = ['guest_access' => $guestAccess];
1231  
1232 return $this->sendStateEvent($roomId, 'm.room.guest_access', $content);
1233 }
1234  
1235 /**
1236 * Gets information about all devices for the current user.
1237 *
1238 * @return array|string
1239 * @throws MatrixException
1240 * @throws MatrixHttpLibException
1241 * @throws MatrixRequestException
1242 */
1243 public function getDevices() {
1244 return $this->send('GET', '/devices');
1245 }
1246  
1247 /**
1248 * Gets information on a single device, by device id.
1249 *
1250 * @param string $deviceId
1251 * @return array|string
1252 * @throws MatrixException
1253 * @throws MatrixHttpLibException
1254 * @throws MatrixRequestException
1255 */
1256 public function getDevice(string $deviceId) {
1257 return $this->send('GET', sprintf('/devices/%s', urlencode($deviceId)));
1258 }
1259  
1260 /**
1261 * Update the display name of a device.
1262 *
1263 * @param string $deviceId The device ID of the device to update.
1264 * @param string $displayName New display name for the device.
1265 * @return array|string
1266 * @throws MatrixException
1267 * @throws MatrixHttpLibException
1268 * @throws MatrixRequestException
1269 */
1270 public function updateDeviceInfo(string $deviceId, string $displayName) {
1271 $content = ['display_name' => $displayName];
1272  
1273 return $this->send('PUT', sprintf('/devices/%s', urlencode($deviceId)), $content);
1274 }
1275  
1276 /**
1277 * Deletes the given device, and invalidates any access token associated with it.
1278 *
1279 * NOTE: This endpoint uses the User-Interactive Authentication API.
1280 *
1281 * @param array $authBody Authentication params.
1282 * @param string $deviceId The device ID of the device to delete.
1283 * @return array|string
1284 * @throws MatrixException
1285 * @throws MatrixHttpLibException
1286 * @throws MatrixRequestException
1287 */
1288 public function deleteDevice(array $authBody, string $deviceId) {
1289 $content = ['auth' => $authBody];
1290  
1291 return $this->send('DELETE', sprintf('/devices/%s', urlencode($deviceId)), $content);
1292 }
1293  
1294 /**
1295 * Bulk deletion of devices.
1296 *
1297 * NOTE: This endpoint uses the User-Interactive Authentication API.
1298 *
1299 * @param array $authBody Authentication params.
1300 * @param array $devices List of device ID"s to delete.
1301 * @return array|string
1302 * @throws MatrixException
1303 * @throws MatrixHttpLibException
1304 * @throws MatrixRequestException
1305 */
1306 public function deleteDevices($authBody, $devices) {
1307 $content = [
1308 'auth' => $authBody,
1309 'devices' => $devices
1310 ];
1311  
1312 return $this->send('POST', '/delete_devices', $content);
1313 }
1314  
1315 /**
1316 * Publishes end-to-end encryption keys for the device.
1317 * Said device must be the one used when logging in.
1318 *
1319 * @param array $deviceKeys Optional. Identity keys for the device. The required keys are:
1320 * | user_id (str): The ID of the user the device belongs to. Must match the user ID used when logging in.
1321 * | device_id (str): The ID of the device these keys belong to. Must match the device ID used when logging in.
1322 * | algorithms (list<str>): The encryption algorithms supported by this device.
1323 * | keys (dict): Public identity keys. Should be formatted as <algorithm:device_id>: <key>.
1324 * | signatures (dict): Signatures for the device key object. Should be formatted as <user_id>: {<algorithm:device_id>: <key>}
1325 * @param array $oneTimeKeys Optional. One-time public keys. Should be
1326 * formatted as <algorithm:key_id>: <key>, the key format being
1327 * determined by the algorithm.
1328 * @return array|string
1329 * @throws MatrixException
1330 * @throws MatrixHttpLibException
1331 * @throws MatrixRequestException
1332 */
1333 public function uploadKeys(array $deviceKeys = [], array $oneTimeKeys = []) {
1334 $content = [];
1335 if ($deviceKeys) {
1336 $content['device_keys'] = $deviceKeys;
1337 }
1338 if ($oneTimeKeys) {
1339 $content['one_time_keys'] = $oneTimeKeys;
1340 }
1341  
1342 return $this->send('POST', '/keys/upload', $content ?: null);
1343 }
1344  
1345 /**
1346 * Query HS for public keys by user and optionally device.
1347 *
1348 * @param array $userDevices The devices whose keys to download. Should be
1349 * formatted as <user_id>: [<device_ids>]. No device_ids indicates
1350 * all devices for the corresponding user.
1351 * @param int $timeout Optional. The time (in milliseconds) to wait when
1352 * downloading keys from remote servers.
1353 * @param string $token Optional. If the client is fetching keys as a result of
1354 * a device update received in a sync request, this should be the
1355 * 'since' token of that sync request, or any later sync token.
1356 * @return array|string
1357 * @throws MatrixException
1358 * @throws MatrixHttpLibException
1359 * @throws MatrixRequestException
1360 */
1361 public function queryKeys(array $userDevices, int $timeout = null, string $token = null) {
1362 $content = ['device_keys' => $userDevices];
1363 if ($timeout) {
1364 $content['timeout'] = $timeout;
1365 }
1366 if ($token) {
1367 $content['token'] = $token;
1368 }
1369  
1370 return $this->send('POST', "/keys/query", $content);
1371 }
1372  
1373 /**
1374 * Claims one-time keys for use in pre-key messages.
1375 *
1376 * @param array $keyRequest The keys to be claimed. Format should be <user_id>: { <device_id>: <algorithm> }.
1377 * @param int $timeout Optional. The time (in ms) to wait when downloading keys from remote servers.
1378 * @return array|string
1379 * @throws MatrixException
1380 * @throws MatrixHttpLibException
1381 * @throws MatrixRequestException
1382 */
1383 public function claimKeys(array $keyRequest, int $timeout) {
1384 $content = ['one_time_keys' => $keyRequest];
1385 if ($timeout) {
1386 $content['timeout'] = $timeout;
1387 }
1388  
1389 return $this->send('POST', "/keys/claim", $content);
1390 }
1391  
1392 /**
1393 * Gets a list of users who have updated their device identity keys.
1394 *
1395 * @param string $fromToken The desired start point of the list. Should be the
1396 * next_batch field from a response to an earlier call to /sync.
1397 * @param string $toToken The desired end point of the list. Should be the next_batch
1398 * field from a recent call to /sync - typically the most recent such call.
1399 * @return array|string
1400 * @throws MatrixException
1401 * @throws MatrixHttpLibException
1402 * @throws MatrixRequestException
1403 */
1404 public function keyChanges(string $fromToken, string $toToken) {
1405 $params = [
1406 'from' => $fromToken,
1407 'to' => $toToken,
1408 ];
1409  
1410 return $this->send("GET", "/keys/changes", null, $params);
1411 }
1412  
1413 /**
1414 * Sends send-to-device events to a set of client devices.
1415 *
1416 * @param string $eventType The type of event to send.
1417 * @param array $messages The messages to send. Format should be
1418 * <user_id>: {<device_id>: <event_content>}.
1419 * The device ID may also be '*', meaning all known devices for the user.
1420 * @param string|null $txnId Optional. The transaction ID for this event, will be generated automatically otherwise.
1421 * @return array|string
1422 * @throws MatrixException
1423 * @throws MatrixHttpLibException
1424 * @throws MatrixRequestException
1425 */
1426 public function sendToDevice(string $eventType, array $messages, string $txnId = null) {
1427 $txnId = $txnId ?: $this->makeTxnId();
1428 $content = ['messages' => $messages];
1429 $path = sprintf("/sendToDevice/%s/%s", urlencode($eventType), urlencode($txnId));
1430  
1431 return $this->send('PUT', $path, $content);
1432 }
1433  
1434 private function makeTxnId(): int {
1435 $txnId = (int) ($this->txnId . (int) (microtime(true) * 1000));
1436 $this->txnId++;
1437  
1438 return $txnId;
1439 }
1440  
1441 /**
1442 * Determine user_id for authenticated user.
1443 *
1444 * @return array
1445 * @throws MatrixException
1446 * @throws MatrixHttpLibException
1447 * @throws MatrixRequestException
1448 */
1449 public function whoami(): array {
1450 if (!$this->token) {
1451 throw new MatrixException('Authentication required.');
1452 }
1453  
1454 return $this->send('GET', '/account/whoami');
1455 }
1456  
1457 public function setToken(?string $token) {
1458 $this->token = $token;
1459 }
1460  
1461  
1462 }